Add new radiusd_eap2mschap module. It provides conversions from EAP
authoryasuoka <yasuoka@openbsd.org>
Sun, 14 Jul 2024 16:09:23 +0000 (16:09 +0000)
committeryasuoka <yasuoka@openbsd.org>
Sun, 14 Jul 2024 16:09:23 +0000 (16:09 +0000)
to MSCHAPv2.

usr.sbin/radiusd/Makefile
usr.sbin/radiusd/Makefile.inc
usr.sbin/radiusd/eap2mschap_local.h [new file with mode: 0644]
usr.sbin/radiusd/parse.y
usr.sbin/radiusd/radiusd.conf.5
usr.sbin/radiusd/radiusd_eap2mschap.8 [new file with mode: 0644]
usr.sbin/radiusd/radiusd_eap2mschap.c [new file with mode: 0644]
usr.sbin/radiusd/radiusd_eap2mschap/Makefile [new file with mode: 0644]

index 1fc2189..b21d203 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.5 2024/07/14 13:44:30 yasuoka Exp $
+#      $OpenBSD: Makefile,v 1.6 2024/07/14 16:09:23 yasuoka Exp $
 
 SUBDIR=                radiusd
 SUBDIR+=       radiusd_bsdauth
@@ -6,5 +6,6 @@ SUBDIR+=        radiusd_file
 SUBDIR+=       radiusd_ipcp
 SUBDIR+=       radiusd_radius
 SUBDIR+=       radiusd_standard
+SUBDIR+=       radiusd_eap2mschap
 
 .include <bsd.prog.mk>
index d5d2462..087857e 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile.inc,v 1.3 2024/07/02 16:18:11 deraadt Exp $
+#      $OpenBSD: Makefile.inc,v 1.4 2024/07/14 16:09:23 yasuoka Exp $
 
 .PATH:         ${.CURDIR}/..
 CFLAGS+=       -I${.CURDIR}/..
@@ -7,5 +7,5 @@ CFLAGS+=        -Wstrict-prototypes -Wmissing-prototypes
 CFLAGS+=       -Wmissing-declarations -Wpointer-arith
 
 #DEGUG=                -g
-#CFLAGS+=      -DRADIUSD_DEBUG
+#CFLAGS+=      -DRADIUSD_DEBUG -DEAP2MSCHAP_DEBUG
 #CFLAGS+=      -Werror
