pw.c revision 38112
120253Sjoerg/*-
220302Sjoerg * Copyright (C) 1996
320302Sjoerg *	David L. Nugent.  All rights reserved.
420253Sjoerg *
520253Sjoerg * Redistribution and use in source and binary forms, with or without
620253Sjoerg * modification, are permitted provided that the following conditions
720253Sjoerg * are met:
820253Sjoerg * 1. Redistributions of source code must retain the above copyright
920302Sjoerg *    notice, this list of conditions and the following disclaimer.
1020253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright
1120253Sjoerg *    notice, this list of conditions and the following disclaimer in the
1220253Sjoerg *    documentation and/or other materials provided with the distribution.
1320253Sjoerg *
1420302Sjoerg * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
1520253Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1620253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1720302Sjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
1820253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1920253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2020253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2120253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2220253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2320253Sjoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2420253Sjoerg * SUCH DAMAGE.
2520253Sjoerg */
2620253Sjoerg
2730259Scharnier#ifndef lint
2830259Scharnierstatic const char rcsid[] =
2938112Snate	"$Id: pw.c,v 1.8 1998/07/16 17:18:25 nate Exp $";
3030259Scharnier#endif /* not lint */
3130259Scharnier
3220253Sjoerg#include "pw.h"
3330259Scharnier#include <err.h>
3438112Snate#include <fcntl.h>
3521330Sdavidn#include <paths.h>
3621330Sdavidn#include <sys/wait.h>
3720253Sjoerg
3820267Sjoergconst char     *Modes[] = {"add", "del", "mod", "show", "next", NULL};
3920253Sjoergconst char     *Which[] = {"user", "group", NULL};
4020267Sjoergstatic const char *Combo1[] = {
4120267Sjoerg  "useradd", "userdel", "usermod", "usershow", "usernext",
4220267Sjoerg  "groupadd", "groupdel", "groupmod", "groupshow", "groupnext",
4320267Sjoerg  NULL};
4420267Sjoergstatic const char *Combo2[] = {
4520267Sjoerg  "adduser", "deluser", "moduser", "showuser", "nextuser",
4620267Sjoerg  "addgroup", "delgroup", "modgroup", "showgroup", "nextgroup",
4720253SjoergNULL};
4820253Sjoerg
4920253Sjoergstatic struct cargs arglist;
5020253Sjoerg
5120253Sjoergstatic int      getindex(const char *words[], const char *word);
5220253Sjoergstatic void     cmdhelp(int mode, int which);
5338112Snatestatic void     filelock(const char *filename);
5420253Sjoerg
5520253Sjoerg
5620253Sjoergint
5720253Sjoergmain(int argc, char *argv[])
5820253Sjoerg{
5920253Sjoerg	int             ch;
6020253Sjoerg	int             mode = -1;
6120253Sjoerg	int             which = -1;
6220253Sjoerg	struct userconf *cnf;
6320253Sjoerg
6420253Sjoerg	static const char *opts[W_NUM][M_NUM] =
6520253Sjoerg	{
6620267Sjoerg		{ /* user */
6721330Sdavidn			"C:qn:u:c:d:e:p:g:G:mk:s:oL:i:w:h:Db:NPy:Y",
6821330Sdavidn			"C:qn:u:rY",
6922394Sdavidn			"C:qn:u:c:d:e:p:g:G:ml:k:s:w:L:h:FNPY",
7020267Sjoerg			"C:qn:u:FPa",
7120267Sjoerg			"C:q"
7220267Sjoerg		},
7320267Sjoerg		{ /* grp  */
7421330Sdavidn			"C:qn:g:h:M:pNPY",
7521330Sdavidn			"C:qn:g:Y",
7621330Sdavidn			"C:qn:g:l:h:FM:m:NPY",
7720267Sjoerg			"C:qn:g:FPa",
7820267Sjoerg			"C:q"
7920267Sjoerg		 }
8020253Sjoerg	};
8120253Sjoerg
8220253Sjoerg	static int      (*funcs[W_NUM]) (struct userconf * _cnf, int _mode, struct cargs * _args) =
8320253Sjoerg	{			/* Request handlers */
8420253Sjoerg		pw_user,
8520253Sjoerg		pw_group
8620253Sjoerg	};
8720253Sjoerg
8820253Sjoerg	umask(0);		/* We wish to handle this manually */
8920253Sjoerg	LIST_INIT(&arglist);
9020253Sjoerg
9120253Sjoerg	/*
9220253Sjoerg	 * Break off the first couple of words to determine what exactly
9320253Sjoerg	 * we're being asked to do
9420253Sjoerg	 */
9520253Sjoerg	while (argc > 1 && *argv[1] != '-') {
9620253Sjoerg		int             tmp;
9720253Sjoerg
9820253Sjoerg		if ((tmp = getindex(Modes, argv[1])) != -1)
9920253Sjoerg			mode = tmp;
10020253Sjoerg		else if ((tmp = getindex(Which, argv[1])) != -1)
10120253Sjoerg			which = tmp;
10220253Sjoerg		else if ((tmp = getindex(Combo1, argv[1])) != -1 || (tmp = getindex(Combo2, argv[1])) != -1) {
10320253Sjoerg			which = tmp / M_NUM;
10420253Sjoerg			mode = tmp % M_NUM;
10520253Sjoerg		} else if (strcmp(argv[1], "help") == 0)
10620253Sjoerg			cmdhelp(mode, which);
10720253Sjoerg		else if (which != -1 && mode != -1 && arglist.lh_first == NULL)
10820253Sjoerg			addarg(&arglist, 'n', argv[1]);
10920253Sjoerg		else
11030259Scharnier			errx(EX_USAGE, "unknown keyword `%s'", argv[1]);
11120253Sjoerg		++argv;
11220253Sjoerg		--argc;
11320253Sjoerg	}
11420253Sjoerg
11520253Sjoerg	/*
11620253Sjoerg	 * Bail out unless the user is specific!
11720253Sjoerg	 */
11820253Sjoerg	if (mode == -1 || which == -1)
11920253Sjoerg		cmdhelp(mode, which);
12020253Sjoerg
12120253Sjoerg	/*
12220253Sjoerg	 * We know which mode we're in and what we're about to do, so now
12320253Sjoerg	 * let's dispatch the remaining command line args in a genric way.
12420253Sjoerg	 */
12520253Sjoerg	optarg = NULL;
12620253Sjoerg
12720253Sjoerg	while ((ch = getopt(argc, argv, opts[which][mode])) != -1) {
12820253Sjoerg		if (ch == '?')
12930259Scharnier			errx(EX_USAGE, NULL);
13020253Sjoerg		else
13120253Sjoerg			addarg(&arglist, ch, optarg);
13220253Sjoerg		optarg = NULL;
13320253Sjoerg	}
13420253Sjoerg
13520253Sjoerg	/*
13620267Sjoerg	 * Must be root to attempt an update
13720267Sjoerg	 */
13827474Sdavidn	if (geteuid() != 0 && mode != M_PRINT && mode != M_NEXT && getarg(&arglist, 'N')==NULL)
13930259Scharnier		errx(EX_NOPERM, "you must be root to run this program");
14020267Sjoerg
14120267Sjoerg	/*
14220253Sjoerg	 * We should immediately look for the -q 'quiet' switch so that we
14320253Sjoerg	 * don't bother with extraneous errors
14420253Sjoerg	 */
14520253Sjoerg	if (getarg(&arglist, 'q') != NULL)
14620253Sjoerg		freopen("/dev/null", "w", stderr);
14720253Sjoerg
14820253Sjoerg	/*
14920253Sjoerg	 * Now, let's do the common initialisation
15020253Sjoerg	 */
15120253Sjoerg	cnf = read_userconfig(getarg(&arglist, 'C') ? getarg(&arglist, 'C')->val : NULL);
15238112Snate
15338112Snate	/*
15438112Snate	 * Try to lock the master passowrd and group files right away (we
15538112Snate	 * don't care if it works, since this is just advisory locking.
15638112Snate	 */
15738112Snate	filelock(_PATH_GROUP);
15838112Snate	filelock(_PATH_MASTERPASSWD);
15938110Snate	ch = funcs[which] (cnf, mode, &arglist);
16021330Sdavidn
16121330Sdavidn	/*
16221330Sdavidn	 * If everything went ok, and we've been asked to update
16321330Sdavidn	 * the NIS maps, then do it now
16421330Sdavidn	 */
16521330Sdavidn	if (ch == EXIT_SUCCESS && getarg(&arglist, 'Y') != NULL) {
16621330Sdavidn		pid_t	pid;
16721330Sdavidn
16821330Sdavidn		fflush(NULL);
16921330Sdavidn		if (chdir(_PATH_YP) == -1)
17030259Scharnier			warn("chdir(" _PATH_YP ")");
17121330Sdavidn		else if ((pid = fork()) == -1)
17230259Scharnier			warn("fork()");
17321330Sdavidn		else if (pid == 0) {
17421330Sdavidn			/* Is make anywhere else? */
17521330Sdavidn			execlp("/usr/bin/make", "make", NULL);
17621330Sdavidn			_exit(1);
17721330Sdavidn		} else {
17821330Sdavidn			int   i;
17921330Sdavidn			waitpid(pid, &i, 0);
18021330Sdavidn			if ((i = WEXITSTATUS(i)) != 0)
18130259Scharnier				errx(ch, "make exited with status %d", i);
18221330Sdavidn			else
18321330Sdavidn				pw_log(cnf, mode, which, "NIS maps updated");
18421330Sdavidn		}
18521330Sdavidn	}
18621330Sdavidn	return ch;
18720253Sjoerg}
18820253Sjoerg
18938112Snatestatic void
19038112Snatefilelock(const char *filename)
19138112Snate{
19238112Snate	open(filename, O_RDONLY | O_EXLOCK, 0);
19338112Snate}
19438112Snate
19520253Sjoergstatic int
19620253Sjoerggetindex(const char *words[], const char *word)
19720253Sjoerg{
19820253Sjoerg	int             i = 0;
19920253Sjoerg
20020253Sjoerg	while (words[i]) {
20120253Sjoerg		if (strcmp(words[i], word) == 0)
20220253Sjoerg			return i;
20320253Sjoerg		i++;
20420253Sjoerg	}
20520253Sjoerg	return -1;
20620253Sjoerg}
20720253Sjoerg
20820253Sjoerg
20920253Sjoerg/*
21020253Sjoerg * This is probably an overkill for a cmdline help system, but it reflects
21120253Sjoerg * the complexity of the command line.
21220253Sjoerg */
21320253Sjoerg
21420253Sjoergstatic void
21520253Sjoergcmdhelp(int mode, int which)
21620253Sjoerg{
21720253Sjoerg	if (which == -1)
21830259Scharnier		fprintf(stderr, "usage: pw [user|group] [add|del|mod|show|next] [ help | switches/values ]\n");
21920253Sjoerg	else if (mode == -1)
22030259Scharnier		fprintf(stderr, "usage: pw %s [add|del|mod|show|next] [ help | switches/values ]\n", Which[which]);
22120253Sjoerg	else {
22220253Sjoerg
22320253Sjoerg		/*
22420253Sjoerg		 * We need to give mode specific help
22520253Sjoerg		 */
22620253Sjoerg		static const char *help[W_NUM][M_NUM] =
22720253Sjoerg		{
22820253Sjoerg			{
22930259Scharnier				"usage: pw useradd [name] [switches]\n"
23020253Sjoerg				"\t-C config      configuration file\n"
23120253Sjoerg				"\t-q             quiet operation\n"
23220253Sjoerg				"  Adding users:\n"
23320253Sjoerg				"\t-n name        login name\n"
23420253Sjoerg				"\t-u uid         user id\n"
23520253Sjoerg				"\t-c comment     user name/comment\n"
23620253Sjoerg				"\t-d directory   home directory\n"
23720253Sjoerg				"\t-e date        account expiry date\n"
23820253Sjoerg				"\t-p date        password expiry date\n"
23920253Sjoerg				"\t-g grp         initial group\n"
24020253Sjoerg				"\t-G grp1,grp2   additional groups\n"
24120253Sjoerg				"\t-m [ -k dir ]  create and set up home\n"
24220253Sjoerg				"\t-s shell       name of login shell\n"
24320253Sjoerg				"\t-o             duplicate uid ok\n"
24420253Sjoerg				"\t-L class       user class\n"
24520253Sjoerg				"\t-h fd          read password on fd\n"
24621330Sdavidn				"\t-Y             update NIS maps\n"
24720267Sjoerg				"\t-N             no update\n"
24820253Sjoerg				"  Setting defaults:\n"
24920253Sjoerg				"\t-D             set user defaults\n"
25020253Sjoerg				"\t-b dir         default home root dir\n"
25120253Sjoerg				"\t-e period      default expiry period\n"
25220253Sjoerg				"\t-p period      default password change period\n"
25320253Sjoerg				"\t-g group       default group\n"
25420253Sjoerg				"\t-G grp1,grp2   additional groups\n"
25520253Sjoerg				"\t-L class       default user class\n"
25620253Sjoerg				"\t-k dir         default home skeleton\n"
25720253Sjoerg				"\t-u min,max     set min,max uids\n"
25820253Sjoerg				"\t-i min,max     set min,max gids\n"
25920253Sjoerg				"\t-w method      set default password method\n"
26021330Sdavidn				"\t-s shell       default shell\n"
26121330Sdavidn				"\t-y path        set NIS passwd file path\n",
26230259Scharnier				"usage: pw userdel [uid|name] [switches]\n"
26320253Sjoerg				"\t-n name        login name\n"
26420253Sjoerg				"\t-u uid         user id\n"
26521330Sdavidn				"\t-Y             update NIS maps\n"
26620253Sjoerg				"\t-r             remove home & contents\n",
26730259Scharnier				"usage: pw usermod [uid|name] [switches]\n"
26820253Sjoerg				"\t-C config      configuration file\n"
26920253Sjoerg				"\t-q             quiet operation\n"
27020253Sjoerg				"\t-F             force add if no user\n"
27120253Sjoerg				"\t-n name        login name\n"
27220253Sjoerg				"\t-u uid         user id\n"
27320253Sjoerg				"\t-c comment     user name/comment\n"
27420253Sjoerg				"\t-d directory   home directory\n"
27520253Sjoerg				"\t-e date        account expiry date\n"
27620253Sjoerg				"\t-p date        password expiry date\n"
27720253Sjoerg				"\t-g grp         initial group\n"
27820253Sjoerg				"\t-G grp1,grp2   additional groups\n"
27920253Sjoerg				"\t-l name        new login name\n"
28020253Sjoerg				"\t-L class       user class\n"
28120253Sjoerg				"\t-m [ -k dir ]  create and set up home\n"
28220253Sjoerg				"\t-s shell       name of login shell\n"
28320267Sjoerg				"\t-w method      set new password using method\n"
28420267Sjoerg				"\t-h fd          read password on fd\n"
28521330Sdavidn				"\t-Y             update NIS maps\n"
28620267Sjoerg				"\t-N             no update\n",
28730259Scharnier				"usage: pw usershow [uid|name] [switches]\n"
28820253Sjoerg				"\t-n name        login name\n"
28920253Sjoerg				"\t-u uid         user id\n"
29020253Sjoerg				"\t-F             force print\n"
29120267Sjoerg				"\t-P             prettier format\n"
29220267Sjoerg				"\t-a             print all users\n",
29330259Scharnier				"usage: pw usernext [switches]\n"
29420267Sjoerg				"\t-C config      configuration file\n"
29520253Sjoerg			},
29620253Sjoerg			{
29730259Scharnier				"usage: pw groupadd [group|gid] [switches]\n"
29820253Sjoerg				"\t-C config      configuration file\n"
29920253Sjoerg				"\t-q             quiet operation\n"
30020253Sjoerg				"\t-n group       group name\n"
30120253Sjoerg				"\t-g gid         group id\n"
30220267Sjoerg				"\t-M usr1,usr2   add users as group members\n"
30320267Sjoerg				"\t-o             duplicate gid ok\n"
30421330Sdavidn				"\t-Y             update NIS maps\n"
30520267Sjoerg				"\t-N             no update\n",
30630259Scharnier				"usage: pw groupdel [group|gid] [switches]\n"
30720253Sjoerg				"\t-n name        group name\n"
30821330Sdavidn				"\t-g gid         group id\n"
30921330Sdavidn				"\t-Y             update NIS maps\n",
31030259Scharnier				"usage: pw groupmod [group|gid] [switches]\n"
31120253Sjoerg				"\t-C config      configuration file\n"
31220253Sjoerg				"\t-q             quiet operation\n"
31320253Sjoerg				"\t-F             force add if not exists\n"
31420253Sjoerg				"\t-n name        group name\n"
31520253Sjoerg				"\t-g gid         group id\n"
31620267Sjoerg				"\t-M usr1,usr2   replaces users as group members\n"
31720267Sjoerg				"\t-m usr1,usr2   add users as group members\n"
31820267Sjoerg				"\t-l name        new group name\n"
31921330Sdavidn				"\t-Y             update NIS maps\n"
32020267Sjoerg				"\t-N             no update\n",
32130259Scharnier				"usage: pw groupshow [group|gid] [switches]\n"
32220253Sjoerg				"\t-n name        group name\n"
32320253Sjoerg				"\t-g gid         group id\n"
32420253Sjoerg				"\t-F             force print\n"
32520267Sjoerg				"\t-P             prettier format\n"
32620267Sjoerg				"\t-a             print all accounting groups\n",
32730259Scharnier				"usage: pw groupnext [switches]\n"
32820267Sjoerg				"\t-C config      configuration file\n"
32920253Sjoerg			}
33020253Sjoerg		};
33120253Sjoerg
33230259Scharnier		fprintf(stderr, help[which][mode]);
33320253Sjoerg	}
33420267Sjoerg	exit(EXIT_FAILURE);
33520253Sjoerg}
33620253Sjoerg
33720253Sjoergstruct carg    *
33820253Sjoerggetarg(struct cargs * _args, int ch)
33920253Sjoerg{
34020253Sjoerg	struct carg    *c = _args->lh_first;
34120253Sjoerg
34220253Sjoerg	while (c != NULL && c->ch != ch)
34320253Sjoerg		c = c->list.le_next;
34420253Sjoerg	return c;
34520253Sjoerg}
34620253Sjoerg
34720253Sjoergstruct carg    *
34820253Sjoergaddarg(struct cargs * _args, int ch, char *argstr)
34920253Sjoerg{
35020253Sjoerg	struct carg    *ca = malloc(sizeof(struct carg));
35120253Sjoerg
35220253Sjoerg	if (ca == NULL)
35330259Scharnier		errx(EX_OSERR, "out of memory");
35420253Sjoerg	ca->ch = ch;
35520253Sjoerg	ca->val = argstr;
35620253Sjoerg	LIST_INSERT_HEAD(_args, ca, list);
35720253Sjoerg	return ca;
35820253Sjoerg}
359