X-Git-Url: https://git.cameronkatri.com/pw-darwin.git/blobdiff_plain/5ad8ded4512753663b89863847a2085fcd0c9d79..a8e235f6926f727fd30a1fc881bda8f39c16ac1b:/pw/pw.c diff --git a/pw/pw.c b/pw/pw.c index 91412a8..5ad2511 100644 --- a/pw/pw.c +++ b/pw/pw.c @@ -22,26 +22,70 @@ * 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 "pw.h" +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include +#include +#include #include +#include #include +#include "pw.h" -static char *progname = "pw"; - -const char *Modes[] = {"add", "del", "mod", "show", "next", NULL}; +#if !defined(_PATH_YP) +#define _PATH_YP "/var/yp/" +#endif +const char *Modes[] = { + "add", "del", "mod", "show", "next", + NULL}; const char *Which[] = {"user", "group", NULL}; static const char *Combo1[] = { "useradd", "userdel", "usermod", "usershow", "usernext", + "lock", "unlock", "groupadd", "groupdel", "groupmod", "groupshow", "groupnext", NULL}; static const char *Combo2[] = { "adduser", "deluser", "moduser", "showuser", "nextuser", + "lock", "unlock", "addgroup", "delgroup", "modgroup", "showgroup", "nextgroup", -NULL}; + 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, +}; + +struct pwconf conf; static struct cargs arglist; @@ -55,61 +99,114 @@ main(int argc, char *argv[]) int ch; int mode = -1; int which = -1; - struct userconf *cnf; + long id = -1; + char *config = NULL; + struct stat st; + const char *errstr; + char arg, *name; + bool relocated, nis; static const char *opts[W_NUM][M_NUM] = { { /* user */ - "C:qn:u:c:d:e:p:g:G:mk:s:oL:i:w:h:Db:NPy:Y", - "C:qn:u:rY", - "C:qn:u:c:d:e:p:g:G:ml:k:s:w:L:h:FNPY", - "C:qn:u:FPa", - "C:q" + "R:V:C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y", + "R:V:C:qn:u:rY", + "R:V:C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:FNPY", + "R:V:C:qn:u:FPa7", + "R:V:C:q", + "R:V:C:q", + "R:V:C:q" }, { /* grp */ - "C:qn:g:h:M:pNPY", - "C:qn:g:Y", - "C:qn:g:l:h:FM:m:NPY", - "C:qn:g:FPa", - "C:q" + "R:V:C:qn:g:h:H:M:opNPY", + "R:V:C:qn:g:Y", + "R:V:C:qn:d:g:l:h:H:FM:m:NPY", + "R:V:C:qn:g:FPa", + "R:V:C:q" } }; - static int (*funcs[W_NUM]) (struct userconf * _cnf, int _mode, struct cargs * _args) = + static int (*funcs[W_NUM]) (int _mode, char *_name, long _id, + struct cargs * _args) = { /* Request handlers */ pw_user, pw_group }; - umask(0); /* We wish to handle this manually */ - progname = strrchr(argv[0], '/'); - if (progname != NULL) - ++progname; - else - progname = argv[0]; + name = 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; LIST_INIT(&arglist); + (void)setlocale(LC_ALL, ""); + /* * Break off the first couple of words to determine what exactly * we're being asked to do */ - while (argc > 1 && *argv[1] != '-') { + while (argc > 1) { int tmp; - if ((tmp = getindex(Modes, argv[1])) != -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 ((tmp = getindex(Which, argv[1])) != -1) + else if (which == -1 && (tmp = getindex(Which, argv[1])) != -1) which = tmp; - else if ((tmp = getindex(Combo1, argv[1])) != -1 || (tmp = getindex(Combo2, argv[1])) != -1) { + 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) + } else if (strcmp(argv[1], "help") == 0 && argv[2] == NULL) cmdhelp(mode, which); - else if (which != -1 && mode != -1 && arglist.lh_first == NULL) - addarg(&arglist, 'n', argv[1]); - else - cmderr(EX_USAGE, "Unknown keyword `%s'\n", argv[1]); + else if (which != -1 && mode != -1) { + if (strspn(argv[1], "0123456789") == strlen(argv[1])) { + id = strtonum(argv[1], 0, LONG_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "Bad id '%s': %s", + argv[1], errstr); + } else + name = argv[1]; + } else + errx(EX_USAGE, "unknown keyword `%s'", argv[1]); ++argv; --argc; } @@ -120,68 +217,192 @@ main(int argc, char *argv[]) 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); + conf.which = which; /* * We know which mode we're in and what we're about to do, so now * let's dispatch the remaining command line args in a genric way. */ - argv[0] = progname; /* Preserve this */ optarg = NULL; while ((ch = getopt(argc, argv, opts[which][mode])) != -1) { - if (ch == '?') - cmderr(EX_USAGE, NULL); - else + switch (ch) { + case '?': + errx(EX_USAGE, "unknown switch"); + break; + case '7': + conf.v7 = true; + break; + case 'C': + conf.config = optarg; + config = conf.config; + break; + case 'F': + conf.force = true; + break; + case 'N': + conf.dryrun = true; + break; + case 'l': + if (strlen(optarg) >= MAXLOGNAME) + errx(EX_USAGE, "new name too long: %s", optarg); + conf.newname = optarg; + break; + case 'P': + conf.pretty = true; + break; + case 'Y': + nis = true; + break; + case 'a': + conf.all = true; + break; + case 'c': + conf.gecos = pw_checkname(optarg, 1); + break; + case 'g': + if (which == 0) { /* for user* */ + addarg(&arglist, 'g', optarg); + break; + } + if (strspn(optarg, "0123456789") != strlen(optarg)) + errx(EX_USAGE, "-g expects a number"); + id = strtonum(optarg, 0, LONG_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "Bad id '%s': %s", optarg, + errstr); + break; + case 'u': + if (strspn(optarg, "0123456789,") != strlen(optarg)) + errx(EX_USAGE, "-u expects a number"); + if (strchr(optarg, ',') != NULL) { + addarg(&arglist, 'u', optarg); + break; + } + id = strtonum(optarg, 0, LONG_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "Bad id '%s': %s", optarg, + errstr); + break; + case 'n': + if (strspn(optarg, "0123456789") != strlen(optarg)) { + name = optarg; + break; + } + id = strtonum(optarg, 0, LONG_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "Bad id '%s': %s", optarg, + errstr); + break; + case 'H': + if (conf.fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + conf.precrypted = true; + if (strspn(optarg, "0123456789") != strlen(optarg)) + errx(EX_USAGE, "'-H' expects a file descriptor"); + + conf.fd = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "Bad file descriptor '%s': %s", + optarg, errstr); + break; + case 'h': + if (conf.fd != -1) + errx(EX_USAGE, "'-h' and '-H' are mutually " + "exclusive options"); + + if (strcmp(optarg, "-") == 0) + conf.fd = '-'; + else if (strspn(optarg, "0123456789") == strlen(optarg)) { + conf.fd = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(EX_USAGE, "'-h' expects a " + "file descriptor or '-'"); + } else + errx(EX_USAGE, "'-h' expects a file " + "descriptor or '-'"); + break; + case 'o': + conf.checkduplicate = false; + break; + case 'q': + conf.quiet = true; + break; + case 'r': + conf.deletehome = true; + break; + default: addarg(&arglist, ch, optarg); + break; + } optarg = NULL; } + if (name != NULL && strlen(name) >= MAXLOGNAME) + errx(EX_USAGE, "name too long: %s", name); + /* * Must be root to attempt an update */ - if (getuid() != 0 && mode != M_PRINT && mode != M_NEXT && getarg(&arglist, 'N')==NULL) - cmderr(EX_NOPERM, "you must be root to run this program\n"); + if (geteuid() != 0 && mode != M_PRINT && mode != M_NEXT && !conf.dryrun) + errx(EX_NOPERM, "you must be root to run this program"); /* * We should immediately look for the -q 'quiet' switch so that we * don't bother with extraneous errors */ - if (getarg(&arglist, 'q') != NULL) - freopen("/dev/null", "w", stderr); + if (conf.quiet) + freopen(_PATH_DEVNULL, "w", stderr); + + /* + * Set our base working path if not overridden + */ + + if (config == NULL) { /* Only override config location if -C not specified */ + asprintf(&config, "%s/pw.conf", conf.etcpath); + if (config == NULL) + errx(EX_OSERR, "out of memory"); + } /* * Now, let's do the common initialisation */ - cnf = read_userconfig(getarg(&arglist, 'C') ? getarg(&arglist, 'C')->val : NULL); - ch = funcs[which] (cnf, mode, &arglist); + conf.userconf = read_userconfig(config); + + ch = funcs[which] (mode, name, id, &arglist); /* * If everything went ok, and we've been asked to update * the NIS maps, then do it now */ - if (ch == EXIT_SUCCESS && getarg(&arglist, 'Y') != NULL) { + if (ch == EXIT_SUCCESS && nis) { pid_t pid; fflush(NULL); if (chdir(_PATH_YP) == -1) - perror("chdir(" _PATH_YP ")"); + warn("chdir(" _PATH_YP ")"); else if ((pid = fork()) == -1) - perror("fork()"); + warn("fork()"); else if (pid == 0) { /* Is make anywhere else? */ - execlp("/usr/bin/make", "make", NULL); + execlp("/usr/bin/make", "make", (char *)NULL); _exit(1); } else { int i; waitpid(pid, &i, 0); if ((i = WEXITSTATUS(i)) != 0) - cmderr(ch, "warning: make exited with status %d\n", i); + errx(ch, "make exited with status %d", i); else - pw_log(cnf, mode, which, "NIS maps updated"); + pw_log(conf.userconf, mode, which, "NIS maps updated"); } } return ch; } + static int getindex(const char *words[], const char *word) { @@ -201,34 +422,13 @@ getindex(const char *words[], const char *word) * the complexity of the command line. */ -static void -banner(void) -{ - fprintf(stderr, "%s: ", progname); -} - -void -cmderr(int ec, char const * fmt,...) -{ - if (fmt != NULL) { - va_list argp; - - banner(); - va_start(argp, fmt); - vfprintf(stderr, fmt, argp); - va_end(argp); - } - exit(ec); -} - static void cmdhelp(int mode, int which) { - banner(); if (which == -1) - fprintf(stderr, "usage: %s [user|group] [add|del|mod|show|next] [ help | switches/values ]\n", progname); + 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: %s %s [add|del|mod|show|next] [ help | switches/values ]\n", progname, Which[which]); + fprintf(stderr, "usage:\n pw %s [add|del|mod|show|next] [help|switches/values]\n", Which[which]); else { /* @@ -237,7 +437,9 @@ cmdhelp(int mode, int which) static const char *help[W_NUM][M_NUM] = { { - "usage: %s useradd [name] [switches]\n" + "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" @@ -250,14 +452,18 @@ cmdhelp(int mode, int which) "\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-D set user 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" @@ -265,17 +471,22 @@ cmdhelp(int mode, int which) "\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: %s userdel [uid|name] [switches]\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-r remove home & contents\n", - "usage: %s usermod [uid|name] [switches]\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" @@ -290,22 +501,40 @@ cmdhelp(int mode, int which) "\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-N no update\n", - "usage: %s usershow [uid|name] [switches]\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", - "usage: %s usernext [switches]\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: %s groupadd [group|gid] [switches]\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" @@ -314,11 +543,15 @@ cmdhelp(int mode, int which) "\t-o duplicate gid ok\n" "\t-Y update NIS maps\n" "\t-N no update\n", - "usage: %s groupdel [group|gid] [switches]\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: %s groupmod [group|gid] [switches]\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" @@ -326,21 +559,27 @@ cmdhelp(int mode, int which) "\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: %s groupshow [group|gid] [switches]\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: %s groupnext [switches]\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, help[which][mode], progname); + fprintf(stderr, "%s", help[which][mode]); } exit(EXIT_FAILURE); } @@ -348,10 +587,15 @@ cmdhelp(int mode, int which) struct carg * getarg(struct cargs * _args, int ch) { - struct carg *c = _args->lh_first; + struct carg *c; + + if (_args == NULL) + return (NULL); + + c = LIST_FIRST(_args); while (c != NULL && c->ch != ch) - c = c->list.le_next; + c = LIST_NEXT(c, list); return c; } @@ -361,7 +605,7 @@ addarg(struct cargs * _args, int ch, char *argstr) struct carg *ca = malloc(sizeof(struct carg)); if (ca == NULL) - cmderr(EX_OSERR, "Abort - out of memory\n"); + errx(EX_OSERR, "out of memory"); ca->ch = ch; ca->val = argstr; LIST_INSERT_HEAD(_args, ca, list);