Index: stable/10/lib/libutil/gr_util.c =================================================================== --- stable/10/lib/libutil/gr_util.c (revision 285204) +++ stable/10/lib/libutil/gr_util.c (revision 285205) @@ -1,638 +1,656 @@ /*- * Copyright (c) 2008 Sean C. Farley * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int lockfd = -1; static char group_dir[PATH_MAX]; static char group_file[PATH_MAX]; static char tempname[PATH_MAX]; static int initialized; static size_t grmemlen(const struct group *, const char *, int *); static struct group *grcopy(const struct group *gr, char *mem, const char *, int ndx); /* * Initialize statics */ int gr_init(const char *dir, const char *group) { if (dir == NULL) { strcpy(group_dir, _PATH_ETC); } else { if (strlen(dir) >= sizeof(group_dir)) { errno = ENAMETOOLONG; return (-1); } strcpy(group_dir, dir); } if (group == NULL) { if (dir == NULL) { strcpy(group_file, _PATH_GROUP); } else if (snprintf(group_file, sizeof(group_file), "%s/group", group_dir) > (int)sizeof(group_file)) { errno = ENAMETOOLONG; return (-1); } } else { if (strlen(group) >= sizeof(group_file)) { errno = ENAMETOOLONG; return (-1); } strcpy(group_file, group); } initialized = 1; return (0); } /* * Lock the group file */ int gr_lock(void) { if (*group_file == '\0') return (-1); for (;;) { struct stat st; lockfd = flopen(group_file, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); if (lockfd == -1) { if (errno == EWOULDBLOCK) { errx(1, "the group file is busy"); } else { err(1, "could not lock the group file: "); } } if (fstat(lockfd, &st) == -1) err(1, "fstat() failed: "); if (st.st_nlink != 0) break; close(lockfd); lockfd = -1; } return (lockfd); } /* * Create and open a presmuably safe temp file for editing group data */ int gr_tmp(int mfd) { char buf[8192]; ssize_t nr; const char *p; int tfd; if (*group_file == '\0') return (-1); if ((p = strrchr(group_file, '/'))) ++p; else p = group_file; if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX", (int)(p - group_file), group_file) >= (int)sizeof(tempname)) { errno = ENAMETOOLONG; return (-1); } - if ((tfd = mkstemp(tempname)) == -1) + if ((tfd = mkostemp(tempname, O_SYNC)) == -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); } /* * Copy the group file from one descriptor to another, replacing, deleting * or adding a single record on the way. */ int gr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr) { char buf[8192], *end, *line, *p, *q, *r, t; struct group *fgr; const struct group *sgr; size_t len; int eof, readlen; if (old_gr == NULL && gr == NULL) return(-1); sgr = old_gr; /* deleting a group */ if (gr == NULL) { line = NULL; } else { if ((line = gr_make(gr)) == NULL) return (-1); } /* adding a group */ if (sgr == NULL) sgr = gr; 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("group 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'; fgr = gr_scan(r); /* fgr is either a struct group for the current line, * or NULL if the line is malformed. */ *q = t; if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) { /* nope */ if (fgr != NULL) free(fgr); if (write(tfd, p, q - p + 1) != q - p + 1) goto err; ++q; continue; } if (old_gr && !gr_equal(fgr, old_gr)) { warnx("entry inconsistent"); free(fgr); errno = EINVAL; /* hack */ goto err; } free(fgr); /* 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); } /* * Regenerate the group file */ int gr_mkdb(void) { + int fd; + if (chmod(tempname, 0644) != 0) return (-1); - return (rename(tempname, group_file)); + if (rename(tempname, group_file) != 0) + return (-1); + + /* + * Make sure new group file is safe on disk. To improve performance we + * will call fsync() to the directory where file lies + */ + if ((fd = open(group_dir, O_RDONLY|O_DIRECTORY)) == -1) + return (-1); + + if (fsync(fd) != 0) { + close(fd); + return (-1); + } + + close(fd); + return(0); } /* * Clean up. Preserves errno for the caller's convenience. */ void gr_fini(void) { int serrno; if (!initialized) return; initialized = 0; serrno = errno; if (*tempname != '\0') { unlink(tempname); *tempname = '\0'; } if (lockfd != -1) close(lockfd); errno = serrno; } /* * Compares two struct group's. */ int gr_equal(const struct group *gr1, const struct group *gr2) { int gr1_ndx; int gr2_ndx; /* Check that the non-member information is the same. */ if (gr1->gr_name == NULL || gr2->gr_name == NULL) { if (gr1->gr_name != gr2->gr_name) return (false); } else if (strcmp(gr1->gr_name, gr2->gr_name) != 0) return (false); if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) { if (gr1->gr_passwd != gr2->gr_passwd) return (false); } else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0) return (false); if (gr1->gr_gid != gr2->gr_gid) return (false); /* Check all members in both groups. * getgrnam can return gr_mem with a pointer to NULL. * gr_dup and gr_add strip out this superfluous NULL, setting * gr_mem to NULL for no members. */ if (gr1->gr_mem != NULL && gr2->gr_mem != NULL) { int i; for (i = 0; gr1->gr_mem[i] != NULL; i++) { if (strcmp(gr1->gr_mem[i], gr2->gr_mem[i]) != 0) return (false); } } /* Count number of members in both structs */ gr2_ndx = 0; if (gr2->gr_mem != NULL) for(; gr2->gr_mem[gr2_ndx] != NULL; gr2_ndx++) /* empty */; gr1_ndx = 0; if (gr1->gr_mem != NULL) for(; gr1->gr_mem[gr1_ndx] != NULL; gr1_ndx++) /* empty */; if (gr1_ndx != gr2_ndx) return (false); return (true); } /* * Make a group line out of a struct group. */ char * gr_make(const struct group *gr) { const char *group_line_format = "%s:%s:%ju:"; const char *sep; char *line; char *p; size_t line_size; int ndx; /* Calculate the length of the group line. */ line_size = snprintf(NULL, 0, group_line_format, gr->gr_name, gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1; if (gr->gr_mem != NULL) { for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) line_size += strlen(gr->gr_mem[ndx]) + 1; if (ndx > 0) line_size--; } /* Create the group line and fill it. */ if ((line = p = malloc(line_size)) == NULL) return (NULL); p += sprintf(p, group_line_format, gr->gr_name, gr->gr_passwd, (uintmax_t)gr->gr_gid); if (gr->gr_mem != NULL) { sep = ""; for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { p = stpcpy(p, sep); p = stpcpy(p, gr->gr_mem[ndx]); sep = ","; } } return (line); } /* * Duplicate a struct group. */ struct group * gr_dup(const struct group *gr) { return (gr_add(gr, NULL)); } /* * Add a new member name to a struct group. */ struct group * gr_add(const struct group *gr, const char *newmember) { char *mem; size_t len; int num_mem; num_mem = 0; len = grmemlen(gr, newmember, &num_mem); /* Create new group and copy old group into it. */ if ((mem = malloc(len)) == NULL) return (NULL); return (grcopy(gr, mem, newmember, num_mem)); } /* It is safer to walk the pointers given at gr_mem since there is no * guarantee the gr_mem + strings are contiguous in the given struct group * but compactify the new group into the following form. * * The new struct is laid out like this in memory. The example given is * for a group with two members only. * * { * (char *name) * (char *passwd) * (int gid) * (gr_mem * newgrp + sizeof(struct group) + sizeof(**)) points to gr_mem area * gr_mem area * (member1 *) * (member2 *) * (NULL) * (name string) * (passwd string) * (member1 string) * (member2 string) * } */ /* * Copy the contents of a group plus given name to a preallocated group struct */ static struct group * grcopy(const struct group *gr, char *dst, const char *name, int ndx) { int i; struct group *newgr; newgr = (struct group *)(void *)dst; /* avoid alignment warning */ dst += sizeof(*newgr); if (ndx != 0) { newgr->gr_mem = (char **)(void *)(dst); /* avoid alignment warning */ dst += (ndx + 1) * sizeof(*newgr->gr_mem); } else newgr->gr_mem = NULL; if (gr->gr_name != NULL) { newgr->gr_name = dst; dst = stpcpy(dst, gr->gr_name) + 1; } else newgr->gr_name = NULL; if (gr->gr_passwd != NULL) { newgr->gr_passwd = dst; dst = stpcpy(dst, gr->gr_passwd) + 1; } else newgr->gr_passwd = NULL; newgr->gr_gid = gr->gr_gid; i = 0; /* Original group struct might have a NULL gr_mem */ if (gr->gr_mem != NULL) { for (; gr->gr_mem[i] != NULL; i++) { newgr->gr_mem[i] = dst; dst = stpcpy(dst, gr->gr_mem[i]) + 1; } } /* If name is not NULL, newgr->gr_mem is known to be not NULL */ if (name != NULL) { newgr->gr_mem[i++] = dst; dst = stpcpy(dst, name) + 1; } /* if newgr->gr_mem is not NULL add NULL marker */ if (newgr->gr_mem != NULL) newgr->gr_mem[i] = NULL; return (newgr); } /* * Calculate length of a struct group + given name */ static size_t grmemlen(const struct group *gr, const char *name, int *num_mem) { size_t len; int i; if (gr == NULL) return (0); /* Calculate size of the group. */ len = sizeof(*gr); if (gr->gr_name != NULL) len += strlen(gr->gr_name) + 1; if (gr->gr_passwd != NULL) len += strlen(gr->gr_passwd) + 1; i = 0; if (gr->gr_mem != NULL) { for (; gr->gr_mem[i] != NULL; i++) { len += strlen(gr->gr_mem[i]) + 1; len += sizeof(*gr->gr_mem); } } if (name != NULL) { i++; len += strlen(name) + 1; len += sizeof(*gr->gr_mem); } /* Allow for NULL pointer */ if (i != 0) len += sizeof(*gr->gr_mem); *num_mem = i; return(len); } /* * Scan a line and place it into a group structure. */ static bool __gr_scan(char *line, struct group *gr) { char *loc; int ndx; /* Assign non-member information to structure. */ gr->gr_name = line; if ((loc = strchr(line, ':')) == NULL) return (false); *loc = '\0'; gr->gr_passwd = loc + 1; if (*gr->gr_passwd == ':') *gr->gr_passwd = '\0'; else { if ((loc = strchr(loc + 1, ':')) == NULL) return (false); *loc = '\0'; } if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1) return (false); /* Assign member information to structure. */ if ((loc = strchr(loc + 1, ':')) == NULL) return (false); line = loc + 1; gr->gr_mem = NULL; ndx = 0; do { gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) * (ndx + 1)); if (gr->gr_mem == NULL) return (false); /* Skip locations without members (i.e., empty string). */ do { gr->gr_mem[ndx] = strsep(&line, ","); } while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0'); } while (gr->gr_mem[ndx++] != NULL); return (true); } /* * Create a struct group from a line. */ struct group * gr_scan(const char *line) { struct group gr; char *line_copy; struct group *new_gr; if ((line_copy = strdup(line)) == NULL) return (NULL); if (!__gr_scan(line_copy, &gr)) { free(line_copy); return (NULL); } new_gr = gr_dup(&gr); free(line_copy); if (gr.gr_mem != NULL) free(gr.gr_mem); return (new_gr); } Index: stable/10/lib/libutil/pw_util.3 =================================================================== --- stable/10/lib/libutil/pw_util.3 (revision 285204) +++ stable/10/lib/libutil/pw_util.3 (revision 285205) @@ -1,286 +1,287 @@ .\" Copyright (c) 2012 Baptiste Daroussin .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 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. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd October 30, 2012 +.Dd July 02, 2015 .Dt PW_UTIL 3 .Os .Sh NAME .Nm pw_copy , .Nm pw_dup , .Nm pw_edit , .Nm pw_equal , .Nm pw_fini , .Nm pw_init , .Nm pw_make , .Nm pw_make_v7 , .Nm pw_mkdb , .Nm pw_lock , .Nm pw_scan , .Nm pw_tempname , .Nm pw_tmp .Nd "functions for passwd file handling" .Sh LIBRARY .Lb libutil .Sh SYNOPSIS .In pwd.h .In libutil.h .Ft int .Fn pw_copy "int ffd" "int tfd" "const struct passwd *pw" "struct passwd *oldpw" .Ft "struct passwd *" .Fn pw_dup "const struct passwd *pw" .Ft int .Fn pw_edit "int nosetuid" .Ft int .Fn pw_equal "const struct passwd *pw1" "const struct passwd *pw2" .Ft void .Fn pw_fini "void" .Ft int .Fn pw_init "const char *dir" const char *master" .Ft "char *" .Fn pw_make "const struct passwd *pw" .Ft "char *" .Fn pw_make_v7 "const struct passwd *pw" .Ft int .Fn pw_mkdb "const char *user" .Ft int .Fn pw_lock "void" .Ft "struct passwd *" .Fn pw_scan "const char *line" "int flags" .Ft "const char *" .Fn pw_tempname "void" .Ft int .Fn pw_tmp "int mfd" .Sh DESCRIPTION The .Fn pw_copy function reads a password file from .Vt ffd and writes it back out to .Vt tfd possibly with modifications: .Bl -dash .It If .Fa pw is .Dv NULL and .Fa oldpw is not .Dv NULL , then the record represented by .Fa oldpw will not be copied (corresponding to user deletion). .It If .Fa pw and .Fa oldpw are not .Dv NULL then the record corresponding to .Fa pw will be replaced by the record corresponding to .Fa oldpw . .It If .Vt pw is set and .Vt oldpw is .Dv NULL then the record corresponding to .Vt pw will be appended (corresponding to user addition). .El .Pp The .Fn pw_copy function returns -1 in case of failure otherwise 0. .Pp The .Fn pw_dup function duplicates the .Vt struct passwd pointed to by .Fa pw and returns a pointer to the copy, or .Dv NULL in case of failure. The new .Vt struct passwd is allocated with .Xr malloc 3 , and it is the caller's responsibility to free it with .Xr free 3 . .Pp The .Fn pw_edit function invokes the command specified by the .Ev EDITOR environment variable (or .Pa /usr/bin/vi if .Ev EDITOR is not defined) on a temporary copy of the master password file created by .Fn pw_tmp . If the file was modified, .Fn pw_edit installs it and regenerates the password database. The .Fn pw_edit function returns -1 in case of failure, 0 if the file was not modified, and a non-zero positive number if the file was modified and successfully installed. .Pp The .Fn pw_equal function compares two .Vt struct passwd and returns 0 if they are equal. .Pp The .Fn pw_fini function destroy the temporary file created by .Fn pw_tmp if any, kills any running instance of .Ev EDITOR executed by .Fn pw_edit if any, and closes the lock created by .Fn pw_lock if any. .Pp The .Fn pw_init initialize the static variable representing the path a password file. .Fa dir is the directory where the password file is located. If set to .Dv NULL , it will default to .Pa /etc . .Fa master is the name of the password file. If set to .Dv NULL? it will default to .Pa master.passwd .Pp The .Fn pw_make function creates a properly formatted .Bx .Xr passwd 5 line from a .Vt struct passwd , and returns a pointer to the resulting string. The string is allocated with .Xr malloc 3 , and it is the caller's responsibility to free it with .Xr free 3 . .Pp The .Fn pw_make_v7 function creates a properly formatted .Ux V7 .Xr passwd 5 line from a .Vt struct passwd , and returns a pointer to the resulting string. The string is allocated with .Xr malloc 3 , and it is the caller's responsibility to free it with .Xr free 3 . .Pp The .Fn pw_mkdb function regenerates the password database by running .Xr pw_mkdb 8 . If .Fa user only the record corresponding to that user will be updated. The .Fn pw_mkdb function returns 0 in case of success and -1 in case of failure. .Pp The .Fn pw_lock function locks the master password file. -It returns 0 in case of success and -1 in case of failure. +It returns a file descriptor to the master password file on success +and -1 on failure. .Pp The .Fn pw_scan function is a wrapper around the internal libc function .Fn __pw_scan . It scans the master password file for a line corresponding to the .Fa line provided and return a .Vt struct passwd if it matched an existing record. In case of failure, it returns .Dv NULL . Otherwise, it returns a pointer to a .Vt struct passwd containing the matching record. The .Vt struct passwd is allocated with .Xr malloc 3 , and it is the caller's responsibility to free it with .Xr free 3 . .Pp The .Fn pw_tempname function returns the temporary name of the masterfile created via .Fn pw_tmp . .Pp The .Fn pw_tmp creates and opens a presumably safe temporary password file. If .Fa mfd is a file descriptor to an open password file, it will be read and written back to the temporary password file. Otherwise if should be set -1. The .Fn pw_tmp returns an open file descriptor to the temporary password file or -1 in case of failure. .Sh AUTHORS Portions of this software were developed for the .Fx Project by ThinkSec AS and Network Associates Laboratories, the Security Research Division of Network Associates, Inc.\& under DARPA/SPAWAR contract N66001-01-C-8035 .Pq Dq CBOSS , as part of the DARPA CHATS research program. .Pp This manual page was written by .An Baptiste Daroussin Aq bapt@FreeBSD.org . Index: stable/10/lib/libutil/pw_util.c =================================================================== --- stable/10/lib/libutil/pw_util.c (revision 285204) +++ stable/10/lib/libutil/pw_util.c (revision 285205) @@ -1,664 +1,664 @@ /*- * 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 * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static const char sccsid[] = "@(#)pw_util.c 8.3 (Berkeley) 4/2/94"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ /* * This file is used by all the "password" programs; vipw(8), chpass(1), * and passwd(1). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libutil.h" static pid_t editpid = -1; 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) { if (editpid != -1) kill(editpid, sig); } #endif /* * 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 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); } /* * 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); (void)setrlimit(RLIMIT_FSIZE, &rlim); (void)setrlimit(RLIMIT_STACK, &rlim); (void)setrlimit(RLIMIT_DATA, &rlim); (void)setrlimit(RLIMIT_RSS, &rlim); /* Don't drop core (not really necessary, but GP's). */ rlim.rlim_cur = rlim.rlim_max = 0; (void)setrlimit(RLIMIT_CORE, &rlim); /* Turn off signals. */ (void)signal(SIGALRM, SIG_IGN); (void)signal(SIGHUP, SIG_IGN); (void)signal(SIGINT, SIG_IGN); (void)signal(SIGPIPE, SIG_IGN); (void)signal(SIGQUIT, SIG_IGN); (void)signal(SIGTERM, SIG_IGN); (void)signal(SIGCONT, pw_cont); /* 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 * that users can't get at the encrypted passwords while editing. * Open should allow flock'ing the file; see 4.4BSD. XXX */ for (;;) { struct stat st; lockfd = flopen(masterpasswd, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); if (lockfd == -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) == -1) err(1, "fstat() failed: "); if (st.st_nlink != 0) break; close(lockfd); lockfd = -1; } 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(int mfd) { char buf[8192]; ssize_t nr; const char *p; int tfd; if (*masterpasswd == '\0') return (-1); if ((p = strrchr(masterpasswd, '/'))) ++p; else 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) + if ((tfd = mkostemp(tempname, O_SYNC)) == -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 *user) { int pstat; pid_t pid; (void)fflush(stderr); 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; } if (waitpid(pid, &pstat, 0) == -1) return (-1); if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) return (0); errno = 0; return (-1); } /* * 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; 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, basename(editor), tempname, (char *)NULL); _exit(errno); default: /* parent */ break; } for (;;) { 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) && WEXITSTATUS(pstat) == 0) { editpid = -1; break; } else { unlink(tempname); editpid = -1; break; } } 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_fini(void) { 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; } /* * Compares two struct pwds. */ int pw_equal(const struct passwd *pw1, const struct passwd *pw2) { 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); } ret = pw_dup(&pw); free(bp); return (ret); } Index: stable/10/usr.sbin/pwd_mkdb/pwd_mkdb.c =================================================================== --- stable/10/usr.sbin/pwd_mkdb/pwd_mkdb.c (revision 285204) +++ stable/10/usr.sbin/pwd_mkdb/pwd_mkdb.c (revision 285205) @@ -1,762 +1,777 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1991, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)pwd_mkdb.c 8.5 (Berkeley) 4/20/94"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include "pw_scan.h" #define INSECURE 1 #define SECURE 2 #define PERM_INSECURE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) #define PERM_SECURE (S_IRUSR|S_IWUSR) #define LEGACY_VERSION(x) _PW_VERSIONED(x, 3) #define CURRENT_VERSION(x) _PW_VERSIONED(x, 4) static HASHINFO openinfo = { 4096, /* bsize */ 32, /* ffactor */ 256, /* nelem */ 2048 * 1024, /* cachesize */ NULL, /* hash() */ BYTE_ORDER /* lorder */ }; static enum state { FILE_INSECURE, FILE_SECURE, FILE_ORIG } clean; static struct passwd pwd; /* password structure */ static char *pname; /* password file name */ static char prefix[MAXPATHLEN]; static int is_comment; /* flag for comments */ static char line[LINE_MAX]; void cleanup(void); void error(const char *); void cp(char *, char *, mode_t mode); void mv(char *, char *); int scan(FILE *, struct passwd *); static void usage(void); int main(int argc, char *argv[]) { static char verskey[] = _PWD_VERSION_KEY; char version = _PWD_CURRENT_VERSION; DB *dp, *sdp, *pw_db; DBT data, sdata, key; FILE *fp, *oldfp; sigset_t set; int ch, cnt, ypcnt, makeold, tfd, yp_enabled = 0; unsigned int len; uint32_t store; const char *t; char *p; char buf[MAX(MAXPATHLEN, LINE_MAX * 2)], tbuf[1024]; char sbuf[MAX(MAXPATHLEN, LINE_MAX * 2)]; char buf2[MAXPATHLEN]; char sbuf2[MAXPATHLEN]; char *username; u_int method, methoduid; int Cflag, dflag, iflag; int nblock = 0; iflag = dflag = Cflag = 0; strcpy(prefix, _PATH_PWD); makeold = 0; username = NULL; oldfp = NULL; while ((ch = getopt(argc, argv, "BCLNd:ips:u:v")) != -1) switch(ch) { case 'B': /* big-endian output */ openinfo.lorder = BIG_ENDIAN; break; case 'C': /* verify only */ Cflag = 1; break; case 'L': /* little-endian output */ openinfo.lorder = LITTLE_ENDIAN; break; case 'N': /* do not wait for lock */ nblock = LOCK_NB; /* will fail if locked */ break; case 'd': dflag++; strlcpy(prefix, optarg, sizeof(prefix)); break; case 'i': iflag++; break; case 'p': /* create V7 "file.orig" */ makeold = 1; break; case 's': /* change default cachesize */ openinfo.cachesize = atoi(optarg) * 1024 * 1024; break; case 'u': /* only update this record */ username = optarg; break; case 'v': /* backward compatible */ break; default: usage(); } argc -= optind; argv += optind; if (argc != 1 || (username && (*username == '+' || *username == '-'))) usage(); /* * This could be changed to allow the user to interrupt. * Probably not worth the effort. */ sigemptyset(&set); sigaddset(&set, SIGTSTP); sigaddset(&set, SIGHUP); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGTERM); (void)sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL); /* We don't care what the user wants. */ (void)umask(0); pname = *argv; /* * Open and lock the original password file. We have to check * the hardlink count after we get the lock to handle any potential * unlink/rename race. * * This lock is necessary when someone runs pwd_mkdb manually, directly * on master.passwd, to handle the case where a user might try to * change his password while pwd_mkdb is running. */ for (;;) { struct stat st; if (!(fp = fopen(pname, "r"))) error(pname); if (flock(fileno(fp), LOCK_EX|nblock) < 0 && !(dflag && iflag)) error("flock"); if (fstat(fileno(fp), &st) < 0) error(pname); if (st.st_nlink != 0) break; fclose(fp); fp = NULL; } /* check only if password database is valid */ if (Cflag) { while (scan(fp, &pwd)) if (!is_comment && strlen(pwd.pw_name) >= MAXLOGNAME) { warnx("%s: username too long", pwd.pw_name); exit(1); } exit(0); } /* Open the temporary insecure password database. */ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB); (void)snprintf(sbuf, sizeof(sbuf), "%s/%s.tmp", prefix, _SMP_DB); if (username) { int use_version; (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB); (void)snprintf(sbuf2, sizeof(sbuf2), "%s/%s", prefix, _SMP_DB); clean = FILE_INSECURE; cp(buf2, buf, PERM_INSECURE); dp = dbopen(buf, - O_RDWR|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo); + O_RDWR|O_EXCL|O_SYNC, PERM_INSECURE, DB_HASH, &openinfo); if (dp == NULL) error(buf); clean = FILE_SECURE; cp(sbuf2, sbuf, PERM_SECURE); sdp = dbopen(sbuf, - O_RDWR|O_EXCL, PERM_SECURE, DB_HASH, &openinfo); + O_RDWR|O_EXCL|O_SYNC, PERM_SECURE, DB_HASH, &openinfo); if (sdp == NULL) error(sbuf); /* * Do some trouble to check if we should store this users * uid. Don't use getpwnam/getpwuid as that interferes * with NIS. */ pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL); if (!pw_db) error(_MP_DB); key.data = verskey; key.size = sizeof(verskey)-1; if ((pw_db->get)(pw_db, &key, &data, 0) == 0) use_version = *(unsigned char *)data.data; else use_version = 3; buf[0] = _PW_VERSIONED(_PW_KEYBYNAME, use_version); len = strlen(username); /* Only check that username fits in buffer */ memmove(buf + 1, username, MIN(len, sizeof(buf) - 1)); key.data = (u_char *)buf; key.size = len + 1; if ((pw_db->get)(pw_db, &key, &data, 0) == 0) { p = (char *)data.data; /* jump over pw_name and pw_passwd, to get to pw_uid */ while (*p++) ; while (*p++) ; buf[0] = _PW_VERSIONED(_PW_KEYBYUID, use_version); memmove(buf + 1, p, sizeof(store)); key.data = (u_char *)buf; key.size = sizeof(store) + 1; if ((pw_db->get)(pw_db, &key, &data, 0) == 0) { /* First field of data.data holds pw_pwname */ if (!strcmp(data.data, username)) methoduid = 0; else methoduid = R_NOOVERWRITE; } else { methoduid = R_NOOVERWRITE; } } else { methoduid = R_NOOVERWRITE; } if ((pw_db->close)(pw_db)) error("close pw_db"); method = 0; } else { dp = dbopen(buf, - O_RDWR|O_CREAT|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo); + O_RDWR|O_CREAT|O_EXCL|O_SYNC, PERM_INSECURE, DB_HASH, &openinfo); if (dp == NULL) error(buf); clean = FILE_INSECURE; sdp = dbopen(sbuf, - O_RDWR|O_CREAT|O_EXCL, PERM_SECURE, DB_HASH, &openinfo); + O_RDWR|O_CREAT|O_EXCL|O_SYNC, PERM_SECURE, DB_HASH, &openinfo); if (sdp == NULL) error(sbuf); clean = FILE_SECURE; method = R_NOOVERWRITE; methoduid = R_NOOVERWRITE; } /* * Open file for old password file. Minor trickiness -- don't want to * chance the file already existing, since someone (stupidly) might * still be using this for permission checking. So, open it first and * fdopen the resulting fd. The resulting file should be readable by * everyone. */ if (makeold) { (void)snprintf(buf, sizeof(buf), "%s.orig", pname); if ((tfd = open(buf, O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0) error(buf); if ((oldfp = fdopen(tfd, "w")) == NULL) error(buf); clean = FILE_ORIG; } /* * The databases actually contain three copies of the original data. * Each password file entry is converted into a rough approximation * of a ``struct passwd'', with the strings placed inline. This * object is then stored as the data for three separate keys. The * first key * is the pw_name field prepended by the _PW_KEYBYNAME * character. The second key is the pw_uid field prepended by the * _PW_KEYBYUID character. The third key is the line number in the * original file prepended by the _PW_KEYBYNUM character. (The special * characters are prepended to ensure that the keys do not collide.) */ /* In order to transition this file into a machine-independent * form, we have to change the format of entries. However, since * older binaries will still expect the old MD format entries, we * create those as usual and use versioned tags for the new entries. */ if (username == NULL) { /* Do not add the VERSION tag when updating a single * user. When operating on `old format' databases, this * would result in applications `seeing' only the updated * entries. */ key.data = verskey; key.size = sizeof(verskey)-1; data.data = &version; data.size = 1; if ((dp->put)(dp, &key, &data, 0) == -1) error("put"); if ((dp->put)(sdp, &key, &data, 0) == -1) error("put"); } ypcnt = 0; data.data = (u_char *)buf; sdata.data = (u_char *)sbuf; key.data = (u_char *)tbuf; for (cnt = 1; scan(fp, &pwd); ++cnt) { if (!is_comment && (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-')) { yp_enabled = 1; ypcnt++; } if (is_comment) --cnt; #define COMPACT(e) t = e; while ((*p++ = *t++)); #define SCALAR(e) store = htonl((uint32_t)(e)); \ memmove(p, &store, sizeof(store)); \ p += sizeof(store); #define LSCALAR(e) store = HTOL((uint32_t)(e)); \ memmove(p, &store, sizeof(store)); \ p += sizeof(store); #define HTOL(e) (openinfo.lorder == BYTE_ORDER ? \ (uint32_t)(e) : \ bswap32((uint32_t)(e))) if (!is_comment && (!username || (strcmp(username, pwd.pw_name) == 0))) { /* Create insecure data. */ p = buf; COMPACT(pwd.pw_name); COMPACT("*"); SCALAR(pwd.pw_uid); SCALAR(pwd.pw_gid); SCALAR(pwd.pw_change); COMPACT(pwd.pw_class); COMPACT(pwd.pw_gecos); COMPACT(pwd.pw_dir); COMPACT(pwd.pw_shell); SCALAR(pwd.pw_expire); SCALAR(pwd.pw_fields); data.size = p - buf; /* Create secure data. */ p = sbuf; COMPACT(pwd.pw_name); COMPACT(pwd.pw_passwd); SCALAR(pwd.pw_uid); SCALAR(pwd.pw_gid); SCALAR(pwd.pw_change); COMPACT(pwd.pw_class); COMPACT(pwd.pw_gecos); COMPACT(pwd.pw_dir); COMPACT(pwd.pw_shell); SCALAR(pwd.pw_expire); SCALAR(pwd.pw_fields); sdata.size = p - sbuf; /* Store insecure by name. */ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNAME); len = strlen(pwd.pw_name); memmove(tbuf + 1, pwd.pw_name, len); key.size = len + 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); /* Store insecure by number. */ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNUM); store = htonl(cnt); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); /* Store insecure by uid. */ tbuf[0] = CURRENT_VERSION(_PW_KEYBYUID); store = htonl(pwd.pw_uid); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((dp->put)(dp, &key, &data, methoduid) == -1) error("put"); /* Store secure by name. */ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNAME); len = strlen(pwd.pw_name); memmove(tbuf + 1, pwd.pw_name, len); key.size = len + 1; if ((sdp->put)(sdp, &key, &sdata, method) == -1) error("put"); /* Store secure by number. */ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNUM); store = htonl(cnt); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((sdp->put)(sdp, &key, &sdata, method) == -1) error("put"); /* Store secure by uid. */ tbuf[0] = CURRENT_VERSION(_PW_KEYBYUID); store = htonl(pwd.pw_uid); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1) error("put"); /* Store insecure and secure special plus and special minus */ if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') { tbuf[0] = CURRENT_VERSION(_PW_KEYYPBYNUM); store = htonl(ypcnt); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); if ((sdp->put)(sdp, &key, &sdata, method) == -1) error("put"); } /* Create insecure data. (legacy version) */ p = buf; COMPACT(pwd.pw_name); COMPACT("*"); LSCALAR(pwd.pw_uid); LSCALAR(pwd.pw_gid); LSCALAR(pwd.pw_change); COMPACT(pwd.pw_class); COMPACT(pwd.pw_gecos); COMPACT(pwd.pw_dir); COMPACT(pwd.pw_shell); LSCALAR(pwd.pw_expire); LSCALAR(pwd.pw_fields); data.size = p - buf; /* Create secure data. (legacy version) */ p = sbuf; COMPACT(pwd.pw_name); COMPACT(pwd.pw_passwd); LSCALAR(pwd.pw_uid); LSCALAR(pwd.pw_gid); LSCALAR(pwd.pw_change); COMPACT(pwd.pw_class); COMPACT(pwd.pw_gecos); COMPACT(pwd.pw_dir); COMPACT(pwd.pw_shell); LSCALAR(pwd.pw_expire); LSCALAR(pwd.pw_fields); sdata.size = p - sbuf; /* Store insecure by name. */ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNAME); len = strlen(pwd.pw_name); memmove(tbuf + 1, pwd.pw_name, len); key.size = len + 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); /* Store insecure by number. */ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNUM); store = HTOL(cnt); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); /* Store insecure by uid. */ tbuf[0] = LEGACY_VERSION(_PW_KEYBYUID); store = HTOL(pwd.pw_uid); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((dp->put)(dp, &key, &data, methoduid) == -1) error("put"); /* Store secure by name. */ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNAME); len = strlen(pwd.pw_name); memmove(tbuf + 1, pwd.pw_name, len); key.size = len + 1; if ((sdp->put)(sdp, &key, &sdata, method) == -1) error("put"); /* Store secure by number. */ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNUM); store = HTOL(cnt); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((sdp->put)(sdp, &key, &sdata, method) == -1) error("put"); /* Store secure by uid. */ tbuf[0] = LEGACY_VERSION(_PW_KEYBYUID); store = HTOL(pwd.pw_uid); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1) error("put"); /* Store insecure and secure special plus and special minus */ if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') { tbuf[0] = LEGACY_VERSION(_PW_KEYYPBYNUM); store = HTOL(ypcnt); memmove(tbuf + 1, &store, sizeof(store)); key.size = sizeof(store) + 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); if ((sdp->put)(sdp, &key, &sdata, method) == -1) error("put"); } } /* Create original format password file entry */ if (is_comment && makeold){ /* copy comments */ if (fprintf(oldfp, "%s\n", line) < 0) error("write old"); } else if (makeold) { char uidstr[20]; char gidstr[20]; snprintf(uidstr, sizeof(uidstr), "%u", pwd.pw_uid); snprintf(gidstr, sizeof(gidstr), "%u", pwd.pw_gid); if (fprintf(oldfp, "%s:*:%s:%s:%s:%s:%s\n", pwd.pw_name, pwd.pw_fields & _PWF_UID ? uidstr : "", pwd.pw_fields & _PWF_GID ? gidstr : "", pwd.pw_gecos, pwd.pw_dir, pwd.pw_shell) < 0) error("write old"); } } /* If YP enabled, set flag. */ if (yp_enabled) { buf[0] = yp_enabled + 2; data.size = 1; key.size = 1; tbuf[0] = CURRENT_VERSION(_PW_KEYYPENABLED); if ((dp->put)(dp, &key, &data, method) == -1) error("put"); if ((sdp->put)(sdp, &key, &data, method) == -1) error("put"); tbuf[0] = LEGACY_VERSION(_PW_KEYYPENABLED); key.size = 1; if ((dp->put)(dp, &key, &data, method) == -1) error("put"); if ((sdp->put)(sdp, &key, &data, method) == -1) error("put"); } if ((dp->close)(dp) == -1) error("close"); if ((sdp->close)(sdp) == -1) error("close"); if (makeold) { (void)fflush(oldfp); if (fclose(oldfp) == EOF) error("close old"); } /* Set master.passwd permissions, in case caller forgot. */ (void)fchmod(fileno(fp), S_IRUSR|S_IWUSR); /* Install as the real password files. */ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB); (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB); mv(buf, buf2); (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB); (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _SMP_DB); mv(buf, buf2); if (makeold) { (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _PASSWD); (void)snprintf(buf, sizeof(buf), "%s.orig", pname); mv(buf, buf2); } /* * Move the master password LAST -- chpass(1), passwd(1) and vipw(8) * all use flock(2) on it to block other incarnations of themselves. * The rename means that everything is unlocked, as the original file * can no longer be accessed. */ (void)snprintf(buf, sizeof(buf), "%s/%s", prefix, _MASTERPASSWD); mv(pname, buf); /* * Close locked password file after rename() */ if (fclose(fp) == EOF) error("close fp"); exit(0); } int scan(FILE *fp, struct passwd *pw) { static int lcnt; size_t len; char *p; p = fgetln(fp, &len); if (p == NULL) return (0); ++lcnt; /* * ``... if I swallow anything evil, put your fingers down my * throat...'' * -- The Who */ if (len > 0 && p[len - 1] == '\n') len--; if (len >= sizeof(line) - 1) { warnx("line #%d too long", lcnt); goto fmt; } memcpy(line, p, len); line[len] = '\0'; /* * Ignore comments: ^[ \t]*# */ for (p = line; *p != '\0'; p++) if (*p != ' ' && *p != '\t') break; if (*p == '#' || *p == '\0') { is_comment = 1; return(1); } else is_comment = 0; if (!__pw_scan(line, pw, _PWSCAN_WARN|_PWSCAN_MASTER)) { warnx("at line #%d", lcnt); fmt: errno = EFTYPE; /* XXX */ error(pname); } return (1); } void cp(char *from, char *to, mode_t mode) { static char buf[MAXBSIZE]; int from_fd, rcount, to_fd, wcount; if ((from_fd = open(from, O_RDONLY, 0)) < 0) error(from); if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) error(to); while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { wcount = write(to_fd, buf, rcount); if (rcount != wcount || wcount == -1) { int sverrno = errno; (void)snprintf(buf, sizeof(buf), "%s to %s", from, to); errno = sverrno; error(buf); } } if (rcount < 0) { int sverrno = errno; (void)snprintf(buf, sizeof(buf), "%s to %s", from, to); errno = sverrno; error(buf); } } void mv(char *from, char *to) { char buf[MAXPATHLEN]; + char *to_dir; + int to_dir_fd = -1; - if (rename(from, to)) { + /* + * Make sure file is safe on disk. To improve performance we will call + * fsync() to the directory where file lies + */ + if (rename(from, to) != 0 || + (to_dir = dirname(to)) == NULL || + (to_dir_fd = open(to_dir, O_RDONLY|O_DIRECTORY)) == -1 || + fsync(to_dir_fd) != 0) { int sverrno = errno; (void)snprintf(buf, sizeof(buf), "%s to %s", from, to); errno = sverrno; + if (to_dir_fd != -1) + close(to_dir_fd); error(buf); } + + if (to_dir_fd != -1) + close(to_dir_fd); } void error(const char *name) { warn("%s", name); cleanup(); exit(1); } void cleanup(void) { char buf[MAXPATHLEN]; switch(clean) { case FILE_ORIG: (void)snprintf(buf, sizeof(buf), "%s.orig", pname); (void)unlink(buf); /* FALLTHROUGH */ case FILE_SECURE: (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB); (void)unlink(buf); /* FALLTHROUGH */ case FILE_INSECURE: (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB); (void)unlink(buf); } } static void usage(void) { (void)fprintf(stderr, "usage: pwd_mkdb [-BCiLNp] [-d directory] [-s cachesize] [-u username] file\n"); exit(1); } Index: stable/10 =================================================================== --- stable/10 (revision 285204) +++ stable/10 (revision 285205) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r285050,285053,285059