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>