Add radiusd_file(8) module. It provides authencation by a local file.
authoryasuoka <yasuoka@openbsd.org>
Sun, 14 Jul 2024 13:44:30 +0000 (13:44 +0000)
committeryasuoka <yasuoka@openbsd.org>
Sun, 14 Jul 2024 13:44:30 +0000 (13:44 +0000)
usr.sbin/radiusd/Makefile
usr.sbin/radiusd/chap_ms.c [new file with mode: 0644]
usr.sbin/radiusd/chap_ms.h [new file with mode: 0644]
usr.sbin/radiusd/parse.y
usr.sbin/radiusd/radiusd_file.c [new file with mode: 0644]
usr.sbin/radiusd/radiusd_file/Makefile [new file with mode: 0644]

index 61ba318..1fc2189 100644 (file)
@@ -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 (file)
index 0000000..878fd4f
--- /dev/null
@@ -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 (file)
index 0000000..b46a886
--- /dev/null
@@ -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 */
index c1aac39..b8dc055 100644 (file)
@@ -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 (file)
index 0000000..562c0da
--- /dev/null
@@ -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 (file)
index 0000000..aa248fc
--- /dev/null
@@ -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>