6087a528f76110b5346ee9f058ff90a8138aa985
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius.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.h"
17 #include "eap_radius_plugin.h"
18 #include "eap_radius_forward.h"
19 #include "eap_radius_provider.h"
20 #include "eap_radius_accounting.h"
21
22 #include <radius_message.h>
23 #include <radius_client.h>
24 #include <bio/bio_writer.h>
25
26 #include <daemon.h>
27
28 typedef struct private_eap_radius_t private_eap_radius_t;
29
30 /**
31 * Private data of an eap_radius_t object.
32 */
33 struct private_eap_radius_t {
34
35 /**
36 * Public authenticator_t interface.
37 */
38 eap_radius_t public;
39
40 /**
41 * ID of the server
42 */
43 identification_t *server;
44
45 /**
46 * ID of the peer
47 */
48 identification_t *peer;
49
50 /**
51 * EAP method type we are proxying
52 */
53 eap_type_t type;
54
55 /**
56 * EAP vendor, if any
57 */
58 u_int32_t vendor;
59
60 /**
61 * EAP message identifier
62 */
63 u_int8_t identifier;
64
65 /**
66 * RADIUS client instance
67 */
68 radius_client_t *client;
69
70 /**
71 * TRUE to use EAP-Start, FALSE to send EAP-Identity Response directly
72 */
73 bool eap_start;
74
75 /**
76 * Prefix to prepend to EAP identity
77 */
78 char *id_prefix;
79 };
80
81 /**
82 * Add EAP-Identity to RADIUS message
83 */
84 static void add_eap_identity(private_eap_radius_t *this,
85 radius_message_t *request)
86 {
87 struct {
88 /** EAP code (REQUEST/RESPONSE) */
89 u_int8_t code;
90 /** unique message identifier */
91 u_int8_t identifier;
92 /** length of whole message */
93 u_int16_t length;
94 /** EAP type */
95 u_int8_t type;
96 /** identity data */
97 u_int8_t data[];
98 } __attribute__((__packed__)) *hdr;
99 chunk_t id, prefix;
100 size_t len;
101
102 id = this->peer->get_encoding(this->peer);
103 prefix = chunk_create(this->id_prefix, strlen(this->id_prefix));
104 len = sizeof(*hdr) + prefix.len + id.len;
105
106 hdr = alloca(len);
107 hdr->code = EAP_RESPONSE;
108 hdr->identifier = this->identifier;
109 hdr->length = htons(len);
110 hdr->type = EAP_IDENTITY;
111 memcpy(hdr->data, prefix.ptr, prefix.len);
112 memcpy(hdr->data + prefix.len, id.ptr, id.len);
113
114 request->add(request, RAT_EAP_MESSAGE, chunk_create((u_char*)hdr, len));
115 }
116
117 /**
118 * Copy EAP-Message attribute from RADIUS message to an new EAP payload
119 */
120 static bool radius2ike(private_eap_radius_t *this,
121 radius_message_t *msg, eap_payload_t **out)
122 {
123 enumerator_t *enumerator;
124 eap_payload_t *payload;
125 chunk_t data, message = chunk_empty;
126 int type;
127
128 enumerator = msg->create_enumerator(msg);
129 while (enumerator->enumerate(enumerator, &type, &data))
130 {
131 if (type == RAT_EAP_MESSAGE && data.len)
132 {
133 message = chunk_cat("mc", message, data);
134 }
135 }
136 enumerator->destroy(enumerator);
137 if (message.len)
138 {
139 *out = payload = eap_payload_create_data(message);
140
141 /* apply EAP method selected by RADIUS server */
142 this->type = payload->get_type(payload, &this->vendor);
143
144 DBG3(DBG_IKE, "%N payload %B", eap_type_names, this->type, &message);
145 free(message.ptr);
146 return TRUE;
147 }
148 return FALSE;
149 }
150
151 /**
152 * See header.
153 */
154 void eap_radius_build_attributes(radius_message_t *request)
155 {
156 ike_sa_t *ike_sa;
157 host_t *host;
158 char buf[40], *station_id_fmt;;
159 u_int32_t value;
160 chunk_t chunk;
161
162 /* virtual NAS-Port-Type */
163 value = htonl(5);
164 request->add(request, RAT_NAS_PORT_TYPE, chunk_from_thing(value));
165 /* framed ServiceType */
166 value = htonl(2);
167 request->add(request, RAT_SERVICE_TYPE, chunk_from_thing(value));
168
169 ike_sa = charon->bus->get_sa(charon->bus);
170 if (ike_sa)
171 {
172 value = htonl(ike_sa->get_unique_id(ike_sa));
173 request->add(request, RAT_NAS_PORT, chunk_from_thing(value));
174 request->add(request, RAT_NAS_PORT_ID,
175 chunk_from_str(ike_sa->get_name(ike_sa)));
176
177 host = ike_sa->get_my_host(ike_sa);
178 chunk = host->get_address(host);
179 switch (host->get_family(host))
180 {
181 case AF_INET:
182 request->add(request, RAT_NAS_IP_ADDRESS, chunk);
183 break;
184 case AF_INET6:
185 request->add(request, RAT_NAS_IPV6_ADDRESS, chunk);
186 default:
187 break;
188 }
189 if (lib->settings->get_bool(lib->settings,
190 "%s.plugins.eap-radius.station_id_with_port",
191 TRUE, charon->name))
192 {
193 station_id_fmt = "%#H";
194 }
195 else
196 {
197 station_id_fmt = "%H";
198 }
199 snprintf(buf, sizeof(buf), station_id_fmt, host);
200 request->add(request, RAT_CALLED_STATION_ID, chunk_from_str(buf));
201 host = ike_sa->get_other_host(ike_sa);
202 snprintf(buf, sizeof(buf), station_id_fmt, host);
203 request->add(request, RAT_CALLING_STATION_ID, chunk_from_str(buf));
204 }
205 }
206
207 /**
208 * Add a set of RADIUS attributes to a request message
209 */
210 static void add_radius_request_attrs(private_eap_radius_t *this,
211 radius_message_t *request)
212 {
213 chunk_t chunk;
214
215 chunk = chunk_from_str(this->id_prefix);
216 chunk = chunk_cata("cc", chunk, this->peer->get_encoding(this->peer));
217 request->add(request, RAT_USER_NAME, chunk);
218
219 eap_radius_build_attributes(request);
220 eap_radius_forward_from_ike(request);
221 }
222
223 METHOD(eap_method_t, initiate, status_t,
224 private_eap_radius_t *this, eap_payload_t **out)
225 {
226 radius_message_t *request, *response;
227 status_t status = FAILED;
228
229 request = radius_message_create(RMC_ACCESS_REQUEST);
230 add_radius_request_attrs(this, request);
231
232 if (this->eap_start)
233 {
234 request->add(request, RAT_EAP_MESSAGE, chunk_empty);
235 }
236 else
237 {
238 add_eap_identity(this, request);
239 }
240
241 response = this->client->request(this->client, request);
242 if (response)
243 {
244 eap_radius_forward_to_ike(response);
245 switch (response->get_code(response))
246 {
247 case RMC_ACCESS_CHALLENGE:
248 if (radius2ike(this, response, out))
249 {
250 status = NEED_MORE;
251 }
252 break;
253 case RMC_ACCESS_ACCEPT:
254 /* Microsoft RADIUS servers can run in a mode where they respond
255 * like this on the first request (i.e. without authentication),
256 * we treat this as Access-Reject */
257 case RMC_ACCESS_REJECT:
258 default:
259 DBG1(DBG_IKE, "RADIUS authentication of '%Y' failed",
260 this->peer);
261 break;
262 }
263 response->destroy(response);
264 }
265 else
266 {
267 eap_radius_handle_timeout(NULL);
268 }
269 request->destroy(request);
270 return status;
271 }
272
273 /**
274 * Handle the Class attribute as group membership information
275 */
276 static void process_class(radius_message_t *msg)
277 {
278 enumerator_t *enumerator;
279 chunk_t data;
280 int type;
281
282 enumerator = msg->create_enumerator(msg);
283 while (enumerator->enumerate(enumerator, &type, &data))
284 {
285 if (type == RAT_CLASS)
286 {
287 identification_t *id;
288 ike_sa_t *ike_sa;
289 auth_cfg_t *auth;
290
291 if (data.len >= 44)
292 { /* quirk: ignore long class attributes, these are used for
293 * other purposes by some RADIUS servers (such as NPS). */
294 continue;
295 }
296
297 ike_sa = charon->bus->get_sa(charon->bus);
298 if (ike_sa)
299 {
300 auth = ike_sa->get_auth_cfg(ike_sa, FALSE);
301 id = identification_create_from_data(data);
302 DBG1(DBG_CFG, "received group membership '%Y' from RADIUS", id);
303 auth->add(auth, AUTH_RULE_GROUP, id);
304 }
305 }
306 }
307 enumerator->destroy(enumerator);
308 }
309
310 /**
311 * Handle the Filter-Id attribute as IPsec CHILD_SA name
312 */
313 static void process_filter_id(radius_message_t *msg)
314 {
315 enumerator_t *enumerator;
316 int type;
317 u_int8_t tunnel_tag;
318 u_int32_t tunnel_type;
319 chunk_t filter_id = chunk_empty, data;
320 bool is_esp_tunnel = FALSE;
321
322 enumerator = msg->create_enumerator(msg);
323 while (enumerator->enumerate(enumerator, &type, &data))
324 {
325 switch (type)
326 {
327 case RAT_TUNNEL_TYPE:
328 if (data.len != 4)
329 {
330 continue;
331 }
332 tunnel_tag = *data.ptr;
333 *data.ptr = 0x00;
334 tunnel_type = untoh32(data.ptr);
335 DBG1(DBG_IKE, "received RADIUS attribute Tunnel-Type: "
336 "tag = %u, value = %u", tunnel_tag, tunnel_type);
337 is_esp_tunnel = (tunnel_type == RADIUS_TUNNEL_TYPE_ESP);
338 break;
339 case RAT_FILTER_ID:
340 filter_id = data;
341 DBG1(DBG_IKE, "received RADIUS attribute Filter-Id: "
342 "'%.*s'", (int)filter_id.len, filter_id.ptr);
343 break;
344 default:
345 break;
346 }
347 }
348 enumerator->destroy(enumerator);
349
350 if (is_esp_tunnel && filter_id.len)
351 {
352 identification_t *id;
353 ike_sa_t *ike_sa;
354 auth_cfg_t *auth;
355
356 ike_sa = charon->bus->get_sa(charon->bus);
357 if (ike_sa)
358 {
359 auth = ike_sa->get_auth_cfg(ike_sa, FALSE);
360 id = identification_create_from_data(filter_id);
361 auth->add(auth, AUTH_RULE_GROUP, id);
362 }
363 }
364 }
365
366 /**
367 * Handle Session-Timeout attribte and Interim updates
368 */
369 static void process_timeout(radius_message_t *msg)
370 {
371 enumerator_t *enumerator;
372 ike_sa_t *ike_sa;
373 chunk_t data;
374 int type;
375
376 ike_sa = charon->bus->get_sa(charon->bus);
377 if (ike_sa)
378 {
379 enumerator = msg->create_enumerator(msg);
380 while (enumerator->enumerate(enumerator, &type, &data))
381 {
382 if (type == RAT_SESSION_TIMEOUT && data.len == 4)
383 {
384 ike_sa->set_auth_lifetime(ike_sa, untoh32(data.ptr));
385 }
386 else if (type == RAT_ACCT_INTERIM_INTERVAL && data.len == 4)
387 {
388 eap_radius_accounting_start_interim(ike_sa, untoh32(data.ptr));
389 }
390 }
391 enumerator->destroy(enumerator);
392 }
393 }
394
395 /**
396 * Add a Cisco Unity configuration attribute
397 */
398 static void add_unity_attribute(eap_radius_provider_t *provider, u_int32_t id,
399 int type, chunk_t data)
400 {
401 switch (type)
402 {
403 case 15: /* CVPN3000-IPSec-Banner1 */
404 case 36: /* CVPN3000-IPSec-Banner2 */
405 provider->add_attribute(provider, id, UNITY_BANNER, data);
406 break;
407 case 28: /* CVPN3000-IPSec-Default-Domain */
408 provider->add_attribute(provider, id, UNITY_DEF_DOMAIN, data);
409 break;
410 case 29: /* CVPN3000-IPSec-Split-DNS-Names */
411 provider->add_attribute(provider, id, UNITY_SPLITDNS_NAME, data);
412 break;
413 }
414 }
415
416 /**
417 * Add a UNITY_LOCAL_LAN or UNITY_SPLIT_INCLUDE attribute
418 */
419 static void add_unity_split_attribute(eap_radius_provider_t *provider,
420 u_int32_t id, configuration_attribute_type_t type,
421 chunk_t data)
422 {
423 enumerator_t *enumerator;
424 bio_writer_t *writer;
425 char buffer[256], *token, *slash;
426
427 if (snprintf(buffer, sizeof(buffer), "%.*s", (int)data.len,
428 data.ptr) >= sizeof(buffer))
429 {
430 return;
431 }
432 writer = bio_writer_create(16); /* two IPv4 addresses and 6 bytes padding */
433 enumerator = enumerator_create_token(buffer, ",", " ");
434 while (enumerator->enumerate(enumerator, &token))
435 {
436 host_t *net, *mask = NULL;
437 chunk_t padding;
438
439 slash = strchr(token, '/');
440 if (slash)
441 {
442 *slash++ = '\0';
443 mask = host_create_from_string(slash, 0);
444 }
445 if (!mask)
446 { /* default to /32 */
447 mask = host_create_from_string("255.255.255.255", 0);
448 }
449 net = host_create_from_string(token, 0);
450 if (!net || net->get_family(net) != AF_INET ||
451 mask->get_family(mask) != AF_INET)
452 {
453 mask->destroy(mask);
454 DESTROY_IF(net);
455 continue;
456 }
457 writer->write_data(writer, net->get_address(net));
458 writer->write_data(writer, mask->get_address(mask));
459 padding = writer->skip(writer, 6); /* 6 bytes pdding */
460 memset(padding.ptr, 0, padding.len);
461 mask->destroy(mask);
462 net->destroy(net);
463 }
464 enumerator->destroy(enumerator);
465
466 data = writer->get_buf(writer);
467 if (data.len)
468 {
469 provider->add_attribute(provider, id, type, data);
470 }
471 writer->destroy(writer);
472 }
473
474 /**
475 * Handle Framed-IP-Address and other IKE configuration attributes
476 */
477 static void process_cfg_attributes(radius_message_t *msg)
478 {
479 eap_radius_provider_t *provider;
480 enumerator_t *enumerator;
481 ike_sa_t *ike_sa;
482 host_t *host;
483 chunk_t data;
484 configuration_attribute_type_t split_type = 0;
485 int type, vendor;
486
487 ike_sa = charon->bus->get_sa(charon->bus);
488 provider = eap_radius_provider_get();
489 if (provider && ike_sa)
490 {
491 enumerator = msg->create_enumerator(msg);
492 while (enumerator->enumerate(enumerator, &type, &data))
493 {
494 if (type == RAT_FRAMED_IP_ADDRESS && data.len == 4)
495 {
496 host = host_create_from_chunk(AF_INET, data, 0);
497 if (host)
498 {
499 provider->add_framed_ip(provider,
500 ike_sa->get_unique_id(ike_sa), host);
501 }
502 }
503 else if (type == RAT_FRAMED_IP_NETMASK && data.len == 4)
504 {
505 provider->add_attribute(provider, ike_sa->get_unique_id(ike_sa),
506 INTERNAL_IP4_NETMASK, data);
507 }
508 }
509 enumerator->destroy(enumerator);
510
511 enumerator = msg->create_vendor_enumerator(msg);
512 while (enumerator->enumerate(enumerator, &vendor, &type, &data))
513 {
514 if (vendor == PEN_ALTIGA /* aka Cisco VPN3000 */)
515 {
516 switch (type)
517 {
518 case 15: /* CVPN3000-IPSec-Banner1 */
519 case 28: /* CVPN3000-IPSec-Default-Domain */
520 case 29: /* CVPN3000-IPSec-Split-DNS-Names */
521 case 36: /* CVPN3000-IPSec-Banner2 */
522 if (ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY))
523 {
524 add_unity_attribute(provider,
525 ike_sa->get_unique_id(ike_sa), type, data);
526 }
527 break;
528 case 55: /* CVPN3000-IPSec-Split-Tunneling-Policy */
529 if (data.len)
530 {
531 switch (data.ptr[data.len - 1])
532 {
533 case 0: /* tunnelall */
534 default:
535 break;
536 case 1: /* tunnelspecified */
537 split_type = UNITY_SPLIT_INCLUDE;
538 break;
539 case 2: /* excludespecified */
540 split_type = UNITY_LOCAL_LAN;
541 break;
542 }
543 }
544 break;
545 default:
546 break;
547 }
548 }
549 }
550 enumerator->destroy(enumerator);
551
552 if (split_type != 0 &&
553 ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY))
554 {
555 enumerator = msg->create_vendor_enumerator(msg);
556 while (enumerator->enumerate(enumerator, &vendor, &type, &data))
557 {
558 if (vendor == PEN_ALTIGA /* aka Cisco VPN3000 */ &&
559 type == 27 /* CVPN3000-IPSec-Split-Tunnel-List */)
560 {
561 add_unity_split_attribute(provider,
562 ike_sa->get_unique_id(ike_sa), split_type, data);
563 }
564 }
565 enumerator->destroy(enumerator);
566 }
567 }
568 }
569
570 /**
571 * See header.
572 */
573 void eap_radius_process_attributes(radius_message_t *message)
574 {
575 if (lib->settings->get_bool(lib->settings,
576 "%s.plugins.eap-radius.class_group", FALSE, charon->name))
577 {
578 process_class(message);
579 }
580 if (lib->settings->get_bool(lib->settings,
581 "%s.plugins.eap-radius.filter_id", FALSE, charon->name))
582 {
583 process_filter_id(message);
584 }
585 process_timeout(message);
586 process_cfg_attributes(message);
587 }
588
589 METHOD(eap_method_t, process, status_t,
590 private_eap_radius_t *this, eap_payload_t *in, eap_payload_t **out)
591 {
592 radius_message_t *request, *response;
593 status_t status = FAILED;
594 chunk_t data;
595
596 request = radius_message_create(RMC_ACCESS_REQUEST);
597 add_radius_request_attrs(this, request);
598
599 data = in->get_data(in);
600 DBG3(DBG_IKE, "%N payload %B", eap_type_names, this->type, &data);
601
602 /* fragment data suitable for RADIUS */
603 while (data.len > MAX_RADIUS_ATTRIBUTE_SIZE)
604 {
605 request->add(request, RAT_EAP_MESSAGE,
606 chunk_create(data.ptr,MAX_RADIUS_ATTRIBUTE_SIZE));
607 data = chunk_skip(data, MAX_RADIUS_ATTRIBUTE_SIZE);
608 }
609 request->add(request, RAT_EAP_MESSAGE, data);
610
611 response = this->client->request(this->client, request);
612 if (response)
613 {
614 eap_radius_forward_to_ike(response);
615 switch (response->get_code(response))
616 {
617 case RMC_ACCESS_CHALLENGE:
618 if (radius2ike(this, response, out))
619 {
620 status = NEED_MORE;
621 break;
622 }
623 status = FAILED;
624 break;
625 case RMC_ACCESS_ACCEPT:
626 eap_radius_process_attributes(response);
627 DBG1(DBG_IKE, "RADIUS authentication of '%Y' successful",
628 this->peer);
629 status = SUCCESS;
630 break;
631 case RMC_ACCESS_REJECT:
632 default:
633 DBG1(DBG_IKE, "RADIUS authentication of '%Y' failed",
634 this->peer);
635 status = FAILED;
636 break;
637 }
638 response->destroy(response);
639 }
640 request->destroy(request);
641 return status;
642 }
643
644 METHOD(eap_method_t, get_type, eap_type_t,
645 private_eap_radius_t *this, u_int32_t *vendor)
646 {
647 *vendor = this->vendor;
648 return this->type;
649 }
650
651 METHOD(eap_method_t, get_msk, status_t,
652 private_eap_radius_t *this, chunk_t *out)
653 {
654 chunk_t msk;
655
656 msk = this->client->get_msk(this->client);
657 if (msk.len)
658 {
659 *out = msk;
660 return SUCCESS;
661 }
662 return FAILED;
663 }
664
665 METHOD(eap_method_t, get_identifier, u_int8_t,
666 private_eap_radius_t *this)
667 {
668 return this->identifier;
669 }
670
671 METHOD(eap_method_t, set_identifier, void,
672 private_eap_radius_t *this, u_int8_t identifier)
673 {
674 this->identifier = identifier;
675 }
676
677 METHOD(eap_method_t, is_mutual, bool,
678 private_eap_radius_t *this)
679 {
680 switch (this->type)
681 {
682 case EAP_AKA:
683 case EAP_SIM:
684 return TRUE;
685 default:
686 return FALSE;
687 }
688 }
689
690 METHOD(eap_method_t, destroy, void,
691 private_eap_radius_t *this)
692 {
693 this->peer->destroy(this->peer);
694 this->server->destroy(this->server);
695 this->client->destroy(this->client);
696 free(this);
697 }
698
699 /**
700 * Generic constructor
701 */
702 eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
703 {
704 private_eap_radius_t *this;
705
706 INIT(this,
707 .public = {
708 .eap_method = {
709 .initiate = _initiate,
710 .process = _process,
711 .get_type = _get_type,
712 .is_mutual = _is_mutual,
713 .get_msk = _get_msk,
714 .get_identifier = _get_identifier,
715 .set_identifier = _set_identifier,
716 .destroy = _destroy,
717 },
718 },
719 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
720 .type = EAP_RADIUS,
721 .eap_start = lib->settings->get_bool(lib->settings,
722 "%s.plugins.eap-radius.eap_start", FALSE,
723 charon->name),
724 .id_prefix = lib->settings->get_str(lib->settings,
725 "%s.plugins.eap-radius.id_prefix", "",
726 charon->name),
727 );
728 this->client = eap_radius_create_client();
729 if (!this->client)
730 {
731 free(this);
732 return NULL;
733 }
734 this->peer = peer->clone(peer);
735 this->server = server->clone(server);
736 return &this->public;
737 }