]>
Commit | Line | Data |
---|---|---|
4a6b84a9 MW |
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. | |
4a6b84a9 | 14 | */ |
7daf5226 | 15 | |
4a6b84a9 | 16 | #include "eap_radius.h" |
f0f94e2c | 17 | #include "eap_radius_plugin.h" |
99cb3539 | 18 | #include "eap_radius_forward.h" |
4a6b84a9 | 19 | |
f0f94e2c MW |
20 | #include <radius_message.h> |
21 | #include <radius_client.h> | |
4a6b84a9 MW |
22 | |
23 | #include <daemon.h> | |
24 | ||
28b23fef AS |
25 | #define TUNNEL_TYPE_ESP 9 |
26 | ||
4a6b84a9 MW |
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 { | |
7daf5226 | 33 | |
4a6b84a9 MW |
34 | /** |
35 | * Public authenticator_t interface. | |
36 | */ | |
37 | eap_radius_t public; | |
7daf5226 | 38 | |
4a6b84a9 MW |
39 | /** |
40 | * ID of the server | |
41 | */ | |
42 | identification_t *server; | |
7daf5226 | 43 | |
4a6b84a9 MW |
44 | /** |
45 | * ID of the peer | |
46 | */ | |
47 | identification_t *peer; | |
7daf5226 | 48 | |
4a6b84a9 MW |
49 | /** |
50 | * EAP method type we are proxying | |
51 | */ | |
52 | eap_type_t type; | |
7daf5226 | 53 | |
4a6b84a9 MW |
54 | /** |
55 | * EAP vendor, if any | |
56 | */ | |
57 | u_int32_t vendor; | |
7daf5226 | 58 | |
ab5e0873 AS |
59 | /** |
60 | * EAP message identifier | |
61 | */ | |
62 | u_int8_t identifier; | |
63 | ||
4a6b84a9 MW |
64 | /** |
65 | * RADIUS client instance | |
66 | */ | |
67 | radius_client_t *client; | |
7daf5226 | 68 | |
c296e51c MW |
69 | /** |
70 | * TRUE to use EAP-Start, FALSE to send EAP-Identity Response directly | |
71 | */ | |
72 | bool eap_start; | |
7daf5226 | 73 | |
3f671262 MW |
74 | /** |
75 | * Prefix to prepend to EAP identity | |
76 | */ | |
77 | char *id_prefix; | |
a4c0da16 MW |
78 | |
79 | /** | |
80 | * Handle the Class attribute as group membership information? | |
81 | */ | |
82 | bool class_group; | |
28b23fef AS |
83 | |
84 | /** | |
85 | * Handle the Filter-Id attribute as IPsec CHILD_SA name? | |
86 | */ | |
87 | bool filter_id; | |
4a6b84a9 MW |
88 | }; |
89 | ||
90 | /** | |
91 | * Add EAP-Identity to RADIUS message | |
92 | */ | |
93 | static void add_eap_identity(private_eap_radius_t *this, | |
94 | radius_message_t *request) | |
95 | { | |
96 | struct { | |
97 | /** EAP code (REQUEST/RESPONSE) */ | |
98 | u_int8_t code; | |
99 | /** unique message identifier */ | |
100 | u_int8_t identifier; | |
101 | /** length of whole message */ | |
102 | u_int16_t length; | |
103 | /** EAP type */ | |
104 | u_int8_t type; | |
105 | /** identity data */ | |
106 | u_int8_t data[]; | |
107 | } __attribute__((__packed__)) *hdr; | |
3f671262 | 108 | chunk_t id, prefix; |
4a6b84a9 | 109 | size_t len; |
7daf5226 | 110 | |
4a6b84a9 | 111 | id = this->peer->get_encoding(this->peer); |
3f671262 MW |
112 | prefix = chunk_create(this->id_prefix, strlen(this->id_prefix)); |
113 | len = sizeof(*hdr) + prefix.len + id.len; | |
7daf5226 | 114 | |
4a6b84a9 MW |
115 | hdr = alloca(len); |
116 | hdr->code = EAP_RESPONSE; | |
ab5e0873 | 117 | hdr->identifier = this->identifier; |
4a6b84a9 MW |
118 | hdr->length = htons(len); |
119 | hdr->type = EAP_IDENTITY; | |
3f671262 MW |
120 | memcpy(hdr->data, prefix.ptr, prefix.len); |
121 | memcpy(hdr->data + prefix.len, id.ptr, id.len); | |
7daf5226 | 122 | |
4a6b84a9 MW |
123 | request->add(request, RAT_EAP_MESSAGE, chunk_create((u_char*)hdr, len)); |
124 | } | |
125 | ||
126 | /** | |
127 | * Copy EAP-Message attribute from RADIUS message to an new EAP payload | |
128 | */ | |
129 | static bool radius2ike(private_eap_radius_t *this, | |
130 | radius_message_t *msg, eap_payload_t **out) | |
131 | { | |
132 | enumerator_t *enumerator; | |
133 | eap_payload_t *payload; | |
47498044 | 134 | chunk_t data, message = chunk_empty; |
4a6b84a9 | 135 | int type; |
7daf5226 | 136 | |
4a6b84a9 MW |
137 | enumerator = msg->create_enumerator(msg); |
138 | while (enumerator->enumerate(enumerator, &type, &data)) | |
139 | { | |
47498044 | 140 | if (type == RAT_EAP_MESSAGE && data.len) |
4a6b84a9 | 141 | { |
47498044 | 142 | message = chunk_cat("mc", message, data); |
4a6b84a9 MW |
143 | } |
144 | } | |
145 | enumerator->destroy(enumerator); | |
47498044 MW |
146 | if (message.len) |
147 | { | |
148 | *out = payload = eap_payload_create_data(message); | |
20c428b6 | 149 | |
47498044 MW |
150 | /* apply EAP method selected by RADIUS server */ |
151 | this->type = payload->get_type(payload, &this->vendor); | |
20c428b6 AS |
152 | |
153 | DBG3(DBG_IKE, "%N payload %B", eap_type_names, this->type, &message); | |
154 | free(message.ptr); | |
47498044 MW |
155 | return TRUE; |
156 | } | |
4a6b84a9 MW |
157 | return FALSE; |
158 | } | |
159 | ||
58d2ef6e MW |
160 | METHOD(eap_method_t, initiate, status_t, |
161 | private_eap_radius_t *this, eap_payload_t **out) | |
4a6b84a9 MW |
162 | { |
163 | radius_message_t *request, *response; | |
164 | status_t status = FAILED; | |
3f671262 | 165 | chunk_t username; |
7daf5226 | 166 | |
3bc18292 | 167 | request = radius_message_create(RMC_ACCESS_REQUEST); |
3f671262 MW |
168 | username = chunk_create(this->id_prefix, strlen(this->id_prefix)); |
169 | username = chunk_cata("cc", username, this->peer->get_encoding(this->peer)); | |
170 | request->add(request, RAT_USER_NAME, username); | |
7daf5226 | 171 | |
c296e51c MW |
172 | if (this->eap_start) |
173 | { | |
174 | request->add(request, RAT_EAP_MESSAGE, chunk_empty); | |
175 | } | |
176 | else | |
177 | { | |
178 | add_eap_identity(this, request); | |
179 | } | |
99cb3539 | 180 | eap_radius_forward_from_ike(request); |
7daf5226 | 181 | |
4a6b84a9 MW |
182 | response = this->client->request(this->client, request); |
183 | if (response) | |
184 | { | |
99cb3539 | 185 | eap_radius_forward_to_ike(response); |
4a6b84a9 MW |
186 | if (radius2ike(this, response, out)) |
187 | { | |
188 | status = NEED_MORE; | |
189 | } | |
190 | response->destroy(response); | |
191 | } | |
990fda9d MW |
192 | else |
193 | { | |
194 | charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING); | |
195 | } | |
4a6b84a9 MW |
196 | request->destroy(request); |
197 | return status; | |
198 | } | |
199 | ||
a4c0da16 MW |
200 | /** |
201 | * Handle the Class attribute as group membership information | |
202 | */ | |
203 | static void process_class(private_eap_radius_t *this, radius_message_t *msg) | |
204 | { | |
205 | enumerator_t *enumerator; | |
206 | chunk_t data; | |
207 | int type; | |
208 | ||
209 | enumerator = msg->create_enumerator(msg); | |
210 | while (enumerator->enumerate(enumerator, &type, &data)) | |
211 | { | |
212 | if (type == RAT_CLASS) | |
213 | { | |
52f97c38 | 214 | identification_t *id; |
a4c0da16 MW |
215 | ike_sa_t *ike_sa; |
216 | auth_cfg_t *auth; | |
217 | ||
52f97c38 MW |
218 | if (data.len >= 44) |
219 | { /* quirk: ignore long class attributes, these are used for | |
220 | * other purposes by some RADIUS servers (such as NPS). */ | |
221 | continue; | |
222 | } | |
223 | ||
a4c0da16 MW |
224 | ike_sa = charon->bus->get_sa(charon->bus); |
225 | if (ike_sa) | |
226 | { | |
227 | auth = ike_sa->get_auth_cfg(ike_sa, FALSE); | |
52f97c38 MW |
228 | id = identification_create_from_data(data); |
229 | DBG1(DBG_CFG, "received group membership '%Y' from RADIUS", id); | |
230 | auth->add(auth, AUTH_RULE_GROUP, id); | |
a4c0da16 MW |
231 | } |
232 | } | |
233 | } | |
234 | enumerator->destroy(enumerator); | |
235 | } | |
236 | ||
28b23fef AS |
237 | /** |
238 | * Handle the Filter-Id attribute as IPsec CHILD_SA name | |
239 | */ | |
a1edf4d3 | 240 | static void process_filter_id(private_eap_radius_t *this, radius_message_t *msg) |
28b23fef AS |
241 | { |
242 | enumerator_t *enumerator; | |
28b23fef AS |
243 | int type; |
244 | u_int8_t tunnel_tag; | |
245 | u_int32_t tunnel_type; | |
a1edf4d3 | 246 | chunk_t filter_id = chunk_empty, data; |
28b23fef AS |
247 | bool is_esp_tunnel = FALSE; |
248 | ||
249 | enumerator = msg->create_enumerator(msg); | |
250 | while (enumerator->enumerate(enumerator, &type, &data)) | |
251 | { | |
252 | switch (type) | |
253 | { | |
254 | case RAT_TUNNEL_TYPE: | |
255 | if (data.len != 4) | |
256 | { | |
257 | continue; | |
258 | } | |
259 | tunnel_tag = *data.ptr; | |
260 | *data.ptr = 0x00; | |
261 | tunnel_type = untoh32(data.ptr); | |
262 | DBG1(DBG_IKE, "received RADIUS attribute Tunnel-Type: " | |
962300b9 | 263 | "tag = %u, value = %u", tunnel_tag, tunnel_type); |
28b23fef AS |
264 | is_esp_tunnel = (tunnel_type == TUNNEL_TYPE_ESP); |
265 | break; | |
266 | case RAT_FILTER_ID: | |
267 | filter_id = data; | |
268 | DBG1(DBG_IKE, "received RADIUS attribute Filter-Id: " | |
962300b9 | 269 | "'%.*s'", filter_id.len, filter_id.ptr); |
28b23fef AS |
270 | break; |
271 | default: | |
272 | break; | |
273 | } | |
28b23fef AS |
274 | } |
275 | enumerator->destroy(enumerator); | |
b540d191 AS |
276 | |
277 | if (is_esp_tunnel && filter_id.len) | |
278 | { | |
a1edf4d3 AS |
279 | identification_t *id; |
280 | ike_sa_t *ike_sa; | |
281 | auth_cfg_t *auth; | |
282 | ||
283 | ike_sa = charon->bus->get_sa(charon->bus); | |
284 | if (ike_sa) | |
285 | { | |
286 | auth = ike_sa->get_auth_cfg(ike_sa, FALSE); | |
287 | id = identification_create_from_data(filter_id); | |
288 | auth->add(auth, AUTH_RULE_GROUP, id); | |
289 | } | |
b540d191 | 290 | } |
28b23fef AS |
291 | } |
292 | ||
c61341a5 MW |
293 | /** |
294 | * Handle Session-Timeout attribte | |
295 | */ | |
296 | static void process_timeout(private_eap_radius_t *this, radius_message_t *msg) | |
297 | { | |
298 | enumerator_t *enumerator; | |
299 | ike_sa_t *ike_sa; | |
300 | chunk_t data; | |
301 | int type; | |
302 | ||
303 | enumerator = msg->create_enumerator(msg); | |
304 | while (enumerator->enumerate(enumerator, &type, &data)) | |
305 | { | |
306 | if (type == RAT_SESSION_TIMEOUT && data.len == 4) | |
307 | { | |
308 | ike_sa = charon->bus->get_sa(charon->bus); | |
309 | if (ike_sa) | |
310 | { | |
311 | ike_sa->set_auth_lifetime(ike_sa, untoh32(data.ptr)); | |
312 | } | |
313 | } | |
314 | } | |
315 | enumerator->destroy(enumerator); | |
316 | } | |
317 | ||
58d2ef6e MW |
318 | METHOD(eap_method_t, process, status_t, |
319 | private_eap_radius_t *this, eap_payload_t *in, eap_payload_t **out) | |
4a6b84a9 MW |
320 | { |
321 | radius_message_t *request, *response; | |
322 | status_t status = FAILED; | |
47498044 | 323 | chunk_t data; |
7daf5226 | 324 | |
3bc18292 | 325 | request = radius_message_create(RMC_ACCESS_REQUEST); |
4a6b84a9 | 326 | request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer)); |
47498044 | 327 | data = in->get_data(in); |
20c428b6 | 328 | DBG3(DBG_IKE, "%N payload %B", eap_type_names, this->type, &data); |
370de553 | 329 | |
47498044 MW |
330 | /* fragment data suitable for RADIUS (not more than 253 bytes) */ |
331 | while (data.len > 253) | |
332 | { | |
333 | request->add(request, RAT_EAP_MESSAGE, chunk_create(data.ptr, 253)); | |
334 | data = chunk_skip(data, 253); | |
335 | } | |
336 | request->add(request, RAT_EAP_MESSAGE, data); | |
7daf5226 | 337 | |
99cb3539 | 338 | eap_radius_forward_from_ike(request); |
4a6b84a9 MW |
339 | response = this->client->request(this->client, request); |
340 | if (response) | |
341 | { | |
99cb3539 | 342 | eap_radius_forward_to_ike(response); |
4a6b84a9 MW |
343 | switch (response->get_code(response)) |
344 | { | |
345 | case RMC_ACCESS_CHALLENGE: | |
346 | if (radius2ike(this, response, out)) | |
347 | { | |
348 | status = NEED_MORE; | |
349 | break; | |
350 | } | |
351 | status = FAILED; | |
352 | break; | |
353 | case RMC_ACCESS_ACCEPT: | |
a4c0da16 MW |
354 | if (this->class_group) |
355 | { | |
356 | process_class(this, response); | |
357 | } | |
28b23fef AS |
358 | if (this->filter_id) |
359 | { | |
a1edf4d3 | 360 | process_filter_id(this, response); |
28b23fef | 361 | } |
c61341a5 | 362 | process_timeout(this, response); |
962300b9 MW |
363 | DBG1(DBG_IKE, "RADIUS authentication of '%Y' successful", |
364 | this->peer); | |
4a6b84a9 MW |
365 | status = SUCCESS; |
366 | break; | |
367 | case RMC_ACCESS_REJECT: | |
368 | default: | |
962300b9 | 369 | DBG1(DBG_IKE, "RADIUS authentication of '%Y' failed", this->peer); |
4a6b84a9 MW |
370 | status = FAILED; |
371 | break; | |
372 | } | |
373 | response->destroy(response); | |
374 | } | |
375 | request->destroy(request); | |
376 | return status; | |
377 | } | |
378 | ||
58d2ef6e MW |
379 | METHOD(eap_method_t, get_type, eap_type_t, |
380 | private_eap_radius_t *this, u_int32_t *vendor) | |
4a6b84a9 MW |
381 | { |
382 | *vendor = this->vendor; | |
383 | return this->type; | |
384 | } | |
385 | ||
58d2ef6e | 386 | METHOD(eap_method_t, get_msk, status_t, |
ce7967c5 | 387 | private_eap_radius_t *this, chunk_t *out) |
4a6b84a9 | 388 | { |
ce7967c5 MW |
389 | chunk_t msk; |
390 | ||
391 | msk = this->client->get_msk(this->client); | |
392 | if (msk.len) | |
4a6b84a9 | 393 | { |
ce7967c5 | 394 | *out = msk; |
4a6b84a9 MW |
395 | return SUCCESS; |
396 | } | |
397 | return FAILED; | |
398 | } | |
399 | ||
ab5e0873 AS |
400 | METHOD(eap_method_t, get_identifier, u_int8_t, |
401 | private_eap_radius_t *this) | |
402 | { | |
403 | return this->identifier; | |
404 | } | |
405 | ||
406 | METHOD(eap_method_t, set_identifier, void, | |
407 | private_eap_radius_t *this, u_int8_t identifier) | |
408 | { | |
409 | this->identifier = identifier; | |
410 | } | |
411 | ||
58d2ef6e MW |
412 | METHOD(eap_method_t, is_mutual, bool, |
413 | private_eap_radius_t *this) | |
4a6b84a9 MW |
414 | { |
415 | switch (this->type) | |
416 | { | |
417 | case EAP_AKA: | |
418 | case EAP_SIM: | |
419 | return TRUE; | |
420 | default: | |
421 | return FALSE; | |
422 | } | |
423 | } | |
424 | ||
58d2ef6e MW |
425 | METHOD(eap_method_t, destroy, void, |
426 | private_eap_radius_t *this) | |
4a6b84a9 MW |
427 | { |
428 | this->peer->destroy(this->peer); | |
429 | this->server->destroy(this->server); | |
430 | this->client->destroy(this->client); | |
4a6b84a9 MW |
431 | free(this); |
432 | } | |
433 | ||
434 | /** | |
435 | * Generic constructor | |
436 | */ | |
437 | eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer) | |
438 | { | |
58d2ef6e MW |
439 | private_eap_radius_t *this; |
440 | ||
441 | INIT(this, | |
ba31fe1f MW |
442 | .public = { |
443 | .eap_method = { | |
444 | .initiate = _initiate, | |
445 | .process = _process, | |
446 | .get_type = _get_type, | |
447 | .is_mutual = _is_mutual, | |
448 | .get_msk = _get_msk, | |
ab5e0873 AS |
449 | .get_identifier = _get_identifier, |
450 | .set_identifier = _set_identifier, | |
ba31fe1f MW |
451 | .destroy = _destroy, |
452 | }, | |
58d2ef6e MW |
453 | }, |
454 | /* initially EAP_RADIUS, but is set to the method selected by RADIUS */ | |
455 | .type = EAP_RADIUS, | |
456 | .eap_start = lib->settings->get_bool(lib->settings, | |
457 | "charon.plugins.eap-radius.eap_start", FALSE), | |
5b0bcfb1 | 458 | .id_prefix = lib->settings->get_str(lib->settings, |
58d2ef6e MW |
459 | "charon.plugins.eap-radius.id_prefix", ""), |
460 | .class_group = lib->settings->get_bool(lib->settings, | |
461 | "charon.plugins.eap-radius.class_group", FALSE), | |
28b23fef AS |
462 | .filter_id = lib->settings->get_bool(lib->settings, |
463 | "charon.plugins.eap-radius.filter_id", FALSE), | |
962300b9 | 464 | |
58d2ef6e | 465 | ); |
f0f94e2c | 466 | this->client = eap_radius_create_client(); |
4a6b84a9 MW |
467 | if (!this->client) |
468 | { | |
469 | free(this); | |
470 | return NULL; | |
471 | } | |
472 | this->peer = peer->clone(peer); | |
473 | this->server = server->clone(server); | |
4a6b84a9 MW |
474 | return &this->public; |
475 | } | |
476 |