Add a return value to radius_message_t.sign()
[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 if (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 else
194 {
195 response->destroy(response);
196 }
197 }
198
199 /**
200 * Add all IKE_SAs matching to user to a list
201 */
202 static void add_matching_ike_sas(linked_list_t *list, identification_t *user)
203 {
204 enumerator_t *enumerator;
205 ike_sa_t *ike_sa;
206 ike_sa_id_t *id;
207
208 enumerator = charon->ike_sa_manager->create_enumerator(
209 charon->ike_sa_manager, FALSE);
210 while (enumerator->enumerate(enumerator, &ike_sa))
211 {
212 if (user->matches(user, ike_sa->get_other_eap_id(ike_sa)))
213 {
214 id = ike_sa->get_id(ike_sa);
215 list->insert_last(list, id->clone(id));
216 }
217 }
218 enumerator->destroy(enumerator);
219 }
220
221 /**
222 * Get list of IKE_SAs matching a Disconnect/CoA request
223 */
224 static linked_list_t *get_matching_ike_sas(private_eap_radius_dae_t *this,
225 radius_message_t *request, host_t *client)
226 {
227 enumerator_t *enumerator;
228 identification_t *user;
229 linked_list_t *ids;
230 chunk_t data;
231 int type;
232
233 ids = linked_list_create();
234
235 enumerator = request->create_enumerator(request);
236 while (enumerator->enumerate(enumerator, &type, &data))
237 {
238 if (type == RAT_USER_NAME && data.len)
239 {
240 user = identification_create_from_data(data);
241 DBG1(DBG_CFG, "received RADIUS DAE %N for %Y from %H",
242 radius_message_code_names, request->get_code(request),
243 user, client);
244 add_matching_ike_sas(ids, user);
245 user->destroy(user);
246 }
247 }
248 enumerator->destroy(enumerator);
249
250 return ids;
251 }
252
253 /**
254 * Process a DAE disconnect request, send response
255 */
256 static void process_disconnect(private_eap_radius_dae_t *this,
257 radius_message_t *request, host_t *client)
258 {
259 enumerator_t *enumerator;
260 linked_list_t *ids;
261 ike_sa_id_t *id;
262
263 ids = get_matching_ike_sas(this, request, client);
264
265 if (ids->get_count(ids))
266 {
267 DBG1(DBG_CFG, "closing %d IKE_SA%s matching %N, sending %N",
268 ids->get_count(ids), ids->get_count(ids) > 1 ? "s" : "",
269 radius_message_code_names, RMC_DISCONNECT_REQUEST,
270 radius_message_code_names, RMC_DISCONNECT_ACK);
271
272 enumerator = ids->create_enumerator(ids);
273 while (enumerator->enumerate(enumerator, &id))
274 {
275 lib->processor->queue_job(lib->processor, (job_t*)
276 delete_ike_sa_job_create(id, TRUE));
277 }
278 enumerator->destroy(enumerator);
279
280 send_response(this, request, RMC_DISCONNECT_ACK, client);
281 }
282 else
283 {
284 DBG1(DBG_CFG, "no IKE_SA matches %N, sending %N",
285 radius_message_code_names, RMC_DISCONNECT_REQUEST,
286 radius_message_code_names, RMC_DISCONNECT_NAK);
287 send_response(this, request, RMC_DISCONNECT_NAK, client);
288 }
289 ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy));
290 }
291
292 /**
293 * Apply a new lifetime to an IKE_SA
294 */
295 static void apply_lifetime(private_eap_radius_dae_t *this, ike_sa_id_t *id,
296 u_int32_t lifetime)
297 {
298 ike_sa_t *ike_sa;
299
300 ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id);
301 if (ike_sa)
302 {
303 if (ike_sa->set_auth_lifetime(ike_sa, lifetime) == DESTROY_ME)
304 {
305 charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
306 ike_sa);
307 }
308 else
309 {
310 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
311 }
312 }
313 }
314
315 /**
316 * Process a DAE CoA request, send response
317 */
318 static void process_coa(private_eap_radius_dae_t *this,
319 radius_message_t *request, host_t *client)
320 {
321 enumerator_t *enumerator;
322 linked_list_t *ids;
323 ike_sa_id_t *id;
324 chunk_t data;
325 int type;
326 u_int32_t lifetime = 0;
327 bool lifetime_seen = FALSE;
328
329 ids = get_matching_ike_sas(this, request, client);
330
331 if (ids->get_count(ids))
332 {
333 enumerator = request->create_enumerator(request);
334 while (enumerator->enumerate(enumerator, &type, &data))
335 {
336 if (type == RAT_SESSION_TIMEOUT && data.len == 4)
337 {
338 lifetime = untoh32(data.ptr);
339 lifetime_seen = TRUE;
340 break;
341 }
342 }
343 enumerator->destroy(enumerator);
344
345 if (lifetime_seen)
346 {
347 DBG1(DBG_CFG, "applying %us lifetime to %d IKE_SA%s matching %N, "
348 "sending %N", lifetime, ids->get_count(ids),
349 ids->get_count(ids) > 1 ? "s" : "",
350 radius_message_code_names, RMC_COA_REQUEST,
351 radius_message_code_names, RMC_COA_ACK);
352
353 enumerator = ids->create_enumerator(ids);
354 while (enumerator->enumerate(enumerator, &id))
355 {
356 apply_lifetime(this, id, lifetime);
357 }
358 enumerator->destroy(enumerator);
359 send_response(this, request, RMC_COA_ACK, client);
360 }
361 else
362 {
363 DBG1(DBG_CFG, "no Session-Timeout attribute found in %N, sending %N",
364 radius_message_code_names, RMC_COA_REQUEST,
365 radius_message_code_names, RMC_COA_NAK);
366 send_response(this, request, RMC_COA_NAK, client);
367 }
368 }
369 else
370 {
371 DBG1(DBG_CFG, "no IKE_SA matches %N, sending %N",
372 radius_message_code_names, RMC_COA_REQUEST,
373 radius_message_code_names, RMC_COA_NAK);
374 send_response(this, request, RMC_COA_NAK, client);
375 }
376 ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy));
377 }
378
379 /**
380 * Receive RADIUS DAE requests
381 */
382 static job_requeue_t receive(private_eap_radius_dae_t *this)
383 {
384 struct sockaddr_storage addr;
385 socklen_t addr_len = sizeof(addr);
386 radius_message_t *request;
387 char buf[2048];
388 ssize_t len;
389 bool oldstate;
390 host_t *client;
391
392 oldstate = thread_cancelability(TRUE);
393 len = recvfrom(this->fd, buf, sizeof(buf), 0,
394 (struct sockaddr*)&addr, &addr_len);
395 thread_cancelability(oldstate);
396
397 if (len > 0)
398 {
399 request = radius_message_parse(chunk_create(buf, len));
400 if (request)
401 {
402 client = host_create_from_sockaddr((struct sockaddr*)&addr);
403 if (client)
404 {
405 if (!send_retransmit(this, request, client))
406 {
407 if (request->verify(request, NULL, this->secret,
408 this->hasher, this->signer))
409 {
410 switch (request->get_code(request))
411 {
412 case RMC_DISCONNECT_REQUEST:
413 process_disconnect(this, request, client);
414 break;
415 case RMC_COA_REQUEST:
416 process_coa(this, request, client);
417 break;
418 default:
419 DBG1(DBG_CFG, "ignoring unsupported RADIUS DAE "
420 "%N message from %H",
421 radius_message_code_names,
422 request->get_code(request), client);
423 break;
424 }
425 }
426 }
427 client->destroy(client);
428 }
429 request->destroy(request);
430 }
431 else
432 {
433 DBG1(DBG_NET, "ignoring invalid RADIUS DAE request");
434 }
435 }
436 else
437 {
438 DBG1(DBG_NET, "receiving RADIUS DAE request failed: %s", strerror(errno));
439 }
440 return JOB_REQUEUE_DIRECT;
441 }
442
443 /**
444 * Open DAE socket
445 */
446 static bool open_socket(private_eap_radius_dae_t *this)
447 {
448 host_t *host;
449
450 this->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
451 if (this->fd == -1)
452 {
453 DBG1(DBG_CFG, "unable to open RADIUS DAE socket: %s", strerror(errno));
454 return FALSE;
455 }
456
457 host = host_create_from_string(
458 lib->settings->get_str(lib->settings,
459 "%s.plugins.eap-radius.dae.listen", "0.0.0.0",
460 charon->name),
461 lib->settings->get_int(lib->settings,
462 "%s.plugins.eap-radius.dae.port", RADIUS_DAE_PORT,
463 charon->name));
464 if (!host)
465 {
466 DBG1(DBG_CFG, "invalid RADIUS DAE listen address");
467 return FALSE;
468 }
469
470 if (bind(this->fd, host->get_sockaddr(host),
471 *host->get_sockaddr_len(host)) == -1)
472 {
473 DBG1(DBG_CFG, "unable to bind RADIUS DAE socket: %s", strerror(errno));
474 host->destroy(host);
475 return FALSE;
476 }
477 host->destroy(host);
478 return TRUE;
479 }
480
481 METHOD(eap_radius_dae_t, destroy, void,
482 private_eap_radius_dae_t *this)
483 {
484 if (this->fd != -1)
485 {
486 close(this->fd);
487 }
488 DESTROY_IF(this->signer);
489 DESTROY_IF(this->hasher);
490 this->responses->destroy_function(this->responses, (void*)entry_destroy);
491 free(this);
492 }
493
494 /**
495 * See header
496 */
497 eap_radius_dae_t *eap_radius_dae_create(eap_radius_accounting_t *accounting)
498 {
499 private_eap_radius_dae_t *this;
500
501 INIT(this,
502 .public = {
503 .destroy = _destroy,
504 },
505 .accounting = accounting,
506 .fd = -1,
507 .secret = {
508 .ptr = lib->settings->get_str(lib->settings,
509 "%s.plugins.eap-radius.dae.secret", NULL,
510 charon->name),
511 },
512 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
513 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
514 .responses = linked_list_create(),
515 );
516
517 if (!this->hasher || !this->signer)
518 {
519 destroy(this);
520 return NULL;
521 }
522 if (!this->secret.ptr)
523 {
524 DBG1(DBG_CFG, "missing RADIUS DAE secret, disabled");
525 destroy(this);
526 return NULL;
527 }
528 this->secret.len = strlen(this->secret.ptr);
529 this->signer->set_key(this->signer, this->secret);
530
531 if (!open_socket(this))
532 {
533 destroy(this);
534 return NULL;
535 }
536
537 lib->processor->queue_job(lib->processor,
538 (job_t*)callback_job_create_with_prio((callback_job_cb_t)receive,
539 this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
540
541 return &this->public;
542 }