summaryrefslogtreecommitdiff
path: root/usr.sbin/radiusd
diff options
context:
space:
mode:
authorYASUOKA Masahiko <yasuoka@cvs.openbsd.org>2024-07-14 13:44:31 +0000
committerYASUOKA Masahiko <yasuoka@cvs.openbsd.org>2024-07-14 13:44:31 +0000
commit673c641e1e58c8c7da39708dfea35dc8879d9884 (patch)
treeee1b11a43e5123bb378a6402d19798834b7688d6 /usr.sbin/radiusd
parenta5606e10b1a371d7239e9e5b50b9a32aab9adc85 (diff)
Add radiusd_file(8) module. It provides authencation by a local file.
Diffstat (limited to 'usr.sbin/radiusd')
-rw-r--r--usr.sbin/radiusd/Makefile3
-rw-r--r--usr.sbin/radiusd/chap_ms.c375
-rw-r--r--usr.sbin/radiusd/chap_ms.h48
-rw-r--r--usr.sbin/radiusd/parse.y3
-rw-r--r--usr.sbin/radiusd/radiusd_file.c586
-rw-r--r--usr.sbin/radiusd/radiusd_file/Makefile12
6 files changed, 1025 insertions, 2 deletions
diff --git a/usr.sbin/radiusd/Makefile b/usr.sbin/radiusd/Makefile
index 61ba31825f3..1fc21891f9e 100644
--- a/usr.sbin/radiusd/Makefile
+++ b/usr.sbin/radiusd/Makefile
@@ -1,7 +1,8 @@
-# $OpenBSD: Makefile,v 1.4 2024/07/09 17:26:14 yasuoka Exp $
+# $OpenBSD: Makefile,v 1.5 2024/07/14 13:44:30 yasuoka Exp $
SUBDIR= radiusd
SUBDIR+= radiusd_bsdauth
+SUBDIR+= radiusd_file
SUBDIR+= radiusd_ipcp
SUBDIR+= radiusd_radius
SUBDIR+= radiusd_standard
diff --git a/usr.sbin/radiusd/chap_ms.c b/usr.sbin/radiusd/chap_ms.c
new file mode 100644
index 00000000000..878fd4fd08c
--- /dev/null
+++ b/usr.sbin/radiusd/chap_ms.c
@@ -0,0 +1,375 @@
+/* $OpenBSD: chap_ms.c,v 1.1 2024/07/14 13:44:30 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;
+
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_md4(), NULL);
+ EVP_DigestUpdate(ctx, in, inlen);
+ EVP_DigestFinal_ex(ctx, hash, &mdlen);
+ EVP_MD_CTX_free(ctx);
+}
+
+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++;
+
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctx, peer_challenge, MSCHAPV2_CHALLENGE_SZ);
+ EVP_DigestUpdate(ctx, auth_challenge, MSCHAPV2_CHALLENGE_SZ);
+ EVP_DigestUpdate(ctx, name, strlen(name));
+ EVP_DigestFinal_ex(ctx, md, &mdlen);
+ EVP_MD_CTX_free(ctx);
+
+ 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);
+
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctx, password_hash2, sizeof(password_hash2));
+ EVP_DigestUpdate(ctx, ntresponse, 24);
+ EVP_DigestUpdate(ctx, magic1, 39);
+ EVP_DigestFinal_ex(ctx, md, &mdlen);
+
+ mschap_challenge_hash(peer_challenge, auth_challenge,
+ username, usernamelen, challenge);
+
+ EVP_DigestInit_ex(ctx, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctx, md, sizeof(md));
+ EVP_DigestUpdate(ctx, challenge, sizeof(challenge));
+ EVP_DigestUpdate(ctx, magic2, 41);
+ EVP_DigestFinal_ex(ctx, md, &mdlen);
+ EVP_MD_CTX_free(ctx);
+
+ /*
+ * 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
+ };
+
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctx, password_hash2, MSCHAP_HASH_SZ);
+ EVP_DigestUpdate(ctx, ntresponse, 24);
+ EVP_DigestUpdate(ctx, magic1, 27);
+ EVP_DigestFinal_ex(ctx, md, &mdlen);
+ EVP_MD_CTX_free(ctx);
+
+ 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;
+
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctx, masterkey, 16);
+ EVP_DigestUpdate(ctx, sha1_pad1, 40);
+ EVP_DigestUpdate(ctx, s, 84);
+ EVP_DigestUpdate(ctx, sha1_pad2, 40);
+ EVP_DigestFinal_ex(ctx, md, &mdlen);
+ EVP_MD_CTX_free(ctx);
+
+ 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 *encrypted,
+ 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;
+
+ ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, secret, strlen(secret));
+ EVP_DigestUpdate(ctx, authenticator, 16);
+ EVP_DigestUpdate(ctx, encrypted, 2);
+ EVP_DigestFinal_ex(ctx, b, &mdlen);
+
+ for (i = 0; i < mdlen; i++) {
+ p[i] = b[i] ^ encrypted[i+2];
+ }
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, secret, strlen(secret));
+ EVP_DigestUpdate(ctx, encrypted + 2, mdlen);
+ EVP_DigestFinal_ex(ctx, b, &mdlen);
+ EVP_MD_CTX_free(ctx);
+
+ for (i = 0; i < mdlen; i++) {
+ p[i+16] = b[i] ^ encrypted[i+18];
+ }
+
+ memcpy(plain, p+1, 16);
+}
diff --git a/usr.sbin/radiusd/chap_ms.h b/usr.sbin/radiusd/chap_ms.h
new file mode 100644
index 00000000000..b46a8862426
--- /dev/null
+++ b/usr.sbin/radiusd/chap_ms.h
@@ -0,0 +1,48 @@
+/* $OpenBSD: chap_ms.h,v 1.1 2024/07/14 13:44:30 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/radiusd/parse.y b/usr.sbin/radiusd/parse.y
index c1aac39f444..b8dc055eef1 100644
--- a/usr.sbin/radiusd/parse.y
+++ b/usr.sbin/radiusd/parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: parse.y,v 1.23 2024/07/13 13:06:47 yasuoka Exp $ */
+/* $OpenBSD: parse.y,v 1.24 2024/07/14 13:44:30 yasuoka Exp $ */
/*
* Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -1010,6 +1010,7 @@ default_module_path(const char *name)
const char *path;
} module_paths[] = {
{ "bsdauth", "/usr/libexec/radiusd/radiusd_bsdauth" },
+ { "file", "/usr/libexec/radiusd/radiusd_file" },
{ "ipcp", "/usr/libexec/radiusd/radiusd_ipcp" },
{ "radius", "/usr/libexec/radiusd/radiusd_radius" },
{ "standard", "/usr/libexec/radiusd/radiusd_standard" }
diff --git a/usr.sbin/radiusd/radiusd_file.c b/usr.sbin/radiusd/radiusd_file.c
new file mode 100644
index 00000000000..562c0dacdcf
--- /dev/null
+++ b/usr.sbin/radiusd/radiusd_file.c
@@ -0,0 +1,586 @@
+/* $OpenBSD: radiusd_file.c,v 1.1 2024/07/14 13:44:30 yasuoka Exp $ */
+
+/*
+ * Copyright (c) 2024 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/cdefs.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <imsg.h>
+#include <limits.h>
+#include <md5.h>
+#include <radius.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "chap_ms.h"
+#include "imsg_subr.h"
+#include "log.h"
+#include "radiusd.h"
+#include "radiusd_module.h"
+
+struct module_file_params {
+ int debug;
+ char path[PATH_MAX];
+};
+
+struct module_file {
+ struct module_base *base;
+ struct imsgbuf ibuf;
+ struct module_file_params
+ params;
+};
+
+struct module_file_userinfo {
+ struct in_addr frame_ip_address;
+ char password[0];
+};
+
+/* IPC between priv and main */
+enum {
+ IMSG_RADIUSD_FILE_OK = 1000,
+ IMSG_RADIUSD_FILE_NG,
+ IMSG_RADIUSD_FILE_PARAMS,
+ IMSG_RADIUSD_FILE_USERINFO
+};
+
+static void parent_dispatch_main(struct module_file_params *,
+ struct imsgbuf *, struct imsg *);
+static void module_file_main(void) __dead;
+static pid_t start_child(char *, int);
+static void module_file_config_set(void *, const char *, int,
+ char * const *);
+static void module_file_start(void *);
+static void module_file_access_request(void *, u_int, const u_char *,
+ size_t);
+static void auth_pap(struct module_file *, u_int, RADIUS_PACKET *, char *,
+ struct module_file_userinfo *);
+static void auth_md5chap(struct module_file *, u_int, RADIUS_PACKET *,
+ char *, struct module_file_userinfo *);
+static void auth_mschapv2(struct module_file *, u_int, RADIUS_PACKET *,
+ char *, struct module_file_userinfo *);
+
+static struct module_handlers module_file_handlers = {
+ .access_request = module_file_access_request,
+ .config_set = module_file_config_set,
+ .start = module_file_start
+};
+
+int
+main(int argc, char *argv[])
+{
+ int ch, pairsock[2], status;
+ pid_t pid;
+ char *saved_argv0;
+ struct imsgbuf ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ size_t datalen;
+ struct module_file_params *paramsp, params;
+
+ while ((ch = getopt(argc, argv, "M")) != -1)
+ switch (ch) {
+ case 'M':
+ module_file_main();
+ /* not reached */
+ break;
+ }
+ saved_argv0 = argv[0];
+
+ argc -= optind;
+ argv += optind;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC,
+ pairsock) == -1)
+ err(EXIT_FAILURE, "socketpair");
+
+ log_init(0);
+
+ pid = start_child(saved_argv0, pairsock[1]);
+
+ /* Privileged process */
+ setproctitle("[priv]");
+ imsg_init(&ibuf, pairsock[0]);
+
+ if (imsg_sync_read(&ibuf, 2000) <= 0 ||
+ (n = imsg_get(&ibuf, &imsg)) <= 0)
+ exit(EXIT_FAILURE);
+ if (imsg.hdr.type != IMSG_RADIUSD_FILE_PARAMS)
+ err(EXIT_FAILURE, "Receieved unknown message type %d",
+ imsg.hdr.type);
+ datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(params))
+ err(EXIT_FAILURE, "Receieved IMSG_RADIUSD_FILE_PARAMS "
+ "message is wrong size");
+ paramsp = imsg.data;
+ if (paramsp->path[0] != '\0') {
+ if (unveil(paramsp->path, "r") == -1)
+ err(EXIT_FAILURE, "unveil");
+ }
+ if (paramsp->debug)
+ log_init(1);
+
+ if (unveil(NULL, NULL) == -1)
+ err(EXIT_FAILURE, "unveil");
+ if (pledge("stdio rpath", NULL) == -1)
+ err(EXIT_FAILURE, "pledge");
+
+ memcpy(&params, paramsp, sizeof(params));
+
+ for (;;) {
+ if ((n = imsg_read(&ibuf)) <= 0 && errno != EAGAIN)
+ break;
+ for (;;) {
+ if ((n = imsg_get(&ibuf, &imsg)) == -1)
+ break;
+ if (n == 0)
+ break;
+ parent_dispatch_main(&params, &ibuf, &imsg);
+ imsg_free(&imsg);
+ imsg_flush(&ibuf);
+ }
+ imsg_flush(&ibuf);
+ }
+ imsg_clear(&ibuf);
+
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ exit(WEXITSTATUS(status));
+}
+
+void
+parent_dispatch_main(struct module_file_params *params, struct imsgbuf *ibuf,
+ struct imsg *imsg)
+{
+ size_t datalen, entsz, passz;
+ const char *username;
+ char *buf, *db[2], *str;
+ int ret;
+ struct module_file_userinfo *ent;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ switch (imsg->hdr.type) {
+ case IMSG_RADIUSD_FILE_USERINFO:
+ if (datalen == 0 ||
+ *((char *)imsg->data + datalen - 1) != '\0') {
+ log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO "
+ "is wrong", __func__);
+ goto on_error;
+ }
+ username = imsg->data;
+ db[0] = params->path;
+ db[1] = NULL;
+ if ((ret = cgetent(&buf, db, username)) < 0) {
+ log_info("user `%s' is not configured", username);
+ goto on_error;
+ }
+ if ((ret = cgetstr(buf, "password", &str)) < 0) {
+ log_info("password for `%s' is not configured",
+ username);
+ goto on_error;
+ }
+ passz = strlen(str) + 1;
+ entsz = offsetof(struct module_file_userinfo, password[passz]);
+ if ((ent = calloc(1, entsz)) == NULL) {
+ log_warn("%s; calloc", __func__);
+ goto on_error;
+ }
+ strlcpy(ent->password, str, passz);
+ imsg_compose(ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1,
+ ent, entsz);
+ freezero(ent, entsz);
+ break;
+ }
+ return;
+ on_error:
+ imsg_compose(ibuf, IMSG_RADIUSD_FILE_NG, 0, -1, -1, NULL, 0);
+}
+
+/* main process */
+void
+module_file_main(void)
+{
+ struct module_file module_file;
+
+ setproctitle("[main]");
+
+ memset(&module_file, 0, sizeof(module_file));
+ if ((module_file.base = module_create(STDIN_FILENO, &module_file,
+ &module_file_handlers)) == NULL)
+ err(1, "Could not create a module instance");
+
+ module_drop_privilege(module_file.base, 0);
+
+ module_load(module_file.base);
+ imsg_init(&module_file.ibuf, 3);
+
+ if (pledge("stdio", NULL) == -1)
+ err(EXIT_FAILURE, "pledge");
+ while (module_run(module_file.base) == 0)
+ ;
+
+ module_destroy(module_file.base);
+
+ exit(0);
+}
+
+pid_t
+start_child(char *argv0, int fd)
+{
+ char *argv[5];
+ int argc = 0;
+ pid_t pid;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("cannot fork");
+ case 0:
+ break;
+ default:
+ close(fd);
+ return (pid);
+ }
+
+ if (fd != 3) {
+ if (dup2(fd, 3) == -1)
+ fatal("cannot setup imsg fd");
+ } else if (fcntl(fd, F_SETFD, 0) == -1)
+ fatal("cannot setup imsg fd");
+
+ argv[argc++] = argv0;
+ argv[argc++] = "-M"; /* main proc */
+ argv[argc++] = NULL;
+ execvp(argv0, argv);
+ fatal("execvp");
+}
+
+void
+module_file_config_set(void *ctx, const char *name, int valc,
+ char * const * valv)
+{
+ struct module_file *module = ctx;
+ char *errmsg;
+
+ if (strcmp(name, "path") == 0) {
+ SYNTAX_ASSERT(valc == 1, "`path' must have a argument");
+ if (strlcpy(module->params.path, valv[0], sizeof(
+ module->params.path)) >= sizeof(module->params.path)) {
+ module_send_message(module->base, IMSG_NG,
+ "`path' is too long");
+ return;
+ }
+ module_send_message(module->base, IMSG_OK, NULL);
+ } else if (strcmp(name, "_debug") == 0) {
+ log_init(1);
+ module->params.debug = 1;
+ module_send_message(module->base, IMSG_OK, NULL);
+ } else if (strncmp(name, "_", 1) == 0)
+ /* ignore all internal messages */
+ module_send_message(module->base, IMSG_OK, NULL);
+ else
+ module_send_message(module->base, IMSG_NG,
+ "Unknown config parameter `%s'", name);
+ return;
+ syntax_error:
+ module_send_message(module->base, IMSG_NG, "%s", errmsg);
+ return;
+}
+
+void
+module_file_start(void *ctx)
+{
+ struct module_file *module = ctx;
+
+ if (module->params.path[0] == '\0') {
+ module_send_message(module->base, IMSG_NG,
+ "`path' is not configured");
+ return;
+ }
+ imsg_compose(&module->ibuf, IMSG_RADIUSD_FILE_PARAMS, 0, -1, -1,
+ &module->params, sizeof(module->params));
+ imsg_flush(&module->ibuf);
+
+ module_send_message(module->base, IMSG_OK, NULL);
+}
+
+void
+module_file_access_request(void *ctx, u_int query_id, const u_char *pkt,
+ size_t pktlen)
+{
+ size_t datalen;
+ struct module_file *self = ctx;
+ RADIUS_PACKET *radpkt = NULL;
+ char username[256];
+ ssize_t n;
+ struct imsg imsg;
+ struct module_file_userinfo *ent;
+
+ memset(&imsg, 0, sizeof(imsg));
+
+ if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
+ log_warn("%s: radius_convert_packet()", __func__);
+ goto on_error;
+ }
+ radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username,
+ sizeof(username));
+
+ imsg_compose(&self->ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1,
+ username, strlen(username) + 1);
+ imsg_flush(&self->ibuf);
+ if ((n = imsg_read(&self->ibuf)) == -1 || n == 0) {
+ log_warn("%s: imsg_read()", __func__);
+ goto on_error;
+ }
+ if ((n = imsg_get(&self->ibuf, &imsg)) <= 0) {
+ log_warn("%s: imsg_get()", __func__);
+ goto on_error;
+ }
+
+ datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ if (imsg.hdr.type == IMSG_RADIUSD_FILE_USERINFO) {
+ if (datalen <= offsetof(struct module_file_userinfo,
+ password[0])) {
+ log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO is "
+ "invalid", __func__);
+ goto on_error;
+ }
+ ent = imsg.data;
+ } else
+ goto on_error;
+
+ if (radius_has_attr(radpkt, RADIUS_TYPE_USER_PASSWORD))
+ auth_pap(self, query_id, radpkt, username, ent);
+ else if (radius_has_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD))
+ auth_md5chap(self, query_id, radpkt, username, ent);
+ else if (radius_has_vs_attr(radpkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MS_CHAP2_RESPONSE))
+ auth_mschapv2(self, query_id, radpkt, username, ent);
+ else {
+ log_info("q=%u unsupported authentication methods", query_id);
+ explicit_bzero(ent->password, strlen(ent->password));
+ }
+ on_error:
+ if (radpkt != NULL)
+ radius_delete_packet(radpkt);
+ imsg_free(&imsg);
+ return;
+}
+
+void
+auth_pap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
+ char *username, struct module_file_userinfo *ent)
+{
+ RADIUS_PACKET *respkt = NULL;
+ char pass[256];
+ int ret;
+
+ if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_PASSWORD, pass,
+ sizeof(pass)) != 0) {
+ log_warnx("%s: radius_get_string_attr", __func__);
+ return;
+ }
+ ret = strcmp(ent->password, pass);
+ log_info("%s %s", ent->password, pass);
+ explicit_bzero(ent->password, strlen(ent->password));
+ log_info("q=%u User `%s' authentication %s (PAP)", q_id, username,
+ (ret == 0)? "succeeded" : "failed");
+ if ((respkt = radius_new_response_packet((ret == 0)?
+ RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt))
+ == NULL) {
+ log_warn("%s: radius_new_response_packet()", __func__);
+ return;
+ }
+ module_accsreq_answer(self->base, q_id,
+ radius_get_data(respkt), radius_get_length(respkt));
+ radius_delete_packet(respkt);
+}
+
+void
+auth_md5chap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
+ char *username, struct module_file_userinfo *ent)
+{
+ RADIUS_PACKET *respkt = NULL;
+ size_t attrlen, challlen;
+ u_char chall[256], idpass[17], digest[16];
+ int ret;
+ MD5_CTX md5;
+
+ attrlen = sizeof(idpass);
+ if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD, idpass,
+ &attrlen) != 0) {
+ log_warnx("%s: radius_get_string_attr", __func__);
+ return;
+ }
+ challlen = sizeof(chall);
+ if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_CHALLENGE, chall,
+ &challlen) != 0) {
+ log_warnx("%s: radius_get_string_attr", __func__);
+ return;
+ }
+ MD5Init(&md5);
+ MD5Update(&md5, idpass, 1);
+ MD5Update(&md5, ent->password, strlen(ent->password));
+ MD5Update(&md5, chall, challlen);
+ MD5Final(digest, &md5);
+
+ ret = timingsafe_bcmp(idpass + 1, digest, sizeof(digest));
+ log_info("q=%u User `%s' authentication %s (CHAP)", q_id, username,
+ (ret == 0)? "succeeded" : "failed");
+ if ((respkt = radius_new_response_packet((ret == 0)?
+ RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt))
+ == NULL) {
+ log_warn("%s: radius_new_response_packet()", __func__);
+ return;
+ }
+ module_accsreq_answer(self->base, q_id,
+ radius_get_data(respkt), radius_get_length(respkt));
+ radius_delete_packet(respkt);
+}
+
+void
+auth_mschapv2(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
+ char *username, struct module_file_userinfo *ent)
+{
+ RADIUS_PACKET *respkt = NULL;
+ size_t attrlen;
+ int i, lpass;
+ char *pass = NULL;
+ uint8_t chall[MSCHAPV2_CHALLENGE_SZ];
+ uint8_t ntresponse[24], authenticator[16];
+ uint8_t pwhash[16], pwhash2[16], master[64];
+ struct {
+ uint8_t salt[2];
+ uint8_t len;
+ uint8_t key[16];
+ uint8_t pad[15];
+ } __packed rcvkey, sndkey;
+ struct {
+ uint8_t ident;
+ uint8_t flags;
+ uint8_t peerchall[16];
+ uint8_t reserved[8];
+ uint8_t ntresponse[24];
+ } __packed resp;
+ struct authresp {
+ uint8_t ident;
+ uint8_t authresp[42];
+ } __packed authresp;
+
+
+ attrlen = sizeof(chall);
+ if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MS_CHAP_CHALLENGE, chall, &attrlen) != 0) {
+ log_info("q=%u failed to retribute MS-CHAP-Challenge", q_id);
+ goto on_error;
+ }
+ attrlen = sizeof(resp);
+ if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, &attrlen) != 0) {
+ log_info("q=%u failed to retribute MS-CHAP2-Response", q_id);
+ goto on_error;
+ }
+
+ /* convert the password to UTF16-LE */
+ lpass = strlen(ent->password);
+ if ((pass = calloc(1, lpass * 2)) == NULL) {
+ log_warn("%s: calloc()", __func__);
+ goto on_error;
+ }
+ for (i = 0; i < lpass; i++) {
+ pass[i * 2] = ent->password[i];
+ pass[i * 2 + 1] = '\0';
+ }
+
+ /* calculate NT-Response by the password */
+ mschap_nt_response(chall, resp.peerchall,
+ username, strlen(username), pass, lpass * 2, ntresponse);
+
+ if (timingsafe_bcmp(ntresponse, resp.ntresponse, 24) != 0) {
+ log_info("q=%u User `%s' authentication failed (MSCHAPv2)",
+ q_id, username);
+ if ((respkt = radius_new_response_packet(
+ RADIUS_CODE_ACCESS_REJECT, radpkt)) == NULL) {
+ log_warn("%s: radius_new_response_packet()", __func__);
+ goto on_error;
+ }
+ authresp.ident = resp.ident;
+ strlcpy(authresp.authresp, "E=691 R=0 V=3",
+ sizeof(authresp.authresp));
+ radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MS_CHAP_ERROR, &authresp,
+ offsetof(struct authresp, authresp[13]));
+ } else {
+ log_info("q=%u User `%s' authentication succeeded (MSCHAPv2)",
+ q_id, username);
+ if ((respkt = radius_new_response_packet(
+ RADIUS_CODE_ACCESS_ACCEPT, radpkt)) == NULL) {
+ log_warn("%s: radius_new_response_packet()", __func__);
+ goto on_error;
+ }
+ mschap_auth_response(pass, lpass * 2, ntresponse, chall,
+ resp.peerchall, username, strlen(username),
+ authresp.authresp);
+ authresp.ident = resp.ident;
+
+ radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MS_CHAP2_SUCCESS, &authresp,
+ offsetof(struct authresp, authresp[42]));
+
+ mschap_ntpassword_hash(pass, lpass * 2, pwhash);
+ mschap_ntpassword_hash(pwhash, sizeof(pwhash), pwhash2);
+ mschap_masterkey(pwhash2, ntresponse, master);
+ radius_get_authenticator(radpkt, authenticator);
+
+ /* MS-MPPE-Recv-Key */
+ memset(&rcvkey, 0, sizeof(rcvkey));
+ arc4random_buf(rcvkey.salt, sizeof(rcvkey.salt));
+ rcvkey.salt[0] |= 0x80;
+ mschap_asymetric_startkey(master, rcvkey.key, 16, 0, 1);
+ radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MPPE_RECV_KEY, &rcvkey, sizeof(rcvkey));
+
+ /* MS-MPPE-Send-Key */
+ memset(&sndkey, 0, sizeof(sndkey));
+ arc4random_buf(sndkey.salt, sizeof(sndkey.salt));
+ sndkey.salt[0] |= 0x80;
+ mschap_asymetric_startkey(master, sndkey.key, 16, 1, 1);
+ radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
+ RADIUS_VTYPE_MPPE_SEND_KEY, &sndkey, sizeof(sndkey));
+ }
+
+ module_accsreq_answer(self->base, q_id,
+ radius_get_data(respkt), radius_get_length(respkt));
+ on_error:
+ /* bzero password */
+ explicit_bzero(ent->password, strlen(ent->password));
+ if (pass != NULL)
+ explicit_bzero(pass, lpass * 2);
+ free(pass);
+ if (respkt != NULL)
+ radius_delete_packet(respkt);
+}
diff --git a/usr.sbin/radiusd/radiusd_file/Makefile b/usr.sbin/radiusd/radiusd_file/Makefile
new file mode 100644
index 00000000000..aa248fc38f5
--- /dev/null
+++ b/usr.sbin/radiusd/radiusd_file/Makefile
@@ -0,0 +1,12 @@
+# $OpenBSD: Makefile,v 1.1 2024/07/14 13:44:30 yasuoka Exp $
+
+PROG= radiusd_file
+BINDIR= /usr/libexec/radiusd
+SRCS= radiusd_file.c radiusd_module.c imsg_subr.c log.c chap_ms.c
+#SRCS+= radius_subr.c
+LDADD+= -lradius -lcrypto -lutil
+DPADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL}
+#MAN= radiusd_file.8
+NOMAN= #
+
+.include <bsd.prog.mk>