reintegrated eap-radius branch into trunk
[strongswan.git] / src / charon / plugins / eap_radius / radius_client.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 * $Id$
16 */
17
18 #include "radius_client.h"
19
20 #include <unistd.h>
21 #include <errno.h>
22
23 #include <daemon.h>
24 #include <utils/host.h>
25 #include <utils/linked_list.h>
26 #include <utils/mutex.h>
27
28 /**
29 * Default RADIUS server port, when not configured
30 */
31 #define RADIUS_PORT 1812
32
33 /**
34 * Vendor-Id of Microsoft specific attributes
35 */
36 #define VENDOR_ID_MICROSOFT 311
37
38 /**
39 * Microsoft specific vendor attributes
40 */
41 #define MS_MPPE_SEND_KEY 16
42 #define MS_MPPE_RECV_KEY 17
43
44 typedef struct private_radius_client_t private_radius_client_t;
45
46 typedef struct entry_t entry_t;
47
48 /**
49 * A socket pool entry.
50 */
51 struct entry_t {
52 /** socket file descriptor */
53 int fd;
54 /** current RADIUS identifier */
55 u_int8_t identifier;
56 /** hasher to use for response verification */
57 hasher_t *hasher;
58 /** HMAC-MD5 signer to build Message-Authenticator attribute */
59 signer_t *signer;
60 /** random number generator for RADIUS request authenticator */
61 rng_t *rng;
62 };
63
64 /**
65 * Private data of an radius_client_t object.
66 */
67 struct private_radius_client_t {
68
69 /**
70 * Public radius_client_t interface.
71 */
72 radius_client_t public;
73
74 /**
75 * The clients socket from the pool
76 */
77 entry_t *socket;
78
79 /**
80 * RADIUS servers State attribute
81 */
82 chunk_t state;
83 };
84
85 /**
86 * Global list of radius sockets, contains entry_t's
87 */
88 static linked_list_t *sockets;
89
90 /**
91 * mutex to lock sockets list
92 */
93 static mutex_t *mutex;
94
95 /**
96 * condvar to wait for sockets
97 */
98 static condvar_t *condvar;
99
100 /**
101 * RADIUS secret
102 */
103 static chunk_t secret;
104
105 /**
106 * NAS-Identifier
107 */
108 static chunk_t nas_identifier;
109
110 /**
111 * Clean up socket list
112 */
113 void radius_client_cleanup()
114 {
115 entry_t *entry;
116
117 mutex->destroy(mutex);
118 condvar->destroy(condvar);
119 while (sockets->remove_last(sockets, (void**)&entry) == SUCCESS)
120 {
121 entry->rng->destroy(entry->rng);
122 entry->hasher->destroy(entry->hasher);
123 entry->signer->destroy(entry->signer);
124 close(entry->fd);
125 free(entry);
126 }
127 sockets->destroy(sockets);
128 }
129
130 /**
131 * Initialize the socket list
132 */
133 bool radius_client_init()
134 {
135 int i, count, fd;
136 u_int16_t port;
137 entry_t *entry;
138 host_t *host;
139 char *server;
140
141 nas_identifier.ptr = lib->settings->get_str(lib->settings,
142 "charon.plugins.eap_radius.nas_identifier", "strongSwan");
143 nas_identifier.len = strlen(nas_identifier.ptr);
144
145 secret.ptr = lib->settings->get_str(lib->settings,
146 "charon.plugins.eap_radius.secret", NULL);
147 if (!secret.ptr)
148 {
149 DBG1(DBG_CFG, "no RADUIS secret defined");
150 return FALSE;
151 }
152 secret.len = strlen(secret.ptr);
153 server = lib->settings->get_str(lib->settings,
154 "charon.plugins.eap_radius.server", NULL);
155 if (!server)
156 {
157 DBG1(DBG_CFG, "no RADUIS server defined");
158 return FALSE;
159 }
160 port = lib->settings->get_int(lib->settings,
161 "charon.plugins.eap_radius.port", RADIUS_PORT);
162 host = host_create_from_dns(server, 0, port);
163 if (!host)
164 {
165 return FALSE;
166 }
167 count = lib->settings->get_int(lib->settings,
168 "charon.plugins.eap_radius.sockets", 5);
169
170 sockets = linked_list_create();
171 mutex = mutex_create(MUTEX_DEFAULT);
172 condvar = condvar_create(CONDVAR_DEFAULT);
173 for (i = 0; i < count; i++)
174 {
175 fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP);
176 if (fd < 0)
177 {
178 DBG1(DBG_CFG, "opening RADIUS socket failed");
179 host->destroy(host);
180 radius_client_cleanup();
181 return FALSE;
182 }
183 if (connect(fd, host->get_sockaddr(host),
184 *host->get_sockaddr_len(host)) < 0)
185 {
186 DBG1(DBG_CFG, "connecting RADIUS socket failed");
187 host->destroy(host);
188 radius_client_cleanup();
189 return FALSE;
190 }
191 entry = malloc_thing(entry_t);
192 entry->fd = fd;
193 /* we use per-socket crypto elements: this reduces overhead, but
194 * is still thread-save. */
195 entry->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
196 entry->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128);
197 entry->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
198 if (!entry->hasher || !entry->signer || !entry->rng)
199 {
200 DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
201 DESTROY_IF(entry->hasher);
202 DESTROY_IF(entry->signer);
203 DESTROY_IF(entry->rng);
204 free(entry);
205 host->destroy(host);
206 radius_client_cleanup();
207 return FALSE;
208 }
209 entry->signer->set_key(entry->signer, secret);
210 /* we use a random identifier, helps if we restart often (testing) */
211 entry->identifier = random();
212 sockets->insert_last(sockets, entry);
213 }
214 host->destroy(host);
215 return TRUE;
216 }
217
218 /**
219 * Get a socket from the pool, block if none available
220 */
221 static entry_t* get_socket()
222 {
223 entry_t *entry;
224
225 mutex->lock(mutex);
226 while (sockets->remove_first(sockets, (void**)&entry) != SUCCESS)
227 {
228 condvar->wait(condvar, mutex);
229 }
230 mutex->unlock(mutex);
231 return entry;
232 }
233
234 /**
235 * Release a socket to the pool
236 */
237 static void put_socket(entry_t *entry)
238 {
239 mutex->lock(mutex);
240 sockets->insert_last(sockets, entry);
241 mutex->unlock(mutex);
242 condvar->signal(condvar);
243 }
244
245 /**
246 * Save the state attribute to include in further request
247 */
248 static void save_state(private_radius_client_t *this, radius_message_t *msg)
249 {
250 enumerator_t *enumerator;
251 int type;
252 chunk_t data;
253
254 enumerator = msg->create_enumerator(msg);
255 while (enumerator->enumerate(enumerator, &type, &data))
256 {
257 if (type == RAT_STATE)
258 {
259 free(this->state.ptr);
260 this->state = chunk_clone(data);
261 enumerator->destroy(enumerator);
262 return;
263 }
264 }
265 enumerator->destroy(enumerator);
266 /* no state attribute found, remove state */
267 chunk_free(&this->state);
268 }
269
270 /**
271 * Implementation of radius_client_t.request
272 */
273 static radius_message_t* request(private_radius_client_t *this,
274 radius_message_t *req)
275 {
276 char virtual[] = {0x00,0x00,0x00,0x05};
277 radius_message_t *response;
278 chunk_t data;
279 fd_set fds;
280 int i;
281
282 /* set Message Identifier */
283 req->set_identifier(req, this->socket->identifier++);
284 /* we add the "Virtual" NAS-Port-Type, as we SHOULD include one */
285 req->add(req, RAT_NAS_PORT_TYPE, chunk_create(virtual, sizeof(virtual)));
286 /* add our NAS-Identifier */
287 req->add(req, RAT_NAS_IDENTIFIER, nas_identifier);
288 /* add State attribute, if server sent one */
289 if (this->state.ptr)
290 {
291 req->add(req, RAT_STATE, this->state);
292 }
293 /* sign the request */
294 req->sign(req, this->socket->rng, this->socket->signer);
295
296 data = req->get_encoding(req);
297 FD_ZERO(&fds);
298 FD_SET(this->socket->fd, &fds);
299 /* timeout after 2, 3, 4, 5 seconds */
300 for (i = 2; i <= 5; i++)
301 {
302 bool retransmit = FALSE;
303 struct timeval tv;
304 char buf[1024];
305 int res, retry = 0;
306
307 if (send(this->socket->fd, data.ptr, data.len, 0) != data.len)
308 {
309 DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
310 return NULL;
311 }
312 while (TRUE)
313 {
314 tv.tv_sec = i;
315 tv.tv_usec = 0;
316
317 res = select(this->socket->fd + 1, &fds, NULL, NULL, &tv);
318 if (res < 0)
319 { /* failed */
320 DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
321 strerror(errno));
322 break;
323 }
324 if (res == 0)
325 { /* timeout */
326 DBG1(DBG_CFG, "retransmitting RADIUS message");
327 retransmit = TRUE;
328 break;
329 }
330 res = recv(this->socket->fd, buf, sizeof(buf), MSG_DONTWAIT);
331 if (res <= 0)
332 {
333 DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
334 strerror(errno));
335 break;
336 }
337 response = radius_message_parse_response(chunk_create(buf, res));
338 if (response)
339 {
340 if (response->verify(response, req->get_authenticator(req),
341 secret, this->socket->hasher, this->socket->signer))
342 {
343 save_state(this, response);
344 return response;
345 }
346 response->destroy(response);
347 }
348 /* might be a maliciously injected packet, read onother one.
349 * Limit the number of retries, an attacker could us trick into
350 * a loop otherwise. */
351 if (retry++ > 5)
352 {
353 break;
354 }
355 }
356 if (!retransmit)
357 {
358 break;
359 }
360 }
361 DBG1(DBG_CFG, "RADIUS server is not responding");
362 return NULL;
363 }
364
365 /**
366 * Decrypt a MS-MPPE-Send/Recv-Key
367 */
368 static chunk_t decrypt_mppe_key(private_radius_client_t *this, u_int16_t salt,
369 chunk_t C, radius_message_t *request)
370 {
371 chunk_t A, R, P, seed;
372 u_char *c, *p;
373
374 /**
375 * From RFC2548 (encryption):
376 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
377 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
378 * . . .
379 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
380 */
381
382 if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5)
383 {
384 return chunk_empty;
385 }
386
387 A = chunk_create((u_char*)&salt, sizeof(salt));
388 R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5);
389 P = chunk_alloca(C.len);
390 p = P.ptr;
391 c = C.ptr;
392
393 seed = chunk_cata("cc", R, A);
394
395 while (c < C.ptr + C.len)
396 {
397 /* b(i) = MD5(S + c(i-1)) */
398 this->socket->hasher->get_hash(this->socket->hasher, secret, NULL);
399 this->socket->hasher->get_hash(this->socket->hasher, seed, p);
400
401 /* p(i) = b(i) xor c(1) */
402 memxor(p, c, HASH_SIZE_MD5);
403
404 /* prepare next round */
405 seed = chunk_create(c, HASH_SIZE_MD5);
406 c += HASH_SIZE_MD5;
407 p += HASH_SIZE_MD5;
408 }
409
410 /* remove truncation, first byte is key length */
411 if (*P.ptr >= P.len)
412 { /* decryption failed? */
413 return chunk_empty;
414 }
415 return chunk_clone(chunk_create(P.ptr + 1, *P.ptr));
416 }
417
418 /**
419 * Implementation of radius_client_t.decrypt_msk
420 */
421 static chunk_t decrypt_msk(private_radius_client_t *this,
422 radius_message_t *response, radius_message_t *request)
423 {
424 struct {
425 u_int32_t id;
426 u_int8_t type;
427 u_int8_t length;
428 u_int16_t salt;
429 u_int8_t key[];
430 } __attribute__((packed)) *mppe_key;
431 enumerator_t *enumerator;
432 chunk_t data, send = chunk_empty, recv = chunk_empty;
433 int type;
434
435 enumerator = response->create_enumerator(response);
436 while (enumerator->enumerate(enumerator, &type, &data))
437 {
438 if (type == RAT_VENDOR_SPECIFIC &&
439 data.len > sizeof(*mppe_key))
440 {
441 mppe_key = (void*)data.ptr;
442 if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT &&
443 mppe_key->length == data.len - sizeof(mppe_key->id))
444 {
445 data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key));
446 if (mppe_key->type == MS_MPPE_SEND_KEY)
447 {
448 send = decrypt_mppe_key(this, mppe_key->salt, data, request);
449 }
450 if (mppe_key->type == MS_MPPE_RECV_KEY)
451 {
452 recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
453 }
454 }
455 }
456 }
457 enumerator->destroy(enumerator);
458 if (send.ptr && recv.ptr)
459 {
460 return chunk_cat("mm", recv, send);
461 }
462 chunk_clear(&send);
463 chunk_clear(&recv);
464 return chunk_empty;
465 }
466
467 /**
468 * Implementation of radius_client_t.destroy.
469 */
470 static void destroy(private_radius_client_t *this)
471 {
472 put_socket(this->socket);
473 free(this->state.ptr);
474 free(this);
475 }
476
477 /**
478 * See header
479 */
480 radius_client_t *radius_client_create()
481 {
482 private_radius_client_t *this = malloc_thing(private_radius_client_t);
483
484 this->public.request = (radius_message_t*(*)(radius_client_t*, radius_message_t *msg))request;
485 this->public.decrypt_msk = (chunk_t(*)(radius_client_t*, radius_message_t *, radius_message_t *))decrypt_msk;
486 this->public.destroy = (void(*)(radius_client_t*))destroy;
487
488 this->socket = get_socket();
489 this->state = chunk_empty;
490
491 return &this->public;
492 }
493