Added support for group membership information containted in the RADIUS class attribute
[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 ike_sa_t *ike_sa;
199 auth_cfg_t *auth;
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 auth->add(auth, AUTH_RULE_GROUP,
206 identification_create_from_data(data));
207 }
208 }
209 }
210 enumerator->destroy(enumerator);
211 }
212
213 /**
214 * Implementation of eap_method_t.process
215 */
216 static status_t process(private_eap_radius_t *this,
217 eap_payload_t *in, eap_payload_t **out)
218 {
219 radius_message_t *request, *response;
220 status_t status = FAILED;
221 chunk_t data;
222
223 request = radius_message_create_request();
224 request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer));
225 data = in->get_data(in);
226 /* fragment data suitable for RADIUS (not more than 253 bytes) */
227 while (data.len > 253)
228 {
229 request->add(request, RAT_EAP_MESSAGE, chunk_create(data.ptr, 253));
230 data = chunk_skip(data, 253);
231 }
232 request->add(request, RAT_EAP_MESSAGE, data);
233
234 response = this->client->request(this->client, request);
235 if (response)
236 {
237 switch (response->get_code(response))
238 {
239 case RMC_ACCESS_CHALLENGE:
240 if (radius2ike(this, response, out))
241 {
242 status = NEED_MORE;
243 break;
244 }
245 status = FAILED;
246 break;
247 case RMC_ACCESS_ACCEPT:
248 this->msk = this->client->decrypt_msk(this->client,
249 response, request);
250 if (this->class_group)
251 {
252 process_class(this, response);
253 }
254 status = SUCCESS;
255 break;
256 case RMC_ACCESS_REJECT:
257 default:
258 DBG1(DBG_CFG, "received %N from RADIUS server",
259 radius_message_code_names, response->get_code(response));
260 status = FAILED;
261 break;
262 }
263 response->destroy(response);
264 }
265 request->destroy(request);
266 return status;
267 }
268
269 /**
270 * Implementation of eap_method_t.get_type.
271 */
272 static eap_type_t get_type(private_eap_radius_t *this, u_int32_t *vendor)
273 {
274 *vendor = this->vendor;
275 return this->type;
276 }
277
278 /**
279 * Implementation of eap_method_t.get_msk.
280 */
281 static status_t get_msk(private_eap_radius_t *this, chunk_t *msk)
282 {
283 if (this->msk.ptr)
284 {
285 *msk = this->msk;
286 return SUCCESS;
287 }
288 return FAILED;
289 }
290
291 /**
292 * Implementation of eap_method_t.is_mutual.
293 */
294 static bool is_mutual(private_eap_radius_t *this)
295 {
296 switch (this->type)
297 {
298 case EAP_AKA:
299 case EAP_SIM:
300 return TRUE;
301 default:
302 return FALSE;
303 }
304 }
305
306 /**
307 * Implementation of eap_method_t.destroy.
308 */
309 static void destroy(private_eap_radius_t *this)
310 {
311 this->peer->destroy(this->peer);
312 this->server->destroy(this->server);
313 this->client->destroy(this->client);
314 chunk_clear(&this->msk);
315 free(this);
316 }
317
318 /**
319 * Generic constructor
320 */
321 eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
322 {
323 private_eap_radius_t *this = malloc_thing(private_eap_radius_t);
324
325 this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate;
326 this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process;
327 this->public.eap_method_interface.get_type = (eap_type_t(*)(eap_method_t*,u_int32_t*))get_type;
328 this->public.eap_method_interface.is_mutual = (bool(*)(eap_method_t*))is_mutual;
329 this->public.eap_method_interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk;
330 this->public.eap_method_interface.destroy = (void(*)(eap_method_t*))destroy;
331
332 this->client = radius_client_create();
333 if (!this->client)
334 {
335 free(this);
336 return NULL;
337 }
338 this->peer = peer->clone(peer);
339 this->server = server->clone(server);
340 /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
341 this->type = EAP_RADIUS;
342 this->vendor = 0;
343 this->msk = chunk_empty;
344 this->eap_start = lib->settings->get_bool(lib->settings,
345 "charon.plugins.eap-radius.eap_start", FALSE);
346 this->id_prefix = lib->settings->get_str(lib->settings,
347 "charon.plugins.eap-radius.id_prefix", "");
348 this->class_group = lib->settings->get_bool(lib->settings,
349 "charon.plugins.eap-radius.class_group", FALSE);
350 return &this->public;
351 }
352