eap-radius: support plain XAuth RADIUS authentication using User-Password
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_plugin.c
1 /*
2 * Copyright (C) 2013 Tobias Brunner
3 * Copyright (C) 2009 Martin Willi
4 * Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "eap_radius_plugin.h"
18
19 #include "eap_radius.h"
20 #include "eap_radius_xauth.h"
21 #include "eap_radius_accounting.h"
22 #include "eap_radius_dae.h"
23 #include "eap_radius_forward.h"
24 #include "eap_radius_provider.h"
25
26 #include <radius_client.h>
27 #include <radius_config.h>
28
29 #include <hydra.h>
30 #include <threading/rwlock.h>
31 #include <processing/jobs/callback_job.h>
32 #include <processing/jobs/delete_ike_sa_job.h>
33
34 /**
35 * Default RADIUS server port for authentication
36 */
37 #define AUTH_PORT 1812
38
39 /**
40 * Default RADIUS server port for accounting
41 */
42 #define ACCT_PORT 1813
43
44 typedef struct private_eap_radius_plugin_t private_eap_radius_plugin_t;
45
46 /**
47 * Private data of an eap_radius_plugin_t object.
48 */
49 struct private_eap_radius_plugin_t {
50
51 /**
52 * Public radius_plugin_t interface.
53 */
54 eap_radius_plugin_t public;
55
56 /**
57 * List of RADIUS server configurations
58 */
59 linked_list_t *configs;
60
61 /**
62 * Lock for configs list
63 */
64 rwlock_t *lock;
65
66 /**
67 * RADIUS sessions for accounting
68 */
69 eap_radius_accounting_t *accounting;
70
71 /**
72 * IKE attribute provider
73 */
74 eap_radius_provider_t *provider;
75
76 /**
77 * Dynamic authorization extensions
78 */
79 eap_radius_dae_t *dae;
80
81 /**
82 * RADIUS <-> IKE attribute forwarding
83 */
84 eap_radius_forward_t *forward;
85 };
86
87 /**
88 * Instance of the EAP plugin
89 */
90 static private_eap_radius_plugin_t *instance = NULL;
91
92 /**
93 * Load RADIUS servers from configuration
94 */
95 static void load_configs(private_eap_radius_plugin_t *this)
96 {
97 enumerator_t *enumerator;
98 radius_config_t *config;
99 char *nas_identifier, *secret, *address, *section;
100 int auth_port, acct_port, sockets, preference;
101
102 address = lib->settings->get_str(lib->settings,
103 "%s.plugins.eap-radius.server", NULL, charon->name);
104 if (address)
105 { /* legacy configuration */
106 secret = lib->settings->get_str(lib->settings,
107 "%s.plugins.eap-radius.secret", NULL, charon->name);
108 if (!secret)
109 {
110 DBG1(DBG_CFG, "no RADIUS secret defined");
111 return;
112 }
113 nas_identifier = lib->settings->get_str(lib->settings,
114 "%s.plugins.eap-radius.nas_identifier", "strongSwan",
115 charon->name);
116 auth_port = lib->settings->get_int(lib->settings,
117 "%s.plugins.eap-radius.port", AUTH_PORT, charon->name);
118 sockets = lib->settings->get_int(lib->settings,
119 "%s.plugins.eap-radius.sockets", 1, charon->name);
120 config = radius_config_create(address, address, auth_port, ACCT_PORT,
121 nas_identifier, secret, sockets, 0);
122 if (!config)
123 {
124 DBG1(DBG_CFG, "no RADUIS server defined");
125 return;
126 }
127 this->configs->insert_last(this->configs, config);
128 return;
129 }
130
131 enumerator = lib->settings->create_section_enumerator(lib->settings,
132 "%s.plugins.eap-radius.servers", charon->name);
133 while (enumerator->enumerate(enumerator, &section))
134 {
135 address = lib->settings->get_str(lib->settings,
136 "%s.plugins.eap-radius.servers.%s.address", NULL,
137 charon->name, section);
138 if (!address)
139 {
140 DBG1(DBG_CFG, "RADIUS server '%s' misses address, skipped", section);
141 continue;
142 }
143 secret = lib->settings->get_str(lib->settings,
144 "%s.plugins.eap-radius.servers.%s.secret", NULL,
145 charon->name, section);
146 if (!secret)
147 {
148 DBG1(DBG_CFG, "RADIUS server '%s' misses secret, skipped", section);
149 continue;
150 }
151 nas_identifier = lib->settings->get_str(lib->settings,
152 "%s.plugins.eap-radius.servers.%s.nas_identifier", "strongSwan",
153 charon->name, section);
154 auth_port = lib->settings->get_int(lib->settings,
155 "%s.plugins.eap-radius.servers.%s.auth_port",
156 lib->settings->get_int(lib->settings,
157 "%s.plugins.eap-radius.servers.%s.port",
158 AUTH_PORT, charon->name, section),
159 charon->name, section);
160 acct_port = lib->settings->get_int(lib->settings,
161 "%s.plugins.eap-radius.servers.%s.acct_port", ACCT_PORT,
162 charon->name, section);
163 sockets = lib->settings->get_int(lib->settings,
164 "%s.plugins.eap-radius.servers.%s.sockets", 1,
165 charon->name, section);
166 preference = lib->settings->get_int(lib->settings,
167 "%s.plugins.eap-radius.servers.%s.preference", 0,
168 charon->name, section);
169 config = radius_config_create(section, address, auth_port, acct_port,
170 nas_identifier, secret, sockets, preference);
171 if (!config)
172 {
173 DBG1(DBG_CFG, "loading RADIUS server '%s' failed, skipped", section);
174 continue;
175 }
176 this->configs->insert_last(this->configs, config);
177 }
178 enumerator->destroy(enumerator);
179
180 DBG1(DBG_CFG, "loaded %d RADIUS server configuration%s",
181 this->configs->get_count(this->configs),
182 this->configs->get_count(this->configs) == 1 ? "" : "s");
183 }
184
185 METHOD(plugin_t, get_name, char*,
186 private_eap_radius_plugin_t *this)
187 {
188 return "eap-radius";
189 }
190
191 /**
192 * Register listener
193 */
194 static bool plugin_cb(private_eap_radius_plugin_t *this,
195 plugin_feature_t *feature, bool reg, void *cb_data)
196 {
197 if (reg)
198 {
199 this->accounting = eap_radius_accounting_create();
200 this->forward = eap_radius_forward_create();
201 this->provider = eap_radius_provider_create();
202
203 load_configs(this);
204
205 if (lib->settings->get_bool(lib->settings,
206 "%s.plugins.eap-radius.dae.enable", FALSE, charon->name))
207 {
208 this->dae = eap_radius_dae_create(this->accounting);
209 }
210 if (this->forward)
211 {
212 charon->bus->add_listener(charon->bus, &this->forward->listener);
213 }
214 hydra->attributes->add_provider(hydra->attributes,
215 &this->provider->provider);
216 }
217 else
218 {
219 hydra->attributes->remove_provider(hydra->attributes,
220 &this->provider->provider);
221 if (this->forward)
222 {
223 charon->bus->remove_listener(charon->bus, &this->forward->listener);
224 this->forward->destroy(this->forward);
225 }
226 DESTROY_IF(this->dae);
227 this->provider->destroy(this->provider);
228 this->accounting->destroy(this->accounting);
229 }
230 return TRUE;
231 }
232
233 METHOD(plugin_t, get_features, int,
234 private_eap_radius_plugin_t *this, plugin_feature_t *features[])
235 {
236 static plugin_feature_t f[] = {
237 PLUGIN_CALLBACK(eap_method_register, eap_radius_create),
238 PLUGIN_PROVIDE(EAP_SERVER, EAP_RADIUS),
239 PLUGIN_DEPENDS(CUSTOM, "eap-radius"),
240 PLUGIN_CALLBACK(xauth_method_register, eap_radius_xauth_create_server),
241 PLUGIN_PROVIDE(XAUTH_SERVER, "radius"),
242 PLUGIN_DEPENDS(CUSTOM, "eap-radius"),
243 PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL),
244 PLUGIN_PROVIDE(CUSTOM, "eap-radius"),
245 PLUGIN_DEPENDS(HASHER, HASH_MD5),
246 PLUGIN_DEPENDS(SIGNER, AUTH_HMAC_MD5_128),
247 PLUGIN_DEPENDS(RNG, RNG_WEAK),
248 };
249 *features = f;
250 return countof(f);
251 }
252
253 METHOD(plugin_t, reload, bool,
254 private_eap_radius_plugin_t *this)
255 {
256 this->lock->write_lock(this->lock);
257 this->configs->destroy_offset(this->configs,
258 offsetof(radius_config_t, destroy));
259 this->configs = linked_list_create();
260 load_configs(this);
261 this->lock->unlock(this->lock);
262 return TRUE;
263 }
264
265 METHOD(plugin_t, destroy, void,
266 private_eap_radius_plugin_t *this)
267 {
268 this->configs->destroy_offset(this->configs,
269 offsetof(radius_config_t, destroy));
270 this->lock->destroy(this->lock);
271 free(this);
272 instance = NULL;
273 }
274
275 /*
276 * see header file
277 */
278 plugin_t *eap_radius_plugin_create()
279 {
280 private_eap_radius_plugin_t *this;
281
282 INIT(this,
283 .public = {
284 .plugin = {
285 .get_name = _get_name,
286 .get_features = _get_features,
287 .reload = _reload,
288 .destroy = _destroy,
289 },
290 },
291 .configs = linked_list_create(),
292 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
293 );
294 instance = this;
295
296 return &this->public.plugin;
297 }
298
299 /**
300 * See header
301 */
302 radius_client_t *eap_radius_create_client()
303 {
304 if (instance)
305 {
306 enumerator_t *enumerator;
307 radius_config_t *config, *selected = NULL;
308 int current, best = -1;
309
310 instance->lock->read_lock(instance->lock);
311 enumerator = instance->configs->create_enumerator(instance->configs);
312 while (enumerator->enumerate(enumerator, &config))
313 {
314 current = config->get_preference(config);
315 if (current > best ||
316 /* for two with equal preference, 50-50 chance */
317 (current == best && random() % 2 == 0))
318 {
319 DBG2(DBG_CFG, "RADIUS server '%s' is candidate: %d",
320 config->get_name(config), current);
321 best = current;
322 DESTROY_IF(selected);
323 selected = config->get_ref(config);
324 }
325 else
326 {
327 DBG2(DBG_CFG, "RADIUS server '%s' skipped: %d",
328 config->get_name(config), current);
329 }
330 }
331 enumerator->destroy(enumerator);
332 instance->lock->unlock(instance->lock);
333
334 if (selected)
335 {
336 return radius_client_create(selected);
337 }
338 }
339 return NULL;
340 }
341
342 /**
343 * Job to delete all active IKE_SAs
344 */
345 static job_requeue_t delete_all_async(void *data)
346 {
347 enumerator_t *enumerator;
348 ike_sa_t *ike_sa;
349
350 enumerator = charon->ike_sa_manager->create_enumerator(
351 charon->ike_sa_manager, TRUE);
352 while (enumerator->enumerate(enumerator, &ike_sa))
353 {
354 lib->processor->queue_job(lib->processor,
355 (job_t*)delete_ike_sa_job_create(ike_sa->get_id(ike_sa), TRUE));
356 }
357 enumerator->destroy(enumerator);
358
359 return JOB_REQUEUE_NONE;
360 }
361
362 /**
363 * See header.
364 */
365 void eap_radius_handle_timeout(ike_sa_id_t *id)
366 {
367 charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING);
368
369 if (lib->settings->get_bool(lib->settings,
370 "%s.plugins.eap-radius.close_all_on_timeout",
371 FALSE, charon->name))
372 {
373 DBG1(DBG_CFG, "deleting all IKE_SAs after RADIUS timeout");
374 lib->processor->queue_job(lib->processor,
375 (job_t*)callback_job_create_with_prio(
376 (callback_job_cb_t)delete_all_async, NULL, NULL,
377 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
378 }
379 else if (id)
380 {
381 DBG1(DBG_CFG, "deleting IKE_SA after RADIUS timeout");
382 lib->processor->queue_job(lib->processor,
383 (job_t*)delete_ike_sa_job_create(id, TRUE));
384 }
385 }