Annotation of embedaddon/strongswan/src/libradius/radius_socket.c, revision 1.1
1.1 ! misho 1: /*
! 2: * Copyright (C) 2010 Martin Willi
! 3: * Copyright (C) 2010 revosec AG
! 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: /*
! 17: * Copyright (C) 2015 Thom Troy
! 18: *
! 19: * Permission is hereby granted, free of charge, to any person obtaining a copy
! 20: * of this software and associated documentation files (the "Software"), to deal
! 21: * in the Software without restriction, including without limitation the rights
! 22: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
! 23: * copies of the Software, and to permit persons to whom the Software is
! 24: * furnished to do so, subject to the following conditions:
! 25: *
! 26: * The above copyright notice and this permission notice shall be included in
! 27: * all copies or substantial portions of the Software.
! 28: *
! 29: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
! 30: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
! 31: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
! 32: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
! 33: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
! 34: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
! 35: * THE SOFTWARE.
! 36: */
! 37:
! 38: #include "radius_socket.h"
! 39: #include "radius_mppe.h"
! 40:
! 41: #include <errno.h>
! 42: #include <unistd.h>
! 43: #include <math.h>
! 44:
! 45: #include <pen/pen.h>
! 46: #include <utils/debug.h>
! 47:
! 48: typedef struct private_radius_socket_t private_radius_socket_t;
! 49:
! 50: /**
! 51: * Private data of an radius_socket_t object.
! 52: */
! 53: struct private_radius_socket_t {
! 54:
! 55: /**
! 56: * Public radius_socket_t interface.
! 57: */
! 58: radius_socket_t public;
! 59:
! 60: /**
! 61: * Server port for authentication
! 62: */
! 63: uint16_t auth_port;
! 64:
! 65: /**
! 66: * socket file descriptor for authentication
! 67: */
! 68: int auth_fd;
! 69:
! 70: /**
! 71: * Server port for accounting
! 72: */
! 73: uint16_t acct_port;
! 74:
! 75: /**
! 76: * socket file descriptor for accounting
! 77: */
! 78: int acct_fd;
! 79:
! 80: /**
! 81: * Server address
! 82: */
! 83: char *address;
! 84:
! 85: /**
! 86: * current RADIUS identifier
! 87: */
! 88: uint8_t identifier;
! 89:
! 90: /**
! 91: * hasher to use for response verification
! 92: */
! 93: hasher_t *hasher;
! 94:
! 95: /**
! 96: * HMAC-MD5 signer to build Message-Authenticator attribute
! 97: */
! 98: signer_t *signer;
! 99:
! 100: /**
! 101: * random number generator for RADIUS request authenticator
! 102: */
! 103: rng_t *rng;
! 104:
! 105: /**
! 106: * RADIUS secret
! 107: */
! 108: chunk_t secret;
! 109:
! 110: /**
! 111: * Number of times we retransmit messages before giving up
! 112: */
! 113: u_int retransmit_tries;
! 114:
! 115: /**
! 116: * Retransmission timeout
! 117: */
! 118: double retransmit_timeout;
! 119:
! 120: /**
! 121: * Base to calculate retransmission timeout
! 122: */
! 123: double retransmit_base;
! 124: };
! 125:
! 126: /**
! 127: * Check or establish RADIUS connection
! 128: */
! 129: static bool check_connection(private_radius_socket_t *this,
! 130: int *fd, uint16_t port)
! 131: {
! 132: if (*fd == -1)
! 133: {
! 134: host_t *server;
! 135:
! 136: server = host_create_from_dns(this->address, AF_UNSPEC, port);
! 137: if (!server)
! 138: {
! 139: DBG1(DBG_CFG, "resolving RADIUS server address '%s' failed",
! 140: this->address);
! 141: return FALSE;
! 142: }
! 143: *fd = socket(server->get_family(server), SOCK_DGRAM, IPPROTO_UDP);
! 144: if (*fd == -1)
! 145: {
! 146: DBG1(DBG_CFG, "opening RADIUS socket for %#H failed: %s",
! 147: server, strerror(errno));
! 148: server->destroy(server);
! 149: return FALSE;
! 150: }
! 151: if (connect(*fd, server->get_sockaddr(server),
! 152: *server->get_sockaddr_len(server)) < 0)
! 153: {
! 154: DBG1(DBG_CFG, "connecting RADIUS socket to %#H failed: %s",
! 155: server, strerror(errno));
! 156: server->destroy(server);
! 157: close(*fd);
! 158: *fd = -1;
! 159: return FALSE;
! 160: }
! 161: server->destroy(server);
! 162: }
! 163: return TRUE;
! 164: }
! 165:
! 166: /**
! 167: * Receive the response to the message with the given ID
! 168: */
! 169: static status_t receive_response(int fd, int timeout, uint8_t id,
! 170: radius_message_t **response)
! 171: {
! 172: radius_message_t *msg;
! 173: char buf[4096];
! 174: int res;
! 175: struct pollfd pfd = {
! 176: .fd = fd,
! 177: .events = POLLIN,
! 178: };
! 179:
! 180: while (TRUE)
! 181: {
! 182: res = poll(&pfd, 1, timeout);
! 183: if (res < 0)
! 184: {
! 185: DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
! 186: strerror(errno));
! 187: return FAILED;
! 188: }
! 189: if (res == 0)
! 190: { /* timeout */
! 191: return OUT_OF_RES;
! 192: }
! 193: res = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
! 194: if (res <= 0)
! 195: {
! 196: DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
! 197: strerror(errno));
! 198: return FAILED;
! 199: }
! 200: msg = radius_message_parse(chunk_create(buf, res));
! 201: if (!msg)
! 202: {
! 203: DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
! 204: return FAILED;
! 205: }
! 206: if (id != msg->get_identifier(msg))
! 207: {
! 208: /* we haven't received the response to our current request, but
! 209: * perhaps one for an earlier request for which we didn't wait
! 210: * long enough */
! 211: DBG1(DBG_CFG, "received RADIUS message with unexpected ID %d "
! 212: "[%d expected], ignored", msg->get_identifier(msg), id);
! 213: msg->destroy(msg);
! 214: continue;
! 215: }
! 216: *response = msg;
! 217: return SUCCESS;
! 218: }
! 219: }
! 220:
! 221: METHOD(radius_socket_t, request, radius_message_t*,
! 222: private_radius_socket_t *this, radius_message_t *request)
! 223: {
! 224: radius_message_t *response;
! 225: chunk_t data;
! 226: int *fd, retransmit = 0, timeout;
! 227: uint16_t port;
! 228: rng_t *rng = NULL;
! 229:
! 230: if (request->get_code(request) == RMC_ACCOUNTING_REQUEST)
! 231: {
! 232: fd = &this->acct_fd;
! 233: port = this->acct_port;
! 234: }
! 235: else
! 236: {
! 237: fd = &this->auth_fd;
! 238: port = this->auth_port;
! 239: rng = this->rng;
! 240: }
! 241:
! 242: /* set Message Identifier */
! 243: request->set_identifier(request, this->identifier++);
! 244: /* sign the request */
! 245: if (!request->sign(request, NULL, this->secret, this->hasher, this->signer,
! 246: rng, rng != NULL))
! 247: {
! 248: return NULL;
! 249: }
! 250:
! 251: if (!check_connection(this, fd, port))
! 252: {
! 253: return NULL;
! 254: }
! 255:
! 256: data = request->get_encoding(request);
! 257: DBG3(DBG_CFG, "%B", &data);
! 258:
! 259: while (retransmit < this->retransmit_tries)
! 260: {
! 261: timeout = (int)(this->retransmit_timeout * 1000.0 *
! 262: pow(this->retransmit_base, retransmit));
! 263: if (retransmit)
! 264: {
! 265: DBG1(DBG_CFG, "retransmit %d of RADIUS %N (timeout: %.1fs)",
! 266: retransmit, radius_message_code_names,
! 267: request->get_code(request), timeout/1000.0);
! 268: }
! 269: if (send(*fd, data.ptr, data.len, 0) != data.len)
! 270: {
! 271: DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
! 272: return NULL;
! 273: }
! 274: switch (receive_response(*fd, timeout, request->get_identifier(request),
! 275: &response))
! 276: {
! 277: case SUCCESS:
! 278: break;
! 279: case OUT_OF_RES:
! 280: retransmit++;
! 281: continue;
! 282: default:
! 283: return NULL;
! 284: }
! 285: if (response->verify(response, request->get_authenticator(request),
! 286: this->secret, this->hasher, this->signer))
! 287: {
! 288: return response;
! 289: }
! 290: response->destroy(response);
! 291: return NULL;
! 292: }
! 293:
! 294: DBG1(DBG_CFG, "RADIUS %N timed out after %d attempts",
! 295: radius_message_code_names, request->get_code(request), retransmit);
! 296: return NULL;
! 297: }
! 298:
! 299: /**
! 300: * Decrypt a MS-MPPE-Send/Recv-Key
! 301: */
! 302: static chunk_t decrypt_mppe_key(private_radius_socket_t *this, uint16_t salt,
! 303: chunk_t C, radius_message_t *request)
! 304: {
! 305: chunk_t decrypted;
! 306:
! 307: decrypted = chunk_alloca(C.len);
! 308: if (!request->crypt(request, chunk_from_thing(salt), C, decrypted,
! 309: this->secret, this->hasher) ||
! 310: decrypted.ptr[0] >= decrypted.len)
! 311: { /* decryption failed? */
! 312: return chunk_empty;
! 313: }
! 314: /* remove truncation, first byte is key length */
! 315: return chunk_clone(chunk_create(decrypted.ptr + 1, decrypted.ptr[0]));
! 316: }
! 317:
! 318: METHOD(radius_socket_t, decrypt_msk, chunk_t,
! 319: private_radius_socket_t *this, radius_message_t *request,
! 320: radius_message_t *response)
! 321: {
! 322: mppe_key_t *mppe_key;
! 323: enumerator_t *enumerator;
! 324: chunk_t data, send = chunk_empty, recv = chunk_empty;
! 325: int type;
! 326:
! 327: enumerator = response->create_enumerator(response);
! 328: while (enumerator->enumerate(enumerator, &type, &data))
! 329: {
! 330: if (type == RAT_VENDOR_SPECIFIC && data.len > sizeof(mppe_key_t))
! 331: {
! 332: mppe_key = (mppe_key_t*)data.ptr;
! 333: if (ntohl(mppe_key->id) == PEN_MICROSOFT &&
! 334: mppe_key->length == data.len - sizeof(mppe_key->id))
! 335: {
! 336: data = chunk_create(mppe_key->key, data.len - sizeof(mppe_key_t));
! 337: if (mppe_key->type == MS_MPPE_SEND_KEY)
! 338: {
! 339: send = decrypt_mppe_key(this, mppe_key->salt, data, request);
! 340: }
! 341: if (mppe_key->type == MS_MPPE_RECV_KEY)
! 342: {
! 343: recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
! 344: }
! 345: }
! 346: }
! 347: }
! 348: enumerator->destroy(enumerator);
! 349: if (send.ptr && recv.ptr)
! 350: {
! 351: chunk_t pad = chunk_empty;
! 352:
! 353: if ((send.len + recv.len) < 64)
! 354: { /* zero-pad MSK to at least 64 bytes */
! 355: pad = chunk_alloca(64 - send.len - recv.len);
! 356: memset(pad.ptr, 0, pad.len);
! 357: }
! 358: return chunk_cat("mmc", recv, send, pad);
! 359: }
! 360: chunk_clear(&send);
! 361: chunk_clear(&recv);
! 362: return chunk_empty;
! 363: }
! 364:
! 365: METHOD(radius_socket_t, destroy, void,
! 366: private_radius_socket_t *this)
! 367: {
! 368: DESTROY_IF(this->hasher);
! 369: DESTROY_IF(this->signer);
! 370: DESTROY_IF(this->rng);
! 371: if (this->auth_fd != -1)
! 372: {
! 373: close(this->auth_fd);
! 374: };
! 375: if (this->acct_fd != -1)
! 376: {
! 377: close(this->acct_fd);
! 378: }
! 379: free(this);
! 380: }
! 381:
! 382: /**
! 383: * See header
! 384: */
! 385: radius_socket_t *radius_socket_create(char *address, uint16_t auth_port,
! 386: uint16_t acct_port, chunk_t secret,
! 387: u_int tries, double timeout, double base)
! 388: {
! 389: private_radius_socket_t *this;
! 390:
! 391: INIT(this,
! 392: .public = {
! 393: .request = _request,
! 394: .decrypt_msk = _decrypt_msk,
! 395: .destroy = _destroy,
! 396: },
! 397: .address = address,
! 398: .auth_port = auth_port,
! 399: .auth_fd = -1,
! 400: .acct_port = acct_port,
! 401: .acct_fd = -1,
! 402: .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
! 403: .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
! 404: .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
! 405: .retransmit_tries = tries,
! 406: .retransmit_timeout = timeout,
! 407: .retransmit_base = base,
! 408: );
! 409:
! 410: if (!this->hasher || !this->signer || !this->rng ||
! 411: !this->signer->set_key(this->signer, secret))
! 412: {
! 413: DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
! 414: destroy(this);
! 415: return NULL;
! 416: }
! 417: this->secret = secret;
! 418: /* we use a random identifier, helps if we restart often */
! 419: this->identifier = random();
! 420:
! 421: return &this->public;
! 422: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>