3038e871d6eca332dff97235f8b27c9ba7015686
[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 METHOD(eap_method_t, initiate, status_t,
149 private_eap_radius_t *this, eap_payload_t **out)
150 {
151 radius_message_t *request, *response;
152 status_t status = FAILED;
153 chunk_t username;
154
155 request = radius_message_create_request();
156 username = chunk_create(this->id_prefix, strlen(this->id_prefix));
157 username = chunk_cata("cc", username, this->peer->get_encoding(this->peer));
158 request->add(request, RAT_USER_NAME, username);
159
160 if (this->eap_start)
161 {
162 request->add(request, RAT_EAP_MESSAGE, chunk_empty);
163 }
164 else
165 {
166 add_eap_identity(this, request);
167 }
168
169 response = this->client->request(this->client, request);
170 if (response)
171 {
172 if (radius2ike(this, response, out))
173 {
174 status = NEED_MORE;
175 }
176 response->destroy(response);
177 }
178 request->destroy(request);
179 return status;
180 }
181
182 /**
183 * Handle the Class attribute as group membership information
184 */
185 static void process_class(private_eap_radius_t *this, radius_message_t *msg)
186 {
187 enumerator_t *enumerator;
188 chunk_t data;
189 int type;
190
191 enumerator = msg->create_enumerator(msg);
192 while (enumerator->enumerate(enumerator, &type, &data))
193 {
194 if (type == RAT_CLASS)
195 {
196 identification_t *id;
197 ike_sa_t *ike_sa;
198 auth_cfg_t *auth;
199
200 if (data.len >= 44)
201 { /* quirk: ignore long class attributes, these are used for
202 * other purposes by some RADIUS servers (such as NPS). */
203 continue;
204 }
205
206 ike_sa = charon->bus->get_sa(charon->bus);
207 if (ike_sa)
208 {
209 auth = ike_sa->get_auth_cfg(ike_sa, FALSE);
210 id = identification_create_from_data(data);
211 DBG1(DBG_CFG, "received group membership '%Y' from RADIUS", id);
212 auth->add(auth, AUTH_RULE_GROUP, id);
213 }
214 }
215 }
216 enumerator->destroy(enumerator);
217 }
218
219 METHOD(eap_method_t, process, status_t,
220 private_eap_radius_t *this, eap_payload_t *in, eap_payload_t **out)
221 {
222 radius_message_t *request, *response;
223 status_t status = FAILED;
224 chunk_t data;
225
226 request = radius_message_create_request();
227 request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer));
228 data = in->get_data(in);
229 /* fragment data suitable for RADIUS (not more than 253 bytes) */
230 while (data.len > 253)
231 {
232 request->add(request, RAT_EAP_MESSAGE, chunk_create(data.ptr, 253));
233 data = chunk_skip(data, 253);
234 }
235 request->add(request, RAT_EAP_MESSAGE, data);
236
237 response = this->client->request(this->client, request);
238 if (response)
239 {
240 switch (response->get_code(response))
241 {
242 case RMC_ACCESS_CHALLENGE:
243 if (radius2ike(this, response, out))
244 {
245 status = NEED_MORE;
246 break;
247 }
248 status = FAILED;
249 break;
250 case RMC_ACCESS_ACCEPT:
251 this->msk = this->client->decrypt_msk(this->client,
252 response, request);
253 if (this->class_group)
254 {
255 process_class(this, response);
256 }
257 status = SUCCESS;
258 break;
259 case RMC_ACCESS_REJECT:
260 default:
261 DBG1(DBG_CFG, "received %N from RADIUS server",
262 radius_message_code_names, response->get_code(response));
263 status = FAILED;
264 break;
265 }
266 response->destroy(response);
267 }
268 request->destroy(request);
269 return status;
270 }
271
272 METHOD(eap_method_t, get_type, eap_type_t,
273 private_eap_radius_t *this, u_int32_t *vendor)
274 {
275 *vendor = this->vendor;
276 return this->type;
277 }
278
279 METHOD(eap_method_t, get_msk, status_t,
280 private_eap_radius_t *this, chunk_t *msk)
281 {
282 if (this->msk.ptr)
283 {
284 *msk = this->msk;
285 return SUCCESS;
286 }
287 return FAILED;
288 }
289
290 METHOD(eap_method_t, is_mutual, bool,
291 private_eap_radius_t *this)
292 {
293 switch (this->type)
294 {
295 case EAP_AKA:
296 case EAP_SIM:
297 return TRUE;
298 default:
299 return FALSE;
300 }
301 }
302
303 METHOD(eap_method_t, destroy, void,
304 private_eap_radius_t *this)
305 {
306 this->peer->destroy(this->peer);
307 this->server->destroy(this->server);
308 this->client->destroy(this->client);
309 chunk_clear(&this->msk);
310 free(this);
311 }
312
313 /**
314 * Generic constructor
315 */
316 eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
317 {
318 private_eap_radius_t *this;
319
320 INIT(this,
321 .public.eap_method_interface = {
322 .initiate = _initiate,
323 .process = _process,
324 .get_type = _get_type,
325 .is_mutual = _is_mutual,
326 .get_msk = _get_msk,
327 .destroy = _destroy,
328 },
329 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
330 .type = EAP_RADIUS,
331 .eap_start = lib->settings->get_bool(lib->settings,
332 "charon.plugins.eap-radius.eap_start", FALSE),
333 .id_prefix = lib->settings->get_str(lib->settings,
334 "charon.plugins.eap-radius.id_prefix", ""),
335 .class_group = lib->settings->get_bool(lib->settings,
336 "charon.plugins.eap-radius.class_group", FALSE),
337 );
338 this->client = radius_client_create();
339 if (!this->client)
340 {
341 free(this);
342 return NULL;
343 }
344 this->peer = peer->clone(peer);
345 this->server = server->clone(server);
346 return &this->public;
347 }
348