login_auth.c revision 22993
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 * Redistribution and use in source and binary forms, with or without
8 * modification, is permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice immediately at the beginning of the file, without modification,
12 *    this list of conditions, and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. This work was done expressly for inclusion into FreeBSD.  Other use
17 *    is permitted provided this notation is included.
18 * 4. Absolutely no warranty of function or purpose is made by the authors.
19 * 5. Modifications may be freely made to this file providing the above
20 *    conditions are met.
21 *
22 * Low-level routines relating to the user capabilities database
23 *
24 *	$Id$
25 */
26
27#include <sys/types.h>
28#include <sys/time.h>
29#include <sys/resource.h>
30#include <sys/stat.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <limits.h>
34#include <stdio.h>
35#include <pwd.h>
36#include <stdlib.h>
37#include <string.h>
38#include <syslog.h>
39#include <unistd.h>
40#include <login_cap.h>
41#include <stdarg.h>
42#include <paths.h>
43#include <sys/wait.h>
44
45#ifdef RLIM_LONG
46# define STRTOV strtol
47#else
48# define STRTOV strtoq
49#endif
50
51#define AUTHMAXLINES  1024
52#define AUTHMAXARGS   16
53
54struct auth_info {
55  int reject;
56  int auths;
57  int env_count;
58  char **env;
59  int file_count;
60  char **files;
61};
62
63static struct auth_info auth_info;
64
65/*
66 * free_auth_info()
67 * Go through the auth_info structure, and free() anything of interest.
68 * This includes the string arrays, and any individual element.
69 * All part of being environmentally conscious ;).
70 */
71
72static void
73free_auth_info(void)
74{
75  int i;
76
77  auth_info.reject = 0;
78  auth_info.auths = 0;
79  if (auth_info.env) {
80    for (i = 0; i < auth_info.env_count; i++) {
81      if (auth_info.env[i])
82	free(auth_info.env[i]);
83    }
84    free(auth_info.env);
85    auth_info.env = NULL;
86  }
87  if (auth_info.files) {
88    for (i = 0; i < auth_info.file_count; i++) {
89      if (auth_info.files[i])
90	free(auth_info.files[i]);
91    }
92    free(auth_info.files);
93    auth_info.files = NULL;
94  }
95}
96
97
98/*
99 * collect_info()
100 * Read from <fd>, a list of authorization commands.
101 * These commands are:
102 *	reject
103 *	authorize [root|secure]
104 *	setenv <name>[ <value>]
105 *	remove <file>
106 * A single reject means the entire thing is bad;
107 * multiple authorize statements can be present (it would be
108 * silly, but that's what the spec says).
109 * The commands are collected, and are accted upon by:
110 *	auth_scan()	-- check for authorization or rejection
111 *	auth_rmfiles()	-- remove the specified files
112 *	auth_env()	-- set the specified environment variables
113 * We only get up to AUTHMAXLINES lines of input from the program.
114 */
115#define STRSIZEOF(x)  (sizeof(x)-1)
116static void
117collect_info(int fd)
118{
119  char *line;
120  FILE *fp;
121  char *ptr;
122  size_t len;
123  int line_count = 0;
124
125  fp = fdopen(fd, "r");
126
127  while ((line = fgetln(fp, &len)) != NULL) {
128    if (++line_count > AUTHMAXLINES)
129      break;
130    if (len && line[len-1] == '\n')
131      --len;
132    line[len] = '\0'; /* Terminate */
133    if (strncasecmp(line, BI_REJECT, STRSIZEOF(BI_REJECT)) == 0) {
134      auth_info.reject = 1;
135    } else if (strncasecmp(line, BI_AUTH, STRSIZEOF(BI_AUTH)) == 0) {
136      ptr = line + STRSIZEOF(BI_AUTH);
137      ptr += strspn(ptr, " \t");
138      if (!*ptr)
139	auth_info.auths |= AUTH_OKAY;
140      else if (strncasecmp(ptr, BI_ROOTOKAY, STRSIZEOF(BI_ROOTOKAY)) == 0)
141	auth_info.auths |= AUTH_ROOTOKAY;
142      else if (strncasecmp(ptr, BI_SECURE, STRSIZEOF(BI_SECURE)) == 0)
143	auth_info.auths |= AUTH_SECURE;
144    } else if (strncasecmp(line, BI_SETENV, STRSIZEOF(BI_SETENV)) == 0) {
145      ptr = line + STRSIZEOF(BI_SETENV);
146      ptr += strspn(ptr, " \t");
147      if (*ptr) {
148	char **tmp = realloc(auth_info.env, sizeof(char*) * (auth_info.env_count + 1));
149	if (tmp != NULL) {
150	  auth_info.env = tmp;
151	  if ((auth_info.env[auth_info.env_count] = strdup(ptr)) != NULL)
152	    auth_info.env_count++;
153	}
154      }
155    } else if (strncasecmp(line, BI_REMOVE, STRSIZEOF(BI_REMOVE)) == 0) {
156      ptr = line + STRSIZEOF(BI_REMOVE);
157      ptr += strspn(ptr, " \t");
158      if (*ptr) {
159	char **tmp = realloc(auth_info.files, sizeof(char*) * (auth_info.file_count + 1));
160	if (tmp != NULL) {
161	  auth_info.files = tmp;
162	  if ((auth_info.files[auth_info.file_count] = strdup(ptr)) != NULL)
163	    auth_info.file_count++;
164	}
165      }
166    }
167  }
168  fclose(fp);
169}
170
171
172/*
173 * authenticate()
174 * Starts an auth_script() for the given <user>, with a class <class>,
175 * style <style>, and service <service>.  <style> is necessary,
176 * as are <user> and <class>, but <service> is optional -- it defaults
177 * to "login".
178 * Since auth_script() expects an execl'able program name, authenticate()
179 * also concatenates <style> to _PATH_AUTHPROG.
180 * Lastly, calls auth_scan(AUTH_NONE) to see if there are any "reject" statements,
181 * or lack of "auth" statements.
182 * Returns -1 on error, 0 on rejection, and >0 on success.
183 * (See AUTH_* for the return values.)
184 *
185 */
186int
187authenticate(const char * name, const char * class, const char * style, const char *service)
188{
189  int retval;
190
191  if (style == NULL || *style == '\0')
192    retval = -1;
193  else {
194    char buf[sizeof(_PATH_AUTHPROG) + 64];
195
196    if (service == NULL || *service == '\0')
197      service = LOGIN_DEFSERVICE;
198
199    free_auth_info();
200
201    if (snprintf(buf, sizeof buf, _PATH_AUTHPROG "%s", style) >= sizeof buf)
202      retval = -1;
203    else {
204      retval = auth_script(buf, style, "-s", service, name, class, NULL);
205      if (retval >= 0)
206	retval = auth_scan(AUTH_NONE);
207    }
208  }
209  return retval;
210}
211
212
213/*
214 * auth_script()
215 * Runs an authentication program with specified arguments.
216 * It sets up file descriptor 3 for the program to write to;
217 * it stashes the output somewhere.  The output of the program
218 * consists of statements:
219 *	reject
220 *	authorize [root|secure]
221 *	setenv <name> [<value>]
222 *	remove <file>
223 *
224 * Terribly exciting, isn't it?  There is no limit specified in
225 * BSDi's API for how much output can be present, but we should
226 * keep it fairly small, I think.
227 * No more than AUTHMAXLINES lines.
228 */
229
230int
231auth_script(const char * path, ...)
232{
233  va_list ap;
234  int pid, status;
235  int argc = 0;
236  int p[2];	/* pipes */
237  char *argv[AUTHMAXARGS+1];
238
239  va_start(ap, path);
240  while (argc < AUTHMAXARGS && (argv[argc++] = va_arg(ap, char*)) != NULL)
241    ;
242  argv[argc] = NULL;
243  va_end(ap);
244
245  fflush(NULL);
246
247  if (pipe(p) >= 0) {
248    if ((pid = fork()) == -1) {
249      close(p[0]);
250      close(p[1]);
251    } else if (pid == 0) {    /* Child */
252      close(p[0]);
253      dup2(p[1], 3);
254      if (setenv("PATH", _PATH_DEFPATH, 1)==0 && setenv("SHELL", _PATH_BSHELL, 1)==0)
255	execv(path, argv);
256      _exit(1);
257    } else {
258      close(p[1]);
259      collect_info(p[0]);
260      if (waitpid(pid, &status, 0) != -1 && WIFEXITED(status) && !WEXITSTATUS(status))
261	return 0;
262    }
263  }
264  return -1;
265}
266
267
268/*
269 * auth_env()
270 * Processes the stored "setenv" lines from the stored authentication
271 * output.
272 */
273
274int
275auth_env(void)
276{
277  int i;
278
279  for (i = 0; i < auth_info.env_count; i++) {
280    char *nam = auth_info.env[i];
281    char *ptr = nam + strcspn(nam, " \t=");
282    if (*ptr) {
283      *ptr++ = '\0';
284      ptr += strspn(ptr, " \t");
285    }
286    setenv(nam, ptr, 1);
287  }
288  return 0;
289}
290
291
292/*
293 * auth_scan()
294 * Goes through the output of the auth_script/authenticate, and
295 * checks for a failure or authentication.
296 * <ok> is a default authentication value -- if there are no
297 * rejection or authentication statements, then it is returned
298 * unmodified.
299 * AUTH_NONE is returned if there were any reject statements
300 * from the authentication program (invoked by auth_script()), and
301 * AUTH, AUTH_ROOTOKAY, and/or AUTH_SECURE are returned if the
302 * appropriate directives were found.  Note that AUTH* are
303 * *bitmasks*!
304 */
305
306int
307auth_scan(int ok)
308{
309  if (auth_info.reject)
310    return 0;
311  return ok | auth_info.auths;
312}
313
314
315/*
316 * auth_rmfiles()
317 * Removes any files that the authentication program said needed to be
318 * removed, said files having come from a previous execution of
319 * auth_script().
320 */
321
322int
323auth_rmfiles(void)
324{
325  int i = auth_info.file_count;
326  while (i-- > 0) {
327    unlink(auth_info.files[i]);
328    free(auth_info.files[i]);
329    auth_info.files[i] = NULL;
330  }
331  return 0;
332}
333
334
335/*
336 * auth_checknologin()
337 * Checks for the existance of a nologin file in the login_cap
338 * capability <lc>.  If there isn't one specified, then it checks
339 * to see if this class should just ignore nologin files.  Lastly,
340 * it tries to print out the default nologin file, and, if such
341 * exists, it exits.
342 */
343
344void
345auth_checknologin(login_cap_t *lc)
346{
347  char *file;
348
349  /* Do we ignore a nologin file? */
350  if (login_getcapbool(lc, "ignorenologin", 0))
351    return;
352
353  /* Note that <file> will be "" if there is no nologin capability */
354  if ((file = login_getcapstr(lc, "nologin", "", NULL)) == NULL)
355    exit(1);
356
357  /*
358   * *file is true IFF there was a "nologin" capability
359   * Note that auth_cat() returns 1 only if the specified
360   * file exists, and is readable.  E.g., /.nologin exists.
361   */
362  if ((*file && auth_cat(file)) || auth_cat(_PATH_NOLOGIN))
363    exit(1);
364}
365
366
367/*
368 * auth_cat()
369 * Checks for the readability of <file>; if it can be opened for
370 * reading, it prints it out to stdout, and then exits.  Otherwise,
371 * it returns 0 (meaning no nologin file).
372 */
373int
374auth_cat(const char *file)
375{
376  int fd, count;
377  char buf[BUFSIZ];
378
379  if ((fd = open(file, O_RDONLY)) < 0)
380    return 0;
381  while ((count = read(fd, buf, sizeof(buf))) > 0)
382    write(fileno(stdout), buf, count);
383  close(fd);
384  return 1;
385}
386