1 /*	$OpenBSD: radiusd_bsdauth.c,v 1.19 2024/11/21 13:43:10 claudio Exp $	*/
2 
3 /*
4  * Copyright (c) 2015 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/queue.h>
21 #include <sys/socket.h>
22 #include <sys/uio.h>
23 #include <sys/wait.h>
24 
25 #include <bsd_auth.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <grp.h>
30 #include <imsg.h>
31 #include <login_cap.h>
32 #include <pwd.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <unistd.h>
39 
40 #include "radiusd.h"
41 #include "radiusd_module.h"
42 
43 struct module_bsdauth {
44 	struct module_base	 *base;
45 	struct imsgbuf		  ibuf;
46 	char			**okgroups;
47 };
48 
49 /* IPC between priv and main */
50 enum {
51 	IMSG_BSDAUTH_OK = 1000,
52 	IMSG_BSDAUTH_NG,
53 	IMSG_BSDAUTH_USERCHECK,
54 	IMSG_BSDAUTH_GROUPCHECK
55 };
56 struct auth_usercheck_args {
57 	size_t	userlen;
58 	size_t	passlen;
59 };
60 struct auth_groupcheck_args {
61 	size_t	userlen;
62 	size_t	grouplen;
63 };
64 
65 __dead static void
66 		 module_bsdauth_main(void);
67 static void	 module_bsdauth_config_set(void *, const char *, int,
68 		    char * const *);
69 static void	 module_bsdauth_userpass(void *, u_int, const char *,
70 		    const char *);
71 static pid_t	 start_child(char *, int);
72 __dead static void
73 		 fatal(const char *);
74 
75 static struct module_handlers module_bsdauth_handlers = {
76 	.userpass = module_bsdauth_userpass,
77 	.config_set = module_bsdauth_config_set
78 };
79 
80 int
main(int argc,char * argv[])81 main(int argc, char *argv[])
82 {
83 	int		 ch, pairsock[2], status;
84 	struct imsgbuf	 ibuf;
85 	struct imsg	 imsg;
86 	ssize_t		 n;
87 	size_t		 datalen;
88 	pid_t		 pid;
89 	char		*saved_argv0;
90 
91 	while ((ch = getopt(argc, argv, "M")) != -1)
92 		switch (ch) {
93 		case 'M':
94 			module_bsdauth_main();
95 			/* never return, not rearched here */
96 			break;
97 		default:
98 			break;
99 		}
100 	saved_argv0 = argv[0];
101 	argc -= optind;
102 	argv += optind;
103 
104 	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC,
105 	    pairsock) == -1)
106 		err(EXIT_FAILURE, "socketpair");
107 
108 	openlog(NULL, LOG_PID, LOG_DAEMON);
109 
110 	pid = start_child(saved_argv0, pairsock[1]);
111 
112 	/*
113 	 * Privileged process
114 	 */
115 	setproctitle("[priv]");
116 	if (imsgbuf_init(&ibuf, pairsock[0]) == 1)
117 		err(EXIT_FAILURE, "imsgbuf_init");
118 
119 	if (pledge("stdio getpw rpath proc exec", NULL) == -1)
120 		err(EXIT_FAILURE, "pledge");
121 
122 	for (;;) {
123 		if (imsgbuf_read(&ibuf) != 1)
124 			break;
125 		for (;;) {
126 			if ((n = imsg_get(&ibuf, &imsg)) == -1)
127 				break;
128 			if (n == 0)
129 				break;
130 			datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
131 			switch (imsg.hdr.type) {
132 			case IMSG_BSDAUTH_USERCHECK:
133 			    {
134 				char		*user, *pass;
135 				bool		 authok = false;
136 				struct auth_usercheck_args
137 						*args;
138 
139 				if (datalen < sizeof(
140 				    struct auth_usercheck_args)) {
141 					syslog(LOG_ERR, "Short message");
142 					break;
143 				}
144 				args = (struct auth_usercheck_args *)imsg.data;
145 
146 				if (datalen < sizeof(struct auth_usercheck_args)
147 				    + args->userlen + args->passlen) {
148 					syslog(LOG_ERR, "Short message");
149 					break;
150 				}
151 				user = (char *)(args + 1);
152 				user[args->userlen - 1] = '\0';
153 				pass = user + args->userlen;
154 				pass[args->passlen - 1] = '\0';
155 
156 				if (auth_userokay(user, NULL, NULL, pass))
157 					authok = true;
158 				explicit_bzero(pass, args->passlen);
159 
160 				imsg_compose(&ibuf, (authok)
161 				    ? IMSG_BSDAUTH_OK : IMSG_BSDAUTH_NG,
162 				    0, 0, -1, NULL, 0);
163 				break;
164 			    }
165 			case IMSG_BSDAUTH_GROUPCHECK:
166 			    {
167 				int		 i;
168 				char		*user, *group;
169 				struct passwd   *pw;
170 				struct group	 gr0, *gr;
171 				char		 g_buf[4096];
172 				bool		 group_ok = false;
173 				struct auth_groupcheck_args
174 						*args;
175 
176 				if (datalen < sizeof(
177 				    struct auth_groupcheck_args)) {
178 					syslog(LOG_ERR, "Short message");
179 					break;
180 				}
181 				args = (struct auth_groupcheck_args *)imsg.data;
182 				if (datalen <
183 				    sizeof(struct auth_groupcheck_args) +
184 				    args->userlen + args->grouplen) {
185 					syslog(LOG_ERR, "Short message");
186 					break;
187 				}
188 				user = (char *)(args + 1);
189 				user[args->userlen - 1] = '\0';
190 				group = user + args->userlen;
191 				group[args->grouplen - 1] = '\0';
192 
193 				user[strcspn(user, ":")] = '\0';
194 				pw = getpwnam(user);
195 				if (pw == NULL)
196 					goto invalid;
197 				if (getgrnam_r(group, &gr0, g_buf,
198 				    sizeof(g_buf), &gr) == -1 || gr == NULL)
199 					goto invalid;
200 
201 				if (gr->gr_gid == pw->pw_gid) {
202 					group_ok = true;
203 					goto invalid;
204 				}
205 				for (i = 0; gr->gr_mem[i] != NULL; i++) {
206 					if (strcmp(gr->gr_mem[i], pw->pw_name)
207 					    == 0) {
208 						group_ok = true;
209 						goto invalid;
210 					}
211 				}
212 invalid:
213 				endgrent();
214 
215 				imsg_compose(&ibuf, (group_ok)
216 				    ? IMSG_BSDAUTH_OK : IMSG_BSDAUTH_NG,
217 				    0, 0, -1, NULL, 0);
218 				break;
219 			    }
220 			}
221 			imsg_free(&imsg);
222 			imsgbuf_flush(&ibuf);
223 		}
224 		imsgbuf_flush(&ibuf);
225 	}
226 	imsgbuf_clear(&ibuf);
227 
228 	while (waitpid(pid, &status, 0) == -1) {
229 		if (errno != EINTR)
230 			break;
231 	}
232 	exit(WEXITSTATUS(status));
233 }
234 
235 static void
module_bsdauth_main(void)236 module_bsdauth_main(void)
237 {
238 	int			 i;
239 	struct module_bsdauth	 module_bsdauth;
240 
241 	/*
242 	 * main process
243 	 */
244 	setproctitle("[main]");
245 	openlog(NULL, LOG_PID, LOG_DAEMON);
246 	memset(&module_bsdauth, 0, sizeof(module_bsdauth));
247 	if ((module_bsdauth.base = module_create(STDIN_FILENO, &module_bsdauth,
248 	    &module_bsdauth_handlers)) == NULL)
249 		err(1, "Could not create a module instance");
250 
251 	module_drop_privilege(module_bsdauth.base, 0);
252 
253 	module_load(module_bsdauth.base);
254 	if (imsgbuf_init(&module_bsdauth.ibuf, 3) == -1)
255 		err(EXIT_FAILURE, "imsgbuf_init");
256 
257 	if (pledge("stdio proc", NULL) == -1)
258 		err(EXIT_FAILURE, "pledge");
259 
260 	while (module_run(module_bsdauth.base) == 0)
261 		;
262 
263 	module_destroy(module_bsdauth.base);
264 	imsgbuf_clear(&module_bsdauth.ibuf);
265 
266 	if (module_bsdauth.okgroups) {
267 		for (i = 0; module_bsdauth.okgroups[i] != NULL; i++)
268 			free(module_bsdauth.okgroups[i]);
269 	}
270 	free(module_bsdauth.okgroups);
271 
272 	exit(EXIT_SUCCESS);
273 }
274 
275 static void
module_bsdauth_config_set(void * ctx,const char * name,int argc,char * const * argv)276 module_bsdauth_config_set(void *ctx, const char *name, int argc,
277     char * const * argv)
278 {
279 	struct module_bsdauth	 *module = ctx;
280 	int			  i;
281 	char			**groups = NULL;
282 
283 	if (strcmp(name, "restrict-group") == 0) {
284 		if (module->okgroups != NULL) {
285 			module_send_message(module->base, IMSG_NG,
286 			    "`restrict-group' is already defined");
287 			goto on_error;
288 		}
289 		if ((groups = calloc(sizeof(char *), argc + 1)) == NULL) {
290 			module_send_message(module->base, IMSG_NG,
291 			    "Out of memory");
292 			goto on_error;
293 		}
294 		for (i = 0; i < argc; i++) {
295 			if ((groups[i] = strdup(argv[i])) == NULL) {
296 				module_send_message(module->base,
297 				    IMSG_NG, "Out of memory");
298 				goto on_error;
299 			}
300 		}
301 		groups[i] = NULL;
302 		module->okgroups = groups;
303 		module_send_message(module->base, IMSG_OK, NULL);
304 	} else if (strncmp(name, "_", 1) == 0)
305 		/* ignore all internal messages */
306 		module_send_message(module->base, IMSG_OK, NULL);
307 	else
308 		module_send_message(module->base, IMSG_NG,
309 		    "Unknown config parameter `%s'", name);
310 	return;
311 on_error:
312 	if (groups != NULL) {
313 		for (i = 0; groups[i] != NULL; i++)
314 			free(groups[i]);
315 		free(groups);
316 	}
317 	return;
318 }
319 
320 
321 static void
module_bsdauth_userpass(void * ctx,u_int q_id,const char * user,const char * pass)322 module_bsdauth_userpass(void *ctx, u_int q_id, const char *user,
323     const char *pass)
324 {
325 	struct module_bsdauth	*module = ctx;
326 	struct auth_usercheck_args
327 				 usercheck;
328 	struct auth_groupcheck_args
329 				 groupcheck;
330 	struct iovec		iov[4];
331 	const char		*group;
332 	u_int			 i;
333 	const char		*reason;
334 	struct imsg		 imsg;
335 	ssize_t			 n;
336 
337 	memset(&imsg, 0, sizeof(imsg));
338 	if (pass == NULL)
339 		pass = "";
340 
341 	usercheck.userlen = strlen(user) + 1;
342 	usercheck.passlen = strlen(pass) + 1;
343 	iov[0].iov_base = &usercheck;
344 	iov[0].iov_len = sizeof(usercheck);
345 	iov[1].iov_base = (char *)user;
346 	iov[1].iov_len = usercheck.userlen;
347 	iov[2].iov_base = (char *)pass;
348 	iov[2].iov_len = usercheck.passlen;
349 
350 	imsg_composev(&module->ibuf, IMSG_BSDAUTH_USERCHECK, 0, 0, -1, iov, 3);
351 	imsgbuf_flush(&module->ibuf);
352 	if (imsgbuf_read(&module->ibuf) != 1)
353 		fatal("imsgbuf_read() failed in module_bsdauth_userpass()");
354 	if ((n = imsg_get(&module->ibuf, &imsg)) <= 0)
355 		fatal("imsg_get() failed in module_bsdauth_userpass()");
356 
357 	if (imsg.hdr.type != IMSG_BSDAUTH_OK) {
358 		reason = "Authentication failed";
359 		goto auth_ng;
360 	}
361 	if (module->okgroups != NULL) {
362 		reason = "Group restriction is not allowed";
363 		for (i = 0; module->okgroups[i] != NULL; i++) {
364 			group = module->okgroups[i];
365 
366 			groupcheck.userlen = strlen(user) + 1;
367 			groupcheck.grouplen = strlen(group) + 1;
368 			iov[0].iov_base = &groupcheck;
369 			iov[0].iov_len = sizeof(groupcheck);
370 			iov[1].iov_base = (char *)user;
371 			iov[1].iov_len = groupcheck.userlen;
372 			iov[2].iov_base = (char *)group;
373 			iov[2].iov_len = groupcheck.grouplen;
374 			imsg_composev(&module->ibuf, IMSG_BSDAUTH_GROUPCHECK,
375 			    0, 0, -1, iov, 3);
376 			imsgbuf_flush(&module->ibuf);
377 			if (imsgbuf_read(&module->ibuf) != 1)
378 				fatal("imsgbuf_read() failed in "
379 				    "module_bsdauth_userpass()");
380 			if ((n = imsg_get(&module->ibuf, &imsg)) <= 0)
381 				fatal("imsg_get() failed in "
382 				    "module_bsdauth_userpass()");
383 			if (imsg.hdr.type == IMSG_BSDAUTH_OK)
384 				goto group_ok;
385 		}
386 		goto auth_ng;
387 	}
388 group_ok:
389 	module_userpass_ok(module->base, q_id, "Authentication succeeded");
390 	imsg_free(&imsg);
391 	return;
392 auth_ng:
393 	module_userpass_fail(module->base, q_id, reason);
394 	imsg_free(&imsg);
395 	return;
396 }
397 
398 pid_t
start_child(char * argv0,int fd)399 start_child(char *argv0, int fd)
400 {
401 	char *argv[5];
402 	int argc = 0;
403 	pid_t pid;
404 
405 	switch (pid = fork()) {
406 	case -1:
407 		fatal("cannot fork");
408 	case 0:
409 		break;
410 	default:
411 		close(fd);
412 		return (pid);
413 	}
414 
415 	if (fd != 3) {
416 		if (dup2(fd, 3) == -1)
417 			fatal("cannot setup imsg fd");
418 	} else if (fcntl(fd, F_SETFD, 0) == -1)
419 		fatal("cannot setup imsg fd");
420 
421 	argv[argc++] = argv0;
422 	argv[argc++] = "-M";	/* main proc */
423 	argv[argc++] = NULL;
424 	execvp(argv0, argv);
425 	fatal("execvp");
426 }
427 
428 static void
fatal(const char * msg)429 fatal(const char *msg)
430 {
431 	syslog(LOG_ERR, "%s: %m", msg);
432 	abort();
433 }
434