diff options
Diffstat (limited to 'lib/libutil/login_auth.c')
-rw-r--r-- | lib/libutil/login_auth.c | 715 |
1 files changed, 500 insertions, 215 deletions
diff --git a/lib/libutil/login_auth.c b/lib/libutil/login_auth.c index 565ddc3..695b006 100644 --- a/lib/libutil/login_auth.c +++ b/lib/libutil/login_auth.c @@ -4,6 +4,10 @@ * David Nugent <davidn@blaze.net.au> * All rights reserved. * + * Portions copyright (c) 1995,1997 by + * Berkeley Software Design, Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions * are met: @@ -21,17 +25,19 @@ * * Low-level routines relating to the user capabilities database * - * $Id$ + * $Id: login_auth.c,v 1.6 1997/02/22 15:08:18 peter Exp $ */ #include <sys/types.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/stat.h> +#include <sys/param.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <stdio.h> +#include <ctype.h> #include <pwd.h> #include <stdlib.h> #include <string.h> @@ -40,297 +46,575 @@ #include <login_cap.h> #include <stdarg.h> #include <paths.h> +#include <sys/socket.h> #include <sys/wait.h> +#include <err.h> +#include <libutil.h> + +#ifdef LOGIN_CAP_AUTH +/* + * Comment from BSDI's authenticate.c module: + * NOTE: THIS MODULE IS TO BE DEPRECATED. FUTURE VERSIONS OF BSD/OS WILL + * HAVE AN UPDATED API, THOUGH THESE FUNCTIONS WILL CONTINUE TO BE AVAILABLE + * FOR BACKWARDS COMPATABILITY + */ -#ifdef RLIM_LONG -# define STRTOV strtol -#else -# define STRTOV strtoq -#endif -#define AUTHMAXLINES 1024 -#define AUTHMAXARGS 16 +#define AUTHMAXSPOOL (8 * 1024) /* Max size of authentication data */ +#define AUTHCOMM_FD 3 /* Handle used to read/write auth data */ + +struct rmfiles { + struct rmfiles *next; + char file[1]; +}; -struct auth_info { - int reject; - int auths; - int env_count; - char **env; - int file_count; - char **files; +struct authopts { + struct authopts *next; + char opt[1]; }; -static struct auth_info auth_info; +static char *spoolbuf = NULL; +static int spoolidx = 0; +static struct rmfiles *rmfirst = NULL; +static struct authopts *optfirst = NULL; + /* - * free_auth_info() - * Go through the auth_info structure, and free() anything of interest. - * This includes the string arrays, and any individual element. - * All part of being environmentally conscious ;). + * Setup a known environment for all authentication scripts. */ -static void -free_auth_info(void) +static char *auth_environ[] = { + "PATH=" _PATH_DEFPATH, + "SHELL=" _PATH_BSHELL, + NULL, +}; + + + +/* + * nextline() + * Get the next line from the data buffer collected from + * the authentication program. This function relies on the + * fact that lines are nul terminated. + */ + +static char * +nextline(int *idx) { - int i; - - auth_info.reject = 0; - auth_info.auths = 0; - if (auth_info.env) { - for (i = 0; i < auth_info.env_count; i++) { - if (auth_info.env[i]) - free(auth_info.env[i]); - } - free(auth_info.env); - auth_info.env = NULL; - } - if (auth_info.files) { - for (i = 0; i < auth_info.file_count; i++) { - if (auth_info.files[i]) - free(auth_info.files[i]); + char *ptr = NULL; + + if (spoolbuf != NULL && *idx < spoolidx) { + ptr = spoolbuf + *idx; + *idx += strlen(ptr) + 1; } - free(auth_info.files); - auth_info.files = NULL; - } + return ptr; } /* - * collect_info() - * Read from <fd>, a list of authorization commands. - * These commands are: - * reject - * authorize [root|secure] - * setenv <name>[ <value>] - * remove <file> - * A single reject means the entire thing is bad; - * multiple authorize statements can be present (it would be - * silly, but that's what the spec says). - * The commands are collected, and are accted upon by: - * auth_scan() -- check for authorization or rejection - * auth_rmfiles() -- remove the specified files - * auth_env() -- set the specified environment variables - * We only get up to AUTHMAXLINES lines of input from the program. + * spooldata() + * Read data returned on authentication backchannel and + * stuff it into our spool buffer. We also replace \n with nul + * to make parsing easier later. */ -#define STRSIZEOF(x) (sizeof(x)-1) -static void -collect_info(int fd) + +static int +spooldata(int fd) { - char *line; - FILE *fp; - char *ptr; - size_t len; - int line_count = 0; - - fp = fdopen(fd, "r"); - - while ((line = fgetln(fp, &len)) != NULL) { - if (++line_count > AUTHMAXLINES) - break; - if (len && line[len-1] == '\n') - --len; - line[len] = '\0'; /* Terminate */ - if (strncasecmp(line, BI_REJECT, STRSIZEOF(BI_REJECT)) == 0) { - auth_info.reject = 1; - } else if (strncasecmp(line, BI_AUTH, STRSIZEOF(BI_AUTH)) == 0) { - ptr = line + STRSIZEOF(BI_AUTH); - ptr += strspn(ptr, " \t"); - if (!*ptr) - auth_info.auths |= AUTH_OKAY; - else if (strncasecmp(ptr, BI_ROOTOKAY, STRSIZEOF(BI_ROOTOKAY)) == 0) - auth_info.auths |= AUTH_ROOTOKAY; - else if (strncasecmp(ptr, BI_SECURE, STRSIZEOF(BI_SECURE)) == 0) - auth_info.auths |= AUTH_SECURE; - } else if (strncasecmp(line, BI_SETENV, STRSIZEOF(BI_SETENV)) == 0) { - ptr = line + STRSIZEOF(BI_SETENV); - ptr += strspn(ptr, " \t"); - if (*ptr) { - char **tmp = realloc(auth_info.env, sizeof(char*) * (auth_info.env_count + 1)); - if (tmp != NULL) { - auth_info.env = tmp; - if ((auth_info.env[auth_info.env_count] = strdup(ptr)) != NULL) - auth_info.env_count++; - } - } - } else if (strncasecmp(line, BI_REMOVE, STRSIZEOF(BI_REMOVE)) == 0) { - ptr = line + STRSIZEOF(BI_REMOVE); - ptr += strspn(ptr, " \t"); - if (*ptr) { - char **tmp = realloc(auth_info.files, sizeof(char*) * (auth_info.file_count + 1)); - if (tmp != NULL) { - auth_info.files = tmp; - if ((auth_info.files[auth_info.file_count] = strdup(ptr)) != NULL) - auth_info.file_count++; + + if (spoolbuf) + free(spoolbuf); + spoolidx = 0; + + if (spoolbuf == NULL && (spoolbuf = malloc(AUTHMAXSPOOL)) == NULL) + syslog(LOG_ERR, "authbuffer malloc: %m"); + + else while (spoolidx < sizeof(spoolbuf) - 1) { + int r = read(fd, spoolbuf + spoolidx, sizeof(spoolbuf)-spoolidx); + char *b; + + if (r <= 0) { + spoolbuf[spoolidx] = '\0'; + return 0; } - } + /* + * Convert newlines into NULs to allow + * easier scanning of the file. + */ + while ((b = memchr(spoolbuf + spoolidx, '\n', r)) != NULL) + *b = '\0'; + spoolidx += r; } - } - fclose(fp); + return -1; } - + /* - * authenticate() + * auth_check() * Starts an auth_script() for the given <user>, with a class <class>, * style <style>, and service <service>. <style> is necessary, * as are <user> and <class>, but <service> is optional -- it defaults * to "login". * Since auth_script() expects an execl'able program name, authenticate() * also concatenates <style> to _PATH_AUTHPROG. - * Lastly, calls auth_scan(AUTH_NONE) to see if there are any "reject" statements, + * Lastly, calls auth_scan(0) to see if there are any "reject" statements, * or lack of "auth" statements. * Returns -1 on error, 0 on rejection, and >0 on success. * (See AUTH_* for the return values.) * */ + int -authenticate(const char * name, const char * class, const char * style, const char *service) +auth_check(const char *name, const char *clss, const char *style, + const char *service, int *status) { - int retval; + int _status; - if (style == NULL || *style == '\0') - retval = -1; - else { - char buf[sizeof(_PATH_AUTHPROG) + 64]; + if (status == NULL) + status = &_status; + *status = 0; - if (service == NULL || *service == '\0') - service = LOGIN_DEFSERVICE; + if (style != NULL) { + char path[MAXPATHLEN]; - free_auth_info(); + if (service == NULL) + service = LOGIN_DEFSERVICE; - if (snprintf(buf, sizeof buf, _PATH_AUTHPROG "%s", style) >= sizeof buf) - retval = -1; - else { - retval = auth_script(buf, style, "-s", service, name, class, NULL); - if (retval >= 0) - retval = auth_scan(AUTH_NONE); + snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); + if (auth_script(path, style, "-s", service, name, clss, 0)) + status = 0; + else + *status = auth_scan(0); + + return *status & AUTH_ALLOW; + } + return -1; +} + + +int +auth_response(const char *name, const char *class, const char *style, + const char *service, int *status, + const char *challenge, const char *response) +{ + int _status; + + if (status == NULL) + status = &_status; + *status = 0; + + if (style != NULL) { + int datalen; + char *data; + + if (service == NULL) + service = LOGIN_DEFSERVICE; + + datalen = strlen(challenge) + strlen(response) + 2; + + if ((data = malloc(datalen)) == NULL) { + syslog(LOG_ERR, "auth_response: %m"); + warnx("internal resource failure"); + } else { + char path[MAXPATHLEN]; + + snprintf(data, datalen, "%s%c%s", challenge, 0, response); + snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); + if (auth_script_data(data, datalen, path, style, "-s", service, + name, class, 0)) + *status = 0; + else + *status = auth_scan(0); + free(data); + return (*status & AUTH_ALLOW); + } + } + return -1; +} + + +int +auth_approve(login_cap_t *lc, const char *name, const char *service) +{ + int r = -1; + char path[MAXPATHLEN]; + + if (lc == NULL) { + if (strlen(name) > MAXPATHLEN) { + syslog(LOG_ERR, "%s: username too long", name); + warnx("username too long"); + } else { + struct passwd *pwd; + char *p; + + pwd = getpwnam(name); + if (pwd == NULL && (p = strchr(name, '.')) != NULL) { + int i = p - name; + + if (i >= MAXPATHLEN) + i = MAXPATHLEN - 1; + strncpy(path, name, i); + path[i] = '\0'; + pwd = getpwnam(path); /* Fixed bug in BSDI code... */ + } + if ((lc = login_getpwclass(pwd ? pwd->pw_class : NULL)) == NULL) + warnx("unable to classify user '%s'", name); + } + } + + if (lc != NULL) { + char *approve; + char *s; + + if (service != NULL) + service = LOGIN_DEFSERVICE; + + snprintf(path, sizeof(path), "approve-%s", service); + + if ((approve = login_getcapstr(lc, s = path, NULL, NULL)) == NULL && + (approve = login_getcapstr(lc, s = "approve", NULL, NULL)) == NULL) + r = AUTH_OKAY; + else { + + if (approve[0] != '/') { + syslog(LOG_ERR, "Invalid %s script: %s", s, approve); + warnx("invalid path to approval script"); + } else { + char *s; + + s = strrchr(approve, '/') + 1; + if (auth_script(approve, s, name, + lc->lc_class, service, 0) == 0 && + (r = auth_scan(AUTH_OKAY) & AUTH_ALLOW) != 0) + auth_env(); + } + } + } + return r; +} + + +void +auth_env(void) +{ + int idx = 0; + char *line; + + while ((line = nextline(&idx)) != NULL) { + if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) { + line += sizeof(BI_SETENV) - 1; + if (*line && isspace(*line)) { + char *name; + char ch, *p; + + while (*line && isspace(*line)) + ++line; + name = line; + while (*line && !isspace(*line)) + ++line; + ch = *(p = line); + if (*line) + ++line; + if (setenv(name, line, 1)) + warn("setenv(%s, %s)", name, line); + *p = ch; + } + } + } +} + + +char * +auth_value(const char *what) +{ + int idx = 0; + char *line; + + while ((line = nextline(&idx)) != NULL) { + if (!strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1)) { + char *name; + + line += sizeof(BI_VALUE) - 1; + while (*line && isspace(*line)) + ++line; + name = line; + if (*line) { + int i; + char ch, *p; + + ch = *(p = line); + *line++ = '\0'; + i = strcmp(name, what); + *p = ch; + if (i == 0) + return auth_mkvalue(line); + } + } + } + return NULL; +} + +char * +auth_mkvalue(const char *value) +{ + char *big, *p; + + big = malloc(strlen(value) * 4 + 1); + if (big != NULL) { + for (p = big; *value; ++value) { + switch (*value) { + case '\r': + *p++ = '\\'; + *p++ = 'r'; + break; + case '\n': + *p++ = '\\'; + *p++ = 'n'; + break; + case '\\': + *p++ = '\\'; + *p++ = *value; + break; + case '\t': + case ' ': + if (p == big) + *p++ = '\\'; + *p++ = *value; + break; + default: + if (!isprint(*value)) { + *p++ = '\\'; + *p++ = ((*value >> 6) & 0x3) + '0'; + *p++ = ((*value >> 3) & 0x7) + '0'; + *p++ = ((*value ) & 0x7) + '0'; + } else + *p++ = *value; + break; + } + } + *p = '\0'; + big = realloc(big, strlen(big) + 1); + } + return big; +} + + +#define NARGC 63 +static int +_auth_script(const char *data, int nbytes, const char *path, va_list ap) +{ + int r, argc, status; + int pfd[2]; + pid_t pid; + struct authopts *e; + char *argv[NARGC+1]; + + r = -1; + argc = 0; + for (e = optfirst; argc < (NARGC - 1) && e != NULL; e = e->next) { + argv[argc++] = "-v"; + argv[argc++] = e->opt; + } + while (argc < NARGC && (argv[argc] = va_arg(ap, char *)) != NULL) + ++argc; + argv[argc] = NULL; + + if (argc >= NARGC && va_arg(ap, char *)) + syslog(LOG_ERR, "too many arguments"); + else if (_secure_path(path, 0, 0) < 0) { + syslog(LOG_ERR, "%s: path not secure", path); + warnx("invalid script: %s", path); + } else if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) { + syslog(LOG_ERR, "unable to create backchannel %m"); + warnx("internal resource failure"); + } else switch (pid = fork()) { + case -1: /* fork() failure */ + close(pfd[0]); + close(pfd[1]); + syslog(LOG_ERR, "fork %s: %m", path); + warnx("internal resource failure"); + break; + case 0: /* child process */ + close(pfd[0]); + if (pfd[1] != AUTHCOMM_FD) { + if (dup2(pfd[1], AUTHCOMM_FD) < 0) + err(1, "dup backchannel"); + close(pfd[1]); + } + for (r = getdtablesize(); --r > AUTHCOMM_FD; ) + close(r); + execve(path, argv, auth_environ); + syslog(LOG_ERR, "exec %s: %m", path); + err(1, path); + default: /* parent */ + close(pfd[1]); + if (data && nbytes) + write(pfd[0], data, nbytes); + r = spooldata(pfd[0]); + close(pfd[0]); + if (waitpid(pid, &status, 0) < 0) { + syslog(LOG_ERR, "%s: waitpid: %m", path); + warnx("internal failure"); + r = -1; + } else { + if (r != 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) + r = -1; + } + /* kill the buffer if it is of no use */ + if (r != 0) { + free(spoolbuf); + spoolbuf = NULL; + spoolidx = 0; + } + break; } - } - return retval; + return r; } + /* * auth_script() * Runs an authentication program with specified arguments. * It sets up file descriptor 3 for the program to write to; * it stashes the output somewhere. The output of the program * consists of statements: - * reject + * reject [challenge|silent] * authorize [root|secure] * setenv <name> [<value>] * remove <file> * - * Terribly exciting, isn't it? There is no limit specified in - * BSDi's API for how much output can be present, but we should - * keep it fairly small, I think. - * No more than AUTHMAXLINES lines. + * Terribly exciting, isn't it? + * Output cannot exceed AUTHMAXSPOOL characters. */ int -auth_script(const char * path, ...) +auth_script(const char *path, ...) { - va_list ap; - int pid, status; - int argc = 0; - int p[2]; /* pipes */ - char *argv[AUTHMAXARGS+1]; - - va_start(ap, path); - while (argc < AUTHMAXARGS && (argv[argc++] = va_arg(ap, char*)) != NULL) - ; - argv[argc] = NULL; - va_end(ap); - - fflush(NULL); - - if (pipe(p) >= 0) { - if ((pid = fork()) == -1) { - close(p[0]); - close(p[1]); - } else if (pid == 0) { /* Child */ - close(p[0]); - dup2(p[1], 3); - if (setenv("PATH", _PATH_DEFPATH, 1)==0 && setenv("SHELL", _PATH_BSHELL, 1)==0) - execv(path, argv); - _exit(1); - } else { - close(p[1]); - collect_info(p[0]); - if (waitpid(pid, &status, 0) != -1 && WIFEXITED(status) && !WEXITSTATUS(status)) - return 0; - } - } - return -1; -} + int r; + va_list ap; + va_start(ap, path); + r = _auth_script(NULL, 0, path, ap); + va_end(ap); + return r; +} -/* - * auth_env() - * Processes the stored "setenv" lines from the stored authentication - * output. - */ int -auth_env(void) +auth_script_data(const char *data, int nbytes, const char *path, ...) +{ + int r; + va_list ap; + + va_start(ap, path); + r = _auth_script(data, nbytes, path, ap); + va_end(ap); + return r; +} + + +static void +add_rmlist(const char *file) { - int i; - - for (i = 0; i < auth_info.env_count; i++) { - char *nam = auth_info.env[i]; - char *ptr = nam + strcspn(nam, " \t="); - if (*ptr) { - *ptr++ = '\0'; - ptr += strspn(ptr, " \t"); + struct rmfiles *rm; + + if ((rm = malloc(sizeof(struct rmfiles) + strlen(file) + 1)) == NULL) + syslog(LOG_ERR, "add_rmfile malloc: %m"); + else { + strcpy(rm->file, file); + rm->next = rmfirst; + rmfirst = rm; } - setenv(nam, ptr, 1); - } - return 0; } -/* - * auth_scan() - * Goes through the output of the auth_script/authenticate, and - * checks for a failure or authentication. - * <ok> is a default authentication value -- if there are no - * rejection or authentication statements, then it is returned - * unmodified. - * AUTH_NONE is returned if there were any reject statements - * from the authentication program (invoked by auth_script()), and - * AUTH, AUTH_ROOTOKAY, and/or AUTH_SECURE are returned if the - * appropriate directives were found. Note that AUTH* are - * *bitmasks*! - */ +int +auth_scan(int okay) +{ + int idx = 0; + char *line; + + while ((line = nextline(&idx)) != NULL) { + if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) { + line += sizeof(BI_REJECT) - 1; + while (*line && isspace(*line)) + ++line; + if (*line) { + if (!strcasecmp(line, "silent")) + return AUTH_SILENT; + if (!strcasecmp(line, "challenge")) + return AUTH_CHALLENGE; + } + return 0; + } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) { + line += sizeof(BI_AUTH) - 1; + while (*line && isspace(*line)) + ++line; + if (*line == '\0') + okay |= AUTH_OKAY; + else if (!strcasecmp(line, "root")) + okay |= AUTH_ROOTOKAY; + else if (!strcasecmp(line, "secure")) + okay |= AUTH_SECURE; + } + else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) { + line += sizeof(BI_REMOVE) - 1; + while (*line && isspace(*line)) + ++line; + if (*line) + add_rmlist(line); + } + } + + return okay; +} + int -auth_scan(int ok) +auth_setopt(const char *n, const char *v) { - if (auth_info.reject) - return 0; - return ok | auth_info.auths; + int r; + struct authopts *e; + + if ((e = malloc(sizeof(*e) + strlen(n) + strlen(v) + 1)) == NULL) + r = -1; + else { + sprintf(e->opt, "%s=%s", n, v); + e->next = optfirst; + optfirst = e; + r = 0; + } + return r; } -/* - * auth_rmfiles() - * Removes any files that the authentication program said needed to be - * removed, said files having come from a previous execution of - * auth_script(). - */ +void +auth_clropts(void) +{ + struct authopts *e; -int + while ((e = optfirst) != NULL) { + optfirst = e->next; + free(e); + } +} + + +void auth_rmfiles(void) { - int i = auth_info.file_count; - while (i-- > 0) { - unlink(auth_info.files[i]); - free(auth_info.files[i]); - auth_info.files[i] = NULL; - } - return 0; + struct rmfiles *rm; + + while ((rm = rmfirst) != NULL) { + unlink(rm->file); + rmfirst = rm->next; + free(rm); + } } +#endif + /* * auth_checknologin() @@ -370,6 +654,7 @@ auth_checknologin(login_cap_t *lc) * reading, it prints it out to stdout, and then exits. Otherwise, * it returns 0 (meaning no nologin file). */ + int auth_cat(const char *file) { @@ -379,7 +664,7 @@ auth_cat(const char *file) if ((fd = open(file, O_RDONLY)) < 0) return 0; while ((count = read(fd, buf, sizeof(buf))) > 0) - write(fileno(stdout), buf, count); + (void)write(fileno(stdout), buf, count); close(fd); return 1; } |