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