login_auth.c revision 39327
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.8 1997/07/19 04:47:05 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
66struct rmfiles {
67    struct rmfiles  *next;
68    char	    file[1];
69};
70
71struct authopts {
72    struct authopts *next;
73    char	    opt[1];
74};
75
76static char *spoolbuf = NULL;
77static int spoolidx = 0;
78static struct rmfiles *rmfirst = NULL;
79static struct authopts *optfirst = NULL;
80
81
82/*
83 * Setup a known environment for all authentication scripts.
84 */
85
86static 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
101static char *
102nextline(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
121static int
122spooldata(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
167int
168auth_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
195int
196auth_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
236int
237auth_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
297void
298auth_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
327char *
328auth_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
357char *
358auth_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 = reallocf(big, strlen(big) + 1);
397    }
398    return big;
399}
400
401
402#define NARGC	63
403static 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
491int
492auth_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
504int
505auth_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
517static void
518add_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
532int
533auth_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
574int
575auth_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
592void
593auth_clropts(void)
594{
595    struct authopts *e;
596
597    while ((e = optfirst) != NULL) {
598	optfirst = e->next;
599	free(e);
600    }
601}
602
603
604void
605auth_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
628void
629auth_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
658int
659auth_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