diff options
author | YASUOKA Masahiko <yasuoka@cvs.openbsd.org> | 2015-07-21 04:06:05 +0000 |
---|---|---|
committer | YASUOKA Masahiko <yasuoka@cvs.openbsd.org> | 2015-07-21 04:06:05 +0000 |
commit | 7b89577c1c556c5f77fe92f4b5b726587e4e6c8a (patch) | |
tree | bd9700c473149378ef37c0113111145b618c7aaa | |
parent | da61f5a34c242b2583e61ed56de4d39bee0ff0b8 (diff) |
Add radiusd(8) and radiusctl(8). They are WIP. radiusd(8) is a RADIUS
server and radiusctl(8) is to control the server. radiusd(8) currently
supports bsdauth and radius (upstream radius servers) as authentication
backends.
fixes from jsg blambert
ok deraadt
28 files changed, 5709 insertions, 1 deletions
diff --git a/etc/mtree/4.4BSD.dist b/etc/mtree/4.4BSD.dist index c1a2f6a476e..4532951c794 100644 --- a/etc/mtree/4.4BSD.dist +++ b/etc/mtree/4.4BSD.dist @@ -1,4 +1,4 @@ -# $OpenBSD: 4.4BSD.dist,v 1.271 2015/04/30 19:02:15 ajacoutot Exp $ +# $OpenBSD: 4.4BSD.dist,v 1.272 2015/07/21 04:06:04 yasuoka Exp $ /set type=dir uname=root gname=wheel mode=0755 @@ -183,6 +183,8 @@ usr .. lpr .. + radiusd type=dir uname=root gname=wheel mode=0755 + .. smtpd type=dir uname=root gname=wheel mode=0755 .. .. diff --git a/usr.sbin/radiusctl/Makefile b/usr.sbin/radiusctl/Makefile new file mode 100644 index 00000000000..0cc602465af --- /dev/null +++ b/usr.sbin/radiusctl/Makefile @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +PROG= radiusctl +SRCS= radiusctl.c parser.c chap_ms.c +#MAN= radiusctl.8 +NOMAN= # +CFLAGS+= -Wall -Wextra -Wno-unused-parameter +LDADD+= -lradius -lcrypto +DPADD+= ${LIBRADIUS} ${LIBCRYPTO} + +.include <bsd.prog.mk> diff --git a/usr.sbin/radiusctl/chap_ms.c b/usr.sbin/radiusctl/chap_ms.c new file mode 100644 index 00000000000..ff8527037b6 --- /dev/null +++ b/usr.sbin/radiusctl/chap_ms.c @@ -0,0 +1,363 @@ +/* $OpenBSD: chap_ms.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 1997-2001 Brian Somers <brian@Awfulhak.org> + * Copyright (c) 1997 Gabor Kincses <gabor@acm.org> + * Copyright (c) 1995 Eric Rosenquist + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> + +#include <ctype.h> +#include <string.h> +#include <stdio.h> + +#include <openssl/evp.h> +#include <openssl/des.h> +#include <openssl/md4.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +#include "chap_ms.h" + +/* + * Documentation & specifications: + * + * MS-CHAP (CHAP80) RFC2433 + * MS-CHAP-V2 (CHAP81) RFC2759 + * MPPE key management RFC3079 + * + * Security analysis: + * Schneier/Mudge/Wagner, "MS-CHAP-v2", Oct 99 + * "It is unclear to us why this protocol is so complicated." + */ + +static u_int8_t sha1_pad1[40] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u_int8_t sha1_pad2[40] = { + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 +}; + +u_int8_t get7bits(u_int8_t *, int); +void mschap_des_addparity(u_int8_t *, u_int8_t *); +void mschap_des_encrypt(u_int8_t *, u_int8_t *, u_int8_t *); +void mschap_challenge_response(u_int8_t *, u_int8_t *, u_int8_t *); + +u_int8_t +get7bits(u_int8_t *in, int start) +{ + u_int word; + + word = (u_int)in[start / 8] << 8; + word |= (u_int)in[start / 8 + 1]; + word >>= 15 - (start % 8 + 7); + + return (word & 0xfe); +} + +/* IN 56 bit DES key missing parity bits + OUT 64 bit DES key with parity bits added */ +void +mschap_des_addparity(u_int8_t *key, u_int8_t *des_key) +{ + des_key[0] = get7bits(key, 0); + des_key[1] = get7bits(key, 7); + des_key[2] = get7bits(key, 14); + des_key[3] = get7bits(key, 21); + des_key[4] = get7bits(key, 28); + des_key[5] = get7bits(key, 35); + des_key[6] = get7bits(key, 42); + des_key[7] = get7bits(key, 49); + + DES_set_odd_parity((DES_cblock *)des_key); +} + +void +mschap_des_encrypt(u_int8_t *clear, u_int8_t *key, u_int8_t *cipher) +{ + DES_cblock des_key; + DES_key_schedule key_schedule; + + mschap_des_addparity(key, des_key); + + DES_set_key(&des_key, &key_schedule); + DES_ecb_encrypt((DES_cblock *)clear, (DES_cblock *)cipher, + &key_schedule, 1); +} + +void +mschap_challenge_response(u_int8_t *challenge, u_int8_t *pwhash, + u_int8_t *response) +{ + u_int8_t padpwhash[21 + 1]; + + bzero(&padpwhash, sizeof(padpwhash)); + memcpy(padpwhash, pwhash, MSCHAP_HASH_SZ); + + mschap_des_encrypt(challenge, padpwhash + 0, response + 0); + mschap_des_encrypt(challenge, padpwhash + 7, response + 8); + mschap_des_encrypt(challenge, padpwhash + 14, response + 16); +} + +void +mschap_ntpassword_hash(u_int8_t *in, int inlen, u_int8_t *hash) +{ + EVP_MD_CTX ctx; + u_int mdlen; + + EVP_DigestInit(&ctx, EVP_md4()); + EVP_DigestUpdate(&ctx, in, inlen); + EVP_DigestFinal(&ctx, hash, &mdlen); +} + +void +mschap_challenge_hash(u_int8_t *peer_challenge, u_int8_t *auth_challenge, + u_int8_t *username, int usernamelen, u_int8_t *challenge) +{ + EVP_MD_CTX ctx; + u_int8_t md[SHA_DIGEST_LENGTH]; + u_int mdlen; + u_int8_t *name; + + if ((name = strrchr(username, '\\')) == NULL) + name = username; + else + name++; + + EVP_DigestInit(&ctx, EVP_sha1()); + EVP_DigestUpdate(&ctx, peer_challenge, MSCHAPV2_CHALLENGE_SZ); + EVP_DigestUpdate(&ctx, auth_challenge, MSCHAPV2_CHALLENGE_SZ); + EVP_DigestUpdate(&ctx, name, strlen(name)); + EVP_DigestFinal(&ctx, md, &mdlen); + + memcpy(challenge, md, MSCHAP_CHALLENGE_SZ); +} + +void +mschap_nt_response(u_int8_t *auth_challenge, u_int8_t *peer_challenge, + u_int8_t *username, int usernamelen, u_int8_t *password, int passwordlen, + u_int8_t *response) +{ + u_int8_t challenge[MSCHAP_CHALLENGE_SZ]; + u_int8_t password_hash[MSCHAP_HASH_SZ]; + + mschap_challenge_hash(peer_challenge, auth_challenge, + username, usernamelen, challenge); + + mschap_ntpassword_hash(password, passwordlen, password_hash); + mschap_challenge_response(challenge, password_hash, response); +} + +void +mschap_auth_response(u_int8_t *password, int passwordlen, + u_int8_t *ntresponse, u_int8_t *auth_challenge, u_int8_t *peer_challenge, + u_int8_t *username, int usernamelen, u_int8_t *auth_response) +{ + EVP_MD_CTX ctx; + u_int8_t password_hash[MSCHAP_HASH_SZ]; + u_int8_t password_hash2[MSCHAP_HASH_SZ]; + u_int8_t challenge[MSCHAP_CHALLENGE_SZ]; + u_int8_t md[SHA_DIGEST_LENGTH], *ptr; + u_int mdlen; + int i; + const u_int8_t hex[] = "0123456789ABCDEF"; + static u_int8_t magic1[39] = { + 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65, + 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, + 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74 + }; + static u_int8_t magic2[41] = { + 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B, + 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F, + 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E, + 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, + 0x6E + }; + + mschap_ntpassword_hash(password, passwordlen, password_hash); + mschap_ntpassword_hash(password_hash, MSCHAP_HASH_SZ, password_hash2); + + EVP_DigestInit(&ctx, EVP_sha1()); + EVP_DigestUpdate(&ctx, password_hash2, sizeof(password_hash2)); + EVP_DigestUpdate(&ctx, ntresponse, 24); + EVP_DigestUpdate(&ctx, magic1, 39); + EVP_DigestFinal(&ctx, md, &mdlen); + + mschap_challenge_hash(peer_challenge, auth_challenge, + username, usernamelen, challenge); + + EVP_DigestInit(&ctx, EVP_sha1()); + EVP_DigestUpdate(&ctx, md, sizeof(md)); + EVP_DigestUpdate(&ctx, challenge, sizeof(challenge)); + EVP_DigestUpdate(&ctx, magic2, 41); + EVP_DigestFinal(&ctx, md, &mdlen); + + /* + * Encode the value of 'Digest' as "S=" followed by + * 40 ASCII hexadecimal digits and return it in + * AuthenticatorResponse. + * For example, + * "S=0123456789ABCDEF0123456789ABCDEF01234567" + */ + ptr = auth_response; + *ptr++ = 'S'; + *ptr++ = '='; + for (i = 0; i < SHA_DIGEST_LENGTH; i++) { + *ptr++ = hex[md[i] >> 4]; + *ptr++ = hex[md[i] & 0x0f]; + } +} + +void +mschap_masterkey(u_int8_t *password_hash2, u_int8_t *ntresponse, + u_int8_t *masterkey) +{ + u_int8_t md[SHA_DIGEST_LENGTH]; + u_int mdlen; + EVP_MD_CTX ctx; + static u_int8_t magic1[27] = { + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 + }; + + EVP_DigestInit(&ctx, EVP_sha1()); + EVP_DigestUpdate(&ctx, password_hash2, MSCHAP_HASH_SZ); + EVP_DigestUpdate(&ctx, ntresponse, 24); + EVP_DigestUpdate(&ctx, magic1, 27); + EVP_DigestFinal(&ctx, md, &mdlen); + + memcpy(masterkey, md, 16); +} + +void +mschap_asymetric_startkey(u_int8_t *masterkey, u_int8_t *sessionkey, + int sessionkeylen, int issend, int isserver) +{ + EVP_MD_CTX ctx; + u_int8_t md[SHA_DIGEST_LENGTH]; + u_int mdlen; + u_int8_t *s; + static u_int8_t magic2[84] = { + 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, + 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, + 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x2e + }; + static u_int8_t magic3[84] = { + 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, + 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, + 0x6b, 0x65, 0x79, 0x2e + }; + + if (issend) + s = isserver ? magic3 : magic2; + else + s = isserver ? magic2 : magic3; + + EVP_DigestInit(&ctx, EVP_sha1()); + EVP_DigestUpdate(&ctx, masterkey, 16); + EVP_DigestUpdate(&ctx, sha1_pad1, 40); + EVP_DigestUpdate(&ctx, s, 84); + EVP_DigestUpdate(&ctx, sha1_pad2, 40); + EVP_DigestFinal(&ctx, md, &mdlen); + + memcpy(sessionkey, md, sessionkeylen); +} + +void +mschap_msk(u_int8_t *password, int passwordlen, + u_int8_t *ntresponse, u_int8_t *msk) +{ + u_int8_t password_hash[MSCHAP_HASH_SZ]; + u_int8_t password_hash2[MSCHAP_HASH_SZ]; + u_int8_t masterkey[MSCHAP_MASTERKEY_SZ]; + u_int8_t sendkey[MSCHAP_MASTERKEY_SZ]; + u_int8_t recvkey[MSCHAP_MASTERKEY_SZ]; + + mschap_ntpassword_hash(password, passwordlen, password_hash); + mschap_ntpassword_hash(password_hash, MSCHAP_HASH_SZ, password_hash2); + + mschap_masterkey(password_hash2, ntresponse, masterkey); + mschap_asymetric_startkey(masterkey, recvkey, sizeof(recvkey), 0, 1); + mschap_asymetric_startkey(masterkey, sendkey, sizeof(sendkey), 1, 1); + + /* 16 bytes receive key + 16 bytes send key + 32 bytes 0 padding */ + bzero(msk, MSCHAP_MSK_SZ); + memcpy(msk, &recvkey, sizeof(recvkey)); + memcpy(msk + sizeof(recvkey), &sendkey, sizeof(sendkey)); +} + +void +mschap_radiuskey(u_int8_t *plain, const u_int8_t *crypted, + const u_int8_t *authenticator, const u_int8_t *secret) +{ + EVP_MD_CTX ctx; + u_int8_t b[MD5_DIGEST_LENGTH], p[32]; + u_int i, mdlen; + + EVP_DigestInit(&ctx, EVP_md5()); + EVP_DigestUpdate(&ctx, secret, strlen(secret)); + EVP_DigestUpdate(&ctx, authenticator, 16); + EVP_DigestUpdate(&ctx, crypted, 2); + EVP_DigestFinal(&ctx, b, &mdlen); + + for (i = 0; i < mdlen; i++) { + p[i] = b[i] ^ crypted[i+2]; + } + + EVP_DigestInit(&ctx, EVP_md5()); + EVP_DigestUpdate(&ctx, secret, strlen(secret)); + EVP_DigestUpdate(&ctx, crypted + 2, mdlen); + EVP_DigestFinal(&ctx, b, &mdlen); + + for (i = 0; i < mdlen; i++) { + p[i+16] = b[i] ^ crypted[i+18]; + } + + memcpy(plain, p+1, 16); +} diff --git a/usr.sbin/radiusctl/chap_ms.h b/usr.sbin/radiusctl/chap_ms.h new file mode 100644 index 00000000000..f509ddc945c --- /dev/null +++ b/usr.sbin/radiusctl/chap_ms.h @@ -0,0 +1,48 @@ +/* $OpenBSD: chap_ms.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ +/* $vantronix: chap_ms.h,v 1.6 2010/05/19 09:37:00 reyk Exp $ */ + +/* + * Copyright (c) 2010 Reyk Floeter <reyk@vantronix.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _CHAP_MS_H +#define _CHAP_MS_H + +#define MSCHAP_CHALLENGE_SZ 8 +#define MSCHAPV2_CHALLENGE_SZ 16 +#define MSCHAP_HASH_SZ 16 +#define MSCHAP_MASTERKEY_SZ 16 +#define MSCHAP_MSK_KEY_SZ 32 +#define MSCHAP_MSK_PADDING_SZ 32 +#define MSCHAP_MSK_SZ 64 + +#define MSCHAP_MAXNTPASSWORD_SZ 255 /* unicode chars */ + +void mschap_nt_response(u_int8_t *, u_int8_t *, u_int8_t *, int, + u_int8_t *, int , u_int8_t *); +void mschap_auth_response(u_int8_t *, int, u_int8_t *, u_int8_t *, + u_int8_t *, u_int8_t *, int, u_int8_t *); + +void mschap_ntpassword_hash(u_int8_t *, int, u_int8_t *); +void mschap_challenge_hash(u_int8_t *, u_int8_t *, u_int8_t *, + int, u_int8_t *); + +void mschap_asymetric_startkey(u_int8_t *, u_int8_t *, int, int, int); +void mschap_masterkey(u_int8_t *, u_int8_t *, u_int8_t *); +void mschap_radiuskey(u_int8_t *, const u_int8_t *, const u_int8_t *, + const u_int8_t *); +void mschap_msk(u_int8_t *, int, u_int8_t *, u_int8_t *); + +#endif /* _CHAP_MS_H */ diff --git a/usr.sbin/radiusctl/parser.c b/usr.sbin/radiusctl/parser.c new file mode 100644 index 00000000000..b666206f616 --- /dev/null +++ b/usr.sbin/radiusctl/parser.c @@ -0,0 +1,300 @@ +/* $OpenBSD: parser.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2010 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "parser.h" + +enum token_type { + NOTOKEN, + KEYWORD, + HOSTNAME, + SECRET, + USERNAME, + PASSWORD, + PORT, + METHOD, + NAS_PORT, + ENDTOKEN +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static struct parse_result res; + +static const struct token t_test[]; +static const struct token t_secret[]; +static const struct token t_username[]; +static const struct token t_test_opts[]; +static const struct token t_password[]; +static const struct token t_port[]; +static const struct token t_method[]; +static const struct token t_nas_port[]; + +static const struct token t_main[] = { + { KEYWORD, "test", TEST, t_test }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_test[] = { + { HOSTNAME, "", NONE, t_secret }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_secret[] = { + { SECRET, "", NONE, t_username }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_username[] = { + { USERNAME, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_test_opts[] = { + { NOTOKEN, "", NONE, NULL }, + { KEYWORD, "password", NONE, t_password }, + { KEYWORD, "port", NONE, t_port }, + { KEYWORD, "method", NONE, t_method }, + { KEYWORD, "nas-port", NONE, t_nas_port }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_password[] = { + { PASSWORD, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_port[] = { + { PORT, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_method[] = { + { METHOD, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_nas_port[] = { + { NAS_PORT, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + + +static const struct token *match_token(char *, const struct token []); +static void show_valid_args(const struct token []); + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc >= 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +static const struct token * +match_token(char *word, const struct token table[]) +{ + u_int i, match = 0; + const struct token *t = NULL; + long long num; + const char *errstr; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case HOSTNAME: + if (word == NULL) + break; + match++; + res.hostname = word; + t = &table[i]; + break; + case SECRET: + if (word == NULL) + break; + match++; + res.secret = word; + t = &table[i]; + break; + case USERNAME: + if (word == NULL) + break; + match++; + res.username = word; + t = &table[i]; + break; + case PASSWORD: + if (word == NULL) + break; + match++; + res.password = word; + t = &table[i]; + break; + case PORT: + if (word == NULL) + break; + num = strtonum(word, 1, UINT16_MAX, &errstr); + if (errstr != NULL) { + fprintf(stderr, + "invalid argument: %s is %s for \"port\"\n", + word, errstr); + return (NULL); + } + match++; + res.port = num; + t = &table[i]; + break; + case METHOD: + if (word == NULL) + break; + if (strcasecmp(word, "pap") == 0) + res.auth_method = PAP; + else if (strcasecmp(word, "chap") == 0) + res.auth_method = CHAP; + else if (strcasecmp(word, "mschapv2") == 0) + res.auth_method = MSCHAPV2; + else { + fprintf(stderr, + "invalid argument: %s for \"method\"\n", + word); + return (NULL); + } + match++; + t = &table[i]; + break; + case NAS_PORT: + if (word == NULL) + break; + num = strtonum(word, 0, 65535, &errstr); + if (errstr != NULL) { + fprintf(stderr, + "invalid argument: %s is %s for " + "\"nas-port\"\n", word, errstr); + return (NULL); + } + match++; + res.nas_port = num; + t = &table[i]; + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (word == NULL) + fprintf(stderr, "missing argument:\n"); + else if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + else if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +static void +show_valid_args(const struct token table[]) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " <cr>\n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case HOSTNAME: + fprintf(stderr, " <hostname>\n"); + break; + case SECRET: + fprintf(stderr, " <radius secret>\n"); + break; + case USERNAME: + fprintf(stderr, " <username>\n"); + break; + case PASSWORD: + fprintf(stderr, " <password>\n"); + break; + case PORT: + fprintf(stderr, " <port number>\n"); + break; + case METHOD: + fprintf(stderr, " <auth method (pap, chap, " + "mschapv2)>\n"); + break; + case NAS_PORT: + fprintf(stderr, " <nas-port (0-65535)>\n"); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/radiusctl/parser.h b/usr.sbin/radiusctl/parser.h new file mode 100644 index 00000000000..b84b4b4d29d --- /dev/null +++ b/usr.sbin/radiusctl/parser.h @@ -0,0 +1,47 @@ +/* $OpenBSD: parser.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* This file is derived from OpenBSD:src/usr.sbin/ikectl/parser.h 1.9 */ +/* + * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _RADIUSCTL_PARSER_H +#define _RADIUSCTL_PARSER_H + +enum actions { + NONE, + TEST +}; + +enum auth_method { + PAP, + CHAP, + MSCHAPV2 +}; + +struct parse_result { + enum actions action; + const char *hostname; + const char *secret; + const char *username; + const char *password; + u_short port; + int nas_port; + enum auth_method auth_method; +}; + +struct parse_result *parse(int, char *[]); + +#endif /* _RADIUSCTL_PARSER_H */ diff --git a/usr.sbin/radiusctl/radiusctl.c b/usr.sbin/radiusctl/radiusctl.c new file mode 100644 index 00000000000..b40b4c0fcb8 --- /dev/null +++ b/usr.sbin/radiusctl/radiusctl.c @@ -0,0 +1,414 @@ +/* $OpenBSD: radiusctl.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ +/* + * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <arpa/inet.h> +#include <err.h> +#include <md5.h> +#include <netdb.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include <radius.h> + +#include "parser.h" +#include "chap_ms.h" + + +static void radius_test (struct parse_result *); +static void radius_dump (FILE *, RADIUS_PACKET *, bool, + const char *); +static const char *radius_code_str (int code); +static const char *hexstr(const u_char *, int, char *, int); + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-h] command [arg ...]\n", __progname); +} + +int +main(int argc, char *argv[]) +{ + int ch; + struct parse_result *result; + + while ((ch = getopt(argc, argv, "h")) != -1) + switch (ch) { + case 'h': + usage(); + exit(EX_USAGE); + } + argc -= optind; + argv += optind; + + if ((result = parse(argc, argv)) == NULL) + exit(EXIT_FAILURE); + + switch (result->action) { + case NONE: + break; + case TEST: + radius_test(result); + break; + } + + exit(EXIT_SUCCESS); +} + +static void +radius_test(struct parse_result *res) +{ + struct addrinfo hints, *ai; + int sock, retval; + struct sockaddr_storage sockaddr; + socklen_t sockaddrlen; + RADIUS_PACKET *reqpkt, *respkt; + struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; + uint32_t u32val; + uint8_t id; + + reqpkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST); + if (reqpkt == NULL) + err(1, "radius_new_request_packet"); + id = arc4random(); + radius_set_id(reqpkt, id); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + retval = getaddrinfo(res->hostname, "radius", &hints, &ai); + if (retval) + errx(1, "%s %s", res->hostname, gai_strerror(retval)); + + if (res->port != 0) + ((struct sockaddr_in *)ai->ai_addr)->sin_port = + htons(res->port); + + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) + err(1, "socket"); + + /* Prepare NAS-IP{,V6}-ADDRESS attribute */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) + err(1, "connect"); + sockaddrlen = sizeof(sockaddr); + if (getsockname(sock, (struct sockaddr *)&sockaddr, &sockaddrlen) == -1) + err(1, "getsockname"); + sin4 = (struct sockaddr_in *)&sockaddr; + sin6 = (struct sockaddr_in6 *)&sockaddr; + switch (sockaddr.ss_family) { + case AF_INET: + radius_put_ipv4_attr(reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS, + sin4->sin_addr); + break; + case AF_INET6: + radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, + sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr.s6_addr)); + break; + } + + /* User-Name and User-Password */ + radius_put_string_attr(reqpkt, RADIUS_TYPE_USER_NAME, + res->username); + + switch (res->auth_method) { + case PAP: + if (res->password != NULL) + radius_put_user_password_attr(reqpkt, res->password, + res->secret); + break; + case CHAP: + { + u_char chal[16]; + u_char resp[1 + MD5_DIGEST_LENGTH]; /* "1 + " for CHAP Id */ + MD5_CTX md5ctx; + + arc4random_buf(resp, 1); /* CHAP Id is random */ + MD5Init(&md5ctx); + MD5Update(&md5ctx, resp, 1); + if (res->password != NULL) + MD5Update(&md5ctx, res->password, + strlen(res->password)); + MD5Update(&md5ctx, chal, sizeof(chal)); + MD5Final(resp + 1, &md5ctx); + radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_CHALLENGE, + chal, sizeof(chal)); + radius_put_raw_attr(reqpkt, RADIUS_TYPE_CHAP_PASSWORD, + resp, sizeof(resp)); + } + break; + case MSCHAPV2: + { + u_char pass[256], chal[16]; + u_int i, lpass; + struct _resp { + u_int8_t ident; + u_int8_t flags; + char peer_challenge[16]; + char reserved[8]; + char response[24]; + } __packed resp; + + if (res->password == NULL) { + lpass = 0; + } else { + lpass = strlen(res->password); + if (lpass * 2 >= sizeof(pass)) + err(1, "password too long"); + for (i = 0; i < lpass; i++) { + pass[i * 2] = res->password[i]; + pass[i * 2 + 1] = 0; + } + } + + memset(&resp, 0, sizeof(resp)); + resp.ident = arc4random(); + arc4random_buf(chal, sizeof(chal)); + arc4random_buf(resp.peer_challenge, + sizeof(resp.peer_challenge)); + + mschap_nt_response(chal, resp.peer_challenge, + (char *)res->username, strlen(res->username), pass, + lpass * 2, resp.response); + + radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_CHALLENGE, chal, sizeof(chal)); + radius_put_vs_raw_attr(reqpkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, sizeof(resp)); + explicit_bzero(pass, sizeof(pass)); + } + break; + + } + u32val = htonl(res->nas_port); + radius_put_raw_attr(reqpkt, RADIUS_TYPE_NAS_PORT, &u32val, 4); + + radius_put_message_authenticator(reqpkt, res->secret); + + /* Send! */ + fprintf(stderr, "Sending:\n"); + radius_dump(stdout, reqpkt, false, res->secret); + if (send(sock, radius_get_data(reqpkt), radius_get_length(reqpkt), 0) + == -1) + warn("send"); + if ((respkt = radius_recv(sock, 0)) == NULL) + warn("recv"); + else { + radius_set_request_packet(respkt, reqpkt); + fprintf(stderr, "\nReceived:\n"); + radius_dump(stdout, respkt, true, res->secret); + } + + /* Release the resources */ + radius_delete_packet(reqpkt); + if (respkt) + radius_delete_packet(respkt); + close(sock); + freeaddrinfo(ai); + + explicit_bzero((char *)res->secret, strlen(res->secret)); + if (res->password) + explicit_bzero((char *)res->password, strlen(res->password)); + + return; +} + +static void +radius_dump(FILE *out, RADIUS_PACKET *pkt, bool resp, const char *secret) +{ + size_t len; + char buf[256], buf1[256]; + uint32_t u32val; + struct in_addr ipv4; + + fprintf(out, + " Id = %d\n" + " Code = %s(%d)\n", + (int)radius_get_id(pkt), radius_code_str((int)radius_get_code(pkt)), + (int)radius_get_code(pkt)); + if (resp && secret) + fprintf(out, " Message-Authenticator = %s\n", + (radius_check_response_authenticator(pkt, secret) == 0) + ? "Verified" : "NG"); + + if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME, buf, + sizeof(buf)) == 0) + fprintf(out, " User-Name = \"%s\"\n", buf); + + if (secret && + radius_get_user_password_attr(pkt, buf, sizeof(buf), secret) == 0) + fprintf(out, " User-Password = \"%s\"\n", buf); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_PASSWORD, buf, &len) + == 0) + fprintf(out, " CHAP-Password = %s\n", + (hexstr(buf, len, buf1, sizeof(buf1)))? buf1 : "(too long)"); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_raw_attr(pkt, RADIUS_TYPE_CHAP_CHALLENGE, buf, &len) + == 0) + fprintf(out, " CHAP-Challenge = %s\n", + (hexstr(buf, len, buf1, sizeof(buf1)))? buf1 : "(too long)"); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_CHALLENGE, buf, &len) == 0) + fprintf(out, " MS-CHAP-Challenge = %s\n", + (hexstr(buf, len, buf1, sizeof(buf1)))? buf1 : "(too long)"); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_RESPONSE, buf, &len) == 0) + fprintf(out, " MS-CHAP2-Response = %s\n", + (hexstr(buf, len, buf1, sizeof(buf1))) + ? buf1 : "(too long)"); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf) - 1; + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_SUCCESS, buf, &len) == 0) { + fprintf(out, " MS-CHAP-Success = Id=%u \"%s\"\n", + (u_int)(u_char)buf[0], buf + 1); + } + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf) - 1; + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_ERROR, buf, &len) == 0) { + fprintf(out, " MS-CHAP-Error = Id=%u \"%s\"\n", + (u_int)(u_char)buf[0], buf + 1); + } + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_SEND_KEY, buf, &len) == 0) + fprintf(out, " MS-MPPE-Send-Key = %s\n", + (hexstr(buf, len, buf1, sizeof(buf1))) + ? buf1 : "(too long)"); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_RECV_KEY, buf, &len) == 0) + fprintf(out, " MS-MPPE-Recv-Key = %s\n", + (hexstr(buf, len, buf1, sizeof(buf1))) + ? buf1 : "(too long)"); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_ENCRYPTION_POLICY, buf, &len) == 0) + fprintf(out, " MS-MPPE-Encryption-Policy = 0x%08x\n", + ntohl(*(u_long *)buf)); + + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_ENCRYPTION_TYPES, buf, &len) == 0) + fprintf(out, " MS-MPPE-Encryption-Types = 0x%08x\n", + ntohl(*(u_long *)buf)); + + if (radius_get_string_attr(pkt, RADIUS_TYPE_REPLY_MESSAGE, buf, + sizeof(buf)) == 0) + fprintf(out, " Reply-Message = \"%s\"\n", buf); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_uint32_attr(pkt, RADIUS_TYPE_NAS_PORT, &u32val) == 0) + fprintf(out, " NAS-Port = %lu\n", + (u_long)u32val); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS, &ipv4) == 0) + fprintf(out, " NAS-IP-Address = %s\n", + inet_ntoa(ipv4)); + + memset(buf, 0, sizeof(buf)); + len = sizeof(buf); + if (radius_get_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, buf, &len) + == 0) + fprintf(out, " NAS-IPv6-Address = %s\n", + inet_ntop(AF_INET6, buf, buf1, len)); + +} + +static const char * +radius_code_str(int code) +{ + int i; + static struct _codestr { + int code; + const char *str; + } codestr[] = { + { RADIUS_CODE_ACCESS_REQUEST, "Access-Request" }, + { RADIUS_CODE_ACCESS_ACCEPT, "Access-Accept" }, + { RADIUS_CODE_ACCESS_REJECT, "Access-Reject" }, + { RADIUS_CODE_ACCOUNTING_REQUEST, "Accounting-Request" }, + { RADIUS_CODE_ACCOUNTING_RESPONSE, "Accounting-Response" }, + { RADIUS_CODE_ACCESS_CHALLENGE, "Access-Challenge" }, + { RADIUS_CODE_STATUS_SERVER, "Status-Server" }, + { RADIUS_CODE_STATUS_CLIENT, "Status-Client" }, + { -1, NULL } + }; + + for (i = 0; codestr[i].code != -1; i++) { + if (codestr[i].code == code) + return (codestr[i].str); + } + + return ("Unknown"); +} + +static const char * +hexstr(const u_char *data, int len, char *str, int strsiz) +{ + int i, off = 0; + static const char hex[] = "0123456789abcdef"; + + for (i = 0; i < len; i++) { + if (strsiz - off < 3) + return (NULL); + str[off++] = hex[(data[i] & 0xf0) >> 4]; + str[off++] = hex[(data[i] & 0x0f)]; + str[off++] = ' '; + } + if (strsiz - off < 1) + return (NULL); + + str[off++] = '\0'; + + return (str); +} diff --git a/usr.sbin/radiusd/Makefile b/usr.sbin/radiusd/Makefile new file mode 100644 index 00000000000..315e14901b7 --- /dev/null +++ b/usr.sbin/radiusd/Makefile @@ -0,0 +1,6 @@ +# $OpenBSD: Makefile,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +SUBDIR= radiusd +SUBDIR+= radiusd_bsdauth +SUBDIR+= radiusd_radius + +.include <bsd.prog.mk> diff --git a/usr.sbin/radiusd/Makefile.inc b/usr.sbin/radiusd/Makefile.inc new file mode 100644 index 00000000000..e3fca4ca46d --- /dev/null +++ b/usr.sbin/radiusd/Makefile.inc @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile.inc,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +.PATH: ${.CURDIR}/.. +CFLAGS+= -I${.CURDIR}/.. +CFLAGS+= -Wall -Wextra -Wshadow -Wno-unused-parameter +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations -Wpointer-arith +.ifdef DEBUG +CFLAGS+= -DRADIUSD_DEBUG +CFLAGS+= -Werror +.endif diff --git a/usr.sbin/radiusd/imsg_subr.c b/usr.sbin/radiusd/imsg_subr.c new file mode 100644 index 00000000000..2ed467dad52 --- /dev/null +++ b/usr.sbin/radiusd/imsg_subr.c @@ -0,0 +1,78 @@ +/* $OpenBSD: imsg_subr.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/uio.h> + +#include <imsg.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <poll.h> +#include <errno.h> + +#include "imsg_subr.h" + +/* + * Check readability not to spin before calling imsg_read(3). Wait 'millisec' + * until it becomes readable. + */ +int +imsg_sync_read(struct imsgbuf *ibuf, int millisec) +{ + struct pollfd fds[1]; + int retval; + + fds[0].fd = ibuf->fd; + fds[0].events = POLLIN; + retval = poll(fds, 1, millisec); + if (retval == 0) { + errno = EAGAIN; + return (-1); + } + if (retval > 0 && (fds[0].revents & POLLIN) != 0) + return imsg_read(ibuf); + + return (-1); +} + +/* + * Check writability not to spin before calling imsg_flush(3). Wait 'millisec' + * until it becomes writable. + */ +int +imsg_sync_flush(struct imsgbuf *ibuf, int millisec) +{ + struct pollfd fds[1]; + int retval; + + if (!ibuf->w.queued) + return (0); /* already flushed */ + + fds[0].fd = ibuf->fd; + fds[0].events = POLLOUT; + retval = poll(fds, 1, millisec); + if (retval == 0) { + errno = EAGAIN; + return (-1); + } + if (retval > 0 && (fds[0].revents & POLLOUT) != 0) + return imsg_flush(ibuf); + + return (-1); +} diff --git a/usr.sbin/radiusd/imsg_subr.h b/usr.sbin/radiusd/imsg_subr.h new file mode 100644 index 00000000000..01580c4145b --- /dev/null +++ b/usr.sbin/radiusd/imsg_subr.h @@ -0,0 +1,16 @@ +/* $OpenBSD: imsg_subr.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +#ifndef _IMSG_SUBR_H +#define _IMSG_SUBR_H + +struct imsgbuf; + +#include <sys/cdefs.h> +__BEGIN_DECLS + +int imsg_sync_read(struct imsgbuf *, int); +int imsg_sync_flush(struct imsgbuf *, int); + +__END_DECLS + +#endif diff --git a/usr.sbin/radiusd/log.c b/usr.sbin/radiusd/log.c new file mode 100644 index 00000000000..68ac16e504f --- /dev/null +++ b/usr.sbin/radiusd/log.c @@ -0,0 +1,192 @@ +/* $OpenBSD: log.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> + +#include "log.h" + +int log_debug_use_syslog = 0; +static int log_initialized = 0; +static int debug = 0; + +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); + log_initialized++; +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char fmtbuf[1024]; + time_t curr; + struct tm tm; + u_int i = 0, j; + static struct { + int v; + const char *l; + } syslog_prionames[] = { +#define NV(_l) { _l, #_l } + NV(LOG_DEBUG), + NV(LOG_INFO), + NV(LOG_NOTICE), + NV(LOG_WARNING), + NV(LOG_ERR), + NV(LOG_ALERT), + NV(LOG_CRIT), +#undef NV + { -1, NULL } + }; + + if (log_initialized && !debug) { + vsyslog(pri, fmt, ap); + return; + } + if (log_initialized) { + time(&curr); + localtime_r(&curr, &tm); + strftime(fmtbuf, sizeof(fmtbuf), "%Y-%m-%d %H:%M:%S:", &tm); + for (i = 0; syslog_prionames[i].v != -1; i++) { + if (syslog_prionames[i].v == LOG_PRI(pri)) { + strlcat(fmtbuf, syslog_prionames[i].l + 4, + sizeof(fmtbuf)); + strlcat(fmtbuf, ": ", sizeof(fmtbuf)); + break; + } + } + i = strlen(fmtbuf); + } + for (j = 0; i < sizeof(fmtbuf) - 2 && fmt[j] != '\0'; j++) { + if (fmt[j] == '%' && fmt[j + 1] == 'm') { + ++j; + fmtbuf[i] = '\0'; + strlcat(fmtbuf, strerror(errno), sizeof(fmtbuf) - 1); + i = strlen(fmtbuf); + } else + fmtbuf[i++] = fmt[j]; + } + fmtbuf[i++] = '\n'; + fmtbuf[i++] = '\0'; + + vfprintf(stderr, fmtbuf, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_WARNING, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_WARNING, emsg, ap); + logit(LOG_WARNING, "%s", strerror(errno)); + } else { + vlog(LOG_WARNING, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_WARNING, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug || log_debug_use_syslog) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} diff --git a/usr.sbin/radiusd/log.h b/usr.sbin/radiusd/log.h new file mode 100644 index 00000000000..11d62f749cc --- /dev/null +++ b/usr.sbin/radiusd/log.h @@ -0,0 +1,29 @@ +/* $OpenBSD: log.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +#ifndef _LOG_H +#define _LOG_H 1 + +#include <sys/cdefs.h> +#include <stdarg.h> /* for va_list */ + +extern int log_debug_use_syslog; + +__BEGIN_DECLS +void log_init (int); +void logit(int, const char *, ...) + __attribute__((__format__ (__syslog__, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (__syslog__, 2, 0))); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void fatal(const char *); +void fatalx(const char *); +__END_DECLS + +#endif diff --git a/usr.sbin/radiusd/parse.y b/usr.sbin/radiusd/parse.y new file mode 100644 index 00000000000..095e74a7e5c --- /dev/null +++ b/usr.sbin/radiusd/parse.y @@ -0,0 +1,806 @@ +/* Adapted from usr.sbin/ntpd/parse.y 1.50 */ + +/* + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <syslog.h> + +#include "radiusd.h" +#include "radiusd_local.h" +#include "log.h" + +static struct radiusd *conf; +static struct radiusd_authentication authen; +static struct radiusd_client client; + +static struct radiusd_module *find_module (const char *); +static void free_str_l (void *); +static struct radiusd_module_ref *create_module_ref (const char *); +static void radiusd_authentication_init (struct radiusd_authentication *); +static void radiusd_client_init (struct radiusd_client *); + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +typedef struct { + union { + int64_t number; + char *string; + struct radiusd_listen listen; + int yesno; + struct { + char **v; + int c; + } str_l; + struct { + int af; + struct radiusd_addr addr; + struct radiusd_addr mask; + } prefix; + } v; + int lineno; +} YYSTYPE; + +%} + +%token INCLUDE LISTEN ON PORT CLIENT SECRET LOAD MODULE MSGAUTH_REQUIRED +%token AUTHENTICATE AUTHENTICATE_BY DECORATE_BY SET +%token ERROR YES NO +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.number> optport +%type <v.listen> listen_addr +%type <v.str_l> str_l +%type <v.prefix> prefix +%type <v.yesno> yesno +%type <v.string> strnum +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar listen '\n' + | grammar client '\n' + | grammar module '\n' + | grammar authenticate '\n' + | grammar error '\n' + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + nfile->lineno--; + } + ; +listen : LISTEN ON listen_addr { + struct radiusd_listen *n; + + if ((n = malloc(sizeof(struct radiusd_listen))) + == NULL) { +outofmemory: + yyerror("Out of memory: %s", strerror(errno)); + YYERROR; + } + *n = $3; + TAILQ_INSERT_TAIL(&conf->listen, n, next); + } +listen_addr : STRING optport { + int gai_errno; + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV; + + if ((gai_errno = + getaddrinfo($1, NULL, &hints, &res)) != 0 || + res->ai_addrlen > sizeof($$.addr)) { + yyerror("Could not parse the address: %s: %s", + $1, gai_strerror(gai_errno)); + free($1); + YYERROR; + } + free($1); + $$.stype = res->ai_socktype; + $$.sproto = res->ai_protocol; + memcpy(&$$.addr, res->ai_addr, res->ai_addrlen); + $$.addr.ipv4.sin_port = ($2 == 0)? + htons(RADIUS_DEFAULT_PORT) : htons($2); + freeaddrinfo(res); + } +optport : { $$ = 0; } + | PORT NUMBER { $$ = $2; } + ; +client : CLIENT prefix optnl clientopts_b { + struct radiusd_client *client0; + + client0 = calloc(1, sizeof(struct radiusd_client)); + if (client0 == NULL) + goto outofmemory; + strlcpy(client0->secret, client.secret, + sizeof(client0->secret)); + client0->msgauth_required = client.msgauth_required; + client0->af = $2.af; + client0->addr = $2.addr; + client0->mask = $2.mask; + TAILQ_INSERT_TAIL(&conf->client, client0, next); + radiusd_client_init(&client); + } + +clientopts_b : '{' optnl_l clientopts_l optnl_l '}' + | '{' optnl_l '}' /* allow empty block */ + ; + +clientopts_l : clientopts_l nl clientopts + | clientopts + ; + +clientopts : SECRET STRING { + if (strlcpy(client.secret, $2, sizeof(client.secret)) + >= sizeof(client.secret)) { + yyerror("secret is too long"); + YYERROR; + } + } + | MSGAUTH_REQUIRED yesno { + client.msgauth_required = $2; + } + ; + +prefix : STRING '/' NUMBER { + int gai_errno, q, r; + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV; + + if ((gai_errno = getaddrinfo($1, NULL, &hints, &res)) + != 0) { + free($1); + yyerror("Could not parse the address: %s: %s", + $1, gai_strerror(gai_errno)); + YYERROR; + } + free($1); + q = $3 >> 3; + r = $3 & 7; + switch (res->ai_family) { + case AF_INET: + if ($3 < 0 || 32 < $3) { + yyerror("mask len %d is out of range", + $3); + YYERROR; + } + $$.addr.addr.ipv4 = ((struct sockaddr_in *) + res->ai_addr)->sin_addr; + $$.mask.addr.ipv4.s_addr = htonl((uint32_t) + ((0xffffffffffULL) << (32 - $3))); + break; + case AF_INET6: + if ($3 < 0 || 128 < $3) { + yyerror("mask len %d is out of range", + $3); + YYERROR; + } + $$.addr.addr.ipv6 = ((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr; + memset(&$$.mask.addr.ipv6, 0, + sizeof($$.mask.addr.ipv6)); + if (q > 0) + memset(&$$.mask.addr.ipv6, 0xff, q); + if (r > 0) + *((u_char *)&$$.mask.addr.ipv6 + q) = + (0xff00 >> r) & 0xff; + break; + } + $$.af = res->ai_family; + freeaddrinfo(res); + } + ; +module : MODULE LOAD STRING STRING { + struct radiusd_module *module; + if ((module = radiusd_module_load(conf, $4, $3)) + == NULL) { + free($3); + free($4); + YYERROR; + } + free($3); + free($4); + TAILQ_INSERT_TAIL(&conf->module, module, next); + } + | MODULE SET STRING STRING str_l { + struct radiusd_module *module; + + module = find_module($3); + if (module == NULL) { + yyerror("module `%s' is not found", $3); + free($3); + free($4); + free_str_l(&$5); + YYERROR; + } + if (radiusd_module_set(module, $4, $5.c, $5.v)) { + yyerror("syntax error by module `%s'", $3); + free($3); + free($4); + free_str_l(&$5); + YYERROR; + } + free($3); + free($4); + free_str_l(&$5); + } + ; +authenticate : AUTHENTICATE str_l optnl authopts_b { + struct radiusd_authentication *a; + + if ((a = calloc(1, + sizeof(struct radiusd_authentication))) == NULL) { + free_str_l(&$2); + goto outofmemory; + } + a->auth = authen.auth; + a->deco = authen.deco; + a->username = $2.v; + + TAILQ_INSERT_TAIL(&conf->authen, a, next); + radiusd_authentication_init(&authen); + } + ; + +authopts_b : '{' optnl_l authopts_l optnl_l '}' + | '{' optnl_l '}' /* empty options */ + ; + +authopts_l : authopts_l nl authopts + | authopts + ; + +authopts : AUTHENTICATE_BY STRING { + struct radiusd_module_ref *modref; + + modref = create_module_ref($2); + free($2); + if (modref == NULL) + YYERROR; + authen.auth = modref; + } + | DECORATE_BY str_l { + int i; + struct radiusd_module_ref *modref; + + for (i = 0; i < $2.c; i++) { + if ((modref = create_module_ref($2.v[i])) + == NULL) { + free_str_l(&$2); + YYERROR; + } + TAILQ_INSERT_TAIL(&authen.deco, modref, next); + } + free_str_l(&$2); + } + ; +str_l : str_l strnum { + int i; + char **v; + if ((v = calloc(sizeof(char **), $$.c + 2)) == NULL) + goto outofmemory; + for (i = 0; i < $$.c; i++) + v[i] = $$.v[i]; + v[i++] = $2; + v[i] = NULL; + $$.c++; + free($$.v); + $$.v = v; + } + | strnum { + if (($$.v = calloc(sizeof(char **), 2)) == NULL) + goto outofmemory; + $$.v[0] = $1; + $$.v[1] = NULL; + $$.c = 1; + } + ; +strnum : STRING { $$ = $1; } + | NUMBER { + /* Treat number as a string */ + asprintf(&($$), "%jd", (intmax_t)$1); + if ($$ == NULL) + goto outofmemory; + } + ; +optnl : + | '\n' + ; +nl : '\n' optnl /* one new line or more */ + ; +optnl_l : + | '\n' optnl_l + ; +yesno : YES { $$ = true; } + | NO { $$ = false; } + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "authenticate", AUTHENTICATE}, + { "authenticate-by", AUTHENTICATE_BY}, + { "client", CLIENT}, + { "decorate-by", DECORATE_BY}, + { "include", INCLUDE}, + { "listen", LISTEN}, + { "load", LOAD}, + { "module", MODULE}, + { "msgauth-required", MSGAUTH_REQUIRED}, + { "no", NO}, + { "on", ON}, + { "port", PORT}, + { "secret", SECRET}, + { "set", SET}, + { "yes", YES}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + fatal("yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + fatal("yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(const char *filename, struct radiusd *radiusd) +{ + int errors = 0; + struct radiusd_listen *l; + struct radiusd_module_ref *m, *mt; + + conf = radiusd; + radiusd_conf_init(conf); + radiusd_authentication_init(&authen); + radiusd_client_init(&client); + authen.auth = NULL; + + if ((file = pushfile(filename)) == NULL) { + errors++; + goto out; + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + if (TAILQ_EMPTY(&conf->listen)) { + if ((l = malloc(sizeof(struct radiusd_listen))) == NULL) { + log_warn("Out of memory"); + return (-1); + } + l->stype = SOCK_DGRAM; + l->sproto = IPPROTO_UDP; + l->addr.ipv4.sin_family = AF_INET; + l->addr.ipv4.sin_len = sizeof(struct sockaddr_in); + l->addr.ipv4.sin_addr.s_addr = htonl(0x7F000001L); + l->addr.ipv4.sin_port = htons(RADIUS_DEFAULT_PORT); + TAILQ_INSERT_TAIL(&conf->listen, l, next); + } + TAILQ_FOREACH(l, &conf->listen, next) { + l->sock = -1; + } + if (authen.auth != NULL) + free(authen.auth); + TAILQ_FOREACH_SAFE(m, &authen.deco, next, mt) { + TAILQ_REMOVE(&authen.deco, m, next); + free(m); + } +out: + conf = NULL; + return (errors ? -1 : 0); +} + +static struct radiusd_module * +find_module(const char *name) +{ + struct radiusd_module *module; + + TAILQ_FOREACH(module, &conf->module, next) { + if (strcmp(name, module->name) == 0) + return (module); + } + + return (NULL); +} + +static void +free_str_l(void *str_l0) +{ + int i; + struct { + char **v; + int c; + } *str_l = str_l0; + + for (i = 0; i < str_l->c; i++) + free(str_l->v[i]); + free(str_l->v); +} + +static struct radiusd_module_ref * +create_module_ref(const char *modulename) +{ + struct radiusd_module *module; + struct radiusd_module_ref *modref; + + if ((module = find_module(modulename)) == NULL) { + yyerror("module `%s' is not found", modulename); + return (NULL); + } + if ((modref = calloc(1, sizeof(struct radiusd_module_ref))) == NULL) { + yyerror("Out of memory: %s", strerror(errno)); + return (NULL); + } + modref->module = module; + + return (modref); +} + +static void +radiusd_authentication_init(struct radiusd_authentication *auth) +{ + memset(auth, 0, sizeof(struct radiusd_authentication)); + TAILQ_INIT(&auth->deco); +} + +static void +radiusd_client_init(struct radiusd_client *clnt) +{ + memset(clnt, 0, sizeof(struct radiusd_client)); + clnt->msgauth_required = true; +} diff --git a/usr.sbin/radiusd/radiusd.8 b/usr.sbin/radiusd/radiusd.8 new file mode 100644 index 00000000000..e63e3562e4f --- /dev/null +++ b/usr.sbin/radiusd/radiusd.8 @@ -0,0 +1,46 @@ +.\" Copyright (c) 2013 Internet Initiative Japan Inc. +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd July 7, 2013 +.Dt RADIUSD 8 +.Os +.Sh NAME +.Nm radiusd +.Nd Remote Authentication Dial In User Service (RADIUS) daemon +.Sh SYNOPSIS +.Nm radiusd +.Op Fl dn +.Op Fl f Ar file +.Sh DESCRIPTION +The +.Nm +daemon implements the RADIUS protocol. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl h +Display usage text and exit. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.El +.Sh SEE ALSO +.Xr radiusd.conf 5 +.Rs +.%R RFC 2865 +.%T Remote Authentication Dial In User Service (RADIUS) +.%D June 2000 +.Re diff --git a/usr.sbin/radiusd/radiusd.c b/usr.sbin/radiusd/radiusd.c new file mode 100644 index 00000000000..4a0a82d655e --- /dev/null +++ b/usr.sbin/radiusd/radiusd.c @@ -0,0 +1,1497 @@ +/* $OpenBSD: radiusd.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2013 Internet Initiative Japan Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <netinet/in.h> + +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <imsg.h> +#include <md5.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <syslog.h> +#include <unistd.h> +#include <util.h> + +#include <radius.h> + +#include "radiusd.h" +#include "radiusd_local.h" +#include "log.h" +#include "util.h" +#include "imsg_subr.h" + +static int radiusd_start(struct radiusd *); +static void radiusd_stop(struct radiusd *); +static void radiusd_free(struct radiusd *); +static void radiusd_listen_on_event(int, short, void *); +static void radiusd_on_sigterm(int, short, void *); +static void radiusd_on_sigint(int, short, void *); +static void radiusd_on_sighup(int, short, void *); +static void radiusd_on_sigchld(int, short, void *); +static int radius_query_request_decoration(struct radius_query *); +static int radius_query_response_decoration( + struct radius_query *); +static const char *radius_code_string(int); +static int radiusd_access_response_fixup (struct radius_query *); + + + +static void radiusd_module_reset_ev_handler( + struct radiusd_module *); +static int radiusd_module_imsg_read(struct radiusd_module *, + bool); +static void radiusd_module_imsg(struct radiusd_module *, + struct imsg *); + +static struct radiusd_module_radpkt_arg * + radiusd_module_recv_radpkt(struct radiusd_module *, + struct imsg *, uint32_t, const char *); +static void radiusd_module_on_imsg_io(int, short, void *); +void radiusd_module_start(struct radiusd_module *); +void radiusd_module_stop(struct radiusd_module *); +static void radiusd_module_close(struct radiusd_module *); +static void radiusd_module_userpass(struct radiusd_module *, + struct radius_query *); +static void radiusd_module_access_request(struct radiusd_module *, + struct radius_query *); + +static u_int radius_query_id_seq = 0; +int debug = 0; + +static __dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dh] [-f file]\n", __progname); + exit(EX_USAGE); +} + +int +main(int argc, char *argv[]) +{ + extern char *__progname; + const char *conffile = CONFFILE; + int ch; + struct radiusd *radiusd; + bool noaction = false; + struct passwd *pw; + + while ((ch = getopt(argc, argv, "df:nh")) != -1) + switch (ch) { + case 'd': + debug++; + break; + + case 'f': + conffile = optarg; + break; + + case 'n': + noaction = true; + break; + + case 'h': + usage(); + /* NOTREACHED */ + } + + argc -= optind; + argv += optind; + + if (argc != 0) + usage(); + + if ((radiusd = calloc(1, sizeof(*radiusd))) == NULL) + err(1, "calloc"); + TAILQ_INIT(&radiusd->listen); + TAILQ_INIT(&radiusd->query); + + log_init(debug); + event_init(); + if (parse_config(conffile, radiusd) != 0) + errx(EX_DATAERR, "config error"); + if (noaction) { + fprintf(stderr, "configuration OK\n"); + exit(EXIT_SUCCESS); + } + + if (debug == 0) + daemon(0, 0); + + if ((pw = getpwnam(RADIUSD_USER)) == NULL) + errx(EXIT_FAILURE, "user `%s' is not found in password " + "database", RADIUSD_USER); + + if (chroot(pw->pw_dir) == -1) + err(EXIT_FAILURE, "chroot"); + if (chdir("/") == -1) + err(EXIT_FAILURE, "chdir(\"/\")"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + err(EXIT_FAILURE, "cannot drop privileges"); + + signal(SIGPIPE, SIG_IGN); + openlog(NULL, LOG_PID, LOG_DAEMON); + + signal_set(&radiusd->ev_sigterm, SIGTERM, radiusd_on_sigterm, radiusd); + signal_set(&radiusd->ev_sigint, SIGINT, radiusd_on_sigint, radiusd); + signal_set(&radiusd->ev_sighup, SIGHUP, radiusd_on_sighup, radiusd); + signal_set(&radiusd->ev_sigchld, SIGCHLD, radiusd_on_sigchld, radiusd); + + if (radiusd_start(radiusd) != 0) + errx(EX_DATAERR, "start failed"); + + if (event_loop(0) < 0) + radiusd_stop(radiusd); + + radiusd_free(radiusd); + event_base_free(NULL); + + exit(EXIT_SUCCESS); +} + +static int +radiusd_start(struct radiusd *radiusd) +{ + struct radiusd_listen *l; + struct radiusd_module *module; + int s; + char hbuf[NI_MAXHOST]; + + TAILQ_FOREACH(l, &radiusd->listen, next) { + if (getnameinfo( + (struct sockaddr *)&l->addr, l->addr.ipv4.sin_len, + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) { + log_warn("%s: getnameinfo()", __func__); + goto on_error; + } + if ((s = socket(l->addr.ipv4.sin_family, l->stype, l->sproto)) + < 0) { + log_warn("Listen %s port %d is failed: socket()", + hbuf, (int)htons(l->addr.ipv4.sin_port)); + goto on_error; + } + if (bind(s, (struct sockaddr *)&l->addr, l->addr.ipv4.sin_len) + != 0) { + log_warn("Listen %s port %d is failed: bind()", + hbuf, (int)htons(l->addr.ipv4.sin_port)); + close(s); + goto on_error; + } + if (l->addr.ipv4.sin_family == AF_INET) + log_info("Start listening on %s:%d/udp", hbuf, + (int)ntohs(l->addr.ipv4.sin_port)); + else + log_info("Start listening on [%s]:%d/udp", hbuf, + (int)ntohs(l->addr.ipv4.sin_port)); + event_set(&l->ev, s, EV_READ | EV_PERSIST, + radiusd_listen_on_event, l); + if (event_add(&l->ev, NULL) != 0) { + log_warn("event_add() failed at %s()", __func__); + close(s); + goto on_error; + } + l->sock = s; + l->radiusd = radiusd; + } + + signal_add(&radiusd->ev_sigterm, NULL); + signal_add(&radiusd->ev_sigint, NULL); + signal_add(&radiusd->ev_sighup, NULL); + signal_add(&radiusd->ev_sigchld, NULL); + + TAILQ_FOREACH(module, &radiusd->module, next) { + radiusd_module_start(module); + } + + return (0); +on_error: + radiusd_stop(radiusd); + + return (-1); +} + +static void +radiusd_stop(struct radiusd *radiusd) +{ + char hbuf[NI_MAXHOST]; + struct radiusd_listen *l; + struct radiusd_module *module; + + TAILQ_FOREACH_REVERSE(l, &radiusd->listen, radiusd_listen_head, next) { + if (l->sock >= 0) { + if (getnameinfo( + (struct sockaddr *)&l->addr, l->addr.ipv4.sin_len, + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + strlcpy(hbuf, "error", sizeof(hbuf)); + if (l->addr.ipv4.sin_family == AF_INET) + log_info("Stop listening on %s:%d/udp", hbuf, + (int)ntohs(l->addr.ipv4.sin_port)); + else + log_info("Stop listening on [%s]:%d/udp", hbuf, + (int)ntohs(l->addr.ipv4.sin_port)); + event_del(&l->ev); + close(l->sock); + } + l->sock = -1; + } + TAILQ_FOREACH(module, &radiusd->module, next) { + radiusd_module_stop(module); + radiusd_module_close(module); + } + if (signal_pending(&radiusd->ev_sigterm, NULL)) + signal_del(&radiusd->ev_sigterm); + if (signal_pending(&radiusd->ev_sigint, NULL)) + signal_del(&radiusd->ev_sigint); + if (signal_pending(&radiusd->ev_sighup, NULL)) + signal_del(&radiusd->ev_sighup); + if (signal_pending(&radiusd->ev_sigchld, NULL)) + signal_del(&radiusd->ev_sigchld); +} + +static void +radiusd_free(struct radiusd *radiusd) +{ + int i; + struct radiusd_listen *listn, *listnt; + struct radiusd_client *client, *clientt; + struct radiusd_module *module, *modulet; + struct radiusd_module_ref *modref, *modreft; + struct radiusd_authentication *authen, *authent; + + TAILQ_FOREACH_SAFE(authen, &radiusd->authen, next, authent) { + TAILQ_REMOVE(&radiusd->authen, authen, next); + if (authen->auth != NULL) + free(authen->auth); + TAILQ_FOREACH_SAFE(modref, &authen->deco, next, modreft) { + TAILQ_REMOVE(&authen->deco, modref, next); + free(modref); + } + for (i = 0; authen->username[i] != NULL; i++) + free(authen->username[i]); + free(authen->username); + free(authen); + } + TAILQ_FOREACH_SAFE(module, &radiusd->module, next, modulet) { + TAILQ_REMOVE(&radiusd->module, module, next); + radiusd_module_unload(module); + } + TAILQ_FOREACH_SAFE(client, &radiusd->client, next, clientt) { + TAILQ_REMOVE(&radiusd->client, client, next); + explicit_bzero(client->secret, sizeof(client->secret)); + free(client); + } + TAILQ_FOREACH_SAFE(listn, &radiusd->listen, next, listnt) { + TAILQ_REMOVE(&radiusd->listen, listn, next); + free(listn); + } + free(radiusd); +} + +/*********************************************************************** + * Network event handlers + ***********************************************************************/ +#define IPv4_cmp(_in, _addr, _mask) ( \ + ((_in)->s_addr & (_mask)->addr.ipv4.s_addr) == \ + (_addr)->addr.ipv4.s_addr) +#define s6_addr32(_in6) ((uint32_t *)(_in6)->s6_addr) +#define IPv6_cmp(_in6, _addr, _mask) ( \ + ((s6_addr32(_in6)[3] & (_mask)->addr.addr32[3]) \ + == (_addr)->addr.addr32[3]) && \ + ((s6_addr32(_in6)[2] & (_mask)->addr.addr32[2]) \ + == (_addr)->addr.addr32[2]) && \ + ((s6_addr32(_in6)[1] & (_mask)->addr.addr32[1]) \ + == (_addr)->addr.addr32[1]) && \ + ((s6_addr32(_in6)[0] & (_mask)->addr.addr32[0]) \ + == (_addr)->addr.addr32[0])) + +static void +radiusd_listen_on_event(int fd, short evmask, void *ctx) +{ + int i, sz, req_id, req_code; + struct radiusd_listen *listn = ctx; + static u_char buf[65535]; + static char username[256]; + struct sockaddr_storage peer; + socklen_t peersz; + RADIUS_PACKET *packet = NULL; + char peerstr[NI_MAXHOST + NI_MAXSERV + 30]; + struct radiusd_authentication *authen; + struct radiusd_client *client; + struct radius_query *q; +#define in(_x) (((struct sockaddr_in *)_x)->sin_addr) +#define in6(_x) (((struct sockaddr_in6 *)_x)->sin6_addr) + + if (evmask & EV_READ) { + peersz = sizeof(peer); + if ((sz = recvfrom(listn->sock, buf, sizeof(buf), 0, + (struct sockaddr *)&peer, &peersz)) < 0) { + log_warn("%s: recvfrom() failed", __func__); + goto on_error; + } + RADIUSD_ASSERT(peer.ss_family == AF_INET || + peer.ss_family == AF_INET6); + + /* prepare some information about this messages */ + if (addrport_tostring((struct sockaddr *)&peer, peersz, + peerstr, sizeof(peerstr)) == NULL) { + log_warn("%s: getnameinfo() failed", __func__); + goto on_error; + } + if ((packet = radius_convert_packet(buf, sz)) == NULL) { + log_warn("%s: radius_convert_packet() failed", + __func__); + goto on_error; + } + req_id = radius_get_id(packet); + req_code = radius_get_code(packet); + + /* + * Find a matching `client' entry + */ + TAILQ_FOREACH(client, &listn->radiusd->client, next) { + if (client->af != peer.ss_family) + continue; + if (peer.ss_family == AF_INET && + IPv4_cmp(&((struct sockaddr_in *)&peer)->sin_addr, + &client->addr, &client->mask)) + break; + else if (peer.ss_family == AF_INET6 && + IPv6_cmp(&((struct sockaddr_in6 *)&peer)->sin6_addr, + &client->addr, &client->mask)) + break; + } + if (client == NULL) { + log_warnx("Received %s(code=%d) from %s id=%d: " + "no `client' matches", radius_code_string(req_code), + req_code, peerstr, req_id); + goto on_error; + } + + /* Check the client's Message-Authenticator */ + if (client->msgauth_required && + !radius_has_attr(packet, + RADIUS_TYPE_MESSAGE_AUTHENTICATOR)) { + log_warnx("Received %s(code=%d) from %s id=%d: " + "no message authenticator", + radius_code_string(req_code), req_code, peerstr, + req_id); + goto on_error; + } + + if (radius_has_attr(packet, + RADIUS_TYPE_MESSAGE_AUTHENTICATOR) && + radius_check_message_authenticator(packet, client->secret) + != 0) { + log_warnx("Received %s(code=%d) from %s id=%d: " + "bad message authenticator", + radius_code_string(req_code), req_code, peerstr, + req_id); + goto on_error; + } + + /* + * Find a duplicate request. In RFC 2865, it has the same + * source IP address and source UDP port and Identifier. + */ + TAILQ_FOREACH(q, &listn->radiusd->query, next) { + if (peer.ss_family == q->clientaddr.ss_family && + ((peer.ss_family == AF_INET && + in(&q->clientaddr).s_addr == + in(&peer).s_addr) || + (peer.ss_family == AF_INET6 && + IN6_ARE_ADDR_EQUAL( + &in6(&q->clientaddr), &in6(&peer)))) && + ((struct sockaddr_in *)&q->clientaddr)->sin_port == + ((struct sockaddr_in *)&peer)->sin_port && + req_id == q->req_id) + break; /* found it */ + } + if (q != NULL) { + /* RFC 5080 suggests to answer the cached result */ + log_info("Received %s(code=%d) from %s id=%d: " + "duplicate request by q=%u", + radius_code_string(req_code), req_code, peerstr, + req_id, q->id); + // XXX + return; + } + + /* FIXME: we can support other request codes */ + if (req_code != RADIUS_CODE_ACCESS_REQUEST) { + log_info("Received %s(code=%d) from %s id=%d: %s " + "is not supported in this implementation", + radius_code_string(req_code), req_code, peerstr, + req_id, radius_code_string(req_code)); + goto on_error; + } + + /* + * Find a matching `authenticate' entry + */ + if (radius_get_string_attr(packet, RADIUS_TYPE_USER_NAME, + username, sizeof(username)) != 0) { + log_info("Received %s(code=%d) from %s id=%d: " + "no User-Name attribute", + radius_code_string(req_code), req_code, peerstr, + req_id); + goto on_error; + } + TAILQ_FOREACH(authen, &listn->radiusd->authen, next) { + for (i = 0; authen->username[i] != NULL; i++) { + if (fnmatch(authen->username[i], username, 0) + == 0) + goto found; + } + } + if (authen == NULL) { + log_warnx("Received %s(code=%d) from %s id=%d " + "username=%s: no `authenticate' matches.", + radius_code_string(req_code), req_code, peerstr, + req_id, username); + goto on_error; + } +found: + if (!MODULE_DO_USERPASS(authen->auth->module) && + !MODULE_DO_ACCSREQ(authen->auth->module)) { + log_warnx("Received %s(code=%d) from %s id=%d " + "username=%s: module `%s' is not running.", + radius_code_string(req_code), req_code, peerstr, + req_id, username, authen->auth->module->name); + goto on_error; + } + if ((q = calloc(1, sizeof(struct radius_query))) == NULL) { + log_warn("%s: Out of memory", __func__); + goto on_error; + } + memcpy(&q->clientaddr, &peer, peersz); + strlcpy(q->username, username, sizeof(q->username)); + q->id = ++radius_query_id_seq; + q->clientaddrlen = peersz; + q->authen = authen; + q->listen = listn; + q->req = packet; + q->client = client; + q->req_id = req_id; + radius_get_authenticator(packet, q->req_auth); + + if (radius_query_request_decoration(q) != 0) { + log_warnx( + "Received %s(code=%d) from %s id=%d username=%s " + "q=%u: failed to decorate the request", + radius_code_string(req_code), req_code, peerstr, + q->req_id, q->username, q->id); + radiusd_access_request_aborted(q); + return; + } + log_info("Received %s(code=%d) from %s id=%d username=%s " + "q=%u: `%s' authentication is starting", + radius_code_string(req_code), req_code, peerstr, q->req_id, + q->username, q->id, q->authen->auth->module->name); + TAILQ_INSERT_TAIL(&listn->radiusd->query, q, next); + + if (MODULE_DO_ACCSREQ(authen->auth->module)) { + radiusd_module_access_request(authen->auth->module, q); + } else if (MODULE_DO_USERPASS(authen->auth->module)) + radiusd_module_userpass(authen->auth->module, q); + + return; + } +on_error: + if (packet != NULL) + radius_delete_packet(packet); +#undef in +#undef in6 + + return; +} + +static int +radius_query_request_decoration(struct radius_query *q) +{ + struct radiusd_module_ref *deco; + + TAILQ_FOREACH(deco, &q->authen->deco, next) { + /* XXX module is running? */ + /* XXX */ + if (deco->module->request_decoration != NULL && + deco->module->request_decoration(NULL, q) != 0) { + log_warnx("q=%u request decoration `%s' failed", q->id, + deco->module->name); + return (-1); + } + } + + return (0); +} + +static int +radius_query_response_decoration(struct radius_query *q) +{ + struct radiusd_module_ref *deco; + + TAILQ_FOREACH(deco, &q->authen->deco, next) { + /* XXX module is running? */ + /* XXX */ + if (deco->module->response_decoration != NULL && + deco->module->response_decoration(NULL, q) != 0) { + log_warnx("q=%u response decoration `%s' failed", q->id, + deco->module->name); + return (-1); + } + } + + return (0); +} + +/*********************************************************************** + * Callback functions from the modules + ***********************************************************************/ +void +radiusd_access_request_answer(struct radius_query *q) +{ + int sz, res_id, res_code; + char buf[NI_MAXHOST + NI_MAXSERV + 30]; + const char *authen_secret = q->authen->auth->module->secret; + + radius_set_request_packet(q->res, q->req); + + if (authen_secret == NULL) { + /* + * The module couldn't check the autheticators + */ + if (radius_check_response_authenticator(q->res, + q->client->secret) != 0) { + log_info("Response from module has bad response " + "authenticator: id=%d", q->id); + goto on_error; + } + if (radius_has_attr(q->res, + RADIUS_TYPE_MESSAGE_AUTHENTICATOR) && + radius_check_message_authenticator(q->res, + q->client->secret) != 0) { + log_info("Response from module has bad message " + "authenticator: id=%d", q->id); + goto on_error; + } + } + + /* Decorate the response */ + if (radius_query_response_decoration(q) != 0) + goto on_error; + + if (radiusd_access_response_fixup(q) != 0) + goto on_error; + + res_id = radius_get_id(q->res); + res_code = radius_get_code(q->res); + + /* + * Reset response authenticator in the following cases: + * - response is modified by decorator + * - server's secret is differ from client's secret. + */ + if (q->res_modified > 0 || + (authen_secret != NULL && + strcmp(q->client->secret, authen_secret) != 0)) + radius_set_response_authenticator(q->res, q->client->secret); + + log_info("Sending %s(code=%d) to %s id=%u q=%u", + radius_code_string(res_code), res_code, + addrport_tostring((struct sockaddr *)&q->clientaddr, + q->clientaddrlen, buf, sizeof(buf)), res_id, q->id); + + if ((sz = sendto(q->listen->sock, radius_get_data(q->res), + radius_get_length(q->res), 0, + (struct sockaddr *)&q->clientaddr, q->clientaddrlen)) <= 0) + log_warn("Sending a RADIUS response failed"); +on_error: + radiusd_access_request_aborted(q); +} + +void +radiusd_access_request_aborted(struct radius_query *q) +{ + if (q->req != NULL) + radius_delete_packet(q->req); + if (q->res != NULL) + radius_delete_packet(q->res); + TAILQ_REMOVE(&q->listen->radiusd->query, q, next); + free(q); +} + +/*********************************************************************** + * Signal handlers + ***********************************************************************/ +static void +radiusd_on_sigterm(int fd, short evmask, void *ctx) +{ + struct radiusd *radiusd = ctx; + + log_info("Received SIGTERM"); + radiusd_stop(radiusd); +} + +static void +radiusd_on_sigint(int fd, short evmask, void *ctx) +{ + struct radiusd *radiusd = ctx; + + log_info("Received SIGINT"); + radiusd_stop(radiusd); +} + +static void +radiusd_on_sighup(int fd, short evmask, void *ctx) +{ + log_info("Received SIGHUP"); +} + +static void +radiusd_on_sigchld(int fd, short evmask, void *ctx) +{ + struct radiusd *radiusd = ctx; + struct radiusd_module *module; + pid_t pid; + int status; + + log_debug("Received SIGCHLD"); + while ((pid = wait3(&status, WNOHANG, NULL)) != 0) { + if (pid == -1) + break; + TAILQ_FOREACH(module, &radiusd->module, next) { + if (module->pid == pid) { + if (WIFEXITED(status)) + log_warnx("module `%s'(pid=%d) exited " + "with status %d", module->name, + (int)pid, WEXITSTATUS(status)); + else + log_warnx("module `%s'(pid=%d) exited " + "by signal %d", module->name, + (int)pid, WTERMSIG(status)); + break; + } + } + if (!module) { + if (WIFEXITED(status)) + log_warnx("unkown child process pid=%d exited " + "with status %d", (int)pid, + WEXITSTATUS(status)); + else + log_warnx("unkown child process pid=%d exited " + "by signal %d", (int)pid, + WTERMSIG(status)); + } + } +} + +static const char * +radius_code_string(int code) +{ + int i; + struct _codestrings { + int code; + const char *string; + } codestrings[] = { + { RADIUS_CODE_ACCESS_REQUEST, "Access-Request" }, + { RADIUS_CODE_ACCESS_ACCEPT, "Access-Accept" }, + { RADIUS_CODE_ACCESS_REJECT, "Access-Reject" }, + { RADIUS_CODE_ACCOUNTING_REQUEST, "Accounting-Request" }, + { RADIUS_CODE_ACCOUNTING_RESPONSE, "Accounting-Response" }, + { RADIUS_CODE_ACCESS_CHALLENGE, "Access-Challenge" }, + { RADIUS_CODE_STATUS_SERVER, "Status-Server" }, + { RADIUS_CODE_STATUS_CLIENT, "Status-Clinet" }, + { -1, NULL } + }; + + for (i = 0; codestrings[i].code != -1; i++) + if (codestrings[i].code == code) + return (codestrings[i].string); + + return "Unknown"; +} + +void +radiusd_conf_init(struct radiusd *conf) +{ + + TAILQ_INIT(&conf->listen); + TAILQ_INIT(&conf->module); + TAILQ_INIT(&conf->authen); + TAILQ_INIT(&conf->client); + + /* + * TODO: load the standard modules + */ +#if 0 +static struct radiusd_module *radiusd_standard_modules[] = { + NULL +}; + + u_int i; + struct radiusd_module *module; + for (i = 0; radiusd_standard_modules[i] != NULL; i++) { + module = radiusd_create_module_class( + radiusd_standard_modules[i]); + TAILQ_INSERT_TAIL(&conf->module, module, next); + } +#endif + + return; +} + +/* + * Fix some attributes which depend the secret value. + */ +static int +radiusd_access_response_fixup(struct radius_query *q) +{ + int res_id; + size_t attrlen; + u_char req_auth[16], attrbuf[256]; + const char *olds, *news; + const char *authen_secret = q->authen->auth->module->secret; + + olds = q->client->secret; + news = authen_secret; + if (news == NULL) + olds = news; + radius_get_authenticator(q->req, req_auth); + + if ((authen_secret != NULL && + strcmp(authen_secret, q->client->secret) != 0) || + timingsafe_bcmp(q->req_auth, req_auth, 16) != 0) { + + /* RFC 2865 Tunnel-Password */ + if (radius_get_raw_attr(q->res, RADIUS_TYPE_TUNNEL_PASSWORD, + attrbuf, &attrlen) == 0) { + radius_attr_unhide(news, req_auth, + attrbuf, attrbuf + 3, attrlen - 3); + radius_attr_hide(olds, q->req_auth, + attrbuf, attrbuf + 3, attrlen - 3); + + radius_del_attr_all(q->res, + RADIUS_TYPE_TUNNEL_PASSWORD); + radius_put_raw_attr(q->res, + RADIUS_TYPE_TUNNEL_PASSWORD, attrbuf, attrlen); + q->res_modified++; + } + + /* RFC 2548 Microsoft MPPE-{Send,Recv}-Key */ + if (radius_get_vs_raw_attr(q->res, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_SEND_KEY, attrbuf, &attrlen) == 0) { + + /* Re-crypt the KEY */ + radius_attr_unhide(news, req_auth, + attrbuf, attrbuf + 2, attrlen - 2); + radius_attr_hide(olds, q->req_auth, + attrbuf, attrbuf + 2, attrlen - 2); + + radius_del_vs_attr_all(q->res, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_SEND_KEY); + radius_put_vs_raw_attr(q->res, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_SEND_KEY, attrbuf, attrlen); + q->res_modified++; + } + if (radius_get_vs_raw_attr(q->res, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_RECV_KEY, attrbuf, &attrlen) == 0) { + + /* Re-crypt the KEY */ + radius_attr_unhide(news, req_auth, + attrbuf, attrbuf + 2, attrlen - 2); + radius_attr_hide(olds, q->req_auth, + attrbuf, attrbuf + 2, attrlen - 2); + + radius_del_vs_attr_all(q->res, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_RECV_KEY); + radius_put_vs_raw_attr(q->res, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_RECV_KEY, attrbuf, attrlen); + q->res_modified++; + } + } + + res_id = radius_get_id(q->res); + if (res_id != q->req_id) { + /* authentication server change the id */ + radius_set_id(q->res, q->req_id); + q->res_modified++; + } + + return (0); +} + +void +radius_attr_hide(const char *secret, const char *authenticator, + const u_char *salt, u_char *plain, int plainlen) +{ + int i, j; + u_char b[16]; + MD5_CTX md5ctx; + + i = 0; + do { + MD5Init(&md5ctx); + MD5Update(&md5ctx, secret, strlen(secret)); + if (i == 0) { + MD5Update(&md5ctx, authenticator, 16); + if (salt != NULL) + MD5Update(&md5ctx, salt, 2); + } else + MD5Update(&md5ctx, plain + i - 16, 16); + MD5Final(b, &md5ctx); + + for (j = 0; j < 16 && i < plainlen; i++, j++) + plain[i] ^= b[j]; + } while (i < plainlen); +} + +void +radius_attr_unhide(const char *secret, const char *authenticator, + const u_char *salt, u_char *crypt0, int crypt0len) +{ + int i, j; + u_char b[16]; + MD5_CTX md5ctx; + + i = 16 * ((crypt0len - 1) / 16); + while (i >= 0) { + MD5Init(&md5ctx); + MD5Update(&md5ctx, secret, strlen(secret)); + if (i == 0) { + MD5Update(&md5ctx, authenticator, 16); + if (salt != NULL) + MD5Update(&md5ctx, salt, 2); + } else + MD5Update(&md5ctx, crypt0 + i - 16, 16); + MD5Final(b, &md5ctx); + + for (j = 0; j < 16 && i + j < crypt0len; j++) + crypt0[i + j] ^= b[j]; + i -= 16; + } +} + +static struct radius_query * +radiusd_find_query(struct radiusd *radiusd, u_int q_id) +{ + struct radius_query *q; + + TAILQ_FOREACH(q, &radiusd->query, next) { + if (q->id == q_id) + return (q); + } + return (NULL); +} + +/*********************************************************************** + * radiusd module handling + ***********************************************************************/ +struct radiusd_module * +radiusd_module_load(struct radiusd *radiusd, const char *path, const char *name) +{ + struct radiusd_module *module = NULL; + pid_t pid; + int on, pairsock[] = { -1, -1 }; + const char *av[3]; + ssize_t n; + struct imsg imsg; + + module = calloc(1, sizeof(struct radiusd_module)); + if (module == NULL) + fatal("Out of memory"); + module->radiusd = radiusd; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pairsock) == -1) { + log_warn("Could not load module `%s'(%s): pipe()", name, path); + goto on_error; + } + + pid = fork(); + if (pid == -1) { + log_warn("Could not load module `%s'(%s): fork()", name, path); + goto on_error; + } + if (pid == 0) { + setsid(); + close(pairsock[0]); + av[0] = path; + av[1] = name; + av[2] = NULL; + dup2(pairsock[1], STDIN_FILENO); + dup2(pairsock[1], STDOUT_FILENO); + close(pairsock[1]); + closefrom(STDERR_FILENO + 1); + execv(path, (char * const *)av); + warn("Failed to execute %s", path); + _exit(EXIT_FAILURE); + } + close(pairsock[1]); + + module->fd = pairsock[0]; + on = 1; + if (fcntl(module->fd, O_NONBLOCK, &on) == -1) { + log_warn("Could not load module `%s': fcntl(,O_NONBLOCK)", + name); + goto on_error; + } + strlcpy(module->name, name, sizeof(module->name)); + module->pid = pid; + imsg_init(&module->ibuf, module->fd); + + if (imsg_sync_read(&module->ibuf, MODULE_IO_TIMEOUT) <= 0 || + (n = imsg_get(&module->ibuf, &imsg)) <= 0) { + log_warnx("Could not load module `%s': module didn't " + "respond", name); + goto on_error; + } + if (imsg.hdr.type != IMSG_RADIUSD_MODULE_LOAD) { + imsg_free(&imsg); + log_warnx("Could not load module `%s': unknown imsg type=%d", + name, imsg.hdr.type); + goto on_error; + } + + module->capabilities = + ((struct radiusd_module_load_arg *)imsg.data)->cap; + radiusd_module_reset_ev_handler(module); + + log_debug("Loaded module `%s' successfully. pid=%d", module->name, + module->pid); + imsg_free(&imsg); + + return (module); + +on_error: + if (module != NULL) + free(module); + if (pairsock[0] >= 0) + close(pairsock[0]); + if (pairsock[1] >= 0) + close(pairsock[1]); + + return (NULL); +} + +void +radiusd_module_start(struct radiusd_module *module) +{ + int datalen; + struct imsg imsg; + + RADIUSD_ASSERT(module->fd >= 0); + imsg_compose(&module->ibuf, IMSG_RADIUSD_MODULE_START, 0, 0, -1, + NULL, 0); + imsg_sync_flush(&module->ibuf, MODULE_IO_TIMEOUT); + if (imsg_sync_read(&module->ibuf, MODULE_IO_TIMEOUT) <= 0 || + imsg_get(&module->ibuf, &imsg) <= 0) { + log_warnx("Module `%s' could not start: no response", + module->name); + goto on_fail; + } + + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (imsg.hdr.type != IMSG_OK) { + if (imsg.hdr.type == IMSG_NG) { + if (datalen > 0) + log_warnx("Module `%s' could not start: %s", + module->name, (char *)imsg.data); + else + log_warnx("Module `%s' could not start", + module->name); + } else + log_warnx("Module `%s' could not started: module " + "returned unknow message type %d", module->name, + imsg.hdr.type); + goto on_fail; + } + + log_debug("Module `%s' started successfully", module->name); + radiusd_module_reset_ev_handler(module); + return; +on_fail: + radiusd_module_close(module); + return; +} + +void +radiusd_module_stop(struct radiusd_module *module) +{ + RADIUSD_ASSERT(module->fd >= 0); + + module->stopped = true; + + if (module->secret != NULL) + explicit_bzero(module->secret, strlen(module->secret)); + free(module->secret); + module->secret = NULL; + + imsg_compose(&module->ibuf, IMSG_RADIUSD_MODULE_STOP, 0, 0, -1, + NULL, 0); + radiusd_module_reset_ev_handler(module); +} + +static void +radiusd_module_close(struct radiusd_module *module) +{ + if (module->fd >= 0) { + event_del(&module->ev); + imsg_clear(&module->ibuf); + close(module->fd); + module->fd = -1; + } +} + +void +radiusd_module_unload(struct radiusd_module *module) +{ + free(module->radpkt); + radiusd_module_close(module); + free(module); +} + +static void +radiusd_module_on_imsg_io(int fd, short evmask, void *ctx) +{ + struct radiusd_module *module = ctx; + int ret; + + if (evmask & EV_WRITE) + module->writeready = true; + + if (evmask & EV_READ || module->ibuf.r.wpos > IMSG_HEADER_SIZE) { + if (radiusd_module_imsg_read(module, + (evmask & EV_READ)? true : false) == -1) + goto on_error; + } + + while (module->writeready && module->ibuf.w.queued) { + ret = msgbuf_write(&module->ibuf.w); + if (ret > 0) + continue; + module->writeready = false; + if (ret == 0 && errno == EAGAIN) + break; + log_warn("Failed to write to module `%s': msgbuf_write()", + module->name); + goto on_error; + } + radiusd_module_reset_ev_handler(module); + + return; +on_error: + radiusd_module_close(module); +} + +static void +radiusd_module_reset_ev_handler(struct radiusd_module *module) +{ + short evmask; + struct timeval *tvp = NULL, tv = { 0, 0 }; + + RADIUSD_ASSERT(module->fd >= 0); + if (event_initialized(&module->ev)) + event_del(&module->ev); + + evmask = EV_READ; + if (module->ibuf.w.queued) { + if (!module->writeready) + evmask |= EV_WRITE; + else + tvp = &tv; /* fire immediately */ + } else if (module->ibuf.r.wpos > IMSG_HEADER_SIZE) + tvp = &tv; /* fire immediately */ + + /* module stopped and no event handler is set */ + if (evmask & EV_WRITE && tvp == NULL && module->stopped) { + /* stop requested and no more to write */ + radiusd_module_close(module); + return; + } + + event_set(&module->ev, module->fd, evmask, radiusd_module_on_imsg_io, + module); + if (event_add(&module->ev, tvp) == -1) { + log_warn("Could not set event handlers for module `%s': " + "event_add()", module->name); + radiusd_module_close(module); + } +} + +static int +radiusd_module_imsg_read(struct radiusd_module *module, bool doread) +{ + int n; + struct imsg imsg; + + if (doread) { + if ((n = imsg_read(&module->ibuf)) == -1 || n == 0) { + if (n == -1 && errno == EAGAIN) + return (0); + if (n == -1) + log_warn("Receiving a message from module `%s' " + "failed: imsg_read", module->name); + /* else closed */ + radiusd_module_close(module); + return (-1); + } + } + for (;;) { + if ((n = imsg_get(&module->ibuf, &imsg)) == -1) { + log_warn("Receiving a message from module `%s' failed: " + "imsg_get", module->name); + return (-1); + } + if (n == 0) + return (0); + radiusd_module_imsg(module, &imsg); + } + + return (0); +} + +static void +radiusd_module_imsg(struct radiusd_module *module, struct imsg *imsg) +{ + int datalen; + struct radius_query *q; + u_int q_id; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + switch (imsg->hdr.type) { + case IMSG_RADIUSD_MODULE_NOTIFY_SECRET: + if (datalen > 0) { + module->secret = strdup(imsg->data); + if (module->secret == NULL) + log_warn("Could not handle NOTIFY_SECRET " + "from `%s'", module->name); + } + break; + case IMSG_RADIUSD_MODULE_USERPASS_OK: + case IMSG_RADIUSD_MODULE_USERPASS_FAIL: + { + char *msg = NULL; + const char *msgtypestr; + + msgtypestr = (imsg->hdr.type == IMSG_RADIUSD_MODULE_USERPASS_OK) + ? "USERPASS_OK" : "USERPASS_NG"; + + q_id = *(u_int *)imsg->data; + if (datalen > (ssize_t)sizeof(u_int)) + msg = (char *)(((u_int *)imsg->data) + 1); + + q = radiusd_find_query(module->radiusd, q_id); + if (q == NULL) { + log_warnx("Received %s from `%s', but query id=%u " + "unknown", msgtypestr, module->name, q_id); + break; + } + + if ((q->res = radius_new_response_packet( + (imsg->hdr.type == IMSG_RADIUSD_MODULE_USERPASS_OK) + ? RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, + q->req)) == NULL) { + log_warn("radius_new_response_packet() failed"); + radiusd_access_request_aborted(q); + } else { + if (msg) + radius_put_string_attr(q->res, + RADIUS_TYPE_REPLY_MESSAGE, msg); + } + radius_set_response_authenticator(q->res, + q->client->secret); + radiusd_access_request_answer(q); + break; + } + case IMSG_RADIUSD_MODULE_ACCSREQ_ANSWER: + { + static struct radiusd_module_radpkt_arg *ans; + if (datalen < + (ssize_t)sizeof(struct radiusd_module_radpkt_arg)) { + log_warnx("Received ACCSREQ_ANSWER message, but " + "length is wrong"); + break; + } + q_id = ((struct radiusd_module_radpkt_arg *)imsg->data)->q_id; + q = radiusd_find_query(module->radiusd, q_id); + if (q == NULL) { + log_warnx("Received ACCSREQ_ANSWER from %s, but query " + "id=%u unknown", module->name, q_id); + break; + } + if ((ans = radiusd_module_recv_radpkt(module, imsg, + IMSG_RADIUSD_MODULE_ACCSREQ_ANSWER, + "ACCSREQ_ANSWER")) != NULL) { + q->res = radius_convert_packet( + module->radpkt, module->radpktoff); + q->res_modified = ans->modified; + radiusd_access_request_answer(q); + module->radpktoff = 0; + } + break; + } + case IMSG_RADIUSD_MODULE_ACCSREQ_ABORTED: + { + q_id = *((u_int *)imsg->data); + q = radiusd_find_query(module->radiusd, q_id); + if (q == NULL) { + log_warnx("Received ACCSREQ_ABORT from %s, but query " + "id=%u unknown", module->name, q_id); + break; + } + radiusd_access_request_aborted(q); + break; + } + default: + RADIUSD_DBG(("Unhandled imsg type=%d", + imsg->hdr.type)); + } +} + +static struct radiusd_module_radpkt_arg * +radiusd_module_recv_radpkt(struct radiusd_module *module, struct imsg *imsg, + uint32_t imsg_type, const char *type_str) +{ + struct radiusd_module_radpkt_arg *ans; + int datalen, chunklen; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + ans = (struct radiusd_module_radpkt_arg *)imsg->data; + if (module->radpktsiz < ans->datalen) { + u_char *nradpkt; + if ((nradpkt = realloc(module->radpkt, ans->datalen)) == NULL) { + log_warn("Could not handle received %s message from " + "`%s'", type_str, module->name); + goto on_fail; + } + module->radpkt = nradpkt; + module->radpktsiz = ans->datalen; + } + chunklen = datalen - sizeof(struct radiusd_module_radpkt_arg); + if (chunklen > module->radpktsiz - module->radpktoff) { + log_warnx("Could not handle received %s message from `%s': " + "received length is too big", type_str, module->name); + goto on_fail; + } + memcpy(module->radpkt + module->radpktoff, + (caddr_t)(ans + 1), chunklen); + module->radpktoff += chunklen; + if (!ans->final) + return (NULL); /* again */ + if (module->radpktoff != module->radpktsiz) { + log_warnx("Could not handle received %s message from `%s': " + "length is mismatch", type_str, module->name); + goto on_fail; + } + + return (ans); +on_fail: + module->radpktoff = 0; + return (NULL); +} + +int +radiusd_module_set(struct radiusd_module *module, const char *name, + int argc, char * const * argv) +{ + struct radiusd_module_set_arg arg; + struct radiusd_module_object *val; + int i, niov = 0; + u_char *buf = NULL, *buf0; + ssize_t n; + size_t bufsiz = 0, bufoff = 0, bufsiz0; + size_t vallen, valsiz; + struct iovec iov[2]; + struct imsg imsg; + + memset(&arg, 0, sizeof(arg)); + arg.nparamval = argc; + strlcpy(arg.paramname, name, sizeof(arg.paramname)); + + iov[niov].iov_base = &arg; + iov[niov].iov_len = sizeof(struct radiusd_module_set_arg); + niov++; + + for (i = 0; i < argc; i++) { + vallen = strlen(argv[i]) + 1; + valsiz = sizeof(struct radiusd_module_object) + vallen; + if (bufsiz < bufoff + valsiz) { + bufsiz0 = bufoff + valsiz + 128; + if ((buf0 = realloc(buf, bufsiz0)) == NULL) { + log_warn("Failed to set config parameter to " + "module `%s': realloc", module->name); + goto on_error; + } + buf = buf0; + bufsiz = bufsiz0; + memset(buf + bufoff, 0, bufsiz - bufoff); + } + val = (struct radiusd_module_object *)(buf + bufoff); + val->size = valsiz; + memcpy(val + 1, argv[i], vallen); + + bufoff += valsiz; + } + iov[niov].iov_base = buf; + iov[niov].iov_len = bufoff; + niov++; + + if (imsg_composev(&module->ibuf, IMSG_RADIUSD_MODULE_SET_CONFIG, 0, 0, + -1, iov, niov) == -1) { + log_warn("Failed to set config parameter to module `%s': " + "imsg_composev", module->name); + goto on_error; + } + if (imsg_sync_flush(&module->ibuf, MODULE_IO_TIMEOUT) == -1) { + log_warn("Failed to set config parameter to module `%s': " + "imsg_flush_timeout", module->name); + goto on_error; + } + for (;;) { + if (imsg_sync_read(&module->ibuf, MODULE_IO_TIMEOUT) <= 0) { + log_warn("Failed to get reply from module `%s': " + "imsg_sync_read", module->name); + goto on_error; + } + if ((n = imsg_get(&module->ibuf, &imsg)) > 0) + break; + if (n < 0) { + log_warn("Failed to get reply from module `%s': " + "imsg_get", module->name); + goto on_error; + } + } + if (imsg.hdr.type == IMSG_NG) { + log_warnx("Could not set `%s' for module `%s': %s", name, + module->name, (char *)imsg.data); + goto on_error; + } else if (imsg.hdr.type != IMSG_OK) { + imsg_free(&imsg); + log_warnx("Failed to get reply from module `%s': " + "unknown imsg type=%d", module->name, imsg.hdr.type); + goto on_error; + } + imsg_free(&imsg); + radiusd_module_reset_ev_handler(module); + + free(buf); + return (0); + +on_error: + radiusd_module_reset_ev_handler(module); + if (buf != NULL) + free(buf); + return (-1); +} + +static void +radiusd_module_userpass(struct radiusd_module *module, struct radius_query *q) +{ + struct radiusd_module_userpass_arg userpass; + + memset(&userpass, 0, sizeof(userpass)); + userpass.q_id = q->id; + + if (radius_get_user_password_attr(q->req, userpass.pass, + sizeof(userpass.pass), q->client->secret) == 0) + userpass.has_pass = true; + else + userpass.has_pass = false; + + if (strlcpy(userpass.user, q->username, sizeof(userpass.user)) + >= sizeof(userpass.user)) { + log_warnx("Could request USERPASS to module `%s': " + "User-Name too long", module->name); + goto on_error; + } + imsg_compose(&module->ibuf, IMSG_RADIUSD_MODULE_USERPASS, 0, 0, -1, + &userpass, sizeof(userpass)); + radiusd_module_reset_ev_handler(module); + return; +on_error: + radiusd_access_request_aborted(q); +} + +static void +radiusd_module_access_request(struct radiusd_module *module, + struct radius_query *q) +{ + struct radiusd_module_radpkt_arg accsreq; + struct iovec iov[2]; + int off = 0, len, siz; + const u_char *pkt; + RADIUS_PACKET *radpkt; + char pass[256]; + + if ((radpkt = radius_convert_packet(radius_get_data(q->req), + radius_get_length(q->req))) == NULL) { + log_warn("Could not send ACCSREQ for `%s'", module->name); + return; + } + if (q->client->secret[0] != '\0' && module->secret != NULL && + radius_get_user_password_attr(radpkt, pass, sizeof(pass), + q->client->secret) == 0) { + radius_del_attr_all(radpkt, RADIUS_TYPE_USER_PASSWORD); + (void)radius_put_raw_attr(radpkt, RADIUS_TYPE_USER_PASSWORD, + pass, strlen(pass)); + } + + pkt = radius_get_data(radpkt); + len = radius_get_length(radpkt); + memset(&accsreq, 0, sizeof(accsreq)); + accsreq.q_id = q->id; + while (off < len) { + siz = MAX_IMSGSIZE - sizeof(accsreq); + if (len - off > siz) { + accsreq.final = false; + accsreq.datalen = siz; + } else { + accsreq.final = true; + accsreq.datalen = len - off; + } + iov[0].iov_base = &accsreq; + iov[0].iov_len = sizeof(accsreq); + iov[1].iov_base = (caddr_t)pkt + off; + iov[1].iov_len = accsreq.datalen; + imsg_composev(&module->ibuf, IMSG_RADIUSD_MODULE_ACCSREQ, 0, 0, + -1, iov, 2); + off += accsreq.datalen; + } + radiusd_module_reset_ev_handler(module); + radius_delete_packet(radpkt); + + return; +} diff --git a/usr.sbin/radiusd/radiusd.conf.5 b/usr.sbin/radiusd/radiusd.conf.5 new file mode 100644 index 00000000000..c498dbc18e2 --- /dev/null +++ b/usr.sbin/radiusd/radiusd.conf.5 @@ -0,0 +1,74 @@ +.\" Copyright (c) 2014 Esdenera Networks GmbH +.\" Copyright (c) 2014 Internet Initiative Japan Inc. +.\" +.\" All rights reserved. +.\" +.Dd October 14, 2014 +.Dt RADIUSD.CONF 5 +.Os +.Sh NAME +.Nm radiusd.conf +.Nd RADIUS daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the RADIUS daemon, +.Xr radiusd 8 . +.Sh SECTIONS +.Nm +is divided into several main sections +.Sh GLOBAL CONFIGURATION +.Bl -tag -width Ds +.It Ic authenticate Ar username +.It Ic client Ar address/mask +.It Xo +.Ic listen on Ar address +.Ic port Ar port +.Xc +Specify an +.Ar address +and a +.Ar port +to listen on. +.It Ic module Ic load Ar name Ar path +Load module +.Ar name +from +.Ar path . +.El +.Sh AUTHENTICATE +.Bl -tag -width Ds +.It Ic authenticate-by Ar module +.It Ic decorate-by Ar label +.It Ic secret Qq Ar passphrase +.El +.Sh CLIENT +.Bl -tag -width Ds +.It Ic secret Qq Ar passphrase +The passphrase for the client. +.It Ic msgauth-required Ar yes Ns | Ns Ar no +Require message authentication if +.Ar yes +is specified. +.El +.Sh EXAMPLES +.Bd -literal -offset indent +include "/etc/radius-section-1.conf" + +listen on 127.0.0.1 port 1812 +listen on ::1 port 1812 + +client 127.0.0.1/32 { secret "hogehoge" } +client 0.0.0.0/0 { + secret "fugafuga" + msgauth-required yes +} + +authenticate * { + authenticate-by radius + set radius server 10.0.0.1 + secret "hogefuga" + msgauth-required yes +} +.Ed +.Sh SEE ALSO +.Xr radiusd 8 diff --git a/usr.sbin/radiusd/radiusd.h b/usr.sbin/radiusd/radiusd.h new file mode 100644 index 00000000000..e31b8c47097 --- /dev/null +++ b/usr.sbin/radiusd/radiusd.h @@ -0,0 +1,73 @@ +/* $OpenBSD: radiusd.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +#ifndef RADIUSD_H +#define RADIUSD_H 1 +/* + * Copyright (c) 2013 Internet Initiative Japan Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <stdbool.h> + +#define RADIUSD_MODULE_NAME_LEN 32 +#define RADIUSD_SECRET_MAX 128 + +enum imsg_type { + IMSG_NONE = 0, + IMSG_OK, + IMSG_NG, + IMSG_RADIUSD_MODULE_LOAD, + IMSG_RADIUSD_MODULE_SET_CONFIG, + IMSG_RADIUSD_MODULE_START, + IMSG_RADIUSD_MODULE_NOTIFY_SECRET, + IMSG_RADIUSD_MODULE_USERPASS, + IMSG_RADIUSD_MODULE_USERPASS_OK, + IMSG_RADIUSD_MODULE_USERPASS_FAIL, + IMSG_RADIUSD_MODULE_ACCSREQ, + /* Check the response's authenticator if the module doesn't */ + IMSG_RADIUSD_MODULE_ACCSREQ_ANSWER, + IMSG_RADIUSD_MODULE_ACCSREQ_ABORTED, + IMSG_RADIUSD_MODULE_STOP +}; + +/* Module sends LOAD when it becomes ready */ +struct radiusd_module_load_arg { + uint32_t cap; /* module capabity bits */ +#define RADIUSD_MODULE_CAP_USERPASS 0x1 +#define RADIUSD_MODULE_CAP_ACCSREQ 0x2 +}; + +struct radiusd_module_object { + size_t size; +}; + +struct radiusd_module_set_arg { + char paramname[32]; + u_int nparamval; +}; + +struct radiusd_module_userpass_arg { + u_int q_id; + bool has_pass; + char user[256]; + char pass[256]; +}; + +struct radiusd_module_radpkt_arg { + u_int q_id; + bool final; + int modified; + int datalen; +}; + +#endif diff --git a/usr.sbin/radiusd/radiusd/Makefile b/usr.sbin/radiusd/radiusd/Makefile new file mode 100644 index 00000000000..bc228c47dc2 --- /dev/null +++ b/usr.sbin/radiusd/radiusd/Makefile @@ -0,0 +1,9 @@ +# $OpenBSD: Makefile,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +PROG= radiusd +BINDIR= /usr/sbin +MAN= radiusd.8 radiusd.conf.5 +SRCS= radiusd.c parse.y log.c util.c imsg_subr.c +LDADD+= -lradius -lcrypto -levent -lutil +DPADD= ${LIBRADIUS} ${LIBCRYPTO} ${LIBEVENT} ${LIBUTIL} + +.include <bsd.prog.mk> diff --git a/usr.sbin/radiusd/radiusd_bsdauth.c b/usr.sbin/radiusd/radiusd_bsdauth.c new file mode 100644 index 00000000000..fccdbaf62a6 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_bsdauth.c @@ -0,0 +1,180 @@ +/* $OpenBSD: radiusd_bsdauth.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bsd_auth.h> +#include <err.h> +#include <grp.h> +#include <login_cap.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "radiusd.h" +#include "radiusd_module.h" + +struct module_bsdauth { + struct module_base *base; + char **okgroups; +}; + +static void module_bsdauth_config_set(void *, const char *, int, + char * const *); +static void module_bsdauth_userpass(void *, u_int, const char *, const char *); + +static struct module_handlers module_bsdauth_handlers = { + .userpass = module_bsdauth_userpass, + .config_set = module_bsdauth_config_set +}; + +int +main(int argc, char *argv[]) +{ + struct module_bsdauth module_bsdauth; + + memset(&module_bsdauth, 0, sizeof(module_bsdauth)); + + openlog(NULL, LOG_PID, LOG_DAEMON); + + if ((module_bsdauth.base = module_create(STDIN_FILENO, &module_bsdauth, + &module_bsdauth_handlers)) == NULL) + err(1, "Could not create a module instance"); + + module_load(module_bsdauth.base); + while (module_run(module_bsdauth.base) == 0) + ; + + module_destroy(module_bsdauth.base); + + exit(EXIT_SUCCESS); +} + +static void +module_bsdauth_config_set(void *ctx, const char *name, int argc, + char * const * argv) +{ + struct module_bsdauth *_this = ctx; + int i; + char **groups = NULL; + + if (strcmp(name, "restrict-group") == 0) { + if (_this->okgroups != NULL) { + module_send_message(_this->base, IMSG_NG, + "`restrict-group' is already defined"); + goto on_error; + } + if ((groups = calloc(sizeof(char *), argc + 1)) == NULL) { + module_send_message(_this->base, IMSG_NG, + "Out of memory"); + goto on_error; + } + for (i = 0; i < argc; i++) { + if (getgrnam(argv[i]) == NULL) { + module_send_message(_this->base, IMSG_NG, + "group `%s' is not found", argv[i]); + endgrent(); + goto on_error; + } else { + if ((groups[i] = strdup(argv[i])) == NULL) { + endgrent(); + module_send_message(_this->base, + IMSG_NG, "Out of memory"); + goto on_error; + } + } + } + groups[i] = NULL; + _this->okgroups = groups; + endgrent(); + module_send_message(_this->base, IMSG_OK, NULL); + } else + module_send_message(_this->base, IMSG_NG, + "Unknown config parameter `%s'", name); + return; +on_error: + if (groups != NULL) { + for (i = 0; groups[i] != NULL; i++) + free(groups[i]); + free(groups); + } + return; +} + + +static void +module_bsdauth_userpass(void *ctx, u_int q_id, const char *user, + const char *pass) +{ + struct module_bsdauth *_this = ctx; + u_int i, j; + auth_session_t *auth = NULL; + struct passwd *pw; + struct group gr0, *gr; + char g_buf[4096]; + const char *reason; + + if (pass == NULL) + pass = ""; + + if ((auth = auth_usercheck((char *)user, NULL, NULL, (char *)pass)) + == NULL || (auth_getstate(auth) & AUTH_OKAY) == 0) { + reason = "Authentication failed"; + goto auth_ng; + } + if (_this->okgroups != NULL) { + reason = "Group restriction is not allowed"; + auth_setpwd(auth, NULL); + if ((pw = auth_getpwd(auth)) == NULL) { + syslog(LOG_WARNING, "auth_getpwd() for user %s " + "failed: %m", user); + goto auth_ng; + } + for (i = 0; _this->okgroups[i] != NULL; i++) { + if (getgrnam_r(_this->okgroups[i], &gr0, g_buf, + sizeof(g_buf), &gr) == -1) { + syslog(LOG_DEBUG, "group %s is not found", + _this->okgroups[i]); + continue; + } + if (gr->gr_gid == pw->pw_gid) + goto group_ok; + for (j = 0; gr->gr_mem[j] != NULL; j++) { + if (strcmp(gr->gr_mem[j], pw->pw_name) == 0) + goto group_ok; + } + } + endgrent(); + goto auth_ng; +group_ok: + endgrent(); + } + module_userpass_ok(_this->base, q_id, "Authentication succeeded"); + if (auth != NULL) + auth_close(auth); + return; +auth_ng: + module_userpass_fail(_this->base, q_id, reason); + if (auth != NULL) + auth_close(auth); + return; +} diff --git a/usr.sbin/radiusd/radiusd_bsdauth/Makefile b/usr.sbin/radiusd/radiusd_bsdauth/Makefile new file mode 100644 index 00000000000..19000878185 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_bsdauth/Makefile @@ -0,0 +1,9 @@ +# $OpenBSD: Makefile,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +PROG= radiusd_bsdauth +BINDIR= /usr/libexec/radiusd +SRCS= radiusd_bsdauth.c radiusd_module.c imsg_subr.c +LDADD+= -lradius -lcrypto -lutil +LDADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL} +NOMAN= # + +.include <bsd.prog.mk> diff --git a/usr.sbin/radiusd/radiusd_local.h b/usr.sbin/radiusd/radiusd_local.h new file mode 100644 index 00000000000..26b3754b3f9 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_local.h @@ -0,0 +1,175 @@ +/* $OpenBSD: radiusd_local.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2013 Internet Initiative Japan Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/socket.h> /* for struct sockaddr_storage */ +#include <sys/queue.h> /* for TAILQ_* */ +#include <netinet/in.h> /* for struct sockaddr_in* */ + +#include <event.h> /* for struct event */ +#include <imsg.h> /* for struct imsgbuf */ +#include <stdarg.h> /* for va_list */ +#include <stdbool.h> /* for bool */ + +#include <radius.h> /* for RADIUS_PACKET */ + +#define MODULE_IO_TIMEOUT 2000 + +#define CONFFILE "/etc/radiusd.conf" +struct radius_query; /* XXX */ + +struct radiusd_addr { + union { + struct in_addr ipv4; + struct in6_addr ipv6; + uint32_t addr32[4]; + } addr; +}; + +struct radiusd_listen { + struct radiusd *radiusd; + struct event ev; + int sock; + union { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } addr; + int stype; + int sproto; + TAILQ_ENTRY(radiusd_listen) next; +}; + +TAILQ_HEAD(radiusd_listen_head, radiusd_listen); + +struct radiusd_client { + char secret[RADIUSD_SECRET_MAX]; + bool msgauth_required; + int af; + struct radiusd_addr addr; + struct radiusd_addr mask; + TAILQ_ENTRY(radiusd_client) next; +}; + +struct radiusd_module { + char name[RADIUSD_MODULE_NAME_LEN]; + struct radiusd *radiusd; + pid_t pid; + int fd; + struct imsgbuf ibuf; + struct event ev; + bool writeready; + bool stopped; + uint32_t capabilities; + u_char *radpkt; + int radpktsiz; + int radpktoff; + char *secret; + TAILQ_ENTRY(radiusd_module) next; + int (*request_decoration)(void *, struct radius_query *); + int (*response_decoration)(void *, struct radius_query *); +}; + +struct radiusd_module_ref { + struct radiusd_module *module; + TAILQ_ENTRY(radiusd_module_ref) next; +}; + +struct radiusd_authentication { + char **username; + char *secret; + struct radiusd_module_ref *auth; + TAILQ_HEAD(,radiusd_module_ref) deco; + TAILQ_ENTRY(radiusd_authentication) next; +}; + +struct radiusd { + struct radiusd_listen_head listen; + struct event ev_sigterm; + struct event ev_sighup; + struct event ev_sigint; + struct event ev_sigchld; + TAILQ_HEAD(,radiusd_module) module; + TAILQ_HEAD(,radiusd_authentication) authen; + TAILQ_HEAD(,radiusd_client) client; + TAILQ_HEAD(,radius_query) query; +}; + +struct radius_query { + u_int id; + struct sockaddr_storage clientaddr; + int clientaddrlen; + int req_id; + u_char req_auth[16]; + struct radiusd_listen *listen; + struct radiusd_client *client; + struct radiusd_authentication *authen; + RADIUS_PACKET *req; + RADIUS_PACKET *res; + int req_modified; + int res_modified; + char username[256]; /* original username */ + TAILQ_ENTRY(radius_query) next; +}; +#ifndef nitems +#define nitems(_x) (sizeof((_x)) / sizeof((_x)[0])) +#endif + +#define RADIUSD_USER "_radiusd" + +#ifdef RADIUSD_DEBUG +#define RADIUSD_DBG(x) log_debug x +#else +#define RADIUSD_DBG(x) +#endif +#define RADIUSD_ASSERT(_cond) \ + do { \ + if (!(_cond)) { \ + log_warnx( \ + "ASSERT(%s) failed in %s() at %s:%d",\ + #_cond, __func__, __FILE__, __LINE__);\ + if (debug) abort(); \ + } \ + } while (0/* CONSTCOND */) + + +#define MODULE_DO_USERPASS(_m) \ + ((_m)->fd >= 0 && \ + ((_m)->capabilities & RADIUSD_MODULE_CAP_USERPASS) != 0) +#define MODULE_DO_ACCSREQ(_m) \ + ((_m)->fd >= 0 && \ + ((_m)->capabilities & RADIUSD_MODULE_CAP_ACCSREQ) != 0) + +extern struct radiusd_module mod_standard; +extern struct radiusd_module mod_radius; + +int parse_config(const char *, struct radiusd *); +void radiusd_conf_init(struct radiusd *); + + +struct radiusd_module *radiusd_module_load(struct radiusd *, const char *, + const char *); +void radiusd_module_unload(struct radiusd_module *); + +void radiusd_access_request_answer(struct radius_query *); +int radiusd_access_request_fixup(struct radius_query *); +void radiusd_access_request_aborted(struct radius_query *); +void radius_attr_hide(const char *, const char *, const u_char *, + u_char *, int); +void radius_attr_unhide(const char *, const char *, const u_char *, + u_char *, int); + +int radiusd_module_set(struct radiusd_module *, const char *, int, char * const *); diff --git a/usr.sbin/radiusd/radiusd_module.c b/usr.sbin/radiusd/radiusd_module.c new file mode 100644 index 00000000000..c99df5c7228 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_module.c @@ -0,0 +1,489 @@ +/* $OpenBSD: radiusd_module.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* radiusd_module.c -- helper functions for radiusd modules */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/uio.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "radiusd.h" +#include "radiusd_module.h" +#include "imsg_subr.h" + +static void (*module_config_set) (void *, const char *, int, + char * const *) = NULL; +static void (*module_start_module) (void *) = NULL; +static void (*module_stop_module) (void *) = NULL; +static void (*module_userpass) (void *, u_int, const char *, const char *) + = NULL; +static void (*module_access_request) (void *, u_int, const u_char *, + size_t) = NULL; + +struct module_base { + void *ctx; + struct imsgbuf ibuf; + + /* Buffer for receiving the RADIUS packet */ + u_char *radpkt; + int radpktsiz; + int radpktoff; + +#ifdef USE_LIBEVENT + struct module_imsgbuf *module_imsgbuf; + bool writeready; + bool ev_onhandler; + struct event ev; +#endif +}; + +static int module_common_radpkt(struct module_base *, uint32_t, u_int, + int, const u_char *, size_t); +static int module_recv_imsg(struct module_base *); +static int module_imsg_handler(struct module_base *, struct imsg *); +#ifdef USE_LIBEVENT +static void module_on_event(int, short, void *); +#endif +static void module_reset_event(struct module_base *); + +struct module_base * +module_create(int sock, void *ctx, struct module_handlers *handler) +{ + struct module_base *base; + + if ((base = calloc(1, sizeof(struct module_base))) == NULL) + return (NULL); + + imsg_init(&base->ibuf, sock); + base->ctx = ctx; + + module_userpass = handler->userpass; + module_access_request = handler->access_request; + module_config_set = handler->config_set; + module_start_module = handler->start; + module_stop_module = handler->stop; + + return (base); +} + +void +module_start(struct module_base *base) +{ +#ifdef USE_LIBEVENT + int on; + + on = 1; + if (fcntl(base->ibuf.fd, O_NONBLOCK, &on) == -1) + err(1, "Failed to setup NONBLOCK"); + module_reset_event(base); +#endif +} + +int +module_run(struct module_base *base) +{ + int ret; + + ret = module_recv_imsg(base); + if (ret == 0) + imsg_flush(&base->ibuf); + + return (ret); +} + +void +module_destroy(struct module_base *base) +{ + imsg_clear(&base->ibuf); + free(base); +} + +void +module_load(struct module_base *base) +{ + struct radiusd_module_load_arg load; + + memset(&load, 0, sizeof(load)); + if (module_userpass != NULL) + load.cap |= RADIUSD_MODULE_CAP_USERPASS; + if (module_access_request != NULL) + load.cap |= RADIUSD_MODULE_CAP_ACCSREQ; + imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_LOAD, 0, 0, -1, &load, + sizeof(load)); + imsg_flush(&base->ibuf); +} + +int +module_notify_secret(struct module_base *base, const char *secret) +{ + int ret; + + ret = imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_NOTIFY_SECRET, + 0, 0, -1, secret, strlen(secret) + 1); + module_reset_event(base); + + return (ret); +} + +int +module_send_message(struct module_base *base, uint32_t cmd, const char *fmt, + ...) +{ + char *msg; + va_list ap; + int ret; + + if (fmt == NULL) + ret = imsg_compose(&base->ibuf, cmd, 0, 0, -1, NULL, 0); + else { + va_start(ap, fmt); + vasprintf(&msg, fmt, ap); + va_end(ap); + if (msg == NULL) + return (-1); + ret = imsg_compose(&base->ibuf, cmd, 0, 0, -1, msg, + strlen(msg) + 1); + free(msg); + } + module_reset_event(base); + + return (ret); +} + +int +module_userpass_ok(struct module_base *base, u_int q_id, const char *msg) +{ + int ret; + struct iovec iov[2]; + + iov[0].iov_base = &q_id; + iov[0].iov_len = sizeof(q_id); + iov[1].iov_base = (char *)msg; + iov[1].iov_len = strlen(msg) + 1; + ret = imsg_composev(&base->ibuf, IMSG_RADIUSD_MODULE_USERPASS_OK, + 0, 0, -1, iov, 2); + module_reset_event(base); + + return (ret); +} + +int +module_userpass_fail(struct module_base *base, u_int q_id, const char *msg) +{ + int ret; + struct iovec iov[2]; + + iov[0].iov_base = &q_id; + iov[0].iov_len = sizeof(q_id); + iov[1].iov_base = (char *)msg; + iov[1].iov_len = strlen(msg) + 1; + ret = imsg_composev(&base->ibuf, IMSG_RADIUSD_MODULE_USERPASS_FAIL, + 0, 0, -1, iov, 2); + module_reset_event(base); + + return (ret); +} + +int +module_accsreq_answer(struct module_base *base, u_int q_id, int modified, + const u_char *pkt, size_t pktlen) +{ + return (module_common_radpkt(base, IMSG_RADIUSD_MODULE_ACCSREQ_ANSWER, + q_id, modified, pkt, pktlen)); +} + +int +module_accsreq_aborted(struct module_base *base, u_int q_id) +{ + int ret; + + ret = imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_ACCSREQ_ABORTED, + 0, 0, -1, &q_id, sizeof(u_int)); + module_reset_event(base); + + return (ret); +} + +static int +module_common_radpkt(struct module_base *base, uint32_t imsg_type, u_int q_id, + int modified, const u_char *pkt, size_t pktlen) +{ + int ret = 0, off = 0, len, siz; + struct iovec iov[2]; + struct radiusd_module_radpkt_arg ans; + + len = pktlen; + ans.q_id = q_id; + ans.modified = modified; + while (off < len) { + siz = MAX_IMSGSIZE - sizeof(ans); + if (len - off > siz) { + ans.final = true; + ans.datalen = siz; + } else { + ans.final = true; + ans.datalen = len - off; + } + iov[0].iov_base = &ans; + iov[0].iov_len = sizeof(ans); + iov[1].iov_base = (u_char *)pkt + off; + iov[1].iov_len = ans.datalen; + ret = imsg_composev(&base->ibuf, imsg_type, 0, 0, -1, iov, 2); + if (ret == -1) + break; + off += ans.datalen; + } + module_reset_event(base); + + return (ret); +} + +static int +module_recv_imsg(struct module_base *base) +{ + ssize_t n; + struct imsg imsg; + + if ((n = imsg_read(&base->ibuf)) == -1 || n == 0) { + /* XXX */ + return (-1); + } + + for (;;) { + if ((n = imsg_get(&base->ibuf, &imsg)) == -1) + /* XXX */ + return (-1); + if (n == 0) + break; + module_imsg_handler(base, &imsg); + imsg_free(&imsg); + } + module_reset_event(base); + + return (0); +} + +static int +module_imsg_handler(struct module_base *base, struct imsg *imsg) +{ + ssize_t datalen; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + switch (imsg->hdr.type) { + case IMSG_RADIUSD_MODULE_SET_CONFIG: + { + struct radiusd_module_set_arg *arg; + struct radiusd_module_object *val; + u_int i; + size_t off; + char **argv; + + arg = (struct radiusd_module_set_arg *)imsg->data; + off = sizeof(struct radiusd_module_set_arg); + + if ((argv = calloc(sizeof(const char *), arg->nparamval)) + == NULL) { + module_send_message(base, IMSG_NG, + "Out of memory: %s", strerror(errno)); + break; + } + for (i = 0; i < arg->nparamval; i++) { + if (datalen - off < + sizeof(struct radiusd_module_object)) + break; + val = (struct radiusd_module_object *) + ((caddr_t)imsg->data + off); + if (datalen - off < val->size) + break; + argv[i] = (char *)(val + 1); + off += val->size; + } + if (i >= arg->nparamval) + module_config_set(base->ctx, arg->paramname, + arg->nparamval, argv); + else + module_send_message(base, IMSG_NG, + "Internal protocol error"); + free(argv); + + break; + } + case IMSG_RADIUSD_MODULE_START: + if (module_start_module != NULL) + module_start_module(base->ctx); + else + module_send_message(base, IMSG_OK, NULL); + break; + case IMSG_RADIUSD_MODULE_STOP: + if (module_stop_module != NULL) + module_stop_module(base->ctx); + break; + case IMSG_RADIUSD_MODULE_USERPASS: + { + struct radiusd_module_userpass_arg *userpass; + + if (module_userpass == NULL) { + syslog(LOG_ERR, "Received USERPASS message, but " + "module doesn't support"); + break; + } + if (datalen < + (ssize_t)sizeof(struct radiusd_module_userpass_arg)) { + syslog(LOG_ERR, "Received USERPASS message, but " + "length is wrong"); + break; + } + userpass = (struct radiusd_module_userpass_arg *)imsg->data; + module_userpass(base->ctx, userpass->q_id, userpass->user, + (userpass->has_pass)? userpass->pass : NULL); + explicit_bzero(userpass, + sizeof(struct radiusd_module_userpass_arg)); + break; + } + case IMSG_RADIUSD_MODULE_ACCSREQ: + { + struct radiusd_module_radpkt_arg *accessreq; + int chunklen; + + if (module_access_request == NULL) { + syslog(LOG_ERR, "Received ACCSREQ message, but " + "module doesn't support"); + break; + } + if (datalen < + (ssize_t)sizeof(struct radiusd_module_radpkt_arg)) { + syslog(LOG_ERR, "Received ACCSREQ message, but " + "length is wrong"); + break; + } + accessreq = (struct radiusd_module_radpkt_arg *)imsg->data; + if (base->radpktsiz < accessreq->datalen) { + u_char *nradpkt; + if ((nradpkt = realloc(base->radpkt, + accessreq->datalen)) == NULL) { + syslog(LOG_ERR, "Could not handle received " + "ACCSREQ message: %m"); + base->radpktoff = 0; + goto accsreq_out; + } + base->radpkt = nradpkt; + base->radpktsiz = accessreq->datalen; + } + chunklen = datalen - sizeof(struct radiusd_module_radpkt_arg); + if (chunklen > base->radpktsiz - base->radpktoff){ + syslog(LOG_ERR, + "Could not handle received ACCSREQ message: " + "received length is too big"); + base->radpktoff = 0; + goto accsreq_out; + } + memcpy(base->radpkt + base->radpktoff, + (caddr_t)(accessreq + 1), chunklen); + base->radpktoff += chunklen; + if (!accessreq->final) + goto accsreq_out; + if (base->radpktoff != base->radpktsiz) { + syslog(LOG_ERR, + "Could not handle received ACCSREQ " + "message: length is mismatch"); + base->radpktoff = 0; + goto accsreq_out; + } + module_access_request(base->ctx, accessreq->q_id, + base->radpkt, base->radpktoff); + base->radpktoff = 0; +accsreq_out: + break; + } + } + + return (0); +} + +#ifdef USE_LIBEVENT +static void +module_on_event(int fd, short evmask, void *ctx) +{ + struct module_base *base = ctx; + int ret; + + base->ev_onhandler = true; + if (evmask & EV_WRITE) + base->writeready = true; + if (evmask & EV_READ) { + ret = module_recv_imsg(base); + if (ret < 0) + goto on_error; + } + while (base->writeready && base->ibuf.w.queued) { + ret = msgbuf_write(&base->ibuf.w); + if (ret > 0) + continue; + base->writeready = false; + if (ret == 0 && errno == EAGAIN) + break; + syslog(LOG_ERR, "Write fail"); + goto on_error; + } + base->ev_onhandler = false; + module_reset_event(base); + return; + +on_error: + if (event_initialized(&base->ev)) + event_del(&base->ev); + close(base->ibuf.fd); +} +#endif + +static void +module_reset_event(struct module_base *base) +{ +#ifdef USE_LIBEVENT + short evmask = 0; + struct timeval *tvp = NULL, tv = { 0, 0 }; + + if (base->ev_onhandler) + return; + if (event_initialized(&base->ev)) + event_del(&base->ev); + + evmask |= EV_READ; + if (base->ibuf.w.queued) { + if (!base->writeready) + evmask |= EV_WRITE; + else + tvp = &tv; /* fire immediately */ + } + event_set(&base->ev, base->ibuf.fd, evmask, module_on_event, base); + if (event_add(&base->ev, tvp) == -1) + syslog(LOG_ERR, "event_add() failed in %s()", __func__); +#endif +} diff --git a/usr.sbin/radiusd/radiusd_module.h b/usr.sbin/radiusd/radiusd_module.h new file mode 100644 index 00000000000..99158611c90 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_module.h @@ -0,0 +1,73 @@ +#ifndef _RADIUS_MODULE_H +#define _RADIUS_MODULE_H + +/* + * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@ysauoka.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "radiusd.h" + +struct module_ctx; + +struct module_handlers { + /* Should send IMSG_OK or IMSG_NG */ + void (*config_set)(void *ctx, const char *paramname, int paramvalc, + char * const * paramvalv); + + void (*start)(void *ctx); + + void (*stop)(void *ctx); + + void (*userpass)(void *ctx, u_int query_id, const char *user, + const char *pass); + + void (*access_request)(void *ctx, u_int query_id, const u_char *pkt, + size_t pktlen); + + /* User-Password Attribute is encrypted if the module has the secret */ +}; + +#define SYNTAX_ASSERT(_cond, _msg) \ + do { \ + if (!(_cond)) { \ + errmsg = (_msg); \ + goto syntax_error; \ + } \ + } while (0 /* CONSTCOND */) +#include <sys/cdefs.h> + +__BEGIN_DECLS + +struct module_base *module_create(int, void *, struct module_handlers *); +void module_start(struct module_base *); +int module_run(struct module_base *); +void module_destroy(struct module_base *); +void module_load(struct module_base *); +int module_notify_secret(struct module_base *, + const char *); +int module_send_message(struct module_base *, uint32_t, + const char *, ...) + __attribute__((__format__ (__printf__, 3, 4))); +int module_userpass_ok(struct module_base *, u_int, + const char *); +int module_userpass_fail(struct module_base *, u_int, + const char *); +int module_accsreq_answer(struct module_base *, u_int, + int, const u_char *, size_t); +int module_accsreq_aborted(struct module_base *, u_int); + +__END_DECLS + +#endif diff --git a/usr.sbin/radiusd/radiusd_radius.c b/usr.sbin/radiusd/radiusd_radius.c new file mode 100644 index 00000000000..d3a224bc56f --- /dev/null +++ b/usr.sbin/radiusd/radiusd_radius.c @@ -0,0 +1,613 @@ +/* $OpenBSD: radiusd_radius.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2013 Internet Initiative Japan Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <err.h> +#include <event.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <radius.h> + +#include "radiusd.h" +#include "radiusd_module.h" +#include "util.h" +#include "log.h" + +struct radius_server { + struct module_radius *module; + int sock; + union { + struct sockaddr_in6 sin6; + struct sockaddr_in sin4; + } addr; + union { + struct sockaddr_in6 sin6; + struct sockaddr_in sin4; + } local; + struct event ev; + u_char req_id_seq; +}; + +struct module_radius { + struct module_base *base; + struct radius_server server[4]; + char secret[RADIUSD_SECRET_MAX]; + u_int nserver; + u_int curr_server; + u_int req_timeout; + u_int max_tries; + u_int max_failovers; + u_int nfailover; + TAILQ_HEAD(,module_radius_req) req; +}; + +struct module_radius_req { + struct module_radius *module; + struct radius_server *server; + u_char q_id; + RADIUS_PACKET *q_pkt; + u_int ntry; + u_int nfailover; + u_char req_id; + struct event ev; + TAILQ_ENTRY(module_radius_req) next; +}; + +static void module_radius_init(struct module_radius *); +static void module_radius_config_set(void *, const char *, int, + char * const *); +static void module_radius_start(void *); +static void module_radius_stop(void *); +static void module_radius_access_request(void *, u_int, const u_char *, + size_t); +static int radius_server_start(struct radius_server *); +static void radius_server_stop(struct radius_server *); +static void radius_server_on_event(int, short, void *); +static void radius_server_on_fail(struct radius_server *, const char *); +static void module_radius_req_send(struct module_radius_req *); +static int module_radius_req_reset_event(struct module_radius_req *); +static void module_radius_req_on_timeout(int, short, void *); +static void module_radius_req_on_success(struct module_radius_req *, + const u_char *, size_t); +static void module_radius_req_on_failure(struct module_radius_req *); + +static void module_radius_req_free(struct module_radius_req *); +static void module_radius_req_select_server(struct module_radius_req *); + +static void module_radius_req_reset_msgauth(struct module_radius_req *); +static void module_radius_log(struct module_radius *, int, const char *, ...); + +static struct module_handlers module_radius_handlers = { + .config_set = module_radius_config_set, + .start = module_radius_start, + .stop = module_radius_stop, + .access_request = module_radius_access_request +}; + +#ifndef nitems +#define nitems(_x) (sizeof((_x)) / sizeof((_x)[0])) +#endif + +int +main(int argc, char *argv[]) +{ + static struct module_radius module_radius; + + module_radius_init(&module_radius); + openlog(NULL, LOG_PID, LOG_DAEMON); + + // XXX drop privilledge + // XXX change root + + if ((module_radius.base = module_create( + STDIN_FILENO, &module_radius, &module_radius_handlers)) == NULL) + err(1, "Could not create a module instance"); + + module_load(module_radius.base); + log_init(1); + event_init(); + module_start(module_radius.base); + event_loop(0); + + exit(EXIT_SUCCESS); +} + +static void +module_radius_init(struct module_radius *_this) +{ + memset(_this, 0, sizeof(struct module_radius)); + TAILQ_INIT(&_this->req); +} + +static void +module_radius_config_set(void *ctx, const char *paramname, int paramvalc, + char * const * paramvalv) +{ + const char *errmsg = NULL; + struct addrinfo *res; + struct module_radius *_this = ctx; + + if (strcmp(paramname, "server") == 0) { + SYNTAX_ASSERT(paramvalc == 1, + "`server' must have just one argument"); + SYNTAX_ASSERT(_this->nserver < (int)nitems(_this->server), + "number of server reached limit"); + + if (addrport_parse(paramvalv[0], IPPROTO_UDP, &res) != 0) + SYNTAX_ASSERT(0, "could not parse address and port"); + memcpy(&_this->server[_this->nserver].addr, res->ai_addr, + res->ai_addrlen); + + if (ntohs(_this->server[_this->nserver].addr.sin4.sin_port) + == 0) + _this->server[_this->nserver].addr.sin4.sin_port + = htons(RADIUS_DEFAULT_PORT); + + _this->server[_this->nserver].sock = -1; + _this->nserver++; + freeaddrinfo(res); + } else if (strcmp(paramname, "request-timeout") == 0) { + SYNTAX_ASSERT(paramvalc == 1, + "`request-timeout' must have just one argument"); + _this->req_timeout = (int)strtonum(paramvalv[0], 0, + UINT16_MAX, &errmsg); + if (_this->req_timeout == 0 && errmsg != NULL) { + module_send_message(_this->base, IMSG_NG, + "`request-timeout must be 0-%d", UINT16_MAX); + return; + } + } else if (strcmp(paramname, "max-tries") == 0) { + SYNTAX_ASSERT(paramvalc == 1, + "`max-tries' must have just one argument"); + _this->max_tries = (int)strtonum(paramvalv[0], 0, + UINT16_MAX, &errmsg); + if (_this->max_tries == 0 && errmsg != NULL) { + module_send_message(_this->base, IMSG_NG, + "`max-tries must be 0-%d", UINT16_MAX); + return; + } + + } else if (strcmp(paramname, "max-failovers") == 0) { + SYNTAX_ASSERT(paramvalc == 1, + "`max-failovers' must have just one argument"); + _this->max_failovers = (int)strtonum(paramvalv[0], 0, + UINT16_MAX, &errmsg); + if (_this->max_failovers == 0 && errmsg != NULL) { + module_send_message(_this->base, IMSG_NG, + "`max-failovers' must be 0-%d", UINT16_MAX); + return; + } + } else if (strcmp(paramname, "secret") == 0) { + SYNTAX_ASSERT(paramvalc == 1, + "`secret' must have just one argument"); + if (strlcpy(_this->secret, paramvalv[0], sizeof(_this->secret)) + >= sizeof(_this->secret)) { + module_send_message(_this->base, IMSG_NG, + "`secret' length must be 0-%lu", + (u_long) sizeof(_this->secret) - 1); + return; + } + } else { + module_send_message(_this->base, IMSG_NG, + "Unknown config parameter name `%s'", paramname); + return; + } + module_send_message(_this->base, IMSG_OK, NULL); + + return; +syntax_error: + module_send_message(_this->base, IMSG_NG, "%s", errmsg); +} + +static void +module_radius_start(void *ctx) +{ + u_int i; + struct module_radius *_this = ctx; + + if (_this->nserver <= 0) { + module_send_message(_this->base, IMSG_NG, + "module `radius' needs one `server' at least"); + return; + } + + for (i = 0; i < _this->nserver; i++) { + _this->server[i].module = _this; + if (radius_server_start(&_this->server[i]) != 0) { + module_send_message(_this->base, IMSG_NG, + "module `radius' failed to start one of " + "the servers"); + return; + } + } + module_send_message(_this->base, IMSG_OK, NULL); + + if (_this->secret[0] != '\0') + module_notify_secret(_this->base, _this->secret); +} + +static void +module_radius_stop(void *ctx) +{ + u_int i; + struct module_radius_req *req, *treq; + struct module_radius *_this = ctx; + + TAILQ_FOREACH_SAFE(req, &_this->req, next, treq) { + module_radius_req_on_failure(req); + TAILQ_REMOVE(&_this->req, req, next); + } + + for (i = 0; i < _this->nserver; i++) + radius_server_stop(&_this->server[i]); +} + +static void +module_radius_access_request(void *ctx, u_int q_id, const u_char *pkt, + size_t pktlen) +{ + struct module_radius *_this = ctx; + struct module_radius_req *req; + u_char attrbuf[256]; + ssize_t attrlen; + + req = malloc(sizeof(struct module_radius_req)); + if (req == NULL) { + module_radius_log(_this, LOG_WARNING, + "%s: Out of memory: %m", __func__); + goto on_fail; + } + + req->ntry = 0; + req->module = _this; + req->q_id = q_id; + if ((req->q_pkt = radius_convert_packet(pkt, pktlen)) == NULL) { + module_radius_log(_this, LOG_WARNING, + "%s: radius_convert_packet() failed: %m", __func__); + goto on_fail; + } + evtimer_set(&req->ev, module_radius_req_on_timeout, req); + TAILQ_INSERT_TAIL(&req->module->req, req, next); + + /* + * radiusd decrypt User-Password attribute. crypt it again with our + * secret. + */ + attrlen = sizeof(attrbuf); + if (_this->secret[0] != '\0' && + radius_get_raw_attr(req->q_pkt, RADIUS_TYPE_USER_PASSWORD, + attrbuf, &attrlen) == 0) { + attrbuf[attrlen] = '\0'; + radius_del_attr_all(req->q_pkt, RADIUS_TYPE_USER_PASSWORD); + radius_put_user_password_attr(req->q_pkt, attrbuf, + _this->secret); + } + + /* select current server */ + module_radius_req_select_server(req); + + module_radius_req_send(req); + + return; + +on_fail: + free(req); + module_accsreq_aborted(_this->base, q_id); +} + +/* + * radius_server + */ +static int +radius_server_start(struct radius_server *_this) +{ + socklen_t locallen; + char buf0[NI_MAXHOST + NI_MAXSERV + 32]; + char buf1[NI_MAXHOST + NI_MAXSERV + 32]; + + if ((_this->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + log_warn("%s: socket() failed", __func__); + goto on_error; + } + if (connect(_this->sock, (struct sockaddr *)&_this->addr, + _this->addr.sin4.sin_len) != 0) { + log_warn("%s: connect to %s failed", __func__, + addrport_tostring((struct sockaddr *)&_this->addr, + _this->addr.sin4.sin_len, buf1, sizeof(buf1))); + goto on_error; + } + locallen = sizeof(_this->local); + if (getsockname(_this->sock, (struct sockaddr *)&_this->local, + &locallen) != 0) { + log_warn("%s: getsockanme() failed", __func__); + goto on_error; + } + module_radius_log(_this->module, LOG_INFO, + "Use %s to send requests for %s", + addrport_tostring((struct sockaddr *)&_this->local, + locallen, buf0, sizeof(buf0)), + addrport_tostring((struct sockaddr *)&_this->addr, + _this->addr.sin4.sin_len, buf1, sizeof(buf1))); + + event_set(&_this->ev, _this->sock, EV_READ | EV_PERSIST, + radius_server_on_event, _this); + if (event_add(&_this->ev, NULL)) { + log_warn("%s: event_add() failed", __func__); + goto on_error; + } + + return (0); +on_error: + if (_this->sock >= 0) + close(_this->sock); + _this->sock = -1; + return (-1); +} + +static void +radius_server_stop(struct radius_server *_this) +{ + event_del(&_this->ev); + if (_this->sock >= 0) + close(_this->sock); + _this->sock = -1; +} + +static void +radius_server_on_event(int fd, short evmask, void *ctx) +{ + int sz, res_id; + u_char pkt[65535]; + char buf[NI_MAXHOST + NI_MAXSERV + 32]; + struct radius_server *_this = ctx; + RADIUS_PACKET *radpkt = NULL; + struct module_radius_req *req; + struct sockaddr *peer; + + peer = (struct sockaddr *)&_this->addr; + if ((sz = recv(_this->sock, pkt, sizeof(pkt), 0)) <= 0) { + module_radius_log(_this->module, LOG_WARNING, + "server=%s recv() failed: %m", + addrport_tostring(peer, peer->sa_len, buf, sizeof(buf))); + return; + } + if ((radpkt = radius_convert_packet(pkt, sz)) == NULL) { + module_radius_log(_this->module, LOG_WARNING, + "server=%s could not convert the received message to a " + "RADIUS packet object: %m", + addrport_tostring(peer, peer->sa_len, buf, sizeof(buf))); + return; + } + res_id = radius_get_id(radpkt); + TAILQ_FOREACH(req, &_this->module->req, next) { + if (req->server == _this && req->req_id == res_id) + break; + } + if (req == NULL) { + module_radius_log(_this->module, LOG_WARNING, + "server=%s Received radius message has unknown id=%d", + addrport_tostring(peer, peer->sa_len, buf, sizeof(buf)), + res_id); + goto out; + } + radius_set_request_packet(radpkt, req->q_pkt); + + if (_this->module->secret[0] != '\0') { + if (radius_check_response_authenticator(radpkt, + _this->module->secret) != 0) { + module_radius_log(_this->module, LOG_WARNING, + "server=%s Received radius message(id=%d) has bad " + "authenticator", + addrport_tostring(peer, peer->sa_len, buf, + sizeof(buf)), res_id); + goto out; + } + if (radius_has_attr(radpkt, + RADIUS_TYPE_MESSAGE_AUTHENTICATOR) && + radius_check_message_authenticator(radpkt, + _this->module->secret) != 0) { + module_radius_log(_this->module, LOG_WARNING, + "server=%s Received radius message(id=%d) has bad " + "message authenticator", + addrport_tostring(peer, peer->sa_len, buf, + sizeof(buf)), res_id); + goto out; + } + } + + module_radius_log(_this->module, LOG_INFO, + "q=%u received a response from server %s", req->q_id, + addrport_tostring(peer, peer->sa_len, buf, sizeof(buf))); + + module_radius_req_on_success(req, radius_get_data(radpkt), + radius_get_length(radpkt)); +out: + if (radpkt != NULL) + radius_delete_packet(radpkt); +} + +static void +radius_server_on_fail(struct radius_server *_this, const char *failmsg) +{ + char buf0[NI_MAXHOST + NI_MAXSERV + 32]; + char buf1[NI_MAXHOST + NI_MAXSERV + 32]; + struct sockaddr *caddr, *naddr; + + caddr = (struct sockaddr *)&_this->addr; + if (_this->module->nserver <= 1) { + module_radius_log(_this->module, LOG_WARNING, + "Server %s failed: %s", + addrport_tostring(caddr, caddr->sa_len, buf0, sizeof(buf0)), + failmsg); + return; + } + _this->module->curr_server++; + _this->module->curr_server %= _this->module->nserver; + naddr = (struct sockaddr *) + &_this->module->server[_this->module->curr_server].addr; + + module_radius_log(_this->module, LOG_WARNING, + "Server %s failed: %s Fail over to %s", + addrport_tostring(caddr, caddr->sa_len, buf0, sizeof(buf0)), + failmsg, + addrport_tostring(naddr, naddr->sa_len, buf1, sizeof(buf1))); +} + +/* module_radius_req */ + +static void +module_radius_req_send(struct module_radius_req *req) +{ + int sz; + struct sockaddr *peer; + char msg[BUFSIZ]; + + peer = (struct sockaddr *)&req->server->addr; + if ((sz = send(req->server->sock, radius_get_data(req->q_pkt), + radius_get_length(req->q_pkt), 0)) < 0) { + module_radius_log(req->module, LOG_WARNING, + "Sending RADIUS query q=%u to %s failed: %m", + req->q_id, + addrport_tostring(peer, peer->sa_len, msg, sizeof(msg))); + /* retry anyway */ + } + module_radius_log(req->module, LOG_INFO, + "Send RADIUS query q=%u id=%d to %s successfully", + req->q_id, req->req_id, + addrport_tostring(peer, peer->sa_len, msg, sizeof(msg))); + if (module_radius_req_reset_event(req) != -1) + req->ntry++; +} + +static int +module_radius_req_reset_event(struct module_radius_req *req) +{ + struct timeval tv; + static int timeouts[] = { 2, 4, 8 }; + + tv.tv_usec = 0; + if (req->module->req_timeout != 0) + tv.tv_sec = req->module->req_timeout; + else { + if (req->ntry < nitems(timeouts)) + tv.tv_sec = timeouts[req->ntry]; + else + tv.tv_sec = timeouts[nitems(timeouts) - 1]; + } + if (evtimer_add(&req->ev, &tv) != 0) { + module_radius_log(req->module, LOG_WARNING, + "Cannot proccess the request for q=%u: " + "evtimer_add() failed: %m", req->q_id); + module_radius_req_on_failure(req); + return(-1); + } + return (0); +} + +static void +module_radius_req_on_timeout(int fd, short evmask, void *ctx) +{ + struct module_radius_req *req = ctx; + char msg[BUFSIZ]; + + + if (req->module->max_tries <= req->ntry) { + snprintf(msg, sizeof(msg), "q=%u didn't response RADIUS query " + "%d time%s", req->q_id, req->ntry, + (req->ntry > 0)? "s" : ""); + radius_server_on_fail(req->server, msg); + if (++req->nfailover >= req->module->max_failovers) { + module_radius_log(req->module, + LOG_WARNING, "RADIUS query q=%u time out", + req->q_id); + module_radius_req_on_failure(req); + return; + } + /* select the next server */ + module_radius_req_select_server(req); + } + module_radius_req_send(req); +} + +static void +module_radius_req_on_success(struct module_radius_req *req, + const u_char *pkt, size_t pktlen) +{ + module_accsreq_answer(req->module->base, req->q_id, 1, pkt, pktlen); + module_radius_req_free(req); +} + +static void +module_radius_req_on_failure(struct module_radius_req *req) +{ + module_accsreq_aborted(req->module->base, req->q_id); + module_radius_req_free(req); +} + + +static void +module_radius_req_free(struct module_radius_req *req) +{ + evtimer_del(&req->ev); + TAILQ_REMOVE(&req->module->req, req, next); + if (req->q_pkt != NULL) + radius_delete_packet(req->q_pkt); + free(req); +} + +static void +module_radius_req_select_server(struct module_radius_req *req) +{ + req->server = &req->module->server[req->module->curr_server]; + req->ntry = 0; + req->req_id = req->server->req_id_seq++; + radius_set_id(req->q_pkt, req->req_id); + module_radius_req_reset_msgauth(req); +} + +static void +module_radius_req_reset_msgauth(struct module_radius_req *req) +{ + if (radius_has_attr(req->q_pkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR)) + radius_del_attr_all(req->q_pkt, + RADIUS_TYPE_MESSAGE_AUTHENTICATOR); + if (req->module->secret[0] != '\0') + radius_put_message_authenticator(req->q_pkt, + req->module->secret); +} + +static void +module_radius_log(struct module_radius *_this, int pri, const char *fmt, ...) +{ + char fmt0[BUFSIZ]; + va_list va; + + snprintf(fmt0, sizeof(fmt0), "radius: %s", fmt); + va_start(va, fmt); + vlog(pri, fmt0, va); + va_end(va); +} diff --git a/usr.sbin/radiusd/radiusd_radius/Makefile b/usr.sbin/radiusd/radiusd_radius/Makefile new file mode 100644 index 00000000000..5c6016b7f18 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_radius/Makefile @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +PROG= radiusd_radius +BINDIR= /usr/libexec/radiusd +SRCS= radiusd_radius.c radiusd_module.c util.c imsg_subr.c log.c +CFLAGS+= -DUSE_LIBEVENT +LDADD+= -lradius -lcrypto -lutil -levent +LDADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL} ${LIBEVENT} +NOMAN= # + +.include <bsd.prog.mk> diff --git a/usr.sbin/radiusd/util.c b/usr.sbin/radiusd/util.c new file mode 100644 index 00000000000..1d3fccf2d3a --- /dev/null +++ b/usr.sbin/radiusd/util.c @@ -0,0 +1,108 @@ +/* $OpenBSD: util.c,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +/* + * Copyright (c) 2013 Internet Initiative Japan Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <netdb.h> +#include <string.h> + +#include "util.h" + +/* + * Convert argument like "192.168.160.1:1723/tcp" or "[::1]:1723/tcp" to + * match getaddrinfo(3)'s specification and pass them to getaddrinfo(3). + */ +int +addrport_parse(const char *addrport, int proto, struct addrinfo **p_ai) +{ + char *servp, *nodep, *slash, buf[256]; + struct addrinfo hints; + + strlcpy(buf, addrport, sizeof(buf)); + if (buf[0] == '[' && (servp = strchr(buf, ']')) != NULL) { + nodep = buf + 1; + *servp++ = '\0'; + if (*servp != ':') + servp = NULL; + } else { + nodep = buf; + servp = strrchr(nodep, ':'); + } + if (servp != NULL) { + *servp = '\0'; + servp++; + slash = strrchr(servp, '/'); + if (slash != NULL) { + /* + * Ignore like "/tcp" + */ + *slash = '\0'; + slash++; + } + } else + servp = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + switch (proto) { + case IPPROTO_TCP: + hints.ai_socktype = SOCK_STREAM; + break; + case IPPROTO_UDP: + hints.ai_socktype = SOCK_DGRAM; + break; + } + hints.ai_protocol = proto; + + return (getaddrinfo(nodep, servp, &hints, p_ai)); +} + +/* + * Make a string like "192.168.160.1:1723" or "[::1]:1723" from a struct + * sockaddr + */ +const char * +addrport_tostring(struct sockaddr *sa, socklen_t salen, char *buf, int lbuf) +{ + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + + if (getnameinfo(sa, salen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + return (NULL); + + switch (sa->sa_family) { + case AF_INET6: + strlcpy(buf, "[", lbuf); + strlcat(buf, hbuf, lbuf); + strlcat(buf, "]:", lbuf); + strlcat(buf, sbuf, lbuf); + break; + + case AF_INET: + strlcpy(buf, hbuf, lbuf); + strlcat(buf, ":", lbuf); + strlcat(buf, sbuf, lbuf); + break; + + default: + return (NULL); + } + + return (buf); +} diff --git a/usr.sbin/radiusd/util.h b/usr.sbin/radiusd/util.h new file mode 100644 index 00000000000..4521c93bad7 --- /dev/null +++ b/usr.sbin/radiusd/util.h @@ -0,0 +1,31 @@ +/* $OpenBSD: util.h,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ */ + +#ifndef RADIUSD_UTIL_H +#define RADIUSD_UTIL_H +/* + * Copyright (c) 2013 Internet Initiative Japan Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/socket.h> /* for struct sockaddr */ +#include <netdb.h> /* for struct addrinfo */ +#include <sys/cdefs.h> + +__BEGIN_DECLS + +int addrport_parse(const char *, int, struct addrinfo **); +const char *addrport_tostring (struct sockaddr *, socklen_t, char *, int); + +__END_DECLS +#endif |