Index: stable/10/usr.sbin/pw/fileupd.c =================================================================== --- stable/10/usr.sbin/pw/fileupd.c (revision 287083) +++ stable/10/usr.sbin/pw/fileupd.c (nonexistent) @@ -1,47 +0,0 @@ -/*- - * Copyright (C) 1996 - * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 -static const char rcsid[] = - "$FreeBSD$"; -#endif /* not lint */ - -#include - -#include "pwupd.h" - -int -extendarray(char ***buf, int * buflen, int needed) -{ - if (needed > *buflen) { - char **tmp = realloc(*buf, needed * sizeof(char *)); - if (tmp == NULL) - return -1; - *buf = tmp; - *buflen = needed; - } - return *buflen; -} Property changes on: stable/10/usr.sbin/pw/fileupd.c ___________________________________________________________________ Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Index: stable/10/usr.sbin/pw/Makefile =================================================================== --- stable/10/usr.sbin/pw/Makefile (revision 287083) +++ stable/10/usr.sbin/pw/Makefile (revision 287084) @@ -1,20 +1,20 @@ # $FreeBSD$ .include PROG= pw MAN= pw.conf.5 pw.8 SRCS= pw.c pw_conf.c pw_user.c pw_group.c pw_log.c pw_nis.c pw_vpw.c \ - grupd.c pwupd.c fileupd.c psdate.c \ - bitmap.c cpdir.c rm_r.c + grupd.c pwupd.c psdate.c bitmap.c cpdir.c rm_r.c strtounum.c \ + pw_utils.c WARNS?= 3 DPADD= ${LIBCRYPT} ${LIBUTIL} ${LIBSBUF} LDADD= -lcrypt -lutil -lsbuf .if ${MK_TESTS} != "no" SUBDIR+= tests .endif .include Index: stable/10/usr.sbin/pw/cpdir.c =================================================================== --- stable/10/usr.sbin/pw/cpdir.c (revision 287083) +++ stable/10/usr.sbin/pw/cpdir.c (revision 287084) @@ -1,131 +1,124 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ +#include #include #include #include -#include #include -#include #include -#include -#include -#include -#include #include "pw.h" #include "pwupd.h" void -copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid) +copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, + gid_t gid, int flags) { - char src[MAXPATHLEN]; - char dst[MAXPATHLEN]; - char lnk[MAXPATHLEN]; - int len; + char *p, lnk[MAXPATHLEN], copybuf[4096]; + int len, homefd, srcfd, destfd; + ssize_t sz; + struct stat st; + struct dirent *e; + DIR *d; - if (mkdir(dir, mode) != 0 && errno != EEXIST) { + if (*dir == '/') + dir++; + + if (mkdirat(rootfd, dir, mode) != 0 && errno != EEXIST) { warn("mkdir(%s)", dir); - } else { - int infd, outfd; - struct stat st; + return; + } + fchownat(rootfd, dir, uid, gid, AT_SYMLINK_NOFOLLOW); + if (flags > 0) + chflagsat(rootfd, dir, flags, AT_SYMLINK_NOFOLLOW); - static char counter = 0; - static char *copybuf = NULL; + if (skelfd == -1) + return; - ++counter; - chown(dir, uid, gid); - if (skel != NULL && *skel != '\0') { - DIR *d = opendir(skel); + homefd = openat(rootfd, dir, O_DIRECTORY); + if ((d = fdopendir(skelfd)) == NULL) { + close(skelfd); + close(homefd); + return; + } - if (d != NULL) { - struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) + continue; - while ((e = readdir(d)) != NULL) { - char *p = e->d_name; + p = e->d_name; + if (fstatat(skelfd, p, &st, AT_SYMLINK_NOFOLLOW) == -1) + continue; - if (snprintf(src, sizeof(src), "%s/%s", skel, p) >= (int)sizeof(src)) - warn("warning: pathname too long '%s/%s' (skel not copied)", skel, p); - else if (lstat(src, &st) == 0) { - if (strncmp(p, "dot.", 4) == 0) /* Conversion */ - p += 3; - if (snprintf(dst, sizeof(dst), "%s/%s", dir, p) >= (int)sizeof(dst)) - warn("warning: path too long '%s/%s' (skel file skipped)", dir, p); - else { - if (S_ISDIR(st.st_mode)) { /* Recurse for this */ - if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) - copymkdir(dst, src, st.st_mode & _DEF_DIRMODE, uid, gid); - chflags(dst, st.st_flags); /* propagate flags */ - } else if (S_ISLNK(st.st_mode) && (len = readlink(src, lnk, sizeof(lnk) - 1)) != -1) { - lnk[len] = '\0'; - symlink(lnk, dst); - lchown(dst, uid, gid); - /* - * Note: don't propagate special attributes - * but do propagate file flags - */ - } else if (S_ISREG(st.st_mode) && (outfd = open(dst, O_RDWR | O_CREAT | O_EXCL, st.st_mode)) != -1) { - if ((infd = open(src, O_RDONLY)) == -1) { - close(outfd); - remove(dst); - } else { - int b; + if (strncmp(p, "dot.", 4) == 0) /* Conversion */ + p += 3; - /* - * Allocate our copy buffer if we need to - */ - if (copybuf == NULL) - copybuf = malloc(4096); - while ((b = read(infd, copybuf, 4096)) > 0) - write(outfd, copybuf, b); - close(infd); - /* - * Propagate special filesystem flags - */ - fchown(outfd, uid, gid); - fchflags(outfd, st.st_flags); - close(outfd); - chown(dst, uid, gid); - } - } - } - } - } - closedir(d); - } + if (S_ISDIR(st.st_mode)) { + copymkdir(homefd, p, openat(skelfd, e->d_name, O_DIRECTORY), + st.st_mode & _DEF_DIRMODE, uid, gid, st.st_flags); + continue; } - if (--counter == 0 && copybuf != NULL) { - free(copybuf); - copybuf = NULL; + + if (S_ISLNK(st.st_mode) && + (len = readlinkat(skelfd, e->d_name, lnk, sizeof(lnk) -1)) + != -1) { + lnk[len] = '\0'; + symlinkat(lnk, homefd, p); + fchownat(homefd, p, uid, gid, AT_SYMLINK_NOFOLLOW); + continue; } + + if (!S_ISREG(st.st_mode)) + continue; + + if ((srcfd = openat(skelfd, e->d_name, O_RDONLY)) == -1) + continue; + destfd = openat(homefd, p, O_RDWR | O_CREAT | O_EXCL, + st.st_mode); + if (destfd == -1) { + close(srcfd); + continue; + } + + while ((sz = read(srcfd, copybuf, sizeof(copybuf))) > 0) + write(destfd, copybuf, sz); + + close(srcfd); + /* + * Propagate special filesystem flags + */ + fchown(destfd, uid, gid); + fchflags(destfd, st.st_flags); + close(destfd); } + closedir(d); } - Index: stable/10/usr.sbin/pw/grupd.c =================================================================== --- stable/10/usr.sbin/pw/grupd.c (revision 287083) +++ stable/10/usr.sbin/pw/grupd.c (revision 287084) @@ -1,107 +1,105 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ +#include #include #include -#include #include #include -#include -#include #include "pwupd.h" char * getgrpath(const char * file) { static char pathbuf[MAXPATHLEN]; snprintf(pathbuf, sizeof pathbuf, "%s/%s", conf.etcpath, file); return (pathbuf); } static int gr_update(struct group * grp, char const * group) { int pfd, tfd; struct group *gr = NULL; struct group *old_gr = NULL; if (grp != NULL) gr = gr_dup(grp); if (group != NULL) old_gr = GETGRNAM(group); if (gr_init(conf.etcpath, NULL)) err(1, "gr_init()"); if ((pfd = gr_lock()) == -1) { gr_fini(); err(1, "gr_lock()"); } if ((tfd = gr_tmp(-1)) == -1) { gr_fini(); err(1, "gr_tmp()"); } if (gr_copy(pfd, tfd, gr, old_gr) == -1) { gr_fini(); err(1, "gr_copy()"); } if (gr_mkdb() == -1) { gr_fini(); err(1, "gr_mkdb()"); } free(gr); gr_fini(); return 0; } int addgrent(struct group * grp) { return gr_update(grp, NULL); } int chggrent(char const * login, struct group * grp) { return gr_update(grp, login); } int delgrent(struct group * grp) { return (gr_update(NULL, grp->gr_name)); } Index: stable/10/usr.sbin/pw/psdate.c =================================================================== --- stable/10/usr.sbin/pw/psdate.c (revision 287083) +++ stable/10/usr.sbin/pw/psdate.c (revision 287084) @@ -1,295 +1,261 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ -#include +#include +#include #include #include -#include +#include #include "psdate.h" static int -a2i(char const ** str) -{ - int i = 0; - char const *s = *str; - - if (isdigit((unsigned char)*s)) { - i = atoi(s); - while (isdigit((unsigned char)*s)) - ++s; - *str = s; - } - return i; -} - -static int numerics(char const * str) { int rc = isdigit((unsigned char)*str); if (rc) while (isdigit((unsigned char)*str) || *str == 'x') ++str; return rc && !*str; } static int aindex(char const * arr[], char const ** str, int len) { int l, i; char mystr[32]; mystr[len] = '\0'; l = strlen(strncpy(mystr, *str, len)); for (i = 0; i < l; i++) mystr[i] = (char) tolower((unsigned char)mystr[i]); for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++); if (arr[i] == NULL) i = -1; else { /* Skip past it */ while (**str && isalpha((unsigned char)**str)) ++(*str); /* And any following whitespace */ while (**str && (**str == ',' || isspace((unsigned char)**str))) ++(*str); } /* Return index */ return i; } static int weekday(char const ** str) { static char const *days[] = {"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL}; return aindex(days, str, 3); } -static int -month(char const ** str) +static void +parse_datesub(char const * str, struct tm *t) { - static char const *months[] = - {"jan", "feb", "mar", "apr", "may", "jun", "jul", - "aug", "sep", "oct", "nov", "dec", NULL}; + struct tm tm; + locale_t l; + int i; + char *ret; + const char *valid_formats[] = { + "%d-%b-%y", + "%d-%b-%Y", + "%d-%m-%y", + "%d-%m-%Y", + "%H:%M %d-%b-%y", + "%H:%M %d-%b-%Y", + "%H:%M %d-%m-%y", + "%H:%M %d-%m-%Y", + "%H:%M:%S %d-%b-%y", + "%H:%M:%S %d-%b-%Y", + "%H:%M:%S %d-%m-%y", + "%H:%M:%S %d-%m-%Y", + "%d-%b-%y %H:%M", + "%d-%b-%Y %H:%M", + "%d-%m-%y %H:%M", + "%d-%m-%Y %H:%M", + "%d-%b-%y %H:%M:%S", + "%d-%b-%Y %H:%M:%S", + "%d-%m-%y %H:%M:%S", + "%d-%m-%Y %H:%M:%S", + "%H:%M\t%d-%b-%y", + "%H:%M\t%d-%b-%Y", + "%H:%M\t%d-%m-%y", + "%H:%M\t%d-%m-%Y", + "%H:%M\t%S %d-%b-%y", + "%H:%M\t%S %d-%b-%Y", + "%H:%M\t%S %d-%m-%y", + "%H:%M\t%S %d-%m-%Y", + "%d-%b-%y\t%H:%M", + "%d-%b-%Y\t%H:%M", + "%d-%m-%y\t%H:%M", + "%d-%m-%Y\t%H:%M", + "%d-%b-%y\t%H:%M:%S", + "%d-%b-%Y\t%H:%M:%S", + "%d-%m-%y\t%H:%M:%S", + "%d-%m-%Y\t%H:%M:%S", + NULL, + }; - return aindex(months, str, 3); -} + l = newlocale(LC_ALL_MASK, "C", NULL); -static void -parse_time(char const * str, int *hour, int *min, int *sec) -{ - *hour = a2i(&str); - if ((str = strchr(str, ':')) == NULL) - *min = *sec = 0; - else { - ++str; - *min = a2i(&str); - *sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str); + memset(&tm, 0, sizeof(tm)); + for (i=0; valid_formats[i] != NULL; i++) { + ret = strptime_l(str, valid_formats[i], &tm, l); + if (ret && *ret == '\0') { + t->tm_mday = tm.tm_mday; + t->tm_mon = tm.tm_mon; + t->tm_year = tm.tm_year; + t->tm_hour = tm.tm_hour; + t->tm_min = tm.tm_min; + t->tm_sec = tm.tm_sec; + freelocale(l); + return; + } } -} + freelocale(l); -static void -parse_datesub(char const * str, int *day, int *mon, int *year) -{ - int i; - - static char const nchrs[] = "0123456789 \t,/-."; - - if ((i = month(&str)) != -1) { - *mon = i; - if ((i = a2i(&str)) != 0) - *day = i; - } else if ((i = a2i(&str)) != 0) { - *day = i; - while (*str && strchr(nchrs + 10, *str) != NULL) - ++str; - if ((i = month(&str)) != -1) - *mon = i; - else if ((i = a2i(&str)) != 0) - *mon = i - 1; - } else - return; - - while (*str && strchr(nchrs + 10, *str) != NULL) - ++str; - if (isdigit((unsigned char)*str)) { - *year = atoi(str); - if (*year > 1900) - *year -= 1900; - else if (*year < 32) - *year += 100; - } + errx(EXIT_FAILURE, "Invalid date"); } /*- * Parse time must be flexible, it handles the following formats: * nnnnnnnnnnn UNIX timestamp (all numeric), 0 = now * 0xnnnnnnnn UNIX timestamp in hexadecimal * 0nnnnnnnnn UNIX timestamp in octal * 0 Given time * +nnnn[smhdwoy] Given time + nnnn hours, mins, days, weeks, months or years * -nnnn[smhdwoy] Given time - nnnn hours, mins, days, weeks, months or years * dd[ ./-]mmm[ ./-]yy Date } * hh:mm:ss Time } May be combined */ time_t parse_date(time_t dt, char const * str) { char *p; int i; long val; struct tm *T; if (dt == 0) dt = time(NULL); while (*str && isspace((unsigned char)*str)) ++str; if (numerics(str)) { dt = strtol(str, &p, 0); } else if (*str == '+' || *str == '-') { val = strtol(str, &p, 0); switch (*p) { case 'h': case 'H': /* hours */ dt += (val * 3600L); break; case '\0': case 'm': case 'M': /* minutes */ dt += (val * 60L); break; case 's': case 'S': /* seconds */ dt += val; break; case 'd': case 'D': /* days */ dt += (val * 86400L); break; case 'w': case 'W': /* weeks */ dt += (val * 604800L); break; case 'o': case 'O': /* months */ T = localtime(&dt); T->tm_mon += (int) val; i = T->tm_mday; goto fixday; case 'y': case 'Y': /* years */ T = localtime(&dt); T->tm_year += (int) val; i = T->tm_mday; fixday: dt = mktime(T); T = localtime(&dt); if (T->tm_mday != i) { T->tm_mday = 1; dt = mktime(T); dt -= (time_t) 86400L; } default: /* unknown */ break; /* leave untouched */ } } else { char *q, tmp[64]; /* * Skip past any weekday prefix */ weekday(&str); strlcpy(tmp, str, sizeof(tmp)); str = tmp; T = localtime(&dt); /* * See if we can break off any timezone */ while ((q = strrchr(tmp, ' ')) != NULL) { if (strchr("(+-", q[1]) != NULL) *q = '\0'; else { int j = 1; while (q[j] && isupper((unsigned char)q[j])) ++j; if (q[j] == '\0') *q = '\0'; else break; } } - /* - * See if there is a time hh:mm[:ss] - */ - if ((p = strchr(tmp, ':')) == NULL) { - - /* - * No time string involved - */ - T->tm_hour = T->tm_min = T->tm_sec = 0; - parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year); - } else { - char datestr[64], timestr[64]; - - /* - * Let's chip off the time string - */ - if ((q = strpbrk(p, " \t")) != NULL) { /* Time first? */ - int l = q - str; - - strlcpy(timestr, str, l + 1); - strlcpy(datestr, q + 1, sizeof(datestr)); - parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec); - parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year); - } else if ((q = strrchr(tmp, ' ')) != NULL) { /* Time last */ - int l = q - tmp; - - strlcpy(timestr, q + 1, sizeof(timestr)); - strlcpy(datestr, tmp, l + 1); - } else /* Bail out */ - return dt; - parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec); - parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year); - } + parse_datesub(tmp, T); dt = mktime(T); } return dt; } Index: stable/10/usr.sbin/pw/pw.8 =================================================================== --- stable/10/usr.sbin/pw/pw.8 (revision 287083) +++ stable/10/usr.sbin/pw/pw.8 (revision 287084) @@ -1,1021 +1,1049 @@ .\" Copyright (C) 1996 .\" David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 June 14, 2015 .Dt PW 8 .Os .Sh NAME .Nm pw .Nd create, remove, modify & display system users and groups .Sh SYNOPSIS .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar useradd -.Op name|uid +.Oo Fl n Oc name Oo Fl u Ar uid Oc .Op Fl C Ar config .Op Fl q -.Op Fl n Ar name -.Op Fl u Ar uid .Op Fl c Ar comment .Op Fl d Ar dir .Op Fl e Ar date .Op Fl p Ar date .Op Fl g Ar group .Op Fl G Ar grouplist .Op Fl m .Op Fl M Ar mode .Op Fl k Ar dir .Op Fl w Ar method .Op Fl s Ar shell .Op Fl o .Op Fl L Ar class .Op Fl h Ar fd | Fl H Ar fd .Op Fl N .Op Fl P .Op Fl Y .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar useradd -.Op name|uid .Fl D .Op Fl C Ar config .Op Fl q .Op Fl b Ar dir .Op Fl e Ar days .Op Fl p Ar days .Op Fl g Ar group .Op Fl G Ar grouplist .Op Fl k Ar dir .Op Fl M Ar mode .Op Fl u Ar min , Ns Ar max .Op Fl i Ar min , Ns Ar max .Op Fl w Ar method .Op Fl s Ar shell .Op Fl y Ar path .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar userdel -.Op name|uid -.Op Fl n Ar name -.Op Fl u Ar uid +.Oo Fl n Oc name|uid | Fl u Ar uid .Op Fl r .Op Fl Y .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar usermod -.Op name|uid +.Oo Fl n Oc name|uid Oo Fl u Ar newuid Oc | Fl u Ar uid .Op Fl C Ar config .Op Fl q -.Op Fl n Ar name -.Op Fl u Ar uid .Op Fl c Ar comment .Op Fl d Ar dir .Op Fl e Ar date .Op Fl p Ar date .Op Fl g Ar group .Op Fl G Ar grouplist -.Op Fl l Ar name +.Op Fl l Ar newname .Op Fl m .Op Fl M Ar mode .Op Fl k Ar dir .Op Fl w Ar method .Op Fl s Ar shell .Op Fl L Ar class .Op Fl h Ar fd | Fl H Ar fd .Op Fl N .Op Fl P .Op Fl Y .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar usershow -.Op name|uid -.Op Fl n Ar name -.Op Fl u Ar uid +.Oo Fl n Oc name|uid | Fl u Ar uid .Op Fl F .Op Fl P .Op Fl 7 .Op Fl a .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar usernext .Op Fl C Ar config .Op Fl q .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar groupadd -.Op group|gid +.Oo Fl n Oc name Oo Fl g Ar gid Oc .Op Fl C Ar config .Op Fl q -.Op Fl n Ar group -.Op Fl g Ar gid .Op Fl M Ar members .Op Fl o .Op Fl h Ar fd | Fl H Ar fd .Op Fl N .Op Fl P .Op Fl Y .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar groupdel -.Op group|gid -.Op Fl n Ar name -.Op Fl g Ar gid +.Oo Fl n Oc name|gid | Fl g Ar gid .Op Fl Y .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar groupmod -.Op group|gid +.Oo Fl n Oc name|gid Oo Fl g Ar newgid Oc | Fl g Ar gid .Op Fl C Ar config .Op Fl q -.Op Fl n Ar name -.Op Fl g Ar gid -.Op Fl l Ar name +.Op Fl l Ar newname .Op Fl M Ar members .Op Fl m Ar newmembers .Op Fl d Ar oldmembers .Op Fl h Ar fd | Fl H Ar fd .Op Fl N .Op Fl P .Op Fl Y .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar groupshow -.Op group|gid -.Op Fl n Ar name -.Op Fl g Ar gid +.Oo Fl n Oc name|gid | Fl g Ar gid .Op Fl F .Op Fl P .Op Fl a .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar groupnext .Op Fl C Ar config .Op Fl q .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar lock -.Op name|uid +.Oo Fl n Oc name|uid | Fl u Ar uid .Op Fl C Ar config .Op Fl q .Nm .Op Fl R Ar rootdir .Op Fl V Ar etcdir .Ar unlock -.Op name|uid +.Oo Fl n Oc name|uid | Fl u Ar uid .Op Fl C Ar config .Op Fl q .Sh DESCRIPTION The .Nm utility is a command-line based editor for the system .Ar user and .Ar group files, allowing the superuser an easy to use and standardized way of adding, modifying and removing users and groups. Note that .Nm only operates on the local user and group files. .Tn NIS users and groups must be maintained on the .Tn NIS server. The .Nm utility handles updating the .Pa passwd , .Pa master.passwd , .Pa group and the secure and insecure password database files, and must be run as root. .Pp The first one or two keywords provided to .Nm on the command line provide the context for the remainder of the arguments. The keywords .Ar user and .Ar group may be combined with .Ar add , .Ar del , .Ar mod , .Ar show , or .Ar next in any order. (For example, .Ar showuser , .Ar usershow , .Ar show user , and .Ar user show all mean the same thing.) This flexibility is useful for interactive scripts calling .Nm for user and group database manipulation. -Following these keywords, you may optionally specify the user or group name or numeric -id as an alternative to using the +Following these keywords, +the user or group name or numeric id may be optionally specified as an +alternative to using the .Fl n Ar name , .Fl u Ar uid , .Fl g Ar gid options. .Pp The following flags are common to most or all modes of operation: .Bl -tag -width "-G grouplist" .It Fl R Ar rootdir Specifies an alternate root directory within which .Nm will operate. Any paths specified will be relative to .Va rootdir . .It Fl V Ar etcdir -This flag sets an alternate location for the password, group and configuration files, -and may be used to maintain a user/group database in an alternate location. +Set an alternate location for the password, group, and configuration files. +Can be used to maintain a user/group database in an alternate location. If this switch is specified, the system .Pa /etc/pw.conf -will not be sourced for default configuration data, but the file pw.conf in the -specified directory will be used instead (or none, if it does not exist). +will not be sourced for default configuration data, +but the file pw.conf in the specified directory will be used instead +.Pq or none, if it does not exist . The .Fl C flag may be used to override this behaviour. As an exception to the general rule where options must follow the operation type, the .Fl V flag must be used on the command line before the operation keyword. .It Fl C Ar config By default, .Nm reads the file .Pa /etc/pw.conf to obtain policy information on how new user accounts and groups are to be created. The .Fl C option specifies a different configuration file. While most of the contents of the configuration file may be overridden via command-line options, it may be more convenient to keep standard information in a configuration file. .It Fl q Use of this option causes .Nm -to suppress error messages, which may be useful in interactive environments where it +to suppress error messages, +which may be useful in interactive environments where it is preferable to interpret status codes returned by .Nm rather than messing up a carefully formatted display. .It Fl N This option is available in .Ar add and .Ar modify operations, and tells .Nm to output the result of the operation without updating the user or group databases. You may use the .Fl P option to switch between standard passwd and readable formats. .It Fl Y Using this option with any of the update modes causes .Nm to run .Xr make 1 after changing to the directory .Pa /var/yp . This is intended to allow automatic updating of .Tn NIS database files. If separate passwd and group files are being used by .Tn NIS , then use the .Fl y Ar path option to specify the location of the .Tn NIS passwd database so that .Nm will concurrently update it with the system password databases. .El .Sh USER OPTIONS The following options apply to the .Ar useradd and .Ar usermod commands: .Bl -tag -width "-G grouplist" -.It Fl n Ar name +.It Oo Fl n Oc Ar name +Required unless +.Fl u Ar uid +is given. Specify the user/account name. +In the case of +.Ar usermod +can be a uid. .It Fl u Ar uid +Required if +.Ar name +is not given. Specify the user/account numeric id. +In the case of +.Ar usermod +if paired with +.Ar name , +changes the numeric id of the named user/account. .Pp -Usually, you only need to provide one or the other of these options, as the account -name will imply the uid, or vice versa. -However, there are times when you need to provide both. +Usually, only one of these options is required, +as the account name will imply the uid, or vice versa. +However, there are times when both are needed. For example, when changing the uid of an existing user with .Ar usermod , -or overriding the default uid when creating a new account. -If you wish -.Nm -to automatically allocate the uid to a new user with +or overriding the default uid when creating a new account with +.Ar useradd . +To automatically allocate the uid to a new user with .Ar useradd , -then you should +then do .Em not use the .Fl u option. -You may also provide either the account or userid immediately after the +Either the account or userid can also be provided immediately after the .Ar useradd , .Ar userdel , .Ar usermod or .Ar usershow keywords on the command line without using the .Fl n or .Fl u options. .El .Bl -tag -width "-G grouplist" .It Fl c Ar comment -This field sets the contents of the passwd GECOS field, which normally contains up -to four comma-separated fields containing the user's full name, office or location, +This field sets the contents of the passwd GECOS field, +which normally contains up to four comma-separated fields containing the +user's full name, office or location, and work and home phone numbers. These sub-fields are used by convention only, however, and are optional. -If this field is to contain spaces, you need to quote the comment itself with double -quotes +If this field is to contain spaces, +the comment must be enclosed in double quotes .Ql \&" . -Avoid using commas in this field as these are used as sub-field separators, and the -colon +Avoid using commas in this field as these are used as sub-field separators, +and the colon .Ql \&: character also cannot be used as this is the field separator for the passwd file itself. .It Fl d Ar dir This option sets the account's home directory. -Normally, you will only use this if the home directory is to be different from the +Normally, +this is only used if the home directory is to be different from the default determined from .Pa /etc/pw.conf - normally .Pa /home with the account name as a subdirectory. .It Fl e Ar date Set the account's expiration date. Format of the date is either a UNIX time in decimal, or a date in .Ql dd-mmm-yy[yy] -format, where dd is the day, mmm is the month, either in numeric or alphabetic format +format, where dd is the day, +mmm is the month, either in numeric or alphabetic format ('Jan', 'Feb', etc) and year is either a two or four digit year. This option also accepts a relative date in the form .Ql \&+n[mhdwoy] where .Ql \&n -is a decimal, octal (leading 0) or hexadecimal (leading 0x) digit followed by the +is a decimal, +octal (leading 0) or hexadecimal (leading 0x) digit followed by the number of Minutes, Hours, Days, Weeks, Months or Years from the current date at which the expiration date is to be set. .It Fl p Ar date Set the account's password expiration date. This field is similar to the account expiration date option, except that it applies to forced password changes. This is set in the same manner as the .Fl e option. .It Fl g Ar group Set the account's primary group to the given group. .Ar group may be defined by either its name or group number. .It Fl G Ar grouplist Set additional group memberships for an account. .Ar grouplist is a comma, space or tab-separated list of group names or group numbers. The user's name is added to the group lists in .Pa /etc/group , and removed from any groups not specified in .Ar grouplist . Note: a user should not be added to their primary group with .Ar grouplist . Also, group membership changes do not take effect for current user login sessions, requiring the user to reconnect to be affected by the changes. .It Fl L Ar class This option sets the login class for the user being created. See .Xr login.conf 5 and .Xr passwd 5 for more information on user login classes. .It Fl m This option instructs .Nm to attempt to create the user's home directory. While primarily useful when adding a new account with .Ar useradd , -this may also be of use when moving an existing user's home directory elsewhere on -the file system. +this may also be of use when moving an existing user's home directory elsewhere +on the file system. The new home directory is populated with the contents of the .Ar skeleton directory, which typically contains a set of shell configuration files that the user may personalize to taste. Files in this directory are usually named .Pa dot . Ns Aq Ar config where the .Pa dot prefix will be stripped. When .Fl m is used on an account with .Ar usermod , existing configuration files in the user's home directory are .Em not overwritten from the skeleton files. .Pp -When a user's home directory is created, it will by default be a subdirectory of the +When a user's home directory is created, +it will by default be a subdirectory of the .Ar basehome directory as specified by the .Fl b option (see below), bearing the name of the new account. This can be overridden by the .Fl d option on the command line, if desired. .It Fl M Ar mode Create the user's home directory with the specified .Ar mode , modified by the current .Xr umask 2 . If omitted, it is derived from the parent process' .Xr umask 2 . This option is only useful in combination with the .Fl m flag. .It Fl k Ar dir Set the .Ar skeleton directory, from which basic startup and configuration files are copied when the user's home directory is created. This option only has meaning when used with the .Fl d or .Fl m flags. .It Fl s Ar shell Set or changes the user's login shell to .Ar shell . If the path to the shell program is omitted, .Nm searches the .Ar shellpath specified in .Pa /etc/pw.conf and fills it in as appropriate. Note that unless you have a specific reason to do so, you should avoid specifying the path - this will allow .Nm to validate that the program exists and is executable. Specifying a full path (or supplying a blank "" shell) avoids this check and allows for such entries as .Pa /nonexistent that should be set for accounts not intended for interactive login. .It Fl h Ar fd This option provides a special interface by which interactive scripts can set an account password using .Nm . Because the command line and environment are fundamentally insecure mechanisms by which programs can accept information, .Nm will only allow setting of account and group passwords via a file descriptor (usually a pipe between an interactive script and the program). .Ar sh , .Ar bash , .Ar ksh and .Ar perl all possess mechanisms by which this can be done. Alternatively, .Nm will prompt for the user's password if .Fl h Ar 0 is given, nominating .Em stdin as the file descriptor on which to read the password. Note that this password will be read only once and is intended for use by a script rather than for interactive use. If you wish to have new password confirmation along the lines of .Xr passwd 1 , this must be implemented as part of an interactive script that calls .Nm . .Pp If a value of .Ql \&- is given as the argument .Ar fd , then the password will be set to .Ql \&* , rendering the account inaccessible via password-based login. .It Fl H Ar fd Read an encrypted password string from the specified file descriptor. This is like .Fl h , but the password should be supplied already encrypted in a form suitable for writing directly to the password database. .El .Pp It is possible to use .Ar useradd to create a new account that duplicates an existing user id. While this is normally considered an error and will be rejected, the .Fl o option overrides the check for duplicates and allows the duplication of the user id. This may be useful if you allow the same user to login under different contexts (different group allocations, different home directory, different shell) while providing basically the same permissions for access to the user's files in each account. .Pp The .Ar useradd command also has the ability to set new user and group defaults by using the .Fl D option. Instead of adding a new user, .Nm writes a new set of defaults to its configuration file, .Pa /etc/pw.conf . When using the .Fl D option, you must not use either .Fl n Ar name or .Fl u Ar uid or an error will result. Use of .Fl D changes the meaning of several command line switches in the .Ar useradd command. These are: .Bl -tag -width "-G grouplist" .It Fl D Set default values in .Pa /etc/pw.conf configuration file, or a different named configuration file if the .Fl C Ar config option is used. .It Fl b Ar dir Set the root directory in which user home directories are created. The default value for this is .Pa /home , but it may be set elsewhere as desired. .It Fl e Ar days Set the default account expiration period in days. -Unlike use without -.Fl D , -the argument must be numeric, which specifies the number of days after creation when -the account is to expire. +When +.Fl D +is used, the +.Ar days +argument is interpreted differently. +It must be numeric and represents the number of days after creation +that the account expires. A value of 0 suppresses automatic calculation of the expiry date. .It Fl p Ar days Set the default password expiration period in days. .It Fl g Ar group Set the default group for new users. If a blank group is specified using .Fl g Ar \&"" , then new users will be allocated their own private primary group with the same name as their login name. If a group is supplied, either its name or uid may be given as an argument. .It Fl G Ar grouplist Set the default groups in which new users are granted membership. -This is a separate set of groups from the primary group, and you should avoid -nominating the same group as both primary and extra groups. +This is a separate set of groups from the primary group. +Avoid nominating the same group as both primary and extra groups. In other words, these extra groups determine membership in groups .Em other than the primary group. .Ar grouplist is a comma-separated list of group names or ids, and are always stored in .Pa /etc/pw.conf by their symbolic names. .It Fl L Ar class This option sets the default login class for new users. .It Fl k Ar dir Set the default .Em skeleton -directory, from which prototype shell and other initialization files are copied when +directory, +from which prototype shell and other initialization files are copied when .Nm creates a user's home directory. See description of .Fl k for naming conventions of these files. .It Xo .Fl u Ar min , Ns Ar max , .Fl i Ar min , Ns Ar max .Xc -These options set the minimum and maximum user and group ids allocated for new accounts -and groups created by +Set the minimum and maximum user and group ids allocated for new +accounts and groups created by .Nm . The default values for each is 1000 minimum and 32000 maximum. .Ar min and .Ar max -are both numbers, where max must be greater than min, and both must be between 0 -and 32767. -In general, user and group ids less than 100 are reserved for use by the system, -and numbers greater than 32000 may also be reserved for special purposes (used by -some system daemons). +are both numbers, where max must be greater than min, +and both must be between 0 and 32767. +In general, +user and group ids less than 100 are reserved for use by the system, +and numbers greater than 32000 may also be reserved for special purposes +.Pq used by some system daemons . .It Fl w Ar method The .Fl w -option sets the default method used to set passwords for newly created user accounts. +option selects the default method used to set passwords for newly created user +accounts. .Ar method is one of: .Pp .Bl -tag -width random -offset indent -compact .It no disable login on newly created accounts .It yes force the password to be the account name .It none force a blank password .It random generate a random password .El .Pp The .Ql \&random or .Ql \&no methods are the most secure; in the former case, .Nm -generates a password and prints it to stdout, which is suitable where you issue -users with passwords to access their accounts rather than having the user nominate -their own (possibly poorly chosen) password. +generates a password and prints it to stdout, +which is suitable when users are issued passwords rather than being allowed +to select their own +.Pq possibly poorly chosen +password. The .Ql \&no method requires that the superuser use .Xr passwd 1 to render the account accessible with a password. .It Fl y Ar path This sets the pathname of the database used by .Tn NIS if you are not sharing the information from .Pa /etc/master.passwd directly with .Tn NIS . You should only set this option for .Tn NIS servers. .El .Pp The .Ar userdel -command has only three valid options. +command has three distinct options. The .Fl n Ar name and .Fl u Ar uid options have already been covered above. The additional option is: .Bl -tag -width "-G grouplist" .It Fl r This tells .Nm to remove the user's home directory and all of its contents. The .Nm utility errs on the side of caution when removing files from the system. -Firstly, it will not do so if the uid of the account being removed is also used by +Firstly, +it will not do so if the uid of the account being removed is also used by another account on the system, and the 'home' directory in the password file is a valid path that commences with the character .Ql \&/ . Secondly, it will only remove files and directories that are actually owned by the user, or symbolic links owned by anyone under the user's home directory. Finally, after deleting all contents owned by the user only empty directories will be removed. If any additional cleanup work is required, this is left to the administrator. .El .Pp -Mail spool files and crontabs are always removed when an account is deleted as these -are unconditionally attached to the user name. +Mail spool files and crontabs are always removed when an account is deleted as +these are unconditionally attached to the user name. Jobs queued for processing by .Ar at -are also removed if the user's uid is unique and not also used by another account on the -system. +are also removed if the user's uid is unique and not also used by another +account on the system. .Pp The .Ar usermod command adds one additional option: .Bl -tag -width "-G grouplist" -.It Fl l Ar name +.It Fl l Ar newname This option allows changing of an existing account name to -.Ql \&name . +.Ql \&newname . The new name must not already exist, and any attempt to duplicate an existing account name will be rejected. .El .Pp The .Ar usershow command allows viewing of an account in one of two formats. By default, the format is identical to the format used in .Pa /etc/master.passwd with the password field replaced with a .Ql \&* . If the .Fl P option is used, then .Nm outputs the account details in a more human readable form. If the .Fl 7 option is used, the account details are shown in v7 format. The .Fl a option lists all users currently on file. Using .Fl F forces .Nm to print the details of an account even if it does not exist. .Pp The command .Ar usernext returns the next available user and group ids separated by a colon. This is normally of interest only to interactive scripts or front-ends that use .Nm . .Sh GROUP OPTIONS The .Fl C and .Fl q options (explained at the start of the previous section) are available with the group manipulation commands. Other common options to all group-related commands are: .Bl -tag -width "-m newmembers" -.It Fl n Ar name +.It Oo Fl n Oc Ar name +Required unless +.Fl g Ar gid +is given. Specify the group name. +In the case of +.Ar groupmod +can be a gid. .It Fl g Ar gid +Required if +.Ar name +is not given. Specify the group numeric id. +In the case of +.Ar groupmod +if paired with +.Ar name , +changes the numeric id of the named group. .Pp As with the account name and id fields, you will usually only need to supply one of these, as the group name implies the uid and vice versa. You will only need to use both when setting a specific group id against a new group or when changing the uid of an existing group. .It Fl M Ar memberlist This option provides an alternative way to add existing users to a new group (in groupadd) or replace an existing membership list (in groupmod). .Ar memberlist is a comma separated list of valid and existing user names or uids. .It Fl m Ar newmembers Similar to .Fl M , this option allows the .Em addition of existing users to a group without replacing the existing list of members. Login names or user ids may be used, and duplicate users are silently eliminated. .It Fl d Ar oldmembers Similar to .Fl M , this option allows the .Em deletion of existing users from a group without replacing the existing list of members. Login names or user ids may be used, and duplicate users are silently eliminated. .El .Pp .Ar groupadd also has a .Fl o option that allows allocation of an existing group id to a new group. -The default action is to reject an attempt to add a group, and this option overrides -the check for duplicate group ids. +The default action is to reject an attempt to add a group, +and this option overrides the check for duplicate group ids. There is rarely any need to duplicate a group id. .Pp The .Ar groupmod command adds one additional option: .Bl -tag -width "-m newmembers" -.It Fl l Ar name +.It Fl l Ar newname This option allows changing of an existing group name to -.Ql \&name . -The new name must not already exist, and any attempt to duplicate an existing group +.Ql \&newname . +The new name must not already exist, +and any attempt to duplicate an existing group name will be rejected. .El .Pp Options for .Ar groupshow are the same as for .Ar usershow , with the .Fl g Ar gid replacing .Fl u Ar uid to specify the group id. The .Fl 7 option does not apply to the .Ar groupshow command. .Pp The command .Ar groupnext returns the next available group id on standard output. .Sh USER LOCKING The .Nm utility supports a simple password locking mechanism for users; it works by prepending the string .Ql *LOCKED* to the beginning of the password field in .Pa master.passwd to prevent successful authentication. .Pp The .Ar lock and .Ar unlock commands take a user name or uid of the account to lock or unlock, respectively. The .Fl V , .Fl C , and .Fl q options as described above are accepted by these commands. .Sh NOTES For a summary of options available with each command, you can use .Dl pw [command] help For example, .Dl pw useradd help lists all available options for the useradd operation. .Pp The .Nm utility allows 8-bit characters in the passwd GECOS field (user's full name, office, work and home phone number subfields), but disallows them in user login and group names. Use 8-bit characters with caution, as connection to the Internet will require that your mail transport program supports 8BITMIME, and will convert headers containing 8-bit characters to 7-bit quoted-printable format. .Xr sendmail 8 does support this. Use of 8-bit characters in the GECOS field should be used in conjunction with the user's default locale and character set and should not be implemented without their use. Using 8-bit characters may also affect other programs that transmit the contents of the GECOS field over the Internet, such as .Xr fingerd 8 , and a small number of TCP/IP clients, such as IRC, where full names specified in the passwd file may be used by default. .Pp The .Nm utility writes a log to the .Pa /var/log/userlog file when actions such as user or group additions or deletions occur. The location of this logfile can be changed in .Xr pw.conf 5 . .Sh FILES .Bl -tag -width /etc/master.passwd.new -compact .It Pa /etc/master.passwd The user database .It Pa /etc/passwd A Version 7 format password file .It Pa /etc/login.conf The user capabilities database .It Pa /etc/group The group database .It Pa /etc/pw.conf Pw default options file .It Pa /var/log/userlog User/group modification logfile .El .Sh EXIT STATUS The .Nm utility returns EXIT_SUCCESS on successful operation, otherwise .Nm returns one of the following exit codes defined by .Xr sysexits 3 as follows: .Bl -tag -width xxxx .It EX_USAGE .Bl -bullet -compact .It Command line syntax errors (invalid keyword, unknown option). .El .It EX_NOPERM .Bl -bullet -compact .It Attempting to run one of the update modes as non-root. .El .It EX_OSERR .Bl -bullet -compact .It Memory allocation error. .It Read error from password file descriptor. .El .It EX_DATAERR .Bl -bullet -compact .It Bad or invalid data provided or missing on the command line or via the password file descriptor. .It Attempted to remove, rename root account or change its uid. .El .It EX_OSFILE .Bl -bullet -compact .It Skeleton directory is invalid or does not exist. .It Base home directory is invalid or does not exist. .It Invalid or non-existent shell specified. .El .It EX_NOUSER .Bl -bullet -compact .It User, user id, group or group id specified does not exist. .It User or group recorded, added, or modified unexpectedly disappeared. .El .It EX_SOFTWARE .Bl -bullet -compact .It No more group or user ids available within specified range. .El .It EX_IOERR .Bl -bullet -compact .It Unable to rewrite configuration file. .It Error updating group or user database files. .It Update error for passwd or group database files. .El .It EX_CONFIG .Bl -bullet -compact .It No base home directory configured. .El .El .Sh SEE ALSO .Xr chpass 1 , .Xr passwd 1 , .Xr umask 2 , .Xr group 5 , .Xr login.conf 5 , .Xr passwd 5 , .Xr pw.conf 5 , .Xr pwd_mkdb 8 , .Xr vipw 8 .Sh HISTORY The .Nm utility was written to mimic many of the options used in the SYSV .Em shadow support suite, but is modified for passwd and group fields specific to the .Bx 4.4 operating system, and combines all of the major elements into a single command. Index: stable/10/usr.sbin/pw/pw.c =================================================================== --- stable/10/usr.sbin/pw/pw.c (revision 287083) +++ stable/10/usr.sbin/pw/pw.c (revision 287084) @@ -1,558 +1,381 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include -#include -#include -#include +#include +#include +#include + #include "pw.h" -#if !defined(_PATH_YP) -#define _PATH_YP "/var/yp/" -#endif const char *Modes[] = { "add", "del", "mod", "show", "next", NULL}; const char *Which[] = {"user", "group", NULL}; static const char *Combo1[] = { "useradd", "userdel", "usermod", "usershow", "usernext", "lock", "unlock", "groupadd", "groupdel", "groupmod", "groupshow", "groupnext", NULL}; static const char *Combo2[] = { "adduser", "deluser", "moduser", "showuser", "nextuser", "lock", "unlock", "addgroup", "delgroup", "modgroup", "showgroup", "nextgroup", NULL}; struct pwf PWF = { PWF_REGULAR, setpwent, endpwent, getpwent, getpwuid, getpwnam, setgrent, endgrent, getgrent, getgrgid, getgrnam, }; struct pwf VPWF = { PWF_ALT, vsetpwent, vendpwent, vgetpwent, vgetpwuid, vgetpwnam, vsetgrent, vendgrent, vgetgrent, vgetgrgid, vgetgrnam, }; +static int (*cmdfunc[W_NUM][M_NUM])(int argc, char **argv, char *_name) = { + { /* user */ + pw_user_add, + pw_user_del, + pw_user_mod, + pw_user_show, + pw_user_next, + pw_user_lock, + pw_user_unlock, + }, + { /* group */ + pw_group_add, + pw_group_del, + pw_group_mod, + pw_group_show, + pw_group_next, + } +}; + struct pwconf conf; -static struct cargs arglist; +static int getindex(const char *words[], const char *word); +static void cmdhelp(int mode, int which); -static int getindex(const char *words[], const char *word); -static void cmdhelp(int mode, int which); - - int main(int argc, char *argv[]) { - int ch; - int mode = -1; - int which = -1; - long id = -1; - char *config = NULL; + int mode = -1, which = -1, tmp; struct stat st; - const char *errstr; - char arg, *name; + char arg, *arg1; bool relocated, nis; - static const char *opts[W_NUM][M_NUM] = - { - { /* user */ - "R:V:C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y", - "R:V:C:qn:u:rY", - "R:V:C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:FNPY", - "R:V:C:qn:u:FPa7", - "R:V:C:q", - "R:V:C:q", - "R:V:C:q" - }, - { /* grp */ - "R:V:C:qn:g:h:H:M:opNPY", - "R:V:C:qn:g:Y", - "R:V:C:qn:d:g:l:h:H:FM:m:NPY", - "R:V:C:qn:g:FPa", - "R:V:C:q" - } - }; - - static int (*funcs[W_NUM]) (int _mode, char *_name, long _id, - struct cargs * _args) = - { /* Request handlers */ - pw_user, - pw_group - }; - - name = NULL; + arg1 = NULL; relocated = nis = false; memset(&conf, 0, sizeof(conf)); + strlcpy(conf.rootdir, "/", sizeof(conf.rootdir)); strlcpy(conf.etcpath, _PATH_PWD, sizeof(conf.etcpath)); conf.checkduplicate = true; + conf.fd = -1; - LIST_INIT(&arglist); + setlocale(LC_ALL, ""); - (void)setlocale(LC_ALL, ""); - /* * Break off the first couple of words to determine what exactly * we're being asked to do */ while (argc > 1) { - int tmp; - if (*argv[1] == '-') { /* * Special case, allow pw -V [args] for scripts etc. */ arg = argv[1][1]; if (arg == 'V' || arg == 'R') { if (relocated) errx(EXIT_FAILURE, "Both '-R' and '-V' " "specified, only one accepted"); relocated = true; optarg = &argv[1][2]; if (*optarg == '\0') { if (stat(argv[2], &st) != 0) errx(EX_OSFILE, \ "no such directory `%s'", argv[2]); if (!S_ISDIR(st.st_mode)) errx(EX_OSFILE, "`%s' not a " "directory", argv[2]); optarg = argv[2]; ++argv; --argc; } memcpy(&PWF, &VPWF, sizeof PWF); if (arg == 'R') { strlcpy(conf.rootdir, optarg, sizeof(conf.rootdir)); PWF._altdir = PWF_ROOTDIR; } snprintf(conf.etcpath, sizeof(conf.etcpath), "%s%s", optarg, arg == 'R' ? "/etc" : ""); } else break; } else if (mode == -1 && (tmp = getindex(Modes, argv[1])) != -1) mode = tmp; else if (which == -1 && (tmp = getindex(Which, argv[1])) != -1) which = tmp; else if ((mode == -1 && which == -1) && ((tmp = getindex(Combo1, argv[1])) != -1 || (tmp = getindex(Combo2, argv[1])) != -1)) { which = tmp / M_NUM; mode = tmp % M_NUM; } else if (strcmp(argv[1], "help") == 0 && argv[2] == NULL) cmdhelp(mode, which); - else if (which != -1 && mode != -1) { - if (strspn(argv[1], "0123456789") == strlen(argv[1])) { - id = strtonum(argv[1], 0, LONG_MAX, &errstr); - if (errstr != NULL) - errx(EX_USAGE, "Bad id '%s': %s", - argv[1], errstr); - } else - name = argv[1]; - } else + else if (which != -1 && mode != -1) + arg1 = argv[1]; + else errx(EX_USAGE, "unknown keyword `%s'", argv[1]); ++argv; --argc; } /* * Bail out unless the user is specific! */ if (mode == -1 || which == -1) cmdhelp(mode, which); - /* - * We know which mode we're in and what we're about to do, so now - * let's dispatch the remaining command line args in a genric way. - */ - optarg = NULL; + conf.rootfd = open(conf.rootdir, O_DIRECTORY|O_CLOEXEC); + if (conf.rootfd == -1) + errx(EXIT_FAILURE, "Unable to open '%s'", conf.rootdir); - while ((ch = getopt(argc, argv, opts[which][mode])) != -1) { - switch (ch) { - case '?': - errx(EX_USAGE, "unknown switch"); - break; - case '7': - conf.v7 = true; - break; - case 'C': - conf.config = optarg; - config = conf.config; - break; - case 'N': - conf.dryrun = true; - break; - case 'l': - if (strlen(optarg) >= MAXLOGNAME) - errx(EX_USAGE, "new name too long: %s", optarg); - conf.newname = optarg; - break; - case 'P': - conf.pretty = true; - break; - case 'Y': - nis = true; - break; - case 'g': - if (which == 0) { /* for user* */ - addarg(&arglist, 'g', optarg); - break; - } - if (strspn(optarg, "0123456789") != strlen(optarg)) - errx(EX_USAGE, "-g expects a number"); - id = strtonum(optarg, 0, LONG_MAX, &errstr); - if (errstr != NULL) - errx(EX_USAGE, "Bad id '%s': %s", optarg, - errstr); - break; - case 'u': - if (strspn(optarg, "0123456789,") != strlen(optarg)) - errx(EX_USAGE, "-u expects a number"); - if (strchr(optarg, ',') != NULL) { - addarg(&arglist, 'u', optarg); - break; - } - id = strtonum(optarg, 0, LONG_MAX, &errstr); - if (errstr != NULL) - errx(EX_USAGE, "Bad id '%s': %s", optarg, - errstr); - break; - case 'n': - if (strspn(optarg, "0123456789") != strlen(optarg)) { - name = optarg; - break; - } - id = strtonum(optarg, 0, LONG_MAX, &errstr); - if (errstr != NULL) - errx(EX_USAGE, "Bad id '%s': %s", optarg, - errstr); - break; - case 'o': - conf.checkduplicate = false; - break; - default: - addarg(&arglist, ch, optarg); - break; - } - optarg = NULL; - } - - if (name != NULL && strlen(name) >= MAXLOGNAME) - errx(EX_USAGE, "name too long: %s", name); - - /* - * Must be root to attempt an update - */ - if (geteuid() != 0 && mode != M_PRINT && mode != M_NEXT && !conf.dryrun) - errx(EX_NOPERM, "you must be root to run this program"); - - /* - * We should immediately look for the -q 'quiet' switch so that we - * don't bother with extraneous errors - */ - if (getarg(&arglist, 'q') != NULL) - freopen(_PATH_DEVNULL, "w", stderr); - - /* - * Set our base working path if not overridden - */ - - if (config == NULL) { /* Only override config location if -C not specified */ - asprintf(&config, "%s/pw.conf", conf.etcpath); - if (config == NULL) - errx(EX_OSERR, "out of memory"); - } - - /* - * Now, let's do the common initialisation - */ - conf.userconf = read_userconfig(config); - - ch = funcs[which] (mode, name, id, &arglist); - - /* - * If everything went ok, and we've been asked to update - * the NIS maps, then do it now - */ - if (ch == EXIT_SUCCESS && nis) { - pid_t pid; - - fflush(NULL); - if (chdir(_PATH_YP) == -1) - warn("chdir(" _PATH_YP ")"); - else if ((pid = fork()) == -1) - warn("fork()"); - else if (pid == 0) { - /* Is make anywhere else? */ - execlp("/usr/bin/make", "make", (char *)NULL); - _exit(1); - } else { - int i; - waitpid(pid, &i, 0); - if ((i = WEXITSTATUS(i)) != 0) - errx(ch, "make exited with status %d", i); - else - pw_log(conf.userconf, mode, which, "NIS maps updated"); - } - } - return ch; + return (cmdfunc[which][mode](argc, argv, arg1)); } static int getindex(const char *words[], const char *word) { - int i = 0; + int i = 0; while (words[i]) { if (strcmp(words[i], word) == 0) - return i; + return (i); i++; } - return -1; + return (-1); } /* * This is probably an overkill for a cmdline help system, but it reflects * the complexity of the command line. */ static void cmdhelp(int mode, int which) { if (which == -1) fprintf(stderr, "usage:\n pw [user|group|lock|unlock] [add|del|mod|show|next] [help|switches/values]\n"); else if (mode == -1) fprintf(stderr, "usage:\n pw %s [add|del|mod|show|next] [help|switches/values]\n", Which[which]); else { /* * We need to give mode specific help */ static const char *help[W_NUM][M_NUM] = { { "usage: pw useradd [name] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-C config configuration file\n" "\t-q quiet operation\n" " Adding users:\n" "\t-n name login name\n" "\t-u uid user id\n" "\t-c comment user name/comment\n" "\t-d directory home directory\n" "\t-e date account expiry date\n" "\t-p date password expiry date\n" "\t-g grp initial group\n" "\t-G grp1,grp2 additional groups\n" "\t-m [ -k dir ] create and set up home\n" "\t-M mode home directory permissions\n" "\t-s shell name of login shell\n" "\t-o duplicate uid ok\n" "\t-L class user class\n" "\t-h fd read password on fd\n" "\t-H fd read encrypted password on fd\n" "\t-Y update NIS maps\n" "\t-N no update\n" " Setting defaults:\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" - "\t-D set user defaults\n" + "\t-D set user defaults\n" "\t-b dir default home root dir\n" "\t-e period default expiry period\n" "\t-p period default password change period\n" "\t-g group default group\n" "\t-G grp1,grp2 additional groups\n" "\t-L class default user class\n" "\t-k dir default home skeleton\n" "\t-M mode home directory permissions\n" "\t-u min,max set min,max uids\n" "\t-i min,max set min,max gids\n" "\t-w method set default password method\n" "\t-s shell default shell\n" "\t-y path set NIS passwd file path\n", "usage: pw userdel [uid|name] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-n name login name\n" "\t-u uid user id\n" "\t-Y update NIS maps\n" + "\t-y path set NIS passwd file path\n" "\t-r remove home & contents\n", "usage: pw usermod [uid|name] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-C config configuration file\n" "\t-q quiet operation\n" "\t-F force add if no user\n" "\t-n name login name\n" "\t-u uid user id\n" "\t-c comment user name/comment\n" "\t-d directory home directory\n" "\t-e date account expiry date\n" "\t-p date password expiry date\n" "\t-g grp initial group\n" "\t-G grp1,grp2 additional groups\n" "\t-l name new login name\n" "\t-L class user class\n" "\t-m [ -k dir ] create and set up home\n" "\t-M mode home directory permissions\n" "\t-s shell name of login shell\n" "\t-w method set new password using method\n" "\t-h fd read password on fd\n" "\t-H fd read encrypted password on fd\n" "\t-Y update NIS maps\n" + "\t-y path set NIS passwd file path\n" "\t-N no update\n", "usage: pw usershow [uid|name] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-n name login name\n" "\t-u uid user id\n" "\t-F force print\n" "\t-P prettier format\n" "\t-a print all users\n" "\t-7 print in v7 format\n", "usage: pw usernext [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-C config configuration file\n" "\t-q quiet operation\n", "usage pw: lock [switches]\n" "\t-V etcdir alternate /etc locations\n" "\t-C config configuration file\n" "\t-q quiet operation\n", "usage pw: unlock [switches]\n" "\t-V etcdir alternate /etc locations\n" "\t-C config configuration file\n" "\t-q quiet operation\n" }, { "usage: pw groupadd [group|gid] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-C config configuration file\n" "\t-q quiet operation\n" "\t-n group group name\n" "\t-g gid group id\n" "\t-M usr1,usr2 add users as group members\n" "\t-o duplicate gid ok\n" "\t-Y update NIS maps\n" "\t-N no update\n", "usage: pw groupdel [group|gid] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-n name group name\n" "\t-g gid group id\n" "\t-Y update NIS maps\n", "usage: pw groupmod [group|gid] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-C config configuration file\n" "\t-q quiet operation\n" "\t-F force add if not exists\n" "\t-n name group name\n" "\t-g gid group id\n" "\t-M usr1,usr2 replaces users as group members\n" "\t-m usr1,usr2 add users as group members\n" "\t-d usr1,usr2 delete users as group members\n" "\t-l name new group name\n" "\t-Y update NIS maps\n" "\t-N no update\n", "usage: pw groupshow [group|gid] [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-n name group name\n" "\t-g gid group id\n" "\t-F force print\n" "\t-P prettier format\n" "\t-a print all accounting groups\n", "usage: pw groupnext [switches]\n" "\t-V etcdir alternate /etc location\n" "\t-R rootir alternate root directory\n" "\t-C config configuration file\n" "\t-q quiet operation\n" } }; fprintf(stderr, "%s", help[which][mode]); } exit(EXIT_FAILURE); -} - -struct carg * -getarg(struct cargs * _args, int ch) -{ - struct carg *c = LIST_FIRST(_args); - - while (c != NULL && c->ch != ch) - c = LIST_NEXT(c, list); - return c; -} - -struct carg * -addarg(struct cargs * _args, int ch, char *argstr) -{ - struct carg *ca = malloc(sizeof(struct carg)); - - if (ca == NULL) - errx(EX_OSERR, "out of memory"); - ca->ch = ch; - ca->val = argstr; - LIST_INSERT_HEAD(_args, ca, list); - return ca; } Index: stable/10/usr.sbin/pw/pw.h =================================================================== --- stable/10/usr.sbin/pw/pw.h (revision 287083) +++ stable/10/usr.sbin/pw/pw.h (revision 287084) @@ -1,101 +1,106 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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$ */ +#include + #define _WITH_GETLINE +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "psdate.h" #include "pwupd.h" enum _mode { M_ADD, M_DELETE, M_UPDATE, M_PRINT, M_NEXT, M_LOCK, M_UNLOCK, M_NUM }; enum _which { W_USER, W_GROUP, W_NUM }; -struct carg -{ - int ch; - char *val; - LIST_ENTRY(carg) list; -}; - -LIST_HEAD(cargs, carg); - #define _DEF_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO) #define _PATH_PW_CONF "/etc/pw.conf" #define _UC_MAXLINE 1024 #define _UC_MAXSHELLS 32 +struct userconf *get_userconfig(const char *cfg); struct userconf *read_userconfig(char const * file); -int write_userconfig(char const * file); -struct carg *addarg(struct cargs * _args, int ch, char *argstr); -struct carg *getarg(struct cargs * _args, int ch); +int write_userconfig(struct userconf *cnf, char const * file); -int pw_user(int mode, char *name, long id, struct cargs * _args); -int pw_group(int mode, char *name, long id, struct cargs * _args); +int pw_group_add(int argc, char **argv, char *name); +int pw_group_del(int argc, char **argv, char *name); +int pw_group_mod(int argc, char **argv, char *name); +int pw_group_next(int argc, char **argv, char *name); +int pw_group_show(int argc, char **argv, char *name); +int pw_user_add(int argc, char **argv, char *name); +int pw_user_add(int argc, char **argv, char *name); +int pw_user_add(int argc, char **argv, char *name); +int pw_user_add(int argc, char **argv, char *name); +int pw_user_del(int argc, char **argv, char *name); +int pw_user_lock(int argc, char **argv, char *name); +int pw_user_mod(int argc, char **argv, char *name); +int pw_user_next(int argc, char **argv, char *name); +int pw_user_show(int argc, char **argv, char *name); +int pw_user_unlock(int argc, char **argv, char *name); +int pw_groupnext(struct userconf *cnf, bool quiet); char *pw_checkname(char *name, int gecos); +uintmax_t pw_checkid(char *nptr, uintmax_t maxval); +int pw_checkfd(char *nptr); int addnispwent(const char *path, struct passwd *pwd); int delnispwent(const char *path, const char *login); int chgnispwent(const char *path, const char *login, struct passwd *pwd); +int groupadd(struct userconf *, char *name, gid_t id, char *members, int fd, + bool dryrun, bool pretty, bool precrypted); + +int nis_update(void); + int boolean_val(char const * str, int dflt); char const *boolean_str(int val); char *newstr(char const * p); void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...) __printflike(4, 5); char *pw_pwcrypt(char *password); extern const char *Modes[]; extern const char *Which[]; + +uintmax_t strtounum(const char * __restrict, uintmax_t, uintmax_t, + const char ** __restrict); Index: stable/10/usr.sbin/pw/pw_conf.c =================================================================== --- stable/10/usr.sbin/pw/pw_conf.c (revision 287083) +++ stable/10/usr.sbin/pw/pw_conf.c (revision 287084) @@ -1,500 +1,524 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include -#include -#include -#include + #include +#include +#include +#include #include "pw.h" #define debugging 0 enum { _UC_NONE, _UC_DEFAULTPWD, _UC_REUSEUID, _UC_REUSEGID, _UC_NISPASSWD, _UC_DOTDIR, _UC_NEWMAIL, _UC_LOGFILE, _UC_HOMEROOT, _UC_HOMEMODE, _UC_SHELLPATH, _UC_SHELLS, _UC_DEFAULTSHELL, _UC_DEFAULTGROUP, _UC_EXTRAGROUPS, _UC_DEFAULTCLASS, _UC_MINUID, _UC_MAXUID, _UC_MINGID, _UC_MAXGID, _UC_EXPIRE, _UC_PASSWORD, _UC_FIELDS }; static char bourne_shell[] = "sh"; static char *system_shells[_UC_MAXSHELLS] = { bourne_shell, "csh", "tcsh" }; static char const *booltrue[] = { "yes", "true", "1", "on", NULL }; static char const *boolfalse[] = { "no", "false", "0", "off", NULL }; static struct userconf config = { 0, /* Default password for new users? (nologin) */ 0, /* Reuse uids? */ 0, /* Reuse gids? */ NULL, /* NIS version of the passwd file */ "/usr/share/skel", /* Where to obtain skeleton files */ NULL, /* Mail to send to new accounts */ "/var/log/userlog", /* Where to log changes */ "/home", /* Where to create home directory */ _DEF_DIRMODE, /* Home directory perms, modified by umask */ "/bin", /* Where shells are located */ system_shells, /* List of shells (first is default) */ bourne_shell, /* Default shell */ NULL, /* Default group name */ NULL, /* Default (additional) groups */ NULL, /* Default login class */ 1000, 32000, /* Allowed range of uids */ 1000, 32000, /* Allowed range of gids */ 0, /* Days until account expires */ - 0, /* Days until password expires */ - 0 /* size of default_group array */ + 0 /* Days until password expires */ }; static char const *comments[_UC_FIELDS] = { "#\n# pw.conf - user/group configuration defaults\n#\n", "\n# Password for new users? no=nologin yes=loginid none=blank random=random\n", "\n# Reuse gaps in uid sequence? (yes or no)\n", "\n# Reuse gaps in gid sequence? (yes or no)\n", "\n# Path to the NIS passwd file (blank or 'no' for none)\n", "\n# Obtain default dotfiles from this directory\n", "\n# Mail this file to new user (/etc/newuser.msg or no)\n", "\n# Log add/change/remove information in this file\n", "\n# Root directory in which $HOME directory is created\n", "\n# Mode for the new $HOME directory, will be modified by umask\n", "\n# Colon separated list of directories containing valid shells\n", "\n# Comma separated list of available shells (without paths)\n", "\n# Default shell (without path)\n", "\n# Default group (leave blank for new group per user)\n", "\n# Extra groups for new users\n", "\n# Default login class for new users\n", "\n# Range of valid default user ids\n", NULL, "\n# Range of valid default group ids\n", NULL, "\n# Days after which account expires (0=disabled)\n", "\n# Days after which password expires (0=disabled)\n" }; static char const *kwds[] = { "", "defaultpasswd", "reuseuids", "reusegids", "nispasswd", "skeleton", "newmail", "logfile", "home", "homemode", "shellpath", "shells", "defaultshell", "defaultgroup", "extragroups", "defaultclass", "minuid", "maxuid", "mingid", "maxgid", "expire_days", "password_days", NULL }; static char * unquote(char const * str) { if (str && (*str == '"' || *str == '\'')) { char *p = strchr(str + 1, *str); if (p != NULL) *p = '\0'; return (char *) (*++str ? str : NULL); } return (char *) str; } int boolean_val(char const * str, int dflt) { if ((str = unquote(str)) != NULL) { int i; for (i = 0; booltrue[i]; i++) if (strcmp(str, booltrue[i]) == 0) return 1; for (i = 0; boolfalse[i]; i++) if (strcmp(str, boolfalse[i]) == 0) return 0; /* * Special cases for defaultpassword */ if (strcmp(str, "random") == 0) return -1; if (strcmp(str, "none") == 0) return -2; } return dflt; } char const * boolean_str(int val) { if (val == -1) return "random"; else if (val == -2) return "none"; else return val ? booltrue[0] : boolfalse[0]; } char * newstr(char const * p) { char *q; if ((p = unquote(p)) == NULL) return (NULL); if ((q = strdup(p)) == NULL) err(1, "strdup()"); return (q); } struct userconf * read_userconfig(char const * file) { FILE *fp; char *buf, *p; + const char *errstr; size_t linecap; ssize_t linelen; buf = NULL; linecap = 0; - config.numgroups = 200; - config.groups = calloc(config.numgroups, sizeof(char *)); - if (config.groups == NULL) - err(1, "calloc()"); if (file == NULL) file = _PATH_PW_CONF; if ((fp = fopen(file, "r")) == NULL) return (&config); while ((linelen = getline(&buf, &linecap, fp)) > 0) { if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') { static char const toks[] = " \t\r\n,="; char *q = strtok(NULL, toks); int i = 0; mode_t *modeset; while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0) ++i; #if debugging if (i == _UC_FIELDS) printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : ""); else printf("Got kwd[%s]=%s\n", p, q); #endif switch (i) { case _UC_DEFAULTPWD: config.default_password = boolean_val(q, 1); break; case _UC_REUSEUID: config.reuse_uids = boolean_val(q, 0); break; case _UC_REUSEGID: config.reuse_gids = boolean_val(q, 0); break; case _UC_NISPASSWD: config.nispasswd = (q == NULL || !boolean_val(q, 1)) ? NULL : newstr(q); break; case _UC_DOTDIR: config.dotdir = (q == NULL || !boolean_val(q, 1)) ? NULL : newstr(q); break; case _UC_NEWMAIL: config.newmail = (q == NULL || !boolean_val(q, 1)) ? NULL : newstr(q); break; case _UC_LOGFILE: config.logfile = (q == NULL || !boolean_val(q, 1)) ? NULL : newstr(q); break; case _UC_HOMEROOT: config.home = (q == NULL || !boolean_val(q, 1)) ? "/home" : newstr(q); break; case _UC_HOMEMODE: modeset = setmode(q); config.homemode = (q == NULL || !boolean_val(q, 1)) ? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE); free(modeset); break; case _UC_SHELLPATH: config.shelldir = (q == NULL || !boolean_val(q, 1)) ? "/bin" : newstr(q); break; case _UC_SHELLS: for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks)) system_shells[i] = newstr(q); if (i > 0) while (i < _UC_MAXSHELLS) system_shells[i++] = NULL; break; case _UC_DEFAULTSHELL: config.shell_default = (q == NULL || !boolean_val(q, 1)) ? (char *) bourne_shell : newstr(q); break; case _UC_DEFAULTGROUP: q = unquote(q); config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL) ? NULL : newstr(q); break; case _UC_EXTRAGROUPS: for (i = 0; q != NULL; q = strtok(NULL, toks)) { - if (extendarray(&config.groups, &config.numgroups, i + 2) != -1) - config.groups[i++] = newstr(q); + if (config.groups == NULL) + config.groups = sl_init(); + sl_add(config.groups, newstr(q)); } - if (i > 0) - while (i < config.numgroups) - config.groups[i++] = NULL; break; case _UC_DEFAULTCLASS: config.default_class = (q == NULL || !boolean_val(q, 1)) ? NULL : newstr(q); break; case _UC_MINUID: - if ((q = unquote(q)) != NULL && isdigit(*q)) - config.min_uid = (uid_t) atol(q); + if ((q = unquote(q)) != NULL) { + config.min_uid = strtounum(q, 0, + UID_MAX, &errstr); + if (errstr) + warnx("Invalid min_uid: '%s';" + " ignoring", q); + } break; case _UC_MAXUID: - if ((q = unquote(q)) != NULL && isdigit(*q)) - config.max_uid = (uid_t) atol(q); + if ((q = unquote(q)) != NULL) { + config.max_uid = strtounum(q, 0, + UID_MAX, &errstr); + if (errstr) + warnx("Invalid max_uid: '%s';" + " ignoring", q); + } break; case _UC_MINGID: - if ((q = unquote(q)) != NULL && isdigit(*q)) - config.min_gid = (gid_t) atol(q); + if ((q = unquote(q)) != NULL) { + config.min_gid = strtounum(q, 0, + GID_MAX, &errstr); + if (errstr) + warnx("Invalid min_gid: '%s';" + " ignoring", q); + } break; case _UC_MAXGID: - if ((q = unquote(q)) != NULL && isdigit(*q)) - config.max_gid = (gid_t) atol(q); + if ((q = unquote(q)) != NULL) { + config.max_gid = strtounum(q, 0, + GID_MAX, &errstr); + if (errstr) + warnx("Invalid max_gid: '%s';" + " ignoring", q); + } break; case _UC_EXPIRE: - if ((q = unquote(q)) != NULL && isdigit(*q)) - config.expire_days = atoi(q); + if ((q = unquote(q)) != NULL) { + config.expire_days = strtonum(q, 0, + INT_MAX, &errstr); + if (errstr) + warnx("Invalid expire days:" + " '%s'; ignoring", q); + } break; case _UC_PASSWORD: - if ((q = unquote(q)) != NULL && isdigit(*q)) - config.password_days = atoi(q); + if ((q = unquote(q)) != NULL) { + config.password_days = strtonum(q, 0, + INT_MAX, &errstr); + if (errstr) + warnx("Invalid password days:" + " '%s'; ignoring", q); + } break; case _UC_FIELDS: case _UC_NONE: break; } } } free(buf); fclose(fp); return (&config); } int -write_userconfig(char const * file) +write_userconfig(struct userconf *cnf, const char *file) { int fd; int i, j; struct sbuf *buf; FILE *fp; if (file == NULL) file = _PATH_PW_CONF; if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1) return (0); if ((fp = fdopen(fd, "w")) == NULL) { close(fd); return (0); } buf = sbuf_new_auto(); for (i = _UC_NONE; i < _UC_FIELDS; i++) { int quote = 1; sbuf_clear(buf); switch (i) { case _UC_DEFAULTPWD: - sbuf_cat(buf, boolean_str(config.default_password)); + sbuf_cat(buf, boolean_str(cnf->default_password)); break; case _UC_REUSEUID: - sbuf_cat(buf, boolean_str(config.reuse_uids)); + sbuf_cat(buf, boolean_str(cnf->reuse_uids)); break; case _UC_REUSEGID: - sbuf_cat(buf, boolean_str(config.reuse_gids)); + sbuf_cat(buf, boolean_str(cnf->reuse_gids)); break; case _UC_NISPASSWD: - sbuf_cat(buf, config.nispasswd ? config.nispasswd : - ""); + sbuf_cat(buf, cnf->nispasswd ? cnf->nispasswd : ""); quote = 0; break; case _UC_DOTDIR: - sbuf_cat(buf, config.dotdir ? config.dotdir : + sbuf_cat(buf, cnf->dotdir ? cnf->dotdir : boolean_str(0)); break; case _UC_NEWMAIL: - sbuf_cat(buf, config.newmail ? config.newmail : + sbuf_cat(buf, cnf->newmail ? cnf->newmail : boolean_str(0)); break; case _UC_LOGFILE: - sbuf_cat(buf, config.logfile ? config.logfile : + sbuf_cat(buf, cnf->logfile ? cnf->logfile : boolean_str(0)); break; case _UC_HOMEROOT: - sbuf_cat(buf, config.home); + sbuf_cat(buf, cnf->home); break; case _UC_HOMEMODE: - sbuf_printf(buf, "%04o", config.homemode); + sbuf_printf(buf, "%04o", cnf->homemode); quote = 0; break; case _UC_SHELLPATH: - sbuf_cat(buf, config.shelldir); + sbuf_cat(buf, cnf->shelldir); break; case _UC_SHELLS: for (j = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) sbuf_printf(buf, "%s\"%s\"", j ? "," : "", system_shells[j]); quote = 0; break; case _UC_DEFAULTSHELL: - sbuf_cat(buf, config.shell_default ? - config.shell_default : bourne_shell); + sbuf_cat(buf, cnf->shell_default ? + cnf->shell_default : bourne_shell); break; case _UC_DEFAULTGROUP: - sbuf_cat(buf, config.default_group ? - config.default_group : ""); + sbuf_cat(buf, cnf->default_group ? + cnf->default_group : ""); break; case _UC_EXTRAGROUPS: - for (j = 0; j < config.numgroups && - config.groups[j] != NULL; j++) + for (j = 0; cnf->groups != NULL && + j < (int)cnf->groups->sl_cur; j++) sbuf_printf(buf, "%s\"%s\"", j ? - "," : "", config.groups[j]); + "," : "", cnf->groups->sl_str[j]); quote = 0; break; case _UC_DEFAULTCLASS: - sbuf_cat(buf, config.default_class ? - config.default_class : ""); + sbuf_cat(buf, cnf->default_class ? + cnf->default_class : ""); break; case _UC_MINUID: - sbuf_printf(buf, "%u", config.min_uid); + sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid); quote = 0; break; case _UC_MAXUID: - sbuf_printf(buf, "%u", config.max_uid); + sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid); quote = 0; break; case _UC_MINGID: - sbuf_printf(buf, "%u", config.min_gid); + sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid); quote = 0; break; case _UC_MAXGID: - sbuf_printf(buf, "%u", config.max_gid); + sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid); quote = 0; break; case _UC_EXPIRE: - sbuf_printf(buf, "%d", config.expire_days); + sbuf_printf(buf, "%jd", (intmax_t)cnf->expire_days); quote = 0; break; case _UC_PASSWORD: - sbuf_printf(buf, "%d", config.password_days); + sbuf_printf(buf, "%jd", (intmax_t)cnf->password_days); quote = 0; break; case _UC_NONE: break; } sbuf_finish(buf); if (comments[i]) fputs(comments[i], fp); if (*kwds[i]) { if (quote) fprintf(fp, "%s = \"%s\"\n", kwds[i], sbuf_data(buf)); else fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf)); #if debugging printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf)); #endif } } sbuf_delete(buf); return (fclose(fp) != EOF); } Index: stable/10/usr.sbin/pw/pw_group.c =================================================================== --- stable/10/usr.sbin/pw/pw_group.c (revision 287083) +++ stable/10/usr.sbin/pw/pw_group.c (revision 287084) @@ -1,422 +1,689 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include "pw.h" #include "bitmap.h" - static struct passwd *lookup_pwent(const char *user); -static void delete_members(char ***members, int *grmembers, int *i, - struct carg *arg, struct group *grp); -static int print_group(struct group * grp); -static gid_t gr_gidpolicy(struct userconf * cnf, long id); +static void delete_members(struct group *grp, char *list); +static int print_group(struct group * grp, bool pretty); +static gid_t gr_gidpolicy(struct userconf * cnf, intmax_t id); -int -pw_group(int mode, char *name, long id, struct cargs * args) +static void +grp_set_passwd(struct group *grp, bool update, int fd, bool precrypted) { - int rc; - struct carg *arg; - struct group *grp = NULL; - int grmembers = 0; - char **members = NULL; - struct userconf *cnf = conf.userconf; + int b; + int istty; + struct termios t, n; + char *p, line[256]; - static struct group fakegroup = - { - "nogroup", - "*", - -1, - NULL - }; + if (fd == -1) + return; - if (mode == M_LOCK || mode == M_UNLOCK) - errx(EX_USAGE, "'lock' command is not available for groups"); - - /* - * With M_NEXT, we only need to return the - * next gid to stdout - */ - if (mode == M_NEXT) { - gid_t next = gr_gidpolicy(cnf, id); - if (getarg(args, 'q')) - return next; - printf("%u\n", next); - return EXIT_SUCCESS; + if (fd == '-') { + grp->gr_passwd = "*"; /* No access */ + return; } - - if (mode == M_PRINT && getarg(args, 'a')) { - SETGRENT(); - while ((grp = GETGRENT()) != NULL) - print_group(grp); - ENDGRENT(); - return EXIT_SUCCESS; + + if ((istty = isatty(fd))) { + n = t; + /* Disable echo */ + n.c_lflag &= ~(ECHO); + tcsetattr(fd, TCSANOW, &n); + printf("%sassword for group %s:", update ? "New p" : "P", + grp->gr_name); + fflush(stdout); } - if (id < 0 && name == NULL) - errx(EX_DATAERR, "group name or id required"); - - grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id); - - if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) { - if (name == NULL && grp == NULL) /* Try harder */ - grp = GETGRGID(id); - - if (grp == NULL) { - if (mode == M_PRINT && getarg(args, 'F')) { - char *fmems[1]; - fmems[0] = NULL; - fakegroup.gr_name = name ? name : "nogroup"; - fakegroup.gr_gid = (gid_t) id; - fakegroup.gr_mem = fmems; - return print_group(&fakegroup); - } - if (name == NULL) - errx(EX_DATAERR, "unknown group `%s'", name); - else - errx(EX_DATAERR, "unknown group `%ld'", id); - } - if (name == NULL) /* Needed later */ - name = grp->gr_name; - - /* - * Handle deletions now - */ - if (mode == M_DELETE) { - rc = delgrent(grp); - if (rc == -1) - err(EX_IOERR, "group '%s' not available (NIS?)", - name); - else if (rc != 0) { - err(EX_IOERR, "group update"); - } - pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", name, id); - return EXIT_SUCCESS; - } else if (mode == M_PRINT) - return print_group(grp); - - if (id > 0) - grp->gr_gid = (gid_t) id; - - if (conf.newname != NULL) - grp->gr_name = pw_checkname(conf.newname, 0); - } else { - if (name == NULL) /* Required */ - errx(EX_DATAERR, "group name required"); - else if (grp != NULL) /* Exists */ - errx(EX_DATAERR, "group name `%s' already exists", name); - - extendarray(&members, &grmembers, 200); - members[0] = NULL; - grp = &fakegroup; - grp->gr_name = pw_checkname(name, 0); - grp->gr_passwd = "*"; - grp->gr_gid = gr_gidpolicy(cnf, id); - grp->gr_mem = members; + b = read(fd, line, sizeof(line) - 1); + if (istty) { /* Restore state */ + tcsetattr(fd, TCSANOW, &t); + fputc('\n', stdout); + fflush(stdout); } + if (b < 0) + err(EX_OSERR, "-h file descriptor"); + line[b] = '\0'; + if ((p = strpbrk(line, " \t\r\n")) != NULL) + *p = '\0'; + if (!*line) + errx(EX_DATAERR, "empty password read on file descriptor %d", + conf.fd); + if (precrypted) { + if (strchr(line, ':') != 0) + errx(EX_DATAERR, "wrong encrypted passwrd"); + grp->gr_passwd = line; + } else + grp->gr_passwd = pw_pwcrypt(line); +} - /* - * This allows us to set a group password Group passwords is an - * antique idea, rarely used and insecure (no secure database) Should - * be discouraged, but it is apparently still supported by some - * software. - */ +int +pw_groupnext(struct userconf *cnf, bool quiet) +{ + gid_t next = gr_gidpolicy(cnf, -1); - if ((arg = getarg(args, 'h')) != NULL || - (arg = getarg(args, 'H')) != NULL) { - if (strcmp(arg->val, "-") == 0) - grp->gr_passwd = "*"; /* No access */ - else { - int fd = atoi(arg->val); - int precrypt = (arg->ch == 'H'); - int b; - int istty = isatty(fd); - struct termios t; - char *p, line[256]; + if (quiet) + return (next); + printf("%ju\n", (uintmax_t)next); - if (istty) { - if (tcgetattr(fd, &t) == -1) - istty = 0; - else { - struct termios n = t; + return (EXIT_SUCCESS); +} - /* Disable echo */ - n.c_lflag &= ~(ECHO); - tcsetattr(fd, TCSANOW, &n); - printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name); - fflush(stdout); - } - } - b = read(fd, line, sizeof(line) - 1); - if (istty) { /* Restore state */ - tcsetattr(fd, TCSANOW, &t); - fputc('\n', stdout); - fflush(stdout); - } - if (b < 0) - err(EX_OSERR, "-h file descriptor"); - line[b] = '\0'; - if ((p = strpbrk(line, " \t\r\n")) != NULL) - *p = '\0'; - if (!*line) - errx(EX_DATAERR, "empty password read on file descriptor %d", fd); - if (precrypt) { - if (strchr(line, ':') != NULL) - return EX_DATAERR; - grp->gr_passwd = line; - } else - grp->gr_passwd = pw_pwcrypt(line); - } - } +static struct group * +getgroup(char *name, intmax_t id, bool fatal) +{ + struct group *grp; - if (((arg = getarg(args, 'M')) != NULL || - (arg = getarg(args, 'd')) != NULL || - (arg = getarg(args, 'm')) != NULL) && arg->val) { - int i = 0; - char *p; - struct passwd *pwd; - - /* Make sure this is not stay NULL with -M "" */ - extendarray(&members, &grmembers, 200); - if (arg->ch == 'd') - delete_members(&members, &grmembers, &i, arg, grp); - else if (arg->ch == 'm') { - int k = 0; - - if (grp->gr_mem != NULL) { - while (grp->gr_mem[k] != NULL) { - if (extendarray(&members, &grmembers, i + 2) != -1) - members[i++] = grp->gr_mem[k]; - k++; - } - } - } - - if (arg->ch != 'd') - for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { - int j; - - /* - * Check for duplicates - */ - pwd = lookup_pwent(p); - for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++) - ; - if (j == i && extendarray(&members, &grmembers, i + 2) != -1) - members[i++] = newstr(pwd->pw_name); - } - while (i < grmembers) - members[i++] = NULL; - grp->gr_mem = members; + if (id < 0 && name == NULL) + errx(EX_DATAERR, "groupname or id required"); + grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id); + if (grp == NULL) { + if (!fatal) + return (NULL); + if (name == NULL) + errx(EX_DATAERR, "unknown gid `%ju'", id); + errx(EX_DATAERR, "unknown group `%s'", name); } - - if (conf.dryrun) - return print_group(grp); - - if (mode == M_ADD && (rc = addgrent(grp)) != 0) { - if (rc == -1) - errx(EX_IOERR, "group '%s' already exists", - grp->gr_name); - else - err(EX_IOERR, "group update"); - } else if (mode == M_UPDATE && (rc = chggrent(name, grp)) != 0) { - if (rc == -1) - errx(EX_IOERR, "group '%s' not available (NIS?)", - grp->gr_name); - else - err(EX_IOERR, "group update"); - } - - if (conf.newname != NULL) - name = conf.newname; - /* grp may have been invalidated */ - if ((grp = GETGRNAM(name)) == NULL) - errx(EX_SOFTWARE, "group disappeared during update"); - - pw_log(cnf, mode, W_GROUP, "%s(%u)", grp->gr_name, grp->gr_gid); - - free(members); - - return EXIT_SUCCESS; + return (grp); } - /* * Lookup a passwd entry using a name or UID. */ static struct passwd * lookup_pwent(const char *user) { struct passwd *pwd; if ((pwd = GETPWNAM(user)) == NULL && (!isdigit((unsigned char)*user) || (pwd = getpwuid((uid_t) atoi(user))) == NULL)) errx(EX_NOUSER, "user `%s' does not exist", user); return (pwd); } /* * Delete requested members from a group. */ static void -delete_members(char ***members, int *grmembers, int *i, struct carg *arg, - struct group *grp) +delete_members(struct group *grp, char *list) { - bool matchFound; - char *user; - char *valueCopy; - char *valuePtr; + char *p; int k; - struct passwd *pwd; if (grp->gr_mem == NULL) return; - k = 0; - while (grp->gr_mem[k] != NULL) { - matchFound = false; - if ((valueCopy = strdup(arg->val)) == NULL) - errx(EX_UNAVAILABLE, "out of memory"); - valuePtr = valueCopy; - while ((user = strsep(&valuePtr, ", \t")) != NULL) { - pwd = lookup_pwent(user); - if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) { - matchFound = true; + for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { + for (k = 0; grp->gr_mem[k] != NULL; k++) { + if (strcmp(grp->gr_mem[k], p) == 0) break; - } } - free(valueCopy); + if (grp->gr_mem[k] == NULL) /* No match */ + continue; - if (!matchFound && - extendarray(members, grmembers, *i + 2) != -1) - (*members)[(*i)++] = grp->gr_mem[k]; - - k++; + for (; grp->gr_mem[k] != NULL; k++) + grp->gr_mem[k] = grp->gr_mem[k+1]; } - - return; } - -static gid_t -gr_gidpolicy(struct userconf * cnf, long id) +static gid_t +gr_gidpolicy(struct userconf * cnf, intmax_t id) { struct group *grp; + struct bitmap bm; gid_t gid = (gid_t) - 1; /* * Check the given gid, if any */ if (id > 0) { gid = (gid_t) id; if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate) - errx(EX_DATAERR, "gid `%u' has already been allocated", grp->gr_gid); - } else { - struct bitmap bm; + errx(EX_DATAERR, "gid `%ju' has already been allocated", + (uintmax_t)grp->gr_gid); + return (gid); + } - /* - * We need to allocate the next available gid under one of - * two policies a) Grab the first unused gid b) Grab the - * highest possible unused gid - */ - if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ - cnf->min_gid = 1000; - cnf->max_gid = 32000; - } - bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); + /* + * We need to allocate the next available gid under one of + * two policies a) Grab the first unused gid b) Grab the + * highest possible unused gid + */ + if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ + cnf->min_gid = 1000; + cnf->max_gid = 32000; + } + bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); - /* - * Now, let's fill the bitmap from the password file - */ - SETGRENT(); - while ((grp = GETGRENT()) != NULL) - if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && - (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) - bm_setbit(&bm, grp->gr_gid - cnf->min_gid); - ENDGRENT(); + /* + * Now, let's fill the bitmap from the password file + */ + SETGRENT(); + while ((grp = GETGRENT()) != NULL) + if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && + (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) + bm_setbit(&bm, grp->gr_gid - cnf->min_gid); + ENDGRENT(); - /* - * Then apply the policy, with fallback to reuse if necessary - */ - if (cnf->reuse_gids) + /* + * Then apply the policy, with fallback to reuse if necessary + */ + if (cnf->reuse_gids) + gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); + else { + gid = (gid_t) (bm_lastset(&bm) + 1); + if (!bm_isset(&bm, gid)) + gid += cnf->min_gid; + else gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); - else { - gid = (gid_t) (bm_lastset(&bm) + 1); - if (!bm_isset(&bm, gid)) - gid += cnf->min_gid; - else - gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); - } - - /* - * Another sanity check - */ - if (gid < cnf->min_gid || gid > cnf->max_gid) - errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used"); - bm_dealloc(&bm); } - return gid; + + /* + * Another sanity check + */ + if (gid < cnf->min_gid || gid > cnf->max_gid) + errx(EX_SOFTWARE, "unable to allocate a new gid - range fully " + "used"); + bm_dealloc(&bm); + return (gid); } - static int -print_group(struct group * grp) +print_group(struct group * grp, bool pretty) { - if (!conf.pretty) { - char *buf = NULL; + char *buf = NULL; + int i; - buf = gr_make(grp); - printf("%s\n", buf); - free(buf); - } else { - int i; - + if (pretty) { printf("Group Name: %-15s #%lu\n" " Members: ", grp->gr_name, (long) grp->gr_gid); if (grp->gr_mem != NULL) { for (i = 0; grp->gr_mem[i]; i++) printf("%s%s", i ? "," : "", grp->gr_mem[i]); } fputs("\n\n", stdout); + return (EXIT_SUCCESS); } - return EXIT_SUCCESS; + + buf = gr_make(grp); + printf("%s\n", buf); + free(buf); + return (EXIT_SUCCESS); +} + +int +pw_group_next(int argc, char **argv, char *arg1 __unused) +{ + struct userconf *cnf; + const char *cfg = NULL; + int ch; + bool quiet; + + while ((ch = getopt(argc, argv, "Cq")) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + cnf = get_userconfig(cfg); + return (pw_groupnext(cnf, quiet)); +} + +int +pw_group_show(int argc, char **argv, char *arg1) +{ + struct group *grp = NULL; + char *name; + intmax_t id = -1; + int ch; + bool all, force, quiet, pretty; + + all = force = quiet = pretty = false; + + struct group fakegroup = { + "nogroup", + "*", + -1, + NULL + }; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, GID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) { + switch (ch) { + case 'C': + /* ignore compatibility */ + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'g': + id = pw_checkid(optarg, GID_MAX); + break; + case 'F': + force = true; + break; + case 'P': + pretty = true; + break; + case 'a': + all = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + if (all) { + SETGRENT(); + while ((grp = GETGRENT()) != NULL) + print_group(grp, pretty); + ENDGRENT(); + return (EXIT_SUCCESS); + } + + grp = getgroup(name, id, !force); + if (grp == NULL) + grp = &fakegroup; + + return (print_group(grp, pretty)); +} + +int +pw_group_del(int argc, char **argv, char *arg1) +{ + struct userconf *cnf = NULL; + struct group *grp = NULL; + char *name; + const char *cfg = NULL; + intmax_t id = -1; + int ch, rc; + bool quiet = false; + bool nis = false; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, GID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'g': + id = pw_checkid(optarg, GID_MAX); + break; + case 'Y': + nis = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + grp = getgroup(name, id, true); + cnf = get_userconfig(cfg); + rc = delgrent(grp); + if (rc == -1) + err(EX_IOERR, "group '%s' not available (NIS?)", name); + else if (rc != 0) + err(EX_IOERR, "group update"); + pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name, + (uintmax_t)id); + + if (nis && nis_update() == 0) + pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated"); + + return (EXIT_SUCCESS); +} + +static bool +grp_has_member(struct group *grp, const char *name) +{ + int j; + + for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++) + if (strcmp(grp->gr_mem[j], name) == 0) + return (true); + return (false); +} + +static void +grp_add_members(struct group **grp, char *members) +{ + struct passwd *pwd; + char *p; + char tok[] = ", \t"; + + if (members == NULL) + return; + for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) { + pwd = lookup_pwent(p); + if (grp_has_member(*grp, pwd->pw_name)) + continue; + *grp = gr_add(*grp, pwd->pw_name); + } +} + +int +groupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd, + bool dryrun, bool pretty, bool precrypted) +{ + struct group *grp; + int rc; + + struct group fakegroup = { + "nogroup", + "*", + -1, + NULL + }; + + grp = &fakegroup; + grp->gr_name = pw_checkname(name, 0); + grp->gr_passwd = "*"; + grp->gr_gid = gr_gidpolicy(cnf, id); + grp->gr_mem = NULL; + + /* + * This allows us to set a group password Group passwords is an + * antique idea, rarely used and insecure (no secure database) Should + * be discouraged, but it is apparently still supported by some + * software. + */ + grp_set_passwd(grp, false, fd, precrypted); + grp_add_members(&grp, members); + if (dryrun) + return (print_group(grp, pretty)); + + if ((rc = addgrent(grp)) != 0) { + if (rc == -1) + errx(EX_IOERR, "group '%s' already exists", + grp->gr_name); + else + err(EX_IOERR, "group update"); + } + + pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name, + (uintmax_t)grp->gr_gid); + + return (EXIT_SUCCESS); +} + +int +pw_group_add(int argc, char **argv, char *arg1) +{ + struct userconf *cnf = NULL; + char *name = NULL; + char *members = NULL; + const char *cfg = NULL; + intmax_t id = -1; + int ch, rc, fd = -1; + bool quiet, precrypted, dryrun, pretty, nis; + + quiet = precrypted = dryrun = pretty = nis = false; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, GID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'g': + id = pw_checkid(optarg, GID_MAX); + break; + case 'H': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + precrypted = true; + if (fd == '-') + errx(EX_USAGE, "-H expects a file descriptor"); + break; + case 'h': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + break; + case 'M': + members = optarg; + break; + case 'o': + conf.checkduplicate = false; + break; + case 'N': + dryrun = true; + break; + case 'P': + pretty = true; + break; + case 'Y': + nis = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + if (name == NULL) + errx(EX_DATAERR, "group name required"); + if (GETGRNAM(name) != NULL) + errx(EX_DATAERR, "group name `%s' already exists", name); + cnf = get_userconfig(cfg); + rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun, + pretty, precrypted); + if (nis && rc == EXIT_SUCCESS && nis_update() == 0) + pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated"); + + return (rc); +} + +int +pw_group_mod(int argc, char **argv, char *arg1) +{ + struct userconf *cnf; + struct group *grp = NULL; + const char *cfg = NULL; + char *oldmembers = NULL; + char *members = NULL; + char *newmembers = NULL; + char *newname = NULL; + char *name = NULL; + intmax_t id = -1; + int ch, rc, fd = -1; + bool quiet, pretty, dryrun, nis, precrypted; + + quiet = pretty = dryrun = nis = precrypted = false; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, GID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'g': + id = pw_checkid(optarg, GID_MAX); + break; + case 'd': + oldmembers = optarg; + break; + case 'l': + newname = optarg; + break; + case 'H': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + precrypted = true; + if (fd == '-') + errx(EX_USAGE, "-H expects a file descriptor"); + break; + case 'h': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + break; + case 'M': + members = optarg; + break; + case 'm': + newmembers = optarg; + break; + case 'N': + dryrun = true; + break; + case 'P': + pretty = true; + break; + case 'Y': + nis = true; + break; + } + } + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + cnf = get_userconfig(cfg); + grp = getgroup(name, id, true); + if (name == NULL) + name = grp->gr_name; + if (id > 0) + grp->gr_gid = id; + + if (newname != NULL) + grp->gr_name = pw_checkname(newname, 0); + + grp_set_passwd(grp, true, fd, precrypted); + /* + * Keep the same logic as old code for now: + * if -M is passed, -d and -m are ignored + * then id -d, -m is ignored + * last is -m + */ + + if (members) { + grp->gr_mem = NULL; + grp_add_members(&grp, members); + } else if (oldmembers) { + delete_members(grp, oldmembers); + } else if (newmembers) { + grp_add_members(&grp, newmembers); + } + + if ((rc = chggrent(name, grp)) != 0) { + if (rc == -1) + errx(EX_IOERR, "group '%s' not available (NIS?)", + grp->gr_name); + else + err(EX_IOERR, "group update"); + } + + if (newname) + name = newname; + + /* grp may have been invalidated */ + if ((grp = GETGRNAM(name)) == NULL) + errx(EX_SOFTWARE, "group disappeared during update"); + + pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name, + (uintmax_t)grp->gr_gid); + + if (nis && nis_update() == 0) + pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated"); + + return (EXIT_SUCCESS); } Index: stable/10/usr.sbin/pw/pw_log.c =================================================================== --- stable/10/usr.sbin/pw/pw_log.c (revision 287083) +++ stable/10/usr.sbin/pw/pw_log.c (revision 287084) @@ -1,66 +1,68 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include +#include +#include #include "pw.h" static FILE *logfile = NULL; void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...) { if (cnf->logfile && *cnf->logfile) { if (logfile == NULL) { /* With umask==0 we need to control file access modes on create */ int fd = open(cnf->logfile, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd != -1) logfile = fdopen(fd, "a"); } if (logfile != NULL) { va_list argp; time_t now = time(NULL); struct tm *t = localtime(&now); char nfmt[256]; const char *name; if ((name = getenv("LOGNAME")) == NULL && (name = getenv("USER")) == NULL) name = "unknown"; /* ISO 8601 International Standard Date format */ strftime(nfmt, sizeof nfmt, "%Y-%m-%d %T ", t); sprintf(nfmt + strlen(nfmt), "[%s:%s%s] %s\n", name, Which[which], Modes[mode], fmt); va_start(argp, fmt); vfprintf(logfile, nfmt, argp); va_end(argp); fflush(logfile); } } } Index: stable/10/usr.sbin/pw/pw_nis.c =================================================================== --- stable/10/usr.sbin/pw/pw_nis.c (revision 287083) +++ stable/10/usr.sbin/pw/pw_nis.c (revision 287084) @@ -1,93 +1,95 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include + #include #include #include #include "pw.h" static int pw_nisupdate(const char * path, struct passwd * pwd, char const * user) { int pfd, tfd; struct passwd *pw = NULL; struct passwd *old_pw = NULL; + printf("===> %s\n", path); if (pwd != NULL) pw = pw_dup(pwd); if (user != NULL) old_pw = GETPWNAM(user); if (pw_init(NULL, path)) err(1,"pw_init()"); if ((pfd = pw_lock()) == -1) { pw_fini(); err(1, "pw_lock()"); } if ((tfd = pw_tmp(-1)) == -1) { pw_fini(); err(1, "pw_tmp()"); } if (pw_copy(pfd, tfd, pw, old_pw) == -1) { pw_fini(); err(1, "pw_copy()"); } if (chmod(pw_tempname(), 0644) == -1) err(1, "chmod()"); if (rename(pw_tempname(), path) == -1) err(1, "rename()"); free(pw); pw_fini(); return (0); } int addnispwent(const char *path, struct passwd * pwd) { return pw_nisupdate(path, pwd, NULL); } int chgnispwent(const char *path, char const * login, struct passwd * pwd) { return pw_nisupdate(path, pwd, login); } int delnispwent(const char *path, const char *login) { return pw_nisupdate(path, NULL, login); } Index: stable/10/usr.sbin/pw/pw_user.c =================================================================== --- stable/10/usr.sbin/pw/pw_user.c (revision 287083) +++ stable/10/usr.sbin/pw/pw_user.c (revision 287084) @@ -1,1321 +1,1807 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ +#include +#include +#include +#include + #include +#include #include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include #include +#include +#include +#include +#include +#include +#include + #include "pw.h" #include "bitmap.h" +#include "psdate.h" #define LOGNAMESIZE (MAXLOGNAME-1) static char locked_str[] = "*LOCKED*"; -static int delete_user(struct userconf *cnf, struct passwd *pwd, - char *name, int delete, int mode); -static int print_user(struct passwd * pwd); -static uid_t pw_uidpolicy(struct userconf * cnf, long id); -static uid_t pw_gidpolicy(struct cargs * args, char *nam, gid_t prefer); -static time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args); -static time_t pw_exppolicy(struct userconf * cnf, struct cargs * args); -static char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user); -static char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell); -static char *pw_password(struct userconf * cnf, struct cargs * args, char const * user); -static char *shell_path(char const * path, char *shells[], char *sh); -static void rmat(uid_t uid); -static void rmopie(char const * name); +static struct passwd fakeuser = { + "nouser", + "*", + -1, + -1, + 0, + "", + "User &", + "/nonexistent", + "/bin/sh", + 0, + 0 +}; +static int print_user(struct passwd *pwd, bool pretty, bool v7); +static uid_t pw_uidpolicy(struct userconf *cnf, intmax_t id); +static uid_t pw_gidpolicy(struct userconf *cnf, char *grname, char *nam, + gid_t prefer, bool dryrun); +static char *pw_homepolicy(struct userconf * cnf, char *homedir, + const char *user); +static char *pw_shellpolicy(struct userconf * cnf); +static char *pw_password(struct userconf * cnf, char const * user, + bool dryrun); +static char *shell_path(char const * path, char *shells[], char *sh); +static void rmat(uid_t uid); +static void rmopie(char const * name); + static void -create_and_populate_homedir(int mode, struct passwd *pwd) +mkdir_home_parents(int dfd, const char *dir) { - char *homedir, *dotdir; - struct userconf *cnf = conf.userconf; + struct stat st; + char *dirs, *tmp; - homedir = dotdir = NULL; + if (*dir != '/') + errx(EX_DATAERR, "invalid base directory for home '%s'", dir); - if (conf.rootdir[0] != '\0') { - asprintf(&homedir, "%s/%s", conf.rootdir, pwd->pw_dir); - if (homedir == NULL) - errx(EX_OSERR, "out of memory"); - asprintf(&dotdir, "%s/%s", conf.rootdir, cnf->dotdir); - } + dir++; - copymkdir(homedir ? homedir : pwd->pw_dir, dotdir ? dotdir: cnf->dotdir, - cnf->homemode, pwd->pw_uid, pwd->pw_gid); - pw_log(cnf, mode, W_USER, "%s(%u) home %s made", pwd->pw_name, - pwd->pw_uid, pwd->pw_dir); -} - -/*- - * -C config configuration file - * -q quiet operation - * -n name login name - * -u uid user id - * -c comment user name/comment - * -d directory home directory - * -e date account expiry date - * -p date password expiry date - * -g grp primary group - * -G grp1,grp2 additional groups - * -m [ -k dir ] create and set up home - * -s shell name of login shell - * -o duplicate uid ok - * -L class user class - * -l name new login name - * -h fd password filehandle - * -H fd encrypted password filehandle - * -F force print or add - * Setting defaults: - * -D set user defaults - * -b dir default home root dir - * -e period default expiry period - * -p period default password change period - * -g group default group - * -G grp1,grp2.. default additional groups - * -L class default login class - * -k dir default home skeleton - * -s shell default shell - * -w method default password method - */ - -int -pw_user(int mode, char *name, long id, struct cargs * args) -{ - int rc, edited = 0; - char *p = NULL; - char *passtmp; - struct carg *arg; - struct passwd *pwd = NULL; - struct group *grp; - struct stat st; - struct userconf *cnf; - char line[_PASSWORD_LEN+1]; - char path[MAXPATHLEN]; - FILE *fp; - char *dmode_c; - void *set = NULL; - - static struct passwd fakeuser = - { - NULL, - "*", - -1, - -1, - 0, - "", - "User &", - "/nonexistent", - "/bin/sh", - 0 -#if defined(__FreeBSD__) - ,0 -#endif - }; - - cnf = conf.userconf; - - /* - * With M_NEXT, we only need to return the - * next uid to stdout - */ - if (mode == M_NEXT) - { - uid_t next = pw_uidpolicy(cnf, id); - if (getarg(args, 'q')) - return next; - printf("%u:", next); - pw_group(mode, name, -1, args); - return EXIT_SUCCESS; + if (fstatat(dfd, dir, &st, 0) != -1) { + if (S_ISDIR(st.st_mode)) + return; + errx(EX_OSFILE, "root home `/%s' is not a directory", dir); } - /* - * We can do all of the common legwork here - */ + dirs = strdup(dir); + if (dirs == NULL) + errx(EX_UNAVAILABLE, "out of memory"); - if ((arg = getarg(args, 'b')) != NULL) { - cnf->home = arg->val; - } + tmp = strrchr(dirs, '/'); + if (tmp == NULL) + return; + tmp[0] = '\0'; - if ((arg = getarg(args, 'M')) != NULL) { - dmode_c = arg->val; - if ((set = setmode(dmode_c)) == NULL) - errx(EX_DATAERR, "invalid directory creation mode '%s'", - dmode_c); - cnf->homemode = getmode(set, _DEF_DIRMODE); - free(set); - } - /* - * If we'll need to use it or we're updating it, - * then create the base home directory if necessary + * This is a kludge especially for Joerg :) + * If the home directory would be created in the root partition, then + * we really create it under /usr which is likely to have more space. + * But we create a symlink from cnf->home -> "/usr" -> cnf->home */ - if (arg != NULL || getarg(args, 'm') != NULL) { - int l = strlen(cnf->home); - - if (l > 1 && cnf->home[l-1] == '/') /* Shave off any trailing path delimiter */ - cnf->home[--l] = '\0'; - - if (l < 2 || *cnf->home != '/') /* Check for absolute path name */ - errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home); - - if (stat(cnf->home, &st) == -1) { - char dbuf[MAXPATHLEN]; - - /* - * This is a kludge especially for Joerg :) - * If the home directory would be created in the root partition, then - * we really create it under /usr which is likely to have more space. - * But we create a symlink from cnf->home -> "/usr" -> cnf->home - */ - if (strchr(cnf->home+1, '/') == NULL) { - snprintf(dbuf, MAXPATHLEN, "/usr%s", cnf->home); - if (mkdir(dbuf, _DEF_DIRMODE) != -1 || errno == EEXIST) { - chown(dbuf, 0, 0); - /* - * Skip first "/" and create symlink: - * /home -> usr/home - */ - symlink(dbuf+1, cnf->home); - } - /* If this falls, fall back to old method */ - } - strlcpy(dbuf, cnf->home, sizeof(dbuf)); - p = dbuf; - if (stat(dbuf, &st) == -1) { - while ((p = strchr(p + 1, '/')) != NULL) { - *p = '\0'; - if (stat(dbuf, &st) == -1) { - if (mkdir(dbuf, _DEF_DIRMODE) == -1) - goto direrr; - chown(dbuf, 0, 0); - } else if (!S_ISDIR(st.st_mode)) - errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf); - *p = '/'; - } - } - if (stat(dbuf, &st) == -1) { - if (mkdir(dbuf, _DEF_DIRMODE) == -1) { - direrr: err(EX_OSFILE, "mkdir '%s'", dbuf); - } - chown(dbuf, 0, 0); - } - } else if (!S_ISDIR(st.st_mode)) - errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home); - } - - if ((arg = getarg(args, 'e')) != NULL) - cnf->expire_days = atoi(arg->val); - - if ((arg = getarg(args, 'y')) != NULL) - cnf->nispasswd = arg->val; - - if ((arg = getarg(args, 'p')) != NULL && arg->val) - cnf->password_days = atoi(arg->val); - - if ((arg = getarg(args, 'g')) != NULL) { - if (!*(p = arg->val)) /* Handle empty group list specially */ - cnf->default_group = ""; - else { - if ((grp = GETGRNAM(p)) == NULL) { - if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) - errx(EX_NOUSER, "group `%s' does not exist", p); - } - cnf->default_group = newstr(grp->gr_name); + if (strchr(dirs, '/') == NULL) { + asprintf(&tmp, "usr/%s", dirs); + if (tmp == NULL) + errx(EX_UNAVAILABLE, "out of memory"); + if (mkdirat(dfd, tmp, _DEF_DIRMODE) != -1 || errno == EEXIST) { + fchownat(dfd, tmp, 0, 0, 0); + symlinkat(tmp, dfd, dirs); } + free(tmp); } - if ((arg = getarg(args, 'L')) != NULL) - cnf->default_class = pw_checkname(arg->val, 0); - - if ((arg = getarg(args, 'G')) != NULL && arg->val) { - int i = 0; - - for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { - if ((grp = GETGRNAM(p)) == NULL) { - if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) - errx(EX_NOUSER, "group `%s' does not exist", p); + tmp = dirs; + if (fstatat(dfd, dirs, &st, 0) == -1) { + while ((tmp = strchr(tmp + 1, '/')) != NULL) { + *tmp = '\0'; + if (fstatat(dfd, dirs, &st, 0) == -1) { + if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) + err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); } - if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1) - cnf->groups[i++] = newstr(grp->gr_name); + *tmp = '/'; } - while (i < cnf->numgroups) - cnf->groups[i++] = NULL; } - - if ((arg = getarg(args, 'k')) != NULL) { - if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode)) - errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir); + if (fstatat(dfd, dirs, &st, 0) == -1) { + if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1) + err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs); + fchownat(dfd, dirs, 0, 0, 0); } - if ((arg = getarg(args, 's')) != NULL) - cnf->shell_default = arg->val; + free(dirs); +} - if ((arg = getarg(args, 'w')) != NULL) - cnf->default_password = boolean_val(arg->val, cnf->default_password); - if (mode == M_ADD && getarg(args, 'D')) { - if (name != NULL) - errx(EX_DATAERR, "can't combine `-D' with `-n name'"); - if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { - if ((cnf->min_uid = (uid_t) atoi(p)) == 0) - cnf->min_uid = 1000; - if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid) - cnf->max_uid = 32000; - } - if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { - if ((cnf->min_gid = (gid_t) atoi(p)) == 0) - cnf->min_gid = 1000; - if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid) - cnf->max_gid = 32000; - } +static void +create_and_populate_homedir(struct userconf *cnf, struct passwd *pwd, + const char *skeldir, mode_t homemode, bool update) +{ + int skelfd = -1; - if (write_userconfig(conf.config)) - return (EXIT_SUCCESS); - err(EX_IOERR, "config udpate"); - } + /* Create home parents directories */ + mkdir_home_parents(conf.rootfd, pwd->pw_dir); - if (mode == M_PRINT && getarg(args, 'a')) { - SETPWENT(); - while ((pwd = GETPWENT()) != NULL) - print_user(pwd); - ENDPWENT(); - return EXIT_SUCCESS; + if (skeldir != NULL && *skeldir != '\0') { + if (*skeldir == '/') + skeldir++; + skelfd = openat(conf.rootfd, skeldir, O_DIRECTORY|O_CLOEXEC); } - if (name != NULL) - pwd = GETPWNAM(pw_checkname(name, 0)); + copymkdir(conf.rootfd, pwd->pw_dir, skelfd, homemode, pwd->pw_uid, + pwd->pw_gid, 0); + pw_log(cnf, update ? M_UPDATE : M_ADD, W_USER, "%s(%ju) home %s made", + pwd->pw_name, (uintmax_t)pwd->pw_uid, pwd->pw_dir); +} - if (id < 0 && name == NULL) - errx(EX_DATAERR, "user name or id required"); +static int +pw_set_passwd(struct passwd *pwd, int fd, bool precrypted, bool update) +{ + int b, istty; + struct termios t, n; + login_cap_t *lc; + char line[_PASSWORD_LEN+1]; + char *p; - /* - * Update, delete & print require that the user exists - */ - if (mode == M_UPDATE || mode == M_DELETE || - mode == M_PRINT || mode == M_LOCK || mode == M_UNLOCK) { - - if (name == NULL && pwd == NULL) /* Try harder */ - pwd = GETPWUID(id); - - if (pwd == NULL) { - if (mode == M_PRINT && getarg(args, 'F')) { - fakeuser.pw_name = name ? name : "nouser"; - fakeuser.pw_uid = (uid_t) id; - return print_user(&fakeuser); - } - if (name == NULL) - errx(EX_NOUSER, "no such uid `%ld'", id); - errx(EX_NOUSER, "no such user `%s'", name); + if (fd == '-') { + if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { + pwd->pw_passwd = "*"; /* No access */ + return (1); } + return (0); + } - if (name == NULL) - name = pwd->pw_name; - - /* - * The M_LOCK and M_UNLOCK functions simply add or remove - * a "*LOCKED*" prefix from in front of the password to - * prevent it decoding correctly, and therefore prevents - * access. Of course, this only prevents access via - * password authentication (not ssh, kerberos or any - * other method that does not use the UNIX password) but - * that is a known limitation. - */ - - if (mode == M_LOCK) { - if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0) - errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); - asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd); - if (passtmp == NULL) /* disaster */ - errx(EX_UNAVAILABLE, "out of memory"); - pwd->pw_passwd = passtmp; - edited = 1; - } else if (mode == M_UNLOCK) { - if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0) - errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); - pwd->pw_passwd += sizeof(locked_str)-1; - edited = 1; - } else if (mode == M_DELETE) - return (delete_user(cnf, pwd, name, - getarg(args, 'r') != NULL, mode)); - else if (mode == M_PRINT) - return print_user(pwd); - - /* - * The rest is edit code - */ - if (conf.newname != NULL) { - if (strcmp(pwd->pw_name, "root") == 0) - errx(EX_DATAERR, "can't rename `root' account"); - pwd->pw_name = pw_checkname(conf.newname, 0); - edited = 1; + if ((istty = isatty(fd))) { + if (tcgetattr(fd, &t) == -1) + istty = 0; + else { + n = t; + n.c_lflag &= ~(ECHO); + tcsetattr(fd, TCSANOW, &n); + printf("%s%spassword for user %s:", + update ? "new " : "", + precrypted ? "encrypted " : "", + pwd->pw_name); + fflush(stdout); } + } + b = read(fd, line, sizeof(line) - 1); + if (istty) { /* Restore state */ + tcsetattr(fd, TCSANOW, &t); + fputc('\n', stdout); + fflush(stdout); + } - if (id > 0) { - pwd->pw_uid = (uid_t)id; - edited = 1; - if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) - errx(EX_DATAERR, "can't change uid of `root' account"); - if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) - warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name); - } - - if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) { /* Already checked this */ - gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid; - if (newgid != pwd->pw_gid) { - edited = 1; - pwd->pw_gid = newgid; - } - } - - if ((arg = getarg(args, 'p')) != NULL) { - if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { - if (pwd->pw_change != 0) { - pwd->pw_change = 0; - edited = 1; - } - } - else { - time_t now = time(NULL); - time_t expire = parse_date(now, arg->val); - - if (pwd->pw_change != expire) { - pwd->pw_change = expire; - edited = 1; - } - } - } - - if ((arg = getarg(args, 'e')) != NULL) { - if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { - if (pwd->pw_expire != 0) { - pwd->pw_expire = 0; - edited = 1; - } - } - else { - time_t now = time(NULL); - time_t expire = parse_date(now, arg->val); - - if (pwd->pw_expire != expire) { - pwd->pw_expire = expire; - edited = 1; - } - } - } - - if ((arg = getarg(args, 's')) != NULL) { - char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val); - if (shell == NULL) - shell = ""; - if (strcmp(shell, pwd->pw_shell) != 0) { - pwd->pw_shell = shell; - edited = 1; - } - } - - if (getarg(args, 'L')) { - if (cnf->default_class == NULL) - cnf->default_class = ""; - if (strcmp(pwd->pw_class, cnf->default_class) != 0) { - pwd->pw_class = cnf->default_class; - edited = 1; - } - } - - if ((arg = getarg(args, 'd')) != NULL) { - if (strcmp(pwd->pw_dir, arg->val)) - edited = 1; - if (stat(pwd->pw_dir = arg->val, &st) == -1) { - if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0) - warnx("WARNING: home `%s' does not exist", pwd->pw_dir); - } else if (!S_ISDIR(st.st_mode)) - warnx("WARNING: home `%s' is not a directory", pwd->pw_dir); - } - - if ((arg = getarg(args, 'w')) != NULL && - getarg(args, 'h') == NULL && getarg(args, 'H') == NULL) { - login_cap_t *lc; - - lc = login_getpwclass(pwd); - if (lc == NULL || - login_setcryptfmt(lc, "sha512", NULL) == NULL) - warn("setting crypt(3) format"); - login_close(lc); - pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); - edited = 1; - } - + if (b < 0) + err(EX_IOERR, "-%c file descriptor", + precrypted ? 'H' : 'h'); + line[b] = '\0'; + if ((p = strpbrk(line, "\r\n")) != NULL) + *p = '\0'; + if (!*line) + errx(EX_DATAERR, "empty password read on file descriptor %d", + fd); + if (precrypted) { + if (strchr(line, ':') != NULL) + errx(EX_DATAERR, "bad encrypted password"); + pwd->pw_passwd = strdup(line); } else { - login_cap_t *lc; - - /* - * Add code - */ - - if (name == NULL) /* Required */ - errx(EX_DATAERR, "login name required"); - else if ((pwd = GETPWNAM(name)) != NULL) /* Exists */ - errx(EX_DATAERR, "login name `%s' already exists", name); - - /* - * Now, set up defaults for a new user - */ - pwd = &fakeuser; - pwd->pw_name = name; - pwd->pw_class = cnf->default_class ? cnf->default_class : ""; - pwd->pw_uid = pw_uidpolicy(cnf, id); - pwd->pw_gid = pw_gidpolicy(args, pwd->pw_name, (gid_t) pwd->pw_uid); - pwd->pw_change = pw_pwdpolicy(cnf, args); - pwd->pw_expire = pw_exppolicy(cnf, args); - pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name); - pwd->pw_shell = pw_shellpolicy(cnf, args, NULL); lc = login_getpwclass(pwd); - if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) + if (lc == NULL || + login_setcryptfmt(lc, "sha512", NULL) == NULL) warn("setting crypt(3) format"); login_close(lc); - pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); - edited = 1; - - if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) - warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name); + pwd->pw_passwd = pw_pwcrypt(line); } + return (1); +} - /* - * Shared add/edit code - */ - if ((arg = getarg(args, 'c')) != NULL) { - char *gecos = pw_checkname(arg->val, 1); - if (strcmp(pwd->pw_gecos, gecos) != 0) { - pwd->pw_gecos = gecos; - edited = 1; - } - } +static void +perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd) +{ + int rc; + struct passwd *nispwd; - if ((arg = getarg(args, 'h')) != NULL || - (arg = getarg(args, 'H')) != NULL) { - if (strcmp(arg->val, "-") == 0) { - if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { - pwd->pw_passwd = "*"; /* No access */ - edited = 1; - } - } else { - int fd = atoi(arg->val); - int precrypt = (arg->ch == 'H'); - int b; - int istty = isatty(fd); - struct termios t; - login_cap_t *lc; + /* duplicate for nis so that chgpwent is not modifying before NIS */ + if (nispasswd && *nispasswd == '/') + nispwd = pw_dup(pwd); - if (istty) { - if (tcgetattr(fd, &t) == -1) - istty = 0; - else { - struct termios n = t; + rc = chgpwent(name, pwd); + if (rc == -1) + errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name); + else if (rc != 0) + err(EX_IOERR, "passwd file update"); - /* Disable echo */ - n.c_lflag &= ~(ECHO); - tcsetattr(fd, TCSANOW, &n); - printf("%s%spassword for user %s:", - (mode == M_UPDATE) ? "new " : "", - precrypt ? "encrypted " : "", - pwd->pw_name); - fflush(stdout); - } - } - b = read(fd, line, sizeof(line) - 1); - if (istty) { /* Restore state */ - tcsetattr(fd, TCSANOW, &t); - fputc('\n', stdout); - fflush(stdout); - } - if (b < 0) - err(EX_IOERR, "-%c file descriptor", - precrypt ? 'H' : 'h'); - line[b] = '\0'; - if ((p = strpbrk(line, "\r\n")) != NULL) - *p = '\0'; - if (!*line) - errx(EX_DATAERR, "empty password read on file descriptor %d", fd); - if (precrypt) { - if (strchr(line, ':') != NULL) - return EX_DATAERR; - pwd->pw_passwd = line; - } else { - lc = login_getpwclass(pwd); - if (lc == NULL || - login_setcryptfmt(lc, "sha512", NULL) == NULL) - warn("setting crypt(3) format"); - login_close(lc); - pwd->pw_passwd = pw_pwcrypt(line); - } - edited = 1; - } - } - - /* - * Special case: -N only displays & exits - */ - if (conf.dryrun) - return print_user(pwd); - - if (mode == M_ADD) { - edited = 1; /* Always */ - rc = addpwent(pwd); + if (nispasswd && *nispasswd == '/') { + rc = chgnispwent(nispasswd, name, nispwd); if (rc == -1) - errx(EX_IOERR, "user '%s' already exists", - pwd->pw_name); + warn("User '%s' not found in NIS passwd", pwd->pw_name); else if (rc != 0) - err(EX_IOERR, "passwd file update"); - if (cnf->nispasswd && *cnf->nispasswd=='/') { - rc = addnispwent(cnf->nispasswd, pwd); - if (rc == -1) - warnx("User '%s' already exists in NIS passwd", pwd->pw_name); - else - warn("NIS passwd update"); - /* NOTE: we treat NIS-only update errors as non-fatal */ - } - } else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) { - if (edited) { /* Only updated this if required */ - rc = chgpwent(name, pwd); - if (rc == -1) - errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name); - else if (rc != 0) - err(EX_IOERR, "passwd file update"); - if ( cnf->nispasswd && *cnf->nispasswd=='/') { - rc = chgnispwent(cnf->nispasswd, name, pwd); - if (rc == -1) - warn("User '%s' not found in NIS passwd", pwd->pw_name); - else - warn("NIS passwd update"); - /* NOTE: NIS-only update errors are not fatal */ - } - } + warn("NIS passwd update"); + /* NOTE: NIS-only update errors are not fatal */ } +} - /* - * Ok, user is created or changed - now edit group file - */ +/* + * The M_LOCK and M_UNLOCK functions simply add or remove + * a "*LOCKED*" prefix from in front of the password to + * prevent it decoding correctly, and therefore prevents + * access. Of course, this only prevents access via + * password authentication (not ssh, kerberos or any + * other method that does not use the UNIX password) but + * that is a known limitation. + */ +static int +pw_userlock(char *arg1, int mode) +{ + struct passwd *pwd = NULL; + char *passtmp = NULL; + char *name; + bool locked = false; + uid_t id; - if (mode == M_ADD || getarg(args, 'G') != NULL) { - int i, j; - /* First remove the user from all group */ - SETGRENT(); - while ((grp = GETGRENT()) != NULL) { - char group[MAXLOGNAME]; - if (grp->gr_mem == NULL) - continue; - for (i = 0; grp->gr_mem[i] != NULL; i++) { - if (strcmp(grp->gr_mem[i] , pwd->pw_name) != 0) - continue; - for (j = i; grp->gr_mem[j] != NULL ; j++) - grp->gr_mem[j] = grp->gr_mem[j+1]; - strlcpy(group, grp->gr_name, MAXLOGNAME); - chggrent(group, grp); - } - } - ENDGRENT(); + if (geteuid() != 0) + errx(EX_NOPERM, "you must be root"); - /* now add to group where needed */ - for (i = 0; cnf->groups[i] != NULL; i++) { - grp = GETGRNAM(cnf->groups[i]); - grp = gr_add(grp, pwd->pw_name); - /* - * grp can only be NULL in 2 cases: - * - the new member is already a member - * - a problem with memory occurs - * in both cases we want to skip now. - */ - if (grp == NULL) - continue; - chggrent(cnf->groups[i], grp); - free(grp); - } - } + if (arg1 == NULL) + errx(EX_DATAERR, "username or id required"); + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, UID_MAX); + else + name = arg1; - /* go get a current version of pwd */ - pwd = GETPWNAM(name); + pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); if (pwd == NULL) { - /* This will fail when we rename, so special case that */ - if (mode == M_UPDATE && conf.newname != NULL) { - name = conf.newname; /* update new name */ - pwd = GETPWNAM(name); /* refetch renamed rec */ - } + if (name == NULL) + errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id); + errx(EX_NOUSER, "no such user `%s'", name); } - if (pwd == NULL) /* can't go on without this */ - errx(EX_NOUSER, "user '%s' disappeared during update", name); - grp = GETGRGID(pwd->pw_gid); - pw_log(cnf, mode, W_USER, "%s(%u):%s(%u):%s:%s:%s", - pwd->pw_name, pwd->pw_uid, - grp ? grp->gr_name : "unknown", (grp ? grp->gr_gid : (uid_t)-1), - pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); + if (name == NULL) + name = pwd->pw_name; - /* - * If adding, let's touch and chown the user's mail file. This is not - * strictly necessary under BSD with a 0755 maildir but it also - * doesn't hurt anything to create the empty mailfile - */ - if (mode == M_ADD) { - if (PWALTDIR() != PWF_ALT) { - arg = getarg(args, 'R'); - snprintf(path, sizeof(path), "%s%s/%s", - arg ? arg->val : "", _PATH_MAILDIR, pwd->pw_name); - close(open(path, O_RDWR | O_CREAT, 0600)); /* Preserve contents & - * mtime */ - chown(path, pwd->pw_uid, pwd->pw_gid); - } - } + if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str) -1) == 0) + locked = true; + if (mode == M_LOCK && locked) + errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); + if (mode == M_UNLOCK && !locked) + errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); - /* - * Let's create and populate the user's home directory. Note - * that this also `works' for editing users if -m is used, but - * existing files will *not* be overwritten. - */ - if (PWALTDIR() != PWF_ALT && getarg(args, 'm') != NULL && pwd->pw_dir && - *pwd->pw_dir == '/' && pwd->pw_dir[1]) - create_and_populate_homedir(mode, pwd); - - /* - * Finally, send mail to the new user as well, if we are asked to - */ - if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) { - FILE *pfp = popen(_PATH_SENDMAIL " -t", "w"); - - if (pfp == NULL) - warn("sendmail"); - else { - fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name); - while (fgets(line, sizeof(line), fp) != NULL) { - /* Do substitutions? */ - fputs(line, pfp); - } - pclose(pfp); - pw_log(cnf, mode, W_USER, "%s(%u) new user mail sent", - pwd->pw_name, pwd->pw_uid); - } - fclose(fp); + if (mode == M_LOCK) { + asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd); + if (passtmp == NULL) /* disaster */ + errx(EX_UNAVAILABLE, "out of memory"); + pwd->pw_passwd = passtmp; + } else { + pwd->pw_passwd += sizeof(locked_str)-1; } - return EXIT_SUCCESS; + perform_chgpwent(name, pwd, NULL); + free(passtmp); + + return (EXIT_SUCCESS); } - -static uid_t -pw_uidpolicy(struct userconf * cnf, long id) +static uid_t +pw_uidpolicy(struct userconf * cnf, intmax_t id) { struct passwd *pwd; + struct bitmap bm; uid_t uid = (uid_t) - 1; /* * Check the given uid, if any */ if (id >= 0) { uid = (uid_t) id; if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate) - errx(EX_DATAERR, "uid `%u' has already been allocated", pwd->pw_uid); - } else { - struct bitmap bm; + errx(EX_DATAERR, "uid `%ju' has already been allocated", + (uintmax_t)pwd->pw_uid); + return (uid); + } + /* + * We need to allocate the next available uid under one of + * two policies a) Grab the first unused uid b) Grab the + * highest possible unused uid + */ + if (cnf->min_uid >= cnf->max_uid) { /* Sanity + * claus^H^H^H^Hheck */ + cnf->min_uid = 1000; + cnf->max_uid = 32000; + } + bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); - /* - * We need to allocate the next available uid under one of - * two policies a) Grab the first unused uid b) Grab the - * highest possible unused uid - */ - if (cnf->min_uid >= cnf->max_uid) { /* Sanity - * claus^H^H^H^Hheck */ - cnf->min_uid = 1000; - cnf->max_uid = 32000; - } - bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); + /* + * Now, let's fill the bitmap from the password file + */ + SETPWENT(); + while ((pwd = GETPWENT()) != NULL) + if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) + bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); + ENDPWENT(); - /* - * Now, let's fill the bitmap from the password file - */ - SETPWENT(); - while ((pwd = GETPWENT()) != NULL) - if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) - bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); - ENDPWENT(); + /* + * Then apply the policy, with fallback to reuse if necessary + */ + if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) + uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); - /* - * Then apply the policy, with fallback to reuse if necessary - */ - if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) - uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); - - /* - * Another sanity check - */ - if (uid < cnf->min_uid || uid > cnf->max_uid) - errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); - bm_dealloc(&bm); - } - return uid; + /* + * Another sanity check + */ + if (uid < cnf->min_uid || uid > cnf->max_uid) + errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); + bm_dealloc(&bm); + return (uid); } - -static uid_t -pw_gidpolicy(struct cargs * args, char *nam, gid_t prefer) +static uid_t +pw_gidpolicy(struct userconf *cnf, char *grname, char *nam, gid_t prefer, bool dryrun) { struct group *grp; gid_t gid = (uid_t) - 1; - struct carg *a_gid = getarg(args, 'g'); - struct userconf *cnf = conf.userconf; /* - * If no arg given, see if default can help out - */ - if (a_gid == NULL && cnf->default_group && *cnf->default_group) - a_gid = addarg(args, 'g', cnf->default_group); - - /* * Check the given gid, if any */ SETGRENT(); - if (a_gid != NULL) { - if ((grp = GETGRNAM(a_gid->val)) == NULL) { - gid = (gid_t) atol(a_gid->val); - if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL) - errx(EX_NOUSER, "group `%s' is not defined", a_gid->val); + if (grname) { + if ((grp = GETGRNAM(grname)) == NULL) { + gid = pw_checkid(grname, GID_MAX); + grp = GETGRGID(gid); } gid = grp->gr_gid; } else if ((grp = GETGRNAM(nam)) != NULL && (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) { gid = grp->gr_gid; /* Already created? Use it anyway... */ } else { - struct cargs grpargs; - gid_t grid = -1; + intmax_t grid = -1; - LIST_INIT(&grpargs); - /* * We need to auto-create a group with the user's name. We * can send all the appropriate output to our sister routine * bit first see if we can create a group with gid==uid so we * can keep the user and group ids in sync. We purposely do * NOT check the gid range if we can force the sync. If the * user's name dups an existing group, then the group add * function will happily handle that case for us and exit. */ if (GETGRGID(prefer) == NULL) grid = prefer; - if (conf.dryrun) { - addarg(&grpargs, 'q', NULL); - gid = pw_group(M_NEXT, nam, -1, &grpargs); - } - else - { - pw_group(M_ADD, nam, grid, &grpargs); + if (dryrun) { + gid = pw_groupnext(cnf, true); + } else { + if (grid == -1) + grid = pw_groupnext(cnf, true); + groupadd(cnf, nam, grid, NULL, -1, false, false, false); if ((grp = GETGRNAM(nam)) != NULL) gid = grp->gr_gid; } } ENDGRENT(); - return gid; + return (gid); } - -static time_t -pw_pwdpolicy(struct userconf * cnf, struct cargs * args) +static char * +pw_homepolicy(struct userconf * cnf, char *homedir, const char *user) { - time_t result = 0; - time_t now = time(NULL); - struct carg *arg = getarg(args, 'p'); - - if (arg != NULL) { - if ((result = parse_date(now, arg->val)) == now) - errx(EX_DATAERR, "invalid date/time `%s'", arg->val); - } else if (cnf->password_days > 0) - result = now + ((long) cnf->password_days * 86400L); - return result; -} - - -static time_t -pw_exppolicy(struct userconf * cnf, struct cargs * args) -{ - time_t result = 0; - time_t now = time(NULL); - struct carg *arg = getarg(args, 'e'); - - if (arg != NULL) { - if ((result = parse_date(now, arg->val)) == now) - errx(EX_DATAERR, "invalid date/time `%s'", arg->val); - } else if (cnf->expire_days > 0) - result = now + ((long) cnf->expire_days * 86400L); - return result; -} - - -static char * -pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user) -{ - struct carg *arg = getarg(args, 'd'); static char home[128]; - if (arg) - return (arg->val); + if (homedir) + return (homedir); if (cnf->home == NULL || *cnf->home == '\0') errx(EX_CONFIG, "no base home directory set"); snprintf(home, sizeof(home), "%s/%s", cnf->home, user); return (home); } -static char * +static char * shell_path(char const * path, char *shells[], char *sh) { if (sh != NULL && (*sh == '/' || *sh == '\0')) return sh; /* specified full path or forced none */ else { char *p; char paths[_UC_MAXLINE]; /* * We need to search paths */ strlcpy(paths, path, sizeof(paths)); for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) { int i; static char shellpath[256]; if (sh != NULL) { snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh); if (access(shellpath, X_OK) == 0) return shellpath; } else for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) { snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]); if (access(shellpath, X_OK) == 0) return shellpath; } } if (sh == NULL) errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh); errx(EX_CONFIG, "no default shell available or defined"); return NULL; } } - -static char * -pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell) +static char * +pw_shellpolicy(struct userconf * cnf) { - char *sh = newshell; - struct carg *arg = getarg(args, 's'); - if (newshell == NULL && arg != NULL) - sh = arg->val; - return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default); + return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default); } #define SALTSIZE 32 static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; -char * +char * pw_pwcrypt(char *password) { int i; char salt[SALTSIZE + 1]; char *cryptpw; - static char buf[256]; /* * Calculate a salt value */ for (i = 0; i < SALTSIZE; i++) salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; salt[SALTSIZE] = '\0'; cryptpw = crypt(password, salt); if (cryptpw == NULL) errx(EX_CONFIG, "crypt(3) failure"); return strcpy(buf, cryptpw); } - -static char * -pw_password(struct userconf * cnf, struct cargs * args, char const * user) +static char * +pw_password(struct userconf * cnf, char const * user, bool dryrun) { int i, l; char pwbuf[32]; switch (cnf->default_password) { case -1: /* Random password */ l = (arc4random() % 8 + 8); /* 8 - 16 chars */ for (i = 0; i < l; i++) pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; pwbuf[i] = '\0'; /* * We give this information back to the user */ - if (getarg(args, 'h') == NULL && getarg(args, 'H') == NULL && - !conf.dryrun) { + if (conf.fd == -1 && !dryrun) { if (isatty(STDOUT_FILENO)) printf("Password for '%s' is: ", user); printf("%s\n", pwbuf); fflush(stdout); } break; case -2: /* No password at all! */ return ""; case 0: /* No login - default */ default: return "*"; case 1: /* user's name */ strlcpy(pwbuf, user, sizeof(pwbuf)); break; } return pw_pwcrypt(pwbuf); } static int -delete_user(struct userconf *cnf, struct passwd *pwd, char *name, - int delete, int mode) +print_user(struct passwd * pwd, bool pretty, bool v7) { - char file[MAXPATHLEN]; - char home[MAXPATHLEN]; - uid_t uid = pwd->pw_uid; - struct group *gr, *grp; - char grname[LOGNAMESIZE]; - int rc; - struct stat st; + int j; + char *p; + struct group *grp = GETGRGID(pwd->pw_gid); + char uname[60] = "User &", office[60] = "[None]", + wphone[60] = "[None]", hphone[60] = "[None]"; + char acexpire[32] = "[None]", pwexpire[32] = "[None]"; + struct tm * tptr; - if (strcmp(pwd->pw_name, "root") == 0) - errx(EX_DATAERR, "cannot remove user 'root'"); - - if (!PWALTDIR()) { - /* - * Remove opie record from /etc/opiekeys - */ - - rmopie(pwd->pw_name); - - /* - * Remove crontabs - */ - snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); - if (access(file, F_OK) == 0) { - snprintf(file, sizeof(file), "crontab -u %s -r", pwd->pw_name); - system(file); - } + if (!pretty) { + p = v7 ? pw_make_v7(pwd) : pw_make(pwd); + printf("%s\n", p); + free(p); + return (EXIT_SUCCESS); } - /* - * Save these for later, since contents of pwd may be - * invalidated by deletion - */ - snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name); - strlcpy(home, pwd->pw_dir, sizeof(home)); - gr = GETGRGID(pwd->pw_gid); - if (gr != NULL) - strlcpy(grname, gr->gr_name, LOGNAMESIZE); - else - grname[0] = '\0'; - rc = delpwent(pwd); - if (rc == -1) - err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); - else if (rc != 0) - err(EX_IOERR, "passwd update"); - - if (cnf->nispasswd && *cnf->nispasswd=='/') { - rc = delnispwent(cnf->nispasswd, name); - if (rc == -1) - warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name); - else if (rc != 0) - warn("WARNING: NIS passwd update"); - /* non-fatal */ - } - - grp = GETGRNAM(name); - if (grp != NULL && - (grp->gr_mem == NULL || *grp->gr_mem == NULL) && - strcmp(name, grname) == 0) - delgrent(GETGRNAM(name)); - SETGRENT(); - while ((grp = GETGRENT()) != NULL) { - int i, j; - char group[MAXLOGNAME]; - if (grp->gr_mem == NULL) - continue; - - for (i = 0; grp->gr_mem[i] != NULL; i++) { - if (strcmp(grp->gr_mem[i], name) != 0) - continue; - - for (j = i; grp->gr_mem[j] != NULL; j++) - grp->gr_mem[j] = grp->gr_mem[j+1]; - strlcpy(group, grp->gr_name, MAXLOGNAME); - chggrent(group, grp); - } - } - ENDGRENT(); - - pw_log(cnf, mode, W_USER, "%s(%u) account removed", name, uid); - - if (!PWALTDIR()) { - /* - * Remove mail file - */ - remove(file); - - /* - * Remove at jobs - */ - if (getpwuid(uid) == NULL) - rmat(uid); - - /* - * Remove home directory and contents - */ - if (delete && *home == '/' && getpwuid(uid) == NULL && - stat(home, &st) != -1) { - rm_r(home, uid); - pw_log(cnf, mode, W_USER, "%s(%u) home '%s' %sremoved", - name, uid, home, - stat(home, &st) == -1 ? "" : "not completely "); - } - } - - return (EXIT_SUCCESS); -} - -static int -print_user(struct passwd * pwd) -{ - if (!conf.pretty) { - char *buf; - - buf = conf.v7 ? pw_make_v7(pwd) : pw_make(pwd); - printf("%s\n", buf); - free(buf); - } else { - int j; - char *p; - struct group *grp = GETGRGID(pwd->pw_gid); - char uname[60] = "User &", office[60] = "[None]", - wphone[60] = "[None]", hphone[60] = "[None]"; - char acexpire[32] = "[None]", pwexpire[32] = "[None]"; - struct tm * tptr; - - if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { - strlcpy(uname, p, sizeof(uname)); + if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { + strlcpy(uname, p, sizeof(uname)); + if ((p = strtok(NULL, ",")) != NULL) { + strlcpy(office, p, sizeof(office)); if ((p = strtok(NULL, ",")) != NULL) { - strlcpy(office, p, sizeof(office)); - if ((p = strtok(NULL, ",")) != NULL) { - strlcpy(wphone, p, sizeof(wphone)); - if ((p = strtok(NULL, "")) != NULL) { - strlcpy(hphone, p, - sizeof(hphone)); - } + strlcpy(wphone, p, sizeof(wphone)); + if ((p = strtok(NULL, "")) != NULL) { + strlcpy(hphone, p, sizeof(hphone)); } } } - /* - * Handle '&' in gecos field - */ - if ((p = strchr(uname, '&')) != NULL) { - int l = strlen(pwd->pw_name); - int m = strlen(p); + } + /* + * Handle '&' in gecos field + */ + if ((p = strchr(uname, '&')) != NULL) { + int l = strlen(pwd->pw_name); + int m = strlen(p); - memmove(p + l, p + 1, m); - memmove(p, pwd->pw_name, l); - *p = (char) toupper((unsigned char)*p); - } - if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) - strftime(acexpire, sizeof acexpire, "%c", tptr); + memmove(p + l, p + 1, m); + memmove(p, pwd->pw_name, l); + *p = (char) toupper((unsigned char)*p); + } + if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) + strftime(acexpire, sizeof acexpire, "%c", tptr); if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) - strftime(pwexpire, sizeof pwexpire, "%c", tptr); - printf("Login Name: %-15s #%-12u Group: %-15s #%u\n" - " Full Name: %s\n" - " Home: %-26.26s Class: %s\n" - " Shell: %-26.26s Office: %s\n" - "Work Phone: %-26.26s Home Phone: %s\n" - "Acc Expire: %-26.26s Pwd Expire: %s\n", - pwd->pw_name, pwd->pw_uid, - grp ? grp->gr_name : "(invalid)", pwd->pw_gid, - uname, pwd->pw_dir, pwd->pw_class, - pwd->pw_shell, office, wphone, hphone, - acexpire, pwexpire); - SETGRENT(); - j = 0; - while ((grp=GETGRENT()) != NULL) - { - int i = 0; - if (grp->gr_mem != NULL) { - while (grp->gr_mem[i] != NULL) - { - if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) - { - printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); - break; - } - ++i; + strftime(pwexpire, sizeof pwexpire, "%c", tptr); + printf("Login Name: %-15s #%-12ju Group: %-15s #%ju\n" + " Full Name: %s\n" + " Home: %-26.26s Class: %s\n" + " Shell: %-26.26s Office: %s\n" + "Work Phone: %-26.26s Home Phone: %s\n" + "Acc Expire: %-26.26s Pwd Expire: %s\n", + pwd->pw_name, (uintmax_t)pwd->pw_uid, + grp ? grp->gr_name : "(invalid)", (uintmax_t)pwd->pw_gid, + uname, pwd->pw_dir, pwd->pw_class, + pwd->pw_shell, office, wphone, hphone, + acexpire, pwexpire); + SETGRENT(); + j = 0; + while ((grp=GETGRENT()) != NULL) { + int i = 0; + if (grp->gr_mem != NULL) { + while (grp->gr_mem[i] != NULL) { + if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) { + printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); + break; } + ++i; } } - ENDGRENT(); - printf("%s", j ? "\n" : ""); } - return EXIT_SUCCESS; + ENDGRENT(); + printf("%s", j ? "\n" : ""); + return (EXIT_SUCCESS); } char * pw_checkname(char *name, int gecos) { char showch[8]; const char *badchars, *ch, *showtype; int reject; ch = name; reject = 0; if (gecos) { /* See if the name is valid as a gecos (comment) field. */ badchars = ":!@"; showtype = "gecos field"; } else { /* See if the name is valid as a userid or group. */ badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\""; showtype = "userid/group name"; /* Userids and groups can not have a leading '-'. */ if (*ch == '-') reject = 1; } if (!reject) { while (*ch) { if (strchr(badchars, *ch) != NULL || *ch < ' ' || *ch == 127) { reject = 1; break; } /* 8-bit characters are only allowed in GECOS fields */ if (!gecos && (*ch & 0x80)) { reject = 1; break; } ch++; } } /* * A `$' is allowed as the final character for userids and groups, * mainly for the benefit of samba. */ if (reject && !gecos) { if (*ch == '$' && *(ch + 1) == '\0') { reject = 0; ch++; } } if (reject) { snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127) ? "`%c'" : "0x%02x", *ch); errx(EX_DATAERR, "invalid character %s at position %td in %s", showch, (ch - name), showtype); } if (!gecos && (ch - name) > LOGNAMESIZE) - errx(EX_DATAERR, "name too long `%s' (max is %d)", name, + errx(EX_USAGE, "name too long `%s' (max is %d)", name, LOGNAMESIZE); return (name); } - static void rmat(uid_t uid) { DIR *d = opendir("/var/at/jobs"); if (d != NULL) { struct dirent *e; while ((e = readdir(d)) != NULL) { struct stat st; if (strncmp(e->d_name, ".lock", 5) != 0 && stat(e->d_name, &st) == 0 && !S_ISDIR(st.st_mode) && st.st_uid == uid) { char tmp[MAXPATHLEN]; - snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", e->d_name); + snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", + e->d_name); system(tmp); } } closedir(d); } } static void rmopie(char const * name) { - static const char etcopie[] = "/etc/opiekeys"; - FILE *fp = fopen(etcopie, "r+"); + char tmp[1014]; + FILE *fp; + int fd; + size_t len; + off_t atofs = 0; + + if ((fd = openat(conf.rootfd, "etc/opiekeys", O_RDWR)) == -1) + return; - if (fp != NULL) { - char tmp[1024]; - off_t atofs = 0; - int length = strlen(name); + fp = fdopen(fd, "r+"); + len = strlen(name); - while (fgets(tmp, sizeof tmp, fp) != NULL) { - if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') { - if (fseek(fp, atofs, SEEK_SET) == 0) { - fwrite("#", 1, 1, fp); /* Comment username out */ - } - break; + while (fgets(tmp, sizeof(tmp), fp) != NULL) { + if (strncmp(name, tmp, len) == 0 && tmp[len]==' ') { + /* Comment username out */ + if (fseek(fp, atofs, SEEK_SET) == 0) + fwrite("#", 1, 1, fp); + break; + } + atofs = ftell(fp); + } + /* + * If we got an error of any sort, don't update! + */ + fclose(fp); +} + +int +pw_user_next(int argc, char **argv, char *name __unused) +{ + struct userconf *cnf = NULL; + const char *cfg = NULL; + int ch; + bool quiet = false; + uid_t next; + + while ((ch = getopt(argc, argv, "Cq")) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + cnf = get_userconfig(cfg); + + next = pw_uidpolicy(cnf, -1); + + printf("%ju:", (uintmax_t)next); + pw_groupnext(cnf, quiet); + + return (EXIT_SUCCESS); +} + +int +pw_user_show(int argc, char **argv, char *arg1) +{ + struct passwd *pwd = NULL; + char *name = NULL; + intmax_t id = -1; + int ch; + bool all = false; + bool pretty = false; + bool force = false; + bool v7 = false; + bool quiet = false; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, UID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, "C:qn:u:FPa7")) != -1) { + switch (ch) { + case 'C': + /* ignore compatibility */ + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'u': + id = pw_checkid(optarg, UID_MAX); + break; + case 'F': + force = true; + break; + case 'P': + pretty = true; + break; + case 'a': + all = true; + break; + case 7: + v7 = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + if (all) { + SETPWENT(); + while ((pwd = GETPWENT()) != NULL) + print_user(pwd, pretty, v7); + ENDPWENT(); + return (EXIT_SUCCESS); + } + + if (id < 0 && name == NULL) + errx(EX_DATAERR, "username or id required"); + + pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); + if (pwd == NULL) { + if (force) { + pwd = &fakeuser; + } else { + if (name == NULL) + errx(EX_NOUSER, "no such uid `%ju'", + (uintmax_t) id); + errx(EX_NOUSER, "no such user `%s'", name); + } + } + + return (print_user(pwd, pretty, v7)); +} + +int +pw_user_del(int argc, char **argv, char *arg1) +{ + struct userconf *cnf = NULL; + struct passwd *pwd = NULL; + struct group *gr, *grp; + char *name = NULL; + char grname[MAXLOGNAME]; + char *nispasswd = NULL; + char file[MAXPATHLEN]; + char home[MAXPATHLEN]; + const char *cfg = NULL; + struct stat st; + intmax_t id = -1; + int ch, rc; + bool nis = false; + bool deletehome = false; + bool quiet = false; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, UID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, "C:qn:u:rYy:")) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'u': + id = pw_checkid(optarg, UID_MAX); + break; + case 'r': + deletehome = true; + break; + case 'y': + nispasswd = optarg; + break; + case 'Y': + nis = true; + break; + } + } + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + if (id < 0 && name == NULL) + errx(EX_DATAERR, "username or id required"); + + cnf = get_userconfig(cfg); + + if (nispasswd == NULL) + nispasswd = cnf->nispasswd; + + pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); + if (pwd == NULL) { + if (name == NULL) + errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id); + errx(EX_NOUSER, "no such user `%s'", name); + } + + if (PWF._altdir == PWF_REGULAR && + ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { + if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { + if (!nis && nispasswd && *nispasswd != '/') + errx(EX_NOUSER, "Cannot remove NIS user `%s'", + name); + } else { + errx(EX_NOUSER, "Cannot remove non local user `%s'", + name); + } + } + + id = pwd->pw_uid; + if (name == NULL) + name = pwd->pw_name; + + if (strcmp(pwd->pw_name, "root") == 0) + errx(EX_DATAERR, "cannot remove user 'root'"); + + /* Remove opie record from /etc/opiekeys */ + if (PWALTDIR() != PWF_ALT) + rmopie(pwd->pw_name); + + if (!PWALTDIR()) { + /* Remove crontabs */ + snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); + if (access(file, F_OK) == 0) { + snprintf(file, sizeof(file), "crontab -u %s -r", + pwd->pw_name); + system(file); + } + } + + /* + * Save these for later, since contents of pwd may be + * invalidated by deletion + */ + snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name); + strlcpy(home, pwd->pw_dir, sizeof(home)); + gr = GETGRGID(pwd->pw_gid); + if (gr != NULL) + strlcpy(grname, gr->gr_name, LOGNAMESIZE); + else + grname[0] = '\0'; + + rc = delpwent(pwd); + if (rc == -1) + err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); + else if (rc != 0) + err(EX_IOERR, "passwd update"); + + if (nis && nispasswd && *nispasswd=='/') { + rc = delnispwent(nispasswd, name); + if (rc == -1) + warnx("WARNING: user '%s' does not exist in NIS passwd", + pwd->pw_name); + else if (rc != 0) + warn("WARNING: NIS passwd update"); + } + + grp = GETGRNAM(name); + if (grp != NULL && + (grp->gr_mem == NULL || *grp->gr_mem == NULL) && + strcmp(name, grname) == 0) + delgrent(GETGRNAM(name)); + SETGRENT(); + while ((grp = GETGRENT()) != NULL) { + int i, j; + char group[MAXLOGNAME]; + if (grp->gr_mem == NULL) + continue; + + for (i = 0; grp->gr_mem[i] != NULL; i++) { + if (strcmp(grp->gr_mem[i], name) != 0) + continue; + + for (j = i; grp->gr_mem[j] != NULL; j++) + grp->gr_mem[j] = grp->gr_mem[j+1]; + strlcpy(group, grp->gr_name, MAXLOGNAME); + chggrent(group, grp); + } + } + ENDGRENT(); + + pw_log(cnf, M_DELETE, W_USER, "%s(%ju) account removed", name, + (uintmax_t)id); + + /* Remove mail file */ + if (PWALTDIR() != PWF_ALT) + unlinkat(conf.rootfd, file + 1, 0); + + /* Remove at jobs */ + if (!PWALTDIR() && getpwuid(id) == NULL) + rmat(id); + + /* Remove home directory and contents */ + if (PWALTDIR() != PWF_ALT && deletehome && *home == '/' && + GETPWUID(id) == NULL && + fstatat(conf.rootfd, home + 1, &st, 0) != -1) { + rm_r(conf.rootfd, home, id); + pw_log(cnf, M_DELETE, W_USER, "%s(%ju) home '%s' %s" + "removed", name, (uintmax_t)id, home, + fstatat(conf.rootfd, home + 1, &st, 0) == -1 ? "" : "not " + "completely "); + } + + return (EXIT_SUCCESS); +} + +int +pw_user_lock(int argc, char **argv, char *arg1) +{ + int ch; + + while ((ch = getopt(argc, argv, "Cq")) != -1) { + switch (ch) { + case 'C': + case 'q': + /* compatibility */ + break; + } + } + + return (pw_userlock(arg1, M_LOCK)); +} + +int +pw_user_unlock(int argc, char **argv, char *arg1) +{ + int ch; + + while ((ch = getopt(argc, argv, "Cq")) != -1) { + switch (ch) { + case 'C': + case 'q': + /* compatibility */ + break; + } + } + + return (pw_userlock(arg1, M_UNLOCK)); +} + +static struct group * +group_from_name_or_id(char *name) +{ + const char *errstr = NULL; + struct group *grp; + uintmax_t id; + + if ((grp = GETGRNAM(name)) == NULL) { + id = strtounum(name, 0, GID_MAX, &errstr); + if (errstr) + errx(EX_NOUSER, "group `%s' does not exist", name); + grp = GETGRGID(id); + if (grp == NULL) + errx(EX_NOUSER, "group `%s' does not exist", name); + } + + return (grp); +} + +static void +split_groups(StringList **groups, char *groupsstr) +{ + struct group *grp; + char *p; + char tok[] = ", \t"; + + for (p = strtok(groupsstr, tok); p != NULL; p = strtok(NULL, tok)) { + grp = group_from_name_or_id(p); + if (*groups == NULL) + *groups = sl_init(); + sl_add(*groups, newstr(grp->gr_name)); + } +} + +static void +validate_grname(struct userconf *cnf, char *group) +{ + struct group *grp; + + if (group == NULL || *group == '\0') { + cnf->default_group = ""; + return; + } + grp = group_from_name_or_id(group); + cnf->default_group = newstr(grp->gr_name); +} + +static mode_t +validate_mode(char *mode) +{ + mode_t m; + void *set; + + if ((set = setmode(mode)) == NULL) + errx(EX_DATAERR, "invalid directory creation mode '%s'", mode); + + m = getmode(set, _DEF_DIRMODE); + free(set); + return (m); +} + +static void +mix_config(struct userconf *cmdcnf, struct userconf *cfg) +{ + + if (cmdcnf->default_password == 0) + cmdcnf->default_password = cfg->default_password; + if (cmdcnf->reuse_uids == 0) + cmdcnf->reuse_uids = cfg->reuse_uids; + if (cmdcnf->reuse_gids == 0) + cmdcnf->reuse_gids = cfg->reuse_gids; + if (cmdcnf->nispasswd == NULL) + cmdcnf->nispasswd = cfg->nispasswd; + if (cmdcnf->dotdir == NULL) + cmdcnf->dotdir = cfg->dotdir; + if (cmdcnf->newmail == NULL) + cmdcnf->newmail = cfg->newmail; + if (cmdcnf->logfile == NULL) + cmdcnf->logfile = cfg->logfile; + if (cmdcnf->home == NULL) + cmdcnf->home = cfg->home; + if (cmdcnf->homemode == 0) + cmdcnf->homemode = cfg->homemode; + if (cmdcnf->shelldir == NULL) + cmdcnf->shelldir = cfg->shelldir; + if (cmdcnf->shells == NULL) + cmdcnf->shells = cfg->shells; + if (cmdcnf->shell_default == NULL) + cmdcnf->shell_default = cfg->shell_default; + if (cmdcnf->default_group == NULL) + cmdcnf->default_group = cfg->default_group; + if (cmdcnf->groups == NULL) + cmdcnf->groups = cfg->groups; + if (cmdcnf->default_class == NULL) + cmdcnf->default_class = cfg->default_class; + if (cmdcnf->min_uid == 0) + cmdcnf->min_uid = cfg->min_uid; + if (cmdcnf->max_uid == 0) + cmdcnf->max_uid = cfg->max_uid; + if (cmdcnf->min_gid == 0) + cmdcnf->min_gid = cfg->min_gid; + if (cmdcnf->max_gid == 0) + cmdcnf->max_gid = cfg->max_gid; + if (cmdcnf->expire_days == 0) + cmdcnf->expire_days = cfg->expire_days; + if (cmdcnf->password_days == 0) + cmdcnf->password_days = cfg->password_days; +} + +int +pw_user_add(int argc, char **argv, char *arg1) +{ + struct userconf *cnf, *cmdcnf; + struct passwd *pwd; + struct group *grp; + struct stat st; + char args[] = "C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y"; + char line[_PASSWORD_LEN+1], path[MAXPATHLEN]; + char *gecos, *homedir, *skel, *walk, *userid, *groupid, *grname; + char *default_passwd, *name, *p; + const char *cfg; + login_cap_t *lc; + FILE *pfp, *fp; + intmax_t id = -1; + time_t now; + int rc, ch, fd = -1; + size_t i; + bool dryrun, nis, pretty, quiet, createhome, precrypted, genconf; + + dryrun = nis = pretty = quiet = createhome = precrypted = false; + genconf = false; + gecos = homedir = skel = userid = groupid = default_passwd = NULL; + grname = name = NULL; + + if ((cmdcnf = calloc(1, sizeof(struct userconf))) == NULL) + err(EXIT_FAILURE, "calloc()"); + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, UID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, args)) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'u': + userid = optarg; + break; + case 'c': + gecos = pw_checkname(optarg, 1); + break; + case 'd': + homedir = optarg; + break; + case 'e': + now = time(NULL); + cmdcnf->expire_days = parse_date(now, optarg); + break; + case 'p': + now = time(NULL); + cmdcnf->password_days = parse_date(now, optarg); + break; + case 'g': + validate_grname(cmdcnf, optarg); + grname = optarg; + break; + case 'G': + split_groups(&cmdcnf->groups, optarg); + break; + case 'm': + createhome = true; + break; + case 'M': + cmdcnf->homemode = validate_mode(optarg); + break; + case 'k': + walk = skel = optarg; + if (*walk == '/') + walk++; + if (fstatat(conf.rootfd, walk, &st, 0) == -1) + errx(EX_OSFILE, "skeleton `%s' does not " + "exists", skel); + if (!S_ISDIR(st.st_mode)) + errx(EX_OSFILE, "skeleton `%s' is not a " + "directory", skel); + cmdcnf->dotdir = skel; + break; + case 's': + cmdcnf->shell_default = optarg; + break; + case 'o': + conf.checkduplicate = false; + break; + case 'L': + cmdcnf->default_class = pw_checkname(optarg, 0); + break; + case 'i': + groupid = optarg; + break; + case 'w': + default_passwd = optarg; + break; + case 'H': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + precrypted = true; + if (fd == '-') + errx(EX_USAGE, "-H expects a file descriptor"); + break; + case 'h': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + break; + case 'D': + genconf = true; + break; + case 'b': + cmdcnf->home = optarg; + break; + case 'N': + dryrun = true; + break; + case 'P': + pretty = true; + break; + case 'y': + cmdcnf->nispasswd = optarg; + break; + case 'Y': + nis = true; + break; + } + } + + if (geteuid() != 0 && ! dryrun) + errx(EX_NOPERM, "you must be root"); + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + cnf = get_userconfig(cfg); + + mix_config(cmdcnf, cnf); + if (default_passwd) + cmdcnf->default_password = boolean_val(default_passwd, + cnf->default_password); + if (genconf) { + if (name != NULL) + errx(EX_DATAERR, "can't combine `-D' with `-n name'"); + if (userid != NULL) { + if ((p = strtok(userid, ", \t")) != NULL) + cmdcnf->min_uid = pw_checkid(p, UID_MAX); + if (cmdcnf->min_uid == 0) + cmdcnf->min_uid = 1000; + if ((p = strtok(NULL, " ,\t")) != NULL) + cmdcnf->max_uid = pw_checkid(p, UID_MAX); + if (cmdcnf->max_uid == 0) + cmdcnf->max_uid = 32000; + } + if (groupid != NULL) { + if ((p = strtok(groupid, ", \t")) != NULL) + cmdcnf->min_gid = pw_checkid(p, GID_MAX); + if (cmdcnf->min_gid == 0) + cmdcnf->min_gid = 1000; + if ((p = strtok(NULL, " ,\t")) != NULL) + cmdcnf->max_gid = pw_checkid(p, GID_MAX); + if (cmdcnf->max_gid == 0) + cmdcnf->max_gid = 32000; + } + if (write_userconfig(cmdcnf, cfg)) + return (EXIT_SUCCESS); + err(EX_IOERR, "config update"); + } + + if (userid) + id = pw_checkid(userid, UID_MAX); + if (id < 0 && name == NULL) + errx(EX_DATAERR, "user name or id required"); + + if (name == NULL) + errx(EX_DATAERR, "login name required"); + + if (GETPWNAM(name) != NULL) + errx(EX_DATAERR, "login name `%s' already exists", name); + + pwd = &fakeuser; + pwd->pw_name = name; + pwd->pw_class = cmdcnf->default_class ? cmdcnf->default_class : ""; + pwd->pw_uid = pw_uidpolicy(cmdcnf, id); + pwd->pw_gid = pw_gidpolicy(cnf, grname, pwd->pw_name, + (gid_t) pwd->pw_uid, dryrun); + pwd->pw_change = cmdcnf->password_days; + pwd->pw_expire = cmdcnf->expire_days; + pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name); + pwd->pw_shell = pw_shellpolicy(cmdcnf); + lc = login_getpwclass(pwd); + if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) + warn("setting crypt(3) format"); + login_close(lc); + pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, dryrun); + if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) + warnx("WARNING: new account `%s' has a uid of 0 " + "(superuser access!)", pwd->pw_name); + if (gecos) + pwd->pw_gecos = gecos; + + if (fd != -1) + pw_set_passwd(pwd, fd, precrypted, false); + + if (dryrun) + return (print_user(pwd, pretty, false)); + + if ((rc = addpwent(pwd)) != 0) { + if (rc == -1) + errx(EX_IOERR, "user '%s' already exists", + pwd->pw_name); + else if (rc != 0) + err(EX_IOERR, "passwd file update"); + } + if (nis && cmdcnf->nispasswd && *cmdcnf->nispasswd == '/') { + printf("%s\n", cmdcnf->nispasswd); + rc = addnispwent(cmdcnf->nispasswd, pwd); + if (rc == -1) + warnx("User '%s' already exists in NIS passwd", + pwd->pw_name); + else if (rc != 0) + warn("NIS passwd update"); + /* NOTE: we treat NIS-only update errors as non-fatal */ + } + + if (cmdcnf->groups != NULL) { + for (i = 0; i < cmdcnf->groups->sl_cur; i++) { + grp = GETGRNAM(cmdcnf->groups->sl_str[i]); + grp = gr_add(grp, pwd->pw_name); + /* + * grp can only be NULL in 2 cases: + * - the new member is already a member + * - a problem with memory occurs + * in both cases we want to skip now. + */ + if (grp == NULL) + continue; + chggrent(grp->gr_name, grp); + free(grp); + } + } + + pwd = GETPWNAM(name); + if (pwd == NULL) + errx(EX_NOUSER, "user '%s' disappeared during update", name); + + grp = GETGRGID(pwd->pw_gid); + pw_log(cnf, M_ADD, W_USER, "%s(%ju):%s(%ju):%s:%s:%s", + pwd->pw_name, (uintmax_t)pwd->pw_uid, + grp ? grp->gr_name : "unknown", + (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1), + pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); + + /* + * let's touch and chown the user's mail file. This is not + * strictly necessary under BSD with a 0755 maildir but it also + * doesn't hurt anything to create the empty mailfile + */ + if (PWALTDIR() != PWF_ALT) { + snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, + pwd->pw_name); + /* Preserve contents & mtime */ + close(openat(conf.rootfd, path +1, O_RDWR | O_CREAT, 0600)); + fchownat(conf.rootfd, path + 1, pwd->pw_uid, pwd->pw_gid, + AT_SYMLINK_NOFOLLOW); + } + + /* + * Let's create and populate the user's home directory. Note + * that this also `works' for editing users if -m is used, but + * existing files will *not* be overwritten. + */ + if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir && + *pwd->pw_dir == '/' && pwd->pw_dir[1]) + create_and_populate_homedir(cmdcnf, pwd, cmdcnf->dotdir, + cmdcnf->homemode, false); + + if (!PWALTDIR() && cmdcnf->newmail && *cmdcnf->newmail && + (fp = fopen(cnf->newmail, "r")) != NULL) { + if ((pfp = popen(_PATH_SENDMAIL " -t", "w")) == NULL) + warn("sendmail"); + else { + fprintf(pfp, "From: root\n" "To: %s\n" + "Subject: Welcome!\n\n", pwd->pw_name); + while (fgets(line, sizeof(line), fp) != NULL) { + /* Do substitutions? */ + fputs(line, pfp); } - atofs = ftell(fp); + pclose(pfp); + pw_log(cnf, M_ADD, W_USER, "%s(%ju) new user mail sent", + pwd->pw_name, (uintmax_t)pwd->pw_uid); } - /* - * If we got an error of any sort, don't update! - */ fclose(fp); } + + if (nis && nis_update() == 0) + pw_log(cnf, M_ADD, W_USER, "NIS maps updated"); + + return (EXIT_SUCCESS); } +int +pw_user_mod(int argc, char **argv, char *arg1) +{ + struct userconf *cnf; + struct passwd *pwd; + struct group *grp; + StringList *groups = NULL; + char args[] = "C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:NPYy:"; + const char *cfg; + char *gecos, *homedir, *grname, *name, *newname, *walk, *skel, *shell; + char *passwd, *class, *nispasswd; + login_cap_t *lc; + struct stat st; + intmax_t id = -1; + int ch, fd = -1; + size_t i, j; + bool quiet, createhome, pretty, dryrun, nis, edited, docreatehome; + bool precrypted; + mode_t homemode = 0; + time_t expire_days, password_days, now; + + expire_days = password_days = -1; + gecos = homedir = grname = name = newname = skel = shell =NULL; + passwd = NULL; + class = nispasswd = NULL; + quiet = createhome = pretty = dryrun = nis = precrypted = false; + edited = docreatehome = false; + + if (arg1 != NULL) { + if (arg1[strspn(arg1, "0123456789")] == '\0') + id = pw_checkid(arg1, UID_MAX); + else + name = arg1; + } + + while ((ch = getopt(argc, argv, args)) != -1) { + switch (ch) { + case 'C': + cfg = optarg; + break; + case 'q': + quiet = true; + break; + case 'n': + name = optarg; + break; + case 'u': + id = pw_checkid(optarg, UID_MAX); + break; + case 'c': + gecos = pw_checkname(optarg, 1); + break; + case 'd': + homedir = optarg; + break; + case 'e': + now = time(NULL); + expire_days = parse_date(now, optarg); + break; + case 'p': + now = time(NULL); + password_days = parse_date(now, optarg); + break; + case 'g': + group_from_name_or_id(optarg); + grname = optarg; + break; + case 'G': + split_groups(&groups, optarg); + break; + case 'm': + createhome = true; + break; + case 'M': + homemode = validate_mode(optarg); + break; + case 'l': + newname = optarg; + break; + case 'k': + walk = skel = optarg; + if (*walk == '/') + walk++; + if (fstatat(conf.rootfd, walk, &st, 0) == -1) + errx(EX_OSFILE, "skeleton `%s' does not " + "exists", skel); + if (!S_ISDIR(st.st_mode)) + errx(EX_OSFILE, "skeleton `%s' is not a " + "directory", skel); + break; + case 's': + shell = optarg; + break; + case 'w': + passwd = optarg; + break; + case 'L': + class = pw_checkname(optarg, 0); + break; + case 'H': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + precrypted = true; + if (fd == '-') + errx(EX_USAGE, "-H expects a file descriptor"); + break; + case 'h': + if (fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + fd = pw_checkfd(optarg); + break; + case 'N': + dryrun = true; + break; + case 'P': + pretty = true; + break; + case 'y': + nispasswd = optarg; + break; + case 'Y': + nis = true; + break; + } + } + + if (geteuid() != 0 && ! dryrun) + errx(EX_NOPERM, "you must be root"); + + if (quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + cnf = get_userconfig(cfg); + + if (id < 0 && name == NULL) + errx(EX_DATAERR, "username or id required"); + + pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id); + if (pwd == NULL) { + if (name == NULL) + errx(EX_NOUSER, "no such uid `%ju'", + (uintmax_t) id); + errx(EX_NOUSER, "no such user `%s'", name); + } + + if (name == NULL) + name = pwd->pw_name; + + if (nis && nispasswd == NULL) + nispasswd = cnf->nispasswd; + + if (PWF._altdir == PWF_REGULAR && + ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { + if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { + if (!nis && nispasswd && *nispasswd != '/') + errx(EX_NOUSER, "Cannot modify NIS user `%s'", + name); + } else { + errx(EX_NOUSER, "Cannot modify non local user `%s'", + name); + } + } + + if (newname) { + if (strcmp(pwd->pw_name, "root") == 0) + errx(EX_DATAERR, "can't rename `root' account"); + if (strcmp(pwd->pw_name, newname) != 0) { + pwd->pw_name = pw_checkname(newname, 0); + edited = true; + } + } + + if (id > 0 && pwd->pw_uid != id) { + pwd->pw_uid = id; + edited = true; + if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) + errx(EX_DATAERR, "can't change uid of `root' account"); + if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) + warnx("WARNING: account `%s' will have a uid of 0 " + "(superuser access!)", pwd->pw_name); + } + + if (grname && pwd->pw_uid != 0) { + grp = GETGRNAM(grname); + if (grp == NULL) + grp = GETGRGID(pw_checkid(grname, GID_MAX)); + if (grp->gr_gid != pwd->pw_gid) { + pwd->pw_gid = grp->gr_gid; + edited = true; + } + } + + if (password_days >= 0 && pwd->pw_change != password_days) { + pwd->pw_change = password_days; + edited = true; + } + + if (expire_days >= 0 && pwd->pw_expire != expire_days) { + pwd->pw_expire = expire_days; + edited = true; + } + + if (shell) { + shell = shell_path(cnf->shelldir, cnf->shells, shell); + if (shell == NULL) + shell = ""; + if (strcmp(shell, pwd->pw_shell) != 0) { + pwd->pw_shell = shell; + edited = true; + } + } + + if (class && strcmp(pwd->pw_class, class) != 0) { + pwd->pw_class = class; + edited = true; + } + + if (homedir && strcmp(pwd->pw_dir, homedir) != 0) { + pwd->pw_dir = homedir; + if (fstatat(conf.rootfd, pwd->pw_dir, &st, 0) == -1) { + if (!createhome) + warnx("WARNING: home `%s' does not exist", + pwd->pw_dir); + else + docreatehome = true; + } else if (!S_ISDIR(st.st_mode)) { + warnx("WARNING: home `%s' is not a directory", + pwd->pw_dir); + } + } + + if (passwd && conf.fd == -1) { + lc = login_getpwclass(pwd); + if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) + warn("setting crypt(3) format"); + login_close(lc); + cnf->default_password = boolean_val(passwd, + cnf->default_password); + pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun); + edited = true; + } + + if (gecos && strcmp(pwd->pw_gecos, gecos) != 0) { + pwd->pw_gecos = gecos; + edited = true; + } + + if (fd != -1) + edited = pw_set_passwd(pwd, fd, precrypted, true); + + if (dryrun) + return (print_user(pwd, pretty, false)); + + if (edited) /* Only updated this if required */ + perform_chgpwent(name, pwd, nis ? nispasswd : NULL); + /* Now perform the needed changes concern groups */ + if (groups != NULL) { + /* Delete User from groups using old name */ + SETGRENT(); + while ((grp = GETGRENT()) != NULL) { + if (grp->gr_mem == NULL) + continue; + for (i = 0; grp->gr_mem[i] != NULL; i++) { + if (strcmp(grp->gr_mem[i] , name) != 0) + continue; + for (j = i; grp->gr_mem[j] != NULL ; j++) + grp->gr_mem[j] = grp->gr_mem[j+1]; + chggrent(grp->gr_name, grp); + break; + } + } + ENDGRENT(); + /* Add the user to the needed groups */ + for (i = 0; i < groups->sl_cur; i++) { + grp = GETGRNAM(groups->sl_str[i]); + grp = gr_add(grp, pwd->pw_name); + if (grp == NULL) + continue; + chggrent(grp->gr_name, grp); + free(grp); + } + } + /* In case of rename we need to walk over the different groups */ + if (newname) { + SETGRENT(); + while ((grp = GETGRENT()) != NULL) { + if (grp->gr_mem == NULL) + continue; + for (i = 0; grp->gr_mem[i] != NULL; i++) { + if (strcmp(grp->gr_mem[i], name) != 0) + continue; + grp->gr_mem[i] = newname; + chggrent(grp->gr_name, grp); + break; + } + } + } + + /* go get a current version of pwd */ + if (newname) + name = newname; + pwd = GETPWNAM(name); + if (pwd == NULL) + errx(EX_NOUSER, "user '%s' disappeared during update", name); + grp = GETGRGID(pwd->pw_gid); + pw_log(cnf, M_UPDATE, W_USER, "%s(%ju):%s(%ju):%s:%s:%s", + pwd->pw_name, (uintmax_t)pwd->pw_uid, + grp ? grp->gr_name : "unknown", + (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1), + pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); + + /* + * Let's create and populate the user's home directory. Note + * that this also `works' for editing users if -m is used, but + * existing files will *not* be overwritten. + */ + if (PWALTDIR() != PWF_ALT && docreatehome && pwd->pw_dir && + *pwd->pw_dir == '/' && pwd->pw_dir[1]) { + if (!skel) + skel = cnf->dotdir; + if (homemode == 0) + homemode = cnf->homemode; + create_and_populate_homedir(cnf, pwd, skel, homemode, true); + } + + if (nis && nis_update() == 0) + pw_log(cnf, M_UPDATE, W_USER, "NIS maps updated"); + + return (EXIT_SUCCESS); +} Index: stable/10/usr.sbin/pw/pw_utils.c =================================================================== --- stable/10/usr.sbin/pw/pw_utils.c (nonexistent) +++ stable/10/usr.sbin/pw/pw_utils.c (revision 287084) @@ -0,0 +1,99 @@ +/*- + * Copyright (C) 2015 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 + * in this position and unchanged. + * 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(S) ``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(S) 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 "pw.h" + +int +pw_checkfd(char *nptr) +{ + const char *errstr; + int fd = -1; + + if (strcmp(nptr, "-") == 0) + return '-'; + fd = strtonum(nptr, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "Bad file descriptor '%s': %s", + nptr, errstr); + return (fd); +} + +uintmax_t +pw_checkid(char *nptr, uintmax_t maxval) +{ + const char *errstr = NULL; + uintmax_t id; + + id = strtounum(nptr, 0, maxval, &errstr); + if (errstr) + errx(EX_USAGE, "Bad id '%s': %s", nptr, errstr); + return (id); +} + +struct userconf * +get_userconfig(const char *config) +{ + char defaultcfg[MAXPATHLEN]; + + if (config != NULL) + return (read_userconfig(config)); + snprintf(defaultcfg, sizeof(defaultcfg), "%s/pw.conf", conf.etcpath); + return (read_userconfig(defaultcfg)); +} + +int +nis_update(void) { + pid_t pid; + int i; + + fflush(NULL); + if ((pid = fork()) == -1) { + warn("fork()"); + return (1); + } + if (pid == 0) { + execlp("/usr/bin/make", "make", "-C", "/var/yp/", (char*) NULL); + _exit(1); + } + waitpid(pid, &i, 0); + if ((i = WEXITSTATUS(i)) != 0) + errx(i, "make exited with status %d", i); + return (i); +} Property changes on: stable/10/usr.sbin/pw/pw_utils.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: stable/10/usr.sbin/pw/pw_vpw.c =================================================================== --- stable/10/usr.sbin/pw/pw_vpw.c (revision 287083) +++ stable/10/usr.sbin/pw/pw_vpw.c (revision 287084) @@ -1,200 +1,207 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #define _WITH_GETLINE #include #include #include #include +#include #include "pwupd.h" static FILE * pwd_fp = NULL; void vendpwent(void) { if (pwd_fp != NULL) { fclose(pwd_fp); pwd_fp = NULL; } } void vsetpwent(void) { vendpwent(); } static struct passwd * vnextpwent(char const *nam, uid_t uid, int doclose) { struct passwd *pw; char *line; size_t linecap; ssize_t linelen; pw = NULL; line = NULL; linecap = 0; linelen = 0; if (pwd_fp != NULL || (pwd_fp = fopen(getpwpath(_MASTERPASSWD), "r")) != NULL) { while ((linelen = getline(&line, &linecap, pwd_fp)) > 0) { /* Skip comments and empty lines */ if (*line == '\n' || *line == '#') continue; /* trim latest \n */ if (line[linelen - 1 ] == '\n') line[linelen - 1] = '\0'; pw = pw_scan(line, PWSCAN_MASTER); + if (pw == NULL) + errx(EXIT_FAILURE, "Invalid user entry in '%s':" + " '%s'", getpwpath(_MASTERPASSWD), line); if (uid != (uid_t)-1) { if (uid == pw->pw_uid) break; } else if (nam != NULL) { if (strcmp(nam, pw->pw_name) == 0) break; } else break; free(pw); pw = NULL; } if (doclose) vendpwent(); } free(line); return (pw); } struct passwd * vgetpwent(void) { return vnextpwent(NULL, -1, 0); } struct passwd * vgetpwuid(uid_t uid) { return vnextpwent(NULL, uid, 1); } struct passwd * vgetpwnam(const char * nam) { return vnextpwent(nam, -1, 1); } static FILE * grp_fp = NULL; void vendgrent(void) { if (grp_fp != NULL) { fclose(grp_fp); grp_fp = NULL; } } RET_SETGRENT vsetgrent(void) { vendgrent(); #if defined(__FreeBSD__) return 0; #endif } static struct group * vnextgrent(char const *nam, gid_t gid, int doclose) { struct group *gr; char *line; size_t linecap; ssize_t linelen; gr = NULL; line = NULL; linecap = 0; linelen = 0; if (grp_fp != NULL || (grp_fp = fopen(getgrpath(_GROUP), "r")) != NULL) { while ((linelen = getline(&line, &linecap, grp_fp)) > 0) { /* Skip comments and empty lines */ if (*line == '\n' || *line == '#') continue; /* trim latest \n */ if (line[linelen - 1 ] == '\n') line[linelen - 1] = '\0'; gr = gr_scan(line); + if (gr == NULL) + errx(EXIT_FAILURE, "Invalid group entry in '%s':" + " '%s'", getgrpath(_GROUP), line); if (gid != (gid_t)-1) { if (gid == gr->gr_gid) break; } else if (nam != NULL) { if (strcmp(nam, gr->gr_name) == 0) break; } else break; free(gr); gr = NULL; } if (doclose) vendgrent(); } free(line); return (gr); } struct group * vgetgrent(void) { return vnextgrent(NULL, -1, 0); } struct group * vgetgrgid(gid_t gid) { return vnextgrent(NULL, gid, 1); } struct group * vgetgrnam(const char * nam) { return vnextgrent(nam, -1, 1); } Index: stable/10/usr.sbin/pw/pwupd.c =================================================================== --- stable/10/usr.sbin/pw/pwupd.c (revision 287083) +++ stable/10/usr.sbin/pw/pwupd.c (revision 287084) @@ -1,151 +1,149 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ +#include + +#include +#include +#include +#include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include "pwupd.h" char * getpwpath(char const * file) { static char pathbuf[MAXPATHLEN]; snprintf(pathbuf, sizeof pathbuf, "%s/%s", conf.etcpath, file); return (pathbuf); } static int pwdb_check(void) { int i = 0; pid_t pid; char *args[10]; args[i++] = _PATH_PWD_MKDB; args[i++] = "-C"; if (strcmp(conf.etcpath, _PATH_PWD) != 0) { args[i++] = "-d"; args[i++] = conf.etcpath; } args[i++] = getpwpath(_MASTERPASSWD); args[i] = NULL; if ((pid = fork()) == -1) /* Error (errno set) */ i = errno; else if (pid == 0) { /* Child */ execv(args[0], args); _exit(1); } else { /* Parent */ waitpid(pid, &i, 0); if (WEXITSTATUS(i)) i = EIO; } return (i); } static int pw_update(struct passwd * pwd, char const * user) { struct passwd *pw = NULL; struct passwd *old_pw = NULL; int rc, pfd, tfd; if ((rc = pwdb_check()) != 0) return (rc); if (pwd != NULL) pw = pw_dup(pwd); if (user != NULL) old_pw = GETPWNAM(user); if (pw_init(conf.etcpath, NULL)) err(1, "pw_init()"); if ((pfd = pw_lock()) == -1) { pw_fini(); err(1, "pw_lock()"); } if ((tfd = pw_tmp(-1)) == -1) { pw_fini(); err(1, "pw_tmp()"); } if (pw_copy(pfd, tfd, pw, old_pw) == -1) { pw_fini(); err(1, "pw_copy()"); } /* * in case of deletion of a user, the whole database * needs to be regenerated */ if (pw_mkdb(pw != NULL ? pw->pw_name : NULL) == -1) { pw_fini(); err(1, "pw_mkdb()"); } free(pw); pw_fini(); return (0); } int addpwent(struct passwd * pwd) { return (pw_update(pwd, NULL)); } int chgpwent(char const * login, struct passwd * pwd) { return (pw_update(pwd, login)); } int delpwent(struct passwd * pwd) { return (pw_update(NULL, pwd->pw_name)); } Index: stable/10/usr.sbin/pw/pwupd.h =================================================================== --- stable/10/usr.sbin/pw/pwupd.h (revision 287083) +++ stable/10/usr.sbin/pw/pwupd.h (revision 287084) @@ -1,156 +1,152 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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$ */ #ifndef _PWUPD_H_ #define _PWUPD_H_ #include #include #include #include #include #include +#include #if defined(__FreeBSD__) #define RET_SETGRENT int #else #define RET_SETGRENT void #endif struct pwf { int _altdir; void (*_setpwent)(void); void (*_endpwent)(void); struct passwd * (*_getpwent)(void); struct passwd * (*_getpwuid)(uid_t uid); struct passwd * (*_getpwnam)(const char * nam); RET_SETGRENT (*_setgrent)(void); void (*_endgrent)(void); struct group * (*_getgrent)(void); struct group * (*_getgrgid)(gid_t gid); struct group * (*_getgrnam)(const char * nam); }; struct userconf { - int default_password; /* Default password for new users? */ - int reuse_uids; /* Reuse uids? */ - int reuse_gids; /* Reuse gids? */ - char *nispasswd; /* Path to NIS version of the passwd file */ - char *dotdir; /* Where to obtain skeleton files */ - char *newmail; /* Mail to send to new accounts */ - char *logfile; /* Where to log changes */ - char *home; /* Where to create home directory */ - mode_t homemode; /* Home directory permissions */ - char *shelldir; /* Where shells are located */ - char **shells; /* List of shells */ - char *shell_default; /* Default shell */ - char *default_group; /* Default group number */ - char **groups; /* Default (additional) groups */ - char *default_class; /* Default user class */ - uid_t min_uid, max_uid; /* Allowed range of uids */ - gid_t min_gid, max_gid; /* Allowed range of gids */ - int expire_days; /* Days to expiry */ - int password_days; /* Days to password expiry */ - int numgroups; /* (internal) size of default_group array */ + int default_password; /* Default password for new users? */ + int reuse_uids; /* Reuse uids? */ + int reuse_gids; /* Reuse gids? */ + char *nispasswd; /* Path to NIS version of the passwd file */ + char *dotdir; /* Where to obtain skeleton files */ + char *newmail; /* Mail to send to new accounts */ + char *logfile; /* Where to log changes */ + char *home; /* Where to create home directory */ + mode_t homemode; /* Home directory permissions */ + char *shelldir; /* Where shells are located */ + char **shells; /* List of shells */ + char *shell_default; /* Default shell */ + char *default_group; /* Default group number */ + StringList *groups; /* Default (additional) groups */ + char *default_class; /* Default user class */ + uid_t min_uid, max_uid; /* Allowed range of uids */ + gid_t min_gid, max_gid; /* Allowed range of gids */ + time_t expire_days; /* Days to expiry */ + time_t password_days; /* Days to password expiry */ }; struct pwconf { char rootdir[MAXPATHLEN]; char etcpath[MAXPATHLEN]; - char *newname; - char *config; - bool dryrun; - bool pretty; - bool v7; + int fd; + int rootfd; bool checkduplicate; - struct userconf *userconf; }; extern struct pwf PWF; extern struct pwf VPWF; extern struct pwconf conf; #define SETPWENT() PWF._setpwent() #define ENDPWENT() PWF._endpwent() #define GETPWENT() PWF._getpwent() #define GETPWUID(uid) PWF._getpwuid(uid) #define GETPWNAM(nam) PWF._getpwnam(nam) #define SETGRENT() PWF._setgrent() #define ENDGRENT() PWF._endgrent() #define GETGRENT() PWF._getgrent() #define GETGRGID(gid) PWF._getgrgid(gid) #define GETGRNAM(nam) PWF._getgrnam(nam) #define PWF_REGULAR 0 #define PWF_ALT 1 #define PWF_ROOTDIR 2 #define PWALTDIR() PWF._altdir #ifndef _PATH_PWD #define _PATH_PWD "/etc" #endif #ifndef _GROUP #define _GROUP "group" #endif #ifndef _MASTERPASSWD #define _MASTERPASSWD "master.passwd" #endif __BEGIN_DECLS int addpwent(struct passwd * pwd); int delpwent(struct passwd * pwd); int chgpwent(char const * login, struct passwd * pwd); char * getpwpath(char const * file); int addgrent(struct group * grp); int delgrent(struct group * grp); int chggrent(char const * name, struct group * grp); char * getgrpath(const char *file); void vsetpwent(void); void vendpwent(void); struct passwd * vgetpwent(void); struct passwd * vgetpwuid(uid_t uid); struct passwd * vgetpwnam(const char * nam); struct group * vgetgrent(void); struct group * vgetgrgid(gid_t gid); struct group * vgetgrnam(const char * nam); RET_SETGRENT vsetgrent(void); void vendgrent(void); -void copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid); -void rm_r(char const * dir, uid_t uid); -int extendarray(char ***buf, int *buflen, int needed); +void copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, + gid_t gid, int flags); +void rm_r(int rootfd, char const * dir, uid_t uid); __END_DECLS #endif /* !_PWUPD_H */ Index: stable/10/usr.sbin/pw/rm_r.c =================================================================== --- stable/10/usr.sbin/pw/rm_r.c (revision 287083) +++ stable/10/usr.sbin/pw/rm_r.c (revision 287084) @@ -1,75 +1,70 @@ /*- * Copyright (C) 1996 * David L. Nugent. 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 DAVID L. NUGENT 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 DAVID L. NUGENT 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 static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ -#include -#include -#include -#include #include -#include -#include + #include +#include +#include +#include #include "pwupd.h" void -rm_r(char const * dir, uid_t uid) +rm_r(int rootfd, const char *path, uid_t uid) { - DIR *d = opendir(dir); + int dirfd; + DIR *d; + struct dirent *e; + struct stat st; - if (d != NULL) { - struct dirent *e; - struct stat st; - char file[MAXPATHLEN]; + if (*path == '/') + path++; - while ((e = readdir(d)) != NULL) { - if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) { - snprintf(file, sizeof(file), "%s/%s", dir, e->d_name); - if (lstat(file, &st) == 0) { /* Need symlinks, not - * linked file */ - if (S_ISDIR(st.st_mode)) /* Directory - recurse */ - rm_r(file, uid); - else { - if (S_ISLNK(st.st_mode) || st.st_uid == uid) - remove(file); - } - } - } - } - closedir(d); - if (lstat(dir, &st) == 0) { - if (S_ISLNK(st.st_mode)) - remove(dir); - else if (st.st_uid == uid) - rmdir(dir); - } + dirfd = openat(rootfd, path, O_DIRECTORY); + + d = fdopendir(dirfd); + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) + continue; + + if (fstatat(dirfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) + continue; + if (S_ISDIR(st.st_mode)) + rm_r(dirfd, e->d_name, uid); + else if (S_ISLNK(st.st_mode) || st.st_uid == uid) + unlinkat(dirfd, e->d_name, 0); } + closedir(d); + if (fstatat(rootfd, path, &st, AT_SYMLINK_NOFOLLOW) != 0) + return; + unlinkat(rootfd, path, S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0); } Index: stable/10/usr.sbin/pw/strtounum.c =================================================================== --- stable/10/usr.sbin/pw/strtounum.c (nonexistent) +++ stable/10/usr.sbin/pw/strtounum.c (revision 287084) @@ -0,0 +1,72 @@ +/*- + * Copyright (C) 2015 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 + * in this position and unchanged. + * 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(S) ``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(S) 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 "pw.h" + +uintmax_t +strtounum(const char * __restrict np, uintmax_t minval, uintmax_t maxval, + const char ** __restrict errpp) +{ + char *endp; + uintmax_t ret; + + *errpp = NULL; + if (minval > maxval) { + errno = EINVAL; + if (errpp != NULL) + *errpp = "invalid"; + return (0); + } + errno = 0; + ret = strtoumax(np, &endp, 10); + if (endp == np || *endp != '\0') { + errno = EINVAL; + if (errpp != NULL) + *errpp = "invalid"; + return (0); + } + if (ret < minval) { + errno = ERANGE; + if (errpp != NULL) + *errpp = "too small"; + return (0); + } + if (errno == ERANGE || ret > maxval) { + errno = ERANGE; + if (errpp != NULL) + *errpp = "too large"; + return (0); + } + return (ret); +} Property changes on: stable/10/usr.sbin/pw/strtounum.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/Makefile =================================================================== --- stable/10/usr.sbin/pw/tests/Makefile (revision 287083) +++ stable/10/usr.sbin/pw/tests/Makefile (revision 287084) @@ -1,34 +1,24 @@ # $FreeBSD$ -TESTSRC= ${.CURDIR}/../../../contrib/netbsd-tests/usr.sbin/useradd -.PATH: ${TESTSRC} - TESTSDIR= ${TESTSBASE}/usr.sbin/pw ATF_TESTS_SH= pw_etcdir \ pw_lock \ pw_config \ + pw_groupadd \ pw_groupdel \ pw_groupmod \ pw_useradd \ pw_userdel \ pw_usermod \ pw_usernext .for tp in ${ATF_TESTS_SH} TEST_METADATA.${tp}+= required_user="root" .endfor FILES= group helper_functions.shin master.passwd pw.conf \ pw-modified.conf FILESDIR= ${TESTSDIR} - -ATF_TESTS_SH+= pw_test -# - user{add,del} does not exist on FreeBSD; use pw user{add,del} instead -# - The command passes on FreeBSD -ATF_TESTS_SH_SED_pw_test= -e 's/useradd /pw useradd /' -ATF_TESTS_SH_SED_pw_test+= -e 's/userdel /pw userdel /' -ATF_TESTS_SH_SED_pw_test+= -e '/atf_expect_fail "PR bin\/39546"/d' -ATF_TESTS_SH_SRC_pw_test= t_useradd.sh .include Index: stable/10/usr.sbin/pw/tests/helper_functions.shin =================================================================== --- stable/10/usr.sbin/pw/tests/helper_functions.shin (revision 287083) +++ stable/10/usr.sbin/pw/tests/helper_functions.shin (revision 287084) @@ -1,18 +1,32 @@ # $FreeBSD$ # The pw command PW="pw -V ${HOME}" +RPW="pw -R ${HOME}" # Workdir to run tests in TESTDIR=$(atf_get_srcdir) -# Populate the files pw needs to use into $HOME/etc +# Populate the files pw needs to use into $HOME populate_etc_skel() { cp ${TESTDIR}/master.passwd ${HOME} || \ atf_fail "Populating master.passwd in ${HOME}" cp ${TESTDIR}/group ${HOME} || atf_fail "Populating group in ${HOME}" # Generate the passwd file pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \ + atf_fail "generate passwd from master.passwd" +} + +# Populate the files pw needs to use into $HOME/etc +populate_root_etc_skel() { + mkdir ${HOME}/etc + cp ${TESTDIR}/master.passwd ${HOME}/etc || \ + atf_fail "Populating master.passwd in ${HOME}/etc" + cp ${TESTDIR}/group ${HOME}/etc || \ + atf_fail "Populating group in ${HOME}/etc" + + # Generate the passwd file + pwd_mkdb -p -d ${HOME}/etc ${HOME}//etc/master.passwd || \ atf_fail "generate passwd from master.passwd" } Index: stable/10/usr.sbin/pw/tests/pw_groupadd.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_groupadd.sh (nonexistent) +++ stable/10/usr.sbin/pw/tests/pw_groupadd.sh (revision 287084) @@ -0,0 +1,26 @@ +# $FreeBSD$ + +# Import helper functions +. $(atf_get_srcdir)/helper_functions.shin + +atf_test_case group_add_gid_too_large +group_add_gid_too_large_body() { + populate_etc_skel + atf_check -s exit:64 -e inline:"pw: Bad id '9999999999999': too large\n" \ + ${PW} groupadd -n test1 -g 9999999999999 +} + +atf_test_case group_add_already_exists +group_add_already_exists_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} groupadd foo + atf_check -s exit:65 \ + -e inline:"pw: group name \`foo' already exists\n" \ + ${PW} groupadd foo +} + +atf_init_test_cases() { + atf_add_test_case group_add_gid_too_large + atf_add_test_case group_add_already_exists +} Property changes on: stable/10/usr.sbin/pw/tests/pw_groupadd.sh ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: stable/10/usr.sbin/pw/tests/pw_groupdel.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_groupdel.sh (revision 287083) +++ stable/10/usr.sbin/pw/tests/pw_groupdel.sh (revision 287084) @@ -1,24 +1,24 @@ # $FreeBSD$ # Import helper functions . $(atf_get_srcdir)/helper_functions.shin # Test to make sure we do not accidentially delete wheel when trying to delete # an unknown group atf_test_case group_do_not_delete_wheel_if_group_unknown group_do_not_delete_wheel_if_group_unknown_head() { atf_set "descr" "Make sure we do not consider gid 0 an unknown group" } group_do_not_delete_wheel_if_group_unknown_body() { populate_etc_skel atf_check -s exit:0 -o inline:"wheel:*:0:root\n" -x ${PW} groupshow wheel - atf_check -e inline:"pw: -g expects a number\n" -s exit:64 -x \ + atf_check -e inline:"pw: Bad id 'I_do_not_exist': invalid\n" -s exit:64 -x \ ${PW} groupdel -g I_do_not_exist atf_check -s exit:0 -o inline:"wheel:*:0:root\n" -x ${PW} groupshow wheel } atf_init_test_cases() { atf_add_test_case group_do_not_delete_wheel_if_group_unknown } Index: stable/10/usr.sbin/pw/tests/pw_groupmod.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_groupmod.sh (revision 287083) +++ stable/10/usr.sbin/pw/tests/pw_groupmod.sh (revision 287084) @@ -1,91 +1,118 @@ # $FreeBSD$ # Import helper functions . $(atf_get_srcdir)/helper_functions.shin # Test adding & removing a user from a group atf_test_case groupmod_user groupmod_user_body() { populate_etc_skel atf_check -s exit:0 ${PW} addgroup test atf_check -s exit:0 ${PW} groupmod test -m root atf_check -s exit:0 -o match:"^test:\*:1001:root$" \ grep "^test:\*:.*:root$" $HOME/group atf_check -s exit:0 ${PW} groupmod test -d root atf_check -s exit:0 -o match:"^test:\*:1001:$" \ grep "^test:\*:.*:$" $HOME/group } # Test adding and removing a user that does not exist atf_test_case groupmod_invalid_user groupmod_invalid_user_body() { populate_etc_skel atf_check -s exit:0 ${PW} addgroup test atf_check -s exit:67 -e match:"does not exist" ${PW} groupmod test -m foo atf_check -s exit:0 ${PW} groupmod test -d foo } atf_test_case groupmod_bug_193704 groupmod_bug_193704_head() { atf_set "descr" "Regression test for the #193704 bug" } groupmod_bug_193704_body() { populate_etc_skel atf_check -s exit:0 -x ${PW} groupadd test atf_check -s exit:0 -x ${PW} groupmod test -l newgroupname atf_check -s exit:65 -e match:"^pw: unknown group" -x ${PW} groupshow test } atf_test_case usermod_bug_185666 usermod_bug_185666_head() { atf_set "descr" "Regression test for the #185666 bug" } usermod_bug_185666_body() { populate_etc_skel atf_check -s exit:0 -x ${PW} useradd testuser atf_check -s exit:0 -x ${PW} groupadd testgroup atf_check -s exit:0 -x ${PW} groupadd testgroup2 atf_check -s exit:0 -x ${PW} usermod testuser -G testgroup atf_check -o inline:"testuser:*:1001:\n" -x ${PW} groupshow testuser atf_check -o inline:"testgroup:*:1002:testuser\n" -x ${PW} groupshow testgroup atf_check -o inline:"testgroup2:*:1003:\n" -x ${PW} groupshow testgroup2 atf_check -s exit:0 -x ${PW} usermod testuser -G testgroup2 atf_check -o inline:"testuser:*:1001:\n" -x ${PW} groupshow testuser atf_check -o inline:"testgroup:*:1002:\n" -x ${PW} groupshow testgroup atf_check -o inline:"testgroup2:*:1003:testuser\n" -x ${PW} groupshow testgroup2 } atf_test_case do_not_duplicate_group_on_gid_change do_not_duplicate_group_on_gid_change_head() { atf_set "descr" "Do not duplicate group on gid change" } do_not_duplicate_group_on_gid_change_body() { populate_etc_skel atf_check -s exit:0 -x ${PW} groupadd testgroup atf_check -s exit:0 -x ${PW} groupmod testgroup -g 12345 # use grep to see if the entry has not be duplicated atf_check -o inline:"testgroup:*:12345:\n" -s exit:0 -x grep "^testgroup" ${HOME}/group } atf_test_case groupmod_rename groupmod_rename_body() { populate_etc_skel atf_check -s exit:0 ${PW} groupadd foo atf_check -s exit:0 ${PW} groupmod foo -l bar atf_check -s exit:0 -o match:"^bar:.*" \ grep "^bar:.*" ${HOME}/group } +atf_test_case groupmod_members +groupmod_members_body() { + populate_etc_skel + + for i in user1 user2 user3 user4; do + atf_check -s exit:0 ${PW} useradd $i + done + + atf_check -s exit:0 ${PW} groupadd foo -M "user1, user2" + atf_check -o inline:"foo:*:1005:user1,user2\n" -s exit:0 \ + ${PW} groupshow foo + atf_check -s exit:0 ${PW} groupmod foo -m "user3, user4" + atf_check -o inline:"foo:*:1005:user1,user2,user3,user4\n" -s exit:0 \ + ${PW} groupshow foo + atf_check -s exit:0 ${PW} groupmod foo -M "user1, user4" + atf_check -o inline:"foo:*:1005:user1,user4\n" -s exit:0 \ + ${PW} groupshow foo + # what about duplicates + atf_check -s exit:0 ${PW} groupmod foo -m "user1, user2, user3, user4" + atf_check -o inline:"foo:*:1005:user1,user4,user2,user3\n" -s exit:0 \ + ${PW} groupshow foo + atf_check -s exit:0 ${PW} groupmod foo -d "user1, user3" + atf_check -o inline:"foo:*:1005:user4,user2\n" -s exit:0 \ + ${PW} groupshow foo +} + atf_init_test_cases() { atf_add_test_case groupmod_user atf_add_test_case groupmod_invalid_user atf_add_test_case groupmod_bug_193704 atf_add_test_case usermod_bug_185666 atf_add_test_case do_not_duplicate_group_on_gid_change atf_add_test_case groupmod_rename + atf_add_test_case groupmod_members } Index: stable/10/usr.sbin/pw/tests/pw_useradd.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_useradd.sh (revision 287083) +++ stable/10/usr.sbin/pw/tests/pw_useradd.sh (revision 287084) @@ -1,196 +1,385 @@ # $FreeBSD$ # Import helper functions . $(atf_get_srcdir)/helper_functions.shin # Test add user atf_test_case user_add user_add_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test atf_check -s exit:0 -o match:"^test:.*" \ grep "^test:.*" $HOME/master.passwd } # Test add user with option -N atf_test_case user_add_noupdate user_add_noupdate_body() { populate_etc_skel atf_check -s exit:0 -o match:"^test:.*" ${PW} useradd test -N atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd } # Test add user with comments atf_test_case user_add_comments user_add_comments_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test -c "Test User,work,123,456" atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \ grep "^test:.*:Test User,work,123,456:" $HOME/master.passwd } # Test add user with comments and option -N atf_test_case user_add_comments_noupdate user_add_comments_noupdate_body() { populate_etc_skel atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \ ${PW} useradd test -c "Test User,work,123,456" -N atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd } # Test add user with invalid comments atf_test_case user_add_comments_invalid user_add_comments_invalid_body() { populate_etc_skel atf_check -s exit:65 -e match:"invalid character" \ ${PW} useradd test -c "Test User,work,123:456,456" atf_check -s exit:1 -o empty \ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd } # Test add user with invalid comments and option -N atf_test_case user_add_comments_invalid_noupdate user_add_comments_invalid_noupdate_body() { populate_etc_skel atf_check -s exit:65 -e match:"invalid character" \ ${PW} useradd test -c "Test User,work,123:456,456" -N atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd } # Test add user with alternate homedir atf_test_case user_add_homedir user_add_homedir_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test -d /foo/bar atf_check -s exit:0 -o match:"^test:\*:.*::0:0:User &:/foo/bar:.*" \ ${PW} usershow test } # Test add user with account expiration as an epoch date atf_test_case user_add_account_expiration_epoch user_add_account_expiration_epoch_body() { populate_etc_skel DATE=`date -j -v+1d "+%s"` atf_check -s exit:0 ${PW} useradd test -e ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::0:${DATE}:.*" \ ${PW} usershow test } # Test add user with account expiration as a DD-MM-YYYY date atf_test_case user_add_account_expiration_date_numeric user_add_account_expiration_date_numeric_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%m-%Y"` EPOCH=`date -j -f "%d-%m-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -e ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::0:${EPOCH}:User &:.*" \ ${PW} usershow test } # Test add user with account expiration as a DD-MM-YYYY date atf_test_case user_add_account_expiration_date_month user_add_account_expiration_date_month_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%b-%Y"` EPOCH=`date -j -f "%d-%b-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -e ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::0:${EPOCH}:User &:.*" \ ${PW} usershow test } # Test add user with account expiration as a relative date atf_test_case user_add_account_expiration_date_relative user_add_account_expiration_date_relative_body() { populate_etc_skel EPOCH=`date -j -v+13m "+%s"` BUF=`expr $EPOCH + 5` atf_check -s exit:0 ${PW} useradd test -e +13o TIME=`${PW} usershow test | awk -F ':' '{print $7}'` [ ! -z $TIME -a $TIME -ge $EPOCH -a $TIME -lt $BUF ] || \ atf_fail "Expiration time($TIME) was not within $EPOCH - $BUF seconds." } # Test add user with password expiration as an epoch date atf_test_case user_add_password_expiration_epoch user_add_password_expiration_epoch_body() { populate_etc_skel DATE=`date -j -v+1d "+%s"` atf_check -s exit:0 ${PW} useradd test -p ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::${DATE}:0:.*" \ ${PW} usershow test } # Test add user with password expiration as a DD-MM-YYYY date atf_test_case user_add_password_expiration_date_numeric user_add_password_expiration_date_numeric_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%m-%Y"` EPOCH=`date -j -f "%d-%m-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -p ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::${EPOCH}:0:User &:.*" \ ${PW} usershow test } # Test add user with password expiration as a DD-MMM-YYYY date atf_test_case user_add_password_expiration_date_month user_add_password_expiration_date_month_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%b-%Y"` EPOCH=`date -j -f "%d-%b-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -p ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::${EPOCH}:0:User &:.*" \ ${PW} usershow test } # Test add user with password expiration as a relative date atf_test_case user_add_password_expiration_date_relative user_add_password_expiration_date_relative_body() { populate_etc_skel EPOCH=`date -j -v+13m "+%s"` BUF=`expr $EPOCH + 5` atf_check -s exit:0 ${PW} useradd test -p +13o TIME=`${PW} usershow test | awk -F ':' '{print $6}'` [ ! -z $TIME -a $TIME -ge $EPOCH -a $TIME -lt $BUF ] || \ atf_fail "Expiration time($TIME) was not within $EPOCH - $BUF seconds." } atf_test_case user_add_name_too_long user_add_name_too_long_body() { populate_etc_skel atf_check -e match:"too long" -s exit:64 \ ${PW} useradd name_very_vert_very_very_very_long } +atf_test_case user_add_expiration +user_add_expiration_body() { + populate_etc_skel + + atf_check -s exit:0 \ + ${PW} useradd foo -e 20-03-2037 + atf_check -o inline:"foo:*:1001:1001::0:2121120000:User &:/home/foo:/bin/sh\n" \ + -s exit:0 grep "^foo" ${HOME}/master.passwd + atf_check -s exit:0 ${PW} userdel foo + atf_check -s exit:0 \ + ${PW} useradd foo -e 20-03-37 + atf_check -o inline:"foo:*:1001:1001::0:2121120000:User &:/home/foo:/bin/sh\n" \ + -s exit:0 grep "^foo" ${HOME}/master.passwd + atf_check -s exit:0 ${PW} userdel foo + atf_check -s exit:0 \ + ${PW} useradd foo -e 20-Mar-2037 + atf_check -o inline:"foo:*:1001:1001::0:2121120000:User &:/home/foo:/bin/sh\n" \ + -s exit:0 grep "^foo" ${HOME}/master.passwd + atf_check -s exit:0 ${PW} userdel foo + atf_check -e inline:"pw: Invalid date\n" -s exit:1 \ + ${PW} useradd foo -e 20-Foo-2037 + atf_check -e inline:"pw: Invalid date\n" -s exit:1 \ + ${PW} useradd foo -e 20-13-2037 + atf_check -s exit:0 ${PW} useradd foo -e "12:00 20-03-2037" + atf_check -s exit:0 ${PW} userdel foo + atf_check -e inline:"pw: Invalid date\n" -s exit:1 \ + ${PW} useradd foo -e "12 20-03-2037" + atf_check -s exit:0 ${PW} useradd foo -e "20-03-2037 12:00" + atf_check -s exit:0 ${PW} userdel foo +} + +atf_test_case user_add_invalid_user_entry +user_add_invalid_user_entry_body() { + touch ${HOME}/master.passwd + touch ${HOME}/group + + pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \ + atf_fail "generate passwd from master.passwd" + atf_check -s exit:0 ${PW} useradd foo + echo "foo1:*:1002" >> ${HOME}/master.passwd + atf_check -s exit:1 -e match:"Invalid user entry" ${PW} useradd foo2 +} + +atf_test_case user_add_invalid_group_entry +user_add_invalid_group_entry_body() { + touch ${HOME}/master.passwd + touch ${HOME}/group + + pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \ + atf_fail "generate passwd from master.passwd" + atf_check -s exit:0 ${PW} useradd foo + echo 'foo1:*:1002' >> group + atf_check -s exit:1 -e match:"Invalid group entry" ${PW} useradd foo2 +} + +atf_test_case user_add_password_from_h +user_add_password_from_h_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} useradd test -h 0 <<-EOF + $(echo test) + EOF +} + +atf_test_case user_add_R +user_add_R_body() { + populate_root_etc_skel + + atf_check -s exit:0 ${RPW} useradd foo + atf_check -s exit:0 ${RPW} useradd bar -m + test -d ${HOME}/home || atf_fail "Home parent directory not created" + test -d ${HOME}/home/bar || atf_fail "Directory not created" + atf_check -s exit:0 ${RPW} userdel bar + test -d ${HOME}/home/bar || atf_fail "Directory removed" + atf_check -s exit:0 ${RPW} useradd bar + atf_check -s exit:0 ${RPW} userdel bar -r + [ ! -d ${HOME}/home/bar ] || atf_fail "Directory not removed" +} + +atf_test_case user_add_R_symlink +user_add_R_symlink_body() { + populate_root_etc_skel + + mkdir ${HOME}/usr + atf_check -s exit:0 ${RPW} useradd foo -m + test -d ${HOME}/usr/home || atf_fail "Home parent directory not created" + test -h ${HOME}/home || atf_fail "/home directory is not a symlink" + atf_check -s exit:0 -o inline:"usr/home\n" readlink ${HOME}/home +} + +atf_test_case user_add_skel +user_add_skel_body() { + populate_root_etc_skel + + mkdir ${HOME}/skel + echo "a" > ${HOME}/skel/.a + echo "b" > ${HOME}/skel/b + mkdir ${HOME}/skel/c + mkdir ${HOME}/skel/c/d + mkdir ${HOME}/skel/dot.plop + echo "c" > ${HOME}/skel/c/d/dot.c + mkdir ${HOME}/home + ln -sf /nonexistent ${HOME}/skel/c/foo + atf_check -s exit:0 ${RPW} useradd foo -k /skel -m + test -d ${HOME}/home/foo || atf_fail "Directory not created" + test -f ${HOME}/home/foo/.a || atf_fail "File not created" + atf_check -o file:${HOME}/skel/.a -s exit:0 cat ${HOME}/home/foo/.a + atf_check -o file:${HOME}/skel/b -s exit:0 cat ${HOME}/home/foo/b + test -d ${HOME}/home/foo/c || atf_fail "Dotted directory in skel not copied" + test -d ${HOME}/home/foo/.plop || atf_fail "Directory in skell not created" + atf_check -o inline:"/nonexistent\n" -s ignore readlink -f ${HOME}/home/foo/c/foo + atf_check -o file:${HOME}/skel/c/d/dot.c -s exit:0 cat ${HOME}/home/foo/c/d/.c +} + +atf_test_case user_add_uid0 +user_add_uid0_body() { + populate_etc_skel + atf_check -e inline:"pw: WARNING: new account \`foo' has a uid of 0 (superuser access!)\n" \ + -s exit:0 ${PW} useradd foo -u 0 -g 0 -d /root -s /bin/sh -c "Bourne-again Superuser" -o + atf_check \ + -o inline:"foo:*:0:0::0:0:Bourne-again Superuser:/root:/bin/sh\n" \ + -s exit:0 ${PW} usershow foo +} + +atf_test_case user_add_uid_too_large +user_add_uid_too_large_body() { + populate_etc_skel + atf_check -s exit:64 -e inline:"pw: Bad id '9999999999999': too large\n" \ + ${PW} useradd -n test1 -u 9999999999999 +} + +atf_test_case user_add_bad_shell +user_add_bad_shell_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} useradd foo -s sh + atf_check -s exit:78 -e ignore ${PW} useradd bar -s badshell +} + +atf_test_case user_add_already_exists +user_add_already_exists_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} useradd foo + atf_check -s exit:65 \ + -e inline:"pw: login name \`foo' already exists\n" \ + ${PW} useradd foo +} + +atf_test_case user_add_w_yes +user_add_w_yes_body() { + populate_etc_skel + atf_check -s exit:0 ${PW} useradd foo -w yes + atf_check -s exit:0 \ + -o match:'^foo:\$.*' \ + grep "^foo" ${HOME}/master.passwd + atf_check -s exit:0 ${PW} usermod foo -w yes + atf_check -s exit:0 \ + -o match:'^foo:\$.*' \ + grep "^foo" ${HOME}/master.passwd +} + +atf_test_case user_add_with_pw_conf +user_add_with_pw_conf_body() +{ + populate_etc_skel + atf_check -s exit:0 \ + ${PW} useradd -D -C ${HOME}/pw.conf \ + -u 2000,32767 -i 2000,32767 + atf_check -s exit:0 \ + -o inline:"minuid = 2000\nmaxuid = 32767\nmingid = 2000\nmaxgid = 32767\n" \ + grep "^m.*id =" ${HOME}/pw.conf + atf_check -s exit:0 \ + ${PW} useradd foo -C ${HOME}/pw.conf +} + atf_init_test_cases() { atf_add_test_case user_add atf_add_test_case user_add_noupdate atf_add_test_case user_add_comments atf_add_test_case user_add_comments_noupdate atf_add_test_case user_add_comments_invalid atf_add_test_case user_add_comments_invalid_noupdate atf_add_test_case user_add_homedir atf_add_test_case user_add_account_expiration_epoch atf_add_test_case user_add_account_expiration_date_numeric atf_add_test_case user_add_account_expiration_date_month atf_add_test_case user_add_account_expiration_date_relative atf_add_test_case user_add_password_expiration_epoch atf_add_test_case user_add_password_expiration_date_numeric atf_add_test_case user_add_password_expiration_date_month atf_add_test_case user_add_password_expiration_date_relative atf_add_test_case user_add_name_too_long + atf_add_test_case user_add_expiration + atf_add_test_case user_add_invalid_user_entry + atf_add_test_case user_add_invalid_group_entry + atf_add_test_case user_add_password_from_h + atf_add_test_case user_add_R + atf_add_test_case user_add_R_symlink + atf_add_test_case user_add_skel + atf_add_test_case user_add_uid0 + atf_add_test_case user_add_uid_too_large + atf_add_test_case user_add_bad_shell + atf_add_test_case user_add_already_exists + atf_add_test_case user_add_w_yes + atf_add_test_case user_add_with_pw_conf } Index: stable/10/usr.sbin/pw/tests/pw_userdel.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_userdel.sh (revision 287083) +++ stable/10/usr.sbin/pw/tests/pw_userdel.sh (revision 287084) @@ -1,37 +1,67 @@ # $FreeBSD$ # Import helper functions . $(atf_get_srcdir)/helper_functions.shin # Test that a user can be deleted when another user is part of this # user's default group and does not go into an infinate loop. # PR: 191427 atf_test_case rmuser_seperate_group cleanup rmuser_seperate_group_head() { atf_set "timeout" "30" } rmuser_seperate_group_body() { populate_etc_skel ${PW} useradd test || atf_fail "Creating test user" ${PW} groupmod test -M 'test,root' || \ atf_fail "Modifying the group" ${PW} userdel test || atf_fail "Delete the test user" } atf_test_case user_do_not_try_to_delete_root_if_user_unknown user_do_not_try_to_delete_root_if_user_unknown_head() { atf_set "descr" \ "Make sure not to try to remove root if deleting an unknown user" } user_do_not_try_to_delete_root_if_user_unknown_body() { populate_etc_skel - atf_check -e inline:"pw: -u expects a number\n" -s exit:64 -x \ + atf_check -e inline:"pw: Bad id 'plop': invalid\n" -s exit:64 -x \ ${PW} userdel -u plop } +atf_test_case delete_files +delete_files_body() { + populate_root_etc_skel + + mkdir -p ${HOME}/skel + touch ${HOME}/skel/a + mkdir -p ${HOME}/home + mkdir -p ${HOME}/var/mail + echo "foo wedontcare" > ${HOME}/etc/opiekeys + atf_check -s exit:0 ${RPW} useradd foo -k /skel -m + test -d ${HOME}/home || atf_fail "Fail to create home directory" + test -f ${HOME}/var/mail/foo || atf_fail "Mail file not created" + atf_check -s exit:0 ${RPW} userdel foo -r + atf_check -s exit:0 -o inline:"#oo wedontcare\n" cat ${HOME}/etc/opiekeys + if test -f ${HOME}/var/mail/foo; then + atf_fail "Mail file not removed" + fi +} + +atf_test_case delete_numeric_name +delete_numeric_name_body() { + populate_etc_skel + + atf_check ${PW} useradd -n foo -u 4001 + atf_check -e inline:"pw: no such user \`4001'\n" -s exit:67 \ + ${PW} userdel -n 4001 +} + atf_init_test_cases() { atf_add_test_case rmuser_seperate_group atf_add_test_case user_do_not_try_to_delete_root_if_user_unknown + atf_add_test_case delete_files + atf_add_test_case delete_numeric_name } Index: stable/10/usr.sbin/pw/tests/pw_usermod.sh =================================================================== --- stable/10/usr.sbin/pw/tests/pw_usermod.sh (revision 287083) +++ stable/10/usr.sbin/pw/tests/pw_usermod.sh (revision 287084) @@ -1,133 +1,222 @@ # $FreeBSD$ # Import helper functions . $(atf_get_srcdir)/helper_functions.shin # Test modifying a user atf_test_case user_mod user_mod_body() { populate_etc_skel atf_check -s exit:67 -e match:"no such user" ${PW} usermod test atf_check -s exit:0 ${PW} useradd test atf_check -s exit:0 ${PW} usermod test atf_check -s exit:0 -o match:"^test:.*" \ grep "^test:.*" $HOME/master.passwd } # Test modifying a user with option -N atf_test_case user_mod_noupdate user_mod_noupdate_body() { populate_etc_skel atf_check -s exit:67 -e match:"no such user" ${PW} usermod test -N atf_check -s exit:0 ${PW} useradd test atf_check -s exit:0 -o match:"^test:.*" ${PW} usermod test -N atf_check -s exit:0 -o match:"^test:.*" \ grep "^test:.*" $HOME/master.passwd } # Test modifying a user with comments atf_test_case user_mod_comments user_mod_comments_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test -c "Test User,home,123,456" atf_check -s exit:0 ${PW} usermod test -c "Test User,work,123,456" atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \ grep "^test:.*:Test User,work,123,456:" $HOME/master.passwd } # Test modifying a user with comments with option -N atf_test_case user_mod_comments_noupdate user_mod_comments_noupdate_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test -c "Test User,home,123,456" atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \ ${PW} usermod test -c "Test User,work,123,456" -N atf_check -s exit:0 -o match:"^test:.*:Test User,home,123,456:" \ grep "^test:.*:Test User,home,123,456:" $HOME/master.passwd } # Test modifying a user with invalid comments atf_test_case user_mod_comments_invalid user_mod_comments_invalid_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test atf_check -s exit:65 -e match:"invalid character" \ ${PW} usermod test -c "Test User,work,123:456,456" atf_check -s exit:1 -o empty \ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd atf_check -s exit:0 -o match:"^test:\*" \ grep "^test:\*" $HOME/master.passwd } # Test modifying a user with invalid comments with option -N atf_test_case user_mod_comments_invalid_noupdate user_mod_comments_invalid_noupdate_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test atf_check -s exit:65 -e match:"invalid character" \ ${PW} usermod test -c "Test User,work,123:456,456" -N atf_check -s exit:1 -o empty \ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd atf_check -s exit:0 -o match:"^test:\*" \ grep "^test:\*" $HOME/master.passwd } # Test modifying a user name with -l atf_test_case user_mod_name user_mod_name_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd foo atf_check -s exit:0 ${PW} usermod foo -l "bar" atf_check -s exit:0 -o match:"^bar:.*" \ grep "^bar:.*" $HOME/master.passwd } # Test modifying a user name with -l with option -N atf_test_case user_mod_name_noupdate user_mod_name_noupdate_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd foo atf_check -s exit:0 -o match:"^bar:.*" ${PW} usermod foo -l "bar" -N atf_check -s exit:0 -o match:"^foo:.*" \ grep "^foo:.*" $HOME/master.passwd } +atf_test_case user_mod_rename_multigroups +user_mod_rename_multigroups_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} groupadd test1 + atf_check -s exit:0 ${PW} groupadd test2 + atf_check -s exit:0 ${PW} useradd foo -G test1,test2 + atf_check -o match:"foo" -s exit:0 ${PW} groupshow test1 + atf_check -o match:"foo" -s exit:0 ${PW} groupshow test2 + atf_check -s exit:0 ${PW} usermod foo -l bar + atf_check -o match:"bar" -s exit:0 ${PW} groupshow test1 + atf_check -o match:"bar" -s exit:0 ${PW} groupshow test2 +} + +atf_test_case user_mod_nogroups +user_mod_nogroups_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} groupadd test1 + atf_check -s exit:0 ${PW} groupadd test2 + atf_check -s exit:0 ${PW} groupadd test3 + atf_check -s exit:0 ${PW} groupadd test4 + atf_check -s exit:0 ${PW} useradd foo -G test1,test2 + atf_check -o match:"foo" -s exit:0 ${PW} groupshow test1 + atf_check -o match:"foo" -s exit:0 ${PW} groupshow test2 + atf_check -s exit:0 ${PW} usermod foo -G test3,test4 + atf_check -s exit:0 -o inline:"test3\ntest4\n" \ + awk -F\: '$4 == "foo" { print $1 }' ${HOME}/group +} + atf_test_case user_mod_rename user_mod_rename_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd foo atf_check -s exit:0 ${PW} usermod foo -l bar atf_check -s exit:0 -o match:"^bar:.*" \ grep "^bar:.*" ${HOME}/master.passwd } atf_test_case user_mod_rename_too_long user_mod_rename_too_long_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd foo atf_check -s exit:64 -e match:"too long" ${PW} usermod foo \ -l name_very_very_very_very_very_long } +atf_test_case user_mod_h +user_mod_h_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} useradd foo + atf_check -s exit:0 ${PW} usermod foo -h 0 <<- EOF + $(echo a) + EOF + atf_check -s exit:0 -o not-match:"^foo:\*:.*" \ + grep "^foo" ${HOME}/master.passwd + atf_check -s exit:0 ${PW} usermod foo -h - <<- EOF + $(echo b) + EOF + atf_check -s exit:0 -o match:"^foo:\*:.*" \ + grep "^foo" ${HOME}/master.passwd + atf_check -e inline:"pw: Bad file descriptor 'a': invalid\n" \ + -s exit:64 ${PW} usermod foo -h a <<- EOF + $(echo a) + EOF +} + +atf_test_case user_mod_H +user_mod_H_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} useradd foo + atf_check -s exit:0 ${PW} usermod foo -H 0 <<- EOF + $(echo a) + EOF + atf_check -s exit:0 -o match:"^foo:a:.*" \ + grep "^foo" ${HOME}/master.passwd + atf_check -s exit:64 -e inline:"pw: -H expects a file descriptor\n" \ + ${PW} usermod foo -H - +} + +atf_test_case user_mod_renamehome +user_mod_renamehome_body() { + populate_root_etc_skel + + mkdir -p ${HOME}/home + atf_check -s exit:0 ${RPW} useradd foo -m + test -d ${HOME}/home/foo || atf_fail "Directory not created" + atf_check -s exit:0 ${RPW} usermod foo -l bar -d /home/bar -m + test -d ${HOME}/home/bar || atf_fail "Directory not created" +} + +atf_test_case user_mod_uid +user_mod_uid_body() { + populate_etc_skel + + atf_check -s exit:0 ${PW} useradd foo + atf_check -s exit:0 ${PW} usermod foo -u 5000 +} + atf_init_test_cases() { atf_add_test_case user_mod atf_add_test_case user_mod_noupdate atf_add_test_case user_mod_comments atf_add_test_case user_mod_comments_noupdate atf_add_test_case user_mod_comments_invalid atf_add_test_case user_mod_comments_invalid_noupdate + atf_add_test_case user_mod_nogroups atf_add_test_case user_mod_rename atf_add_test_case user_mod_name_noupdate - atf_add_test_case user_mod_rename atf_add_test_case user_mod_rename_too_long + atf_add_test_case user_mod_rename_multigroups + atf_add_test_case user_mod_h + atf_add_test_case user_mod_H + atf_add_test_case user_mod_renamehome + atf_add_test_case user_mod_uid } Index: stable/10 =================================================================== --- stable/10 (revision 287083) +++ stable/10 (revision 287084) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r285133,285136-285137,285156-285158,285256,285318,285395-285396,285398,285401,285403,285405-285406,285408-285409,285411-285413,285415,285418,285430,285433-285434,285442,285948,285984-285985,285989,285996-285997,286045,286047,286066,286150-286152,286154-286157,286173,286196-286204,286210-286211,286217-286218,286258-286259,286341,286775,286982,286986,286991,286993