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