/* * Copyright (C) 2009 Martin Willi * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "radius_client.h" #include #include #include #include #include #include /** * Default RADIUS server port, when not configured */ #define RADIUS_PORT 1812 /** * Vendor-Id of Microsoft specific attributes */ #define VENDOR_ID_MICROSOFT 311 /** * Microsoft specific vendor attributes */ #define MS_MPPE_SEND_KEY 16 #define MS_MPPE_RECV_KEY 17 typedef struct private_radius_client_t private_radius_client_t; typedef struct entry_t entry_t; /** * A socket pool entry. */ struct entry_t { /** socket file descriptor */ int fd; /** current RADIUS identifier */ u_int8_t identifier; /** hasher to use for response verification */ hasher_t *hasher; /** HMAC-MD5 signer to build Message-Authenticator attribute */ signer_t *signer; /** random number generator for RADIUS request authenticator */ rng_t *rng; }; /** * Private data of an radius_client_t object. */ struct private_radius_client_t { /** * Public radius_client_t interface. */ radius_client_t public; /** * RADIUS servers State attribute */ chunk_t state; }; /** * Global list of radius sockets, contains entry_t's */ static linked_list_t *sockets; /** * mutex to lock sockets list */ static mutex_t *mutex; /** * condvar to wait for sockets */ static condvar_t *condvar; /** * RADIUS secret */ static chunk_t secret; /** * NAS-Identifier */ static chunk_t nas_identifier; /** * Clean up socket list */ void radius_client_cleanup() { entry_t *entry; mutex->destroy(mutex); condvar->destroy(condvar); while (sockets->remove_last(sockets, (void**)&entry) == SUCCESS) { entry->rng->destroy(entry->rng); entry->hasher->destroy(entry->hasher); entry->signer->destroy(entry->signer); close(entry->fd); free(entry); } sockets->destroy(sockets); } /** * Initialize the socket list */ bool radius_client_init() { int i, count, fd; u_int16_t port; entry_t *entry; host_t *host; char *server; nas_identifier.ptr = lib->settings->get_str(lib->settings, "charon.plugins.eap_radius.nas_identifier", "strongSwan"); nas_identifier.len = strlen(nas_identifier.ptr); secret.ptr = lib->settings->get_str(lib->settings, "charon.plugins.eap_radius.secret", NULL); if (!secret.ptr) { DBG1(DBG_CFG, "no RADUIS secret defined"); return FALSE; } secret.len = strlen(secret.ptr); server = lib->settings->get_str(lib->settings, "charon.plugins.eap_radius.server", NULL); if (!server) { DBG1(DBG_CFG, "no RADUIS server defined"); return FALSE; } port = lib->settings->get_int(lib->settings, "charon.plugins.eap_radius.port", RADIUS_PORT); host = host_create_from_dns(server, 0, port); if (!host) { return FALSE; } count = lib->settings->get_int(lib->settings, "charon.plugins.eap_radius.sockets", 1); sockets = linked_list_create(); mutex = mutex_create(MUTEX_TYPE_DEFAULT); condvar = condvar_create(CONDVAR_TYPE_DEFAULT); for (i = 0; i < count; i++) { fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { DBG1(DBG_CFG, "opening RADIUS socket failed"); host->destroy(host); radius_client_cleanup(); return FALSE; } if (connect(fd, host->get_sockaddr(host), *host->get_sockaddr_len(host)) < 0) { DBG1(DBG_CFG, "connecting RADIUS socket failed"); host->destroy(host); radius_client_cleanup(); return FALSE; } entry = malloc_thing(entry_t); entry->fd = fd; /* we use per-socket crypto elements: this reduces overhead, but * is still thread-save. */ entry->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); entry->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128); entry->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); if (!entry->hasher || !entry->signer || !entry->rng) { DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required"); DESTROY_IF(entry->hasher); DESTROY_IF(entry->signer); DESTROY_IF(entry->rng); free(entry); host->destroy(host); radius_client_cleanup(); return FALSE; } entry->signer->set_key(entry->signer, secret); /* we use a random identifier, helps if we restart often (testing) */ entry->identifier = random(); sockets->insert_last(sockets, entry); } host->destroy(host); return TRUE; } /** * Get a socket from the pool, block if none available */ static entry_t* get_socket() { entry_t *entry; mutex->lock(mutex); while (sockets->remove_first(sockets, (void**)&entry) != SUCCESS) { condvar->wait(condvar, mutex); } mutex->unlock(mutex); return entry; } /** * Release a socket to the pool */ static void put_socket(entry_t *entry) { mutex->lock(mutex); sockets->insert_last(sockets, entry); mutex->unlock(mutex); condvar->signal(condvar); } /** * Save the state attribute to include in further request */ static void save_state(private_radius_client_t *this, radius_message_t *msg) { enumerator_t *enumerator; int type; chunk_t data; enumerator = msg->create_enumerator(msg); while (enumerator->enumerate(enumerator, &type, &data)) { if (type == RAT_STATE) { free(this->state.ptr); this->state = chunk_clone(data); enumerator->destroy(enumerator); return; } } enumerator->destroy(enumerator); /* no state attribute found, remove state */ chunk_free(&this->state); } /** * Implementation of radius_client_t.request */ static radius_message_t* request(private_radius_client_t *this, radius_message_t *req) { char virtual[] = {0x00,0x00,0x00,0x05}; entry_t *socket; chunk_t data; int i; socket = get_socket(); /* set Message Identifier */ req->set_identifier(req, socket->identifier++); /* we add the "Virtual" NAS-Port-Type, as we SHOULD include one */ req->add(req, RAT_NAS_PORT_TYPE, chunk_create(virtual, sizeof(virtual))); /* add our NAS-Identifier */ req->add(req, RAT_NAS_IDENTIFIER, nas_identifier); /* add State attribute, if server sent one */ if (this->state.ptr) { req->add(req, RAT_STATE, this->state); } /* sign the request */ req->sign(req, socket->rng, socket->signer); data = req->get_encoding(req); /* timeout after 2, 3, 4, 5 seconds */ for (i = 2; i <= 5; i++) { radius_message_t *response; bool retransmit = FALSE; struct timeval tv; char buf[1024]; fd_set fds; int res; if (send(socket->fd, data.ptr, data.len, 0) != data.len) { DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno)); put_socket(socket); return NULL; } tv.tv_sec = i; tv.tv_usec = 0; while (TRUE) { FD_ZERO(&fds); FD_SET(socket->fd, &fds); res = select(socket->fd + 1, &fds, NULL, NULL, &tv); /* TODO: updated tv to time not waited. Linux does this for us. */ if (res < 0) { /* failed */ DBG1(DBG_CFG, "waiting for RADIUS message failed: %s", strerror(errno)); break; } if (res == 0) { /* timeout */ DBG1(DBG_CFG, "retransmitting RADIUS message"); retransmit = TRUE; break; } res = recv(socket->fd, buf, sizeof(buf), MSG_DONTWAIT); if (res <= 0) { DBG1(DBG_CFG, "receiving RADIUS message failed: %s", strerror(errno)); break; } response = radius_message_parse_response(chunk_create(buf, res)); if (response) { if (response->verify(response, req->get_authenticator(req), secret, socket->hasher, socket->signer)) { save_state(this, response); put_socket(socket); return response; } response->destroy(response); } DBG1(DBG_CFG, "received invalid RADIUS message, ignored"); } if (!retransmit) { break; } } DBG1(DBG_CFG, "RADIUS server is not responding"); put_socket(socket); charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING); return NULL; } /** * Decrypt a MS-MPPE-Send/Recv-Key */ static chunk_t decrypt_mppe_key(private_radius_client_t *this, u_int16_t salt, chunk_t C, radius_message_t *request) { chunk_t A, R, P, seed; u_char *c, *p; hasher_t *hasher; /** * From RFC2548 (encryption): * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1) * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2) * . . . * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i) */ if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5) { return chunk_empty; } hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); if (!hasher) { return chunk_empty; } A = chunk_create((u_char*)&salt, sizeof(salt)); R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5); P = chunk_alloca(C.len); p = P.ptr; c = C.ptr; seed = chunk_cata("cc", R, A); while (c < C.ptr + C.len) { /* b(i) = MD5(S + c(i-1)) */ hasher->get_hash(hasher, secret, NULL); hasher->get_hash(hasher, seed, p); /* p(i) = b(i) xor c(1) */ memxor(p, c, HASH_SIZE_MD5); /* prepare next round */ seed = chunk_create(c, HASH_SIZE_MD5); c += HASH_SIZE_MD5; p += HASH_SIZE_MD5; } hasher->destroy(hasher); /* remove truncation, first byte is key length */ if (*P.ptr >= P.len) { /* decryption failed? */ return chunk_empty; } return chunk_clone(chunk_create(P.ptr + 1, *P.ptr)); } /** * Implementation of radius_client_t.decrypt_msk */ static chunk_t decrypt_msk(private_radius_client_t *this, radius_message_t *response, radius_message_t *request) { struct { u_int32_t id; u_int8_t type; u_int8_t length; u_int16_t salt; u_int8_t key[]; } __attribute__((packed)) *mppe_key; enumerator_t *enumerator; chunk_t data, send = chunk_empty, recv = chunk_empty; int type; enumerator = response->create_enumerator(response); while (enumerator->enumerate(enumerator, &type, &data)) { if (type == RAT_VENDOR_SPECIFIC && data.len > sizeof(*mppe_key)) { mppe_key = (void*)data.ptr; if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT && mppe_key->length == data.len - sizeof(mppe_key->id)) { data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key)); if (mppe_key->type == MS_MPPE_SEND_KEY) { send = decrypt_mppe_key(this, mppe_key->salt, data, request); } if (mppe_key->type == MS_MPPE_RECV_KEY) { recv = decrypt_mppe_key(this, mppe_key->salt, data, request); } } } } enumerator->destroy(enumerator); if (send.ptr && recv.ptr) { return chunk_cat("mm", recv, send); } chunk_clear(&send); chunk_clear(&recv); return chunk_empty; } /** * Implementation of radius_client_t.destroy. */ static void destroy(private_radius_client_t *this) { free(this->state.ptr); free(this); } /** * See header */ radius_client_t *radius_client_create() { private_radius_client_t *this = malloc_thing(private_radius_client_t); this->public.request = (radius_message_t*(*)(radius_client_t*, radius_message_t *msg))request; this->public.decrypt_msk = (chunk_t(*)(radius_client_t*, radius_message_t *, radius_message_t *))decrypt_msk; this->public.destroy = (void(*)(radius_client_t*))destroy; this->state = chunk_empty; return &this->public; }