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