diff --git a/usr.sbin/radiusd/eap2mschap_local.h b/usr.sbin/radiusd/eap2mschap_local.h
new file mode 100644 (file)
index 0000000..ff761f2
--- /dev/null
@@ -0,0 +1,205 @@
+/*     $OpenBSD: eap2mschap_local.h,v 1.1 2024/07/14 16:09:23 yasuoka Exp $    */
+
+/*
+ * Copyright (c) 2024 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.
+ */
+
+#define        EAP_CODE_REQUEST        1
+#define        EAP_CODE_RESPONSE       2
+#define        EAP_CODE_SUCCESS        3
+#define        EAP_CODE_FAILURE        4
+
+#define        EAP_TYPE_IDENTITY       1
+#define        EAP_TYPE_NOTIFICATION   2
+#define        EAP_TYPE_NAK            3
+#define        EAP_TYPE_MSCHAPV2       0x1a    /* [MS-CHAP] MS-EAP-Authentication */
+
+#define CHAP_CHALLENGE         1
+#define CHAP_RESPONSE          2
+#define CHAP_SUCCESS           3
+#define CHAP_FAILURE           4
+
+/* From [MS-CHAP] */
+enum eap_chap_status {
+       EAP_CHAP_NONE,
+       EAP_CHAP_CHALLENGE_SENT,
+       EAP_CHAP_SUCCESS_REQUEST_SENT,
+       EAP_CHAP_FAILURE_REQUEST_SENT,
+       EAP_CHAP_CHANGE_PASSWORD_SENT,
+       EAP_CHAP_FAILED,
+       EAP_CHAP_SUCCESS
+};
+
+struct eap {
+       uint8_t         code;
+       uint8_t         id;
+       uint16_t        length;
+       uint8_t         value[0];
+} __packed;
+
+struct chap {
+       uint8_t         code;
+       uint8_t         id;
+       uint16_t        length;
+       int8_t          value[0];
+} __packed;
+
+struct eap_chap {
+       struct eap      eap;
+       uint8_t         eap_type;
+       struct chap     chap;
+};
+
+struct eap_mschap_challenge {
+       struct eap      eap;
+       uint8_t         eap_type;
+       struct chap     chap;
+       uint8_t         challsiz;
+       uint8_t         chall[16];
+       char            chap_name[0];
+} __packed;
+static_assert(sizeof(struct eap_mschap_challenge) == 26, "");
+static_assert(offsetof(struct eap_mschap_challenge, chap) == 5, "");
+static_assert(offsetof(struct eap_mschap_challenge, chall) == 10, "");
+
+struct eap_mschap_response {
+       struct eap      eap;
+       uint8_t         eap_type;
+       struct chap     chap;
+       uint8_t         challsiz;
+       uint8_t         peerchall[16];
+       uint8_t         reserved[8];
+       uint8_t         ntresponse[24];
+       uint8_t         flags;
+       uint8_t         chap_name[0];
+} __packed;
+static_assert(sizeof(struct eap_mschap_response) == 59, "");
+static_assert(offsetof(struct eap_mschap_response, chap) == 5, "");
+static_assert(offsetof(struct eap_mschap_response, peerchall) == 10, "");
+
+struct radius_ms_chap2_response {
+       uint8_t         ident;
+       uint8_t         flags;
+       uint8_t         peerchall[16];
+       uint8_t         reserved[8];
+       uint8_t         ntresponse[24];
+} __packed;
+
+
+struct eap2mschap;
+
+struct access_req {
+       struct eap2mschap       *eap2mschap;
+       char                    *username;
+       u_int                    q_id;
+       TAILQ_ENTRY(access_req)  next;
+       RB_ENTRY(access_req)     tree;
+       /* for EAP */
+       enum eap_chap_status     eap_chap_status;
+       char                     state[16];
+       unsigned char            chap_id;
+       unsigned char            eap_id;
+       time_t                   eap_time;
+       char                     chall[16];
+       RADIUS_PACKET           *pkt;
+
+};
+TAILQ_HEAD(access_reqq, access_req);
+RB_HEAD(access_reqt, access_req);
+
+#define CHAP_NAME_MAX                  40
+
+struct eap2mschap {
+       struct module_base      *base;
+       char                    *secret;
+       char                     chap_name[CHAP_NAME_MAX + 1];
+       struct access_reqq       reqq;
+       struct access_reqt       eapt;
+       struct event             ev_eapt;
+};
+
+/* Attributes copied from CHAP Access-Accept to EAP Access-Access-Accept */
+struct preserve_attrs {
+       uint8_t         type;
+       uint32_t        vendor;
+} preserve_attrs[] = {
+       { RADIUS_TYPE_FRAMED_PROTOCOL,          0},
+       { RADIUS_TYPE_FRAMED_IP_ADDRESS,        0},
+       { RADIUS_TYPE_FRAMED_IP_NETMASK,        0},
+       { RADIUS_TYPE_FRAMED_IPV6_ADDRESS,      0},
+       { RADIUS_TYPE_DNS_SERVER_IPV6_ADDRESS,  0},
+       { RADIUS_TYPE_FRAMED_ROUTING,           0},
+       { RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER,   RADIUS_VENDOR_MICROSOFT },
+       { RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER, RADIUS_VENDOR_MICROSOFT },
+       { RADIUS_VTYPE_MS_PRIMARY_NBNS_SERVER,  RADIUS_VENDOR_MICROSOFT },
+       { RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER,RADIUS_VENDOR_MICROSOFT },
+       { RADIUS_VTYPE_MPPE_SEND_KEY,           RADIUS_VENDOR_MICROSOFT },
+       { RADIUS_VTYPE_MPPE_RECV_KEY,           RADIUS_VENDOR_MICROSOFT }
+};
+
+#ifndef EAP2MSCHAP_DEBUG
+#define        EAP2MSCHAP_DBG(...)
+#define        EAP2MSCHAP_ASSERT(_cond)
+#else
+#define        EAP2MSCHAP_DBG(...)     logit(LOG_DEBUG, __VA_ARGS__)
+#define        EAP2MSCHAP_ASSERT(_cond)                                \
+       do {                                                    \
+               if (!(_cond)) {                                 \
+                       log_warnx(                              \
+                           "ASSERT(%s) failed in %s() at %s:%d",\
+                           #_cond, __func__, __FILE__, __LINE__);\
+                       abort();                                \
+               }                                               \
+       } while (0/* CONSTCOND */);
+#endif
+#ifndef nitems
+#define nitems(_x)    (sizeof((_x)) / sizeof((_x)[0]))
+#endif
+
+static void     eap2mschap_init(struct eap2mschap *);
+static void     eap2mschap_start(void *);
+static void     eap2mschap_config_set(void *, const char *, int,
+                   char * const *);
+static void     eap2mschap_stop(void *);
+static void     eap2mschap_access_request(void *, u_int, const u_char *,
+                   size_t);
+static void     eap2mschap_next_response(void *, u_int, const u_char *,
+                   size_t);
+
+static void     eap2mschap_on_eapt (int, short, void *);
+static void     eap2mschap_reset_eaptimer (struct eap2mschap *);
+
+static struct access_req
+               *access_request_new(struct eap2mschap *, u_int);
+static void     access_request_free(struct access_req *);
+static int      access_request_compar(struct access_req *,
+                   struct access_req *);
+
+
+static struct access_req
+               *eap_recv(struct eap2mschap *, u_int, RADIUS_PACKET *);
+static struct access_req
+               *eap_recv_mschap(struct eap2mschap *, struct access_req *,
+                   RADIUS_PACKET *, struct eap_chap *);
+static void     eap_resp_mschap(struct eap2mschap *, struct access_req *,
+                   RADIUS_PACKET *);
+static void     eap_send_reject(struct access_req *, RADIUS_PACKET *, u_int);
+static const char
+               *eap_chap_status_string(enum eap_chap_status);
+static const char
+               *hex_string(const char *, size_t, char *, size_t);
+static time_t   monotime(void);
+
+RB_PROTOTYPE_STATIC(access_reqt, access_req, tree, access_request_compar);
index 294f6a1..ec062b0 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.25 2024/07/14 15:27:57 yasuoka Exp $      */
+/*     $OpenBSD: parse.y,v 1.26 2024/07/14 16:09:23 yasuoka Exp $      */
 
 /*
  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -1036,6 +1036,7 @@ default_module_path(const char *name)
                const char *path;
        } module_paths[] = {
                { "bsdauth",    "/usr/libexec/radiusd/radiusd_bsdauth" },
+               { "eap2mschap", "/usr/libexec/radiusd/radiusd_eap2mschap" },
                { "file",       "/usr/libexec/radiusd/radiusd_file" },
                { "ipcp",       "/usr/libexec/radiusd/radiusd_ipcp" },
                { "radius",     "/usr/libexec/radiusd/radiusd_radius" },
index 605132f..e1f21cf 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: radiusd.conf.5,v 1.29 2024/07/10 05:40:08 jmc Exp $
+.\"    $OpenBSD: radiusd.conf.5,v 1.30 2024/07/14 16:09:23 yasuoka Exp $
 .\"
 .\" Copyright (c) 2014 Esdenera Networks GmbH
 .\" Copyright (c) 2014, 2023 Internet Initiative Japan Inc.
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: July 10 2024 $
+.Dd $Mdocdate: July 14 2024 $
 .Dt RADIUSD.CONF 5
 .Os
 .Sh NAME
@@ -74,15 +74,21 @@ each module can have configurations respectively and work independently.
 .Pp
 The following modules are predefined:
 .Bl -tag -width Ds
+.Nd provides conversion from EAP-MSCHAPv2 to MS-CHAPv2
 .It Do bsdauth Dc module
 The
 .Dq bsdauth
-module
-provides authentication from the local system's
+module provides authentication from the local system's
 .Xr authenticate 3
 interface.
 See
 .Xr radiusd_bsdauth 8 .
+.It Do eap2mschap Dc module
+The
+.Dq eap2mschap
+module provides conversion from EAP-MSCHAPv2 to MS-CHAPv2.
+See
+.Xr radiusd_eap2mschap 8 .
 .It Do ipcp Dc module
 The
 .Dq ipcp
@@ -218,6 +224,7 @@ account * to standard
 .Sh SEE ALSO
 .Xr radiusd 8 ,
 .Xr radiusd_bsdauth 8 ,
+.Xr radiusd_eap2mschap 8 ,
 .Xr radiusd_ipcp 8 ,
 .Xr radiusd_radius 8 ,
 .Xr radiusd_standard 8
diff --git a/usr.sbin/radiusd/radiusd_eap2mschap.8 b/usr.sbin/radiusd/radiusd_eap2mschap.8
new file mode 100644 (file)
index 0000000..398b421
--- /dev/null
@@ -0,0 +1,88 @@
+.\"    $OpenBSD: radiusd_eap2mschap.8,v 1.1 2024/07/14 16:09:23 yasuoka Exp $
+.\"
+.\" Copyright (c) 2024 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.
+.\"
+.\" The following requests are required for all man pages.
+.\"
+.Dd $Mdocdate: July 14 2024 $
+.Dt RADIUSD_EAP2MSCHAP 8
+.Os
+.Sh NAME
+.Nm radiusd_eap2mschap
+.Nd provides conversion from EAP-MSCHAPv2 to MSCHAPv2
+.Sh SYNOPSIS
+.Nm radiusd_eap2mschap
+.Sh DESCRIPTION
+The
+.Nm
+module is executed by
+.Xr radiusd 8
+as a
+.Dq authentication-filter
+module to provide conversion from EAP-MSCHAPv2 authentication messages to
+MS-CHAPv2 authentication messages.
+.Sh CONFIGURATIONS
+The
+.Nm
+module supports the following configuration key and value:
+.Pp
+.Bl -tag -width Ds
+.It Ic chap-name Ar name
+Specify the name in CHAP.
+The default is
+.Dq radiusd .
+.El
+.Sh FILES
+.Bl -tag -width "/usr/libexec/radiusd/radiusd_eap2mschap" -compact
+.It Pa /usr/libexec/radiusd/radiusd_eap2mschap
+.Dq eap2mschap
+module executable.
+.El
+.Sh EXAMPLES
+An example shows
+.Nm
+module provides an authentication server that supports EAP-MSCHAPv2.
+Although
+.Xr radiusd_file 8
+module itself doesn't support any EAP method,
+but by working with the
+.Nm
+module,
+it becomes possible to support EAP-MSCHAPv2.
+.Pp
+.Pa /etc/radiusd.conf :
+.Bd -literal -offset indent
+listen on 192.168.0.1
+client 192.168.0.0/24 {
+    secret SECRET
+}
+
+module file {
+    set path "/etc/npppd/npppd-users"
+}
+module eap2mschap
+
+authentication-filter * by eap2mschap
+authenticate * by file
+.Ed
+.Sh SEE ALSO
+.Xr authenticate 3 ,
+.Xr radiusd 8 ,
+.Xr radiusd.conf 5
+.Sh HISTORY
+The
+.Nm
+daemon first appeared in
+.Ox 7.6 .
diff --git a/usr.sbin/radiusd/radiusd_eap2mschap.c b/usr.sbin/radiusd/radiusd_eap2mschap.c
new file mode 100644 (file)
index 0000000..2e0b252
--- /dev/null
@@ -0,0 +1,781 @@
+/*     $OpenBSD: radiusd_eap2mschap.c,v 1.1 2024/07/14 16:09:23 yasuoka Exp $  */
+
+/*
+ * Copyright (c) 2024 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/cdefs.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/tree.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <err.h>
+#include <event.h>
+#include <radius.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "radiusd.h"
+#include "radiusd_module.h"
+#include "radius_subr.h"
+#include "log.h"
+
+#define EAP_TIMEOUT                    60
+
+#include "eap2mschap_local.h"
+
+int
+main(int argc, char *argv[])
+{
+       struct module_handlers handlers = {
+               .start = eap2mschap_start,
+               .config_set = eap2mschap_config_set,
+               .stop = eap2mschap_stop,
+               .access_request = eap2mschap_access_request,
+               .next_response = eap2mschap_next_response
+       };
+       struct eap2mschap       eap2mschap;
+
+       eap2mschap_init(&eap2mschap);
+       if ((eap2mschap.base = module_create(STDIN_FILENO, &eap2mschap,
+           &handlers)) == NULL)
+               err(1, "module_create");
+
+       module_drop_privilege(eap2mschap.base, 0);
+       setproctitle("[main]");
+
+       module_load(eap2mschap.base);
+       event_init();
+       log_init(0);
+
+       if (pledge("stdio", NULL) == -1)
+               err(1, "pledge");
+
+       module_start(eap2mschap.base);
+       event_loop(0);
+       module_destroy(eap2mschap.base);
+
+       exit(EXIT_SUCCESS);
+}
+
+void
+eap2mschap_init(struct eap2mschap *self)
+{
+       memset(self, 0, sizeof(struct eap2mschap));
+       RB_INIT(&self->eapt);
+       TAILQ_INIT(&self->reqq);
+}
+
+void
+eap2mschap_start(void *ctx)
+{
+       struct eap2mschap       *self = ctx;
+
+       if (self->chap_name[0] == '\0')
+               strlcpy(self->chap_name, "radiusd", sizeof(self->chap_name));
+
+       module_send_message(self->base, IMSG_OK, NULL);
+
+       evtimer_set(&self->ev_eapt, eap2mschap_on_eapt, self);
+}
+
+void
+eap2mschap_config_set(void *ctx, const char *name, int argc,
+    char * const * argv)
+{
+       struct eap2mschap       *self = ctx;
+       const char              *errmsg = "none";
+
+       if (strcmp(name, "chap-name") == 0) {
+               SYNTAX_ASSERT(argc == 1,
+                   "specify 1 argument for `chap-name'");
+               if (strlcpy(self->chap_name, argv[0], sizeof(self->chap_name))
+                   >= sizeof(self->chap_name)) {
+                       module_send_message(self->base, IMSG_NG,
+                           "chap-name is too long");
+                       return;
+               }
+       } else if (strcmp(name, "_debug") == 0)
+               log_init(1);
+       else if (strncmp(name, "_", 1) == 0)
+               /* ignore */;
+
+       module_send_message(self->base, IMSG_OK, NULL);
+       return;
+ syntax_error:
+       module_send_message(self->base, IMSG_NG, "%s", errmsg);
+}
+
+void
+eap2mschap_stop(void *ctx)
+{
+       struct eap2mschap       *self = ctx;
+       struct access_req       *req, *reqt;
+
+       evtimer_del(&self->ev_eapt);
+
+       RB_FOREACH_SAFE(req, access_reqt, &self->eapt, reqt)
+               access_request_free(req);
+       TAILQ_FOREACH_SAFE(req, &self->reqq, next, reqt)
+               access_request_free(req);
+}
+
+void
+eap2mschap_access_request(void *ctx, u_int q_id, const u_char *reqpkt,
+    size_t reqpktlen)
+{
+       struct eap2mschap       *self = ctx;
+       struct access_req       *req = NULL;
+       RADIUS_PACKET           *pkt;
+
+       if ((pkt = radius_convert_packet(reqpkt, reqpktlen)) == NULL) {
+               log_warn("%s: radius_convert_packet() failed", __func__);
+               goto on_fail;
+       }
+
+       if (radius_has_attr(pkt, RADIUS_TYPE_EAP_MESSAGE)) {
+               if ((req = eap_recv(self, q_id, pkt)) == NULL)
+                       return;
+               TAILQ_INSERT_TAIL(&self->reqq, req, next);
+               radius_delete_packet(pkt);
+               return;
+       }
+       if (pkt != NULL)
+               radius_delete_packet(pkt);
+       module_accsreq_next(self->base, q_id, reqpkt, reqpktlen);
+       return;
+ on_fail:
+       if (pkt != NULL)
+               radius_delete_packet(pkt);
+       module_accsreq_aborted(self->base, q_id);
+}
+
+void
+eap2mschap_next_response(void *ctx, u_int q_id, const u_char *respkt,
+    size_t respktlen)
+{
+       struct eap2mschap       *self = ctx;
+       struct access_req       *req = NULL;
+       RADIUS_PACKET           *pkt = NULL;
+
+       TAILQ_FOREACH(req, &self->reqq, next) {
+               if (req->q_id == q_id)
+                       break;
+       }
+       if (req == NULL) {
+               module_accsreq_answer(self->base, q_id, respkt, respktlen);
+               return;
+       }
+       TAILQ_REMOVE(&self->reqq, req, next);
+       if ((pkt = radius_convert_packet(respkt, respktlen)) == NULL) {
+               log_warn("%s: q=%u radius_convert_packet() failed", __func__,
+                   q_id);
+               goto on_fail;
+       }
+       eap_resp_mschap(self, req, pkt);
+       return;
+ on_fail:
+       if (pkt != NULL)
+               radius_delete_packet(pkt);
+       module_accsreq_aborted(self->base, q_id);
+}
+
+void
+eap2mschap_on_eapt(int fd, short ev, void *ctx)
+{
+       struct eap2mschap       *self = ctx;
+       time_t                   currtime;
+       struct access_req       *req, *reqt;
+
+       currtime = monotime();
+       RB_FOREACH_SAFE(req, access_reqt, &self->eapt, reqt) {
+               if (currtime - req->eap_time > EAP_TIMEOUT) {
+                       RB_REMOVE(access_reqt, &self->eapt, req);
+                       access_request_free(req);
+               }
+       }
+       TAILQ_FOREACH_SAFE(req, &self->reqq, next, reqt) {
+               if (currtime - req->eap_time > EAP_TIMEOUT) {
+                       TAILQ_REMOVE(&self->reqq, req, next);
+                       access_request_free(req);
+               }
+       }
+
+       eap2mschap_reset_eaptimer(self);
+}
+
+void
+eap2mschap_reset_eaptimer(struct eap2mschap *self)
+{
+       struct timeval   tv = { 4, 0 };
+
+       if ((!RB_EMPTY(&self->eapt) || !TAILQ_EMPTY(&self->reqq)) &&
+           evtimer_pending(&self->ev_eapt, NULL) == 0)
+               evtimer_add(&self->ev_eapt, &tv);
+}
+
+struct access_req *
+access_request_new(struct eap2mschap *self, u_int q_id)
+{
+       struct access_req       *req = NULL;
+
+       if ((req = calloc(1, sizeof(struct access_req))) == NULL) {
+               log_warn("%s: Out of memory", __func__);
+               return (NULL);
+       }
+       req->eap2mschap = self;
+       req->q_id = q_id;
+
+       EAP2MSCHAP_DBG("%s(%p)", __func__, req);
+       return (req);
+}
+
+void
+access_request_free(struct access_req *req)
+{
+       EAP2MSCHAP_DBG("%s(%p)", __func__, req);
+       free(req->username);
+       if (req->pkt != NULL)
+               radius_delete_packet(req->pkt);
+       free(req);
+}
+
+int
+access_request_compar(struct access_req *a, struct access_req *b)
+{
+       return (memcmp(a->state, b->state, sizeof(a->state)));
+}
+
+RB_GENERATE_STATIC(access_reqt, access_req, tree, access_request_compar);
+
+/***********************************************************************
+ * EAP related functions
+ * Specfication: RFC 3748 [MS-CHAP]
+ ***********************************************************************/
+struct access_req *
+eap_recv(struct eap2mschap *self, u_int q_id, RADIUS_PACKET *pkt)
+{
+       char                     buf[512], buf2[80];
+       size_t                   msgsiz = 0;
+       struct eap              *eap;
+       int                      namesiz;
+       struct access_req       *req = NULL;
+       char                     state[16];
+       size_t                   statesiz;
+       struct access_req        key;
+
+       /*
+        * Check the message authenticator.  OK if it exists since the check
+        * is done by radiusd(8).
+        */
+       if (!radius_has_attr(pkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR)) {
+               log_warnx("q=%u Received EAP message but has no message "
+                   "authenticator", q_id);
+               goto fail;
+       }
+
+       if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, NULL,
+           &msgsiz) != 0) {
+               log_warnx("q=%u Received EAP message is too big %zu", q_id,
+                   msgsiz);
+               goto fail;
+       }
+       msgsiz = sizeof(buf);
+       if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, buf,
+           &msgsiz) != 0) {
+               log_warnx("%s: radius_get_raw_attr_cat() failed", __func__);
+               goto fail;
+       }
+
+       eap = (struct eap *)buf;
+       if (msgsiz < offsetof(struct eap, value[1]) ||
+           ntohs(eap->length) > msgsiz) {
+               log_warnx("q=%u Received EAP message has wrong in size: "
+                   "received length %zu eap.length=%u", q_id, msgsiz,
+                   ntohs(eap->length));
+               goto fail;
+       }
+
+       EAP2MSCHAP_DBG("q=%u Received EAP code=%d type=%d", q_id,
+           (int)eap->code, (int)eap->value[0]);
+
+       if (eap->code != EAP_CODE_RESPONSE) {
+               log_warnx("q=%u Received EAP message has unexpected code %u",
+                   q_id, (unsigned)eap->code);
+               goto fail;
+       }
+
+       if (eap->value[0] == EAP_TYPE_IDENTITY) {
+               /*
+                * Handle EAP-Indentity
+                */
+               struct eap_mschap_challenge     *chall;
+               RADIUS_PACKET                   *radres = NULL;
+
+               if ((req = access_request_new(self, q_id)) == NULL)
+                       goto fail;
+               req->eap_time = monotime();
+               arc4random_buf(req->state, sizeof(req->state));
+               arc4random_buf(req->chall, sizeof(req->chall));
+
+               namesiz = ntohs(eap->length) - offsetof(struct eap, value[1]);
+               log_info("q=%u EAP state=%s EAP-Identity %.*s ",
+                   q_id, hex_string(req->state, sizeof(req->state),
+                   buf2, sizeof(buf2)), namesiz, eap->value + 1);
+               namesiz = strlen(self->chap_name);
+
+               /*
+                * Start MS-CHAP-V2
+                */
+               msgsiz = offsetof(struct eap_mschap_challenge,
+                   chap_name[namesiz]);
+               chall = (struct eap_mschap_challenge *)buf;
+               chall->eap.code = EAP_CODE_REQUEST;
+               chall->eap.id = ++req->eap_id;
+               chall->eap.length = htons(msgsiz);
+               chall->eap_type = EAP_TYPE_MSCHAPV2;
+               chall->chap.code = CHAP_CHALLENGE;
+               chall->chap.id = ++req->chap_id;
+               chall->chap.length = htons(msgsiz -
+                   offsetof(struct eap_mschap_challenge, chap));
+               chall->challsiz = sizeof(chall->chall);
+               memcpy(chall->chall, req->chall, sizeof(chall->chall));
+               memcpy(chall->chap_name, self->chap_name, namesiz);
+
+               if ((radres = radius_new_response_packet(
+                   RADIUS_CODE_ACCESS_CHALLENGE, pkt)) == NULL) {
+                       log_warn("%s: radius_new_response_packet() failed",
+                           __func__);
+                       goto fail;
+               }
+               radius_put_raw_attr(radres, RADIUS_TYPE_EAP_MESSAGE, buf,
+                   msgsiz);
+               radius_put_raw_attr(radres, RADIUS_TYPE_STATE, req->state,
+                   sizeof(req->state));
+               radius_put_uint32_attr(radres, RADIUS_TYPE_SESSION_TIMEOUT,
+                   EAP_TIMEOUT);
+               radius_put_message_authenticator(radres, "");   /* dummy */
+
+               req->eap_chap_status = EAP_CHAP_CHALLENGE_SENT;
+               module_accsreq_answer(self->base, req->q_id,
+                   radius_get_data(radres), radius_get_length(radres));
+
+               radius_delete_packet(pkt);
+               radius_delete_packet(radres);
+               RB_INSERT(access_reqt, &self->eapt, req);
+               eap2mschap_reset_eaptimer(self);
+
+               return (NULL);
+       }
+       /* Other than EAP-Identity */
+       statesiz = sizeof(state);
+       if (radius_get_raw_attr(pkt, RADIUS_TYPE_STATE, state, &statesiz) != 0)
+       {
+               log_info("q=%u received EAP message (type=%d) doesn't have a "
+                   "proper state attribute", q_id, eap->value[0]);
+               goto fail;
+       }
+
+       memcpy(key.state, state, statesiz);
+       if ((req = RB_FIND(access_reqt, &self->eapt, &key)) == NULL) {
+               log_info("q=%u received EAP message (type=%d) no context for "
+                   "the state=%s", q_id, eap->value[0], hex_string(state,
+                   statesiz, buf2, sizeof(buf2)));
+               goto fail;
+       }
+       req->eap_time = monotime();
+       req->q_id = q_id;
+       switch (eap->value[0]) {
+       case EAP_TYPE_NAK:
+               log_info("q=%u EAP state=%s NAK received", q_id,
+                   hex_string(state, statesiz, buf2, sizeof(buf2)));
+               eap_send_reject(req, pkt, q_id);
+               goto fail;
+       case EAP_TYPE_MSCHAPV2:
+               if (msgsiz < offsetof(struct eap, value[1])) {
+                       log_warnx(
+                           "q=%u EAP state=%s Received message has wrong in "
+                           "size for EAP-MS-CHAPV2: received length %zu "
+                           "eap.length=%u", q_id, hex_string(state, statesiz,
+                           buf2, sizeof(buf2)), msgsiz, ntohs(eap->length));
+                       goto fail;
+               }
+               req = eap_recv_mschap(self, req, pkt, (struct eap_chap *)eap);
+
+               break;
+       default:
+               log_warnx(
+                   "q=%u EAP state=%s EAP unknown type=%u receieved.",
+                   q_id, hex_string(state, statesiz, buf2, sizeof(buf2)),
+                   eap->value[0]);
+               goto fail;
+       }
+
+       return (req);
+ fail:
+       radius_delete_packet(pkt);
+       return (NULL);
+}
+
+struct access_req *
+eap_recv_mschap(struct eap2mschap *self, struct access_req *req,
+    RADIUS_PACKET *pkt, struct eap_chap *chap)
+{
+       size_t           eapsiz;
+       char             buf[80];
+
+       EAP2MSCHAP_DBG("%s(%p)", __func__, req);
+
+       eapsiz = ntohs(chap->eap.length);
+       switch (chap->chap.code) {
+       case CHAP_RESPONSE:
+           {
+               struct eap_mschap_response      *resp;
+               struct radius_ms_chap2_response  rr;
+               size_t                           namelen;
+               bool                             reset_username = false;
+
+               if (req->eap_chap_status != EAP_CHAP_CHALLENGE_SENT)
+                       goto failmsg;
+               resp = (struct eap_mschap_response *)chap;
+               if (eapsiz < sizeof(struct eap_mschap_response) ||
+                   htons(resp->chap.length) <
+                   sizeof(struct eap_mschap_response) -
+                   offsetof(struct eap_mschap_response, chap)) {
+                       log_warnx(
+                           "q=%u EAP state=%s Received EAP message has wrong "
+                           "in size: received length %zu eap.length=%u "
+                           "chap.length=%u valuesize=%u", req->q_id,
+                           hex_string(req->state, sizeof(req->state), buf,
+                           sizeof(buf)), eapsiz, ntohs(resp->eap.length),
+                           ntohs(resp->chap.length), resp->chap.value[9]);
+                       goto fail;
+               }
+               log_info("q=%u EAP state=%s Received "
+                   "CHAP-Response", req->q_id, hex_string(req->state,
+                   sizeof(req->state), buf, sizeof(buf)));
+
+               /* Unknown identity in EAP and got the username in CHAP */
+               namelen = ntohs(resp->chap.length) -
+                   (offsetof(struct eap_mschap_response, chap_name[0]) -
+                   offsetof(struct eap_mschap_response, chap));
+               if ((req->username == NULL || req->username[0] == '\0') &&
+                   namelen > 0) {
+                       free(req->username);
+                       if ((req->username = strndup(resp->chap_name, namelen))
+                           == NULL) {
+                               log_warn("%s: strndup", __func__);
+                               goto fail;
+                       }
+                       log_info("q=%u EAP state=%s username=%s", req->q_id,
+                           hex_string(req->state, sizeof(req->state), buf,
+                           sizeof(buf)), req->username);
+                       reset_username = true;
+               }
+
+               rr.ident = resp->chap.id;
+               rr.flags = resp->flags;
+               memcpy(rr.peerchall, resp->peerchall, sizeof(rr.peerchall));
+               memcpy(rr.reserved, resp->reserved, sizeof(rr.reserved));
+               memcpy(rr.ntresponse, resp->ntresponse, sizeof(rr.ntresponse));
+
+               radius_del_attr_all(pkt, RADIUS_TYPE_EAP_MESSAGE);
+               radius_del_attr_all(pkt, RADIUS_TYPE_STATE);
+
+               if (reset_username) {
+                       radius_del_attr_all(pkt, RADIUS_TYPE_USER_NAME);
+                       radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME,
+                           req->username);
+               }
+               radius_put_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
+                   RADIUS_VTYPE_MS_CHAP_CHALLENGE, req->chall,
+                   sizeof(req->chall));
+               radius_put_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
+                   RADIUS_VTYPE_MS_CHAP2_RESPONSE, &rr, sizeof(rr));
+               req->eap_chap_status = EAP_CHAP_CHALLENGE_SENT;
+               RB_REMOVE(access_reqt, &self->eapt, req);
+               module_accsreq_next(self->base, req->q_id, radius_get_data(pkt),
+                   radius_get_length(pkt));
+               return (req);
+           }
+       case CHAP_SUCCESS:
+           {
+               struct eap               eapres;
+               RADIUS_PACKET           *radres = NULL;
+               unsigned int             i;
+               uint8_t                  attr[256];
+               size_t                   attrlen;
+
+               /* Receiving Success-Reponse */
+               if (chap->eap.code != EAP_CODE_RESPONSE) {
+                       log_info("q=%u EAP state=%s Received "
+                           "CHAP-Success but EAP code is wrong %u", req->q_id,
+                           hex_string(req->state, sizeof(req->state), buf,
+                           sizeof(buf)), chap->eap.code);
+                       goto fail;
+               }
+               if (req->eap_chap_status == EAP_CHAP_SUCCESS_REQUEST_SENT)
+                       eapres.id = ++req->eap_id;
+               else if (req->eap_chap_status != EAP_CHAP_SUCCESS)
+                       goto failmsg;
+
+               req->eap_chap_status = EAP_CHAP_SUCCESS;
+               eapres.code = EAP_CODE_SUCCESS;
+               eapres.length = htons(sizeof(struct eap));
+
+               if ((radres = radius_new_response_packet(
+                   RADIUS_CODE_ACCESS_ACCEPT, pkt)) == NULL) {
+                       log_warn("%s: radius_new_response_packet failed",
+                           __func__);
+                       goto fail;
+               }
+
+               radius_put_raw_attr(radres, RADIUS_TYPE_EAP_MESSAGE, &eapres,
+                   sizeof(struct eap));
+               radius_put_raw_attr(radres, RADIUS_TYPE_STATE, req->state,
+                   sizeof(req->state));
+               /* notice authenticated username */
+               radius_put_string_attr(radres, RADIUS_TYPE_USER_NAME,
+                   req->username);
+               radius_put_message_authenticator(radres, "");   /* dummy */
+
+               /* restore attributes */
+               for (i = 0; i < nitems(preserve_attrs); i++) {
+                       attrlen = sizeof(attr);
+                       if (preserve_attrs[i].vendor == 0) {
+                               if (radius_get_raw_attr(req->pkt,
+                                   preserve_attrs[i].type, &attr, &attrlen)
+                                   == 0)
+                                       radius_put_raw_attr(radres,
+                                           preserve_attrs[i].type, &attr,
+                                           attrlen);
+                       } else {
+                               if (radius_get_vs_raw_attr(req->pkt,
+                                   preserve_attrs[i].vendor,
+                                   preserve_attrs[i].type, &attr, &attrlen)
+                                   == 0)
+                                       radius_put_vs_raw_attr(radres,
+                                           preserve_attrs[i].vendor,
+                                           preserve_attrs[i].type, &attr,
+                                           attrlen);
+                       }
+               }
+
+               module_accsreq_answer(self->base, req->q_id,
+                   radius_get_data(radres), radius_get_length(radres));
+
+               radius_delete_packet(pkt);
+               radius_delete_packet(radres);
+
+               return (NULL);
+           }
+               break;
+       }
+ failmsg:
+       log_warnx(
+           "q=%u EAP state=%s Can't handle the received EAP-CHAP message "
+           "(chap.code=%d) in EAP CHAP state=%s", req->q_id, hex_string(
+           req->state, sizeof(req->state), buf, sizeof(buf)), chap->chap.code,
+           eap_chap_status_string(req->eap_chap_status));
+ fail:
+       radius_delete_packet(pkt);
+       return (NULL);
+}
+
+void
+eap_resp_mschap(struct eap2mschap *self, struct access_req *req,
+    RADIUS_PACKET *pkt)
+{
+       bool                     accept = false;
+       int                      id, code;
+       char                     resp[256 + 1], buf[80];
+       size_t                   respsiz = 0, eapsiz;
+       struct {
+               struct eap_chap  chap;
+               char             space[256];
+       }                        eap;
+
+       code = radius_get_code(pkt);
+       id = radius_get_id(pkt);
+       EAP2MSCHAP_DBG("id=%d code=%d", id, code);
+       switch (code) {
+       case RADIUS_CODE_ACCESS_ACCEPT:
+       case RADIUS_CODE_ACCESS_REJECT:
+           {
+               RADIUS_PACKET           *respkt;
+
+               respsiz = sizeof(resp);
+               if (code == RADIUS_CODE_ACCESS_ACCEPT) {
+                       accept = true;
+                       if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
+                           RADIUS_VTYPE_MS_CHAP2_SUCCESS, &resp, &respsiz)
+                           != 0) {
+                               log_warnx("q=%u EAP state=%s no "
+                                   "MS-CHAP2-Success attribute", req->q_id,
+                                   hex_string(req->state, sizeof(req->state),
+                                   buf, sizeof(buf)));
+                               goto fail;
+                       }
+               } else {
+                       if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
+                           RADIUS_VTYPE_MS_CHAP_ERROR, &resp, &respsiz)
+                           != 0) {
+                               resp[0] = ++req->chap_id;
+                               snprintf(resp + 1, sizeof(resp) - 1,
+                                   "E=691 R=0 V=3");
+                               respsiz = 1 + strlen(resp + 1);
+                       }
+               }
+
+               /* Send EAP-CHAP "Success-Request" or "Failure-Request" */
+               if ((respkt = radius_new_request_packet(accept
+                   ? RADIUS_CODE_ACCESS_CHALLENGE
+                   : RADIUS_CODE_ACCESS_REJECT)) == NULL) {
+                       log_warn("%s: radius_new_request_packet", __func__);
+                       goto fail;
+               }
+               radius_set_id(respkt, id);
+
+               eapsiz  = offsetof(struct eap_chap, chap.value[respsiz - 1]);
+               eap.chap.eap.code = EAP_CODE_REQUEST;
+               eap.chap.eap.id = ++req->eap_id;
+               eap.chap.eap.length = htons(eapsiz);
+               eap.chap.eap_type = EAP_TYPE_MSCHAPV2;
+               eap.chap.chap.id = resp[0];
+               eap.chap.chap.length = htons(
+                   offsetof(struct chap, value[respsiz - 1]));
+               memcpy(eap.chap.chap.value, resp + 1, respsiz - 1);
+               if (accept)
+                       eap.chap.chap.code = CHAP_SUCCESS;
+               else
+                       eap.chap.chap.code = CHAP_FAILURE;
+
+               radius_put_raw_attr(respkt, RADIUS_TYPE_STATE, req->state,
+                   sizeof(req->state));
+               radius_put_raw_attr(respkt, RADIUS_TYPE_EAP_MESSAGE, &eap,
+                   eapsiz);
+
+               module_accsreq_answer(req->eap2mschap->base, req->q_id,
+                   radius_get_data(respkt), radius_get_length(respkt));
+               radius_delete_packet(respkt);
+               if (accept)
+                       req->eap_chap_status = EAP_CHAP_SUCCESS_REQUEST_SENT;
+               else
+                       req->eap_chap_status = EAP_CHAP_FAILURE_REQUEST_SENT;
+
+               RB_INSERT(access_reqt, &req->eap2mschap->eapt, req);
+               eap2mschap_reset_eaptimer(self);
+               req->pkt = pkt;
+               pkt = NULL;
+               break;
+           }
+       default:
+               log_warnx("q=%u Received unknown RADIUS packet code=%d",
+                   req->q_id, code);
+               goto fail;
+       }
+       return;
+ fail:
+       if (pkt != NULL)
+               radius_delete_packet(pkt);
+       module_accsreq_aborted(self->base, req->q_id);
+       access_request_free(req);
+       return;
+}
+
+void
+eap_send_reject(struct access_req *req, RADIUS_PACKET *reqp, u_int q_id)
+{
+       RADIUS_PACKET           *resp;
+       struct {
+               uint8_t          code;
+               uint8_t          id;
+               uint16_t         length;
+       } __packed               eap;
+
+       resp = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT, reqp);
+       if (resp == NULL) {
+               log_warn("%s: radius_new_response_packet() failed", __func__);
+               module_accsreq_aborted(req->eap2mschap->base, q_id);
+               return;
+       }
+       memset(&eap, 0, sizeof(eap));   /* just in case */
+       eap.code = EAP_CODE_REQUEST;
+       eap.id = ++req->eap_id;
+       eap.length = htons(sizeof(eap));
+       radius_put_raw_attr(resp, RADIUS_TYPE_EAP_MESSAGE, &eap,
+           ntohs(eap.length));
+       module_accsreq_answer(req->eap2mschap->base, q_id,
+           radius_get_data(resp), radius_get_length(resp));
+       radius_delete_packet(resp);
+}
+
+const char *
+eap_chap_status_string(enum eap_chap_status status)
+{
+       switch (status) {
+       case EAP_CHAP_NONE:             return "None";
+       case EAP_CHAP_CHALLENGE_SENT:   return "Challenge-Sent";
+       case EAP_CHAP_SUCCESS_REQUEST_SENT:
+                                       return "Success-Request-Sent";
+       case EAP_CHAP_FAILURE_REQUEST_SENT:
+                                       return "Failure-Request-Sent";
+       case EAP_CHAP_CHANGE_PASSWORD_SENT:
+                                       return "Change-Password-Sent";
+       case EAP_CHAP_SUCCESS:          return "Success";
+       case EAP_CHAP_FAILED:           return "Failed";
+       }
+       return "Error";
+}
+
+/***********************************************************************
+ * Miscellaneous functions
+ ***********************************************************************/
+const char *
+hex_string(const char *bytes, size_t byteslen, char *buf, size_t bufsiz)
+{
+       const char       hexstr[] = "0123456789abcdef";
+       unsigned         i, j;
+
+       for (i = 0, j = 0; i < byteslen && j + 2 < bufsiz; i++, j += 2) {
+               buf[j]     = hexstr[(bytes[i] & 0xf0) >> 4];
+               buf[j + 1] = hexstr[bytes[i] & 0xf];
+       }
+
+       if (i < byteslen)
+               return (NULL);
+       buf[j] = '\0';
+       return (buf);
+}
+
+time_t
+monotime(void)
+{
+       struct timespec         ts;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+               fatal("clock_gettime(CLOCK_MONOTONIC,) failed");
+
+       return (ts.tv_sec);
+}
diff --git a/usr.sbin/radiusd/radiusd_eap2mschap/Makefile b/usr.sbin/radiusd/radiusd_eap2mschap/Makefile
new file mode 100644 (file)
index 0000000..4267c7e
--- /dev/null
@@ -0,0 +1,10 @@
+#      $OpenBSD: Makefile,v 1.1 2024/07/14 16:09:23 yasuoka Exp $
+PROG=          radiusd_eap2mschap
+BINDIR=                /usr/libexec/radiusd
+SRCS=          radiusd_eap2mschap.c radiusd_module.c radius_subr.c log.c
+CFLAGS+=       -DUSE_LIBEVENT
+LDADD+=                -lradius -lutil -lcrypto -levent
+DPADD+=                ${LIBRADIUS} ${LIBUTIL} ${LIBCRYPTO} ${LIBEVENT}
+MAN=           radiusd_eap2mschap.8
+
+.include <bsd.prog.mk>