Index: head/usr.sbin/pw/cpdir.c =================================================================== --- head/usr.sbin/pw/cpdir.c (revision 286200) +++ head/usr.sbin/pw/cpdir.c (revision 286201) @@ -1,129 +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(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, gid_t gid, int flags) { char *p, lnk[MAXPATHLEN], copybuf[4096]; int len, homefd, srcfd, destfd; ssize_t sz; struct stat st; struct dirent *e; DIR *d; if (*dir == '/') dir++; if (mkdirat(rootfd, dir, mode) != 0 && errno != EEXIST) { warn("mkdir(%s)", dir); return; } fchownat(rootfd, dir, uid, gid, AT_SYMLINK_NOFOLLOW); if (flags > 0) chflagsat(rootfd, dir, flags, AT_SYMLINK_NOFOLLOW); if (skelfd == -1) return; homefd = openat(rootfd, dir, O_DIRECTORY); if ((d = fdopendir(skelfd)) == NULL) { close(skelfd); close(homefd); return; } while ((e = readdir(d)) != NULL) { if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue; p = e->d_name; if (fstatat(skelfd, p, &st, AT_SYMLINK_NOFOLLOW) == -1) continue; if (strncmp(p, "dot.", 4) == 0) /* Conversion */ p += 3; 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 (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: head/usr.sbin/pw/grupd.c =================================================================== --- head/usr.sbin/pw/grupd.c (revision 286200) +++ head/usr.sbin/pw/grupd.c (revision 286201) @@ -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: head/usr.sbin/pw/psdate.c =================================================================== --- head/usr.sbin/pw/psdate.c (revision 286200) +++ head/usr.sbin/pw/psdate.c (revision 286201) @@ -1,262 +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 #include "psdate.h" 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 void parse_datesub(char const * str, struct tm *t) { 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, }; l = newlocale(LC_ALL_MASK, "C", NULL); 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); 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; } } parse_datesub(tmp, T); dt = mktime(T); } return dt; } Index: head/usr.sbin/pw/pw.c =================================================================== --- head/usr.sbin/pw/pw.c (revision 286200) +++ head/usr.sbin/pw/pw.c (revision 286201) @@ -1,380 +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" 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 int getindex(const char *words[], const char *word); static void cmdhelp(int mode, int which); int main(int argc, char *argv[]) { int mode = -1, which = -1, tmp; struct stat st; char arg, *arg1; bool relocated, nis; 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.fd = -1; conf.checkduplicate = true; setlocale(LC_ALL, ""); /* * Break off the first couple of words to determine what exactly * we're being asked to do */ while (argc > 1) { 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) 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); conf.rootfd = open(conf.rootdir, O_DIRECTORY|O_CLOEXEC); if (conf.rootfd == -1) errx(EXIT_FAILURE, "Unable to open '%s'", conf.rootdir); return (cmdfunc[which][mode](argc, argv, arg1)); } static int getindex(const char *words[], const char *word) { int i = 0; while (words[i]) { if (strcmp(words[i], word) == 0) return (i); i++; } 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-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); } Index: head/usr.sbin/pw/pw.h =================================================================== --- head/usr.sbin/pw/pw.h (revision 286200) +++ head/usr.sbin/pw/pw.h (revision 286201) @@ -1,116 +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 -#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 }; #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(struct userconf *cnf, char const * file); 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: head/usr.sbin/pw/pw_conf.c =================================================================== --- head/usr.sbin/pw/pw_conf.c (revision 286200) +++ head/usr.sbin/pw/pw_conf.c (revision 286201) @@ -1,524 +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 #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 */ }; 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; 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 (config.groups == NULL) config.groups = sl_init(); sl_add(config.groups, newstr(q)); } 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) { 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) { 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) { 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) { 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) { 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) { 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(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(cnf->default_password)); break; case _UC_REUSEUID: sbuf_cat(buf, boolean_str(cnf->reuse_uids)); break; case _UC_REUSEGID: sbuf_cat(buf, boolean_str(cnf->reuse_gids)); break; case _UC_NISPASSWD: sbuf_cat(buf, cnf->nispasswd ? cnf->nispasswd : ""); quote = 0; break; case _UC_DOTDIR: sbuf_cat(buf, cnf->dotdir ? cnf->dotdir : boolean_str(0)); break; case _UC_NEWMAIL: sbuf_cat(buf, cnf->newmail ? cnf->newmail : boolean_str(0)); break; case _UC_LOGFILE: sbuf_cat(buf, cnf->logfile ? cnf->logfile : boolean_str(0)); break; case _UC_HOMEROOT: sbuf_cat(buf, cnf->home); break; case _UC_HOMEMODE: sbuf_printf(buf, "%04o", cnf->homemode); quote = 0; break; case _UC_SHELLPATH: 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, cnf->shell_default ? cnf->shell_default : bourne_shell); break; case _UC_DEFAULTGROUP: sbuf_cat(buf, cnf->default_group ? cnf->default_group : ""); break; case _UC_EXTRAGROUPS: for (j = 0; cnf->groups != NULL && j < (int)cnf->groups->sl_cur; j++) sbuf_printf(buf, "%s\"%s\"", j ? "," : "", cnf->groups->sl_str[j]); quote = 0; break; case _UC_DEFAULTCLASS: sbuf_cat(buf, cnf->default_class ? cnf->default_class : ""); break; case _UC_MINUID: sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid); quote = 0; break; case _UC_MAXUID: sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid); quote = 0; break; case _UC_MINGID: sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid); quote = 0; break; case _UC_MAXGID: sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid); quote = 0; break; case _UC_EXPIRE: sbuf_printf(buf, "%ld", cnf->expire_days); quote = 0; break; case _UC_PASSWORD: sbuf_printf(buf, "%ld", 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: head/usr.sbin/pw/pw_group.c =================================================================== --- head/usr.sbin/pw/pw_group.c (revision 286200) +++ head/usr.sbin/pw/pw_group.c (revision 286201) @@ -1,687 +1,687 @@ /*- * 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 "pw.h" #include "bitmap.h" static struct passwd *lookup_pwent(const char *user); 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); static void grp_set_passwd(struct group *grp, bool update, int fd, bool precrypted) { int b; int istty; struct termios t, n; char *p, line[256]; if (fd == -1) return; if (fd == '-') { grp->gr_passwd = "*"; /* No access */ return; } 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); } 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); } int pw_groupnext(struct userconf *cnf, bool quiet) { gid_t next = gr_gidpolicy(cnf, -1); if (quiet) return (next); printf("%ju\n", (uintmax_t)next); return (EXIT_SUCCESS); } static struct group * getgroup(char *name, intmax_t id, bool fatal) { struct group *grp; 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); } 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(struct group *grp, char *list) { char *p; int k; if (grp->gr_mem == NULL) return; 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; } if (grp->gr_mem[k] == NULL) /* No match */ continue; for (; grp->gr_mem[k] != NULL; k++) grp->gr_mem[k] = grp->gr_mem[k+1]; } } 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 `%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); /* * 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) 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); } static int print_group(struct group * grp, bool pretty) { char *buf = NULL; 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); } 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 (strspn(arg1, "0123456789") == strlen(arg1)) 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 (strspn(arg1, "0123456789") == strlen(arg1)) 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 (strspn(arg1, "0123456789") == strlen(arg1)) 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 (strspn(arg1, "0123456789") == strlen(arg1)) 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: head/usr.sbin/pw/pw_log.c =================================================================== --- head/usr.sbin/pw/pw_log.c (revision 286200) +++ head/usr.sbin/pw/pw_log.c (revision 286201) @@ -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: head/usr.sbin/pw/pw_nis.c =================================================================== --- head/usr.sbin/pw/pw_nis.c (revision 286200) +++ head/usr.sbin/pw/pw_nis.c (revision 286201) @@ -1,94 +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: head/usr.sbin/pw/pw_user.c =================================================================== --- head/usr.sbin/pw/pw_user.c (revision 286200) +++ head/usr.sbin/pw/pw_user.c (revision 286201) @@ -1,1728 +1,1733 @@ /*- * 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 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(struct userconf *cnf, struct passwd *pwd, const char *skeldir, mode_t homemode, bool update) { int skelfd = -1; if (skeldir != NULL && *skeldir != '\0') { if (*skeldir == '/') skeldir++; skelfd = openat(conf.rootfd, skeldir, O_DIRECTORY|O_CLOEXEC); } 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); } 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; if (fd == '-') { if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { pwd->pw_passwd = "*"; /* No access */ return (1); } return (0); } 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 (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 { 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); } return (1); } static void perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd) { int rc; struct passwd *nispwd; /* duplicate for nis so that chgpwent is not modifying before NIS */ if (nispasswd && *nispasswd == '/') nispwd = pw_dup(pwd); 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 (nispasswd && *nispasswd == '/') { rc = chgnispwent(nispasswd, name, nispwd); if (rc == -1) warn("User '%s' not found in NIS passwd", pwd->pw_name); else if (rc != 0) warn("NIS passwd update"); /* NOTE: NIS-only update errors are not fatal */ } } /* * 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 (geteuid() != 0) errx(EX_NOPERM, "you must be root"); if (arg1 == NULL) errx(EX_DATAERR, "username or id required"); if (strspn(arg1, "0123456789") == strlen(arg1)) id = pw_checkid(arg1, UID_MAX); else name = arg1; 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 (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); 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; } perform_chgpwent(name, pwd, NULL); free(passtmp); return (EXIT_SUCCESS); } 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 `%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); /* * 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); /* * 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 userconf *cnf, char *grname, char *nam, gid_t prefer, bool dryrun) { struct group *grp; gid_t gid = (uid_t) - 1; /* * Check the given gid, if any */ SETGRENT(); 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 { intmax_t grid = -1; /* * 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 (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); } static char * pw_homepolicy(struct userconf * cnf, char *homedir, const char *user) { static char home[128]; 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 * 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) { return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default); } #define SALTSIZE 32 static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; 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, 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 (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 print_user(struct passwd * pwd, bool pretty, bool v7) { 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 (!pretty) { p = v7 ? pw_make_v7(pwd) : pw_make(pwd); printf("%s\n", p); free(p); return (EXIT_SUCCESS); } 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(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); 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 #%-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); } 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_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); system(tmp); } } closedir(d); } } static void rmopie(char const * name) { 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; fp = fdopen(fd, "r+"); len = strlen(name); 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; 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; uid_t id = -1; int ch; bool all = false; bool pretty = false; bool force = false; bool v7 = false; bool quiet = false; if (arg1 != NULL) { if (strspn(arg1, "0123456789") == strlen(arg1)) 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; uid_t id; int ch, rc; bool nis = false; bool deletehome = false; bool quiet = false; if (arg1 != NULL) { if (strspn(arg1, "0123456789") == strlen(arg1)) 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 (strspn(arg1, "0123456789") == strlen(arg1)) 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); } pclose(pfp); pw_log(cnf, M_ADD, W_USER, "%s(%ju) new user mail sent", pwd->pw_name, (uintmax_t)pwd->pw_uid); } 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; mode_t homemode = 0; time_t expire_days, password_days, now, precrypted; 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 (strspn(arg1, "0123456789") == strlen(arg1)) 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); 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: head/usr.sbin/pw/pw_utils.c =================================================================== --- head/usr.sbin/pw/pw_utils.c (revision 286200) +++ head/usr.sbin/pw/pw_utils.c (revision 286201) @@ -1,97 +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); } Index: head/usr.sbin/pw/pwupd.c =================================================================== --- head/usr.sbin/pw/pwupd.c (revision 286200) +++ head/usr.sbin/pw/pwupd.c (revision 286201) @@ -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)); }