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