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