pw_user.c revision 286202
1/*-
2 * Copyright (C) 1996
3 *	David L. Nugent.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28#ifndef lint
29static const char rcsid[] =
30  "$FreeBSD: head/usr.sbin/pw/pw_user.c 286202 2015-08-02 13:32:23Z bapt $";
31#endif /* not lint */
32
33#include <sys/param.h>
34#include <sys/resource.h>
35#include <sys/time.h>
36#include <sys/types.h>
37
38#include <ctype.h>
39#include <dirent.h>
40#include <err.h>
41#include <fcntl.h>
42#include <grp.h>
43#include <pwd.h>
44#include <libutil.h>
45#include <login_cap.h>
46#include <paths.h>
47#include <string.h>
48#include <sysexits.h>
49#include <termios.h>
50#include <unistd.h>
51
52#include "pw.h"
53#include "bitmap.h"
54#include "psdate.h"
55
56#define LOGNAMESIZE (MAXLOGNAME-1)
57
58static		char locked_str[] = "*LOCKED*";
59
60static struct passwd fakeuser = {
61	"nouser",
62	"*",
63	-1,
64	-1,
65	0,
66	"",
67	"User &",
68	"/nonexistent",
69	"/bin/sh",
70	0,
71	0
72};
73
74static int	 print_user(struct passwd *pwd, bool pretty, bool v7);
75static uid_t	 pw_uidpolicy(struct userconf *cnf, intmax_t id);
76static uid_t	 pw_gidpolicy(struct userconf *cnf, char *grname, char *nam,
77    gid_t prefer, bool dryrun);
78static char	*pw_homepolicy(struct userconf * cnf, char *homedir,
79    const char *user);
80static char	*pw_shellpolicy(struct userconf * cnf);
81static char	*pw_password(struct userconf * cnf, char const * user,
82    bool dryrun);
83static char	*shell_path(char const * path, char *shells[], char *sh);
84static void	rmat(uid_t uid);
85static void	rmopie(char const * name);
86
87static void
88create_and_populate_homedir(struct userconf *cnf, struct passwd *pwd,
89    const char *skeldir, mode_t homemode, bool update)
90{
91	int skelfd = -1;
92
93	if (skeldir != NULL && *skeldir != '\0') {
94		if (*skeldir == '/')
95			skeldir++;
96		skelfd = openat(conf.rootfd, skeldir, O_DIRECTORY|O_CLOEXEC);
97	}
98
99	copymkdir(conf.rootfd, pwd->pw_dir, skelfd, homemode, pwd->pw_uid,
100	    pwd->pw_gid, 0);
101	pw_log(cnf, update ? M_UPDATE : M_ADD, W_USER, "%s(%ju) home %s made",
102	    pwd->pw_name, (uintmax_t)pwd->pw_uid, pwd->pw_dir);
103}
104
105static int
106pw_set_passwd(struct passwd *pwd, int fd, bool precrypted, bool update)
107{
108	int		 b, istty;
109	struct termios	 t, n;
110	login_cap_t	*lc;
111	char		line[_PASSWORD_LEN+1];
112	char		*p;
113
114	if (fd == '-') {
115		if (!pwd->pw_passwd || *pwd->pw_passwd != '*') {
116			pwd->pw_passwd = "*";	/* No access */
117			return (1);
118		}
119		return (0);
120	}
121
122	if ((istty = isatty(fd))) {
123		if (tcgetattr(fd, &t) == -1)
124			istty = 0;
125		else {
126			n = t;
127			n.c_lflag &= ~(ECHO);
128			tcsetattr(fd, TCSANOW, &n);
129			printf("%s%spassword for user %s:",
130			    update ? "new " : "",
131			    precrypted ? "encrypted " : "",
132			    pwd->pw_name);
133			fflush(stdout);
134		}
135	}
136	b = read(fd, line, sizeof(line) - 1);
137	if (istty) {	/* Restore state */
138		tcsetattr(fd, TCSANOW, &t);
139		fputc('\n', stdout);
140		fflush(stdout);
141	}
142
143	if (b < 0)
144		err(EX_IOERR, "-%c file descriptor",
145		    precrypted ? 'H' : 'h');
146	line[b] = '\0';
147	if ((p = strpbrk(line, "\r\n")) != NULL)
148		*p = '\0';
149	if (!*line)
150		errx(EX_DATAERR, "empty password read on file descriptor %d",
151		    fd);
152	if (precrypted) {
153		if (strchr(line, ':') != NULL)
154			errx(EX_DATAERR, "bad encrypted password");
155		pwd->pw_passwd = strdup(line);
156	} else {
157		lc = login_getpwclass(pwd);
158		if (lc == NULL ||
159				login_setcryptfmt(lc, "sha512", NULL) == NULL)
160			warn("setting crypt(3) format");
161		login_close(lc);
162		pwd->pw_passwd = pw_pwcrypt(line);
163	}
164	return (1);
165}
166
167static void
168perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd)
169{
170	int rc;
171	struct passwd *nispwd;
172
173	/* duplicate for nis so that chgpwent is not modifying before NIS */
174	if (nispasswd && *nispasswd == '/')
175		nispwd = pw_dup(pwd);
176
177	rc = chgpwent(name, pwd);
178	if (rc == -1)
179		errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name);
180	else if (rc != 0)
181		err(EX_IOERR, "passwd file update");
182
183	if (nispasswd && *nispasswd == '/') {
184		rc = chgnispwent(nispasswd, name, nispwd);
185		if (rc == -1)
186			warn("User '%s' not found in NIS passwd", pwd->pw_name);
187		else if (rc != 0)
188			warn("NIS passwd update");
189		/* NOTE: NIS-only update errors are not fatal */
190	}
191}
192
193/*
194 * The M_LOCK and M_UNLOCK functions simply add or remove
195 * a "*LOCKED*" prefix from in front of the password to
196 * prevent it decoding correctly, and therefore prevents
197 * access. Of course, this only prevents access via
198 * password authentication (not ssh, kerberos or any
199 * other method that does not use the UNIX password) but
200 * that is a known limitation.
201 */
202static int
203pw_userlock(char *arg1, int mode)
204{
205	struct passwd *pwd = NULL;
206	char *passtmp = NULL;
207	char *name;
208	bool locked = false;
209	uid_t id;
210
211	if (geteuid() != 0)
212		errx(EX_NOPERM, "you must be root");
213
214	if (arg1 == NULL)
215		errx(EX_DATAERR, "username or id required");
216
217	if (strspn(arg1, "0123456789") == strlen(arg1))
218		id = pw_checkid(arg1, UID_MAX);
219	else
220		name = arg1;
221
222	pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
223	if (pwd == NULL) {
224		if (name == NULL)
225			errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id);
226		errx(EX_NOUSER, "no such user `%s'", name);
227	}
228
229	if (name == NULL)
230		name = pwd->pw_name;
231
232	if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str) -1) == 0)
233		locked = true;
234	if (mode == M_LOCK && locked)
235		errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name);
236	if (mode == M_UNLOCK && !locked)
237		errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name);
238
239	if (mode == M_LOCK) {
240		asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd);
241		if (passtmp == NULL)	/* disaster */
242			errx(EX_UNAVAILABLE, "out of memory");
243		pwd->pw_passwd = passtmp;
244	} else {
245		pwd->pw_passwd += sizeof(locked_str)-1;
246	}
247
248	perform_chgpwent(name, pwd, NULL);
249	free(passtmp);
250
251	return (EXIT_SUCCESS);
252}
253
254static uid_t
255pw_uidpolicy(struct userconf * cnf, intmax_t id)
256{
257	struct passwd  *pwd;
258	struct bitmap   bm;
259	uid_t           uid = (uid_t) - 1;
260
261	/*
262	 * Check the given uid, if any
263	 */
264	if (id >= 0) {
265		uid = (uid_t) id;
266
267		if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate)
268			errx(EX_DATAERR, "uid `%ju' has already been allocated",
269			    (uintmax_t)pwd->pw_uid);
270		return (uid);
271	}
272	/*
273	 * We need to allocate the next available uid under one of
274	 * two policies a) Grab the first unused uid b) Grab the
275	 * highest possible unused uid
276	 */
277	if (cnf->min_uid >= cnf->max_uid) {	/* Sanity
278						 * claus^H^H^H^Hheck */
279		cnf->min_uid = 1000;
280		cnf->max_uid = 32000;
281	}
282	bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1);
283
284	/*
285	 * Now, let's fill the bitmap from the password file
286	 */
287	SETPWENT();
288	while ((pwd = GETPWENT()) != NULL)
289		if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid)
290			bm_setbit(&bm, pwd->pw_uid - cnf->min_uid);
291	ENDPWENT();
292
293	/*
294	 * Then apply the policy, with fallback to reuse if necessary
295	 */
296	if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid)
297		uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid);
298
299	/*
300	 * Another sanity check
301	 */
302	if (uid < cnf->min_uid || uid > cnf->max_uid)
303		errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used");
304	bm_dealloc(&bm);
305	return (uid);
306}
307
308static uid_t
309pw_gidpolicy(struct userconf *cnf, char *grname, char *nam, gid_t prefer, bool dryrun)
310{
311	struct group   *grp;
312	gid_t           gid = (uid_t) - 1;
313
314	/*
315	 * Check the given gid, if any
316	 */
317	SETGRENT();
318	if (grname) {
319		if ((grp = GETGRNAM(grname)) == NULL) {
320			gid = pw_checkid(grname, GID_MAX);
321			grp = GETGRGID(gid);
322		}
323		gid = grp->gr_gid;
324	} else if ((grp = GETGRNAM(nam)) != NULL &&
325	    (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) {
326		gid = grp->gr_gid;  /* Already created? Use it anyway... */
327	} else {
328		intmax_t		grid = -1;
329
330		/*
331		 * We need to auto-create a group with the user's name. We
332		 * can send all the appropriate output to our sister routine
333		 * bit first see if we can create a group with gid==uid so we
334		 * can keep the user and group ids in sync. We purposely do
335		 * NOT check the gid range if we can force the sync. If the
336		 * user's name dups an existing group, then the group add
337		 * function will happily handle that case for us and exit.
338		 */
339		if (GETGRGID(prefer) == NULL)
340			grid = prefer;
341		if (dryrun) {
342			gid = pw_groupnext(cnf, true);
343		} else {
344			if (grid == -1)
345				grid =  pw_groupnext(cnf, true);
346			groupadd(cnf, nam, grid, NULL, -1, false, false, false);
347			if ((grp = GETGRNAM(nam)) != NULL)
348				gid = grp->gr_gid;
349		}
350	}
351	ENDGRENT();
352	return (gid);
353}
354
355static char *
356pw_homepolicy(struct userconf * cnf, char *homedir, const char *user)
357{
358	static char     home[128];
359
360	if (homedir)
361		return (homedir);
362
363	if (cnf->home == NULL || *cnf->home == '\0')
364		errx(EX_CONFIG, "no base home directory set");
365	snprintf(home, sizeof(home), "%s/%s", cnf->home, user);
366
367	return (home);
368}
369
370static char *
371shell_path(char const * path, char *shells[], char *sh)
372{
373	if (sh != NULL && (*sh == '/' || *sh == '\0'))
374		return sh;	/* specified full path or forced none */
375	else {
376		char           *p;
377		char            paths[_UC_MAXLINE];
378
379		/*
380		 * We need to search paths
381		 */
382		strlcpy(paths, path, sizeof(paths));
383		for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) {
384			int             i;
385			static char     shellpath[256];
386
387			if (sh != NULL) {
388				snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh);
389				if (access(shellpath, X_OK) == 0)
390					return shellpath;
391			} else
392				for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) {
393					snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]);
394					if (access(shellpath, X_OK) == 0)
395						return shellpath;
396				}
397		}
398		if (sh == NULL)
399			errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh);
400		errx(EX_CONFIG, "no default shell available or defined");
401		return NULL;
402	}
403}
404
405static char *
406pw_shellpolicy(struct userconf * cnf)
407{
408
409	return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default);
410}
411
412#define	SALTSIZE	32
413
414static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
415
416char *
417pw_pwcrypt(char *password)
418{
419	int             i;
420	char            salt[SALTSIZE + 1];
421	char		*cryptpw;
422	static char     buf[256];
423
424	/*
425	 * Calculate a salt value
426	 */
427	for (i = 0; i < SALTSIZE; i++)
428		salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)];
429	salt[SALTSIZE] = '\0';
430
431	cryptpw = crypt(password, salt);
432	if (cryptpw == NULL)
433		errx(EX_CONFIG, "crypt(3) failure");
434	return strcpy(buf, cryptpw);
435}
436
437static char *
438pw_password(struct userconf * cnf, char const * user, bool dryrun)
439{
440	int             i, l;
441	char            pwbuf[32];
442
443	switch (cnf->default_password) {
444	case -1:		/* Random password */
445		l = (arc4random() % 8 + 8);	/* 8 - 16 chars */
446		for (i = 0; i < l; i++)
447			pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)];
448		pwbuf[i] = '\0';
449
450		/*
451		 * We give this information back to the user
452		 */
453		if (conf.fd == -1 && !dryrun) {
454			if (isatty(STDOUT_FILENO))
455				printf("Password for '%s' is: ", user);
456			printf("%s\n", pwbuf);
457			fflush(stdout);
458		}
459		break;
460
461	case -2:		/* No password at all! */
462		return "";
463
464	case 0:		/* No login - default */
465	default:
466		return "*";
467
468	case 1:		/* user's name */
469		strlcpy(pwbuf, user, sizeof(pwbuf));
470		break;
471	}
472	return pw_pwcrypt(pwbuf);
473}
474
475static int
476print_user(struct passwd * pwd, bool pretty, bool v7)
477{
478	int		j;
479	char           *p;
480	struct group   *grp = GETGRGID(pwd->pw_gid);
481	char            uname[60] = "User &", office[60] = "[None]",
482			wphone[60] = "[None]", hphone[60] = "[None]";
483	char		acexpire[32] = "[None]", pwexpire[32] = "[None]";
484	struct tm *    tptr;
485
486	if (!pretty) {
487		p = v7 ? pw_make_v7(pwd) : pw_make(pwd);
488		printf("%s\n", p);
489		free(p);
490		return (EXIT_SUCCESS);
491	}
492
493	if ((p = strtok(pwd->pw_gecos, ",")) != NULL) {
494		strlcpy(uname, p, sizeof(uname));
495		if ((p = strtok(NULL, ",")) != NULL) {
496			strlcpy(office, p, sizeof(office));
497			if ((p = strtok(NULL, ",")) != NULL) {
498				strlcpy(wphone, p, sizeof(wphone));
499				if ((p = strtok(NULL, "")) != NULL) {
500					strlcpy(hphone, p, sizeof(hphone));
501				}
502			}
503		}
504	}
505	/*
506	 * Handle '&' in gecos field
507	 */
508	if ((p = strchr(uname, '&')) != NULL) {
509		int             l = strlen(pwd->pw_name);
510		int             m = strlen(p);
511
512		memmove(p + l, p + 1, m);
513		memmove(p, pwd->pw_name, l);
514		*p = (char) toupper((unsigned char)*p);
515	}
516	if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL)
517		strftime(acexpire, sizeof acexpire, "%c", tptr);
518		if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL)
519		strftime(pwexpire, sizeof pwexpire, "%c", tptr);
520	printf("Login Name: %-15s   #%-12ju Group: %-15s   #%ju\n"
521	       " Full Name: %s\n"
522	       "      Home: %-26.26s      Class: %s\n"
523	       "     Shell: %-26.26s     Office: %s\n"
524	       "Work Phone: %-26.26s Home Phone: %s\n"
525	       "Acc Expire: %-26.26s Pwd Expire: %s\n",
526	       pwd->pw_name, (uintmax_t)pwd->pw_uid,
527	       grp ? grp->gr_name : "(invalid)", (uintmax_t)pwd->pw_gid,
528	       uname, pwd->pw_dir, pwd->pw_class,
529	       pwd->pw_shell, office, wphone, hphone,
530	       acexpire, pwexpire);
531        SETGRENT();
532	j = 0;
533	while ((grp=GETGRENT()) != NULL) {
534		int     i = 0;
535		if (grp->gr_mem != NULL) {
536			while (grp->gr_mem[i] != NULL) {
537				if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) {
538					printf(j++ == 0 ? "    Groups: %s" : ",%s", grp->gr_name);
539					break;
540				}
541				++i;
542			}
543		}
544	}
545	ENDGRENT();
546	printf("%s", j ? "\n" : "");
547	return (EXIT_SUCCESS);
548}
549
550char *
551pw_checkname(char *name, int gecos)
552{
553	char showch[8];
554	const char *badchars, *ch, *showtype;
555	int reject;
556
557	ch = name;
558	reject = 0;
559	if (gecos) {
560		/* See if the name is valid as a gecos (comment) field. */
561		badchars = ":!@";
562		showtype = "gecos field";
563	} else {
564		/* See if the name is valid as a userid or group. */
565		badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\"";
566		showtype = "userid/group name";
567		/* Userids and groups can not have a leading '-'. */
568		if (*ch == '-')
569			reject = 1;
570	}
571	if (!reject) {
572		while (*ch) {
573			if (strchr(badchars, *ch) != NULL || *ch < ' ' ||
574			    *ch == 127) {
575				reject = 1;
576				break;
577			}
578			/* 8-bit characters are only allowed in GECOS fields */
579			if (!gecos && (*ch & 0x80)) {
580				reject = 1;
581				break;
582			}
583			ch++;
584		}
585	}
586	/*
587	 * A `$' is allowed as the final character for userids and groups,
588	 * mainly for the benefit of samba.
589	 */
590	if (reject && !gecos) {
591		if (*ch == '$' && *(ch + 1) == '\0') {
592			reject = 0;
593			ch++;
594		}
595	}
596	if (reject) {
597		snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127)
598		    ? "`%c'" : "0x%02x", *ch);
599		errx(EX_DATAERR, "invalid character %s at position %td in %s",
600		    showch, (ch - name), showtype);
601	}
602	if (!gecos && (ch - name) > LOGNAMESIZE)
603		errx(EX_USAGE, "name too long `%s' (max is %d)", name,
604		    LOGNAMESIZE);
605
606	return (name);
607}
608
609static void
610rmat(uid_t uid)
611{
612	DIR            *d = opendir("/var/at/jobs");
613
614	if (d != NULL) {
615		struct dirent  *e;
616
617		while ((e = readdir(d)) != NULL) {
618			struct stat     st;
619
620			if (strncmp(e->d_name, ".lock", 5) != 0 &&
621			    stat(e->d_name, &st) == 0 &&
622			    !S_ISDIR(st.st_mode) &&
623			    st.st_uid == uid) {
624				char            tmp[MAXPATHLEN];
625
626				snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s",
627				    e->d_name);
628				system(tmp);
629			}
630		}
631		closedir(d);
632	}
633}
634
635static void
636rmopie(char const * name)
637{
638	char tmp[1014];
639	FILE *fp;
640	int fd;
641	size_t len;
642	off_t	atofs = 0;
643
644	if ((fd = openat(conf.rootfd, "etc/opiekeys", O_RDWR)) == -1)
645		return;
646
647	fp = fdopen(fd, "r+");
648	len = strlen(name);
649
650	while (fgets(tmp, sizeof(tmp), fp) != NULL) {
651		if (strncmp(name, tmp, len) == 0 && tmp[len]==' ') {
652			/* Comment username out */
653			if (fseek(fp, atofs, SEEK_SET) == 0)
654				fwrite("#", 1, 1, fp);
655			break;
656		}
657		atofs = ftell(fp);
658	}
659	/*
660	 * If we got an error of any sort, don't update!
661	 */
662	fclose(fp);
663}
664
665int
666pw_user_next(int argc, char **argv, char *name __unused)
667{
668	struct userconf *cnf = NULL;
669	const char *cfg = NULL;
670	int ch;
671	bool quiet = false;
672	uid_t next;
673
674	while ((ch = getopt(argc, argv, "Cq")) != -1) {
675		switch (ch) {
676		case 'C':
677			cfg = optarg;
678			break;
679		case 'q':
680			quiet;
681			break;
682		}
683	}
684
685	if (quiet)
686		freopen(_PATH_DEVNULL, "w", stderr);
687
688	cnf = get_userconfig(cfg);
689
690	next = pw_uidpolicy(cnf, -1);
691
692	printf("%ju:", (uintmax_t)next);
693	pw_groupnext(cnf, quiet);
694
695	return (EXIT_SUCCESS);
696}
697
698int
699pw_user_show(int argc, char **argv, char *arg1)
700{
701	struct passwd *pwd = NULL;
702	char *name = NULL;
703	uid_t id = -1;
704	int ch;
705	bool all = false;
706	bool pretty = false;
707	bool force = false;
708	bool v7 = false;
709	bool quiet = false;
710
711	if (arg1 != NULL) {
712		if (strspn(arg1, "0123456789") == strlen(arg1))
713			id = pw_checkid(arg1, UID_MAX);
714		else
715			name = arg1;
716	}
717
718	while ((ch = getopt(argc, argv, "C:qn:u:FPa7")) != -1) {
719		switch (ch) {
720		case 'C':
721			/* ignore compatibility */
722			break;
723		case 'q':
724			quiet = true;
725			break;
726		case 'n':
727			name = optarg;
728			break;
729		case 'u':
730			id = pw_checkid(optarg, UID_MAX);
731			break;
732		case 'F':
733			force = true;
734			break;
735		case 'P':
736			pretty = true;
737			break;
738		case 'a':
739			all = true;
740			break;
741		case 7:
742			v7 = true;
743			break;
744		}
745	}
746
747	if (quiet)
748		freopen(_PATH_DEVNULL, "w", stderr);
749
750	if (all) {
751		SETPWENT();
752		while ((pwd = GETPWENT()) != NULL)
753			print_user(pwd, pretty, v7);
754		ENDPWENT();
755		return (EXIT_SUCCESS);
756	}
757
758	if (id < 0 && name == NULL)
759		errx(EX_DATAERR, "username or id required");
760
761	pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
762	if (pwd == NULL) {
763		if (force) {
764			pwd = &fakeuser;
765		} else {
766			if (name == NULL)
767				errx(EX_NOUSER, "no such uid `%ju'",
768				    (uintmax_t) id);
769			errx(EX_NOUSER, "no such user `%s'", name);
770		}
771	}
772
773	return (print_user(pwd, pretty, v7));
774}
775
776int
777pw_user_del(int argc, char **argv, char *arg1)
778{
779	struct userconf *cnf = NULL;
780	struct passwd *pwd = NULL;
781	struct group *gr, *grp;
782	char *name = NULL;
783	char grname[MAXLOGNAME];
784	char *nispasswd = NULL;
785	char file[MAXPATHLEN];
786	char home[MAXPATHLEN];
787	const char *cfg = NULL;
788	struct stat st;
789	uid_t id;
790	int ch, rc;
791	bool nis = false;
792	bool deletehome = false;
793	bool quiet = false;
794
795	if (arg1 != NULL) {
796		if (strspn(arg1, "0123456789") == strlen(arg1))
797			id = pw_checkid(arg1, UID_MAX);
798		else
799			name = arg1;
800	}
801
802	while ((ch = getopt(argc, argv, "C:qn:u:rYy:")) != -1) {
803		switch (ch) {
804		case 'C':
805			cfg = optarg;
806			break;
807		case 'q':
808			quiet = true;
809			break;
810		case 'n':
811			name = optarg;
812			break;
813		case 'u':
814			id = pw_checkid(optarg, UID_MAX);
815			break;
816		case 'r':
817			deletehome = true;
818			break;
819		case 'y':
820			nispasswd = optarg;
821			break;
822		case 'Y':
823			nis = true;
824			break;
825		}
826	}
827
828	if (quiet)
829		freopen(_PATH_DEVNULL, "w", stderr);
830
831	if (id < 0 && name == NULL)
832		errx(EX_DATAERR, "username or id required");
833
834	cnf = get_userconfig(cfg);
835
836	if (nispasswd == NULL)
837		nispasswd = cnf->nispasswd;
838
839	pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
840	if (pwd == NULL) {
841		if (name == NULL)
842			errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id);
843		errx(EX_NOUSER, "no such user `%s'", name);
844	}
845
846	if (PWF._altdir == PWF_REGULAR &&
847	    ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) {
848		if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
849			if (!nis && nispasswd && *nispasswd != '/')
850				errx(EX_NOUSER, "Cannot remove NIS user `%s'",
851				    name);
852		} else {
853			errx(EX_NOUSER, "Cannot remove non local user `%s'",
854			    name);
855		}
856	}
857
858	id = pwd->pw_uid;
859	if (name == NULL)
860		name = pwd->pw_name;
861
862	if (strcmp(pwd->pw_name, "root") == 0)
863		errx(EX_DATAERR, "cannot remove user 'root'");
864
865	/* Remove opie record from /etc/opiekeys */
866	if (PWALTDIR() != PWF_ALT)
867		rmopie(pwd->pw_name);
868
869	if (!PWALTDIR()) {
870		/* Remove crontabs */
871		snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name);
872		if (access(file, F_OK) == 0) {
873			snprintf(file, sizeof(file), "crontab -u %s -r",
874			    pwd->pw_name);
875			system(file);
876		}
877	}
878
879	/*
880	 * Save these for later, since contents of pwd may be
881	 * invalidated by deletion
882	 */
883	snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
884	strlcpy(home, pwd->pw_dir, sizeof(home));
885	gr = GETGRGID(pwd->pw_gid);
886	if (gr != NULL)
887		strlcpy(grname, gr->gr_name, LOGNAMESIZE);
888	else
889		grname[0] = '\0';
890
891	rc = delpwent(pwd);
892	if (rc == -1)
893		err(EX_IOERR, "user '%s' does not exist", pwd->pw_name);
894	else if (rc != 0)
895		err(EX_IOERR, "passwd update");
896
897	if (nis && nispasswd && *nispasswd=='/') {
898		rc = delnispwent(nispasswd, name);
899		if (rc == -1)
900			warnx("WARNING: user '%s' does not exist in NIS passwd",
901			    pwd->pw_name);
902		else if (rc != 0)
903			warn("WARNING: NIS passwd update");
904	}
905
906	grp = GETGRNAM(name);
907	if (grp != NULL &&
908	    (grp->gr_mem == NULL || *grp->gr_mem == NULL) &&
909	    strcmp(name, grname) == 0)
910		delgrent(GETGRNAM(name));
911	SETGRENT();
912	while ((grp = GETGRENT()) != NULL) {
913		int i, j;
914		char group[MAXLOGNAME];
915		if (grp->gr_mem == NULL)
916			continue;
917
918		for (i = 0; grp->gr_mem[i] != NULL; i++) {
919			if (strcmp(grp->gr_mem[i], name) != 0)
920				continue;
921
922			for (j = i; grp->gr_mem[j] != NULL; j++)
923				grp->gr_mem[j] = grp->gr_mem[j+1];
924			strlcpy(group, grp->gr_name, MAXLOGNAME);
925			chggrent(group, grp);
926		}
927	}
928	ENDGRENT();
929
930	pw_log(cnf, M_DELETE, W_USER, "%s(%ju) account removed", name,
931	    (uintmax_t)id);
932
933	/* Remove mail file */
934	if (PWALTDIR() != PWF_ALT)
935		unlinkat(conf.rootfd, file + 1, 0);
936
937	/* Remove at jobs */
938	if (!PWALTDIR() && getpwuid(id) == NULL)
939		rmat(id);
940
941	/* Remove home directory and contents */
942	if (PWALTDIR() != PWF_ALT && deletehome && *home == '/' &&
943	    GETPWUID(id) == NULL &&
944	    fstatat(conf.rootfd, home + 1, &st, 0) != -1) {
945		rm_r(conf.rootfd, home, id);
946		pw_log(cnf, M_DELETE, W_USER, "%s(%ju) home '%s' %s"
947		    "removed", name, (uintmax_t)id, home,
948		     fstatat(conf.rootfd, home + 1, &st, 0) == -1 ? "" : "not "
949		     "completely ");
950	}
951
952	return (EXIT_SUCCESS);
953}
954
955int
956pw_user_lock(int argc, char **argv, char *arg1)
957{
958	int ch;
959
960	while ((ch = getopt(argc, argv, "Cq")) != -1) {
961		switch (ch) {
962		case 'C':
963		case 'q':
964			/* compatibility */
965			break;
966		}
967	}
968
969	return (pw_userlock(arg1, M_LOCK));
970}
971
972int
973pw_user_unlock(int argc, char **argv, char *arg1)
974{
975	int ch;
976
977	while ((ch = getopt(argc, argv, "Cq")) != -1) {
978		switch (ch) {
979		case 'C':
980		case 'q':
981			/* compatibility */
982			break;
983		}
984	}
985
986	return (pw_userlock(arg1, M_UNLOCK));
987}
988
989static struct group *
990group_from_name_or_id(char *name)
991{
992	const char *errstr = NULL;
993	struct group *grp;
994	uintmax_t id;
995
996	if ((grp = GETGRNAM(name)) == NULL) {
997		id = strtounum(name, 0, GID_MAX, &errstr);
998		if (errstr)
999			errx(EX_NOUSER, "group `%s' does not exist", name);
1000		grp = GETGRGID(id);
1001		if (grp == NULL)
1002			errx(EX_NOUSER, "group `%s' does not exist", name);
1003	}
1004
1005	return (grp);
1006}
1007
1008static void
1009split_groups(StringList **groups, char *groupsstr)
1010{
1011	struct group *grp;
1012	char *p;
1013	char tok[] = ", \t";
1014
1015	for (p = strtok(groupsstr, tok); p != NULL; p = strtok(NULL, tok)) {
1016		grp = group_from_name_or_id(p);
1017		if (*groups == NULL)
1018			*groups = sl_init();
1019		sl_add(*groups, newstr(grp->gr_name));
1020	}
1021}
1022
1023static void
1024validate_grname(struct userconf *cnf, char *group)
1025{
1026	struct group *grp;
1027
1028	if (group == NULL || *group == '\0') {
1029		cnf->default_group = "";
1030		return;
1031	}
1032	grp = group_from_name_or_id(group);
1033	cnf->default_group = newstr(grp->gr_name);
1034}
1035
1036static mode_t
1037validate_mode(char *mode)
1038{
1039	mode_t m;
1040	void *set;
1041
1042	if ((set = setmode(mode)) == NULL)
1043		errx(EX_DATAERR, "invalid directory creation mode '%s'", mode);
1044
1045	m = getmode(set, _DEF_DIRMODE);
1046	free(set);
1047	return (m);
1048}
1049
1050static void
1051mix_config(struct userconf *cmdcnf, struct userconf *cfg)
1052{
1053
1054	if (cmdcnf->default_password == 0)
1055		cmdcnf->default_password = cfg->default_password;
1056	if (cmdcnf->reuse_uids == 0)
1057		cmdcnf->reuse_uids = cfg->reuse_uids;
1058	if (cmdcnf->reuse_gids == 0)
1059		cmdcnf->reuse_gids = cfg->reuse_gids;
1060	if (cmdcnf->nispasswd == NULL)
1061		cmdcnf->nispasswd = cfg->nispasswd;
1062	if (cmdcnf->dotdir == NULL)
1063		cmdcnf->dotdir = cfg->dotdir;
1064	if (cmdcnf->newmail == NULL)
1065		cmdcnf->newmail = cfg->newmail;
1066	if (cmdcnf->logfile == NULL)
1067		cmdcnf->logfile = cfg->logfile;
1068	if (cmdcnf->home == NULL)
1069		cmdcnf->home = cfg->home;
1070	if (cmdcnf->homemode == 0)
1071		cmdcnf->homemode = cfg->homemode;
1072	if (cmdcnf->shelldir == NULL)
1073		cmdcnf->shelldir = cfg->shelldir;
1074	if (cmdcnf->shells == NULL)
1075		cmdcnf->shells = cfg->shells;
1076	if (cmdcnf->shell_default == NULL)
1077		cmdcnf->shell_default = cfg->shell_default;
1078	if (cmdcnf->default_group == NULL)
1079		cmdcnf->default_group = cfg->default_group;
1080	if (cmdcnf->groups == NULL)
1081		cmdcnf->groups = cfg->groups;
1082	if (cmdcnf->default_class == NULL)
1083		cmdcnf->default_class = cfg->default_class;
1084	if (cmdcnf->min_uid == 0)
1085		cmdcnf->min_uid = cfg->min_uid;
1086	if (cmdcnf->max_uid == 0)
1087		cmdcnf->max_uid = cfg->max_uid;
1088	if (cmdcnf->min_gid == 0)
1089		cmdcnf->min_gid = cfg->min_gid;
1090	if (cmdcnf->max_gid == 0)
1091		cmdcnf->max_gid = cfg->max_gid;
1092	if (cmdcnf->expire_days == 0)
1093		cmdcnf->expire_days = cfg->expire_days;
1094	if (cmdcnf->password_days == 0)
1095		cmdcnf->password_days = cfg->password_days;
1096}
1097
1098int
1099pw_user_add(int argc, char **argv, char *arg1)
1100{
1101	struct userconf *cnf, *cmdcnf;
1102	struct passwd *pwd;
1103	struct group *grp;
1104	struct stat st;
1105	char args[] = "C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y";
1106	char line[_PASSWORD_LEN+1], path[MAXPATHLEN];
1107	char *gecos, *homedir, *skel, *walk, *userid, *groupid, *grname;
1108	char *default_passwd, *name, *p;
1109	const char *cfg;
1110	login_cap_t *lc;
1111	FILE *pfp, *fp;
1112	intmax_t id = -1;
1113	time_t now;
1114	int rc, ch, fd = -1;
1115	size_t i;
1116	bool dryrun, nis, pretty, quiet, createhome, precrypted, genconf;
1117
1118	dryrun = nis = pretty = quiet = createhome = precrypted = false;
1119	genconf = false;
1120	gecos = homedir = skel = userid = groupid = default_passwd = NULL;
1121	grname = name = NULL;
1122
1123	if ((cmdcnf = calloc(1, sizeof(struct userconf))) == NULL)
1124		err(EXIT_FAILURE, "calloc()");
1125
1126	if (arg1 != NULL) {
1127		if (strspn(arg1, "0123456789") == strlen(arg1))
1128			id = pw_checkid(arg1, UID_MAX);
1129		else
1130			name = arg1;
1131	}
1132
1133	while ((ch = getopt(argc, argv, args)) != -1) {
1134		switch (ch) {
1135		case 'C':
1136			cfg = optarg;
1137			break;
1138		case 'q':
1139			quiet = true;
1140			break;
1141		case 'n':
1142			name = optarg;
1143			break;
1144		case 'u':
1145			userid = optarg;
1146			break;
1147		case 'c':
1148			gecos = pw_checkname(optarg, 1);
1149			break;
1150		case 'd':
1151			homedir = optarg;
1152			break;
1153		case 'e':
1154			now = time(NULL);
1155			cmdcnf->expire_days = parse_date(now, optarg);
1156			break;
1157		case 'p':
1158			now = time(NULL);
1159			cmdcnf->password_days = parse_date(now, optarg);
1160			break;
1161		case 'g':
1162			validate_grname(cmdcnf, optarg);
1163			grname = optarg;
1164			break;
1165		case 'G':
1166			split_groups(&cmdcnf->groups, optarg);
1167			break;
1168		case 'm':
1169			createhome = true;
1170			break;
1171		case 'M':
1172			cmdcnf->homemode = validate_mode(optarg);
1173			break;
1174		case 'k':
1175			walk = skel = optarg;
1176			if (*walk == '/')
1177				walk++;
1178			if (fstatat(conf.rootfd, walk, &st, 0) == -1)
1179				errx(EX_OSFILE, "skeleton `%s' does not "
1180				    "exists", skel);
1181			if (!S_ISDIR(st.st_mode))
1182				errx(EX_OSFILE, "skeleton `%s' is not a "
1183				    "directory", skel);
1184			cmdcnf->dotdir = skel;
1185			break;
1186		case 's':
1187			cmdcnf->shell_default = optarg;
1188			break;
1189		case 'o':
1190			conf.checkduplicate = false;
1191			break;
1192		case 'L':
1193			cmdcnf->default_class = pw_checkname(optarg, 0);
1194			break;
1195		case 'i':
1196			groupid = optarg;
1197			break;
1198		case 'w':
1199			default_passwd = optarg;
1200			break;
1201		case 'H':
1202			if (fd != -1)
1203				errx(EX_USAGE, "'-h' and '-H' are mutually "
1204				    "exclusive options");
1205			fd = pw_checkfd(optarg);
1206			precrypted = true;
1207			if (fd == '-')
1208				errx(EX_USAGE, "-H expects a file descriptor");
1209			break;
1210		case 'h':
1211			if (fd != -1)
1212				errx(EX_USAGE, "'-h' and '-H' are mutually "
1213				    "exclusive options");
1214			fd = pw_checkfd(optarg);
1215			break;
1216		case 'D':
1217			genconf = true;
1218			break;
1219		case 'b':
1220			cmdcnf->home = optarg;
1221			break;
1222		case 'N':
1223			dryrun = true;
1224			break;
1225		case 'P':
1226			pretty = true;
1227			break;
1228		case 'y':
1229			cmdcnf->nispasswd = optarg;
1230			break;
1231		case 'Y':
1232			nis = true;
1233			break;
1234		}
1235	}
1236
1237	if (geteuid() != 0 && ! dryrun)
1238		errx(EX_NOPERM, "you must be root");
1239
1240	if (quiet)
1241		freopen(_PATH_DEVNULL, "w", stderr);
1242
1243	cnf = get_userconfig(cfg);
1244
1245	mix_config(cmdcnf, cnf);
1246	if (default_passwd)
1247		cmdcnf->default_password = boolean_val(default_passwd,
1248		    cnf->default_password);
1249	if (genconf) {
1250		if (name != NULL)
1251			errx(EX_DATAERR, "can't combine `-D' with `-n name'");
1252		if (userid != NULL) {
1253			if ((p = strtok(userid, ", \t")) != NULL)
1254				cmdcnf->min_uid = pw_checkid(p, UID_MAX);
1255			if (cmdcnf->min_uid == 0)
1256				cmdcnf->min_uid = 1000;
1257			if ((p = strtok(NULL, " ,\t")) != NULL)
1258				cmdcnf->max_uid = pw_checkid(p, UID_MAX);
1259			if (cmdcnf->max_uid == 0)
1260				cmdcnf->max_uid = 32000;
1261		}
1262		if (groupid != NULL) {
1263			if ((p = strtok(groupid, ", \t")) != NULL)
1264				cmdcnf->min_gid = pw_checkid(p, GID_MAX);
1265			if (cmdcnf->min_gid == 0)
1266				cmdcnf->min_gid = 1000;
1267			if ((p = strtok(NULL, " ,\t")) != NULL)
1268				cmdcnf->max_gid = pw_checkid(p, GID_MAX);
1269			if (cmdcnf->max_gid == 0)
1270				cmdcnf->max_gid = 32000;
1271		}
1272		if (write_userconfig(cmdcnf, cfg))
1273			return (EXIT_SUCCESS);
1274		err(EX_IOERR, "config update");
1275	}
1276
1277	if (userid)
1278		id = pw_checkid(userid, UID_MAX);
1279	if (id < 0 && name == NULL)
1280		errx(EX_DATAERR, "user name or id required");
1281
1282	if (name == NULL)
1283		errx(EX_DATAERR, "login name required");
1284
1285	if (GETPWNAM(name) != NULL)
1286		errx(EX_DATAERR, "login name `%s' already exists", name);
1287
1288	pwd = &fakeuser;
1289	pwd->pw_name = name;
1290	pwd->pw_class = cmdcnf->default_class ? cmdcnf->default_class : "";
1291	pwd->pw_uid = pw_uidpolicy(cmdcnf, id);
1292	pwd->pw_gid = pw_gidpolicy(cnf, grname, pwd->pw_name,
1293	    (gid_t) pwd->pw_uid, dryrun);
1294	pwd->pw_change = cmdcnf->password_days;
1295	pwd->pw_expire = cmdcnf->expire_days;
1296	pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name);
1297	pwd->pw_shell = pw_shellpolicy(cmdcnf);
1298	lc = login_getpwclass(pwd);
1299	if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL)
1300		warn("setting crypt(3) format");
1301	login_close(lc);
1302	pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, dryrun);
1303	if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
1304		warnx("WARNING: new account `%s' has a uid of 0 "
1305		    "(superuser access!)", pwd->pw_name);
1306	if (gecos)
1307		pwd->pw_gecos = gecos;
1308
1309	if (fd != -1)
1310		pw_set_passwd(pwd, fd, precrypted, false);
1311
1312	if (dryrun)
1313		return (print_user(pwd, pretty, false));
1314
1315	if ((rc = addpwent(pwd)) != 0) {
1316		if (rc == -1)
1317			errx(EX_IOERR, "user '%s' already exists",
1318			    pwd->pw_name);
1319		else if (rc != 0)
1320			err(EX_IOERR, "passwd file update");
1321	}
1322	if (nis && cmdcnf->nispasswd && *cmdcnf->nispasswd == '/') {
1323		printf("%s\n", cmdcnf->nispasswd);
1324		rc = addnispwent(cmdcnf->nispasswd, pwd);
1325		if (rc == -1)
1326			warnx("User '%s' already exists in NIS passwd",
1327			    pwd->pw_name);
1328		else if (rc != 0)
1329			warn("NIS passwd update");
1330		/* NOTE: we treat NIS-only update errors as non-fatal */
1331	}
1332
1333	if (cmdcnf->groups != NULL) {
1334		for (i = 0; i < cmdcnf->groups->sl_cur; i++) {
1335			grp = GETGRNAM(cmdcnf->groups->sl_str[i]);
1336			grp = gr_add(grp, pwd->pw_name);
1337			/*
1338			 * grp can only be NULL in 2 cases:
1339			 * - the new member is already a member
1340			 * - a problem with memory occurs
1341			 * in both cases we want to skip now.
1342			 */
1343			if (grp == NULL)
1344				continue;
1345			chggrent(grp->gr_name, grp);
1346			free(grp);
1347		}
1348	}
1349
1350	pwd = GETPWNAM(name);
1351	if (pwd == NULL)
1352		errx(EX_NOUSER, "user '%s' disappeared during update", name);
1353
1354	grp = GETGRGID(pwd->pw_gid);
1355	pw_log(cnf, M_ADD, W_USER, "%s(%ju):%s(%ju):%s:%s:%s",
1356	       pwd->pw_name, (uintmax_t)pwd->pw_uid,
1357	    grp ? grp->gr_name : "unknown",
1358	       (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1),
1359	       pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
1360
1361	/*
1362	 * let's touch and chown the user's mail file. This is not
1363	 * strictly necessary under BSD with a 0755 maildir but it also
1364	 * doesn't hurt anything to create the empty mailfile
1365	 */
1366	if (PWALTDIR() != PWF_ALT) {
1367		snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR,
1368		    pwd->pw_name);
1369		/* Preserve contents & mtime */
1370		close(openat(conf.rootfd, path +1, O_RDWR | O_CREAT, 0600));
1371		fchownat(conf.rootfd, path + 1, pwd->pw_uid, pwd->pw_gid,
1372		    AT_SYMLINK_NOFOLLOW);
1373	}
1374
1375	/*
1376	 * Let's create and populate the user's home directory. Note
1377	 * that this also `works' for editing users if -m is used, but
1378	 * existing files will *not* be overwritten.
1379	 */
1380	if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir &&
1381	    *pwd->pw_dir == '/' && pwd->pw_dir[1])
1382		create_and_populate_homedir(cmdcnf, pwd, cmdcnf->dotdir,
1383		    cmdcnf->homemode, false);
1384
1385	if (!PWALTDIR() && cmdcnf->newmail && *cmdcnf->newmail &&
1386	    (fp = fopen(cnf->newmail, "r")) != NULL) {
1387		if ((pfp = popen(_PATH_SENDMAIL " -t", "w")) == NULL)
1388			warn("sendmail");
1389		else {
1390			fprintf(pfp, "From: root\n" "To: %s\n"
1391			    "Subject: Welcome!\n\n", pwd->pw_name);
1392			while (fgets(line, sizeof(line), fp) != NULL) {
1393				/* Do substitutions? */
1394				fputs(line, pfp);
1395			}
1396			pclose(pfp);
1397			pw_log(cnf, M_ADD, W_USER, "%s(%ju) new user mail sent",
1398			    pwd->pw_name, (uintmax_t)pwd->pw_uid);
1399		}
1400		fclose(fp);
1401	}
1402
1403	if (nis && nis_update() == 0)
1404		pw_log(cnf, M_ADD, W_USER, "NIS maps updated");
1405
1406	return (EXIT_SUCCESS);
1407}
1408
1409int
1410pw_user_mod(int argc, char **argv, char *arg1)
1411{
1412	struct userconf *cnf;
1413	struct passwd *pwd;
1414	struct group *grp;
1415	StringList *groups = NULL;
1416	char args[] = "C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:NPYy:";
1417	const char *cfg;
1418	char *gecos, *homedir, *grname, *name, *newname, *walk, *skel, *shell;
1419	char *passwd, *class, *nispasswd;
1420	login_cap_t *lc;
1421	struct stat st;
1422	intmax_t id = -1;
1423	int ch, fd = -1;
1424	size_t i, j;
1425	bool quiet, createhome, pretty, dryrun, nis, edited, docreatehome;
1426	mode_t homemode = 0;
1427	time_t expire_days, password_days, now, precrypted;
1428
1429	expire_days = password_days = -1;
1430	gecos = homedir = grname = name = newname = skel = shell =NULL;
1431	passwd = NULL;
1432	class = nispasswd = NULL;
1433	quiet = createhome = pretty = dryrun = nis = precrypted = false;
1434	edited = docreatehome = false;
1435
1436	if (arg1 != NULL) {
1437		if (strspn(arg1, "0123456789") == strlen(arg1))
1438			id = pw_checkid(arg1, UID_MAX);
1439		else
1440			name = arg1;
1441	}
1442
1443	while ((ch = getopt(argc, argv, args)) != -1) {
1444		switch (ch) {
1445		case 'C':
1446			cfg = optarg;
1447			break;
1448		case 'q':
1449			quiet = true;
1450			break;
1451		case 'n':
1452			name = optarg;
1453			break;
1454		case 'u':
1455			id = pw_checkid(optarg, UID_MAX);
1456			break;
1457		case 'c':
1458			gecos = pw_checkname(optarg, 1);
1459			break;
1460		case 'd':
1461			homedir = optarg;
1462			break;
1463		case 'e':
1464			now = time(NULL);
1465			expire_days = parse_date(now, optarg);
1466			break;
1467		case 'p':
1468			now = time(NULL);
1469			password_days = parse_date(now, optarg);
1470			break;
1471		case 'g':
1472			group_from_name_or_id(optarg);
1473			grname = optarg;
1474			break;
1475		case 'G':
1476			split_groups(&groups, optarg);
1477			break;
1478		case 'm':
1479			createhome = true;
1480			break;
1481		case 'M':
1482			homemode = validate_mode(optarg);
1483			break;
1484		case 'l':
1485			newname = optarg;
1486			break;
1487		case 'k':
1488			walk = skel = optarg;
1489			if (*walk == '/')
1490				walk++;
1491			if (fstatat(conf.rootfd, walk, &st, 0) == -1)
1492				errx(EX_OSFILE, "skeleton `%s' does not "
1493				    "exists", skel);
1494			if (!S_ISDIR(st.st_mode))
1495				errx(EX_OSFILE, "skeleton `%s' is not a "
1496				    "directory", skel);
1497			break;
1498		case 's':
1499			shell = optarg;
1500			break;
1501		case 'w':
1502			passwd = optarg;
1503			break;
1504		case 'L':
1505			class = pw_checkname(optarg, 0);
1506			break;
1507		case 'H':
1508			if (fd != -1)
1509				errx(EX_USAGE, "'-h' and '-H' are mutually "
1510				    "exclusive options");
1511			fd = pw_checkfd(optarg);
1512			precrypted = true;
1513			if (fd == '-')
1514				errx(EX_USAGE, "-H expects a file descriptor");
1515			break;
1516		case 'h':
1517			if (fd != -1)
1518				errx(EX_USAGE, "'-h' and '-H' are mutually "
1519				    "exclusive options");
1520			fd = pw_checkfd(optarg);
1521			break;
1522		case 'N':
1523			dryrun = true;
1524			break;
1525		case 'P':
1526			pretty = true;
1527			break;
1528		case 'y':
1529			nispasswd = optarg;
1530			break;
1531		case 'Y':
1532			nis = true;
1533			break;
1534		}
1535	}
1536
1537	if (geteuid() != 0 && ! dryrun)
1538		errx(EX_NOPERM, "you must be root");
1539
1540	if (quiet)
1541		freopen(_PATH_DEVNULL, "w", stderr);
1542
1543	cnf = get_userconfig(cfg);
1544
1545	if (id < 0 && name == NULL)
1546		errx(EX_DATAERR, "username or id required");
1547
1548	pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
1549	if (pwd == NULL) {
1550		if (name == NULL)
1551			errx(EX_NOUSER, "no such uid `%ju'",
1552			    (uintmax_t) id);
1553		errx(EX_NOUSER, "no such user `%s'", name);
1554	}
1555
1556	if (name == NULL)
1557		name = pwd->pw_name;
1558
1559	if (nis && nispasswd == NULL)
1560		nispasswd = cnf->nispasswd;
1561
1562	if (PWF._altdir == PWF_REGULAR &&
1563	    ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) {
1564		if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
1565			if (!nis && nispasswd && *nispasswd != '/')
1566				errx(EX_NOUSER, "Cannot modify NIS user `%s'",
1567				    name);
1568		} else {
1569			errx(EX_NOUSER, "Cannot modify non local user `%s'",
1570			    name);
1571		}
1572	}
1573
1574	if (newname) {
1575		if (strcmp(pwd->pw_name, "root") == 0)
1576			errx(EX_DATAERR, "can't rename `root' account");
1577		if (strcmp(pwd->pw_name, newname) != 0) {
1578			pwd->pw_name = pw_checkname(newname, 0);
1579			edited = true;
1580		}
1581	}
1582
1583	if (id > 0 && pwd->pw_uid != id) {
1584		pwd->pw_uid = id;
1585		edited = true;
1586		if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0)
1587			errx(EX_DATAERR, "can't change uid of `root' account");
1588		if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
1589			warnx("WARNING: account `%s' will have a uid of 0 "
1590			    "(superuser access!)", pwd->pw_name);
1591	}
1592
1593	if (grname && pwd->pw_uid != 0) {
1594		grp = GETGRNAM(grname);
1595		if (grp == NULL)
1596			grp = GETGRGID(pw_checkid(grname, GID_MAX));
1597		if (grp->gr_gid != pwd->pw_gid) {
1598			pwd->pw_gid = grp->gr_gid;
1599			edited = true;
1600		}
1601	}
1602
1603	if (password_days >= 0 && pwd->pw_change != password_days) {
1604		pwd->pw_change = password_days;
1605		edited = true;
1606	}
1607
1608	if (expire_days >= 0 && pwd->pw_expire != expire_days) {
1609		pwd->pw_expire = expire_days;
1610		edited = true;
1611	}
1612
1613	if (shell) {
1614		shell = shell_path(cnf->shelldir, cnf->shells, shell);
1615		if (shell == NULL)
1616			shell = "";
1617		if (strcmp(shell, pwd->pw_shell) != 0) {
1618			pwd->pw_shell = shell;
1619			edited = true;
1620		}
1621	}
1622
1623	if (class && strcmp(pwd->pw_class, class) != 0) {
1624		pwd->pw_class = class;
1625		edited = true;
1626	}
1627
1628	if (homedir && strcmp(pwd->pw_dir, homedir) != 0) {
1629		pwd->pw_dir = homedir;
1630		if (fstatat(conf.rootfd, pwd->pw_dir, &st, 0) == -1) {
1631			if (!createhome)
1632				warnx("WARNING: home `%s' does not exist",
1633				    pwd->pw_dir);
1634			else
1635				docreatehome = true;
1636		} else if (!S_ISDIR(st.st_mode)) {
1637			warnx("WARNING: home `%s' is not a directory",
1638			    pwd->pw_dir);
1639		}
1640	}
1641
1642	if (passwd && conf.fd == -1) {
1643		lc = login_getpwclass(pwd);
1644		if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL)
1645			warn("setting crypt(3) format");
1646		login_close(lc);
1647		pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun);
1648		edited = true;
1649	}
1650
1651	if (gecos && strcmp(pwd->pw_gecos, gecos) != 0) {
1652		pwd->pw_gecos = gecos;
1653		edited = true;
1654	}
1655
1656	if (fd != -1)
1657		edited = pw_set_passwd(pwd, fd, precrypted, true);
1658
1659	if (dryrun)
1660		return (print_user(pwd, pretty, false));
1661
1662	if (edited) /* Only updated this if required */
1663		perform_chgpwent(name, pwd, nis ? nispasswd : NULL);
1664	/* Now perform the needed changes concern groups */
1665	if (groups != NULL) {
1666		/* Delete User from groups using old name */
1667		SETGRENT();
1668		while ((grp = GETGRENT()) != NULL) {
1669			if (grp->gr_mem == NULL)
1670				continue;
1671			for (i = 0; grp->gr_mem[i] != NULL; i++) {
1672				if (strcmp(grp->gr_mem[i] , name) != 0)
1673					continue;
1674				for (j = i; grp->gr_mem[j] != NULL ; j++)
1675					grp->gr_mem[j] = grp->gr_mem[j+1];
1676				chggrent(grp->gr_name, grp);
1677				break;
1678			}
1679		}
1680		ENDGRENT();
1681		/* Add the user to the needed groups */
1682		for (i = 0; i < groups->sl_cur; i++) {
1683			grp = GETGRNAM(groups->sl_str[i]);
1684			grp = gr_add(grp, pwd->pw_name);
1685			if (grp == NULL)
1686				continue;
1687			chggrent(grp->gr_name, grp);
1688			free(grp);
1689		}
1690	}
1691	/* In case of rename we need to walk over the different groups */
1692	if (newname) {
1693		SETGRENT();
1694		while ((grp = GETGRENT()) != NULL) {
1695			if (grp->gr_mem == NULL)
1696				continue;
1697			for (i = 0; grp->gr_mem[i] != NULL; i++) {
1698				if (strcmp(grp->gr_mem[i], name) != 0)
1699					continue;
1700				grp->gr_mem[i] = newname;
1701				chggrent(grp->gr_name, grp);
1702				break;
1703			}
1704		}
1705	}
1706
1707	/* go get a current version of pwd */
1708	if (newname)
1709		name = newname;
1710	pwd = GETPWNAM(name);
1711	if (pwd == NULL)
1712		errx(EX_NOUSER, "user '%s' disappeared during update", name);
1713	grp = GETGRGID(pwd->pw_gid);
1714	pw_log(cnf, M_UPDATE, W_USER, "%s(%ju):%s(%ju):%s:%s:%s",
1715	    pwd->pw_name, (uintmax_t)pwd->pw_uid,
1716	    grp ? grp->gr_name : "unknown",
1717	    (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1),
1718	    pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
1719
1720	/*
1721	 * Let's create and populate the user's home directory. Note
1722	 * that this also `works' for editing users if -m is used, but
1723	 * existing files will *not* be overwritten.
1724	 */
1725	if (PWALTDIR() != PWF_ALT && docreatehome && pwd->pw_dir &&
1726	    *pwd->pw_dir == '/' && pwd->pw_dir[1]) {
1727		if (!skel)
1728			skel = cnf->dotdir;
1729		if (homemode == 0)
1730			homemode = cnf->homemode;
1731		create_and_populate_homedir(cnf, pwd, skel, homemode, true);
1732	}
1733
1734	if (nis && nis_update() == 0)
1735		pw_log(cnf, M_UPDATE, W_USER, "NIS maps updated");
1736
1737	return (EXIT_SUCCESS);
1738}
1739