1/*	$OpenBSD: radiusd_file.c,v 1.5 2024/07/18 22:40:09 yasuoka Exp $	*/
2
3/*
4 * Copyright (c) 2024 YASUOKA Masahiko <yasuoka@yasuoka.net>
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
19#include <sys/types.h>
20#include <sys/cdefs.h>
21#include <sys/queue.h>
22#include <sys/socket.h>
23#include <sys/wait.h>
24#include <netinet/in.h>
25
26#include <err.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <imsg.h>
30#include <limits.h>
31#include <md5.h>
32#include <radius.h>
33#include <stddef.h>
34#include <stdint.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include "chap_ms.h"
40#include "imsg_subr.h"
41#include "log.h"
42#include "radiusd.h"
43#include "radiusd_module.h"
44
45struct module_file_params {
46	int			 debug;
47	char			 path[PATH_MAX];
48};
49
50struct module_file {
51	struct module_base	*base;
52	struct imsgbuf		 ibuf;
53	struct module_file_params
54				 params;
55};
56
57struct module_file_userinfo {
58	struct in_addr		frame_ip_address;
59	char			password[0];
60};
61
62/* IPC between priv and main */
63enum {
64	IMSG_RADIUSD_FILE_OK = 1000,
65	IMSG_RADIUSD_FILE_NG,
66	IMSG_RADIUSD_FILE_PARAMS,
67	IMSG_RADIUSD_FILE_USERINFO
68};
69
70static void	 parent_dispatch_main(struct module_file_params *,
71		    struct imsgbuf *, struct imsg *);
72static void	 module_file_main(void) __dead;
73static pid_t	 start_child(char *, int);
74static void	 module_file_config_set(void *, const char *, int,
75		    char * const *);
76static void	 module_file_start(void *);
77static void	 module_file_access_request(void *, u_int, const u_char *,
78		    size_t);
79static void	 auth_pap(struct module_file *, u_int, RADIUS_PACKET *, char *,
80		    struct module_file_userinfo *);
81static void	 auth_md5chap(struct module_file *, u_int, RADIUS_PACKET *,
82		    char *, struct module_file_userinfo *);
83static void	 auth_mschapv2(struct module_file *, u_int, RADIUS_PACKET *,
84		    char *, struct module_file_userinfo *);
85static void	 auth_reject(struct module_file *, u_int, RADIUS_PACKET *,
86		    char *, struct module_file_userinfo *);
87
88static struct module_handlers module_file_handlers = {
89	.access_request		= module_file_access_request,
90	.config_set		= module_file_config_set,
91	.start			= module_file_start
92};
93
94int
95main(int argc, char *argv[])
96{
97	int				 ch, pairsock[2], status;
98	pid_t				 pid;
99	char				*saved_argv0;
100	struct imsgbuf			 ibuf;
101	struct imsg			 imsg;
102	ssize_t				 n;
103	size_t				 datalen;
104	struct module_file_params	*paramsp, params;
105	char				 pathdb[PATH_MAX];
106
107	while ((ch = getopt(argc, argv, "M")) != -1)
108		switch (ch) {
109		case 'M':
110			module_file_main();
111			/* not reached */
112			break;
113		}
114	saved_argv0 = argv[0];
115
116	argc -= optind;
117	argv += optind;
118
119	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC,
120	    pairsock) == -1)
121		err(EXIT_FAILURE, "socketpair");
122
123	log_init(0);
124
125	pid = start_child(saved_argv0, pairsock[1]);
126
127	/* Privileged process */
128	if (pledge("stdio rpath unveil", NULL) == -1)
129		err(EXIT_FAILURE, "pledge");
130	setproctitle("[priv]");
131	imsg_init(&ibuf, pairsock[0]);
132
133	/* Receive parameters from the main process. */
134	if (imsg_sync_read(&ibuf, 2000) <= 0 ||
135	    (n = imsg_get(&ibuf, &imsg)) <= 0)
136		exit(EXIT_FAILURE);
137	if (imsg.hdr.type != IMSG_RADIUSD_FILE_PARAMS)
138		err(EXIT_FAILURE, "Receieved unknown message type %d",
139		    imsg.hdr.type);
140	datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
141	if (datalen < sizeof(params))
142		err(EXIT_FAILURE, "Receieved IMSG_RADIUSD_FILE_PARAMS "
143		    "message is wrong size");
144	paramsp = imsg.data;
145	if (paramsp->path[0] != '\0') {
146		strlcpy(pathdb, paramsp->path, sizeof(pathdb));
147		strlcat(pathdb, ".db", sizeof(pathdb));
148		if (unveil(paramsp->path, "r") == -1 ||
149		    unveil(pathdb, "r") == -1)
150			err(EXIT_FAILURE, "unveil");
151	}
152	if (paramsp->debug)
153		log_init(1);
154
155	if (unveil(NULL, NULL) == -1)
156		err(EXIT_FAILURE, "unveil");
157
158	memcpy(&params, paramsp, sizeof(params));
159
160	for (;;) {
161		if ((n = imsg_read(&ibuf)) <= 0 && errno != EAGAIN)
162			break;
163		for (;;) {
164			if ((n = imsg_get(&ibuf, &imsg)) == -1)
165				break;
166			if (n == 0)
167				break;
168			parent_dispatch_main(&params, &ibuf, &imsg);
169			imsg_free(&imsg);
170			imsg_flush(&ibuf);
171		}
172		imsg_flush(&ibuf);
173	}
174	imsg_clear(&ibuf);
175
176	while (waitpid(pid, &status, 0) == -1) {
177		if (errno != EINTR)
178			break;
179	}
180	exit(WEXITSTATUS(status));
181}
182
183void
184parent_dispatch_main(struct module_file_params *params, struct imsgbuf *ibuf,
185    struct imsg *imsg)
186{
187	size_t				 datalen, entsz, passz;
188	const char			*username;
189	char				*buf, *db[2], *str;
190	int				 ret;
191	struct module_file_userinfo	*ent;
192
193	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
194	switch (imsg->hdr.type) {
195	case IMSG_RADIUSD_FILE_USERINFO:
196		if (datalen == 0 ||
197		    *((char *)imsg->data + datalen - 1) != '\0') {
198			log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO "
199			    "is wrong", __func__);
200			goto on_error;
201		}
202		username = imsg->data;
203		db[0] = params->path;
204		db[1] = NULL;
205		if ((ret = cgetent(&buf, db, username)) < 0) {
206			log_info("user `%s' is not configured", username);
207			goto on_error;
208		}
209		if ((ret = cgetstr(buf, "password", &str)) < 0) {
210			log_info("password for `%s' is not configured",
211			    username);
212			goto on_error;
213		}
214		passz = strlen(str) + 1;
215		entsz = offsetof(struct module_file_userinfo, password[passz]);
216		if ((ent = calloc(1, entsz)) == NULL) {
217			log_warn("%s; calloc", __func__);
218			goto on_error;
219		}
220		strlcpy(ent->password, str, passz);
221		imsg_compose(ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1,
222		    ent, entsz);
223		freezero(ent, entsz);
224		break;
225	}
226	return;
227 on_error:
228	imsg_compose(ibuf, IMSG_RADIUSD_FILE_NG, 0, -1, -1, NULL, 0);
229}
230
231/* main process */
232void
233module_file_main(void)
234{
235	struct module_file	 module_file;
236
237	setproctitle("[main]");
238
239	memset(&module_file, 0, sizeof(module_file));
240	if ((module_file.base = module_create(STDIN_FILENO, &module_file,
241	    &module_file_handlers)) == NULL)
242		err(1, "Could not create a module instance");
243
244	module_drop_privilege(module_file.base, 0);
245
246	module_load(module_file.base);
247	imsg_init(&module_file.ibuf, 3);
248
249	if (pledge("stdio", NULL) == -1)
250		err(EXIT_FAILURE, "pledge");
251	while (module_run(module_file.base) == 0)
252		;
253
254	module_destroy(module_file.base);
255
256	exit(0);
257}
258
259pid_t
260start_child(char *argv0, int fd)
261{
262	char *argv[5];
263	int argc = 0;
264	pid_t pid;
265
266	switch (pid = fork()) {
267	case -1:
268		fatal("cannot fork");
269	case 0:
270		break;
271	default:
272		close(fd);
273		return (pid);
274	}
275
276	if (fd != 3) {
277		if (dup2(fd, 3) == -1)
278			fatal("cannot setup imsg fd");
279	} else if (fcntl(fd, F_SETFD, 0) == -1)
280		fatal("cannot setup imsg fd");
281
282	argv[argc++] = argv0;
283	argv[argc++] = "-M";	/* main proc */
284	argv[argc++] = NULL;
285	execvp(argv0, argv);
286	fatal("execvp");
287}
288
289void
290module_file_config_set(void *ctx, const char *name, int valc,
291    char * const * valv)
292{
293	struct module_file	*module = ctx;
294	char			*errmsg;
295
296	if (strcmp(name, "path") == 0) {
297		SYNTAX_ASSERT(valc == 1, "`path' must have a argument");
298		if (strlcpy(module->params.path, valv[0], sizeof(
299		    module->params.path)) >= sizeof(module->params.path)) {
300			module_send_message(module->base, IMSG_NG,
301			    "`path' is too long");
302			return;
303		}
304		module_send_message(module->base, IMSG_OK, NULL);
305	} else if (strcmp(name, "_debug") == 0) {
306		log_init(1);
307		module->params.debug = 1;
308		module_send_message(module->base, IMSG_OK, NULL);
309	} else if (strncmp(name, "_", 1) == 0)
310		/* ignore all internal messages */
311		module_send_message(module->base, IMSG_OK, NULL);
312	else
313		module_send_message(module->base, IMSG_NG,
314		    "Unknown config parameter `%s'", name);
315	return;
316 syntax_error:
317	module_send_message(module->base, IMSG_NG, "%s", errmsg);
318	return;
319}
320
321void
322module_file_start(void *ctx)
323{
324	struct module_file	*module = ctx;
325
326	/* Send parameters to parent */
327	if (module->params.path[0] == '\0') {
328		module_send_message(module->base, IMSG_NG,
329		    "`path' is not configured");
330		return;
331	}
332	imsg_compose(&module->ibuf, IMSG_RADIUSD_FILE_PARAMS, 0, -1, -1,
333	    &module->params, sizeof(module->params));
334	imsg_flush(&module->ibuf);
335
336	module_send_message(module->base, IMSG_OK, NULL);
337}
338
339void
340module_file_access_request(void *ctx, u_int query_id, const u_char *pkt,
341    size_t pktlen)
342{
343	size_t				 datalen;
344	struct module_file		*self = ctx;
345	RADIUS_PACKET			*radpkt = NULL;
346	char				 username[256];
347	ssize_t				 n;
348	struct imsg			 imsg;
349	struct module_file_userinfo	*ent;
350
351	memset(&imsg, 0, sizeof(imsg));
352
353	if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
354		log_warn("%s: radius_convert_packet()", __func__);
355		goto out;
356	}
357	radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username,
358	    sizeof(username));
359
360	imsg_compose(&self->ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1,
361	    username, strlen(username) + 1);
362	imsg_flush(&self->ibuf);
363	if ((n = imsg_read(&self->ibuf)) == -1 || n == 0) {
364		log_warn("%s: imsg_read()", __func__);
365		goto out;
366	}
367	if ((n = imsg_get(&self->ibuf, &imsg)) <= 0) {
368		log_warn("%s: imsg_get()", __func__);
369		goto out;
370	}
371
372	datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
373	if (imsg.hdr.type == IMSG_RADIUSD_FILE_USERINFO) {
374		if (datalen <= offsetof(struct module_file_userinfo,
375		    password[0])) {
376			log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO is "
377			    "invalid", __func__);
378			goto out;
379		}
380		ent = imsg.data;
381		if (radius_has_attr(radpkt, RADIUS_TYPE_USER_PASSWORD))
382			auth_pap(self, query_id, radpkt, username, ent);
383		else if (radius_has_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD))
384			auth_md5chap(self, query_id, radpkt, username, ent);
385		else if (radius_has_vs_attr(radpkt, RADIUS_VENDOR_MICROSOFT,
386		    RADIUS_VTYPE_MS_CHAP2_RESPONSE))
387			auth_mschapv2(self, query_id, radpkt, username, ent);
388		else
389			auth_reject(self, query_id, radpkt, username, ent);
390	} else
391		auth_reject(self, query_id, radpkt, username, NULL);
392 out:
393	if (radpkt != NULL)
394		radius_delete_packet(radpkt);
395	imsg_free(&imsg);
396	return;
397}
398
399void
400auth_pap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
401    char *username, struct module_file_userinfo *ent)
402{
403	RADIUS_PACKET	*respkt = NULL;
404	char		 pass[256];
405	int		 ret;
406
407	if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_PASSWORD, pass,
408	    sizeof(pass)) != 0) {
409		log_warnx("%s: radius_get_string_attr", __func__);
410		return;
411	}
412	ret = strcmp(ent->password, pass);
413	explicit_bzero(ent->password, strlen(ent->password));
414	log_info("q=%u User `%s' authentication %s (PAP)", q_id, username,
415	    (ret == 0)? "succeeded" : "failed");
416	if ((respkt = radius_new_response_packet((ret == 0)?
417	    RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt))
418	    == NULL) {
419		log_warn("%s: radius_new_response_packet()", __func__);
420		return;
421	}
422	module_accsreq_answer(self->base, q_id,
423	    radius_get_data(respkt), radius_get_length(respkt));
424	radius_delete_packet(respkt);
425}
426
427void
428auth_md5chap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
429    char *username, struct module_file_userinfo *ent)
430{
431	RADIUS_PACKET	*respkt = NULL;
432	size_t		 attrlen, challlen;
433	u_char		 chall[256], idpass[17], digest[16];
434	int		 ret;
435	MD5_CTX		 md5;
436
437	attrlen = sizeof(idpass);
438	if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD, idpass,
439	    &attrlen) != 0) {
440		log_warnx("%s: radius_get_string_attr", __func__);
441		return;
442	}
443	challlen = sizeof(chall);
444	if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_CHALLENGE, chall,
445	    &challlen) != 0) {
446		log_warnx("%s: radius_get_string_attr", __func__);
447		return;
448	}
449	MD5Init(&md5);
450	MD5Update(&md5, idpass, 1);
451	MD5Update(&md5, ent->password, strlen(ent->password));
452	MD5Update(&md5, chall, challlen);
453	MD5Final(digest, &md5);
454
455	ret = timingsafe_bcmp(idpass + 1, digest, sizeof(digest));
456	log_info("q=%u User `%s' authentication %s (CHAP)", q_id, username,
457	    (ret == 0)? "succeeded" : "failed");
458	if ((respkt = radius_new_response_packet((ret == 0)?
459	    RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt))
460	    == NULL) {
461		log_warn("%s: radius_new_response_packet()", __func__);
462		return;
463	}
464	module_accsreq_answer(self->base, q_id,
465	    radius_get_data(respkt), radius_get_length(respkt));
466	radius_delete_packet(respkt);
467}
468
469void
470auth_mschapv2(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
471    char *username, struct module_file_userinfo *ent)
472{
473	RADIUS_PACKET		*respkt = NULL;
474	size_t			 attrlen;
475	int			 i, lpass;
476	char			*pass = NULL;
477	uint8_t			 chall[MSCHAPV2_CHALLENGE_SZ];
478	uint8_t			 ntresponse[24], authenticator[16];
479	uint8_t			 pwhash[16], pwhash2[16], master[64];
480	struct {
481		uint8_t		 salt[2];
482		uint8_t		 len;
483		uint8_t		 key[16];
484		uint8_t		 pad[15];
485	} __packed		 rcvkey, sndkey;
486	struct {
487		uint8_t		 ident;
488		uint8_t		 flags;
489		uint8_t		 peerchall[16];
490		uint8_t		 reserved[8];
491		uint8_t		 ntresponse[24];
492	} __packed		 resp;
493	struct authresp {
494		uint8_t		 ident;
495		uint8_t		 authresp[42];
496	} __packed		 authresp;
497
498
499	attrlen = sizeof(chall);
500	if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT,
501	    RADIUS_VTYPE_MS_CHAP_CHALLENGE, chall, &attrlen) != 0) {
502		log_info("q=%u failed to retribute MS-CHAP-Challenge", q_id);
503		goto on_error;
504	}
505	attrlen = sizeof(resp);
506	if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT,
507	    RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, &attrlen) != 0) {
508		log_info("q=%u failed to retribute MS-CHAP2-Response", q_id);
509		goto on_error;
510	}
511
512	/* convert the password to UTF16-LE */
513	lpass = strlen(ent->password);
514	if ((pass = calloc(1, lpass * 2)) == NULL) {
515		log_warn("%s: calloc()", __func__);
516		goto on_error;
517	}
518	for (i = 0; i < lpass; i++) {
519		pass[i * 2] = ent->password[i];
520		pass[i * 2 + 1] = '\0';
521	}
522
523	/* calculate NT-Response by the password */
524	mschap_nt_response(chall, resp.peerchall,
525	    username, strlen(username), pass, lpass * 2, ntresponse);
526
527	if (timingsafe_bcmp(ntresponse, resp.ntresponse, 24) != 0) {
528		log_info("q=%u User `%s' authentication failed (MSCHAPv2)",
529		    q_id, username);
530		if ((respkt = radius_new_response_packet(
531		    RADIUS_CODE_ACCESS_REJECT, radpkt)) == NULL) {
532			log_warn("%s: radius_new_response_packet()", __func__);
533			goto on_error;
534		}
535		authresp.ident = resp.ident;
536		strlcpy(authresp.authresp, "E=691 R=0 V=3",
537		    sizeof(authresp.authresp));
538		radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
539		    RADIUS_VTYPE_MS_CHAP_ERROR, &authresp,
540		    offsetof(struct authresp, authresp[13]));
541	} else {
542		log_info("q=%u User `%s' authentication succeeded (MSCHAPv2)",
543		    q_id, username);
544		if ((respkt = radius_new_response_packet(
545		    RADIUS_CODE_ACCESS_ACCEPT, radpkt)) == NULL) {
546			log_warn("%s: radius_new_response_packet()", __func__);
547			goto on_error;
548		}
549		mschap_auth_response(pass, lpass * 2, ntresponse, chall,
550		    resp.peerchall, username, strlen(username),
551		    authresp.authresp);
552		authresp.ident = resp.ident;
553
554		radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
555		    RADIUS_VTYPE_MS_CHAP2_SUCCESS, &authresp,
556		    offsetof(struct authresp, authresp[42]));
557
558		mschap_ntpassword_hash(pass, lpass * 2, pwhash);
559		mschap_ntpassword_hash(pwhash, sizeof(pwhash), pwhash2);
560		mschap_masterkey(pwhash2, ntresponse, master);
561		radius_get_authenticator(radpkt, authenticator);
562
563		/* MS-MPPE-Recv-Key  */
564		memset(&rcvkey, 0, sizeof(rcvkey));
565		arc4random_buf(rcvkey.salt, sizeof(rcvkey.salt));
566		rcvkey.salt[0] |= 0x80;
567		rcvkey.len = 16;
568		mschap_asymetric_startkey(master, rcvkey.key, 16, 0, 1);
569		radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
570		    RADIUS_VTYPE_MPPE_RECV_KEY, &rcvkey, sizeof(rcvkey));
571
572		/* MS-MPPE-Send-Key  */
573		memset(&sndkey, 0, sizeof(sndkey));
574		arc4random_buf(sndkey.salt, sizeof(sndkey.salt));
575		sndkey.salt[0] |= 0x80;
576		sndkey.len = 16;
577		mschap_asymetric_startkey(master, sndkey.key, 16, 1, 1);
578		radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT,
579		    RADIUS_VTYPE_MPPE_SEND_KEY, &sndkey, sizeof(sndkey));
580	}
581
582	module_accsreq_answer(self->base, q_id,
583	    radius_get_data(respkt), radius_get_length(respkt));
584 on_error:
585	/* bzero password */
586	explicit_bzero(ent->password, strlen(ent->password));
587	if (pass != NULL)
588		explicit_bzero(pass, lpass * 2);
589	free(pass);
590	if (respkt != NULL)
591		radius_delete_packet(respkt);
592}
593
594void
595auth_reject(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt,
596    char *username, struct module_file_userinfo *ent)
597{
598	RADIUS_PACKET	*respkt = NULL;
599
600	if (ent != NULL)
601		explicit_bzero(ent->password, strlen(ent->password));
602
603	log_info("q=%u User `%s' authentication failed", q_id,
604	    username);
605	if ((respkt = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT,
606	    radpkt)) == NULL) {
607		log_warn("%s: radius_new_response_packet()", __func__);
608		return;
609	}
610	module_accsreq_answer(self->base, q_id,
611	    radius_get_data(respkt), radius_get_length(respkt));
612	radius_delete_packet(respkt);
613}
614