1/*	$OpenBSD: radiusd_eap2mschap.c,v 1.3 2024/08/16 09:52:16 yasuoka Exp $	*/
2
3/*
4 * Copyright (c) 2024 Internet Initiative Japan Inc.
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#include <sys/types.h>
19#include <sys/cdefs.h>
20#include <sys/queue.h>
21#include <sys/time.h>
22#include <sys/tree.h>
23#include <arpa/inet.h>
24
25#include <assert.h>
26#include <err.h>
27#include <event.h>
28#include <radius.h>
29#include <stdbool.h>
30#include <stddef.h>
31#include <stdint.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <syslog.h>
36#include <time.h>
37#include <unistd.h>
38
39#include "radiusd.h"
40#include "radiusd_module.h"
41#include "radius_subr.h"
42#include "log.h"
43
44#define EAP_TIMEOUT			60
45
46#include "eap2mschap_local.h"
47
48int
49main(int argc, char *argv[])
50{
51	struct module_handlers handlers = {
52		.start = eap2mschap_start,
53		.config_set = eap2mschap_config_set,
54		.stop = eap2mschap_stop,
55		.access_request = eap2mschap_access_request,
56		.next_response = eap2mschap_next_response
57	};
58	struct eap2mschap	eap2mschap;
59
60	eap2mschap_init(&eap2mschap);
61	if ((eap2mschap.base = module_create(STDIN_FILENO, &eap2mschap,
62	    &handlers)) == NULL)
63		err(1, "module_create");
64
65	module_drop_privilege(eap2mschap.base, 0);
66	setproctitle("[main]");
67
68	module_load(eap2mschap.base);
69	event_init();
70	log_init(0);
71
72	if (pledge("stdio", NULL) == -1)
73		err(1, "pledge");
74
75	module_start(eap2mschap.base);
76	event_loop(0);
77
78	module_destroy(eap2mschap.base);
79
80	event_loop(0);
81	event_base_free(NULL);
82
83	exit(EXIT_SUCCESS);
84}
85
86void
87eap2mschap_init(struct eap2mschap *self)
88{
89	memset(self, 0, sizeof(struct eap2mschap));
90	RB_INIT(&self->eapt);
91	TAILQ_INIT(&self->reqq);
92}
93
94void
95eap2mschap_start(void *ctx)
96{
97	struct eap2mschap	*self = ctx;
98
99	if (self->chap_name[0] == '\0')
100		strlcpy(self->chap_name, "radiusd", sizeof(self->chap_name));
101
102	module_send_message(self->base, IMSG_OK, NULL);
103
104	evtimer_set(&self->ev_eapt, eap2mschap_on_eapt, self);
105}
106
107void
108eap2mschap_config_set(void *ctx, const char *name, int argc,
109    char * const * argv)
110{
111	struct eap2mschap	*self = ctx;
112	const char		*errmsg = "none";
113
114	if (strcmp(name, "chap-name") == 0) {
115		SYNTAX_ASSERT(argc == 1,
116		    "specify 1 argument for `chap-name'");
117		if (strlcpy(self->chap_name, argv[0], sizeof(self->chap_name))
118		    >= sizeof(self->chap_name)) {
119			module_send_message(self->base, IMSG_NG,
120			    "chap-name is too long");
121			return;
122		}
123	} else if (strcmp(name, "_debug") == 0)
124		log_init(1);
125	else if (strncmp(name, "_", 1) == 0)
126		/* ignore all internal messages */;
127	else {
128		module_send_message(self->base, IMSG_NG,
129		    "Unknown config parameter `%s'", name);
130		return;
131	}
132
133	module_send_message(self->base, IMSG_OK, NULL);
134	return;
135 syntax_error:
136	module_send_message(self->base, IMSG_NG, "%s", errmsg);
137}
138
139void
140eap2mschap_stop(void *ctx)
141{
142	struct eap2mschap	*self = ctx;
143	struct access_req	*req, *reqt;
144
145	evtimer_del(&self->ev_eapt);
146
147	RB_FOREACH_SAFE(req, access_reqt, &self->eapt, reqt) {
148		RB_REMOVE(access_reqt, &self->eapt, req);
149		access_request_free(req);
150	}
151	TAILQ_FOREACH_SAFE(req, &self->reqq, next, reqt) {
152		TAILQ_REMOVE(&self->reqq, req, next);
153		access_request_free(req);
154	}
155}
156
157void
158eap2mschap_access_request(void *ctx, u_int q_id, const u_char *reqpkt,
159    size_t reqpktlen)
160{
161	struct eap2mschap	*self = ctx;
162	struct access_req	*req = NULL;
163	RADIUS_PACKET		*pkt;
164
165	if ((pkt = radius_convert_packet(reqpkt, reqpktlen)) == NULL) {
166		log_warn("%s: radius_convert_packet() failed", __func__);
167		goto on_fail;
168	}
169
170	if (radius_has_attr(pkt, RADIUS_TYPE_EAP_MESSAGE)) {
171		if ((req = eap_recv(self, q_id, pkt)) == NULL)
172			return;
173		TAILQ_INSERT_TAIL(&self->reqq, req, next);
174		radius_delete_packet(pkt);
175		return;
176	}
177	if (pkt != NULL)
178		radius_delete_packet(pkt);
179	module_accsreq_next(self->base, q_id, reqpkt, reqpktlen);
180	return;
181 on_fail:
182	if (pkt != NULL)
183		radius_delete_packet(pkt);
184	module_accsreq_aborted(self->base, q_id);
185}
186
187void
188eap2mschap_next_response(void *ctx, u_int q_id, const u_char *respkt,
189    size_t respktlen)
190{
191	struct eap2mschap	*self = ctx;
192	struct access_req	*req = NULL;
193	RADIUS_PACKET		*pkt = NULL;
194
195	TAILQ_FOREACH(req, &self->reqq, next) {
196		if (req->q_id == q_id)
197			break;
198	}
199	if (req == NULL) {
200		module_accsreq_answer(self->base, q_id, respkt, respktlen);
201		return;
202	}
203	TAILQ_REMOVE(&self->reqq, req, next);
204	if ((pkt = radius_convert_packet(respkt, respktlen)) == NULL) {
205		log_warn("%s: q=%u radius_convert_packet() failed", __func__,
206		    q_id);
207		goto on_fail;
208	}
209	eap_resp_mschap(self, req, pkt);
210	return;
211 on_fail:
212	if (pkt != NULL)
213		radius_delete_packet(pkt);
214	module_accsreq_aborted(self->base, q_id);
215}
216
217void
218eap2mschap_on_eapt(int fd, short ev, void *ctx)
219{
220	struct eap2mschap	*self = ctx;
221	time_t			 currtime;
222	struct access_req	*req, *reqt;
223
224	currtime = monotime();
225	RB_FOREACH_SAFE(req, access_reqt, &self->eapt, reqt) {
226		if (currtime - req->eap_time > EAP_TIMEOUT) {
227			RB_REMOVE(access_reqt, &self->eapt, req);
228			access_request_free(req);
229		}
230	}
231	TAILQ_FOREACH_SAFE(req, &self->reqq, next, reqt) {
232		if (currtime - req->eap_time > EAP_TIMEOUT) {
233			TAILQ_REMOVE(&self->reqq, req, next);
234			access_request_free(req);
235		}
236	}
237
238	eap2mschap_reset_eaptimer(self);
239}
240
241void
242eap2mschap_reset_eaptimer(struct eap2mschap *self)
243{
244	struct timeval	 tv = { 4, 0 };
245
246	if ((!RB_EMPTY(&self->eapt) || !TAILQ_EMPTY(&self->reqq)) &&
247	    evtimer_pending(&self->ev_eapt, NULL) == 0)
248		evtimer_add(&self->ev_eapt, &tv);
249}
250
251struct access_req *
252access_request_new(struct eap2mschap *self, u_int q_id)
253{
254	struct access_req	*req = NULL;
255
256	if ((req = calloc(1, sizeof(struct access_req))) == NULL) {
257		log_warn("%s: Out of memory", __func__);
258		return (NULL);
259	}
260	req->eap2mschap = self;
261	req->q_id = q_id;
262
263	EAP2MSCHAP_DBG("%s(%p)", __func__, req);
264	return (req);
265}
266
267void
268access_request_free(struct access_req *req)
269{
270	EAP2MSCHAP_DBG("%s(%p)", __func__, req);
271	free(req->username);
272	if (req->pkt != NULL)
273		radius_delete_packet(req->pkt);
274	free(req);
275}
276
277int
278access_request_compar(struct access_req *a, struct access_req *b)
279{
280	return (memcmp(a->state, b->state, sizeof(a->state)));
281}
282
283RB_GENERATE_STATIC(access_reqt, access_req, tree, access_request_compar);
284
285/***********************************************************************
286 * EAP related functions
287 * Specfication: RFC 3748 [MS-CHAP]
288 ***********************************************************************/
289struct access_req *
290eap_recv(struct eap2mschap *self, u_int q_id, RADIUS_PACKET *pkt)
291{
292	char			 buf[512], buf2[80];
293	size_t			 msgsiz = 0;
294	struct eap		*eap;
295	int			 namesiz;
296	struct access_req	*req = NULL;
297	char			 state[16];
298	size_t			 statesiz;
299	struct access_req	 key;
300
301	/*
302	 * Check the message authenticator.  OK if it exists since the check
303	 * is done by radiusd(8).
304	 */
305	if (!radius_has_attr(pkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR)) {
306		log_warnx("q=%u Received EAP message but has no message "
307		    "authenticator", q_id);
308		goto fail;
309	}
310
311	if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, NULL,
312	    &msgsiz) != 0) {
313		log_warnx("q=%u Received EAP message is too big %zu", q_id,
314		    msgsiz);
315		goto fail;
316	}
317	msgsiz = sizeof(buf);
318	if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, buf,
319	    &msgsiz) != 0) {
320		log_warnx("%s: radius_get_raw_attr_cat() failed", __func__);
321		goto fail;
322	}
323
324	eap = (struct eap *)buf;
325	if (msgsiz < offsetof(struct eap, value[1]) ||
326	    ntohs(eap->length) > msgsiz) {
327		log_warnx("q=%u Received EAP message has wrong in size: "
328		    "received length %zu eap.length=%u", q_id, msgsiz,
329		    ntohs(eap->length));
330		goto fail;
331	}
332
333	EAP2MSCHAP_DBG("q=%u Received EAP code=%d type=%d", q_id,
334	    (int)eap->code, (int)eap->value[0]);
335
336	if (eap->code != EAP_CODE_RESPONSE) {
337		log_warnx("q=%u Received EAP message has unexpected code %u",
338		    q_id, (unsigned)eap->code);
339		goto fail;
340	}
341
342	if (eap->value[0] == EAP_TYPE_IDENTITY) {
343		/*
344		 * Handle EAP-Indentity
345		 */
346		struct eap_mschap_challenge	*chall;
347		RADIUS_PACKET			*radres = NULL;
348
349		if ((req = access_request_new(self, q_id)) == NULL)
350			goto fail;
351		req->eap_time = monotime();
352		arc4random_buf(req->state, sizeof(req->state));
353		arc4random_buf(req->chall, sizeof(req->chall));
354
355		namesiz = ntohs(eap->length) - offsetof(struct eap, value[1]);
356		log_info("q=%u EAP state=%s EAP-Identity %.*s ",
357		    q_id, hex_string(req->state, sizeof(req->state),
358		    buf2, sizeof(buf2)), namesiz, eap->value + 1);
359		namesiz = strlen(self->chap_name);
360
361		/*
362		 * Start MS-CHAP-V2
363		 */
364		msgsiz = offsetof(struct eap_mschap_challenge,
365		    chap_name[namesiz]);
366		chall = (struct eap_mschap_challenge *)buf;
367		chall->eap.code = EAP_CODE_REQUEST;
368		chall->eap.id = ++req->eap_id;
369		chall->eap.length = htons(msgsiz);
370		chall->eap_type = EAP_TYPE_MSCHAPV2;
371		chall->chap.code = CHAP_CHALLENGE;
372		chall->chap.id = ++req->chap_id;
373		chall->chap.length = htons(msgsiz -
374		    offsetof(struct eap_mschap_challenge, chap));
375		chall->challsiz = sizeof(chall->chall);
376		memcpy(chall->chall, req->chall, sizeof(chall->chall));
377		memcpy(chall->chap_name, self->chap_name, namesiz);
378
379		if ((radres = radius_new_response_packet(
380		    RADIUS_CODE_ACCESS_CHALLENGE, pkt)) == NULL) {
381			log_warn("%s: radius_new_response_packet() failed",
382			    __func__);
383			goto fail;
384		}
385		radius_put_raw_attr(radres, RADIUS_TYPE_EAP_MESSAGE, buf,
386		    msgsiz);
387		radius_put_raw_attr(radres, RADIUS_TYPE_STATE, req->state,
388		    sizeof(req->state));
389		radius_put_uint32_attr(radres, RADIUS_TYPE_SESSION_TIMEOUT,
390		    EAP_TIMEOUT);
391		radius_put_message_authenticator(radres, "");	/* dummy */
392
393		req->eap_chap_status = EAP_CHAP_CHALLENGE_SENT;
394		module_accsreq_answer(self->base, req->q_id,
395		    radius_get_data(radres), radius_get_length(radres));
396
397		radius_delete_packet(pkt);
398		radius_delete_packet(radres);
399		RB_INSERT(access_reqt, &self->eapt, req);
400		eap2mschap_reset_eaptimer(self);
401
402		return (NULL);
403	}
404	/* Other than EAP-Identity */
405	statesiz = sizeof(state);
406	if (radius_get_raw_attr(pkt, RADIUS_TYPE_STATE, state, &statesiz) != 0)
407	{
408		log_info("q=%u received EAP message (type=%d) doesn't have a "
409		    "proper state attribute", q_id, eap->value[0]);
410		goto fail;
411	}
412
413	memcpy(key.state, state, statesiz);
414	if ((req = RB_FIND(access_reqt, &self->eapt, &key)) == NULL) {
415		log_info("q=%u received EAP message (type=%d) no context for "
416		    "the state=%s", q_id, eap->value[0], hex_string(state,
417		    statesiz, buf2, sizeof(buf2)));
418		goto fail;
419	}
420	req->eap_time = monotime();
421	req->q_id = q_id;
422	switch (eap->value[0]) {
423	case EAP_TYPE_NAK:
424		log_info("q=%u EAP state=%s NAK received", q_id,
425		    hex_string(state, statesiz, buf2, sizeof(buf2)));
426		eap_send_reject(req, pkt, q_id);
427		goto fail;
428	case EAP_TYPE_MSCHAPV2:
429		if (msgsiz < offsetof(struct eap, value[1])) {
430			log_warnx(
431			    "q=%u EAP state=%s Received message has wrong in "
432			    "size for EAP-MS-CHAPV2: received length %zu "
433			    "eap.length=%u", q_id, hex_string(state, statesiz,
434			    buf2, sizeof(buf2)), msgsiz, ntohs(eap->length));
435			goto fail;
436		}
437		req = eap_recv_mschap(self, req, pkt, (struct eap_chap *)eap);
438
439		break;
440	default:
441		log_warnx(
442		    "q=%u EAP state=%s EAP unknown type=%u receieved.",
443		    q_id, hex_string(state, statesiz, buf2, sizeof(buf2)),
444		    eap->value[0]);
445		goto fail;
446	}
447
448	return (req);
449 fail:
450	radius_delete_packet(pkt);
451	return (NULL);
452}
453
454struct access_req *
455eap_recv_mschap(struct eap2mschap *self, struct access_req *req,
456    RADIUS_PACKET *pkt, struct eap_chap *chap)
457{
458	size_t		 eapsiz;
459	char		 buf[80];
460
461	EAP2MSCHAP_DBG("%s(%p)", __func__, req);
462
463	eapsiz = ntohs(chap->eap.length);
464	switch (chap->chap.code) {
465	case CHAP_RESPONSE:
466	    {
467		struct eap_mschap_response	*resp;
468		struct radius_ms_chap2_response	 rr;
469		size_t				 namelen;
470		bool				 reset_username = false;
471
472		if (req->eap_chap_status != EAP_CHAP_CHALLENGE_SENT)
473			goto failmsg;
474		resp = (struct eap_mschap_response *)chap;
475		if (eapsiz < sizeof(struct eap_mschap_response) ||
476		    htons(resp->chap.length) <
477		    sizeof(struct eap_mschap_response) -
478		    offsetof(struct eap_mschap_response, chap)) {
479			log_warnx(
480			    "q=%u EAP state=%s Received EAP message has wrong "
481			    "in size: received length %zu eap.length=%u "
482			    "chap.length=%u valuesize=%u", req->q_id,
483			    hex_string(req->state, sizeof(req->state), buf,
484			    sizeof(buf)), eapsiz, ntohs(resp->eap.length),
485			    ntohs(resp->chap.length), resp->chap.value[9]);
486			goto fail;
487		}
488		log_info("q=%u EAP state=%s Received "
489		    "CHAP-Response", req->q_id, hex_string(req->state,
490		    sizeof(req->state), buf, sizeof(buf)));
491
492		/* Unknown identity in EAP and got the username in CHAP */
493		namelen = ntohs(resp->chap.length) -
494		    (offsetof(struct eap_mschap_response, chap_name[0]) -
495		    offsetof(struct eap_mschap_response, chap));
496		if ((req->username == NULL || req->username[0] == '\0') &&
497		    namelen > 0) {
498			free(req->username);
499			if ((req->username = strndup(resp->chap_name, namelen))
500			    == NULL) {
501				log_warn("%s: strndup", __func__);
502				goto fail;
503			}
504			log_info("q=%u EAP state=%s username=%s", req->q_id,
505			    hex_string(req->state, sizeof(req->state), buf,
506			    sizeof(buf)), req->username);
507			reset_username = true;
508		}
509
510		rr.ident = resp->chap.id;
511		rr.flags = resp->flags;
512		memcpy(rr.peerchall, resp->peerchall, sizeof(rr.peerchall));
513		memcpy(rr.reserved, resp->reserved, sizeof(rr.reserved));
514		memcpy(rr.ntresponse, resp->ntresponse, sizeof(rr.ntresponse));
515
516		radius_del_attr_all(pkt, RADIUS_TYPE_EAP_MESSAGE);
517		radius_del_attr_all(pkt, RADIUS_TYPE_STATE);
518
519		if (reset_username) {
520			radius_del_attr_all(pkt, RADIUS_TYPE_USER_NAME);
521			radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME,
522			    req->username);
523		}
524		radius_put_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
525		    RADIUS_VTYPE_MS_CHAP_CHALLENGE, req->chall,
526		    sizeof(req->chall));
527		radius_put_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
528		    RADIUS_VTYPE_MS_CHAP2_RESPONSE, &rr, sizeof(rr));
529		req->eap_chap_status = EAP_CHAP_CHALLENGE_SENT;
530		RB_REMOVE(access_reqt, &self->eapt, req);
531		module_accsreq_next(self->base, req->q_id, radius_get_data(pkt),
532		    radius_get_length(pkt));
533		return (req);
534	    }
535	case CHAP_SUCCESS:
536	    {
537		struct eap		 eapres;
538		RADIUS_PACKET		*radres = NULL;
539		unsigned int		 i;
540		uint8_t			 attr[256];
541		size_t			 attrlen;
542
543		/* Receiving Success-Reponse */
544		if (chap->eap.code != EAP_CODE_RESPONSE) {
545			log_info("q=%u EAP state=%s Received "
546			    "CHAP-Success but EAP code is wrong %u", req->q_id,
547			    hex_string(req->state, sizeof(req->state), buf,
548			    sizeof(buf)), chap->eap.code);
549			goto fail;
550		}
551		if (req->eap_chap_status == EAP_CHAP_SUCCESS_REQUEST_SENT)
552			eapres.id = ++req->eap_id;
553		else if (req->eap_chap_status != EAP_CHAP_SUCCESS)
554			goto failmsg;
555
556		req->eap_chap_status = EAP_CHAP_SUCCESS;
557		eapres.code = EAP_CODE_SUCCESS;
558		eapres.length = htons(sizeof(struct eap));
559
560		if ((radres = radius_new_response_packet(
561		    RADIUS_CODE_ACCESS_ACCEPT, pkt)) == NULL) {
562			log_warn("%s: radius_new_response_packet failed",
563			    __func__);
564			goto fail;
565		}
566
567		radius_put_raw_attr(radres, RADIUS_TYPE_EAP_MESSAGE, &eapres,
568		    sizeof(struct eap));
569		radius_put_raw_attr(radres, RADIUS_TYPE_STATE, req->state,
570		    sizeof(req->state));
571		/* notice authenticated username */
572		radius_put_string_attr(radres, RADIUS_TYPE_USER_NAME,
573		    req->username);
574		radius_put_message_authenticator(radres, "");	/* dummy */
575
576		/* restore attributes */
577		for (i = 0; i < nitems(preserve_attrs); i++) {
578			attrlen = sizeof(attr);
579			if (preserve_attrs[i].vendor == 0) {
580				if (radius_get_raw_attr(req->pkt,
581				    preserve_attrs[i].type, &attr, &attrlen)
582				    == 0)
583					radius_put_raw_attr(radres,
584					    preserve_attrs[i].type, &attr,
585					    attrlen);
586			} else {
587				if (radius_get_vs_raw_attr(req->pkt,
588				    preserve_attrs[i].vendor,
589				    preserve_attrs[i].type, &attr, &attrlen)
590				    == 0)
591					radius_put_vs_raw_attr(radres,
592					    preserve_attrs[i].vendor,
593					    preserve_attrs[i].type, &attr,
594					    attrlen);
595			}
596		}
597
598		module_accsreq_answer(self->base, req->q_id,
599		    radius_get_data(radres), radius_get_length(radres));
600
601		radius_delete_packet(pkt);
602		radius_delete_packet(radres);
603
604		return (NULL);
605	    }
606		break;
607	}
608 failmsg:
609	log_warnx(
610	    "q=%u EAP state=%s Can't handle the received EAP-CHAP message "
611	    "(chap.code=%d) in EAP CHAP state=%s", req->q_id, hex_string(
612	    req->state, sizeof(req->state), buf, sizeof(buf)), chap->chap.code,
613	    eap_chap_status_string(req->eap_chap_status));
614 fail:
615	radius_delete_packet(pkt);
616	return (NULL);
617}
618
619void
620eap_resp_mschap(struct eap2mschap *self, struct access_req *req,
621    RADIUS_PACKET *pkt)
622{
623	bool			 accept = false;
624	int			 id, code;
625	char			 resp[256 + 1], buf[80];
626	size_t			 respsiz = 0, eapsiz;
627	struct {
628		struct eap_chap	 chap;
629		char		 space[256];
630	}			 eap;
631
632	code = radius_get_code(pkt);
633	id = radius_get_id(pkt);
634	EAP2MSCHAP_DBG("id=%d code=%d", id, code);
635	switch (code) {
636	case RADIUS_CODE_ACCESS_ACCEPT:
637	case RADIUS_CODE_ACCESS_REJECT:
638	    {
639		RADIUS_PACKET		*respkt;
640
641		respsiz = sizeof(resp);
642		if (code == RADIUS_CODE_ACCESS_ACCEPT) {
643			accept = true;
644			if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
645			    RADIUS_VTYPE_MS_CHAP2_SUCCESS, &resp, &respsiz)
646			    != 0) {
647				log_warnx("q=%u EAP state=%s no "
648				    "MS-CHAP2-Success attribute", req->q_id,
649				    hex_string(req->state, sizeof(req->state),
650				    buf, sizeof(buf)));
651				goto fail;
652			}
653		} else {
654			if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
655			    RADIUS_VTYPE_MS_CHAP_ERROR, &resp, &respsiz)
656			    != 0) {
657				resp[0] = ++req->chap_id;
658				snprintf(resp + 1, sizeof(resp) - 1,
659				    "E=691 R=0 V=3");
660				respsiz = 1 + strlen(resp + 1);
661			}
662		}
663
664		/* Send EAP-CHAP "Success-Request" or "Failure-Request" */
665		if ((respkt = radius_new_request_packet(accept
666		    ? RADIUS_CODE_ACCESS_CHALLENGE
667		    : RADIUS_CODE_ACCESS_REJECT)) == NULL) {
668			log_warn("%s: radius_new_request_packet", __func__);
669			goto fail;
670		}
671		radius_set_id(respkt, id);
672
673		eapsiz  = offsetof(struct eap_chap, chap.value[respsiz - 1]);
674		eap.chap.eap.code = EAP_CODE_REQUEST;
675		eap.chap.eap.id = ++req->eap_id;
676		eap.chap.eap.length = htons(eapsiz);
677		eap.chap.eap_type = EAP_TYPE_MSCHAPV2;
678		eap.chap.chap.id = resp[0];
679		eap.chap.chap.length = htons(
680		    offsetof(struct chap, value[respsiz - 1]));
681		memcpy(eap.chap.chap.value, resp + 1, respsiz - 1);
682		if (accept)
683			eap.chap.chap.code = CHAP_SUCCESS;
684		else
685			eap.chap.chap.code = CHAP_FAILURE;
686
687		radius_put_raw_attr(respkt, RADIUS_TYPE_STATE, req->state,
688		    sizeof(req->state));
689		radius_put_raw_attr(respkt, RADIUS_TYPE_EAP_MESSAGE, &eap,
690		    eapsiz);
691
692		module_accsreq_answer(req->eap2mschap->base, req->q_id,
693		    radius_get_data(respkt), radius_get_length(respkt));
694		radius_delete_packet(respkt);
695		if (accept)
696			req->eap_chap_status = EAP_CHAP_SUCCESS_REQUEST_SENT;
697		else
698			req->eap_chap_status = EAP_CHAP_FAILURE_REQUEST_SENT;
699
700		RB_INSERT(access_reqt, &req->eap2mschap->eapt, req);
701		eap2mschap_reset_eaptimer(self);
702		req->pkt = pkt;
703		pkt = NULL;
704		break;
705	    }
706	default:
707		log_warnx("q=%u Received unknown RADIUS packet code=%d",
708		    req->q_id, code);
709		goto fail;
710	}
711	return;
712 fail:
713	if (pkt != NULL)
714		radius_delete_packet(pkt);
715	module_accsreq_aborted(self->base, req->q_id);
716	access_request_free(req);
717	return;
718}
719
720void
721eap_send_reject(struct access_req *req, RADIUS_PACKET *reqp, u_int q_id)
722{
723	RADIUS_PACKET		*resp;
724	struct {
725		uint8_t		 code;
726		uint8_t		 id;
727		uint16_t	 length;
728	} __packed		 eap;
729
730	resp = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT, reqp);
731	if (resp == NULL) {
732		log_warn("%s: radius_new_response_packet() failed", __func__);
733		module_accsreq_aborted(req->eap2mschap->base, q_id);
734		return;
735	}
736	memset(&eap, 0, sizeof(eap));	/* just in case */
737	eap.code = EAP_CODE_REQUEST;
738	eap.id = ++req->eap_id;
739	eap.length = htons(sizeof(eap));
740	radius_put_raw_attr(resp, RADIUS_TYPE_EAP_MESSAGE, &eap,
741	    ntohs(eap.length));
742	module_accsreq_answer(req->eap2mschap->base, q_id,
743	    radius_get_data(resp), radius_get_length(resp));
744	radius_delete_packet(resp);
745}
746
747const char *
748eap_chap_status_string(enum eap_chap_status status)
749{
750	switch (status) {
751	case EAP_CHAP_NONE:		return "None";
752	case EAP_CHAP_CHALLENGE_SENT:	return "Challenge-Sent";
753	case EAP_CHAP_SUCCESS_REQUEST_SENT:
754					return "Success-Request-Sent";
755	case EAP_CHAP_FAILURE_REQUEST_SENT:
756					return "Failure-Request-Sent";
757	case EAP_CHAP_CHANGE_PASSWORD_SENT:
758					return "Change-Password-Sent";
759	case EAP_CHAP_SUCCESS:		return "Success";
760	case EAP_CHAP_FAILED:		return "Failed";
761	}
762	return "Error";
763}
764
765/***********************************************************************
766 * Miscellaneous functions
767 ***********************************************************************/
768const char *
769hex_string(const char *bytes, size_t byteslen, char *buf, size_t bufsiz)
770{
771	const char	 hexstr[] = "0123456789abcdef";
772	unsigned	 i, j;
773
774	for (i = 0, j = 0; i < byteslen && j + 2 < bufsiz; i++, j += 2) {
775		buf[j]     = hexstr[(bytes[i] & 0xf0) >> 4];
776		buf[j + 1] = hexstr[bytes[i] & 0xf];
777	}
778
779	if (i < byteslen)
780		return (NULL);
781	buf[j] = '\0';
782	return (buf);
783}
784
785time_t
786monotime(void)
787{
788	struct timespec		ts;
789
790	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
791		fatal("clock_gettime(CLOCK_MONOTONIC,) failed");
792
793	return (ts.tv_sec);
794}
795