#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);
event_loop(0);
event_base_free(NULL);
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)
;
else {
module_send_message(self->base, IMSG_NG,
"Unknown config parameter `%s'", name);
return;
}
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) {
RB_REMOVE(access_reqt, &self->eapt, req);
access_request_free(req);
}
TAILQ_FOREACH_SAFE(req, &self->reqq, next, reqt) {
TAILQ_REMOVE(&self->reqq, req, next);
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);
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;
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) {
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);
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, "");
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);
}
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)));
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;
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));
radius_put_string_attr(radres, RADIUS_TYPE_USER_NAME,
req->username);
radius_put_message_authenticator(radres, "");
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);
}
}
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));
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";
}
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);
}