80da99a0f3e3a90b682c823aa83f782d410c7369
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_dae.c
1 /*
2 * Copyright (C) 2012 Martin Willi
3 * Copyright (C) 2012 revosec AG
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_dae.h"
17
18 #include <radius_message.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <unistd.h>
24 #include <errno.h>
25
26 #include <daemon.h>
27 #include <threading/thread.h>
28 #include <processing/jobs/callback_job.h>
29 #include <processing/jobs/delete_ike_sa_job.h>
30
31 #define RADIUS_DAE_PORT 3799
32
33 typedef struct private_eap_radius_dae_t private_eap_radius_dae_t;
34
35 /**
36 * Private data of an eap_radius_dae_t object.
37 */
38 struct private_eap_radius_dae_t {
39
40 /**
41 * Public eap_radius_dae_t interface.
42 */
43 eap_radius_dae_t public;
44
45 /**
46 * RADIUS session state
47 */
48 eap_radius_accounting_t *accounting;
49
50 /**
51 * Socket to listen on authorization extension port
52 */
53 int fd;
54
55 /**
56 * RADIUS shared secret for DAE exchanges
57 */
58 chunk_t secret;
59
60 /**
61 * MD5 hasher
62 */
63 hasher_t *hasher;
64
65 /**
66 * HMAC MD5 signer, with secret set
67 */
68 signer_t *signer;
69
70 /**
71 * List of responses for retransmission, as entry_t
72 */
73 linked_list_t *responses;
74 };
75
76 /**
77 * Entry to store responses for retransmit
78 */
79 typedef struct {
80 /** stored response */
81 radius_message_t *response;
82 /** client that sent the request */
83 host_t *client;
84 } entry_t;
85
86 /**
87 * Clean up an entry
88 */
89 static void entry_destroy(entry_t *entry)
90 {
91 entry->response->destroy(entry->response);
92 entry->client->destroy(entry->client);
93 free(entry);
94 }
95
96 /**
97 * Save/Replace response for retransmission
98 */
99 static void save_retransmit(private_eap_radius_dae_t *this,
100 radius_message_t *response, host_t *client)
101 {
102 enumerator_t *enumerator;
103 entry_t *entry;
104 bool found = FALSE;
105
106 enumerator = this->responses->create_enumerator(this->responses);
107 while (enumerator->enumerate(enumerator, &entry))
108 {
109 if (client->equals(client, entry->client))
110 {
111 entry->response->destroy(entry->response);
112 entry->response = response;
113 found = TRUE;
114 break;
115 }
116 }
117 enumerator->destroy(enumerator);
118
119 if (!found)
120 {
121 INIT(entry,
122 .response = response,
123 .client = client->clone(client),
124 );
125 this->responses->insert_first(this->responses, entry);
126 }
127 }
128
129 /**
130 * Send a RADIUS message to client
131 */
132 static void send_message(private_eap_radius_dae_t *this,
133 radius_message_t *message, host_t *client)
134 {
135 chunk_t data;
136
137 data = message->get_encoding(message);
138 if (sendto(this->fd, data.ptr, data.len, 0, client->get_sockaddr(client),
139 *client->get_sockaddr_len(client)) != data.len)
140 {
141 DBG1(DBG_CFG, "sending RADIUS DAE response failed: %s", strerror(errno));
142 }
143 }
144
145 /**
146 * Check if we request is a retransmit, retransmit stored response
147 */
148 static bool send_retransmit(private_eap_radius_dae_t *this,
149 radius_message_t *request, host_t *client)
150 {
151 enumerator_t *enumerator;
152 entry_t *entry;
153 bool found = FALSE;
154
155 enumerator = this->responses->create_enumerator(this->responses);
156 while (enumerator->enumerate(enumerator, &entry))
157 {
158 if (client->equals(client, entry->client) &&
159 request->get_identifier(request) ==
160 entry->response->get_identifier(entry->response))
161 {
162 DBG1(DBG_CFG, "received retransmit of RADIUS %N, retransmitting %N "
163 "to %H", radius_message_code_names, request->get_code(request),
164 radius_message_code_names,
165 entry->response->get_code(entry->response), client);
166 send_message(this, entry->response, client);
167 found = TRUE;
168 break;
169 }
170 }
171 enumerator->destroy(enumerator);
172
173 return found;
174 }
175
176 /**
177 * Send an ACK/NAK response for a request
178 */
179 static void send_response(private_eap_radius_dae_t *this,
180 radius_message_t *request, radius_message_code_t code,
181 host_t *client)
182 {
183 radius_message_t *response;
184
185 response = radius_message_create(code);
186 response->set_identifier(response, request->get_identifier(request));
187 response->sign(response, request->get_authenticator(request),
188 this->secret, this->hasher, this->signer, NULL, FALSE);
189
190 send_message(this, response, client);
191 save_retransmit(this, response, client);
192 }
193
194 /**
195 * Add all IKE_SAs matching to user to a list
196 */
197 static void add_matching_ike_sas(linked_list_t *list, identification_t *user)
198 {
199 enumerator_t *enumerator;
200 ike_sa_t *ike_sa;
201 ike_sa_id_t *id;
202
203 enumerator = charon->ike_sa_manager->create_enumerator(
204 charon->ike_sa_manager, FALSE);
205 while (enumerator->enumerate(enumerator, &ike_sa))
206 {
207 if (user->matches(user, ike_sa->get_other_eap_id(ike_sa)))
208 {
209 id = ike_sa->get_id(ike_sa);
210 list->insert_last(list, id->clone(id));
211 }
212 }
213 enumerator->destroy(enumerator);
214 }
215
216 /**
217 * Get list of IKE_SAs matching a Disconnect/CoA request
218 */
219 static linked_list_t *get_matching_ike_sas(private_eap_radius_dae_t *this,
220 radius_message_t *request, host_t *client)
221 {
222 enumerator_t *enumerator;
223 identification_t *user;
224 linked_list_t *ids;
225 chunk_t data;
226 int type;
227
228 ids = linked_list_create();
229
230 enumerator = request->create_enumerator(request);
231 while (enumerator->enumerate(enumerator, &type, &data))
232 {
233 if (type == RAT_USER_NAME && data.len)
234 {
235 user = identification_create_from_data(data);
236 DBG1(DBG_CFG, "received RADIUS DAE %N for %Y from %H",
237 radius_message_code_names, request->get_code(request),
238 user, client);
239 add_matching_ike_sas(ids, user);
240 user->destroy(user);
241 }
242 }
243 enumerator->destroy(enumerator);
244
245 return ids;
246 }
247
248 /**
249 * Process a DAE disconnect request, send response
250 */
251 static void process_disconnect(private_eap_radius_dae_t *this,
252 radius_message_t *request, host_t *client)
253 {
254 enumerator_t *enumerator;
255 linked_list_t *ids;
256 ike_sa_id_t *id;
257
258 ids = get_matching_ike_sas(this, request, client);
259
260 if (ids->get_count(ids))
261 {
262 DBG1(DBG_CFG, "closing %d IKE_SA%s matching %N, sending %N",
263 ids->get_count(ids), ids->get_count(ids) > 1 ? "s" : "",
264 radius_message_code_names, RMC_DISCONNECT_REQUEST,
265 radius_message_code_names, RMC_DISCONNECT_ACK);
266
267 enumerator = ids->create_enumerator(ids);
268 while (enumerator->enumerate(enumerator, &id))
269 {
270 lib->processor->queue_job(lib->processor, (job_t*)
271 delete_ike_sa_job_create(id, TRUE));
272 }
273 enumerator->destroy(enumerator);
274
275 send_response(this, request, RMC_DISCONNECT_ACK, client);
276 }
277 else
278 {
279 DBG1(DBG_CFG, "no IKE_SA matches %N, sending %N",
280 radius_message_code_names, RMC_DISCONNECT_REQUEST,
281 radius_message_code_names, RMC_DISCONNECT_NAK);
282 send_response(this, request, RMC_DISCONNECT_NAK, client);
283 }
284 ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy));
285 }
286
287 /**
288 * Apply a new lifetime to an IKE_SA
289 */
290 static void apply_lifetime(private_eap_radius_dae_t *this, ike_sa_id_t *id,
291 u_int32_t lifetime)
292 {
293 ike_sa_t *ike_sa;
294
295 ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id);
296 if (ike_sa)
297 {
298 if (ike_sa->set_auth_lifetime(ike_sa, lifetime) == DESTROY_ME)
299 {
300 charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
301 ike_sa);
302 }
303 else
304 {
305 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
306 }
307 }
308 }
309
310 /**
311 * Process a DAE CoA request, send response
312 */
313 static void process_coa(private_eap_radius_dae_t *this,
314 radius_message_t *request, host_t *client)
315 {
316 enumerator_t *enumerator;
317 linked_list_t *ids;
318 ike_sa_id_t *id;
319 chunk_t data;
320 int type;
321 u_int32_t lifetime = 0;
322 bool lifetime_seen = FALSE;
323
324 ids = get_matching_ike_sas(this, request, client);
325
326 if (ids->get_count(ids))
327 {
328 enumerator = request->create_enumerator(request);
329 while (enumerator->enumerate(enumerator, &type, &data))
330 {
331 if (type == RAT_SESSION_TIMEOUT && data.len == 4)
332 {
333 lifetime = untoh32(data.ptr);
334 lifetime_seen = TRUE;
335 break;
336 }
337 }
338 enumerator->destroy(enumerator);
339
340 if (lifetime_seen)
341 {
342 DBG1(DBG_CFG, "applying %us lifetime to %d IKE_SA%s matching %N, "
343 "sending %N", lifetime, ids->get_count(ids),
344 ids->get_count(ids) > 1 ? "s" : "",
345 radius_message_code_names, RMC_COA_REQUEST,
346 radius_message_code_names, RMC_COA_ACK);
347
348 enumerator = ids->create_enumerator(ids);
349 while (enumerator->enumerate(enumerator, &id))
350 {
351 apply_lifetime(this, id, lifetime);
352 }
353 enumerator->destroy(enumerator);
354 send_response(this, request, RMC_COA_ACK, client);
355 }
356 else
357 {
358 DBG1(DBG_CFG, "no Session-Timeout attribute found in %N, sending %N",
359 radius_message_code_names, RMC_COA_REQUEST,
360 radius_message_code_names, RMC_COA_NAK);
361 send_response(this, request, RMC_COA_NAK, client);
362 }
363 }
364 else
365 {
366 DBG1(DBG_CFG, "no IKE_SA matches %N, sending %N",
367 radius_message_code_names, RMC_COA_REQUEST,
368 radius_message_code_names, RMC_COA_NAK);
369 send_response(this, request, RMC_COA_NAK, client);
370 }
371 ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy));
372 }
373
374 /**
375 * Receive RADIUS DAE requests
376 */
377 static job_requeue_t receive(private_eap_radius_dae_t *this)
378 {
379 struct sockaddr_storage addr;
380 socklen_t addr_len = sizeof(addr);
381 radius_message_t *request;
382 char buf[2048];
383 ssize_t len;
384 bool oldstate;
385 host_t *client;
386
387 oldstate = thread_cancelability(TRUE);
388 len = recvfrom(this->fd, buf, sizeof(buf), 0,
389 (struct sockaddr*)&addr, &addr_len);
390 thread_cancelability(oldstate);
391
392 if (len > 0)
393 {
394 request = radius_message_parse(chunk_create(buf, len));
395 if (request)
396 {
397 client = host_create_from_sockaddr((struct sockaddr*)&addr);
398 if (client)
399 {
400 if (!send_retransmit(this, request, client))
401 {
402 if (request->verify(request, NULL, this->secret,
403 this->hasher, this->signer))
404 {
405 switch (request->get_code(request))
406 {
407 case RMC_DISCONNECT_REQUEST:
408 process_disconnect(this, request, client);
409 break;
410 case RMC_COA_REQUEST:
411 process_coa(this, request, client);
412 break;
413 default:
414 DBG1(DBG_CFG, "ignoring unsupported RADIUS DAE "
415 "%N message from %H",
416 radius_message_code_names,
417 request->get_code(request), client);
418 break;
419 }
420 }
421 }
422 client->destroy(client);
423 }
424 request->destroy(request);
425 }
426 else
427 {
428 DBG1(DBG_NET, "ignoring invalid RADIUS DAE request");
429 }
430 }
431 else
432 {
433 DBG1(DBG_NET, "receiving RADIUS DAE request failed: %s", strerror(errno));
434 }
435 return JOB_REQUEUE_DIRECT;
436 }
437
438 /**
439 * Open DAE socket
440 */
441 static bool open_socket(private_eap_radius_dae_t *this)
442 {
443 host_t *host;
444
445 this->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
446 if (this->fd == -1)
447 {
448 DBG1(DBG_CFG, "unable to open RADIUS DAE socket: %s", strerror(errno));
449 return FALSE;
450 }
451
452 host = host_create_from_string(
453 lib->settings->get_str(lib->settings,
454 "%s.plugins.eap-radius.dae.listen", "0.0.0.0",
455 charon->name),
456 lib->settings->get_int(lib->settings,
457 "%s.plugins.eap-radius.dae.port", RADIUS_DAE_PORT,
458 charon->name));
459 if (!host)
460 {
461 DBG1(DBG_CFG, "invalid RADIUS DAE listen address");
462 return FALSE;
463 }
464
465 if (bind(this->fd, host->get_sockaddr(host),
466 *host->get_sockaddr_len(host)) == -1)
467 {
468 DBG1(DBG_CFG, "unable to bind RADIUS DAE socket: %s", strerror(errno));
469 host->destroy(host);
470 return FALSE;
471 }
472 host->destroy(host);
473 return TRUE;
474 }
475
476 METHOD(eap_radius_dae_t, destroy, void,
477 private_eap_radius_dae_t *this)
478 {
479 if (this->fd != -1)
480 {
481 close(this->fd);
482 }
483 DESTROY_IF(this->signer);
484 DESTROY_IF(this->hasher);
485 this->responses->destroy_function(this->responses, (void*)entry_destroy);
486 free(this);
487 }
488
489 /**
490 * See header
491 */
492 eap_radius_dae_t *eap_radius_dae_create(eap_radius_accounting_t *accounting)
493 {
494 private_eap_radius_dae_t *this;
495
496 INIT(this,
497 .public = {
498 .destroy = _destroy,
499 },
500 .accounting = accounting,
501 .fd = -1,
502 .secret = {
503 .ptr = lib->settings->get_str(lib->settings,
504 "%s.plugins.eap-radius.dae.secret", NULL,
505 charon->name),
506 },
507 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
508 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
509 .responses = linked_list_create(),
510 );
511
512 if (!this->hasher || !this->signer)
513 {
514 destroy(this);
515 return NULL;
516 }
517 if (!this->secret.ptr)
518 {
519 DBG1(DBG_CFG, "missing RADIUS DAE secret, disabled");
520 destroy(this);
521 return NULL;
522 }
523 this->secret.len = strlen(this->secret.ptr);
524 this->signer->set_key(this->signer, this->secret);
525
526 if (!open_socket(this))
527 {
528 destroy(this);
529 return NULL;
530 }
531
532 lib->processor->queue_job(lib->processor,
533 (job_t*)callback_job_create_with_prio((callback_job_cb_t)receive,
534 this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
535
536 return &this->public;
537 }