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