eap-radius: Forward UNITY_SPLIT_INCLUDE or UNITY_LOCAL_LAN attributes
[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 }
504 enumerator->destroy(enumerator);
505
506 enumerator = msg->create_vendor_enumerator(msg);
507 while (enumerator->enumerate(enumerator, &vendor, &type, &data))
508 {
509 if (vendor == PEN_ALTIGA /* aka Cisco VPN3000 */)
510 {
511 switch (type)
512 {
513 case 15: /* CVPN3000-IPSec-Banner1 */
514 case 28: /* CVPN3000-IPSec-Default-Domain */
515 case 29: /* CVPN3000-IPSec-Split-DNS-Names */
516 case 36: /* CVPN3000-IPSec-Banner2 */
517 if (ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY))
518 {
519 add_unity_attribute(provider,
520 ike_sa->get_unique_id(ike_sa), type, data);
521 }
522 break;
523 case 55: /* CVPN3000-IPSec-Split-Tunneling-Policy */
524 if (data.len)
525 {
526 switch (data.ptr[data.len - 1])
527 {
528 case 0: /* tunnelall */
529 default:
530 break;
531 case 1: /* tunnelspecified */
532 split_type = UNITY_SPLIT_INCLUDE;
533 break;
534 case 2: /* excludespecified */
535 split_type = UNITY_LOCAL_LAN;
536 break;
537 }
538 }
539 break;
540 default:
541 break;
542 }
543 }
544 }
545 enumerator->destroy(enumerator);
546
547 if (split_type != 0 &&
548 ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY))
549 {
550 enumerator = msg->create_vendor_enumerator(msg);
551 while (enumerator->enumerate(enumerator, &vendor, &type, &data))
552 {
553 if (vendor == PEN_ALTIGA /* aka Cisco VPN3000 */ &&
554 type == 27 /* CVPN3000-IPSec-Split-Tunnel-List */)
555 {
556 add_unity_split_attribute(provider,
557 ike_sa->get_unique_id(ike_sa), split_type, data);
558 }
559 }
560 enumerator->destroy(enumerator);
561 }
562 }
563 }
564
565 /**
566 * See header.
567 */
568 void eap_radius_process_attributes(radius_message_t *message)
569 {
570 if (lib->settings->get_bool(lib->settings,
571 "%s.plugins.eap-radius.class_group", FALSE, charon->name))
572 {
573 process_class(message);
574 }
575 if (lib->settings->get_bool(lib->settings,
576 "%s.plugins.eap-radius.filter_id", FALSE, charon->name))
577 {
578 process_filter_id(message);
579 }
580 process_timeout(message);
581 process_cfg_attributes(message);
582 }
583
584 METHOD(eap_method_t, process, status_t,
585 private_eap_radius_t *this, eap_payload_t *in, eap_payload_t **out)
586 {
587 radius_message_t *request, *response;
588 status_t status = FAILED;
589 chunk_t data;
590
591 request = radius_message_create(RMC_ACCESS_REQUEST);
592 add_radius_request_attrs(this, request);
593
594 data = in->get_data(in);
595 DBG3(DBG_IKE, "%N payload %B", eap_type_names, this->type, &data);
596
597 /* fragment data suitable for RADIUS */
598 while (data.len > MAX_RADIUS_ATTRIBUTE_SIZE)
599 {
600 request->add(request, RAT_EAP_MESSAGE,
601 chunk_create(data.ptr,MAX_RADIUS_ATTRIBUTE_SIZE));
602 data = chunk_skip(data, MAX_RADIUS_ATTRIBUTE_SIZE);
603 }
604 request->add(request, RAT_EAP_MESSAGE, data);
605
606 response = this->client->request(this->client, request);
607 if (response)
608 {
609 eap_radius_forward_to_ike(response);
610 switch (response->get_code(response))
611 {
612 case RMC_ACCESS_CHALLENGE:
613 if (radius2ike(this, response, out))
614 {
615 status = NEED_MORE;
616 break;
617 }
618 status = FAILED;
619 break;
620 case RMC_ACCESS_ACCEPT:
621 eap_radius_process_attributes(response);
622 DBG1(DBG_IKE, "RADIUS authentication of '%Y' successful",
623 this->peer);
624 status = SUCCESS;
625 break;
626 case RMC_ACCESS_REJECT:
627 default:
628 DBG1(DBG_IKE, "RADIUS authentication of '%Y' failed",
629 this->peer);
630 status = FAILED;
631 break;
632 }
633 response->destroy(response);
634 }
635 request->destroy(request);
636 return status;
637 }
638
639 METHOD(eap_method_t, get_type, eap_type_t,
640 private_eap_radius_t *this, u_int32_t *vendor)
641 {
642 *vendor = this->vendor;
643 return this->type;
644 }
645
646 METHOD(eap_method_t, get_msk, status_t,
647 private_eap_radius_t *this, chunk_t *out)
648 {
649 chunk_t msk;
650
651 msk = this->client->get_msk(this->client);
652 if (msk.len)
653 {
654 *out = msk;
655 return SUCCESS;
656 }
657 return FAILED;
658 }
659
660 METHOD(eap_method_t, get_identifier, u_int8_t,
661 private_eap_radius_t *this)
662 {
663 return this->identifier;
664 }
665
666 METHOD(eap_method_t, set_identifier, void,
667 private_eap_radius_t *this, u_int8_t identifier)
668 {
669 this->identifier = identifier;
670 }
671
672 METHOD(eap_method_t, is_mutual, bool,
673 private_eap_radius_t *this)
674 {
675 switch (this->type)
676 {
677 case EAP_AKA:
678 case EAP_SIM:
679 return TRUE;
680 default:
681 return FALSE;
682 }
683 }
684
685 METHOD(eap_method_t, destroy, void,
686 private_eap_radius_t *this)
687 {
688 this->peer->destroy(this->peer);
689 this->server->destroy(this->server);
690 this->client->destroy(this->client);
691 free(this);
692 }
693
694 /**
695 * Generic constructor
696 */
697 eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
698 {
699 private_eap_radius_t *this;
700
701 INIT(this,
702 .public = {
703 .eap_method = {
704 .initiate = _initiate,
705 .process = _process,
706 .get_type = _get_type,
707 .is_mutual = _is_mutual,
708 .get_msk = _get_msk,
709 .get_identifier = _get_identifier,
710 .set_identifier = _set_identifier,
711 .destroy = _destroy,
712 },
713 },
714 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
715 .type = EAP_RADIUS,
716 .eap_start = lib->settings->get_bool(lib->settings,
717 "%s.plugins.eap-radius.eap_start", FALSE,
718 charon->name),
719 .id_prefix = lib->settings->get_str(lib->settings,
720 "%s.plugins.eap-radius.id_prefix", "",
721 charon->name),
722 );
723 this->client = eap_radius_create_client();
724 if (!this->client)
725 {
726 free(this);
727 return NULL;
728 }
729 this->peer = peer->clone(peer);
730 this->server = server->clone(server);
731 return &this->public;
732 }