xref: /openbsd-src/usr.sbin/user/user.c (revision 897fc685943471cf985a0fe38ba076ea6fe74fa5)
1 /* $OpenBSD: user.c,v 1.120 2017/05/24 09:18:15 mestre Exp $ */
2 /* $NetBSD: user.c,v 1.69 2003/04/14 17:40:07 agc Exp $ */
3 
4 /*
5  * Copyright (c) 1999 Alistair G. Crooks.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote
16  *    products derived from this software without specific prior written
17  *    permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
20  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 
35 #include <ctype.h>
36 #include <dirent.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <grp.h>
41 #include <limits.h>
42 #include <login_cap.h>
43 #include <paths.h>
44 #include <pwd.h>
45 #include <stdarg.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <time.h>
51 #include <unistd.h>
52 #include <util.h>
53 
54 #include "usermgmt.h"
55 
56 
57 /* this struct describes a uid range */
58 typedef struct range_t {
59 	uid_t	r_from;		/* low uid */
60 	uid_t	r_to;		/* high uid */
61 } range_t;
62 
63 /* this struct encapsulates the user information */
64 typedef struct user_t {
65 	int		u_flags;		/* see below */
66 	uid_t		u_uid;			/* uid of user */
67 	char	       *u_password;		/* encrypted password */
68 	char	       *u_comment;		/* comment field */
69 	char	       *u_home;		/* home directory */
70 	char	       *u_primgrp;		/* primary group */
71 	int		u_groupc;		/* # of secondary groups */
72 	const char     *u_groupv[NGROUPS_MAX];	/* secondary groups */
73 	char	       *u_shell;		/* user's shell */
74 	char	       *u_basedir;		/* base directory for home */
75 	char	       *u_expire;		/* when account will expire */
76 	char	       *u_inactive;		/* when password will expire */
77 	char	       *u_skeldir;		/* directory for startup files */
78 	char	       *u_class;		/* login class */
79 	unsigned int	u_rsize;		/* size of range array */
80 	unsigned int	u_rc;			/* # of ranges */
81 	range_t	       *u_rv;			/* the ranges */
82 	unsigned int	u_defrc;		/* # of ranges in defaults */
83 	int		u_preserve;		/* preserve uids on deletion */
84 } user_t;
85 
86 /* flags for which fields of the user_t replace the passwd entry */
87 enum {
88 	F_COMMENT	= 0x0001,
89 	F_DUPUID	= 0x0002,
90 	F_EXPIRE	= 0x0004,
91 	F_GROUP		= 0x0008,
92 	F_HOMEDIR	= 0x0010,
93 	F_MKDIR		= 0x0020,
94 	F_INACTIVE	= 0x0040,
95 	F_PASSWORD	= 0x0080,
96 	F_SECGROUP	= 0x0100,
97 	F_SHELL		= 0x0200,
98 	F_UID		= 0x0400,
99 	F_USERNAME	= 0x0800,
100 	F_CLASS		= 0x1000,
101 	F_SETSECGROUP	= 0x4000,
102 	F_ACCTLOCK	= 0x8000,
103 	F_ACCTUNLOCK	= 0x10000
104 };
105 
106 #define CONFFILE		"/etc/usermgmt.conf"
107 #define _PATH_NONEXISTENT	"/nonexistent"
108 
109 #ifndef DEF_GROUP
110 #define DEF_GROUP	"=uid"
111 #endif
112 
113 #ifndef DEF_BASEDIR
114 #define DEF_BASEDIR	"/home"
115 #endif
116 
117 #ifndef DEF_SKELDIR
118 #define DEF_SKELDIR	"/etc/skel"
119 #endif
120 
121 #ifndef DEF_SHELL
122 #define DEF_SHELL	_PATH_KSHELL
123 #endif
124 
125 #ifndef DEF_COMMENT
126 #define DEF_COMMENT	""
127 #endif
128 
129 #ifndef DEF_LOWUID
130 #define DEF_LOWUID	1000
131 #endif
132 
133 #ifndef DEF_HIGHUID
134 #define DEF_HIGHUID	60000
135 #endif
136 
137 #ifndef DEF_INACTIVE
138 #define DEF_INACTIVE	0
139 #endif
140 
141 #ifndef DEF_EXPIRE
142 #define DEF_EXPIRE	NULL
143 #endif
144 
145 #ifndef DEF_CLASS
146 #define DEF_CLASS	""
147 #endif
148 
149 #ifndef WAITSECS
150 #define WAITSECS	10
151 #endif
152 
153 #ifndef NOBODY_UID
154 #define NOBODY_UID	32767
155 #endif
156 
157 /* some useful constants */
158 enum {
159 	MaxShellNameLen = 256,
160 	MaxFileNameLen = PATH_MAX,
161 	MaxUserNameLen = _PW_NAME_LEN,
162 	MaxCommandLen = 2048,
163 	PasswordLength = _PASSWORD_LEN,
164 	LowGid = DEF_LOWUID,
165 	HighGid = DEF_HIGHUID
166 };
167 
168 /* Full paths of programs used here */
169 #define CHMOD		"/bin/chmod"
170 #define CHOWN		"/sbin/chown"
171 #define MKDIR		"/bin/mkdir"
172 #define MV		"/bin/mv"
173 #define NOLOGIN		"/sbin/nologin"
174 #define PAX		"/bin/pax"
175 #define RM		"/bin/rm"
176 
177 #define UNSET_INACTIVE	"Null (unset)"
178 #define UNSET_EXPIRY	"Null (unset)"
179 
180 static int adduser(char *, user_t *);
181 static int append_group(char *, int, const char **);
182 static int asystem(const char *, ...)
183 	__attribute__((__format__(__printf__, 1, 2)));
184 static int copydotfiles(char *, char *);
185 static int creategid(char *, gid_t, const char *);
186 static int getnextgid(uid_t *, uid_t, uid_t);
187 static int getnextuid(int, uid_t *, uid_t, uid_t);
188 static int is_local(char *, const char *);
189 static int modify_gid(char *, char *);
190 static int moduser(char *, char *, user_t *);
191 static int removehomedir(const char *, uid_t, const char *);
192 static int rm_user_from_groups(char *);
193 static int save_range(user_t *, char *);
194 static int scantime(time_t *, char *);
195 static int setdefaults(user_t *);
196 static int valid_class(char *);
197 static int valid_group(char *);
198 static int valid_login(char *);
199 static size_t expand_len(const char *, const char *);
200 static struct group *find_group_info(const char *);
201 static struct passwd *find_user_info(const char *);
202 static void checkeuid(void);
203 static void memsave(char **, const char *, size_t);
204 static void read_defaults(user_t *);
205 
206 static int	verbose;
207 
208 /* if *cpp is non-null, free it, then assign `n' chars of `s' to it */
209 static void
210 memsave(char **cpp, const char *s, size_t n)
211 {
212 	free(*cpp);
213 	if ((*cpp = calloc (n + 1, sizeof(char))) == NULL)
214 		err(1, NULL);
215 	memcpy(*cpp, s, n);
216 	(*cpp)[n] = '\0';
217 }
218 
219 /* a replacement for system(3) */
220 static int
221 asystem(const char *fmt, ...)
222 {
223 	va_list	vp;
224 	char	buf[MaxCommandLen];
225 	int	ret;
226 
227 	va_start(vp, fmt);
228 	(void) vsnprintf(buf, sizeof(buf), fmt, vp);
229 	va_end(vp);
230 	if (verbose) {
231 		printf("Command: %s\n", buf);
232 	}
233 	if ((ret = system(buf)) != 0) {
234 		warnx("[Warning] can't system `%s'", buf);
235 	}
236 	return ret;
237 }
238 
239 /* remove a users home directory, returning 1 for success (ie, no problems encountered) */
240 static int
241 removehomedir(const char *user, uid_t uid, const char *dir)
242 {
243 	struct stat st;
244 
245 	/* userid not root? */
246 	if (uid == 0) {
247 		warnx("Not deleting home directory `%s'; userid is 0", dir);
248 		return 0;
249 	}
250 
251 	/* directory exists (and is a directory!) */
252 	if (stat(dir, &st) < 0) {
253 		warnx("Home directory `%s' doesn't exist", dir);
254 		return 0;
255 	}
256 	if (!S_ISDIR(st.st_mode)) {
257 		warnx("Home directory `%s' is not a directory", dir);
258 		return 0;
259 	}
260 
261 	/* userid matches directory owner? */
262 	if (st.st_uid != uid) {
263 		warnx("User `%s' doesn't own directory `%s', not removed",
264 		    user, dir);
265 		return 0;
266 	}
267 
268 	(void) seteuid(uid);
269 	/* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */
270 	(void) asystem("%s -rf %s > /dev/null 2>&1 || true", RM, dir);
271 	(void) seteuid(0);
272 	if (rmdir(dir) < 0) {
273 		warnx("Unable to remove all files in `%s'", dir);
274 		return 0;
275 	}
276 	return 1;
277 }
278 
279 /*
280  * check that the effective uid is 0 - called from funcs which will
281  * modify data and config files.
282  */
283 static void
284 checkeuid(void)
285 {
286 	if (geteuid() != 0) {
287 		errx(EXIT_FAILURE, "Program must be run as root");
288 	}
289 }
290 
291 /* copy any dot files into the user's home directory */
292 static int
293 copydotfiles(char *skeldir, char *dir)
294 {
295 	struct dirent	*dp;
296 	DIR		*dirp;
297 	int		n;
298 
299 	if (*skeldir == '\0')
300 		return 0;
301 	if ((dirp = opendir(skeldir)) == NULL) {
302 		warn("can't open source . files dir `%s'", skeldir);
303 		return 0;
304 	}
305 	for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) {
306 		if (strcmp(dp->d_name, ".") == 0 ||
307 		    strcmp(dp->d_name, "..") == 0) {
308 			continue;
309 		}
310 		n = 1;
311 	}
312 	(void) closedir(dirp);
313 	if (n == 0) {
314 		warnx("No \"dot\" initialisation files found");
315 	} else {
316 		(void) asystem("cd %s && %s -rw -pe %s . %s",
317 				skeldir, PAX, (verbose) ? "-v" : "", dir);
318 	}
319 	return n;
320 }
321 
322 /* create a group entry with gid `gid' */
323 static int
324 creategid(char *group, gid_t gid, const char *name)
325 {
326 	struct stat	st;
327 	FILE		*from;
328 	FILE		*to;
329 	char		*buf;
330 	char		f[MaxFileNameLen];
331 	int		fd, ret;
332 	int		wroteit = 0;
333 	size_t		len;
334 
335 	if (getgrnam(group) != NULL) {
336 		warnx("group `%s' already exists", group);
337 		return 0;
338 	}
339 	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
340 		warn("can't create gid for `%s': can't open `%s'", group,
341 		    _PATH_GROUP);
342 		return 0;
343 	}
344 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
345 		warn("can't lock `%s'", _PATH_GROUP);
346 	}
347 	(void) fstat(fileno(from), &st);
348 	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
349 	if ((fd = mkstemp(f)) < 0) {
350 		warn("can't create gid: mkstemp failed");
351 		fclose(from);
352 		return 0;
353 	}
354 	if ((to = fdopen(fd, "w")) == NULL) {
355 		warn("can't create gid: fdopen `%s' failed", f);
356 		fclose(from);
357 		close(fd);
358 		unlink(f);
359 		return 0;
360 	}
361 	while ((buf = fgetln(from, &len)) != NULL && len > 0) {
362 		ret = 0;
363 		if (buf[0] == '+' && wroteit == 0) {
364 			ret = fprintf(to, "%s:*:%u:%s\n", group, gid, name);
365 			wroteit = 1;
366 		}
367 		if (ret == -1 ||
368 		    fprintf(to, "%*.*s", (int)len, (int)len, buf) != len) {
369 			warn("can't create gid: short write to `%s'", f);
370 			fclose(from);
371 			fclose(to);
372 			unlink(f);
373 			return 0;
374 		}
375 	}
376 	ret = 0;
377 	if (wroteit == 0)
378 		ret = fprintf(to, "%s:*:%u:%s\n", group, gid, name);
379 	fclose(from);
380 	if (fclose(to) == EOF || ret == -1) {
381 		warn("can't create gid: short write to `%s'", f);
382 		unlink(f);
383 		return 0;
384 	}
385 	if (rename(f, _PATH_GROUP) < 0) {
386 		warn("can't create gid: can't rename `%s' to `%s'", f,
387 		    _PATH_GROUP);
388 		unlink(f);
389 		return 0;
390 	}
391 	(void) chmod(_PATH_GROUP, st.st_mode & 0777);
392 	syslog(LOG_INFO, "new group added: name=%s, gid=%u", group, gid);
393 	return 1;
394 }
395 
396 /* modify the group entry with name `group' to be newent */
397 static int
398 modify_gid(char *group, char *newent)
399 {
400 	struct stat	st;
401 	FILE		*from;
402 	FILE		*to;
403 	char		buf[LINE_MAX];
404 	char		f[MaxFileNameLen];
405 	char		*colon;
406 	int		groupc;
407 	int		entc;
408 	int		fd;
409 	int		cc;
410 
411 	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
412 		warn("can't modify gid for `%s': can't open `%s'", group,
413 		    _PATH_GROUP);
414 		return 0;
415 	}
416 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
417 		warn("can't lock `%s'", _PATH_GROUP);
418 	}
419 	(void) fstat(fileno(from), &st);
420 	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
421 	if ((fd = mkstemp(f)) < 0) {
422 		warn("can't modify gid: mkstemp failed");
423 		fclose(from);
424 		return 0;
425 	}
426 	if ((to = fdopen(fd, "w")) == NULL) {
427 		warn("can't modify gid: fdopen `%s' failed", f);
428 		fclose(from);
429 		close(fd);
430 		unlink(f);
431 		return 0;
432 	}
433 	groupc = strlen(group);
434 	while (fgets(buf, sizeof(buf), from) != NULL) {
435 		cc = strlen(buf);
436 		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
437 			while (fgetc(from) != '\n' && !feof(from))
438 				cc++;
439 			warnx("%s: line `%s' too long (%d bytes), skipping",
440 			    _PATH_GROUP, buf, cc);
441 			continue;
442 		}
443 		if ((colon = strchr(buf, ':')) == NULL) {
444 			/*
445 			 * The only valid entry with no column is the all-YP
446 			 * line.
447 			 */
448 			if (strcmp(buf, "+\n") != 0) {
449 				warnx("badly formed entry `%.*s'", cc - 1, buf);
450 				continue;
451 			}
452 		} else {
453 			entc = (int)(colon - buf);
454 			if (entc == groupc && strncmp(group, buf, entc) == 0) {
455 				if (newent == NULL) {
456 					continue;
457 				} else {
458 					cc = strlcpy(buf, newent, sizeof(buf));
459 					if (cc >= sizeof(buf)) {
460 						warnx("group `%s' entry too long",
461 						    newent);
462 						fclose(from);
463 						fclose(to);
464 						unlink(f);
465 						return (0);
466 					}
467 				}
468 			}
469 		}
470 		if (fwrite(buf, cc, 1, to) != 1) {
471 			warn("can't modify gid: short write to `%s'", f);
472 			fclose(from);
473 			fclose(to);
474 			unlink(f);
475 			return 0;
476 		}
477 	}
478 	fclose(from);
479 	if (fclose(to) == EOF) {
480 		warn("can't modify gid: short write to `%s'", f);
481 		unlink(f);
482 		return 0;
483 	}
484 	if (rename(f, _PATH_GROUP) < 0) {
485 		warn("can't modify gid: can't rename `%s' to `%s'", f, _PATH_GROUP);
486 		unlink(f);
487 		return 0;
488 	}
489 	(void) chmod(_PATH_GROUP, st.st_mode & 0777);
490 	if (newent == NULL) {
491 		syslog(LOG_INFO, "group deleted: name=%s", group);
492 	} else {
493 		syslog(LOG_INFO, "group information modified: name=%s", group);
494 	}
495 	return 1;
496 }
497 
498 /* modify the group entries for all `groups', by adding `user' */
499 static int
500 append_group(char *user, int ngroups, const char **groups)
501 {
502 	struct group	*grp;
503 	struct passwd	*pwp;
504 	struct stat	st;
505 	FILE		*from;
506 	FILE		*to;
507 	char		buf[LINE_MAX];
508 	char		f[MaxFileNameLen];
509 	char		*colon;
510 	char		*ugid = NULL;
511 	int		fd;
512 	int		cc;
513 	int		i;
514 	int		j;
515 
516 	if ((pwp = getpwnam(user))) {
517 		if ((ugid = group_from_gid(pwp->pw_gid, 1)) == NULL) {
518 			warnx("can't get primary group for user `%s'", user);
519 			return 0;
520 		}
521 	}
522 
523 	for (i = 0 ; i < ngroups ; i++) {
524 		if ((grp = getgrnam(groups[i])) == NULL) {
525 			warnx("can't append group `%s' for user `%s'",
526 			    groups[i], user);
527 		} else {
528 			for (j = 0 ; grp->gr_mem[j] ; j++) {
529 				if (strcmp(user, grp->gr_mem[j]) == 0) {
530 					/* already in it */
531 					groups[i] = "";
532 				}
533 			}
534 		}
535 	}
536 	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
537 		warn("can't append group for `%s': can't open `%s'", user,
538 		    _PATH_GROUP);
539 		return 0;
540 	}
541 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
542 		warn("can't lock `%s'", _PATH_GROUP);
543 	}
544 	(void) fstat(fileno(from), &st);
545 	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
546 	if ((fd = mkstemp(f)) < 0) {
547 		warn("can't append group: mkstemp failed");
548 		fclose(from);
549 		return 0;
550 	}
551 	if ((to = fdopen(fd, "w")) == NULL) {
552 		warn("can't append group: fdopen `%s' failed", f);
553 		fclose(from);
554 		close(fd);
555 		unlink(f);
556 		return 0;
557 	}
558 	while (fgets(buf, sizeof(buf), from) != NULL) {
559 		cc = strlen(buf);
560 		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
561 			while (fgetc(from) != '\n' && !feof(from))
562 				cc++;
563 			warnx("%s: line `%s' too long (%d bytes), skipping",
564 			    _PATH_GROUP, buf, cc);
565 			continue;
566 		}
567 		if ((colon = strchr(buf, ':')) == NULL) {
568 			warnx("badly formed entry `%s'", buf);
569 			continue;
570 		}
571 		for (i = 0 ; i < ngroups ; i++) {
572 			j = (int)(colon - buf);
573 			if (ugid) {
574 				if (strcmp(ugid, groups[i]) == 0) {
575 					/* user's primary group, no need to append */
576 					groups[i] = "";
577 				}
578 			}
579 			if (strncmp(groups[i], buf, j) == 0 &&
580 			    groups[i][j] == '\0') {
581 				while (isspace((unsigned char)buf[cc - 1]))
582 					cc--;
583 				buf[(j = cc)] = '\0';
584 				if (buf[strlen(buf) - 1] != ':')
585 					strlcat(buf, ",", sizeof(buf));
586 				cc = strlcat(buf, user, sizeof(buf)) + 1;
587 				if (cc >= sizeof(buf)) {
588 					warnx("Warning: group `%s' would "
589 					    "become too long, not modifying",
590 					    groups[i]);
591 					cc = j + 1;
592 				}
593 				buf[cc - 1] = '\n';
594 				buf[cc] = '\0';
595 			}
596 		}
597 		if (fwrite(buf, cc, 1, to) != 1) {
598 			warn("can't append group: short write to `%s'", f);
599 			fclose(from);
600 			fclose(to);
601 			unlink(f);
602 			return 0;
603 		}
604 	}
605 	fclose(from);
606 	if (fclose(to) == EOF) {
607 		warn("can't append group: short write to `%s'", f);
608 		unlink(f);
609 		return 0;
610 	}
611 	if (rename(f, _PATH_GROUP) < 0) {
612 		warn("can't append group: can't rename `%s' to `%s'", f, _PATH_GROUP);
613 		unlink(f);
614 		return 0;
615 	}
616 	(void) chmod(_PATH_GROUP, st.st_mode & 0777);
617 	return 1;
618 }
619 
620 /* return 1 if `login' is a valid login name */
621 static int
622 valid_login(char *login_name)
623 {
624 	unsigned char	*cp;
625 
626 	/* The first character cannot be a hyphen */
627 	if (*login_name == '-')
628 		return 0;
629 
630 	for (cp = login_name ; *cp ; cp++) {
631 		/* We allow '$' as the last character for samba */
632 		if (!isalnum((unsigned char)*cp) && *cp != '.' &&
633 		    *cp != '_' && *cp != '-' &&
634 		    !(*cp == '$' && *(cp + 1) == '\0')) {
635 			return 0;
636 		}
637 	}
638 	if ((char *)cp - login_name > MaxUserNameLen)
639 		return 0;
640 	return 1;
641 }
642 
643 /* return 1 if `group' is a valid group name */
644 static int
645 valid_group(char *group)
646 {
647 	unsigned char	*cp;
648 
649 	for (cp = group ; *cp ; cp++) {
650 		if (!isalnum((unsigned char)*cp) && *cp != '.' &&
651 		    *cp != '_' && *cp != '-') {
652 			return 0;
653 		}
654 	}
655 	if ((char *)cp - group > MaxUserNameLen)
656 		return 0;
657 	return 1;
658 }
659 
660 /* return 1 if `class' exists */
661 static int
662 valid_class(char *class)
663 {
664 	login_cap_t *lc;
665 
666 	if ((lc = login_getclass(class)) != NULL)
667 		login_close(lc);
668 	return lc != NULL;
669 }
670 
671 /* find the next gid in the range lo .. hi */
672 static int
673 getnextgid(uid_t *gidp, uid_t lo, uid_t hi)
674 {
675 	for (*gidp = lo ; *gidp < hi ; *gidp += 1) {
676 		if (getgrgid((gid_t)*gidp) == NULL) {
677 			return 1;
678 		}
679 	}
680 	return 0;
681 }
682 
683 /* save a range of uids */
684 static int
685 save_range(user_t *up, char *cp)
686 {
687 	uid_t	from;
688 	uid_t	to;
689 	int	i;
690 
691 	if (up->u_rc == up->u_rsize) {
692 		up->u_rsize *= 2;
693 		if ((up->u_rv = reallocarray(up->u_rv, up->u_rsize,
694 		    sizeof(range_t))) == NULL) {
695 			warn(NULL);
696 			return 0;
697 		}
698 	}
699 	if (up->u_rv && sscanf(cp, "%u..%u", &from, &to) == 2) {
700 		for (i = up->u_defrc ; i < up->u_rc ; i++) {
701 			if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) {
702 				break;
703 			}
704 		}
705 		if (i == up->u_rc) {
706 			up->u_rv[up->u_rc].r_from = from;
707 			up->u_rv[up->u_rc].r_to = to;
708 			up->u_rc += 1;
709 		}
710 	} else {
711 		warnx("Bad range `%s'", cp);
712 		return 0;
713 	}
714 	return 1;
715 }
716 
717 /* set the defaults in the defaults file */
718 static int
719 setdefaults(user_t *up)
720 {
721 	char	template[MaxFileNameLen];
722 	FILE	*fp;
723 	int	ret;
724 	int	fd;
725 	int	i;
726 
727 	(void) snprintf(template, sizeof(template), "%s.XXXXXXXX", CONFFILE);
728 	if ((fd = mkstemp(template)) < 0) {
729 		warnx("can't mkstemp `%s' for writing", CONFFILE);
730 		return 0;
731 	}
732 	if ((fp = fdopen(fd, "w")) == NULL) {
733 		warn("can't fdopen `%s' for writing", CONFFILE);
734 		return 0;
735 	}
736 	ret = 1;
737 	if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 ||
738 	    fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 ||
739 	    fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 ||
740 	    fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 ||
741 	    fprintf(fp, "class\t\t%s\n", up->u_class) <= 0 ||
742 	    fprintf(fp, "inactive\t%s\n", (up->u_inactive == NULL) ? UNSET_INACTIVE : up->u_inactive) <= 0 ||
743 	    fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? UNSET_EXPIRY : up->u_expire) <= 0 ||
744 	    fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? "false" : "true") <= 0) {
745 		warn("can't write to `%s'", CONFFILE);
746 		ret = 0;
747 	}
748 	for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) {
749 		if (fprintf(fp, "range\t\t%u..%u\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) {
750 			warn("can't write to `%s'", CONFFILE);
751 			ret = 0;
752 		}
753 	}
754 	if (fclose(fp) == EOF) {
755 		warn("can't write to `%s'", CONFFILE);
756 		ret = 0;
757 	}
758 	if (ret) {
759 		ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0));
760 	}
761 	return ret;
762 }
763 
764 /* read the defaults file */
765 static void
766 read_defaults(user_t *up)
767 {
768 	struct stat	st;
769 	size_t		lineno;
770 	size_t		len;
771 	FILE		*fp;
772 	unsigned char	*cp;
773 	unsigned char	*s;
774 
775 	memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP));
776 	memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR));
777 	memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR));
778 	memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL));
779 	memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT));
780 	memsave(&up->u_class, DEF_CLASS, strlen(DEF_CLASS));
781 	up->u_rsize = 16;
782 	up->u_defrc = 0;
783 	if ((up->u_rv = calloc(up->u_rsize, sizeof(range_t))) == NULL)
784 		err(1, NULL);
785 	up->u_inactive = DEF_INACTIVE;
786 	up->u_expire = DEF_EXPIRE;
787 	if ((fp = fopen(CONFFILE, "r")) == NULL) {
788 		if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) {
789 			warn("can't create `%s' defaults file", CONFFILE);
790 		}
791 		fp = fopen(CONFFILE, "r");
792 	}
793 	if (fp != NULL) {
794 		while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) {
795 			if (strncmp(s, "group", 5) == 0) {
796 				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
797 				}
798 				memsave(&up->u_primgrp, cp, strlen(cp));
799 			} else if (strncmp(s, "base_dir", 8) == 0) {
800 				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
801 				}
802 				memsave(&up->u_basedir, cp, strlen(cp));
803 			} else if (strncmp(s, "skel_dir", 8) == 0) {
804 				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
805 				}
806 				memsave(&up->u_skeldir, cp, strlen(cp));
807 			} else if (strncmp(s, "shell", 5) == 0) {
808 				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
809 				}
810 				memsave(&up->u_shell, cp, strlen(cp));
811 			} else if (strncmp(s, "password", 8) == 0) {
812 				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
813 				}
814 				memsave(&up->u_password, cp, strlen(cp));
815 			} else if (strncmp(s, "class", 5) == 0) {
816 				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
817 				}
818 				memsave(&up->u_class, cp, strlen(cp));
819 			} else if (strncmp(s, "inactive", 8) == 0) {
820 				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
821 				}
822 				if (strcmp(cp, UNSET_INACTIVE) == 0) {
823 					free(up->u_inactive);
824 					up->u_inactive = NULL;
825 				} else {
826 					memsave(&up->u_inactive, cp, strlen(cp));
827 				}
828 			} else if (strncmp(s, "range", 5) == 0) {
829 				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
830 				}
831 				(void) save_range(up, cp);
832 			} else if (strncmp(s, "preserve", 8) == 0) {
833 				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
834 				}
835 				up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 :
836 						  (strncmp(cp, "yes", 3) == 0) ? 1 :
837 						   strtonum(cp, INT_MIN, INT_MAX, NULL);
838 			} else if (strncmp(s, "expire", 6) == 0) {
839 				for (cp = s + 6 ; isspace((unsigned char)*cp); cp++) {
840 				}
841 				if (strcmp(cp, UNSET_EXPIRY) == 0) {
842 					free(up->u_expire);
843 					up->u_expire = NULL;
844 				} else {
845 					memsave(&up->u_expire, cp, strlen(cp));
846 				}
847 			}
848 			free(s);
849 		}
850 		fclose(fp);
851 	}
852 	if (up->u_rc == 0) {
853 		up->u_rv[up->u_rc].r_from = DEF_LOWUID;
854 		up->u_rv[up->u_rc].r_to = DEF_HIGHUID;
855 		up->u_rc += 1;
856 	}
857 	up->u_defrc = up->u_rc;
858 }
859 
860 /* return the next valid unused uid */
861 static int
862 getnextuid(int sync_uid_gid, uid_t *uid, uid_t low_uid, uid_t high_uid)
863 {
864 	for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) {
865 		if (getpwuid((uid_t)(*uid)) == NULL && *uid != NOBODY_UID) {
866 			if (sync_uid_gid) {
867 				if (getgrgid((gid_t)(*uid)) == NULL) {
868 					return 1;
869 				}
870 			} else {
871 				return 1;
872 			}
873 		}
874 	}
875 	return 0;
876 }
877 
878 /* look for a valid time, return 0 if it was specified but bad */
879 static int
880 scantime(time_t *tp, char *s)
881 {
882 	struct tm	tm;
883 
884 	*tp = 0;
885 	if (s != NULL) {
886 		memset(&tm, 0, sizeof(tm));
887 		tm.tm_isdst = -1;
888 		if (strptime(s, "%c", &tm) != NULL) {
889 			*tp = mktime(&tm);
890 		} else if (strptime(s, "%B %d %Y", &tm) != NULL) {
891 			*tp = mktime(&tm);
892 		} else if (isdigit((unsigned char) s[0]) != 0) {
893 			*tp = (time_t)atoll(s);
894 		} else {
895 			return 0;
896 		}
897 	}
898 	return 1;
899 }
900 
901 /* compute the extra length '&' expansion consumes */
902 static size_t
903 expand_len(const char *p, const char *username)
904 {
905 	size_t alen;
906 	size_t ulen;
907 
908 	ulen = strlen(username);
909 	for (alen = 0; *p != '\0'; p++)
910 		if (*p == '&')
911 			alen += ulen - 1;
912 	return alen;
913 }
914 
915 /* see if we can find out the user struct */
916 static struct passwd *
917 find_user_info(const char *name)
918 {
919 	struct passwd	*pwp;
920 	const char	*errstr;
921 	uid_t		uid;
922 
923 	if ((pwp = getpwnam(name)) == NULL) {
924 		uid = strtonum(name, -1, UID_MAX, &errstr);
925 		if (errstr == NULL)
926 			pwp = getpwuid(uid);
927 	}
928 	return pwp;
929 }
930 
931 /* see if we can find out the group struct */
932 static struct group *
933 find_group_info(const char *name)
934 {
935 	struct group	*grp;
936 	const char	*errstr;
937 	gid_t		gid;
938 
939 	if ((grp = getgrnam(name)) == NULL) {
940 		gid = strtonum(name, -1, GID_MAX, &errstr);
941 		if (errstr == NULL)
942 			grp = getgrgid(gid);
943 	}
944 	return grp;
945 }
946 
947 /* add a user */
948 static int
949 adduser(char *login_name, user_t *up)
950 {
951 	struct group	*grp;
952 	struct stat	st;
953 	time_t		expire;
954 	time_t		inactive;
955 	char		password[PasswordLength + 1];
956 	char		home[MaxFileNameLen];
957 	char		buf[LINE_MAX];
958 	int		sync_uid_gid;
959 	int		masterfd;
960 	int		ptmpfd;
961 	gid_t		gid;
962 	int		cc;
963 	int		i, yp = 0;
964 	FILE		*fp;
965 
966 	if (!valid_login(login_name)) {
967 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
968 	}
969 	if (!valid_class(up->u_class)) {
970 		errx(EXIT_FAILURE, "No such login class `%s'", up->u_class);
971 	}
972 	if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
973 		err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
974 	}
975 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
976 		err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
977 	}
978 	pw_init();
979 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
980 		int saved_errno = errno;
981 		close(masterfd);
982 		errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
983 	}
984 	if ((fp = fdopen(masterfd, "r")) == NULL) {
985 		int saved_errno = errno;
986 		close(masterfd);
987 		close(ptmpfd);
988 		pw_abort();
989 		errc(EXIT_FAILURE, saved_errno,
990 		    "can't fdopen `%s' for reading", _PATH_MASTERPASSWD);
991 	}
992 	while (fgets(buf, sizeof(buf), fp) != NULL) {
993 		cc = strlen(buf);
994 		/*
995 		 * Stop copying the file at the yp entry; we want to
996 		 * put the new user before it, and preserve entries
997 		 * after the yp entry.
998 		 */
999 		if (cc > 1 && buf[0] == '+' && buf[1] == ':') {
1000 			yp = 1;
1001 			break;
1002 		}
1003 		if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1004 			int saved_errno = errno;
1005 			fclose(fp);
1006 			close(ptmpfd);
1007 			pw_abort();
1008 			errc(EXIT_FAILURE, saved_errno,
1009 			    "short write to /etc/ptmp (not %d chars)", cc);
1010 		}
1011 	}
1012 	if (ferror(fp)) {
1013 		int saved_errno = errno;
1014 		fclose(fp);
1015 		close(ptmpfd);
1016 		pw_abort();
1017 		errc(EXIT_FAILURE, saved_errno, "read error on %s",
1018 		    _PATH_MASTERPASSWD);
1019 	}
1020 	/* if no uid was specified, get next one in [low_uid..high_uid] range */
1021 	sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
1022 	if (up->u_uid == UID_MAX) {
1023 		int got_id = 0;
1024 
1025 		/*
1026 		 * Look for a free UID in the command line ranges (if any).
1027 		 * These start after the ranges specified in the config file.
1028 		 */
1029 		for (i = up->u_defrc; got_id == 0 && i < up->u_rc ; i++) {
1030 			got_id = getnextuid(sync_uid_gid, &up->u_uid,
1031 			    up->u_rv[i].r_from, up->u_rv[i].r_to);
1032 		}
1033 		/*
1034 		 * If there were no free UIDs in the command line ranges,
1035 		 * try the ranges from the config file (there will always
1036 		 * be at least one default).
1037 		 */
1038 		if (got_id == 0) {
1039 			for (i = 0; got_id == 0 && i < up->u_defrc; i++) {
1040 				got_id = getnextuid(sync_uid_gid, &up->u_uid,
1041 				    up->u_rv[i].r_from, up->u_rv[i].r_to);
1042 			}
1043 		}
1044 		if (got_id == 0) {
1045 			close(ptmpfd);
1046 			pw_abort();
1047 			errx(EXIT_FAILURE, "can't get next uid for %u", up->u_uid);
1048 		}
1049 	}
1050 	/* check uid isn't already allocated */
1051 	if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
1052 		close(ptmpfd);
1053 		pw_abort();
1054 		errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1055 	}
1056 	/* if -g=uid was specified, check gid is unused */
1057 	if (sync_uid_gid) {
1058 		if (getgrgid((gid_t)(up->u_uid)) != NULL) {
1059 			close(ptmpfd);
1060 			pw_abort();
1061 			errx(EXIT_FAILURE, "gid %u is already in use", up->u_uid);
1062 		}
1063 		gid = up->u_uid;
1064 	} else {
1065 		if ((grp = find_group_info(up->u_primgrp)) == NULL) {
1066 			close(ptmpfd);
1067 			pw_abort();
1068 			errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
1069 		}
1070 		gid = grp->gr_gid;
1071 	}
1072 	/* check name isn't already in use */
1073 	if (!(up->u_flags & F_DUPUID) && getpwnam(login_name) != NULL) {
1074 		close(ptmpfd);
1075 		pw_abort();
1076 		errx(EXIT_FAILURE, "already a `%s' user", login_name);
1077 	}
1078 	if (up->u_flags & F_HOMEDIR) {
1079 		if (strlcpy(home, up->u_home, sizeof(home)) >= sizeof(home)) {
1080 			close(ptmpfd);
1081 			pw_abort();
1082 			errx(EXIT_FAILURE, "home directory `%s' too long",
1083 			    up->u_home);
1084 		}
1085 	} else {
1086 		/* if home directory hasn't been given, make it up */
1087 		if (snprintf(home, sizeof(home), "%s/%s", up->u_basedir,
1088 		    login_name) >= sizeof(home)) {
1089 			close(ptmpfd);
1090 			pw_abort();
1091 			errx(EXIT_FAILURE, "home directory `%s/%s' too long",
1092 			    up->u_basedir, login_name);
1093 		}
1094 	}
1095 	if (!scantime(&inactive, up->u_inactive)) {
1096 		warnx("Warning: inactive time `%s' invalid, password expiry off",
1097 				up->u_inactive);
1098 	}
1099 	if (!scantime(&expire, up->u_expire)) {
1100 		warnx("Warning: expire time `%s' invalid, account expiry off",
1101 				up->u_expire);
1102 	}
1103 	if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR) &&
1104 	    strcmp(home, _PATH_NONEXISTENT) != 0) {
1105 		warnx("Warning: home directory `%s' doesn't exist, and -m was"
1106 		    " not specified", home);
1107 	}
1108 	(void) strlcpy(password, up->u_password ? up->u_password : "*",
1109 	    sizeof(password));
1110 	cc = snprintf(buf, sizeof(buf), "%s:%s:%u:%u:%s:%lld:%lld:%s:%s:%s\n",
1111 	    login_name,
1112 	    password,
1113 	    up->u_uid,
1114 	    gid,
1115 	    up->u_class,
1116 	    (long long) inactive,
1117 	    (long long) expire,
1118 	    up->u_comment,
1119 	    home,
1120 	    up->u_shell);
1121 	if (cc >= sizeof(buf) || cc < 0 ||
1122 	    cc + expand_len(up->u_comment, login_name) >= 1023) {
1123 		close(ptmpfd);
1124 		pw_abort();
1125 		errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1126 	}
1127 	if (write(ptmpfd, buf, (size_t) cc) != cc) {
1128 		int saved_errno = errno;
1129 		close(ptmpfd);
1130 		pw_abort();
1131 		errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1132 	}
1133 	if (yp) {
1134 		/* put back the + line */
1135 		cc = snprintf(buf, sizeof(buf), "+:*::::::::\n");
1136 		if (cc == -1 || cc >= sizeof(buf)) {
1137 			close(ptmpfd);
1138 			pw_abort();
1139 			errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1140 		}
1141 		if (write(ptmpfd, buf, (size_t) cc) != cc) {
1142 			int saved_errno = errno;
1143 			close(ptmpfd);
1144 			pw_abort();
1145 			errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1146 		}
1147 		/* copy the entries following it, if any */
1148 		while (fgets(buf, sizeof(buf), fp) != NULL) {
1149 			cc = strlen(buf);
1150 			if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1151 				int saved_errno = errno;
1152 				fclose(fp);
1153 				close(ptmpfd);
1154 				pw_abort();
1155 				errc(EXIT_FAILURE, saved_errno,
1156 				    "short write to /etc/ptmp (not %d chars)",
1157 				    cc);
1158 			}
1159 		}
1160 		if (ferror(fp)) {
1161 			int saved_errno = errno;
1162 			fclose(fp);
1163 			close(ptmpfd);
1164 			pw_abort();
1165 			errc(EXIT_FAILURE, saved_errno, "read error on %s",
1166 			    _PATH_MASTERPASSWD);
1167 		}
1168 	}
1169 	if (up->u_flags & F_MKDIR) {
1170 		if (lstat(home, &st) == 0) {
1171 			close(ptmpfd);
1172 			pw_abort();
1173 			errx(EXIT_FAILURE, "home directory `%s' already exists",
1174 			    home);
1175 		} else {
1176 			if (asystem("%s -p %s", MKDIR, home) != 0) {
1177 				int saved_errno = errno;
1178 				close(ptmpfd);
1179 				pw_abort();
1180 				errc(EXIT_FAILURE, saved_errno,
1181 				    "can't mkdir `%s'", home);
1182 			}
1183 			(void) copydotfiles(up->u_skeldir, home);
1184 			(void) asystem("%s -R -P %u:%u %s", CHOWN, up->u_uid,
1185 			    gid, home);
1186 			(void) asystem("%s -R u+w %s", CHMOD, home);
1187 		}
1188 	}
1189 	if (strcmp(up->u_primgrp, "=uid") == 0 &&
1190 	    getgrnam(login_name) == NULL &&
1191 	    !creategid(login_name, gid, "")) {
1192 		close(ptmpfd);
1193 		pw_abort();
1194 		errx(EXIT_FAILURE, "can't create gid %u for login name %s",
1195 		    gid, login_name);
1196 	}
1197 	if (up->u_groupc > 0 && !append_group(login_name, up->u_groupc, up->u_groupv)) {
1198 		close(ptmpfd);
1199 		pw_abort();
1200 		errx(EXIT_FAILURE, "can't append `%s' to new groups", login_name);
1201 	}
1202 	fclose(fp);
1203 	close(ptmpfd);
1204 	if (pw_mkdb(yp ? NULL : login_name, 0) < 0) {
1205 		pw_abort();
1206 		err(EXIT_FAILURE, "pw_mkdb failed");
1207 	}
1208 	syslog(LOG_INFO, "new user added: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1209 		login_name, up->u_uid, gid, home, up->u_shell);
1210 	return 1;
1211 }
1212 
1213 /* remove a user from the groups file */
1214 static int
1215 rm_user_from_groups(char *login_name)
1216 {
1217 	struct stat	st;
1218 	size_t		login_len;
1219 	FILE		*from;
1220 	FILE		*to;
1221 	char		buf[LINE_MAX];
1222 	char		f[MaxFileNameLen];
1223 	char		*cp, *ep;
1224 	int		fd;
1225 	int		cc;
1226 
1227 	login_len = strlen(login_name);
1228 	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
1229 		warn("can't remove gid for `%s': can't open `%s'",
1230 		    login_name, _PATH_GROUP);
1231 		return 0;
1232 	}
1233 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
1234 		warn("can't lock `%s'", _PATH_GROUP);
1235 	}
1236 	(void) fstat(fileno(from), &st);
1237 	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
1238 	if ((fd = mkstemp(f)) < 0) {
1239 		warn("can't remove gid for `%s': mkstemp failed", login_name);
1240 		fclose(from);
1241 		return 0;
1242 	}
1243 	if ((to = fdopen(fd, "w")) == NULL) {
1244 		warn("can't remove gid for `%s': fdopen `%s' failed",
1245 		    login_name, f);
1246 		fclose(from);
1247 		close(fd);
1248 		unlink(f);
1249 		return 0;
1250 	}
1251 	while (fgets(buf, sizeof(buf), from) != NULL) {
1252 		cc = strlen(buf);
1253 		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
1254 			while (fgetc(from) != '\n' && !feof(from))
1255 				cc++;
1256 			warnx("%s: line `%s' too long (%d bytes), skipping",
1257 			    _PATH_GROUP, buf, cc);
1258 			continue;
1259 		}
1260 
1261 		/* Break out the group list. */
1262 		for (cp = buf, cc = 0; *cp != '\0' && cc < 3; cp++) {
1263 			if (*cp == ':')
1264 				cc++;
1265 		}
1266 		if (cc != 3) {
1267 			buf[strcspn(buf, "\n")] = '\0';
1268 			warnx("Malformed entry `%s'. Skipping", buf);
1269 			continue;
1270 		}
1271 		while ((cp = strstr(cp, login_name)) != NULL) {
1272 			if ((cp[-1] == ':' || cp[-1] == ',') &&
1273 			    (cp[login_len] == ',' || cp[login_len] == '\n')) {
1274 				ep = cp + login_len;
1275 				if (cp[login_len] == ',')
1276 					ep++;
1277 				else if (cp[-1] == ',')
1278 					cp--;
1279 				memmove(cp, ep, strlen(ep) + 1);
1280 			} else {
1281 				if ((cp = strchr(cp, ',')) == NULL)
1282 					break;
1283 				cp++;
1284 			}
1285 		}
1286 		if (fwrite(buf, strlen(buf), 1, to) != 1) {
1287 			warn("can't remove gid for `%s': short write to `%s'",
1288 			    login_name, f);
1289 			fclose(from);
1290 			fclose(to);
1291 			unlink(f);
1292 			return 0;
1293 		}
1294 	}
1295 	(void) fchmod(fileno(to), st.st_mode & 0777);
1296 	fclose(from);
1297 	if (fclose(to) == EOF) {
1298 		warn("can't remove gid for `%s': short write to `%s'",
1299 		    login_name, f);
1300 		unlink(f);
1301 		return 0;
1302 	}
1303 	if (rename(f, _PATH_GROUP) < 0) {
1304 		warn("can't remove gid for `%s': can't rename `%s' to `%s'",
1305 		    login_name, f, _PATH_GROUP);
1306 		unlink(f);
1307 		return 0;
1308 	}
1309 	return 1;
1310 }
1311 
1312 /* check that the user or group is local, not from YP/NIS */
1313 static int
1314 is_local(char *name, const char *file)
1315 {
1316 	FILE		*fp;
1317 	char		buf[LINE_MAX];
1318 	size_t		len;
1319 	int		ret;
1320 	int		cc;
1321 
1322 	if ((fp = fopen(file, "r")) == NULL) {
1323 		err(EXIT_FAILURE, "can't open `%s'", file);
1324 	}
1325 	len = strlen(name);
1326 	for (ret = 0 ; fgets(buf, sizeof(buf), fp) != NULL ; ) {
1327 		cc = strlen(buf);
1328 		if (cc > 0 && buf[cc - 1] != '\n' && !feof(fp)) {
1329 			while (fgetc(fp) != '\n' && !feof(fp))
1330 				cc++;
1331 			warnx("%s: line `%s' too long (%d bytes), skipping",
1332 			    file, buf, cc);
1333 			continue;
1334 		}
1335 		if (strncmp(buf, name, len) == 0 && buf[len] == ':') {
1336 			ret = 1;
1337 			break;
1338 		}
1339 	}
1340 	fclose(fp);
1341 	return ret;
1342 }
1343 
1344 /* modify a user */
1345 static int
1346 moduser(char *login_name, char *newlogin, user_t *up)
1347 {
1348 	struct passwd	*pwp = NULL;
1349 	struct group	*grp;
1350 	const char	*homedir;
1351 	char		buf[LINE_MAX];
1352 	char		acctlock_str[] = "-";
1353 	char		pwlock_str[] = "*";
1354 	char		pw_len[PasswordLength + 1];
1355 	char		shell_len[MaxShellNameLen];
1356 	char		*shell_last_char;
1357 	size_t		colonc, loginc;
1358 	size_t		cc;
1359 	size_t		shell_buf;
1360 	FILE		*master;
1361 	char		newdir[MaxFileNameLen];
1362 	char		*colon;
1363 	char		*pw_tmp = NULL;
1364 	char		*shell_tmp = NULL;
1365 	int		len;
1366 	int		locked = 0;
1367 	int		unlocked = 0;
1368 	int		masterfd;
1369 	int		ptmpfd;
1370 	int		rval;
1371 	int		i;
1372 
1373 	if (!valid_login(newlogin)) {
1374 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
1375 	}
1376 	if ((pwp = getpwnam_shadow(login_name)) == NULL) {
1377 		errx(EXIT_FAILURE, "No such user `%s'", login_name);
1378 	}
1379 	if (up != NULL) {
1380 		if ((*pwp->pw_passwd != '\0') &&
1381 		    (up->u_flags & F_PASSWORD) == 0) {
1382 			up->u_flags |= F_PASSWORD;
1383 			memsave(&up->u_password, pwp->pw_passwd,
1384 			    strlen(pwp->pw_passwd));
1385 			explicit_bzero(pwp->pw_passwd, strlen(pwp->pw_passwd));
1386 		}
1387 	}
1388 	endpwent();
1389 
1390 	if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
1391 	    NULL) == -1)
1392 		err(1, "pledge");
1393 
1394 	if (!is_local(login_name, _PATH_MASTERPASSWD)) {
1395 		errx(EXIT_FAILURE, "User `%s' must be a local user", login_name);
1396 	}
1397 	if (up != NULL) {
1398 		if ((up->u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)) && (pwp->pw_uid < 1000))
1399 			errx(EXIT_FAILURE, "(un)locking is not supported for the `%s' account", pwp->pw_name);
1400 	}
1401 	/* keep dir name in case we need it for '-m' */
1402 	homedir = pwp->pw_dir;
1403 
1404 	/* get the last char of the shell in case we need it for '-U' or '-Z' */
1405 	shell_last_char = pwp->pw_shell+strlen(pwp->pw_shell) - 1;
1406 
1407 	if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
1408 		err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
1409 	}
1410 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
1411 		err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
1412 	}
1413 	pw_init();
1414 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
1415 		int saved_errno = errno;
1416 		close(masterfd);
1417 		errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
1418 	}
1419 	if ((master = fdopen(masterfd, "r")) == NULL) {
1420 		int saved_errno = errno;
1421 		close(masterfd);
1422 		close(ptmpfd);
1423 		pw_abort();
1424 		errc(EXIT_FAILURE, saved_errno, "can't fdopen fd for %s",
1425 		    _PATH_MASTERPASSWD);
1426 	}
1427 	if (up != NULL) {
1428 		if (up->u_flags & F_USERNAME) {
1429 			/* if changing name, check new name isn't already in use */
1430 			if (strcmp(login_name, newlogin) != 0 && getpwnam(newlogin) != NULL) {
1431 				close(ptmpfd);
1432 				pw_abort();
1433 				errx(EXIT_FAILURE, "already a `%s' user", newlogin);
1434 			}
1435 			pwp->pw_name = newlogin;
1436 
1437 			/*
1438 			 * Provide a new directory name in case the
1439 			 * home directory is to be moved.
1440 			 */
1441 			if (up->u_flags & F_MKDIR) {
1442 				(void) snprintf(newdir, sizeof(newdir),
1443 				    "%s/%s", up->u_basedir, newlogin);
1444 				pwp->pw_dir = newdir;
1445 			}
1446 		}
1447 		if (up->u_flags & F_PASSWORD) {
1448 			if (up->u_password != NULL)
1449 				pwp->pw_passwd = up->u_password;
1450 		}
1451 		if (up->u_flags & F_ACCTLOCK) {
1452 			/* lock the account */
1453 			if (*shell_last_char != *acctlock_str) {
1454 				shell_tmp = malloc(strlen(pwp->pw_shell) + sizeof(acctlock_str));
1455 				if (shell_tmp == NULL) {
1456 					close(ptmpfd);
1457 					pw_abort();
1458 					errx(EXIT_FAILURE, "account lock: cannot allocate memory");
1459 				}
1460 				strlcpy(shell_tmp, pwp->pw_shell, sizeof(shell_len));
1461 				strlcat(shell_tmp, acctlock_str, sizeof(shell_len));
1462 				pwp->pw_shell = shell_tmp;
1463 			} else {
1464 				locked++;
1465 			}
1466 			/* lock the password */
1467 			if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) != 0) {
1468 				pw_tmp = malloc(strlen(pwp->pw_passwd) + sizeof(pwlock_str));
1469 				if (pw_tmp == NULL) {
1470 					close(ptmpfd);
1471 					pw_abort();
1472 					errx(EXIT_FAILURE, "password lock: cannot allocate memory");
1473 				}
1474 				strlcpy(pw_tmp, pwlock_str, sizeof(pw_len));
1475 				strlcat(pw_tmp, pwp->pw_passwd, sizeof(pw_len));
1476 				pwp->pw_passwd = pw_tmp;
1477 			} else {
1478 				locked++;
1479 			}
1480 
1481 			if (locked > 1)
1482 				warnx("account `%s' is already locked", pwp->pw_name);
1483 		}
1484 		if (up->u_flags & F_ACCTUNLOCK) {
1485 			/* unlock the password */
1486 			if (strcmp(pwp->pw_passwd, pwlock_str) != 0 &&
1487 			    strcmp(pwp->pw_passwd, "*************") != 0) {
1488 				if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) == 0) {
1489 					pwp->pw_passwd += sizeof(pwlock_str)-1;
1490 				} else {
1491 					unlocked++;
1492 				}
1493 			} else {
1494 				warnx("account `%s' has no password: cannot fully unlock", pwp->pw_name);
1495 			}
1496 			/* unlock the account */
1497 			if (*shell_last_char == *acctlock_str) {
1498 				shell_buf = strlen(pwp->pw_shell) + 2 - sizeof(acctlock_str);
1499 				shell_tmp = malloc(shell_buf);
1500 				if (shell_tmp == NULL) {
1501 					close(ptmpfd);
1502 					pw_abort();
1503 					errx(EXIT_FAILURE, "unlock: cannot allocate memory");
1504 				}
1505 				strlcpy(shell_tmp, pwp->pw_shell, shell_buf);
1506 				pwp->pw_shell = shell_tmp;
1507 			} else {
1508 				unlocked++;
1509 			}
1510 
1511 			if (unlocked > 1)
1512 				warnx("account `%s' is not locked", pwp->pw_name);
1513 		}
1514 		if (up->u_flags & F_UID) {
1515 			/* check uid isn't already allocated */
1516 			if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
1517 				close(ptmpfd);
1518 				pw_abort();
1519 				errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1520 			}
1521 			pwp->pw_uid = up->u_uid;
1522 		}
1523 		if (up->u_flags & F_GROUP) {
1524 			/* if -g=uid was specified, check gid is unused */
1525 			if (strcmp(up->u_primgrp, "=uid") == 0) {
1526 				if (getgrgid((gid_t)(pwp->pw_uid)) != NULL) {
1527 					close(ptmpfd);
1528 					pw_abort();
1529 					errx(EXIT_FAILURE, "gid %u is already "
1530 					    "in use", pwp->pw_uid);
1531 				}
1532 				pwp->pw_gid = pwp->pw_uid;
1533 				if (!creategid(newlogin, pwp->pw_gid, "")) {
1534 					close(ptmpfd);
1535 					pw_abort();
1536 					errx(EXIT_FAILURE, "could not create "
1537 					    "group %s with gid %u", newlogin,
1538 					    pwp->pw_gid);
1539 				}
1540 			} else {
1541 				if ((grp = find_group_info(up->u_primgrp)) == NULL) {
1542 					close(ptmpfd);
1543 					pw_abort();
1544 					errx(EXIT_FAILURE, "group %s not found",
1545 					    up->u_primgrp);
1546 				}
1547 				pwp->pw_gid = grp->gr_gid;
1548 			}
1549 		}
1550 		if (up->u_flags & F_INACTIVE) {
1551 			if (!scantime(&pwp->pw_change, up->u_inactive)) {
1552 				warnx("Warning: inactive time `%s' invalid, password expiry off",
1553 					up->u_inactive);
1554 			}
1555 		}
1556 		if (up->u_flags & F_EXPIRE) {
1557 			if (!scantime(&pwp->pw_expire, up->u_expire)) {
1558 				warnx("Warning: expire time `%s' invalid, account expiry off",
1559 					up->u_expire);
1560 			}
1561 		}
1562 		if (up->u_flags & F_COMMENT)
1563 			pwp->pw_gecos = up->u_comment;
1564 		if (up->u_flags & F_HOMEDIR)
1565 			pwp->pw_dir = up->u_home;
1566 		if (up->u_flags & F_SHELL)
1567 			pwp->pw_shell = up->u_shell;
1568 		if (up->u_flags & F_CLASS) {
1569 			if (!valid_class(up->u_class)) {
1570 				close(ptmpfd);
1571 				pw_abort();
1572 				errx(EXIT_FAILURE,
1573 				    "No such login class `%s'", up->u_class);
1574 			}
1575 			pwp->pw_class = up->u_class;
1576 		}
1577 	}
1578 	loginc = strlen(login_name);
1579 	while (fgets(buf, sizeof(buf), master) != NULL) {
1580 		if ((colon = strchr(buf, ':')) == NULL) {
1581 			warnx("Malformed entry `%s'. Skipping", buf);
1582 			continue;
1583 		}
1584 		colonc = (size_t)(colon - buf);
1585 		if (strncmp(login_name, buf, loginc) == 0 && loginc == colonc) {
1586 			if (up != NULL) {
1587 				if ((len = snprintf(buf, sizeof(buf),
1588 				    "%s:%s:%u:%u:%s:%lld:%lld:%s:%s:%s\n",
1589 				    newlogin,
1590 				    pwp->pw_passwd,
1591 				    pwp->pw_uid,
1592 				    pwp->pw_gid,
1593 				    pwp->pw_class,
1594 				    (long long)pwp->pw_change,
1595 				    (long long)pwp->pw_expire,
1596 				    pwp->pw_gecos,
1597 				    pwp->pw_dir,
1598 				    pwp->pw_shell)) >= sizeof(buf) || len < 0 ||
1599 				    len + expand_len(pwp->pw_gecos, newlogin)
1600 				    >= 1023) {
1601 					close(ptmpfd);
1602 					pw_abort();
1603 					errx(EXIT_FAILURE, "can't add `%s', "
1604 					    "line too long (%zu bytes)", buf,
1605 					    len + expand_len(pwp->pw_gecos,
1606 					    newlogin));
1607 				}
1608 				if (write(ptmpfd, buf, len) != len) {
1609 					int saved_errno = errno;
1610 					close(ptmpfd);
1611 					pw_abort();
1612 					errc(EXIT_FAILURE, saved_errno,
1613 					    "can't add `%s'", buf);
1614 				}
1615 			}
1616 		} else {
1617 			len = strlen(buf);
1618 			if ((cc = write(ptmpfd, buf, len)) != len) {
1619 				int saved_errno = errno;
1620 				close(masterfd);
1621 				close(ptmpfd);
1622 				pw_abort();
1623 				errc(EXIT_FAILURE, saved_errno,
1624 				    "short write to /etc/ptmp (%lld not %lld chars)",
1625 				    (long long)cc, (long long)len);
1626 			}
1627 		}
1628 	}
1629 	if (up != NULL) {
1630 		if ((up->u_flags & F_MKDIR) &&
1631 		    asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) {
1632 			int saved_errno = errno;
1633 			close(ptmpfd);
1634 			pw_abort();
1635 			errc(EXIT_FAILURE, saved_errno,
1636 			    "can't move `%s' to `%s'", homedir, pwp->pw_dir);
1637 		}
1638 		if (up->u_flags & F_SETSECGROUP) {
1639 			for (i = 0 ; i < up->u_groupc ; i++) {
1640 				if (getgrnam(up->u_groupv[i]) == NULL) {
1641 					close(ptmpfd);
1642 					pw_abort();
1643 					errx(EXIT_FAILURE,
1644 					    "aborting, group `%s' does not exist",
1645 					    up->u_groupv[i]);
1646 				}
1647 			}
1648 			if (!rm_user_from_groups(newlogin)) {
1649 				close(ptmpfd);
1650 				pw_abort();
1651 				errx(EXIT_FAILURE,
1652 				    "can't reset groups for `%s'", newlogin);
1653 			}
1654 		}
1655 		if (up->u_groupc > 0) {
1656 		    if (!append_group(newlogin, up->u_groupc, up->u_groupv)) {
1657 			close(ptmpfd);
1658 			pw_abort();
1659 			errx(EXIT_FAILURE, "can't append `%s' to new groups",
1660 			    newlogin);
1661 		    }
1662 		}
1663 	}
1664 	fclose(master);
1665 	close(ptmpfd);
1666 	free(pw_tmp);
1667 	free(shell_tmp);
1668 	if (up != NULL && strcmp(login_name, newlogin) == 0)
1669 		rval = pw_mkdb(login_name, 0);
1670 	else
1671 		rval = pw_mkdb(NULL, 0);
1672 	if (rval == -1) {
1673 		pw_abort();
1674 		err(EXIT_FAILURE, "pw_mkdb failed");
1675 	}
1676 	if (up == NULL) {
1677 		syslog(LOG_INFO, "user removed: name=%s", login_name);
1678 	} else if (strcmp(login_name, newlogin) == 0) {
1679 		syslog(LOG_INFO, "user information modified: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1680 			login_name, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, pwp->pw_shell);
1681 	} else {
1682 		syslog(LOG_INFO, "user information modified: name=%s, new name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1683 			login_name, newlogin, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, pwp->pw_shell);
1684 	}
1685 	return 1;
1686 }
1687 
1688 /* print out usage message, and then exit */
1689 void
1690 usermgmt_usage(const char *prog)
1691 {
1692 	if (strcmp(prog, "useradd") == 0) {
1693 		fprintf(stderr, "usage: %s -D [-b base-directory] "
1694 		    "[-e expiry-time] [-f inactive-time]\n"
1695 		    "               [-g gid | name | =uid] [-k skel-directory] "
1696 		    "[-L login-class]\n"
1697 		    "               [-r low..high] [-s shell]\n", prog);
1698 		fprintf(stderr, "       %s [-mov] [-b base-directory] "
1699 		    "[-c comment] [-d home-directory]\n"
1700 		    "               [-e expiry-time] [-f inactive-time]\n"
1701 		    "               [-G secondary-group[,group,...]] "
1702 		    "[-g gid | name | =uid]\n"
1703 		    "               [-k skel-directory] [-L login-class] "
1704 		    "[-p password] [-r low..high]\n"
1705 		    "               [-s shell] [-u uid] user\n", prog);
1706 	} else if (strcmp(prog, "usermod") == 0) {
1707 		fprintf(stderr, "usage: %s [-moUvZ] "
1708 		    "[-c comment] [-d home-directory] [-e expiry-time]\n"
1709 		    "               [-f inactive-time] "
1710 		    "[-G secondary-group[,group,...]]\n"
1711 		    "               [-g gid | name | =uid] [-L login-class] "
1712 		    "[-l new-login]\n"
1713 		    "               [-p password] "
1714 		    "[-S secondary-group[,group,...]]\n"
1715 		    "               [-s shell] [-u uid] user\n",
1716 		    prog);
1717 	} else if (strcmp(prog, "userdel") == 0) {
1718 		fprintf(stderr, "usage: %s -D [-p preserve-value]\n",
1719 		    prog);
1720 		fprintf(stderr, "       %s [-rv] [-p preserve-value] "
1721 		    "user\n", prog);
1722 	} else if (strcmp(prog, "userinfo") == 0) {
1723 		fprintf(stderr, "usage: %s [-e] user\n", prog);
1724 	} else if (strcmp(prog, "groupadd") == 0) {
1725 		fprintf(stderr, "usage: %s [-ov] [-g gid] group\n",
1726 		    prog);
1727 	} else if (strcmp(prog, "groupdel") == 0) {
1728 		fprintf(stderr, "usage: %s [-v] group\n", prog);
1729 	} else if (strcmp(prog, "groupmod") == 0) {
1730 		fprintf(stderr, "usage: %s [-ov] [-g gid] [-n newname] "
1731 		    "group\n", prog);
1732 	} else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) {
1733 		fprintf(stderr, "usage: %s [add | del | mod"
1734 		" | info"
1735 		"] ...\n",
1736 		    prog);
1737 	} else if (strcmp(prog, "groupinfo") == 0) {
1738 		fprintf(stderr, "usage: %s [-e] group\n", prog);
1739 	} else {
1740 		fprintf(stderr, "This program must be called as {user,group}{add,del,mod,info},\n%s is not an understood name.\n", prog);
1741 	}
1742 	exit(EXIT_FAILURE);
1743 }
1744 
1745 int
1746 useradd(int argc, char **argv)
1747 {
1748 	user_t	u;
1749 	const char *errstr;
1750 	int	defaultfield;
1751 	int	bigD;
1752 	int	c;
1753 	int	i;
1754 
1755 	memset(&u, 0, sizeof(u));
1756 	read_defaults(&u);
1757 	u.u_uid = UID_MAX;
1758 	defaultfield = bigD = 0;
1759 	while ((c = getopt(argc, argv, "DG:L:b:c:d:e:f:g:k:mop:r:s:u:v")) != -1) {
1760 		switch(c) {
1761 		case 'D':
1762 			bigD = 1;
1763 			break;
1764 		case 'G':
1765 			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1766 			    u.u_groupc < NGROUPS_MAX - 2) {
1767 				if (u.u_groupv[u.u_groupc][0] != 0) {
1768 					u.u_groupc++;
1769 				}
1770 			}
1771 			if (optarg != NULL) {
1772 				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1773 			}
1774 			break;
1775 		case 'b':
1776 			defaultfield = 1;
1777 			memsave(&u.u_basedir, optarg, strlen(optarg));
1778 			break;
1779 		case 'c':
1780 			memsave(&u.u_comment, optarg, strlen(optarg));
1781 			break;
1782 		case 'd':
1783 			memsave(&u.u_home, optarg, strlen(optarg));
1784 			u.u_flags |= F_HOMEDIR;
1785 			break;
1786 		case 'e':
1787 			defaultfield = 1;
1788 			memsave(&u.u_expire, optarg, strlen(optarg));
1789 			break;
1790 		case 'f':
1791 			defaultfield = 1;
1792 			memsave(&u.u_inactive, optarg, strlen(optarg));
1793 			break;
1794 		case 'g':
1795 			defaultfield = 1;
1796 			memsave(&u.u_primgrp, optarg, strlen(optarg));
1797 			break;
1798 		case 'k':
1799 			defaultfield = 1;
1800 			memsave(&u.u_skeldir, optarg, strlen(optarg));
1801 			break;
1802 		case 'L':
1803 			defaultfield = 1;
1804 			memsave(&u.u_class, optarg, strlen(optarg));
1805 			break;
1806 		case 'm':
1807 			u.u_flags |= F_MKDIR;
1808 			break;
1809 		case 'o':
1810 			u.u_flags |= F_DUPUID;
1811 			break;
1812 		case 'p':
1813 			memsave(&u.u_password, optarg, strlen(optarg));
1814 			explicit_bzero(optarg, strlen(optarg));
1815 			break;
1816 		case 'r':
1817 			defaultfield = 1;
1818 			(void) save_range(&u, optarg);
1819 			break;
1820 		case 's':
1821 			defaultfield = 1;
1822 			memsave(&u.u_shell, optarg, strlen(optarg));
1823 			break;
1824 		case 'u':
1825 			u.u_uid = strtonum(optarg, -1, UID_MAX, &errstr);
1826 			if (errstr != NULL) {
1827 				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1828 			}
1829 			break;
1830 		case 'v':
1831 			verbose = 1;
1832 			break;
1833 		default:
1834 			usermgmt_usage("useradd");
1835 		}
1836 	}
1837 
1838 	if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
1839 	    NULL) == -1)
1840 		err(1, "pledge");
1841 
1842 	if (bigD) {
1843 		if (defaultfield) {
1844 			checkeuid();
1845 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
1846 		}
1847 		printf("group\t\t%s\n", u.u_primgrp);
1848 		printf("base_dir\t%s\n", u.u_basedir);
1849 		printf("skel_dir\t%s\n", u.u_skeldir);
1850 		printf("shell\t\t%s\n", u.u_shell);
1851 		printf("class\t\t%s\n", u.u_class);
1852 		printf("inactive\t%s\n", (u.u_inactive == NULL) ? UNSET_INACTIVE : u.u_inactive);
1853 		printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire);
1854 		for (i = 0 ; i < u.u_rc ; i++) {
1855 			printf("range\t\t%u..%u\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
1856 		}
1857 		return EXIT_SUCCESS;
1858 	}
1859 	argc -= optind;
1860 	argv += optind;
1861 	if (argc != 1) {
1862 		usermgmt_usage("useradd");
1863 	}
1864 	checkeuid();
1865 	openlog("useradd", LOG_PID, LOG_USER);
1866 	return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1867 }
1868 
1869 int
1870 usermod(int argc, char **argv)
1871 {
1872 	user_t	u;
1873 	char	newuser[MaxUserNameLen + 1];
1874 	int	c, have_new_user;
1875 	const char *errstr;
1876 
1877 	memset(&u, 0, sizeof(u));
1878 	memset(newuser, 0, sizeof(newuser));
1879 	read_defaults(&u);
1880 	free(u.u_primgrp);
1881 	u.u_primgrp = NULL;
1882 	have_new_user = 0;
1883 	while ((c = getopt(argc, argv, "G:L:S:UZc:d:e:f:g:l:mop:s:u:v")) != -1) {
1884 		switch(c) {
1885 		case 'G':
1886 			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1887 			    u.u_groupc < NGROUPS_MAX - 2) {
1888 				if (u.u_groupv[u.u_groupc][0] != 0) {
1889 					u.u_groupc++;
1890 				}
1891 			}
1892 			if (optarg != NULL) {
1893 				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1894 			}
1895 			u.u_flags |= F_SECGROUP;
1896 			break;
1897 		case 'S':
1898 			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1899 			    u.u_groupc < NGROUPS_MAX - 2) {
1900 				if (u.u_groupv[u.u_groupc][0] != 0) {
1901 					u.u_groupc++;
1902 				}
1903 			}
1904 			if (optarg != NULL) {
1905 				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1906 			}
1907 			u.u_flags |= F_SETSECGROUP;
1908 			break;
1909 		case 'U':
1910 			u.u_flags |= F_ACCTUNLOCK;
1911 			break;
1912 		case 'Z':
1913 			u.u_flags |= F_ACCTLOCK;
1914 			break;
1915 		case 'c':
1916 			memsave(&u.u_comment, optarg, strlen(optarg));
1917 			u.u_flags |= F_COMMENT;
1918 			break;
1919 		case 'd':
1920 			memsave(&u.u_home, optarg, strlen(optarg));
1921 			u.u_flags |= F_HOMEDIR;
1922 			break;
1923 		case 'e':
1924 			memsave(&u.u_expire, optarg, strlen(optarg));
1925 			u.u_flags |= F_EXPIRE;
1926 			break;
1927 		case 'f':
1928 			memsave(&u.u_inactive, optarg, strlen(optarg));
1929 			u.u_flags |= F_INACTIVE;
1930 			break;
1931 		case 'g':
1932 			memsave(&u.u_primgrp, optarg, strlen(optarg));
1933 			u.u_flags |= F_GROUP;
1934 			break;
1935 		case 'l':
1936 			if (strlcpy(newuser, optarg, sizeof(newuser)) >=
1937 			    sizeof(newuser))
1938 				errx(EXIT_FAILURE, "username `%s' too long",
1939 				    optarg);
1940 			have_new_user = 1;
1941 			u.u_flags |= F_USERNAME;
1942 			break;
1943 		case 'L':
1944 			memsave(&u.u_class, optarg, strlen(optarg));
1945 			u.u_flags |= F_CLASS;
1946 			break;
1947 		case 'm':
1948 			u.u_flags |= F_MKDIR;
1949 			break;
1950 		case 'o':
1951 			u.u_flags |= F_DUPUID;
1952 			break;
1953 		case 'p':
1954 			memsave(&u.u_password, optarg, strlen(optarg));
1955 			explicit_bzero(optarg, strlen(optarg));
1956 			u.u_flags |= F_PASSWORD;
1957 			break;
1958 		case 's':
1959 			memsave(&u.u_shell, optarg, strlen(optarg));
1960 			u.u_flags |= F_SHELL;
1961 			break;
1962 		case 'u':
1963 			u.u_uid = strtonum(optarg, -1, UID_MAX, &errstr);
1964 			u.u_flags |= F_UID;
1965 			if (errstr != NULL) {
1966 				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1967 			}
1968 			break;
1969 		case 'v':
1970 			verbose = 1;
1971 			break;
1972 		default:
1973 			usermgmt_usage("usermod");
1974 		}
1975 	}
1976 
1977 	if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) &&
1978 	    !(u.u_flags & F_USERNAME)) {
1979 		warnx("option 'm' useless without 'd' or 'l' -- ignored");
1980 		u.u_flags &= ~F_MKDIR;
1981 	}
1982 	if ((u.u_flags & F_SECGROUP) && (u.u_flags & F_SETSECGROUP))
1983 		errx(EXIT_FAILURE, "options 'G' and 'S' are mutually exclusive");
1984 	if ((u.u_flags & F_ACCTLOCK) && (u.u_flags & F_ACCTUNLOCK))
1985 		errx(EXIT_FAILURE, "options 'U' and 'Z' are mutually exclusive");
1986 	if ((u.u_flags & F_PASSWORD) && (u.u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)))
1987 		errx(EXIT_FAILURE, "options 'U' or 'Z' with 'p' are mutually exclusive");
1988 	argc -= optind;
1989 	argv += optind;
1990 	if (argc != 1) {
1991 		usermgmt_usage("usermod");
1992 	}
1993 	checkeuid();
1994 	openlog("usermod", LOG_PID, LOG_USER);
1995 	return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ?
1996 	    EXIT_SUCCESS : EXIT_FAILURE;
1997 }
1998 
1999 int
2000 userdel(int argc, char **argv)
2001 {
2002 	struct passwd	*pwp;
2003 	user_t		u;
2004 	int		defaultfield;
2005 	int		rmhome;
2006 	int		bigD;
2007 	int		c;
2008 
2009 	memset(&u, 0, sizeof(u));
2010 	read_defaults(&u);
2011 	defaultfield = bigD = rmhome = 0;
2012 	while ((c = getopt(argc, argv, "Dp:rv")) != -1) {
2013 		switch(c) {
2014 		case 'D':
2015 			bigD = 1;
2016 			break;
2017 		case 'p':
2018 			defaultfield = 1;
2019 			u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
2020 					(strcmp(optarg, "yes") == 0) ? 1 :
2021 					 strtonum(optarg, INT_MIN, INT_MAX, NULL);
2022 			break;
2023 		case 'r':
2024 			rmhome = 1;
2025 			break;
2026 		case 'v':
2027 			verbose = 1;
2028 			break;
2029 		default:
2030 			usermgmt_usage("userdel");
2031 		}
2032 	}
2033 	if (bigD) {
2034 		if (defaultfield) {
2035 			checkeuid();
2036 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
2037 		}
2038 		printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
2039 		return EXIT_SUCCESS;
2040 	}
2041 	argc -= optind;
2042 	argv += optind;
2043 	if (argc != 1) {
2044 		usermgmt_usage("userdel");
2045 	}
2046 
2047 	if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
2048 	    NULL) == -1)
2049 		err(1, "pledge");
2050 
2051 	checkeuid();
2052 	if ((pwp = getpwnam(*argv)) == NULL) {
2053 		warnx("No such user `%s'", *argv);
2054 		return EXIT_FAILURE;
2055 	}
2056 	if (rmhome)
2057 		(void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir);
2058 	if (u.u_preserve) {
2059 		u.u_flags |= F_SHELL;
2060 		memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN));
2061 		memsave(&u.u_password, "*", strlen("*"));
2062 		u.u_flags |= F_PASSWORD;
2063 		openlog("userdel", LOG_PID, LOG_USER);
2064 		return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
2065 	}
2066 	if (!rm_user_from_groups(*argv)) {
2067 		return 0;
2068 	}
2069 	openlog("userdel", LOG_PID, LOG_USER);
2070 	return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
2071 }
2072 
2073 /* add a group */
2074 int
2075 groupadd(int argc, char **argv)
2076 {
2077 	int	dupgid;
2078 	int	gid;
2079 	int	c;
2080 	const char *errstr;
2081 
2082 	gid = GID_MAX;
2083 	dupgid = 0;
2084 	while ((c = getopt(argc, argv, "g:ov")) != -1) {
2085 		switch(c) {
2086 		case 'g':
2087 			gid = strtonum(optarg, -1, GID_MAX, &errstr);
2088 			if (errstr != NULL) {
2089 				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2090 			}
2091 			break;
2092 		case 'o':
2093 			dupgid = 1;
2094 			break;
2095 		case 'v':
2096 			verbose = 1;
2097 			break;
2098 		default:
2099 			usermgmt_usage("groupadd");
2100 		}
2101 	}
2102 	argc -= optind;
2103 	argv += optind;
2104 	if (argc != 1) {
2105 		usermgmt_usage("groupadd");
2106 	}
2107 
2108 	if (pledge("stdio rpath wpath cpath fattr flock getpw", NULL) == -1)
2109 		err(1, "pledge");
2110 
2111 	checkeuid();
2112 	if (!valid_group(*argv)) {
2113 		errx(EXIT_FAILURE, "invalid group name `%s'", *argv);
2114 	}
2115 	if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
2116 		errx(EXIT_FAILURE, "can't add group: can't get next gid");
2117 	}
2118 	if (!dupgid && getgrgid((gid_t) gid) != NULL) {
2119 		errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
2120 	}
2121 	openlog("groupadd", LOG_PID, LOG_USER);
2122 	if (!creategid(*argv, gid, "")) {
2123 		errx(EXIT_FAILURE, "can't add group: problems with %s file",
2124 		    _PATH_GROUP);
2125 	}
2126 	return EXIT_SUCCESS;
2127 }
2128 
2129 /* remove a group */
2130 int
2131 groupdel(int argc, char **argv)
2132 {
2133 	int	c;
2134 
2135 	while ((c = getopt(argc, argv, "v")) != -1) {
2136 		switch(c) {
2137 		case 'v':
2138 			verbose = 1;
2139 			break;
2140 		default:
2141 			usermgmt_usage("groupdel");
2142 		}
2143 	}
2144 	argc -= optind;
2145 	argv += optind;
2146 	if (argc != 1) {
2147 		usermgmt_usage("groupdel");
2148 	}
2149 	checkeuid();
2150 	openlog("groupdel", LOG_PID, LOG_USER);
2151 	if (getgrnam(*argv) == NULL) {
2152 		warnx("No such group: `%s'", *argv);
2153 		return EXIT_FAILURE;
2154 	}
2155 
2156 	if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1)
2157 		err(1, "pledge");
2158 
2159 	if (!modify_gid(*argv, NULL)) {
2160 		err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2161 	}
2162 	return EXIT_SUCCESS;
2163 }
2164 
2165 /* modify a group */
2166 int
2167 groupmod(int argc, char **argv)
2168 {
2169 	struct group	*grp;
2170 	const char	*errstr;
2171 	char		buf[LINE_MAX];
2172 	char		*newname;
2173 	char		**cpp;
2174 	int		dupgid;
2175 	int		gid;
2176 	int		cc;
2177 	int		c;
2178 
2179 	gid = GID_MAX;
2180 	dupgid = 0;
2181 	newname = NULL;
2182 	while ((c = getopt(argc, argv, "g:n:ov")) != -1) {
2183 		switch(c) {
2184 		case 'g':
2185 			gid = strtonum(optarg, -1, GID_MAX, &errstr);
2186 			if (errstr != NULL) {
2187 				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2188 			}
2189 			break;
2190 		case 'o':
2191 			dupgid = 1;
2192 			break;
2193 		case 'n':
2194 			memsave(&newname, optarg, strlen(optarg));
2195 			break;
2196 		case 'v':
2197 			verbose = 1;
2198 			break;
2199 		default:
2200 			usermgmt_usage("groupmod");
2201 		}
2202 	}
2203 	argc -= optind;
2204 	argv += optind;
2205 	if (argc != 1) {
2206 		usermgmt_usage("groupmod");
2207 	}
2208 	checkeuid();
2209 	if (gid < 0 && newname == NULL) {
2210 		errx(EXIT_FAILURE, "Nothing to change");
2211 	}
2212 	if (dupgid && gid < 0) {
2213 		errx(EXIT_FAILURE, "Duplicate which gid?");
2214 	}
2215 	if ((grp = getgrnam(*argv)) == NULL) {
2216 		errx(EXIT_FAILURE, "can't find group `%s' to modify", *argv);
2217 	}
2218 
2219 	if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1)
2220 		err(1, "pledge");
2221 
2222 	if (!is_local(*argv, _PATH_GROUP)) {
2223 		errx(EXIT_FAILURE, "Group `%s' must be a local group", *argv);
2224 	}
2225 	if (newname != NULL && !valid_group(newname)) {
2226 		errx(EXIT_FAILURE, "invalid group name `%s'", newname);
2227 	}
2228 	if ((cc = snprintf(buf, sizeof(buf), "%s:%s:%u:",
2229 	    (newname) ? newname : grp->gr_name, grp->gr_passwd,
2230 	    (gid < 0) ? grp->gr_gid : gid)) >= sizeof(buf) || cc < 0)
2231 		errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2232 
2233 	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2234 		cc = strlcat(buf, *cpp, sizeof(buf)) + 1;
2235 		if (cc >= sizeof(buf))
2236 			errx(EXIT_FAILURE, "group `%s' entry too long",
2237 			    grp->gr_name);
2238 		if (cpp[1] != NULL) {
2239 			buf[cc - 1] = ',';
2240 			buf[cc] = '\0';
2241 		}
2242 	}
2243 	cc = strlcat(buf, "\n", sizeof(buf));
2244 	if (cc >= sizeof(buf))
2245 		errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2246 
2247 	openlog("groupmod", LOG_PID, LOG_USER);
2248 	if (!modify_gid(*argv, buf))
2249 		err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2250 	return EXIT_SUCCESS;
2251 }
2252 
2253 /* display user information */
2254 int
2255 userinfo(int argc, char **argv)
2256 {
2257 	struct passwd	*pwp;
2258 	struct group	*grp;
2259 	char		**cpp;
2260 	int		exists;
2261 	int		i;
2262 
2263 	exists = 0;
2264 	while ((i = getopt(argc, argv, "ev")) != -1) {
2265 		switch(i) {
2266 		case 'e':
2267 			exists = 1;
2268 			break;
2269 		case 'v':
2270 			verbose = 1;
2271 			break;
2272 		default:
2273 			usermgmt_usage("userinfo");
2274 		}
2275 	}
2276 	argc -= optind;
2277 	argv += optind;
2278 	if (argc != 1) {
2279 		usermgmt_usage("userinfo");
2280 	}
2281 
2282 	if (pledge("stdio getpw", NULL) == -1)
2283 		err(1, "pledge");
2284 
2285 	pwp = find_user_info(*argv);
2286 	if (exists) {
2287 		exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE);
2288 	}
2289 	if (pwp == NULL) {
2290 		errx(EXIT_FAILURE, "can't find user `%s'", *argv);
2291 	}
2292 	printf("login\t%s\n", pwp->pw_name);
2293 	printf("passwd\t%s\n", pwp->pw_passwd);
2294 	printf("uid\t%u\n", pwp->pw_uid);
2295 	if ((grp = getgrgid(pwp->pw_gid)) == NULL)
2296 		printf("groups\t%u", pwp->pw_gid);
2297 	else
2298 		printf("groups\t%s", grp->gr_name);
2299 	while ((grp = getgrent()) != NULL) {
2300 		for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2301 			if (strcmp(*cpp, pwp->pw_name) == 0 &&
2302 			    grp->gr_gid != pwp->pw_gid)
2303 				printf(" %s", grp->gr_name);
2304 		}
2305 	}
2306 	fputc('\n', stdout);
2307 	printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n");
2308 	printf("class\t%s\n", pwp->pw_class);
2309 	printf("gecos\t%s\n", pwp->pw_gecos);
2310 	printf("dir\t%s\n", pwp->pw_dir);
2311 	printf("shell\t%s\n", pwp->pw_shell);
2312 	printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n");
2313 	return EXIT_SUCCESS;
2314 }
2315 
2316 /* display user information */
2317 int
2318 groupinfo(int argc, char **argv)
2319 {
2320 	struct group	*grp;
2321 	char		**cpp;
2322 	int		exists;
2323 	int		i;
2324 
2325 	exists = 0;
2326 	while ((i = getopt(argc, argv, "ev")) != -1) {
2327 		switch(i) {
2328 		case 'e':
2329 			exists = 1;
2330 			break;
2331 		case 'v':
2332 			verbose = 1;
2333 			break;
2334 		default:
2335 			usermgmt_usage("groupinfo");
2336 		}
2337 	}
2338 	argc -= optind;
2339 	argv += optind;
2340 	if (argc != 1) {
2341 		usermgmt_usage("groupinfo");
2342 	}
2343 
2344 	if (pledge("stdio getpw", NULL) == -1)
2345 		err(1, "pledge");
2346 
2347 	grp = find_group_info(*argv);
2348 	if (exists) {
2349 		exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE);
2350 	}
2351 	if (grp == NULL) {
2352 		errx(EXIT_FAILURE, "can't find group `%s'", *argv);
2353 	}
2354 	printf("name\t%s\n", grp->gr_name);
2355 	printf("passwd\t%s\n", grp->gr_passwd);
2356 	printf("gid\t%u\n", grp->gr_gid);
2357 	printf("members\t");
2358 	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2359 		printf("%s ", *cpp);
2360 	}
2361 	fputc('\n', stdout);
2362 	return EXIT_SUCCESS;
2363 }
2364