From b0e7e43d1394e2fbd3af515e44968f308c39f89d Mon Sep 17 00:00:00 2001 From: yasuoka Date: Sun, 14 Jul 2024 16:09:23 +0000 Subject: [PATCH] Add new radiusd_eap2mschap module. It provides conversions from EAP to MSCHAPv2. --- usr.sbin/radiusd/Makefile | 3 +- usr.sbin/radiusd/Makefile.inc | 4 +- usr.sbin/radiusd/eap2mschap_local.h | 205 +++++ usr.sbin/radiusd/parse.y | 3 +- usr.sbin/radiusd/radiusd.conf.5 | 15 +- usr.sbin/radiusd/radiusd_eap2mschap.8 | 88 +++ usr.sbin/radiusd/radiusd_eap2mschap.c | 781 +++++++++++++++++++ usr.sbin/radiusd/radiusd_eap2mschap/Makefile | 10 + 8 files changed, 1101 insertions(+), 8 deletions(-) create mode 100644 usr.sbin/radiusd/eap2mschap_local.h create mode 100644 usr.sbin/radiusd/radiusd_eap2mschap.8 create mode 100644 usr.sbin/radiusd/radiusd_eap2mschap.c create mode 100644 usr.sbin/radiusd/radiusd_eap2mschap/Makefile diff --git a/usr.sbin/radiusd/Makefile b/usr.sbin/radiusd/Makefile index 1fc21891f9e..b21d20323ea 100644 --- a/usr.sbin/radiusd/Makefile +++ b/usr.sbin/radiusd/Makefile @@ -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 diff --git a/usr.sbin/radiusd/Makefile.inc b/usr.sbin/radiusd/Makefile.inc index d5d2462d791..087857ebb22 100644 --- a/usr.sbin/radiusd/Makefile.inc +++ b/usr.sbin/radiusd/Makefile.inc @@ -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 index 00000000000..ff761f27524 --- /dev/null +++ b/usr.sbin/radiusd/eap2mschap_local.h @@ -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); diff --git a/usr.sbin/radiusd/parse.y b/usr.sbin/radiusd/parse.y index 294f6a1815c..ec062b0f16c 100644 --- a/usr.sbin/radiusd/parse.y +++ b/usr.sbin/radiusd/parse.y @@ -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 @@ -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" }, diff --git a/usr.sbin/radiusd/radiusd.conf.5 b/usr.sbin/radiusd/radiusd.conf.5 index 605132fbb19..e1f21cf00b1 100644 --- a/usr.sbin/radiusd/radiusd.conf.5 +++ b/usr.sbin/radiusd/radiusd.conf.5 @@ -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 index 00000000000..398b4215385 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_eap2mschap.8 @@ -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 index 00000000000..2e0b252e5c3 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_eap2mschap.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000000..4267c7e8fc0 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_eap2mschap/Makefile @@ -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 -- 2.20.1