X-Git-Url: https://git.cameronkatri.com/pw-darwin.git/blobdiff_plain/994d63f5d34d47c81e16d301ed3fb8ed20ab037d..cb57f04cb26553c23feb9874da15b4386f68a98f:/libutil/pw_util.c diff --git a/libutil/pw_util.c b/libutil/pw_util.c index 1c163d2..6013e6d 100644 --- a/libutil/pw_util.c +++ b/libutil/pw_util.c @@ -1,6 +1,13 @@ /*- * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. + * Copyright (c) 2002 Networks Associates Technology, Inc. + * All rights reserved. + * + * Portions of this software were developed for the FreeBSD Project by + * ThinkSec AS and NAI Labs, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +17,6 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. @@ -51,8 +54,11 @@ static const char rcsid[] = #include #include +#include #include #include +#include +#include #include #include #include @@ -61,19 +67,16 @@ static const char rcsid[] = #include #include -#include "pw_util.h" +#include "libutil.h" -extern char *tempname; static pid_t editpid = -1; -static int lockfd; -static char _default_editor[] = _PATH_VI; -static char _default_mppath[] = _PATH_PWD; -static char _default_masterpasswd[] = _PATH_MASTERPASSWD; -char *mppath = _default_mppath; -char *masterpasswd = _default_masterpasswd; - -void pw_cont(int); +static int lockfd = -1; +static char masterpasswd[PATH_MAX]; +static char passwd_dir[PATH_MAX]; +static char tempname[PATH_MAX]; +static int initialized; +#if 0 void pw_cont(int sig) { @@ -81,12 +84,63 @@ pw_cont(int sig) if (editpid != -1) kill(editpid, sig); } +#endif -void -pw_init(void) +/* + * Initialize statics and set limits, signals & umask to try to avoid + * interruptions, crashes etc. that might expose passord data. + */ +int +pw_init(const char *dir, const char *master) { +#if 0 struct rlimit rlim; +#endif + struct stat st; + + if (dir == NULL) { + strcpy(passwd_dir, _PATH_ETC); + } else { + if (strlen(dir) >= sizeof(passwd_dir)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(passwd_dir, dir); + } + + if (master == NULL) { + if (dir == NULL) { + strcpy(masterpasswd, _PATH_MASTERPASSWD); + } else if (snprintf(masterpasswd, sizeof(masterpasswd), "%s/%s", + passwd_dir, _MASTERPASSWD) > (int)sizeof(masterpasswd)) { + errno = ENAMETOOLONG; + return (-1); + } + } else { + if (strlen(master) >= sizeof(masterpasswd)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(masterpasswd, master); + } + if (stat(masterpasswd, &st) == -1) + return (-1); + + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + return (-1); + } + + /* + * The code that follows is extremely disruptive to the calling + * process, and is therefore disabled until someone can conceive + * of a realistic scenario where it would fend off a compromise. + * Race conditions concerning the temporary files can be guarded + * against in other ways than masking signals (by checking stat(2) + * results after creation). + */ +#if 0 /* Unlimited resource limits. */ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; (void)setrlimit(RLIMIT_CPU, &rlim); @@ -110,11 +164,21 @@ pw_init(void) /* Create with exact permissions. */ (void)umask(0); +#endif + initialized = 1; + return (0); } +/* + * Lock the master password file. + */ int pw_lock(void) { + + if (*masterpasswd == '\0') + return (-1); + /* * If the master password file doesn't exist, the system is hosed. * Might as well try to build one. Set the close-on-exec bit so @@ -127,16 +191,22 @@ pw_lock(void) lockfd = open(masterpasswd, O_RDONLY, 0); if (lockfd < 0 || fcntl(lockfd, F_SETFD, 1) == -1) err(1, "%s", masterpasswd); - if (flock(lockfd, LOCK_EX|LOCK_NB)) - errx(1, "the password db file is busy"); + /* XXX vulnerable to race conditions */ + if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) { + if (errno == EWOULDBLOCK) { + errx(1, "the password db file is busy"); + } else { + err(1, "could not lock the passwd file: "); + } + } /* * If the password file was replaced while we were trying to * get the lock, our hardlink count will be 0 and we have to * close and retry. */ - if (fstat(lockfd, &st) < 0) - errx(1, "fstat() failed"); + if (fstat(lockfd, &st) == -1) + err(1, "fstat() failed: "); if (st.st_nlink != 0) break; close(lockfd); @@ -145,114 +215,462 @@ pw_lock(void) return (lockfd); } +/* + * Create and open a presumably safe temp file for editing the password + * data, and copy the master password file into it. + */ int -pw_tmp(void) +pw_tmp(int mfd) { - static char path[MAXPATHLEN]; - int fd; - char *p; + char buf[8192]; + ssize_t nr; + const char *p; + int tfd; - strncpy(path, masterpasswd, MAXPATHLEN - 1); - path[MAXPATHLEN] = '\0'; - - if ((p = strrchr(path, '/'))) + if (*masterpasswd == '\0') + return (-1); + if ((p = strrchr(masterpasswd, '/'))) ++p; else - p = path; - strcpy(p, "pw.XXXXXX"); - if ((fd = mkstemp(path)) == -1) - err(1, "%s", path); - tempname = path; - return (fd); + p = masterpasswd; + if (snprintf(tempname, sizeof(tempname), "%.*spw.XXXXXX", + (int)(p - masterpasswd), masterpasswd) >= (int)sizeof(tempname)) { + errno = ENAMETOOLONG; + return (-1); + } + if ((tfd = mkstemp(tempname)) == -1) + return (-1); + if (mfd != -1) { + while ((nr = read(mfd, buf, sizeof(buf))) > 0) + if (write(tfd, buf, (size_t)nr) != nr) + break; + if (nr != 0) { + unlink(tempname); + *tempname = '\0'; + close(tfd); + return (-1); + } + } + return (tfd); } +/* + * Regenerate the password database. + */ int -pw_mkdb(const char *username) +pw_mkdb(const char *user) { int pstat; pid_t pid; (void)fflush(stderr); - if (!(pid = fork())) { - if(!username) { - warnx("rebuilding the database..."); - execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", "-d", mppath, - tempname, (char *)NULL); - } else { - warnx("updating the database..."); - execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", "-d", mppath, - "-u", username, tempname, (char *)NULL); - } - pw_error(_PATH_PWD_MKDB, 1, 1); + switch ((pid = fork())) { + case -1: + return (-1); + case 0: + /* child */ + if (user == NULL) + execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", + "-d", passwd_dir, tempname, (char *)NULL); + else + execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", + "-d", passwd_dir, "-u", user, tempname, + (char *)NULL); + _exit(1); + /* NOTREACHED */ + default: + /* parent */ + break; } - pid = waitpid(pid, &pstat, 0); - if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0) + if (waitpid(pid, &pstat, 0) == -1) + return (-1); + if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) return (0); - warnx("done"); - return (1); + errno = 0; + return (-1); } -void +/* + * Edit the temp file. Return -1 on error, >0 if the file was modified, 0 + * if it was not. + */ +int pw_edit(int notsetuid) { + struct sigaction sa, sa_int, sa_quit; + sigset_t oldsigset, nsigset; + struct stat st1, st2; + const char *editor; int pstat; - char *p, *editor; - - if (!(editor = getenv("EDITOR"))) - editor = _default_editor; - if ((p = strrchr(editor, '/'))) - ++p; - else - p = editor; - if (!(editpid = fork())) { + if ((editor = getenv("EDITOR")) == NULL) + editor = _PATH_VI; + if (stat(tempname, &st1) == -1) + return (-1); + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, &sa_int); + sigaction(SIGQUIT, &sa, &sa_quit); + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGCHLD); + sigprocmask(SIG_BLOCK, &nsigset, &oldsigset); + switch ((editpid = fork())) { + case -1: + return (-1); + case 0: + sigaction(SIGINT, &sa_int, NULL); + sigaction(SIGQUIT, &sa_quit, NULL); + sigprocmask(SIG_SETMASK, &oldsigset, NULL); if (notsetuid) { (void)setgid(getgid()); (void)setuid(getuid()); } errno = 0; - execlp(editor, p, tempname, (char *)NULL); + execlp(editor, basename(editor), tempname, (char *)NULL); _exit(errno); + default: + /* parent */ + break; } for (;;) { - editpid = waitpid(editpid, (int *)&pstat, WUNTRACED); - errno = WEXITSTATUS(pstat); - if (editpid == -1) - pw_error(editor, 1, 1); - else if (WIFSTOPPED(pstat)) + if (waitpid(editpid, &pstat, WUNTRACED) == -1) { + if (errno == EINTR) + continue; + unlink(tempname); + editpid = -1; + break; + } else if (WIFSTOPPED(pstat)) { raise(WSTOPSIG(pstat)); - else if (WIFEXITED(pstat) && errno == 0) + } else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) { + editpid = -1; break; - else - pw_error(editor, 1, 1); + } else { + unlink(tempname); + editpid = -1; + break; + } } - editpid = -1; + sigaction(SIGINT, &sa_int, NULL); + sigaction(SIGQUIT, &sa_quit, NULL); + sigprocmask(SIG_SETMASK, &oldsigset, NULL); + if (stat(tempname, &st2) == -1) + return (-1); + return (st1.st_mtim.tv_sec != st2.st_mtim.tv_sec || + st1.st_mtim.tv_nsec != st2.st_mtim.tv_nsec); } +/* + * Clean up. Preserve errno for the caller's convenience. + */ void -pw_prompt(void) +pw_fini(void) { - int c, first; - - (void)printf("re-edit the password file? [y]: "); - (void)fflush(stdout); - first = c = getchar(); - while (c != '\n' && c != EOF) - c = getchar(); - if (first == 'n') - pw_error(NULL, 0, 0); + int serrno, status; + + if (!initialized) + return; + initialized = 0; + serrno = errno; + if (editpid != -1) { + kill(editpid, SIGTERM); + kill(editpid, SIGCONT); + waitpid(editpid, &status, 0); + editpid = -1; + } + if (*tempname != '\0') { + unlink(tempname); + *tempname = '\0'; + } + if (lockfd != -1) + close(lockfd); + errno = serrno; } -void -pw_error(const char *name, int error, int eval) +/* + * Compares two struct pwds. + */ +int +pw_equal(const struct passwd *pw1, const struct passwd *pw2) { - if (error) { - if (name != NULL) - warn("%s", name); - else - warn(NULL); + return (strcmp(pw1->pw_name, pw2->pw_name) == 0 && + pw1->pw_uid == pw2->pw_uid && + pw1->pw_gid == pw2->pw_gid && + strcmp(pw1->pw_class, pw2->pw_class) == 0 && + pw1->pw_change == pw2->pw_change && + pw1->pw_expire == pw2->pw_expire && + strcmp(pw1->pw_gecos, pw2->pw_gecos) == 0 && + strcmp(pw1->pw_dir, pw2->pw_dir) == 0 && + strcmp(pw1->pw_shell, pw2->pw_shell) == 0); +} + +/* + * Make a passwd line out of a struct passwd. + */ +char * +pw_make(const struct passwd *pw) +{ + char *line; + + asprintf(&line, "%s:%s:%ju:%ju:%s:%ju:%ju:%s:%s:%s", pw->pw_name, + pw->pw_passwd, (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid, + pw->pw_class, (uintmax_t)pw->pw_change, (uintmax_t)pw->pw_expire, + pw->pw_gecos, pw->pw_dir, pw->pw_shell); + return (line); +} + +/* + * Make a passwd line (in v7 format) out of a struct passwd + */ +char * +pw_make_v7(const struct passwd *pw) +{ + char *line; + + asprintf(&line, "%s:*:%ju:%ju:%s:%s:%s", pw->pw_name, + (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid, + pw->pw_gecos, pw->pw_dir, pw->pw_shell); + return (line); +} + +/* + * Copy password file from one descriptor to another, replacing, deleting + * or adding a single record on the way. + */ +int +pw_copy(int ffd, int tfd, const struct passwd *pw, struct passwd *old_pw) +{ + char buf[8192], *end, *line, *p, *q, *r, t; + struct passwd *fpw; + const struct passwd *spw; + size_t len; + int eof, readlen; + + if (old_pw == NULL && pw == NULL) + return (-1); + + spw = old_pw; + /* deleting a user */ + if (pw == NULL) { + line = NULL; + } else { + if ((line = pw_make(pw)) == NULL) + return (-1); + } + + /* adding a user */ + if (spw == NULL) + spw = pw; + + eof = 0; + len = 0; + p = q = end = buf; + for (;;) { + /* find the end of the current line */ + for (p = q; q < end && *q != '\0'; ++q) + if (*q == '\n') + break; + + /* if we don't have a complete line, fill up the buffer */ + if (q >= end) { + if (eof) + break; + if ((size_t)(q - p) >= sizeof(buf)) { + warnx("passwd line too long"); + errno = EINVAL; /* hack */ + goto err; + } + if (p < end) { + q = memmove(buf, p, end - p); + end -= p - buf; + } else { + p = q = end = buf; + } + readlen = read(ffd, end, sizeof(buf) - (end - buf)); + if (readlen == -1) + goto err; + else + len = (size_t)readlen; + if (len == 0 && p == buf) + break; + end += len; + len = end - buf; + if (len < (ssize_t)sizeof(buf)) { + eof = 1; + if (len > 0 && buf[len - 1] != '\n') + ++len, *end++ = '\n'; + } + continue; + } + + /* is it a blank line or a comment? */ + for (r = p; r < q && isspace(*r); ++r) + /* nothing */ ; + if (r == q || *r == '#') { + /* yep */ + if (write(tfd, p, q - p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + + /* is it the one we're looking for? */ + + t = *q; + *q = '\0'; + + fpw = pw_scan(r, PWSCAN_MASTER); + + /* + * fpw is either the struct passwd for the current line, + * or NULL if the line is malformed. + */ + + *q = t; + if (fpw == NULL || strcmp(fpw->pw_name, spw->pw_name) != 0) { + /* nope */ + if (fpw != NULL) + free(fpw); + if (write(tfd, p, q - p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + if (old_pw && !pw_equal(fpw, old_pw)) { + warnx("entry inconsistent"); + free(fpw); + errno = EINVAL; /* hack */ + goto err; + } + free(fpw); + + /* it is, replace or remove it */ + if (line != NULL) { + len = strlen(line); + if (write(tfd, line, len) != (int)len) + goto err; + } else { + /* when removed, avoid the \n */ + q++; + } + /* we're done, just copy the rest over */ + for (;;) { + if (write(tfd, q, end - q) != end - q) + goto err; + q = buf; + readlen = read(ffd, buf, sizeof(buf)); + if (readlen == 0) + break; + else + len = (size_t)readlen; + if (readlen == -1) + goto err; + end = buf + len; + } + goto done; + } + + /* if we got here, we didn't find the old entry */ + if (line == NULL) { + errno = ENOENT; + goto err; + } + len = strlen(line); + if ((size_t)write(tfd, line, len) != len || + write(tfd, "\n", 1) != 1) + goto err; + done: + if (line != NULL) + free(line); + return (0); + err: + if (line != NULL) + free(line); + return (-1); +} + +/* + * Return the current value of tempname. + */ +const char * +pw_tempname(void) +{ + + return (tempname); +} + +/* + * Duplicate a struct passwd. + */ +struct passwd * +pw_dup(const struct passwd *pw) +{ + char *dst; + struct passwd *npw; + ssize_t len; + + len = sizeof(*npw); + if (pw->pw_name != NULL) + len += strlen(pw->pw_name) + 1; + if (pw->pw_passwd != NULL) + len += strlen(pw->pw_passwd) + 1; + if (pw->pw_class != NULL) + len += strlen(pw->pw_class) + 1; + if (pw->pw_gecos != NULL) + len += strlen(pw->pw_gecos) + 1; + if (pw->pw_dir != NULL) + len += strlen(pw->pw_dir) + 1; + if (pw->pw_shell != NULL) + len += strlen(pw->pw_shell) + 1; + if ((npw = malloc((size_t)len)) == NULL) + return (NULL); + memcpy(npw, pw, sizeof(*npw)); + dst = (char *)npw + sizeof(*npw); + if (pw->pw_name != NULL) { + npw->pw_name = dst; + dst = stpcpy(npw->pw_name, pw->pw_name) + 1; + } + if (pw->pw_passwd != NULL) { + npw->pw_passwd = dst; + dst = stpcpy(npw->pw_passwd, pw->pw_passwd) + 1; + } + if (pw->pw_class != NULL) { + npw->pw_class = dst; + dst = stpcpy(npw->pw_class, pw->pw_class) + 1; + } + if (pw->pw_gecos != NULL) { + npw->pw_gecos = dst; + dst = stpcpy(npw->pw_gecos, pw->pw_gecos) + 1; + } + if (pw->pw_dir != NULL) { + npw->pw_dir = dst; + dst = stpcpy(npw->pw_dir, pw->pw_dir) + 1; + } + if (pw->pw_shell != NULL) { + npw->pw_shell = dst; + dst = stpcpy(npw->pw_shell, pw->pw_shell) + 1; + } + return (npw); +} + +#include "pw_scan.h" + +/* + * Wrapper around an internal libc function + */ +struct passwd * +pw_scan(const char *line, int flags) +{ + struct passwd pw, *ret; + char *bp; + + if ((bp = strdup(line)) == NULL) + return (NULL); + if (!__pw_scan(bp, &pw, flags)) { + free(bp); + return (NULL); } - warnx("password information unchanged"); - (void)unlink(tempname); - exit(eval); + ret = pw_dup(&pw); + free(bp); + return (ret); }