eap-radius: Add an option to exclude ports from Called/Calling-Station-Id
[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, this->peer, host);
416 }
417 }
418 }
419 enumerator->destroy(enumerator);
420
421 enumerator = msg->create_vendor_enumerator(msg);
422 while (enumerator->enumerate(enumerator, &vendor, &type, &data))
423 {
424 if (vendor == PEN_ALTIGA /* aka Cisco VPN3000 */)
425 {
426 switch (type)
427 {
428 case 15: /* CVPN3000-IPSec-Banner1 */
429 case 36: /* CVPN3000-IPSec-Banner2 */
430 if (ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY))
431 {
432 provider->add_attribute(provider, this->peer,
433 UNITY_BANNER, data);
434 }
435 break;
436 default:
437 break;
438 }
439 }
440 }
441 enumerator->destroy(enumerator);
442 }
443 }
444
445 METHOD(eap_method_t, process, status_t,
446 private_eap_radius_t *this, eap_payload_t *in, eap_payload_t **out)
447 {
448 radius_message_t *request, *response;
449 status_t status = FAILED;
450 chunk_t data;
451
452 request = radius_message_create(RMC_ACCESS_REQUEST);
453 add_radius_request_attrs(this, request);
454
455 data = in->get_data(in);
456 DBG3(DBG_IKE, "%N payload %B", eap_type_names, this->type, &data);
457
458 /* fragment data suitable for RADIUS */
459 while (data.len > MAX_RADIUS_ATTRIBUTE_SIZE)
460 {
461 request->add(request, RAT_EAP_MESSAGE,
462 chunk_create(data.ptr,MAX_RADIUS_ATTRIBUTE_SIZE));
463 data = chunk_skip(data, MAX_RADIUS_ATTRIBUTE_SIZE);
464 }
465 request->add(request, RAT_EAP_MESSAGE, data);
466
467 response = this->client->request(this->client, request);
468 if (response)
469 {
470 eap_radius_forward_to_ike(response);
471 switch (response->get_code(response))
472 {
473 case RMC_ACCESS_CHALLENGE:
474 if (radius2ike(this, response, out))
475 {
476 status = NEED_MORE;
477 break;
478 }
479 status = FAILED;
480 break;
481 case RMC_ACCESS_ACCEPT:
482 if (this->class_group)
483 {
484 process_class(this, response);
485 }
486 if (this->filter_id)
487 {
488 process_filter_id(this, response);
489 }
490 process_timeout(this, response);
491 process_cfg_attributes(this, response);
492 DBG1(DBG_IKE, "RADIUS authentication of '%Y' successful",
493 this->peer);
494 status = SUCCESS;
495 break;
496 case RMC_ACCESS_REJECT:
497 default:
498 DBG1(DBG_IKE, "RADIUS authentication of '%Y' failed",
499 this->peer);
500 status = FAILED;
501 break;
502 }
503 response->destroy(response);
504 }
505 request->destroy(request);
506 return status;
507 }
508
509 METHOD(eap_method_t, get_type, eap_type_t,
510 private_eap_radius_t *this, u_int32_t *vendor)
511 {
512 *vendor = this->vendor;
513 return this->type;
514 }
515
516 METHOD(eap_method_t, get_msk, status_t,
517 private_eap_radius_t *this, chunk_t *out)
518 {
519 chunk_t msk;
520
521 msk = this->client->get_msk(this->client);
522 if (msk.len)
523 {
524 *out = msk;
525 return SUCCESS;
526 }
527 return FAILED;
528 }
529
530 METHOD(eap_method_t, get_identifier, u_int8_t,
531 private_eap_radius_t *this)
532 {
533 return this->identifier;
534 }
535
536 METHOD(eap_method_t, set_identifier, void,
537 private_eap_radius_t *this, u_int8_t identifier)
538 {
539 this->identifier = identifier;
540 }
541
542 METHOD(eap_method_t, is_mutual, bool,
543 private_eap_radius_t *this)
544 {
545 switch (this->type)
546 {
547 case EAP_AKA:
548 case EAP_SIM:
549 return TRUE;
550 default:
551 return FALSE;
552 }
553 }
554
555 METHOD(eap_method_t, destroy, void,
556 private_eap_radius_t *this)
557 {
558 this->peer->destroy(this->peer);
559 this->server->destroy(this->server);
560 this->client->destroy(this->client);
561 free(this);
562 }
563
564 /**
565 * Generic constructor
566 */
567 eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
568 {
569 private_eap_radius_t *this;
570
571 INIT(this,
572 .public = {
573 .eap_method = {
574 .initiate = _initiate,
575 .process = _process,
576 .get_type = _get_type,
577 .is_mutual = _is_mutual,
578 .get_msk = _get_msk,
579 .get_identifier = _get_identifier,
580 .set_identifier = _set_identifier,
581 .destroy = _destroy,
582 },
583 },
584 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
585 .type = EAP_RADIUS,
586 .eap_start = lib->settings->get_bool(lib->settings,
587 "%s.plugins.eap-radius.eap_start", FALSE,
588 charon->name),
589 .id_prefix = lib->settings->get_str(lib->settings,
590 "%s.plugins.eap-radius.id_prefix", "",
591 charon->name),
592 .class_group = lib->settings->get_bool(lib->settings,
593 "%s.plugins.eap-radius.class_group", FALSE,
594 charon->name),
595 .filter_id = lib->settings->get_bool(lib->settings,
596 "%s.plugins.eap-radius.filter_id", FALSE,
597 charon->name),
598 );
599 if (lib->settings->get_bool(lib->settings,
600 "%s.plugins.eap-radius.station_id_with_port", TRUE, charon->name))
601 {
602 this->station_id_fmt = "%#H";
603 }
604 else
605 {
606 this->station_id_fmt = "%H";
607 }
608 this->client = eap_radius_create_client();
609 if (!this->client)
610 {
611 free(this);
612 return NULL;
613 }
614 this->peer = peer->clone(peer);
615 this->server = server->clone(server);
616 return &this->public;
617 }