1 /*	$OpenBSD: radiusd_eap2mschap.c,v 1.4 2024/09/15 05:31:23 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 
48 int
main(int argc,char * argv[])49 main(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 
86 void
eap2mschap_init(struct eap2mschap * self)87 eap2mschap_init(struct eap2mschap *self)
88 {
89 	memset(self, 0, sizeof(struct eap2mschap));
90 	RB_INIT(&self->eapt);
91 	TAILQ_INIT(&self->reqq);
92 }
93 
94 void
eap2mschap_start(void * ctx)95 eap2mschap_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 
107 void
eap2mschap_config_set(void * ctx,const char * name,int argc,char * const * argv)108 eap2mschap_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 
139 void
eap2mschap_stop(void * ctx)140 eap2mschap_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 
157 void
eap2mschap_access_request(void * ctx,u_int q_id,const u_char * reqpkt,size_t reqpktlen)158 eap2mschap_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 
187 void
eap2mschap_next_response(void * ctx,u_int q_id,const u_char * respkt,size_t respktlen)188 eap2mschap_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 
217 void
eap2mschap_on_eapt(int fd,short ev,void * ctx)218 eap2mschap_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 
241 void
eap2mschap_reset_eaptimer(struct eap2mschap * self)242 eap2mschap_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 
251 struct access_req *
access_request_new(struct eap2mschap * self,u_int q_id)252 access_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 
267 void
access_request_free(struct access_req * req)268 access_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 
277 int
access_request_compar(struct access_req * a,struct access_req * b)278 access_request_compar(struct access_req *a, struct access_req *b)
279 {
280 	return (memcmp(a->state, b->state, sizeof(a->state)));
281 }
282 
283 RB_GENERATE_STATIC(access_reqt, access_req, tree, access_request_compar);
284 
285 /***********************************************************************
286  * EAP related functions
287  * Specfication: RFC 3748 [MS-CHAP]
288  ***********************************************************************/
289 struct access_req *
eap_recv(struct eap2mschap * self,u_int q_id,RADIUS_PACKET * pkt)290 eap_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("q=%u EAP state=%s Received message has "
431 			    "wrong in size for EAP-MS-CHAPV2: received length "
432 			    "%zu eap.length=%u", q_id,
433 			    hex_string(state, statesiz, buf2, sizeof(buf2)),
434 			    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("q=%u EAP state=%s EAP unknown type=%u receieved.",
442 		    q_id, hex_string(state, statesiz, buf2, sizeof(buf2)),
443 		    eap->value[0]);
444 		goto fail;
445 	}
446 
447 	return (req);
448  fail:
449 	radius_delete_packet(pkt);
450 	return (NULL);
451 }
452 
453 struct access_req *
eap_recv_mschap(struct eap2mschap * self,struct access_req * req,RADIUS_PACKET * pkt,struct eap_chap * chap)454 eap_recv_mschap(struct eap2mschap *self, struct access_req *req,
455     RADIUS_PACKET *pkt, struct eap_chap *chap)
456 {
457 	size_t		 eapsiz;
458 	char		 buf[80];
459 
460 	EAP2MSCHAP_DBG("%s(%p)", __func__, req);
461 
462 	eapsiz = ntohs(chap->eap.length);
463 	switch (chap->chap.code) {
464 	case CHAP_RESPONSE:
465 	    {
466 		struct eap_mschap_response	*resp;
467 		struct radius_ms_chap2_response	 rr;
468 		size_t				 namelen;
469 		bool				 reset_username = false;
470 
471 		if (req->eap_chap_status != EAP_CHAP_CHALLENGE_SENT)
472 			goto failmsg;
473 		resp = (struct eap_mschap_response *)chap;
474 		if (eapsiz < sizeof(struct eap_mschap_response) ||
475 		    htons(resp->chap.length) <
476 		    sizeof(struct eap_mschap_response) -
477 		    offsetof(struct eap_mschap_response, chap)) {
478 			log_warnx("q=%u EAP state=%s Received EAP message has "
479 			    "wrong in size: received length %zu eap.length=%u "
480 			    "chap.length=%u valuesize=%u", req->q_id,
481 			    hex_string(req->state, sizeof(req->state), buf,
482 			    sizeof(buf)), eapsiz, ntohs(resp->eap.length),
483 			    ntohs(resp->chap.length), resp->chap.value[9]);
484 			goto fail;
485 		}
486 		log_info("q=%u EAP state=%s Received "
487 		    "CHAP-Response", req->q_id, hex_string(req->state,
488 		    sizeof(req->state), buf, sizeof(buf)));
489 
490 		/* Unknown identity in EAP and got the username in CHAP */
491 		namelen = ntohs(resp->chap.length) -
492 		    (offsetof(struct eap_mschap_response, chap_name[0]) -
493 		    offsetof(struct eap_mschap_response, chap));
494 		if ((req->username == NULL || req->username[0] == '\0') &&
495 		    namelen > 0) {
496 			free(req->username);
497 			if ((req->username = strndup(resp->chap_name, namelen))
498 			    == NULL) {
499 				log_warn("%s: strndup", __func__);
500 				goto fail;
501 			}
502 			log_info("q=%u EAP state=%s username=%s", req->q_id,
503 			    hex_string(req->state, sizeof(req->state), buf,
504 			    sizeof(buf)), req->username);
505 			reset_username = true;
506 		}
507 
508 		rr.ident = resp->chap.id;
509 		rr.flags = resp->flags;
510 		memcpy(rr.peerchall, resp->peerchall, sizeof(rr.peerchall));
511 		memcpy(rr.reserved, resp->reserved, sizeof(rr.reserved));
512 		memcpy(rr.ntresponse, resp->ntresponse, sizeof(rr.ntresponse));
513 
514 		radius_del_attr_all(pkt, RADIUS_TYPE_EAP_MESSAGE);
515 		radius_del_attr_all(pkt, RADIUS_TYPE_STATE);
516 
517 		if (reset_username) {
518 			radius_del_attr_all(pkt, RADIUS_TYPE_USER_NAME);
519 			radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME,
520 			    req->username);
521 		}
522 		radius_put_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
523 		    RADIUS_VTYPE_MS_CHAP_CHALLENGE, req->chall,
524 		    sizeof(req->chall));
525 		radius_put_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
526 		    RADIUS_VTYPE_MS_CHAP2_RESPONSE, &rr, sizeof(rr));
527 		req->eap_chap_status = EAP_CHAP_CHALLENGE_SENT;
528 		RB_REMOVE(access_reqt, &self->eapt, req);
529 		module_accsreq_next(self->base, req->q_id, radius_get_data(pkt),
530 		    radius_get_length(pkt));
531 		return (req);
532 	    }
533 	case CHAP_SUCCESS:
534 	    {
535 		struct eap		 eapres;
536 		RADIUS_PACKET		*radres = NULL;
537 		unsigned int		 i;
538 		uint8_t			 attr[256];
539 		size_t			 attrlen;
540 
541 		/* Receiving Success-Reponse */
542 		if (chap->eap.code != EAP_CODE_RESPONSE) {
543 			log_info("q=%u EAP state=%s Received "
544 			    "CHAP-Success but EAP code is wrong %u", req->q_id,
545 			    hex_string(req->state, sizeof(req->state), buf,
546 			    sizeof(buf)), chap->eap.code);
547 			goto fail;
548 		}
549 		if (req->eap_chap_status == EAP_CHAP_SUCCESS_REQUEST_SENT)
550 			eapres.id = ++req->eap_id;
551 		else if (req->eap_chap_status != EAP_CHAP_SUCCESS)
552 			goto failmsg;
553 
554 		req->eap_chap_status = EAP_CHAP_SUCCESS;
555 		eapres.code = EAP_CODE_SUCCESS;
556 		eapres.length = htons(sizeof(struct eap));
557 
558 		if ((radres = radius_new_response_packet(
559 		    RADIUS_CODE_ACCESS_ACCEPT, pkt)) == NULL) {
560 			log_warn("%s: radius_new_response_packet failed",
561 			    __func__);
562 			goto fail;
563 		}
564 
565 		radius_put_raw_attr(radres, RADIUS_TYPE_EAP_MESSAGE, &eapres,
566 		    sizeof(struct eap));
567 		radius_put_raw_attr(radres, RADIUS_TYPE_STATE, req->state,
568 		    sizeof(req->state));
569 		/* notice authenticated username */
570 		radius_put_string_attr(radres, RADIUS_TYPE_USER_NAME,
571 		    req->username);
572 		radius_put_message_authenticator(radres, "");	/* dummy */
573 
574 		/* restore attributes */
575 		for (i = 0; i < nitems(preserve_attrs); i++) {
576 			attrlen = sizeof(attr);
577 			if (preserve_attrs[i].vendor == 0) {
578 				if (radius_get_raw_attr(req->pkt,
579 				    preserve_attrs[i].type, &attr, &attrlen)
580 				    == 0)
581 					radius_put_raw_attr(radres,
582 					    preserve_attrs[i].type, &attr,
583 					    attrlen);
584 			} else {
585 				if (radius_get_vs_raw_attr(req->pkt,
586 				    preserve_attrs[i].vendor,
587 				    preserve_attrs[i].type, &attr, &attrlen)
588 				    == 0)
589 					radius_put_vs_raw_attr(radres,
590 					    preserve_attrs[i].vendor,
591 					    preserve_attrs[i].type, &attr,
592 					    attrlen);
593 			}
594 		}
595 
596 		module_accsreq_answer(self->base, req->q_id,
597 		    radius_get_data(radres), radius_get_length(radres));
598 
599 		radius_delete_packet(pkt);
600 		radius_delete_packet(radres);
601 
602 		return (NULL);
603 	    }
604 		break;
605 	}
606  failmsg:
607 	log_warnx(
608 	    "q=%u EAP state=%s Can't handle the received EAP-CHAP message "
609 	    "(chap.code=%d) in EAP CHAP state=%s", req->q_id, hex_string(
610 	    req->state, sizeof(req->state), buf, sizeof(buf)), chap->chap.code,
611 	    eap_chap_status_string(req->eap_chap_status));
612  fail:
613 	radius_delete_packet(pkt);
614 	return (NULL);
615 }
616 
617 void
eap_resp_mschap(struct eap2mschap * self,struct access_req * req,RADIUS_PACKET * pkt)618 eap_resp_mschap(struct eap2mschap *self, struct access_req *req,
619     RADIUS_PACKET *pkt)
620 {
621 	bool			 accept = false;
622 	int			 id, code;
623 	char			 resp[256 + 1], buf[80];
624 	size_t			 respsiz = 0, eapsiz;
625 	struct {
626 		struct eap_chap	 chap;
627 		char		 space[256];
628 	}			 eap;
629 
630 	code = radius_get_code(pkt);
631 	id = radius_get_id(pkt);
632 	EAP2MSCHAP_DBG("id=%d code=%d", id, code);
633 	switch (code) {
634 	case RADIUS_CODE_ACCESS_ACCEPT:
635 	case RADIUS_CODE_ACCESS_REJECT:
636 	    {
637 		RADIUS_PACKET		*respkt;
638 
639 		respsiz = sizeof(resp);
640 		if (code == RADIUS_CODE_ACCESS_ACCEPT) {
641 			accept = true;
642 			if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
643 			    RADIUS_VTYPE_MS_CHAP2_SUCCESS, &resp, &respsiz)
644 			    != 0) {
645 				log_warnx("q=%u EAP state=%s no "
646 				    "MS-CHAP2-Success attribute", req->q_id,
647 				    hex_string(req->state, sizeof(req->state),
648 				    buf, sizeof(buf)));
649 				goto fail;
650 			}
651 		} else {
652 			if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT,
653 			    RADIUS_VTYPE_MS_CHAP_ERROR, &resp, &respsiz)
654 			    != 0) {
655 				resp[0] = ++req->chap_id;
656 				snprintf(resp + 1, sizeof(resp) - 1,
657 				    "E=691 R=0 V=3");
658 				respsiz = 1 + strlen(resp + 1);
659 			}
660 		}
661 
662 		/* Send EAP-CHAP "Success-Request" or "Failure-Request" */
663 		if ((respkt = radius_new_request_packet(accept
664 		    ? RADIUS_CODE_ACCESS_CHALLENGE
665 		    : RADIUS_CODE_ACCESS_REJECT)) == NULL) {
666 			log_warn("%s: radius_new_request_packet", __func__);
667 			goto fail;
668 		}
669 		radius_set_id(respkt, id);
670 
671 		eapsiz  = offsetof(struct eap_chap, chap.value[respsiz - 1]);
672 		eap.chap.eap.code = EAP_CODE_REQUEST;
673 		eap.chap.eap.id = ++req->eap_id;
674 		eap.chap.eap.length = htons(eapsiz);
675 		eap.chap.eap_type = EAP_TYPE_MSCHAPV2;
676 		eap.chap.chap.id = resp[0];
677 		eap.chap.chap.length = htons(
678 		    offsetof(struct chap, value[respsiz - 1]));
679 		memcpy(eap.chap.chap.value, resp + 1, respsiz - 1);
680 		if (accept)
681 			eap.chap.chap.code = CHAP_SUCCESS;
682 		else
683 			eap.chap.chap.code = CHAP_FAILURE;
684 
685 		radius_put_raw_attr(respkt, RADIUS_TYPE_STATE, req->state,
686 		    sizeof(req->state));
687 		radius_put_raw_attr(respkt, RADIUS_TYPE_EAP_MESSAGE, &eap,
688 		    eapsiz);
689 
690 		module_accsreq_answer(req->eap2mschap->base, req->q_id,
691 		    radius_get_data(respkt), radius_get_length(respkt));
692 		radius_delete_packet(respkt);
693 		if (accept)
694 			req->eap_chap_status = EAP_CHAP_SUCCESS_REQUEST_SENT;
695 		else
696 			req->eap_chap_status = EAP_CHAP_FAILURE_REQUEST_SENT;
697 
698 		RB_INSERT(access_reqt, &req->eap2mschap->eapt, req);
699 		eap2mschap_reset_eaptimer(self);
700 		req->pkt = pkt;
701 		pkt = NULL;
702 		break;
703 	    }
704 	default:
705 		log_warnx("q=%u Received unknown RADIUS packet code=%d",
706 		    req->q_id, code);
707 		goto fail;
708 	}
709 	return;
710  fail:
711 	if (pkt != NULL)
712 		radius_delete_packet(pkt);
713 	module_accsreq_aborted(self->base, req->q_id);
714 	access_request_free(req);
715 	return;
716 }
717 
718 void
eap_send_reject(struct access_req * req,RADIUS_PACKET * reqp,u_int q_id)719 eap_send_reject(struct access_req *req, RADIUS_PACKET *reqp, u_int q_id)
720 {
721 	RADIUS_PACKET		*resp;
722 	struct {
723 		uint8_t		 code;
724 		uint8_t		 id;
725 		uint16_t	 length;
726 	} __packed		 eap;
727 
728 	resp = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT, reqp);
729 	if (resp == NULL) {
730 		log_warn("%s: radius_new_response_packet() failed", __func__);
731 		module_accsreq_aborted(req->eap2mschap->base, q_id);
732 		return;
733 	}
734 	memset(&eap, 0, sizeof(eap));	/* just in case */
735 	eap.code = EAP_CODE_REQUEST;
736 	eap.id = ++req->eap_id;
737 	eap.length = htons(sizeof(eap));
738 	radius_put_raw_attr(resp, RADIUS_TYPE_EAP_MESSAGE, &eap,
739 	    ntohs(eap.length));
740 	module_accsreq_answer(req->eap2mschap->base, q_id,
741 	    radius_get_data(resp), radius_get_length(resp));
742 	radius_delete_packet(resp);
743 }
744 
745 const char *
eap_chap_status_string(enum eap_chap_status status)746 eap_chap_status_string(enum eap_chap_status status)
747 {
748 	switch (status) {
749 	case EAP_CHAP_NONE:		return "None";
750 	case EAP_CHAP_CHALLENGE_SENT:	return "Challenge-Sent";
751 	case EAP_CHAP_SUCCESS_REQUEST_SENT:
752 					return "Success-Request-Sent";
753 	case EAP_CHAP_FAILURE_REQUEST_SENT:
754 					return "Failure-Request-Sent";
755 	case EAP_CHAP_CHANGE_PASSWORD_SENT:
756 					return "Change-Password-Sent";
757 	case EAP_CHAP_SUCCESS:		return "Success";
758 	case EAP_CHAP_FAILED:		return "Failed";
759 	}
760 	return "Error";
761 }
762 
763 /***********************************************************************
764  * Miscellaneous functions
765  ***********************************************************************/
766 const char *
hex_string(const char * bytes,size_t byteslen,char * buf,size_t bufsiz)767 hex_string(const char *bytes, size_t byteslen, char *buf, size_t bufsiz)
768 {
769 	const char	 hexstr[] = "0123456789abcdef";
770 	unsigned	 i, j;
771 
772 	for (i = 0, j = 0; i < byteslen && j + 2 < bufsiz; i++, j += 2) {
773 		buf[j]     = hexstr[(bytes[i] & 0xf0) >> 4];
774 		buf[j + 1] = hexstr[bytes[i] & 0xf];
775 	}
776 
777 	if (i < byteslen)
778 		return (NULL);
779 	buf[j] = '\0';
780 	return (buf);
781 }
782 
783 time_t
monotime(void)784 monotime(void)
785 {
786 	struct timespec		ts;
787 
788 	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
789 		fatal("clock_gettime(CLOCK_MONOTONIC,) failed");
790 
791 	return (ts.tv_sec);
792 }
793