2 * Copyright (C) 2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
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>.
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
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"
22 #include <radius_message.h>
23 #include <radius_client.h>
24 #include <bio/bio_writer.h>
28 typedef struct private_eap_radius_t private_eap_radius_t
;
31 * Private data of an eap_radius_t object.
33 struct private_eap_radius_t
{
36 * Public authenticator_t interface.
43 identification_t
*server
;
48 identification_t
*peer
;
51 * EAP method type we are proxying
61 * EAP message identifier
66 * RADIUS client instance
68 radius_client_t
*client
;
71 * TRUE to use EAP-Start, FALSE to send EAP-Identity Response directly
76 * Prefix to prepend to EAP identity
82 * Add EAP-Identity to RADIUS message
84 static void add_eap_identity(private_eap_radius_t
*this,
85 radius_message_t
*request
)
88 /** EAP code (REQUEST/RESPONSE) */
90 /** unique message identifier */
92 /** length of whole message */
98 } __attribute__((__packed__
)) *hdr
;
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
;
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
);
114 request
->add(request
, RAT_EAP_MESSAGE
, chunk_create((u_char
*)hdr
, len
));
118 * Copy EAP-Message attribute from RADIUS message to an new EAP payload
120 static bool radius2ike(private_eap_radius_t
*this,
121 radius_message_t
*msg
, eap_payload_t
**out
)
123 enumerator_t
*enumerator
;
124 eap_payload_t
*payload
;
125 chunk_t data
, message
= chunk_empty
;
128 enumerator
= msg
->create_enumerator(msg
);
129 while (enumerator
->enumerate(enumerator
, &type
, &data
))
131 if (type
== RAT_EAP_MESSAGE
&& data
.len
)
133 message
= chunk_cat("mc", message
, data
);
136 enumerator
->destroy(enumerator
);
139 *out
= payload
= eap_payload_create_data(message
);
141 /* apply EAP method selected by RADIUS server */
142 this->type
= payload
->get_type(payload
, &this->vendor
);
144 DBG3(DBG_IKE
, "%N payload %B", eap_type_names
, this->type
, &message
);
154 void eap_radius_build_attributes(radius_message_t
*request
)
158 char buf
[40], *station_id_fmt
;;
162 /* virtual NAS-Port-Type */
164 request
->add(request
, RAT_NAS_PORT_TYPE
, chunk_from_thing(value
));
165 /* framed ServiceType */
167 request
->add(request
, RAT_SERVICE_TYPE
, chunk_from_thing(value
));
169 ike_sa
= charon
->bus
->get_sa(charon
->bus
);
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
)));
177 host
= ike_sa
->get_my_host(ike_sa
);
178 chunk
= host
->get_address(host
);
179 switch (host
->get_family(host
))
182 request
->add(request
, RAT_NAS_IP_ADDRESS
, chunk
);
185 request
->add(request
, RAT_NAS_IPV6_ADDRESS
, chunk
);
189 if (lib
->settings
->get_bool(lib
->settings
,
190 "%s.plugins.eap-radius.station_id_with_port",
193 station_id_fmt
= "%#H";
197 station_id_fmt
= "%H";
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
));
208 * Add a set of RADIUS attributes to a request message
210 static void add_radius_request_attrs(private_eap_radius_t
*this,
211 radius_message_t
*request
)
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
);
219 eap_radius_build_attributes(request
);
220 eap_radius_forward_from_ike(request
);
223 METHOD(eap_method_t
, initiate
, status_t
,
224 private_eap_radius_t
*this, eap_payload_t
**out
)
226 radius_message_t
*request
, *response
;
227 status_t status
= FAILED
;
229 request
= radius_message_create(RMC_ACCESS_REQUEST
);
230 add_radius_request_attrs(this, request
);
234 request
->add(request
, RAT_EAP_MESSAGE
, chunk_empty
);
238 add_eap_identity(this, request
);
241 response
= this->client
->request(this->client
, request
);
244 eap_radius_forward_to_ike(response
);
245 switch (response
->get_code(response
))
247 case RMC_ACCESS_CHALLENGE
:
248 if (radius2ike(this, response
, out
))
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
:
259 DBG1(DBG_IKE
, "RADIUS authentication of '%Y' failed",
263 response
->destroy(response
);
267 eap_radius_handle_timeout(NULL
);
269 request
->destroy(request
);
274 * Handle the Class attribute as group membership information
276 static void process_class(radius_message_t
*msg
)
278 enumerator_t
*enumerator
;
282 enumerator
= msg
->create_enumerator(msg
);
283 while (enumerator
->enumerate(enumerator
, &type
, &data
))
285 if (type
== RAT_CLASS
)
287 identification_t
*id
;
292 { /* quirk: ignore long class attributes, these are used for
293 * other purposes by some RADIUS servers (such as NPS). */
297 ike_sa
= charon
->bus
->get_sa(charon
->bus
);
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
);
307 enumerator
->destroy(enumerator
);
311 * Handle the Filter-Id attribute as IPsec CHILD_SA name
313 static void process_filter_id(radius_message_t
*msg
)
315 enumerator_t
*enumerator
;
318 u_int32_t tunnel_type
;
319 chunk_t filter_id
= chunk_empty
, data
;
320 bool is_esp_tunnel
= FALSE
;
322 enumerator
= msg
->create_enumerator(msg
);
323 while (enumerator
->enumerate(enumerator
, &type
, &data
))
327 case RAT_TUNNEL_TYPE
:
332 tunnel_tag
= *data
.ptr
;
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
);
341 DBG1(DBG_IKE
, "received RADIUS attribute Filter-Id: "
342 "'%.*s'", (int)filter_id
.len
, filter_id
.ptr
);
348 enumerator
->destroy(enumerator
);
350 if (is_esp_tunnel
&& filter_id
.len
)
352 identification_t
*id
;
356 ike_sa
= charon
->bus
->get_sa(charon
->bus
);
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
);
367 * Handle Session-Timeout attribte and Interim updates
369 static void process_timeout(radius_message_t
*msg
)
371 enumerator_t
*enumerator
;
376 ike_sa
= charon
->bus
->get_sa(charon
->bus
);
379 enumerator
= msg
->create_enumerator(msg
);
380 while (enumerator
->enumerate(enumerator
, &type
, &data
))
382 if (type
== RAT_SESSION_TIMEOUT
&& data
.len
== 4)
384 ike_sa
->set_auth_lifetime(ike_sa
, untoh32(data
.ptr
));
386 else if (type
== RAT_ACCT_INTERIM_INTERVAL
&& data
.len
== 4)
388 eap_radius_accounting_start_interim(ike_sa
, untoh32(data
.ptr
));
391 enumerator
->destroy(enumerator
);
396 * Add a Cisco Unity configuration attribute
398 static void add_unity_attribute(eap_radius_provider_t
*provider
, u_int32_t id
,
399 int type
, chunk_t data
)
403 case 15: /* CVPN3000-IPSec-Banner1 */
404 case 36: /* CVPN3000-IPSec-Banner2 */
405 provider
->add_attribute(provider
, id
, UNITY_BANNER
, data
);
407 case 28: /* CVPN3000-IPSec-Default-Domain */
408 provider
->add_attribute(provider
, id
, UNITY_DEF_DOMAIN
, data
);
410 case 29: /* CVPN3000-IPSec-Split-DNS-Names */
411 provider
->add_attribute(provider
, id
, UNITY_SPLITDNS_NAME
, data
);
417 * Add a DNS/NBNS configuration attribute
419 static void add_nameserver_attribute(eap_radius_provider_t
*provider
,
420 u_int32_t id
, int type
, chunk_t data
)
422 /* these are from different vendors, but there is currently no conflict */
425 case 5: /* CVPN3000-Primary-DNS */
426 case 6: /* CVPN3000-Secondary-DNS */
427 case 28: /* MS-Primary-DNS-Server */
428 case 29: /* MS-Secondary-DNS-Server */
429 provider
->add_attribute(provider
, id
, INTERNAL_IP4_DNS
, data
);
431 case 7: /* CVPN3000-Primary-WINS */
432 case 8: /* CVPN3000-Secondary-WINS */
433 case 30: /* MS-Primary-NBNS-Server */
434 case 31: /* MS-Secondary-NBNS-Server */
435 provider
->add_attribute(provider
, id
, INTERNAL_IP4_NBNS
, data
);
437 case RAT_FRAMED_IPV6_DNS_SERVER
:
438 provider
->add_attribute(provider
, id
, INTERNAL_IP6_DNS
, data
);
444 * Add a UNITY_LOCAL_LAN or UNITY_SPLIT_INCLUDE attribute
446 static void add_unity_split_attribute(eap_radius_provider_t
*provider
,
447 u_int32_t id
, configuration_attribute_type_t type
,
450 enumerator_t
*enumerator
;
451 bio_writer_t
*writer
;
452 char buffer
[256], *token
, *slash
;
454 if (snprintf(buffer
, sizeof(buffer
), "%.*s", (int)data
.len
,
455 data
.ptr
) >= sizeof(buffer
))
459 writer
= bio_writer_create(16); /* two IPv4 addresses and 6 bytes padding */
460 enumerator
= enumerator_create_token(buffer
, ",", " ");
461 while (enumerator
->enumerate(enumerator
, &token
))
463 host_t
*net
, *mask
= NULL
;
466 slash
= strchr(token
, '/');
470 mask
= host_create_from_string(slash
, 0);
473 { /* default to /32 */
474 mask
= host_create_from_string("255.255.255.255", 0);
476 net
= host_create_from_string(token
, 0);
477 if (!net
|| net
->get_family(net
) != AF_INET
||
478 mask
->get_family(mask
) != AF_INET
)
484 writer
->write_data(writer
, net
->get_address(net
));
485 writer
->write_data(writer
, mask
->get_address(mask
));
486 padding
= writer
->skip(writer
, 6); /* 6 bytes pdding */
487 memset(padding
.ptr
, 0, padding
.len
);
491 enumerator
->destroy(enumerator
);
493 data
= writer
->get_buf(writer
);
496 provider
->add_attribute(provider
, id
, type
, data
);
498 writer
->destroy(writer
);
502 * Handle Framed-IP-Address and other IKE configuration attributes
504 static void process_cfg_attributes(radius_message_t
*msg
)
506 eap_radius_provider_t
*provider
;
507 enumerator_t
*enumerator
;
511 configuration_attribute_type_t split_type
= 0;
514 ike_sa
= charon
->bus
->get_sa(charon
->bus
);
515 provider
= eap_radius_provider_get();
516 if (provider
&& ike_sa
)
518 enumerator
= msg
->create_enumerator(msg
);
519 while (enumerator
->enumerate(enumerator
, &type
, &data
))
521 if ((type
== RAT_FRAMED_IP_ADDRESS
&& data
.len
== 4) ||
522 (type
== RAT_FRAMED_IPV6_ADDRESS
&& data
.len
== 16))
524 host
= host_create_from_chunk(AF_INET
, data
, 0);
527 provider
->add_framed_ip(provider
,
528 ike_sa
->get_unique_id(ike_sa
), host
);
531 else if (type
== RAT_FRAMED_IP_NETMASK
&& data
.len
== 4)
533 provider
->add_attribute(provider
, ike_sa
->get_unique_id(ike_sa
),
534 INTERNAL_IP4_NETMASK
, data
);
536 else if (type
== RAT_FRAMED_IPV6_DNS_SERVER
&& data
.len
== 16)
538 add_nameserver_attribute(provider
,
539 ike_sa
->get_unique_id(ike_sa
), type
, data
);
542 enumerator
->destroy(enumerator
);
544 enumerator
= msg
->create_vendor_enumerator(msg
);
545 while (enumerator
->enumerate(enumerator
, &vendor
, &type
, &data
))
547 if (vendor
== PEN_ALTIGA
/* aka Cisco VPN3000 */)
551 case 5: /* CVPN3000-Primary-DNS */
552 case 6: /* CVPN3000-Secondary-DNS */
553 case 7: /* CVPN3000-Primary-WINS */
554 case 8: /* CVPN3000-Secondary-WINS */
557 add_nameserver_attribute(provider
,
558 ike_sa
->get_unique_id(ike_sa
), type
, data
);
561 case 15: /* CVPN3000-IPSec-Banner1 */
562 case 28: /* CVPN3000-IPSec-Default-Domain */
563 case 29: /* CVPN3000-IPSec-Split-DNS-Names */
564 case 36: /* CVPN3000-IPSec-Banner2 */
565 if (ike_sa
->supports_extension(ike_sa
, EXT_CISCO_UNITY
))
567 add_unity_attribute(provider
,
568 ike_sa
->get_unique_id(ike_sa
), type
, data
);
571 case 55: /* CVPN3000-IPSec-Split-Tunneling-Policy */
574 switch (data
.ptr
[data
.len
- 1])
576 case 0: /* tunnelall */
579 case 1: /* tunnelspecified */
580 split_type
= UNITY_SPLIT_INCLUDE
;
582 case 2: /* excludespecified */
583 split_type
= UNITY_LOCAL_LAN
;
592 if (vendor
== PEN_MICROSOFT
)
596 case 28: /* MS-Primary-DNS-Server */
597 case 29: /* MS-Secondary-DNS-Server */
598 case 30: /* MS-Primary-NBNS-Server */
599 case 31: /* MS-Secondary-NBNS-Server */
602 add_nameserver_attribute(provider
,
603 ike_sa
->get_unique_id(ike_sa
), type
, data
);
609 enumerator
->destroy(enumerator
);
611 if (split_type
!= 0 &&
612 ike_sa
->supports_extension(ike_sa
, EXT_CISCO_UNITY
))
614 enumerator
= msg
->create_vendor_enumerator(msg
);
615 while (enumerator
->enumerate(enumerator
, &vendor
, &type
, &data
))
617 if (vendor
== PEN_ALTIGA
/* aka Cisco VPN3000 */ &&
618 type
== 27 /* CVPN3000-IPSec-Split-Tunnel-List */)
620 add_unity_split_attribute(provider
,
621 ike_sa
->get_unique_id(ike_sa
), split_type
, data
);
624 enumerator
->destroy(enumerator
);
632 void eap_radius_process_attributes(radius_message_t
*message
)
634 if (lib
->settings
->get_bool(lib
->settings
,
635 "%s.plugins.eap-radius.class_group", FALSE
, lib
->ns
))
637 process_class(message
);
639 if (lib
->settings
->get_bool(lib
->settings
,
640 "%s.plugins.eap-radius.filter_id", FALSE
, lib
->ns
))
642 process_filter_id(message
);
644 process_timeout(message
);
645 process_cfg_attributes(message
);
648 METHOD(eap_method_t
, process
, status_t
,
649 private_eap_radius_t
*this, eap_payload_t
*in
, eap_payload_t
**out
)
651 radius_message_t
*request
, *response
;
652 status_t status
= FAILED
;
655 request
= radius_message_create(RMC_ACCESS_REQUEST
);
656 add_radius_request_attrs(this, request
);
658 data
= in
->get_data(in
);
659 DBG3(DBG_IKE
, "%N payload %B", eap_type_names
, this->type
, &data
);
661 /* fragment data suitable for RADIUS */
662 while (data
.len
> MAX_RADIUS_ATTRIBUTE_SIZE
)
664 request
->add(request
, RAT_EAP_MESSAGE
,
665 chunk_create(data
.ptr
,MAX_RADIUS_ATTRIBUTE_SIZE
));
666 data
= chunk_skip(data
, MAX_RADIUS_ATTRIBUTE_SIZE
);
668 request
->add(request
, RAT_EAP_MESSAGE
, data
);
670 response
= this->client
->request(this->client
, request
);
673 eap_radius_forward_to_ike(response
);
674 switch (response
->get_code(response
))
676 case RMC_ACCESS_CHALLENGE
:
677 if (radius2ike(this, response
, out
))
684 case RMC_ACCESS_ACCEPT
:
685 eap_radius_process_attributes(response
);
686 DBG1(DBG_IKE
, "RADIUS authentication of '%Y' successful",
690 case RMC_ACCESS_REJECT
:
692 DBG1(DBG_IKE
, "RADIUS authentication of '%Y' failed",
697 response
->destroy(response
);
699 request
->destroy(request
);
703 METHOD(eap_method_t
, get_type
, eap_type_t
,
704 private_eap_radius_t
*this, u_int32_t
*vendor
)
706 *vendor
= this->vendor
;
710 METHOD(eap_method_t
, get_msk
, status_t
,
711 private_eap_radius_t
*this, chunk_t
*out
)
715 msk
= this->client
->get_msk(this->client
);
724 METHOD(eap_method_t
, get_identifier
, u_int8_t
,
725 private_eap_radius_t
*this)
727 return this->identifier
;
730 METHOD(eap_method_t
, set_identifier
, void,
731 private_eap_radius_t
*this, u_int8_t identifier
)
733 this->identifier
= identifier
;
736 METHOD(eap_method_t
, is_mutual
, bool,
737 private_eap_radius_t
*this)
749 METHOD(eap_method_t
, destroy
, void,
750 private_eap_radius_t
*this)
752 this->peer
->destroy(this->peer
);
753 this->server
->destroy(this->server
);
754 this->client
->destroy(this->client
);
759 * Generic constructor
761 eap_radius_t
*eap_radius_create(identification_t
*server
, identification_t
*peer
)
763 private_eap_radius_t
*this;
768 .initiate
= _initiate
,
770 .get_type
= _get_type
,
771 .is_mutual
= _is_mutual
,
773 .get_identifier
= _get_identifier
,
774 .set_identifier
= _set_identifier
,
778 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
780 .eap_start
= lib
->settings
->get_bool(lib
->settings
,
781 "%s.plugins.eap-radius.eap_start", FALSE
,
783 .id_prefix
= lib
->settings
->get_str(lib
->settings
,
784 "%s.plugins.eap-radius.id_prefix", "",
787 this->client
= eap_radius_create_client();
793 this->peer
= peer
->clone(peer
);
794 this->server
= server
->clone(server
);
795 return &this->public;