xref: /freebsd/lib/libutil/login_auth.c (revision 0de89efe5c443f213c7ea28773ef2dc6cf3af2ed)
1 /*-
2  * Copyright (c) 1996 by
3  * Sean Eric Fagan <sef@kithrup.com>
4  * David Nugent <davidn@blaze.net.au>
5  * All rights reserved.
6  *
7  * Portions copyright (c) 1995,1997 by
8  * Berkeley Software Design, Inc.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, is permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice immediately at the beginning of the file, without modification,
16  *    this list of conditions, and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. This work was done expressly for inclusion into FreeBSD.  Other use
21  *    is permitted provided this notation is included.
22  * 4. Absolutely no warranty of function or purpose is made by the authors.
23  * 5. Modifications may be freely made to this file providing the above
24  *    conditions are met.
25  *
26  * Low-level routines relating to the user capabilities database
27  *
28  *	$Id: login_auth.c,v 1.7 1997/05/10 18:55:37 davidn Exp $
29  */
30 
31 #include <sys/types.h>
32 #include <sys/time.h>
33 #include <sys/resource.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <limits.h>
39 #include <stdio.h>
40 #include <ctype.h>
41 #include <pwd.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 #include <unistd.h>
46 #include <login_cap.h>
47 #include <stdarg.h>
48 #include <paths.h>
49 #include <sys/socket.h>
50 #include <sys/wait.h>
51 #include <err.h>
52 #include <libutil.h>
53 
54 #ifdef	LOGIN_CAP_AUTH
55 /*
56  * Comment from BSDI's authenticate.c module:
57  * NOTE: THIS MODULE IS TO BE DEPRECATED.  FUTURE VERSIONS OF BSD/OS WILL
58  * HAVE AN UPDATED API, THOUGH THESE FUNCTIONS WILL CONTINUE TO BE AVAILABLE
59  * FOR BACKWARDS COMPATABILITY
60  */
61 
62 
63 #define AUTHMAXSPOOL	(8 * 1024) /* Max size of authentication data */
64 #define	AUTHCOMM_FD	3	   /* Handle used to read/write auth data */
65 
66 struct rmfiles {
67     struct rmfiles  *next;
68     char	    file[1];
69 };
70 
71 struct authopts {
72     struct authopts *next;
73     char	    opt[1];
74 };
75 
76 static char *spoolbuf = NULL;
77 static int spoolidx = 0;
78 static struct rmfiles *rmfirst = NULL;
79 static struct authopts *optfirst = NULL;
80 
81 
82 /*
83  * Setup a known environment for all authentication scripts.
84  */
85 
86 static char *auth_environ[] = {
87     "PATH=" _PATH_DEFPATH,
88     "SHELL=" _PATH_BSHELL,
89     NULL,
90 };
91 
92 
93 
94 /*
95  * nextline()
96  * Get the next line from the data buffer collected from
97  * the authentication program. This function relies on the
98  * fact that lines are nul terminated.
99  */
100 
101 static char *
102 nextline(int *idx)
103 {
104     char    *ptr = NULL;
105 
106     if (spoolbuf != NULL && *idx < spoolidx) {
107 	ptr = spoolbuf + *idx;
108 	*idx += strlen(ptr) + 1;
109     }
110     return ptr;
111 }
112 
113 
114 /*
115  * spooldata()
116  * Read data returned on authentication backchannel and
117  * stuff it into our spool buffer. We also replace \n with nul
118  * to make parsing easier later.
119  */
120 
121 static int
122 spooldata(int fd)
123 {
124 
125     if (spoolbuf)
126 	free(spoolbuf);
127     spoolidx = 0;
128 
129     if (spoolbuf == NULL && (spoolbuf = malloc(AUTHMAXSPOOL)) == NULL)
130 	syslog(LOG_ERR, "authbuffer malloc: %m");
131 
132     else while (spoolidx < sizeof(spoolbuf) - 1) {
133 	int	r = read(fd, spoolbuf + spoolidx, sizeof(spoolbuf)-spoolidx);
134 	char	*b;
135 
136 	if (r <= 0) {
137 	    spoolbuf[spoolidx] = '\0';
138 	    return 0;
139 	}
140 	/*
141 	 * Convert newlines into NULs to allow
142 	 * easier scanning of the file.
143 	 */
144 	while ((b = memchr(spoolbuf + spoolidx, '\n', r)) != NULL)
145 	    *b = '\0';
146 	spoolidx += r;
147     }
148     return -1;
149 }
150 
151 
152 /*
153  * auth_check()
154  * Starts an auth_script() for the given <user>, with a class <class>,
155  * style <style>, and service <service>.  <style> is necessary,
156  * as are <user> and <class>, but <service> is optional -- it defaults
157  * to "login".
158  * Since auth_script() expects an execl'able program name, authenticate()
159  * also concatenates <style> to _PATH_AUTHPROG.
160  * Lastly, calls auth_scan(0) to see if there are any "reject" statements,
161  * or lack of "auth" statements.
162  * Returns -1 on error, 0 on rejection, and >0 on success.
163  * (See AUTH_* for the return values.)
164  *
165  */
166 
167 int
168 auth_check(const char *name, const char *clss, const char *style,
169 	   const char *service, int *status)
170 {
171     int	    _status;
172 
173     if (status == NULL)
174 	status = &_status;
175     *status = 0;
176 
177     if (style != NULL) {
178 	char	path[MAXPATHLEN];
179 
180 	if (service == NULL)
181 	    service = LOGIN_DEFSERVICE;
182 
183 	snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
184 	if (auth_script(path, style, "-s", service, name, clss, 0))
185 	    status = 0;
186 	else
187 	    *status = auth_scan(0);
188 
189 	return *status & AUTH_ALLOW;
190     }
191     return -1;
192 }
193 
194 
195 int
196 auth_response(const char *name, const char *class, const char *style,
197 	      const char *service, int *status,
198 	      const char *challenge, const char *response)
199 {
200     int	    _status;
201 
202     if (status == NULL)
203 	status = &_status;
204     *status = 0;
205 
206     if (style != NULL) {
207 	int	datalen;
208 	char    *data;
209 
210 	if (service == NULL)
211 	    service = LOGIN_DEFSERVICE;
212 
213 	datalen = strlen(challenge) + strlen(response) + 2;
214 
215 	if ((data = malloc(datalen)) == NULL) {
216 	    syslog(LOG_ERR, "auth_response: %m");
217 	    warnx("internal resource failure");
218 	} else {
219 	    char    path[MAXPATHLEN];
220 
221 	    snprintf(data, datalen, "%s%c%s", challenge, 0, response);
222 	    snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
223 	    if (auth_script_data(data, datalen, path, style, "-s", service,
224 				 name, class, 0))
225 		*status = 0;
226 	    else
227 		*status = auth_scan(0);
228 	    free(data);
229 	    return (*status & AUTH_ALLOW);
230 	}
231     }
232     return -1;
233 }
234 
235 
236 int
237 auth_approve(login_cap_t *lc, const char *name, const char *service)
238 {
239     int	    r = -1;
240     char    path[MAXPATHLEN];
241 
242     if (lc == NULL) {
243 	if (strlen(name) > MAXPATHLEN) {
244 	    syslog(LOG_ERR, "%s: username too long", name);
245 	    warnx("username too long");
246 	} else {
247 	    struct passwd   *pwd;
248 	    char	    *p;
249 
250 	    pwd = getpwnam(name);
251 	    if (pwd == NULL && (p = strchr(name, '.')) != NULL) {
252 		int	i = p - name;
253 
254 		if (i >= MAXPATHLEN)
255 		    i = MAXPATHLEN - 1;
256 		strncpy(path, name, i);
257 		path[i] = '\0';
258 		pwd = getpwnam(path); /* Fixed bug in BSDI code... */
259 	    }
260 	    if ((lc = login_getpwclass(pwd ? pwd->pw_class : NULL)) == NULL)
261 		warnx("unable to classify user '%s'", name);
262 	}
263     }
264 
265     if (lc != NULL) {
266 	char	*approve;
267 	char	*s;
268 
269 	if (service != NULL)
270 		service = LOGIN_DEFSERVICE;
271 
272 	snprintf(path, sizeof(path), "approve-%s", service);
273 
274         if ((approve = login_getcapstr(lc, s = path, NULL, NULL)) == NULL &&
275 	    (approve = login_getcapstr(lc, s = "approve", NULL, NULL)) == NULL)
276 	    r = AUTH_OKAY;
277 	else {
278 
279 	    if (approve[0] != '/') {
280 		syslog(LOG_ERR, "Invalid %s script: %s", s, approve);
281 		warnx("invalid path to approval script");
282 	    } else {
283 		char	*s;
284 
285 		s = strrchr(approve, '/') + 1;
286 		if (auth_script(approve, s, name,
287 				lc->lc_class, service, 0) == 0 &&
288 		    (r = auth_scan(AUTH_OKAY) & AUTH_ALLOW) != 0)
289 		    auth_env();
290 	    }
291 	}
292     }
293     return r;
294 }
295 
296 
297 void
298 auth_env(void)
299 {
300     int	    idx = 0;
301     char    *line;
302 
303     while ((line = nextline(&idx)) != NULL) {
304 	if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
305 	    line += sizeof(BI_SETENV) - 1;
306 	    if (*line && isspace(*line)) {
307 		char	*name;
308 		char	ch, *p;
309 
310 		while (*line && isspace(*line))
311 		    ++line;
312 		name = line;
313 		while (*line && !isspace(*line))
314 		    ++line;
315 		ch = *(p = line);
316 		if (*line)
317 		    ++line;
318 		if (setenv(name, line, 1))
319 		    warn("setenv(%s, %s)", name, line);
320 		*p = ch;
321 	    }
322 	}
323     }
324 }
325 
326 
327 char *
328 auth_value(const char *what)
329 {
330     int	    idx = 0;
331     char    *line;
332 
333     while ((line = nextline(&idx)) != NULL) {
334 	if (!strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1)) {
335 	    char    *name;
336 
337 	    line += sizeof(BI_VALUE) - 1;
338 	    while (*line && isspace(*line))
339 		++line;
340 	    name = line;
341 	    if (*line) {
342 		int	i;
343 		char	ch, *p;
344 
345 		ch = *(p = line);
346 		*line++ = '\0';
347 		i = strcmp(name, what);
348 		*p = ch;
349 		if (i == 0)
350 		    return auth_mkvalue(line);
351 	    }
352 	}
353     }
354     return NULL;
355 }
356 
357 char *
358 auth_mkvalue(const char *value)
359 {
360     char *big, *p;
361 
362     big = malloc(strlen(value) * 4 + 1);
363     if (big != NULL) {
364 	for (p = big; *value; ++value) {
365 	    switch (*value) {
366 	    case '\r':
367 		*p++ = '\\';
368 		*p++ = 'r';
369 		break;
370 	    case '\n':
371 		*p++ = '\\';
372 		*p++ = 'n';
373 		break;
374 	    case '\\':
375 		*p++ = '\\';
376 		*p++ = *value;
377 		break;
378 	    case '\t':
379 	    case ' ':
380 		if (p == big)
381 		    *p++ = '\\';
382 		*p++ = *value;
383 		break;
384 	    default:
385 		if (!isprint(*value)) {
386 		    *p++ = '\\';
387 		    *p++ = ((*value >> 6) & 0x3) + '0';
388 		    *p++ = ((*value >> 3) & 0x7) + '0';
389 		    *p++ = ((*value     ) & 0x7) + '0';
390 		} else
391 		    *p++ = *value;
392 		break;
393 	    }
394 	}
395 	*p = '\0';
396 	big = realloc(big, strlen(big) + 1);
397     }
398     return big;
399 }
400 
401 
402 #define NARGC	63
403 static int
404 _auth_script(const char *data, int nbytes, const char *path, va_list ap)
405 {
406     int		    r, argc, status;
407     int		    pfd[2];
408     pid_t	    pid;
409     struct authopts *e;
410     char	    *argv[NARGC+1];
411 
412     r = -1;
413     argc = 0;
414     for (e = optfirst; argc < (NARGC - 1) && e != NULL; e = e->next) {
415 	argv[argc++] = "-v";
416 	argv[argc++] = e->opt;
417     }
418     while (argc < NARGC && (argv[argc] = va_arg(ap, char *)) != NULL)
419 	++argc;
420     argv[argc] = NULL;
421 
422     if (argc >= NARGC && va_arg(ap, char *))
423 	syslog(LOG_ERR, "too many arguments");
424     else if (_secure_path(path, 0, 0) < 0) {
425 	syslog(LOG_ERR, "%s: path not secure", path);
426 	warnx("invalid script: %s", path);
427     } else if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
428 	syslog(LOG_ERR, "unable to create backchannel %m");
429 	warnx("internal resource failure");
430     } else switch (pid = fork()) {
431     case -1:			/* fork() failure */
432 	close(pfd[0]);
433 	close(pfd[1]);
434 	syslog(LOG_ERR, "fork %s: %m", path);
435 	warnx("internal resource failure");
436 	break;
437     case 0:			/* child process */
438 	close(pfd[0]);
439 	if (pfd[1] != AUTHCOMM_FD) {
440 	    if (dup2(pfd[1], AUTHCOMM_FD) < 0)
441 		err(1, "dup backchannel");
442 	    close(pfd[1]);
443 	}
444 	for (r = getdtablesize(); --r > AUTHCOMM_FD; )
445 	    close(r);
446 	execve(path, argv, auth_environ);
447 	syslog(LOG_ERR, "exec %s: %m", path);
448 	err(1, path);
449     default:			/* parent */
450 	close(pfd[1]);
451 	if (data && nbytes)
452 	    write(pfd[0], data, nbytes);
453 	r = spooldata(pfd[0]);
454 	close(pfd[0]);
455 	if (waitpid(pid, &status, 0) < 0) {
456 	    syslog(LOG_ERR, "%s: waitpid: %m", path);
457 	    warnx("internal failure");
458 	    r = -1;
459 	} else {
460 	    if (r != 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
461 		r = -1;
462 	}
463 	/* kill the buffer if it is of no use */
464 	if (r != 0) {
465 	    free(spoolbuf);
466 	    spoolbuf = NULL;
467 	    spoolidx = 0;
468 	}
469 	break;
470     }
471     return r;
472 }
473 
474 
475 
476 /*
477  * auth_script()
478  * Runs an authentication program with specified arguments.
479  * It sets up file descriptor 3 for the program to write to;
480  * it stashes the output somewhere.  The output of the program
481  * consists of statements:
482  *	reject [challenge|silent]
483  *	authorize [root|secure]
484  *	setenv <name> [<value>]
485  *	remove <file>
486  *
487  * Terribly exciting, isn't it?
488  * Output cannot exceed AUTHMAXSPOOL characters.
489  */
490 
491 int
492 auth_script(const char *path, ...)
493 {
494     int		r;
495     va_list	ap;
496 
497     va_start(ap, path);
498     r = _auth_script(NULL, 0, path, ap);
499     va_end(ap);
500     return r;
501 }
502 
503 
504 int
505 auth_script_data(const char *data, int nbytes, const char *path, ...)
506 {
507     int		r;
508     va_list	ap;
509 
510     va_start(ap, path);
511     r = _auth_script(data, nbytes, path, ap);
512     va_end(ap);
513     return r;
514 }
515 
516 
517 static void
518 add_rmlist(const char *file)
519 {
520     struct rmfiles *rm;
521 
522     if ((rm = malloc(sizeof(struct rmfiles) + strlen(file) + 1)) == NULL)
523 	syslog(LOG_ERR, "add_rmfile malloc: %m");
524     else {
525 	strcpy(rm->file, file);
526 	rm->next = rmfirst;
527 	rmfirst = rm;
528     }
529 }
530 
531 
532 int
533 auth_scan(int okay)
534 {
535     int	    idx = 0;
536     char    *line;
537 
538     while ((line = nextline(&idx)) != NULL) {
539 	if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
540 	    line += sizeof(BI_REJECT) - 1;
541 	    while (*line && isspace(*line))
542 		++line;
543 	    if (*line) {
544 		if (!strcasecmp(line, "silent"))
545 		    return AUTH_SILENT;
546 		if (!strcasecmp(line, "challenge"))
547 		    return AUTH_CHALLENGE;
548 	    }
549 	    return 0;
550 	} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
551 	    line += sizeof(BI_AUTH) - 1;
552 	    while (*line && isspace(*line))
553 		++line;
554 	    if (*line == '\0')
555 		okay |= AUTH_OKAY;
556 	    else if (!strcasecmp(line, "root"))
557 		okay |= AUTH_ROOTOKAY;
558 	    else if (!strcasecmp(line, "secure"))
559 		okay |= AUTH_SECURE;
560 	}
561 	else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
562 	    line += sizeof(BI_REMOVE) - 1;
563 	    while (*line && isspace(*line))
564 		++line;
565 	    if (*line)
566 		add_rmlist(line);
567 	}
568     }
569 
570     return okay;
571 }
572 
573 
574 int
575 auth_setopt(const char *n, const char *v)
576 {
577     int		    r;
578     struct authopts *e;
579 
580     if ((e = malloc(sizeof(*e) + strlen(n) + strlen(v) + 1)) == NULL)
581 	r = -1;
582     else {
583 	sprintf(e->opt, "%s=%s", n, v);
584 	e->next = optfirst;
585 	optfirst = e;
586 	r = 0;
587     }
588     return r;
589 }
590 
591 
592 void
593 auth_clropts(void)
594 {
595     struct authopts *e;
596 
597     while ((e = optfirst) != NULL) {
598 	optfirst = e->next;
599 	free(e);
600     }
601 }
602 
603 
604 void
605 auth_rmfiles(void)
606 {
607     struct rmfiles  *rm;
608 
609     while ((rm = rmfirst) != NULL) {
610 	unlink(rm->file);
611 	rmfirst = rm->next;
612 	free(rm);
613     }
614 }
615 
616 #endif
617 
618 
619 /*
620  * auth_checknologin()
621  * Checks for the existance of a nologin file in the login_cap
622  * capability <lc>.  If there isn't one specified, then it checks
623  * to see if this class should just ignore nologin files.  Lastly,
624  * it tries to print out the default nologin file, and, if such
625  * exists, it exits.
626  */
627 
628 void
629 auth_checknologin(login_cap_t *lc)
630 {
631   char *file;
632 
633   /* Do we ignore a nologin file? */
634   if (login_getcapbool(lc, "ignorenologin", 0))
635     return;
636 
637   /* Note that <file> will be "" if there is no nologin capability */
638   if ((file = login_getcapstr(lc, "nologin", "", NULL)) == NULL)
639     exit(1);
640 
641   /*
642    * *file is true IFF there was a "nologin" capability
643    * Note that auth_cat() returns 1 only if the specified
644    * file exists, and is readable.  E.g., /.nologin exists.
645    */
646   if ((*file && auth_cat(file)) || auth_cat(_PATH_NOLOGIN))
647     exit(1);
648 }
649 
650 
651 /*
652  * auth_cat()
653  * Checks for the readability of <file>; if it can be opened for
654  * reading, it prints it out to stdout, and then exits.  Otherwise,
655  * it returns 0 (meaning no nologin file).
656  */
657 
658 int
659 auth_cat(const char *file)
660 {
661   int fd, count;
662   char buf[BUFSIZ];
663 
664   if ((fd = open(file, O_RDONLY)) < 0)
665     return 0;
666   while ((count = read(fd, buf, sizeof(buf))) > 0)
667     (void)write(fileno(stdout), buf, count);
668   close(fd);
669   sleep(5);	/* wait an arbitrary time to drain */
670   return 1;
671 }
672