diff options
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 |