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