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