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