summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/mtree/4.4BSD.dist4
-rw-r--r--usr.sbin/radiusctl/Makefile10
-rw-r--r--usr.sbin/radiusctl/chap_ms.c363
-rw-r--r--usr.sbin/radiusctl/chap_ms.h48
-rw-r--r--usr.sbin/radiusctl/parser.c300
-rw-r--r--usr.sbin/radiusctl/parser.h47
-rw-r--r--usr.sbin/radiusctl/radiusctl.c414
-rw-r--r--usr.sbin/radiusd/Makefile6
-rw-r--r--usr.sbin/radiusd/Makefile.inc10
-rw-r--r--usr.sbin/radiusd/imsg_subr.c78
-rw-r--r--usr.sbin/radiusd/imsg_subr.h16
-rw-r--r--usr.sbin/radiusd/log.c192
-rw-r--r--usr.sbin/radiusd/log.h29
-rw-r--r--usr.sbin/radiusd/parse.y806
-rw-r--r--usr.sbin/radiusd/radiusd.846
-rw-r--r--usr.sbin/radiusd/radiusd.c1497
-rw-r--r--usr.sbin/radiusd/radiusd.conf.574
-rw-r--r--usr.sbin/radiusd/radiusd.h73
-rw-r--r--usr.sbin/radiusd/radiusd/Makefile9
-rw-r--r--usr.sbin/radiusd/radiusd_bsdauth.c180
-rw-r--r--usr.sbin/radiusd/radiusd_bsdauth/Makefile9
-rw-r--r--usr.sbin/radiusd/radiusd_local.h175
-rw-r--r--usr.sbin/radiusd/radiusd_module.c489
-rw-r--r--usr.sbin/radiusd/radiusd_module.h73
-rw-r--r--usr.sbin/radiusd/radiusd_radius.c613
-rw-r--r--usr.sbin/radiusd/radiusd_radius/Makefile10
-rw-r--r--usr.sbin/radiusd/util.c108
-rw-r--r--usr.sbin/radiusd/util.h31
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