Moved generic RADIUS protocol support to a dedicated libradius
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_plugin.c
1 /*
2 * Copyright (C) 2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "eap_radius_plugin.h"
17
18 #include "eap_radius.h"
19 #include "eap_radius_accounting.h"
20 #include "eap_radius_dae.h"
21 #include "eap_radius_forward.h"
22
23 #include <radius_client.h>
24 #include <radius_server.h>
25
26 #include <daemon.h>
27 #include <threading/rwlock.h>
28
29 /**
30 * Default RADIUS server port for authentication
31 */
32 #define AUTH_PORT 1812
33
34 /**
35 * Default RADIUS server port for accounting
36 */
37 #define ACCT_PORT 1813
38
39 typedef struct private_eap_radius_plugin_t private_eap_radius_plugin_t;
40
41 /**
42 * Private data of an eap_radius_plugin_t object.
43 */
44 struct private_eap_radius_plugin_t {
45
46 /**
47 * Public radius_plugin_t interface.
48 */
49 eap_radius_plugin_t public;
50
51 /**
52 * List of RADIUS servers
53 */
54 linked_list_t *servers;
55
56 /**
57 * Lock for server list
58 */
59 rwlock_t *lock;
60
61 /**
62 * RADIUS sessions for accounting
63 */
64 eap_radius_accounting_t *accounting;
65
66 /**
67 * Dynamic authorization extensions
68 */
69 eap_radius_dae_t *dae;
70
71 /**
72 * RADIUS <-> IKE attribute forwarding
73 */
74 eap_radius_forward_t *forward;
75 };
76
77 /**
78 * Instance of the EAP plugin
79 */
80 static private_eap_radius_plugin_t *instance = NULL;
81
82 /**
83 * Load RADIUS servers from configuration
84 */
85 static void load_servers(private_eap_radius_plugin_t *this)
86 {
87 enumerator_t *enumerator;
88 radius_server_t *server;
89 char *nas_identifier, *secret, *address, *section;
90 int auth_port, acct_port, sockets, preference;
91
92 address = lib->settings->get_str(lib->settings,
93 "charon.plugins.eap-radius.server", NULL);
94 if (address)
95 { /* legacy configuration */
96 secret = lib->settings->get_str(lib->settings,
97 "charon.plugins.eap-radius.secret", NULL);
98 if (!secret)
99 {
100 DBG1(DBG_CFG, "no RADUIS secret defined");
101 return;
102 }
103 nas_identifier = lib->settings->get_str(lib->settings,
104 "charon.plugins.eap-radius.nas_identifier", "strongSwan");
105 auth_port = lib->settings->get_int(lib->settings,
106 "charon.plugins.eap-radius.port", AUTH_PORT);
107 sockets = lib->settings->get_int(lib->settings,
108 "charon.plugins.eap-radius.sockets", 1);
109 server = radius_server_create(address, address, auth_port, ACCT_PORT,
110 nas_identifier, secret, sockets, 0);
111 if (!server)
112 {
113 DBG1(DBG_CFG, "no RADUIS server defined");
114 return;
115 }
116 this->servers->insert_last(this->servers, server);
117 return;
118 }
119
120 enumerator = lib->settings->create_section_enumerator(lib->settings,
121 "charon.plugins.eap-radius.servers");
122 while (enumerator->enumerate(enumerator, &section))
123 {
124 address = lib->settings->get_str(lib->settings,
125 "charon.plugins.eap-radius.servers.%s.address", NULL, section);
126 if (!address)
127 {
128 DBG1(DBG_CFG, "RADIUS server '%s' misses address, skipped", section);
129 continue;
130 }
131 secret = lib->settings->get_str(lib->settings,
132 "charon.plugins.eap-radius.servers.%s.secret", NULL, section);
133 if (!secret)
134 {
135 DBG1(DBG_CFG, "RADIUS server '%s' misses secret, skipped", section);
136 continue;
137 }
138 nas_identifier = lib->settings->get_str(lib->settings,
139 "charon.plugins.eap-radius.servers.%s.nas_identifier",
140 "strongSwan", section);
141 auth_port = lib->settings->get_int(lib->settings,
142 "charon.plugins.eap-radius.servers.%s.auth_port",
143 lib->settings->get_int(lib->settings,
144 "charon.plugins.eap-radius.servers.%s.port",
145 AUTH_PORT, section),
146 section);
147 acct_port = lib->settings->get_int(lib->settings,
148 "charon.plugins.eap-radius.servers.%s.acct_port", ACCT_PORT, section);
149 sockets = lib->settings->get_int(lib->settings,
150 "charon.plugins.eap-radius.servers.%s.sockets", 1, section);
151 preference = lib->settings->get_int(lib->settings,
152 "charon.plugins.eap-radius.servers.%s.preference", 0, section);
153 server = radius_server_create(section, address, auth_port, acct_port,
154 nas_identifier, secret, sockets, preference);
155 if (!server)
156 {
157 DBG1(DBG_CFG, "loading RADIUS server '%s' failed, skipped", section);
158 continue;
159 }
160 this->servers->insert_last(this->servers, server);
161 }
162 enumerator->destroy(enumerator);
163
164 DBG1(DBG_CFG, "loaded %d RADIUS server configuration%s",
165 this->servers->get_count(this->servers),
166 this->servers->get_count(this->servers) == 1 ? "" : "s");
167 }
168
169 METHOD(plugin_t, get_name, char*,
170 private_eap_radius_plugin_t *this)
171 {
172 return "eap-radius";
173 }
174
175 METHOD(plugin_t, get_features, int,
176 eap_radius_plugin_t *this, plugin_feature_t *features[])
177 {
178 static plugin_feature_t f[] = {
179 PLUGIN_CALLBACK(eap_method_register, eap_radius_create),
180 PLUGIN_PROVIDE(EAP_SERVER, EAP_RADIUS),
181 PLUGIN_DEPENDS(HASHER, HASH_MD5),
182 PLUGIN_DEPENDS(SIGNER, AUTH_HMAC_MD5_128),
183 PLUGIN_DEPENDS(RNG, RNG_WEAK),
184 };
185 *features = f;
186 return countof(f);
187 }
188
189 METHOD(plugin_t, reload, bool,
190 private_eap_radius_plugin_t *this)
191 {
192 this->lock->write_lock(this->lock);
193 this->servers->destroy_offset(this->servers,
194 offsetof(radius_server_t, destroy));
195 this->servers = linked_list_create();
196 load_servers(this);
197 this->lock->unlock(this->lock);
198 return TRUE;
199 }
200
201 METHOD(plugin_t, destroy, void,
202 private_eap_radius_plugin_t *this)
203 {
204 if (this->forward)
205 {
206 charon->bus->remove_listener(charon->bus, &this->forward->listener);
207 this->forward->destroy(this->forward);
208 }
209 DESTROY_IF(this->dae);
210 this->servers->destroy_offset(this->servers,
211 offsetof(radius_server_t, destroy));
212 this->lock->destroy(this->lock);
213 charon->bus->remove_listener(charon->bus, &this->accounting->listener);
214 this->accounting->destroy(this->accounting);
215 free(this);
216 instance = NULL;
217 }
218
219 /*
220 * see header file
221 */
222 plugin_t *eap_radius_plugin_create()
223 {
224 private_eap_radius_plugin_t *this;
225
226 INIT(this,
227 .public = {
228 .plugin = {
229 .get_name = _get_name,
230 .get_features = _get_features,
231 .reload = _reload,
232 .destroy = _destroy,
233 },
234 },
235 .servers = linked_list_create(),
236 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
237 .accounting = eap_radius_accounting_create(),
238 .forward = eap_radius_forward_create(),
239 );
240
241 load_servers(this);
242 instance = this;
243
244 if (lib->settings->get_bool(lib->settings,
245 "charon.plugins.eap-radius.accounting", FALSE))
246 {
247 charon->bus->add_listener(charon->bus, &this->accounting->listener);
248 }
249 if (lib->settings->get_bool(lib->settings,
250 "charon.plugins.eap-radius.dae.enable", FALSE))
251 {
252 this->dae = eap_radius_dae_create(this->accounting);
253 }
254 if (this->forward)
255 {
256 charon->bus->add_listener(charon->bus, &this->forward->listener);
257 }
258
259 return &this->public.plugin;
260 }
261
262 /**
263 * See header
264 */
265 radius_client_t *eap_radius_create_client()
266 {
267 if (instance)
268 {
269 enumerator_t *enumerator;
270 radius_server_t *server, *selected = NULL;
271 int current, best = -1;
272
273 instance->lock->read_lock(instance->lock);
274 enumerator = instance->servers->create_enumerator(instance->servers);
275 while (enumerator->enumerate(enumerator, &server))
276 {
277 current = server->get_preference(server);
278 if (current > best ||
279 /* for two with equal preference, 50-50 chance */
280 (current == best && random() % 2 == 0))
281 {
282 DBG2(DBG_CFG, "RADIUS server '%s' is candidate: %d",
283 server->get_name(server), current);
284 best = current;
285 DESTROY_IF(selected);
286 selected = server->get_ref(server);
287 }
288 else
289 {
290 DBG2(DBG_CFG, "RADIUS server '%s' skipped: %d",
291 server->get_name(server), current);
292 }
293 }
294 enumerator->destroy(enumerator);
295 instance->lock->unlock(instance->lock);
296
297 if (selected)
298 {
299 return radius_client_create(selected);
300 }
301 }
302 return NULL;
303 }
304