Do not interpret long class attributes (such as from NPS) as group
[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
18 #include "radius_message.h"
19 #include "radius_client.h"
20
21 #include <daemon.h>
22
23 typedef struct private_eap_radius_t private_eap_radius_t;
24
25 /**
26 * Private data of an eap_radius_t object.
27 */
28 struct private_eap_radius_t {
29
30 /**
31 * Public authenticator_t interface.
32 */
33 eap_radius_t public;
34
35 /**
36 * ID of the server
37 */
38 identification_t *server;
39
40 /**
41 * ID of the peer
42 */
43 identification_t *peer;
44
45 /**
46 * EAP method type we are proxying
47 */
48 eap_type_t type;
49
50 /**
51 * EAP vendor, if any
52 */
53 u_int32_t vendor;
54
55 /**
56 * EAP MSK, if method established one
57 */
58 chunk_t msk;
59
60 /**
61 * RADIUS client instance
62 */
63 radius_client_t *client;
64
65 /**
66 * TRUE to use EAP-Start, FALSE to send EAP-Identity Response directly
67 */
68 bool eap_start;
69
70 /**
71 * Prefix to prepend to EAP identity
72 */
73 char *id_prefix;
74
75 /**
76 * Handle the Class attribute as group membership information?
77 */
78 bool class_group;
79 };
80
81 /**
82 * Add EAP-Identity to RADIUS message
83 */
84 static void add_eap_identity(private_eap_radius_t *this,
85 radius_message_t *request)
86 {
87 struct {
88 /** EAP code (REQUEST/RESPONSE) */
89 u_int8_t code;
90 /** unique message identifier */
91 u_int8_t identifier;
92 /** length of whole message */
93 u_int16_t length;
94 /** EAP type */
95 u_int8_t type;
96 /** identity data */
97 u_int8_t data[];
98 } __attribute__((__packed__)) *hdr;
99 chunk_t id, prefix;
100 size_t len;
101
102 id = this->peer->get_encoding(this->peer);
103 prefix = chunk_create(this->id_prefix, strlen(this->id_prefix));
104 len = sizeof(*hdr) + prefix.len + id.len;
105
106 hdr = alloca(len);
107 hdr->code = EAP_RESPONSE;
108 hdr->identifier = 0;
109 hdr->length = htons(len);
110 hdr->type = EAP_IDENTITY;
111 memcpy(hdr->data, prefix.ptr, prefix.len);
112 memcpy(hdr->data + prefix.len, id.ptr, id.len);
113
114 request->add(request, RAT_EAP_MESSAGE, chunk_create((u_char*)hdr, len));
115 }
116
117 /**
118 * Copy EAP-Message attribute from RADIUS message to an new EAP payload
119 */
120 static bool radius2ike(private_eap_radius_t *this,
121 radius_message_t *msg, eap_payload_t **out)
122 {
123 enumerator_t *enumerator;
124 eap_payload_t *payload;
125 chunk_t data, message = chunk_empty;
126 int type;
127
128 enumerator = msg->create_enumerator(msg);
129 while (enumerator->enumerate(enumerator, &type, &data))
130 {
131 if (type == RAT_EAP_MESSAGE && data.len)
132 {
133 message = chunk_cat("mc", message, data);
134 }
135 }
136 enumerator->destroy(enumerator);
137 if (message.len)
138 {
139 *out = payload = eap_payload_create_data(message);
140 free(message.ptr);
141 /* apply EAP method selected by RADIUS server */
142 this->type = payload->get_type(payload, &this->vendor);
143 return TRUE;
144 }
145 return FALSE;
146 }
147
148 /**
149 * Implementation of eap_method_t.initiate
150 */
151 static status_t initiate(private_eap_radius_t *this, eap_payload_t **out)
152 {
153 radius_message_t *request, *response;
154 status_t status = FAILED;
155 chunk_t username;
156
157 request = radius_message_create_request();
158 username = chunk_create(this->id_prefix, strlen(this->id_prefix));
159 username = chunk_cata("cc", username, this->peer->get_encoding(this->peer));
160 request->add(request, RAT_USER_NAME, username);
161
162 if (this->eap_start)
163 {
164 request->add(request, RAT_EAP_MESSAGE, chunk_empty);
165 }
166 else
167 {
168 add_eap_identity(this, request);
169 }
170
171 response = this->client->request(this->client, request);
172 if (response)
173 {
174 if (radius2ike(this, response, out))
175 {
176 status = NEED_MORE;
177 }
178 response->destroy(response);
179 }
180 request->destroy(request);
181 return status;
182 }
183
184 /**
185 * Handle the Class attribute as group membership information
186 */
187 static void process_class(private_eap_radius_t *this, radius_message_t *msg)
188 {
189 enumerator_t *enumerator;
190 chunk_t data;
191 int type;
192
193 enumerator = msg->create_enumerator(msg);
194 while (enumerator->enumerate(enumerator, &type, &data))
195 {
196 if (type == RAT_CLASS)
197 {
198 identification_t *id;
199 ike_sa_t *ike_sa;
200 auth_cfg_t *auth;
201
202 if (data.len >= 44)
203 { /* quirk: ignore long class attributes, these are used for
204 * other purposes by some RADIUS servers (such as NPS). */
205 continue;
206 }
207
208 ike_sa = charon->bus->get_sa(charon->bus);
209 if (ike_sa)
210 {
211 auth = ike_sa->get_auth_cfg(ike_sa, FALSE);
212 id = identification_create_from_data(data);
213 DBG1(DBG_CFG, "received group membership '%Y' from RADIUS", id);
214 auth->add(auth, AUTH_RULE_GROUP, id);
215 }
216 }
217 }
218 enumerator->destroy(enumerator);
219 }
220
221 /**
222 * Implementation of eap_method_t.process
223 */
224 static status_t process(private_eap_radius_t *this,
225 eap_payload_t *in, eap_payload_t **out)
226 {
227 radius_message_t *request, *response;
228 status_t status = FAILED;
229 chunk_t data;
230
231 request = radius_message_create_request();
232 request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer));
233 data = in->get_data(in);
234 /* fragment data suitable for RADIUS (not more than 253 bytes) */
235 while (data.len > 253)
236 {
237 request->add(request, RAT_EAP_MESSAGE, chunk_create(data.ptr, 253));
238 data = chunk_skip(data, 253);
239 }
240 request->add(request, RAT_EAP_MESSAGE, data);
241
242 response = this->client->request(this->client, request);
243 if (response)
244 {
245 switch (response->get_code(response))
246 {
247 case RMC_ACCESS_CHALLENGE:
248 if (radius2ike(this, response, out))
249 {
250 status = NEED_MORE;
251 break;
252 }
253 status = FAILED;
254 break;
255 case RMC_ACCESS_ACCEPT:
256 this->msk = this->client->decrypt_msk(this->client,
257 response, request);
258 if (this->class_group)
259 {
260 process_class(this, response);
261 }
262 status = SUCCESS;
263 break;
264 case RMC_ACCESS_REJECT:
265 default:
266 DBG1(DBG_CFG, "received %N from RADIUS server",
267 radius_message_code_names, response->get_code(response));
268 status = FAILED;
269 break;
270 }
271 response->destroy(response);
272 }
273 request->destroy(request);
274 return status;
275 }
276
277 /**
278 * Implementation of eap_method_t.get_type.
279 */
280 static eap_type_t get_type(private_eap_radius_t *this, u_int32_t *vendor)
281 {
282 *vendor = this->vendor;
283 return this->type;
284 }
285
286 /**
287 * Implementation of eap_method_t.get_msk.
288 */
289 static status_t get_msk(private_eap_radius_t *this, chunk_t *msk)
290 {
291 if (this->msk.ptr)
292 {
293 *msk = this->msk;
294 return SUCCESS;
295 }
296 return FAILED;
297 }
298
299 /**
300 * Implementation of eap_method_t.is_mutual.
301 */
302 static bool is_mutual(private_eap_radius_t *this)
303 {
304 switch (this->type)
305 {
306 case EAP_AKA:
307 case EAP_SIM:
308 return TRUE;
309 default:
310 return FALSE;
311 }
312 }
313
314 /**
315 * Implementation of eap_method_t.destroy.
316 */
317 static void destroy(private_eap_radius_t *this)
318 {
319 this->peer->destroy(this->peer);
320 this->server->destroy(this->server);
321 this->client->destroy(this->client);
322 chunk_clear(&this->msk);
323 free(this);
324 }
325
326 /**
327 * Generic constructor
328 */
329 eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
330 {
331 private_eap_radius_t *this = malloc_thing(private_eap_radius_t);
332
333 this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate;
334 this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process;
335 this->public.eap_method_interface.get_type = (eap_type_t(*)(eap_method_t*,u_int32_t*))get_type;
336 this->public.eap_method_interface.is_mutual = (bool(*)(eap_method_t*))is_mutual;
337 this->public.eap_method_interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk;
338 this->public.eap_method_interface.destroy = (void(*)(eap_method_t*))destroy;
339
340 this->client = radius_client_create();
341 if (!this->client)
342 {
343 free(this);
344 return NULL;
345 }
346 this->peer = peer->clone(peer);
347 this->server = server->clone(server);
348 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
349 this->type = EAP_RADIUS;
350 this->vendor = 0;
351 this->msk = chunk_empty;
352 this->eap_start = lib->settings->get_bool(lib->settings,
353 "charon.plugins.eap-radius.eap_start", FALSE);
354 this->id_prefix = lib->settings->get_str(lib->settings,
355 "charon.plugins.eap-radius.id_prefix", "");
356 this->class_group = lib->settings->get_bool(lib->settings,
357 "charon.plugins.eap-radius.class_group", FALSE);
358 return &this->public;
359 }
360