xref: /openbsd-src/usr.sbin/user/user.c (revision e5157e49389faebcb42b7237d55fbf096d9c2523)
1 /* $OpenBSD: user.c,v 1.100 2014/08/27 06:51:35 sebastia 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 NBLF "$2b"
880 #define BLF  "$2a"
881 #define MD5  "$1"
882 #define DES  ""
883 
884 static passwd_type_t	passwd_types[] = {
885 	{ NBLF,	3,	54	},	/* Blowfish bcrypt version 2b */
886 	{ BLF,	3,	54	},	/* Blowfish */
887 	{ MD5,	2,	34	},	/* MD5 */
888 	{ DES,	0,	DES_Len	},	/* standard DES */
889 	{ NULL,	-1,	-1	}	/* none - terminate search */
890 };
891 
892 /* return non-zero if it's a valid password - check length for cipher type */
893 static int
894 valid_password_length(char *newpasswd)
895 {
896 	passwd_type_t  *pwtp;
897 
898 	for (pwtp = passwd_types ; pwtp->desc_length >= 0 ; pwtp++) {
899 		if (strncmp(newpasswd, pwtp->type, pwtp->desc_length) == 0) {
900 			char *p;
901 
902 			if (strcmp(pwtp->type, BLF) != 0 &&
903 			    strcmp(pwtp->type, NBLF) != 0) {
904 				return strlen(newpasswd) == pwtp->length;
905 			}
906 			/* Skip first three `$'. */
907 			if ((p = strchr(newpasswd, '$')) == NULL ||
908 			    *(++p) == '$' || (p = strchr(p, '$')) == NULL ||
909 			    *(++p) == '$' || (p = strchr(p, '$')) == NULL)
910 				continue;
911 			return (strlen(p) - 1);
912 		}
913 	}
914 	return 0;
915 }
916 
917 /* look for a valid time, return 0 if it was specified but bad */
918 static int
919 scantime(time_t *tp, char *s)
920 {
921 	struct tm	tm;
922 
923 	*tp = 0;
924 	if (s != NULL) {
925 		(void) memset(&tm, 0, sizeof(tm));
926 		tm.tm_isdst = -1;
927 		if (strptime(s, "%c", &tm) != NULL) {
928 			*tp = mktime(&tm);
929 		} else if (strptime(s, "%B %d %Y", &tm) != NULL) {
930 			*tp = mktime(&tm);
931 		} else if (isdigit((unsigned char) s[0]) != 0) {
932 			*tp = (time_t)atoll(s);
933 		} else {
934 			return 0;
935 		}
936 	}
937 	return 1;
938 }
939 
940 /* compute the extra length '&' expansion consumes */
941 static size_t
942 expand_len(const char *p, const char *username)
943 {
944 	size_t alen;
945 	size_t ulen;
946 
947 	ulen = strlen(username);
948 	for (alen = 0; *p != '\0'; p++)
949 		if (*p == '&')
950 			alen += ulen - 1;
951 	return alen;
952 }
953 
954 /* add a user */
955 static int
956 adduser(char *login_name, user_t *up)
957 {
958 	struct group	*grp;
959 	struct stat	st;
960 	time_t		expire;
961 	time_t		inactive;
962 	char		password[PasswordLength + 1];
963 	char		home[MaxFileNameLen];
964 	char		buf[LINE_MAX];
965 	int		sync_uid_gid;
966 	int		masterfd;
967 	int		ptmpfd;
968 	gid_t		gid;
969 	int		cc;
970 	int		i, yp = 0;
971 	FILE		*fp;
972 
973 	if (!valid_login(login_name)) {
974 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
975 	}
976 	if (!valid_class(up->u_class)) {
977 		errx(EXIT_FAILURE, "No such login class `%s'", up->u_class);
978 	}
979 	if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
980 		err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
981 	}
982 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
983 		err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
984 	}
985 	pw_init();
986 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
987 		int saved_errno = errno;
988 		(void) close(masterfd);
989 		errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
990 	}
991 	if ((fp = fdopen(masterfd, "r")) == NULL) {
992 		int saved_errno = errno;
993 		(void) close(masterfd);
994 		(void) close(ptmpfd);
995 		pw_abort();
996 		errc(EXIT_FAILURE, saved_errno,
997 		    "can't fdopen `%s' for reading", _PATH_MASTERPASSWD);
998 	}
999 	while (fgets(buf, sizeof(buf), fp) != NULL) {
1000 		cc = strlen(buf);
1001 		/*
1002 		 * Stop copying the file at the yp entry; we want to
1003 		 * put the new user before it, and preserve entries
1004 		 * after the yp entry.
1005 		 */
1006 		if (cc > 1 && buf[0] == '+' && buf[1] == ':') {
1007 			yp = 1;
1008 			break;
1009 		}
1010 		if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1011 			int saved_errno = errno;
1012 			(void) fclose(fp);
1013 			(void) close(ptmpfd);
1014 			pw_abort();
1015 			errc(EXIT_FAILURE, saved_errno,
1016 			    "short write to /etc/ptmp (not %d chars)", cc);
1017 		}
1018 	}
1019 	if (ferror(fp)) {
1020 		int saved_errno = errno;
1021 		(void) fclose(fp);
1022 		(void) close(ptmpfd);
1023 		pw_abort();
1024 		errc(EXIT_FAILURE, saved_errno, "read error on %s",
1025 		    _PATH_MASTERPASSWD);
1026 	}
1027 	/* if no uid was specified, get next one in [low_uid..high_uid] range */
1028 	sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
1029 	if (up->u_uid == UID_MAX) {
1030 		int got_id = 0;
1031 
1032 		/*
1033 		 * Look for a free UID in the command line ranges (if any).
1034 		 * These start after the ranges specified in the config file.
1035 		 */
1036 		for (i = up->u_defrc; got_id == 0 && i < up->u_rc ; i++) {
1037 			got_id = getnextuid(sync_uid_gid, &up->u_uid,
1038 			    up->u_rv[i].r_from, up->u_rv[i].r_to);
1039 	 	}
1040 		/*
1041 		 * If there were no free UIDs in the command line ranges,
1042 		 * try the ranges from the config file (there will always
1043 		 * be at least one default).
1044 		 */
1045 		if (got_id == 0) {
1046 			for (i = 0; got_id == 0 && i < up->u_defrc; i++) {
1047 				got_id = getnextuid(sync_uid_gid, &up->u_uid,
1048 				    up->u_rv[i].r_from, up->u_rv[i].r_to);
1049 			}
1050 		}
1051 		if (got_id == 0) {
1052 			(void) close(ptmpfd);
1053 			pw_abort();
1054 			errx(EXIT_FAILURE, "can't get next uid for %u", up->u_uid);
1055 		}
1056 	}
1057 	/* check uid isn't already allocated */
1058 	if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
1059 		(void) close(ptmpfd);
1060 		pw_abort();
1061 		errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1062 	}
1063 	/* if -g=uid was specified, check gid is unused */
1064 	if (sync_uid_gid) {
1065 		if (getgrgid((gid_t)(up->u_uid)) != NULL) {
1066 			(void) close(ptmpfd);
1067 			pw_abort();
1068 			errx(EXIT_FAILURE, "gid %u is already in use", up->u_uid);
1069 		}
1070 		gid = up->u_uid;
1071 	} else if ((grp = getgrnam(up->u_primgrp)) != NULL) {
1072 		gid = grp->gr_gid;
1073 	} else if (is_number(up->u_primgrp) &&
1074 		   (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) {
1075 		gid = grp->gr_gid;
1076 	} else {
1077 		(void) close(ptmpfd);
1078 		pw_abort();
1079 		errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
1080 	}
1081 	/* check name isn't already in use */
1082 	if (!(up->u_flags & F_DUPUID) && getpwnam(login_name) != NULL) {
1083 		(void) close(ptmpfd);
1084 		pw_abort();
1085 		errx(EXIT_FAILURE, "already a `%s' user", login_name);
1086 	}
1087 	if (up->u_flags & F_HOMEDIR) {
1088 		if (strlcpy(home, up->u_home, sizeof(home)) >= sizeof(home)) {
1089 			(void) close(ptmpfd);
1090 			pw_abort();
1091 			errx(EXIT_FAILURE, "home directory `%s' too long",
1092 			    up->u_home);
1093 		}
1094 	} else {
1095 		/* if home directory hasn't been given, make it up */
1096 		if (snprintf(home, sizeof(home), "%s/%s", up->u_basedir,
1097 		    login_name) >= sizeof(home)) {
1098 			(void) close(ptmpfd);
1099 			pw_abort();
1100 			errx(EXIT_FAILURE, "home directory `%s/%s' too long",
1101 			    up->u_basedir, login_name);
1102 		}
1103 	}
1104 	if (!scantime(&inactive, up->u_inactive)) {
1105 		warnx("Warning: inactive time `%s' invalid, password expiry off",
1106 				up->u_inactive);
1107 	}
1108 	if (!scantime(&expire, up->u_expire)) {
1109 		warnx("Warning: expire time `%s' invalid, account expiry off",
1110 				up->u_expire);
1111 	}
1112 	if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR) &&
1113 	    strcmp(home, _PATH_NONEXISTENT) != 0) {
1114 		warnx("Warning: home directory `%s' doesn't exist, and -m was"
1115 		    " not specified", home);
1116 	}
1117 	if (up->u_password != NULL && valid_password_length(up->u_password)) {
1118 		(void) strlcpy(password, up->u_password, sizeof(password));
1119 	} else {
1120 		(void) memset(password, '*', DES_Len);
1121 		password[DES_Len] = 0;
1122 		if (up->u_password != NULL) {
1123 			warnx("Password `%s' is invalid: setting it to `%s'",
1124 				up->u_password, password);
1125 		}
1126 	}
1127 	cc = snprintf(buf, sizeof(buf), "%s:%s:%u:%u:%s:%ld:%ld:%s:%s:%s\n",
1128 	    login_name,
1129 	    password,
1130 	    up->u_uid,
1131 	    gid,
1132 	    up->u_class,
1133 	    (long) inactive,
1134 	    (long) expire,
1135 	    up->u_comment,
1136 	    home,
1137 	    up->u_shell);
1138 	if (cc >= sizeof(buf) || cc < 0 ||
1139 	    cc + expand_len(up->u_comment, login_name) >= 1023) {
1140 		(void) close(ptmpfd);
1141 		pw_abort();
1142 		errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1143 	}
1144 	if (write(ptmpfd, buf, (size_t) cc) != cc) {
1145 		int saved_errno = errno;
1146 		(void) close(ptmpfd);
1147 		pw_abort();
1148 		errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1149 	}
1150 	if (yp) {
1151 		/* put back the + line */
1152 		cc = snprintf(buf, sizeof(buf), "+:*::::::::\n");
1153 		if (cc == -1 || cc >= sizeof(buf)) {
1154 			(void) close(ptmpfd);
1155 			pw_abort();
1156 			errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1157 		}
1158 		if (write(ptmpfd, buf, (size_t) cc) != cc) {
1159 			int saved_errno = errno;
1160 			(void) close(ptmpfd);
1161 			pw_abort();
1162 			errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1163 		}
1164 		/* copy the entries following it, if any */
1165 		while (fgets(buf, sizeof(buf), fp) != NULL) {
1166 			cc = strlen(buf);
1167 			if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1168 				int saved_errno = errno;
1169 				(void) fclose(fp);
1170 				(void) close(ptmpfd);
1171 				pw_abort();
1172 				errc(EXIT_FAILURE, saved_errno,
1173 				    "short write to /etc/ptmp (not %d chars)",
1174 				    cc);
1175 			}
1176 		}
1177 		if (ferror(fp)) {
1178 			int saved_errno = errno;
1179 			(void) fclose(fp);
1180 			(void) close(ptmpfd);
1181 			pw_abort();
1182 			errc(EXIT_FAILURE, saved_errno, "read error on %s",
1183 			    _PATH_MASTERPASSWD);
1184 		}
1185 	}
1186 	if (up->u_flags & F_MKDIR) {
1187 		if (lstat(home, &st) == 0) {
1188 			(void) close(ptmpfd);
1189 			pw_abort();
1190 			errx(EXIT_FAILURE, "home directory `%s' already exists",
1191 			    home);
1192 		} else {
1193 			if (asystem("%s -p %s", MKDIR, home) != 0) {
1194 				int saved_errno = errno;
1195 				(void) close(ptmpfd);
1196 				pw_abort();
1197 				errc(EXIT_FAILURE, saved_errno,
1198 				    "can't mkdir `%s'", home);
1199 			}
1200 			(void) copydotfiles(up->u_skeldir, up->u_uid, gid, home);
1201 			(void) asystem("%s -R -P %u:%u %s", CHOWN, up->u_uid,
1202 			    gid, home);
1203 			(void) asystem("%s -R u+w %s", CHMOD, home);
1204 		}
1205 	}
1206 	if (strcmp(up->u_primgrp, "=uid") == 0 &&
1207 	    getgrnam(login_name) == NULL &&
1208 	    !creategid(login_name, gid, "")) {
1209 		(void) close(ptmpfd);
1210 		pw_abort();
1211 		errx(EXIT_FAILURE, "can't create gid %u for login name %s",
1212 		    gid, login_name);
1213 	}
1214 	if (up->u_groupc > 0 && !append_group(login_name, up->u_groupc, up->u_groupv)) {
1215 		(void) close(ptmpfd);
1216 		pw_abort();
1217 		errx(EXIT_FAILURE, "can't append `%s' to new groups", login_name);
1218 	}
1219 	(void) close(ptmpfd);
1220 	if (pw_mkdb(yp ? NULL : login_name, 0) < 0) {
1221 		pw_abort();
1222 		err(EXIT_FAILURE, "pw_mkdb failed");
1223 	}
1224 	syslog(LOG_INFO, "new user added: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1225 		login_name, up->u_uid, gid, home, up->u_shell);
1226 	return 1;
1227 }
1228 
1229 /* remove a user from the groups file */
1230 static int
1231 rm_user_from_groups(char *login_name)
1232 {
1233 	struct stat	st;
1234 	size_t		login_len;
1235 	FILE		*from;
1236 	FILE		*to;
1237 	char		buf[LINE_MAX];
1238 	char		f[MaxFileNameLen];
1239 	char		*cp, *ep;
1240 	int		fd;
1241 	int		cc;
1242 
1243 	login_len = strlen(login_name);
1244 	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
1245 		warn("can't remove gid for `%s': can't open `%s'",
1246 		    login_name, _PATH_GROUP);
1247 		return 0;
1248 	}
1249 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
1250 		warn("can't lock `%s'", _PATH_GROUP);
1251 	}
1252 	(void) fstat(fileno(from), &st);
1253 	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
1254 	if ((fd = mkstemp(f)) < 0) {
1255 		warn("can't remove gid for `%s': mkstemp failed", login_name);
1256 		(void) fclose(from);
1257 		return 0;
1258 	}
1259 	if ((to = fdopen(fd, "w")) == NULL) {
1260 		warn("can't remove gid for `%s': fdopen `%s' failed",
1261 		    login_name, f);
1262 		(void) fclose(from);
1263 		(void) close(fd);
1264 		(void) unlink(f);
1265 		return 0;
1266 	}
1267 	while (fgets(buf, sizeof(buf), from) != NULL) {
1268 		cc = strlen(buf);
1269 		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
1270 			while (fgetc(from) != '\n' && !feof(from))
1271 				cc++;
1272 			warnx("%s: line `%s' too long (%d bytes), skipping",
1273 			    _PATH_GROUP, buf, cc);
1274 			continue;
1275 		}
1276 
1277 		/* Break out the group list. */
1278 		for (cp = buf, cc = 0; *cp != '\0' && cc < 3; cp++) {
1279 			if (*cp == ':')
1280 				cc++;
1281 		}
1282 		if (cc != 3) {
1283 			buf[strcspn(buf, "\n")] = '\0';
1284 			warnx("Malformed entry `%s'. Skipping", buf);
1285 			continue;
1286 		}
1287 		while ((cp = strstr(cp, login_name)) != NULL) {
1288 			if ((cp[-1] == ':' || cp[-1] == ',') &&
1289 			    (cp[login_len] == ',' || cp[login_len] == '\n')) {
1290 				ep = cp + login_len;
1291 				if (cp[login_len] == ',')
1292 					ep++;
1293 				else if (cp[-1] == ',')
1294 					cp--;
1295 				memmove(cp, ep, strlen(ep) + 1);
1296 			} else {
1297 				if ((cp = strchr(cp, ',')) == NULL)
1298 					break;
1299 				cp++;
1300 			}
1301 		}
1302 		if (fwrite(buf, strlen(buf), 1, to) != 1) {
1303 			warn("can't remove gid for `%s': short write to `%s'",
1304 			    login_name, f);
1305 			(void) fclose(from);
1306 			(void) fclose(to);
1307 			(void) unlink(f);
1308 			return 0;
1309 		}
1310 	}
1311 	(void) fchmod(fileno(to), st.st_mode & 07777);
1312 	(void) fclose(from);
1313 	if (fclose(to) == EOF) {
1314 		warn("can't remove gid for `%s': short write to `%s'",
1315 		    login_name, f);
1316 		(void) unlink(f);
1317 		return 0;
1318 	}
1319 	if (rename(f, _PATH_GROUP) < 0) {
1320 		warn("can't remove gid for `%s': can't rename `%s' to `%s'",
1321 		    login_name, f, _PATH_GROUP);
1322 		(void) unlink(f);
1323 		return 0;
1324 	}
1325 	return 1;
1326 }
1327 
1328 /* check that the user or group is local, not from YP/NIS */
1329 static int
1330 is_local(char *name, const char *file)
1331 {
1332 	FILE	       *fp;
1333 	char		buf[LINE_MAX];
1334 	size_t		len;
1335 	int		ret;
1336 	int		cc;
1337 
1338 	if ((fp = fopen(file, "r")) == NULL) {
1339 		err(EXIT_FAILURE, "can't open `%s'", file);
1340 	}
1341 	len = strlen(name);
1342 	for (ret = 0 ; fgets(buf, sizeof(buf), fp) != NULL ; ) {
1343 		cc = strlen(buf);
1344 		if (cc > 0 && buf[cc - 1] != '\n' && !feof(fp)) {
1345 			while (fgetc(fp) != '\n' && !feof(fp))
1346 				cc++;
1347 			warnx("%s: line `%s' too long (%d bytes), skipping",
1348 			    file, buf, cc);
1349 			continue;
1350 		}
1351 		if (strncmp(buf, name, len) == 0 && buf[len] == ':') {
1352 			ret = 1;
1353 			break;
1354 		}
1355 	}
1356 	(void) fclose(fp);
1357 	return ret;
1358 }
1359 
1360 /* modify a user */
1361 static int
1362 moduser(char *login_name, char *newlogin, user_t *up)
1363 {
1364 	struct passwd	*pwp;
1365 	struct group	*grp;
1366 	const char	*homedir;
1367 	char		buf[LINE_MAX];
1368 	char		acctlock_str[] = "-";
1369 	char		pwlock_str[] = "*";
1370 	char		pw_len[PasswordLength + 1];
1371 	char		shell_len[MaxShellNameLen];
1372 	char		*shell_last_char;
1373 	size_t		colonc, loginc;
1374 	size_t		cc;
1375 	size_t		shell_buf;
1376 	FILE		*master;
1377 	char		newdir[MaxFileNameLen];
1378 	char		*colon;
1379 	char		*pw_tmp = NULL;
1380 	char		*shell_tmp = NULL;
1381 	int		len;
1382 	int		locked = 0;
1383 	int		unlocked = 0;
1384 	int		masterfd;
1385 	int		ptmpfd;
1386 	int		rval;
1387 	int		i;
1388 
1389 	if (!valid_login(newlogin)) {
1390 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
1391 	}
1392 	if ((pwp = getpwnam(login_name)) == NULL) {
1393 		errx(EXIT_FAILURE, "No such user `%s'", login_name);
1394 	}
1395 	if (!is_local(login_name, _PATH_MASTERPASSWD)) {
1396 		errx(EXIT_FAILURE, "User `%s' must be a local user", login_name);
1397 	}
1398 	if (up != NULL) {
1399 		if ((up->u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)) && (pwp->pw_uid < 1000))
1400 			errx(EXIT_FAILURE, "(un)locking is not supported for the `%s' account", pwp->pw_name);
1401 	}
1402 	/* keep dir name in case we need it for '-m' */
1403 	homedir = pwp->pw_dir;
1404 
1405 	/* get the last char of the shell in case we need it for '-U' or '-Z' */
1406 	shell_last_char = pwp->pw_shell+strlen(pwp->pw_shell) - 1;
1407 
1408 	if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
1409 		err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
1410 	}
1411 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
1412 		err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
1413 	}
1414 	pw_init();
1415 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
1416 		int saved_errno = errno;
1417 		(void) close(masterfd);
1418 		errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
1419 	}
1420 	if ((master = fdopen(masterfd, "r")) == NULL) {
1421 		int saved_errno = errno;
1422 		(void) close(masterfd);
1423 		(void) close(ptmpfd);
1424 		pw_abort();
1425 		errc(EXIT_FAILURE, saved_errno, "can't fdopen fd for %s",
1426 		    _PATH_MASTERPASSWD);
1427 	}
1428 	if (up != NULL) {
1429 		if (up->u_flags & F_USERNAME) {
1430 			/* if changing name, check new name isn't already in use */
1431 			if (strcmp(login_name, newlogin) != 0 && getpwnam(newlogin) != NULL) {
1432 				(void) close(ptmpfd);
1433 				pw_abort();
1434 				errx(EXIT_FAILURE, "already a `%s' user", newlogin);
1435 			}
1436 			pwp->pw_name = newlogin;
1437 
1438 			/*
1439 			 * Provide a new directory name in case the
1440 			 * home directory is to be moved.
1441 			 */
1442 			if (up->u_flags & F_MKDIR) {
1443 				(void) snprintf(newdir, sizeof(newdir),
1444 				    "%s/%s", up->u_basedir, newlogin);
1445 				pwp->pw_dir = newdir;
1446 			}
1447 		}
1448 		if (up->u_flags & F_PASSWORD) {
1449 			if (up->u_password != NULL) {
1450 				if (!valid_password_length(up->u_password)) {
1451 					(void) close(ptmpfd);
1452 					pw_abort();
1453 					errx(EXIT_FAILURE, "Invalid password: `%s'",
1454 						up->u_password);
1455 				}
1456 				pwp->pw_passwd = up->u_password;
1457 			}
1458 		}
1459 		if (up->u_flags & F_ACCTLOCK) {
1460 			/* lock the account */
1461 			if (*shell_last_char != *acctlock_str) {
1462 				shell_tmp = malloc(strlen(pwp->pw_shell) + sizeof(acctlock_str));
1463 				if (shell_tmp == NULL) {
1464 					(void) close(ptmpfd);
1465 					pw_abort();
1466 					errx(EXIT_FAILURE, "account lock: cannot allocate memory");
1467 				}
1468 				strlcpy(shell_tmp, pwp->pw_shell, sizeof(shell_len));
1469 				strlcat(shell_tmp, acctlock_str, sizeof(shell_len));
1470 				pwp->pw_shell = shell_tmp;
1471 			} else {
1472 				locked++;
1473 			}
1474 			/* lock the password */
1475 			if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) != 0) {
1476 				pw_tmp = malloc(strlen(pwp->pw_passwd) + sizeof(pwlock_str));
1477 				if (pw_tmp == NULL) {
1478 					(void) close(ptmpfd);
1479 					pw_abort();
1480 					errx(EXIT_FAILURE, "password lock: cannot allocate memory");
1481 				}
1482 				strlcpy(pw_tmp, pwlock_str, sizeof(pw_len));
1483 				strlcat(pw_tmp, pwp->pw_passwd, sizeof(pw_len));
1484 				pwp->pw_passwd = pw_tmp;
1485 			} else {
1486 				locked++;
1487 			}
1488 
1489 			if (locked > 1)
1490 				warnx("account `%s' is already locked", pwp->pw_name);
1491 		}
1492 		if (up->u_flags & F_ACCTUNLOCK) {
1493 			/* unlock the password */
1494 			if (strcmp(pwp->pw_passwd, pwlock_str) != 0 &&
1495 			    strcmp(pwp->pw_passwd, "*************") != 0) {
1496 				if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) == 0) {
1497 					pwp->pw_passwd += sizeof(pwlock_str)-1;
1498 				} else {
1499 					unlocked++;
1500 				}
1501 			} else {
1502 				warnx("account `%s' has no password: cannot fully unlock", pwp->pw_name);
1503 			}
1504 			/* unlock the account */
1505 			if (*shell_last_char == *acctlock_str) {
1506 				shell_buf = strlen(pwp->pw_shell) + 2 - sizeof(acctlock_str);
1507 				shell_tmp = malloc(shell_buf);
1508 				if (shell_tmp == NULL) {
1509 					(void) close(ptmpfd);
1510 					pw_abort();
1511 					errx(EXIT_FAILURE, "unlock: cannot allocate memory");
1512 				}
1513 				strlcpy(shell_tmp, pwp->pw_shell, shell_buf);
1514 				pwp->pw_shell = shell_tmp;
1515 			} else {
1516 				unlocked++;
1517 			}
1518 
1519 			if (unlocked > 1)
1520 				warnx("account `%s' is not locked", pwp->pw_name);
1521 		}
1522 		if (up->u_flags & F_UID) {
1523 			/* check uid isn't already allocated */
1524 			if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
1525 				(void) close(ptmpfd);
1526 				pw_abort();
1527 				errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1528 			}
1529 			pwp->pw_uid = up->u_uid;
1530 		}
1531 		if (up->u_flags & F_GROUP) {
1532 			/* if -g=uid was specified, check gid is unused */
1533 			if (strcmp(up->u_primgrp, "=uid") == 0) {
1534 				if (getgrgid((gid_t)(up->u_uid)) != NULL) {
1535 					(void) close(ptmpfd);
1536 					pw_abort();
1537 					errx(EXIT_FAILURE, "gid %u is already in use", up->u_uid);
1538 				}
1539 				pwp->pw_gid = up->u_uid;
1540 			} else if ((grp = getgrnam(up->u_primgrp)) != NULL) {
1541 				pwp->pw_gid = grp->gr_gid;
1542 			} else if (is_number(up->u_primgrp) &&
1543 				   (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) {
1544 				pwp->pw_gid = grp->gr_gid;
1545 			} else {
1546 				(void) close(ptmpfd);
1547 				pw_abort();
1548 				errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
1549 			}
1550 		}
1551 		if (up->u_flags & F_INACTIVE) {
1552 			if (!scantime(&pwp->pw_change, up->u_inactive)) {
1553 				warnx("Warning: inactive time `%s' invalid, password expiry off",
1554 					up->u_inactive);
1555 			}
1556 		}
1557 		if (up->u_flags & F_EXPIRE) {
1558 			if (!scantime(&pwp->pw_expire, up->u_expire)) {
1559 				warnx("Warning: expire time `%s' invalid, account expiry off",
1560 					up->u_expire);
1561 			}
1562 		}
1563 		if (up->u_flags & F_COMMENT)
1564 			pwp->pw_gecos = up->u_comment;
1565 		if (up->u_flags & F_HOMEDIR)
1566 			pwp->pw_dir = up->u_home;
1567 		if (up->u_flags & F_SHELL)
1568 			pwp->pw_shell = up->u_shell;
1569 		if (up->u_flags & F_CLASS) {
1570 			if (!valid_class(up->u_class)) {
1571 				(void) close(ptmpfd);
1572 				pw_abort();
1573 				errx(EXIT_FAILURE,
1574 				    "No such login class `%s'", up->u_class);
1575 			}
1576 			pwp->pw_class = up->u_class;
1577 		}
1578 	}
1579 	loginc = strlen(login_name);
1580 	while (fgets(buf, sizeof(buf), master) != NULL) {
1581 		if ((colon = strchr(buf, ':')) == NULL) {
1582 			warnx("Malformed entry `%s'. Skipping", buf);
1583 			continue;
1584 		}
1585 		colonc = (size_t)(colon - buf);
1586 		if (strncmp(login_name, buf, loginc) == 0 && loginc == colonc) {
1587 			if (up != NULL) {
1588 				if ((len = snprintf(buf, sizeof(buf),
1589 				    "%s:%s:%u:%u:%s:%ld:%ld:%s:%s:%s\n",
1590 				    newlogin,
1591 				    pwp->pw_passwd,
1592 				    pwp->pw_uid,
1593 				    pwp->pw_gid,
1594 				    pwp->pw_class,
1595 				    (long)pwp->pw_change,
1596 				    (long)pwp->pw_expire,
1597 				    pwp->pw_gecos,
1598 				    pwp->pw_dir,
1599 				    pwp->pw_shell)) >= sizeof(buf) || len < 0 ||
1600 				    len + expand_len(pwp->pw_gecos, newlogin)
1601 				    >= 1023) {
1602 					(void) close(ptmpfd);
1603 					pw_abort();
1604 					errx(EXIT_FAILURE, "can't add `%s', "
1605 					    "line too long (%zu bytes)", buf,
1606 					    len + expand_len(pwp->pw_gecos,
1607 					    newlogin));
1608 				}
1609 				if (write(ptmpfd, buf, len) != len) {
1610 					int saved_errno = errno;
1611 					(void) close(ptmpfd);
1612 					pw_abort();
1613 					errc(EXIT_FAILURE, saved_errno,
1614 					    "can't add `%s'", buf);
1615 				}
1616 			}
1617 		} else {
1618 			len = strlen(buf);
1619 			if ((cc = write(ptmpfd, buf, len)) != len) {
1620 				int saved_errno = errno;
1621 				(void) close(masterfd);
1622 				(void) close(ptmpfd);
1623 				pw_abort();
1624 				errc(EXIT_FAILURE, saved_errno,
1625 				    "short write to /etc/ptmp (%lld not %lld chars)",
1626 				    (long long)cc, (long long)len);
1627 			}
1628 		}
1629 	}
1630 	if (up != NULL) {
1631 		if ((up->u_flags & F_MKDIR) &&
1632 		    asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) {
1633 			int saved_errno = errno;
1634 			(void) close(ptmpfd);
1635 			pw_abort();
1636 			errc(EXIT_FAILURE, saved_errno,
1637 			    "can't move `%s' to `%s'", homedir, pwp->pw_dir);
1638 		}
1639 		if (up->u_flags & F_SETSECGROUP) {
1640 		    for (i = 0 ; i < up->u_groupc ; i++) {
1641 		        if (getgrnam(up->u_groupv[i]) == NULL) {
1642 		            (void) close(ptmpfd);
1643 		            pw_abort();
1644 		            errx(EXIT_FAILURE, "aborting, group `%s' does not exist",
1645 			        up->u_groupv[i]);
1646 		        }
1647 		    }
1648 		    if (!rm_user_from_groups(newlogin)) {
1649 		        (void) close(ptmpfd);
1650 		        pw_abort();
1651 		        errx(EXIT_FAILURE, "can't reset groups for `%s'", newlogin);
1652 		    }
1653 		}
1654 		if (up->u_groupc > 0) {
1655 		    if (!append_group(newlogin, up->u_groupc, up->u_groupv)) {
1656 			(void) close(ptmpfd);
1657 			pw_abort();
1658 			errx(EXIT_FAILURE, "can't append `%s' to new groups",
1659 			    newlogin);
1660 		    }
1661 		}
1662 	}
1663 	(void) close(ptmpfd);
1664 	if (pw_tmp)
1665 		FREE(pw_tmp);
1666 	if (shell_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 
1689 /* see if we can find out the user struct */
1690 static struct passwd *
1691 find_user_info(char *name)
1692 {
1693 	struct passwd	*pwp;
1694 
1695 	if ((pwp = getpwnam(name)) != NULL) {
1696 		return pwp;
1697 	}
1698 	if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != NULL) {
1699 		return pwp;
1700 	}
1701 	return NULL;
1702 }
1703 
1704 /* see if we can find out the group struct */
1705 static struct group *
1706 find_group_info(char *name)
1707 {
1708 	struct group	*grp;
1709 
1710 	if ((grp = getgrnam(name)) != NULL) {
1711 		return grp;
1712 	}
1713 	if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != NULL) {
1714 		return grp;
1715 	}
1716 	return NULL;
1717 }
1718 
1719 /* print out usage message, and then exit */
1720 void
1721 usermgmt_usage(const char *prog)
1722 {
1723 	if (strcmp(prog, "useradd") == 0) {
1724 		(void) fprintf(stderr, "usage: %s -D [-b base-directory] "
1725 		    "[-e expiry-time] [-f inactive-time]\n"
1726 		    "               [-g gid | name | =uid] [-k skel-directory] "
1727 		    "[-L login-class]\n"
1728 		    "               [-r low..high] [-s shell]\n", prog);
1729 		(void) fprintf(stderr, "       %s [-mov] [-b base-directory] "
1730 		    "[-c comment] [-d home-directory]\n"
1731 		    "               [-e expiry-time] [-f inactive-time]\n"
1732 		    "               [-G secondary-group[,group,...]] "
1733 		    "[-g gid | name | =uid]\n"
1734 		    "               [-k skel-directory] [-L login-class] "
1735 		    "[-p password] [-r low..high]\n"
1736 		    "               [-s shell] [-u uid] user\n", prog);
1737 	} else if (strcmp(prog, "usermod") == 0) {
1738 		(void) fprintf(stderr, "usage: %s [-moUvZ] "
1739 		    "[-c comment] [-d home-directory] [-e expiry-time]\n"
1740 		    "               [-f inactive-time] "
1741 		    "[-G secondary-group[,group,...]]\n"
1742 		    "               [-g gid | name | =uid] [-L login-class] "
1743 		    "[-l new-login]\n"
1744 		    "               [-p password] "
1745 		    "[-S secondary-group[,group,...]]\n"
1746 		    "               [-s shell] [-u uid] user\n",
1747 		    prog);
1748 	} else if (strcmp(prog, "userdel") == 0) {
1749 		(void) fprintf(stderr, "usage: %s -D [-p preserve-value]\n",
1750 		    prog);
1751 		(void) fprintf(stderr, "       %s [-rv] [-p preserve-value] "
1752 		    "user\n", prog);
1753 	} else if (strcmp(prog, "userinfo") == 0) {
1754 		(void) fprintf(stderr, "usage: %s [-e] user\n", prog);
1755 	} else if (strcmp(prog, "groupadd") == 0) {
1756 		(void) fprintf(stderr, "usage: %s [-ov] [-g gid] group\n",
1757 		    prog);
1758 	} else if (strcmp(prog, "groupdel") == 0) {
1759 		(void) fprintf(stderr, "usage: %s [-v] group\n", prog);
1760 	} else if (strcmp(prog, "groupmod") == 0) {
1761 		(void) fprintf(stderr, "usage: %s [-ov] [-g gid] [-n newname] "
1762 		    "group\n", prog);
1763 	} else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) {
1764 		(void) fprintf(stderr, "usage: %s [add | del | mod"
1765 		" | info"
1766 		"] ...\n",
1767 		    prog);
1768 	} else if (strcmp(prog, "groupinfo") == 0) {
1769 		(void) fprintf(stderr, "usage: %s [-e] group\n", prog);
1770 	} else {
1771 		(void) fprintf(stderr, "This program must be called as {user,group}{add,del,mod,info},\n%s is not an understood name.\n", prog);
1772 	}
1773 	exit(EXIT_FAILURE);
1774 	/* NOTREACHED */
1775 }
1776 
1777 int
1778 useradd(int argc, char **argv)
1779 {
1780 	user_t	u;
1781 	int	defaultfield;
1782 	int	bigD;
1783 	int	c;
1784 	int	i;
1785 
1786 	(void) memset(&u, 0, sizeof(u));
1787 	read_defaults(&u);
1788 	u.u_uid = UID_MAX;
1789 	defaultfield = bigD = 0;
1790 	while ((c = getopt(argc, argv, "DG:L:b:c:d:e:f:g:k:mop:r:s:u:v")) != -1) {
1791 		switch(c) {
1792 		case 'D':
1793 			bigD = 1;
1794 			break;
1795 		case 'G':
1796 			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1797 			    u.u_groupc < NGROUPS_MAX - 2) {
1798 				if (u.u_groupv[u.u_groupc][0] != 0) {
1799 					u.u_groupc++;
1800 				}
1801 			}
1802 			if (optarg != NULL) {
1803 				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1804 			}
1805 			break;
1806 		case 'b':
1807 			defaultfield = 1;
1808 			memsave(&u.u_basedir, optarg, strlen(optarg));
1809 			break;
1810 		case 'c':
1811 			memsave(&u.u_comment, optarg, strlen(optarg));
1812 			break;
1813 		case 'd':
1814 			memsave(&u.u_home, optarg, strlen(optarg));
1815 			u.u_flags |= F_HOMEDIR;
1816 			break;
1817 		case 'e':
1818 			defaultfield = 1;
1819 			memsave(&u.u_expire, optarg, strlen(optarg));
1820 			break;
1821 		case 'f':
1822 			defaultfield = 1;
1823 			memsave(&u.u_inactive, optarg, strlen(optarg));
1824 			break;
1825 		case 'g':
1826 			defaultfield = 1;
1827 			memsave(&u.u_primgrp, optarg, strlen(optarg));
1828 			break;
1829 		case 'k':
1830 			defaultfield = 1;
1831 			memsave(&u.u_skeldir, optarg, strlen(optarg));
1832 			break;
1833 		case 'L':
1834 			defaultfield = 1;
1835 			memsave(&u.u_class, optarg, strlen(optarg));
1836 			break;
1837 		case 'm':
1838 			u.u_flags |= F_MKDIR;
1839 			break;
1840 		case 'o':
1841 			u.u_flags |= F_DUPUID;
1842 			break;
1843 		case 'p':
1844 			memsave(&u.u_password, optarg, strlen(optarg));
1845 			memset(optarg, 'X', strlen(optarg));
1846 			break;
1847 		case 'r':
1848 			defaultfield = 1;
1849 			(void) save_range(&u, optarg);
1850 			break;
1851 		case 's':
1852 			defaultfield = 1;
1853 			memsave(&u.u_shell, optarg, strlen(optarg));
1854 			break;
1855 		case 'u':
1856 			if (!is_number(optarg)) {
1857 				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1858 			}
1859 			u.u_uid = atoi(optarg);
1860 			break;
1861 		case 'v':
1862 			verbose = 1;
1863 			break;
1864 		default:
1865 			usermgmt_usage("useradd");
1866 			/* NOTREACHED */
1867 		}
1868 	}
1869 	if (bigD) {
1870 		if (defaultfield) {
1871 			checkeuid();
1872 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
1873 		}
1874 		(void) printf("group\t\t%s\n", u.u_primgrp);
1875 		(void) printf("base_dir\t%s\n", u.u_basedir);
1876 		(void) printf("skel_dir\t%s\n", u.u_skeldir);
1877 		(void) printf("shell\t\t%s\n", u.u_shell);
1878 		(void) printf("class\t\t%s\n", u.u_class);
1879 		(void) printf("inactive\t%s\n", (u.u_inactive == NULL) ? UNSET_INACTIVE : u.u_inactive);
1880 		(void) printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire);
1881 		for (i = 0 ; i < u.u_rc ; i++) {
1882 			(void) printf("range\t\t%u..%u\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
1883 		}
1884 		return EXIT_SUCCESS;
1885 	}
1886 	argc -= optind;
1887 	argv += optind;
1888 	if (argc != 1) {
1889 		usermgmt_usage("useradd");
1890 	}
1891 	checkeuid();
1892 	openlog("useradd", LOG_PID, LOG_USER);
1893 	return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1894 }
1895 
1896 int
1897 usermod(int argc, char **argv)
1898 {
1899 	user_t	u;
1900 	char	newuser[MaxUserNameLen + 1];
1901 	int	c, have_new_user;
1902 
1903 	(void) memset(&u, 0, sizeof(u));
1904 	(void) memset(newuser, 0, sizeof(newuser));
1905 	read_defaults(&u);
1906 	free(u.u_primgrp);
1907 	u.u_primgrp = NULL;
1908 	have_new_user = 0;
1909 	while ((c = getopt(argc, argv, "G:L:S:UZc:d:e:f:g:l:mop:s:u:v")) != -1) {
1910 		switch(c) {
1911 		case 'G':
1912 			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1913 			    u.u_groupc < NGROUPS_MAX - 2) {
1914 				if (u.u_groupv[u.u_groupc][0] != 0) {
1915 					u.u_groupc++;
1916 				}
1917 			}
1918 			if (optarg != NULL) {
1919 			  	warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1920 			}
1921 			u.u_flags |= F_SECGROUP;
1922 			break;
1923 		case 'S':
1924 			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1925 			    u.u_groupc < NGROUPS_MAX - 2) {
1926 				if (u.u_groupv[u.u_groupc][0] != 0) {
1927 					u.u_groupc++;
1928 				}
1929 			}
1930 			if (optarg != NULL) {
1931 			  	warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1932 			}
1933 			u.u_flags |= F_SETSECGROUP;
1934 			break;
1935 		case 'U':
1936 			u.u_flags |= F_ACCTUNLOCK;
1937 			break;
1938 		case 'Z':
1939 			u.u_flags |= F_ACCTLOCK;
1940 			break;
1941 		case 'c':
1942 			memsave(&u.u_comment, optarg, strlen(optarg));
1943 			u.u_flags |= F_COMMENT;
1944 			break;
1945 		case 'd':
1946 			memsave(&u.u_home, optarg, strlen(optarg));
1947 			u.u_flags |= F_HOMEDIR;
1948 			break;
1949 		case 'e':
1950 			memsave(&u.u_expire, optarg, strlen(optarg));
1951 			u.u_flags |= F_EXPIRE;
1952 			break;
1953 		case 'f':
1954 			memsave(&u.u_inactive, optarg, strlen(optarg));
1955 			u.u_flags |= F_INACTIVE;
1956 			break;
1957 		case 'g':
1958 			memsave(&u.u_primgrp, optarg, strlen(optarg));
1959 			u.u_flags |= F_GROUP;
1960 			break;
1961 		case 'l':
1962 			if (strlcpy(newuser, optarg, sizeof(newuser)) >=
1963 			    sizeof(newuser))
1964 				errx(EXIT_FAILURE, "username `%s' too long",
1965 				    optarg);
1966 			have_new_user = 1;
1967 			u.u_flags |= F_USERNAME;
1968 			break;
1969 		case 'L':
1970 			memsave(&u.u_class, optarg, strlen(optarg));
1971 			u.u_flags |= F_CLASS;
1972 			break;
1973 		case 'm':
1974 			u.u_flags |= F_MKDIR;
1975 			break;
1976 		case 'o':
1977 			u.u_flags |= F_DUPUID;
1978 			break;
1979 		case 'p':
1980 			memsave(&u.u_password, optarg, strlen(optarg));
1981 			memset(optarg, 'X', strlen(optarg));
1982 			u.u_flags |= F_PASSWORD;
1983 			break;
1984 		case 's':
1985 			memsave(&u.u_shell, optarg, strlen(optarg));
1986 			u.u_flags |= F_SHELL;
1987 			break;
1988 		case 'u':
1989 			if (!is_number(optarg)) {
1990 				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1991 			}
1992 			u.u_uid = atoi(optarg);
1993 			u.u_flags |= F_UID;
1994 			break;
1995 		case 'v':
1996 			verbose = 1;
1997 			break;
1998 		default:
1999 			usermgmt_usage("usermod");
2000 			/* NOTREACHED */
2001 		}
2002 	}
2003 	if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) &&
2004 	    !(u.u_flags & F_USERNAME)) {
2005 		warnx("option 'm' useless without 'd' or 'l' -- ignored");
2006 		u.u_flags &= ~F_MKDIR;
2007 	}
2008 	if ((u.u_flags & F_SECGROUP) && (u.u_flags & F_SETSECGROUP))
2009 		errx(EXIT_FAILURE, "options 'G' and 'S' are mutually exclusive");
2010 	if ((u.u_flags & F_ACCTLOCK) && (u.u_flags & F_ACCTUNLOCK))
2011 		errx(EXIT_FAILURE, "options 'U' and 'Z' are mutually exclusive");
2012 	if ((u.u_flags & F_PASSWORD) && (u.u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)))
2013 		errx(EXIT_FAILURE, "options 'U' or 'Z' with 'p' are mutually exclusive");
2014 	argc -= optind;
2015 	argv += optind;
2016 	if (argc != 1) {
2017 		usermgmt_usage("usermod");
2018 	}
2019 	checkeuid();
2020 	openlog("usermod", LOG_PID, LOG_USER);
2021 	return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ?
2022 	    EXIT_SUCCESS : EXIT_FAILURE;
2023 }
2024 
2025 int
2026 userdel(int argc, char **argv)
2027 {
2028 	struct passwd	*pwp;
2029 	user_t		u;
2030 	char		password[PasswordLength + 1];
2031 	int		defaultfield;
2032 	int		rmhome;
2033 	int		bigD;
2034 	int		c;
2035 
2036 	(void) memset(&u, 0, sizeof(u));
2037 	read_defaults(&u);
2038 	defaultfield = bigD = rmhome = 0;
2039 	while ((c = getopt(argc, argv, "Dp:rv")) != -1) {
2040 		switch(c) {
2041 		case 'D':
2042 			bigD = 1;
2043 			break;
2044 		case 'p':
2045 			defaultfield = 1;
2046 			u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
2047 					(strcmp(optarg, "yes") == 0) ? 1 :
2048 					 atoi(optarg);
2049 			break;
2050 		case 'r':
2051 			rmhome = 1;
2052 			break;
2053 		case 'v':
2054 			verbose = 1;
2055 			break;
2056 		default:
2057 			usermgmt_usage("userdel");
2058 			/* NOTREACHED */
2059 		}
2060 	}
2061 	if (bigD) {
2062 		if (defaultfield) {
2063 			checkeuid();
2064 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
2065 		}
2066 		(void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
2067 		return EXIT_SUCCESS;
2068 	}
2069 	argc -= optind;
2070 	argv += optind;
2071 	if (argc != 1) {
2072 		usermgmt_usage("userdel");
2073 	}
2074 	checkeuid();
2075 	if ((pwp = getpwnam(*argv)) == NULL) {
2076 		warnx("No such user `%s'", *argv);
2077 		return EXIT_FAILURE;
2078 	}
2079 	if (rmhome)
2080 		(void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir);
2081 	if (u.u_preserve) {
2082 		u.u_flags |= F_SHELL;
2083 		memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN));
2084 		(void) memset(password, '*', DES_Len);
2085 		password[DES_Len] = 0;
2086 		memsave(&u.u_password, password, strlen(password));
2087 		u.u_flags |= F_PASSWORD;
2088 		openlog("userdel", LOG_PID, LOG_USER);
2089 		return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
2090 	}
2091 	if (!rm_user_from_groups(*argv)) {
2092 		return 0;
2093 	}
2094 	openlog("userdel", LOG_PID, LOG_USER);
2095 	return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
2096 }
2097 
2098 /* add a group */
2099 int
2100 groupadd(int argc, char **argv)
2101 {
2102 	int	dupgid;
2103 	int	gid;
2104 	int	c;
2105 
2106 	gid = GID_MAX;
2107 	dupgid = 0;
2108 	while ((c = getopt(argc, argv, "g:ov")) != -1) {
2109 		switch(c) {
2110 		case 'g':
2111 			if (!is_number(optarg)) {
2112 				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2113 			}
2114 			gid = atoi(optarg);
2115 			break;
2116 		case 'o':
2117 			dupgid = 1;
2118 			break;
2119 		case 'v':
2120 			verbose = 1;
2121 			break;
2122 		default:
2123 			usermgmt_usage("groupadd");
2124 			/* NOTREACHED */
2125 		}
2126 	}
2127 	argc -= optind;
2128 	argv += optind;
2129 	if (argc != 1) {
2130 		usermgmt_usage("groupadd");
2131 	}
2132 	checkeuid();
2133 	if (!valid_group(*argv)) {
2134 		errx(EXIT_FAILURE, "invalid group name `%s'", *argv);
2135 	}
2136 	if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
2137 		errx(EXIT_FAILURE, "can't add group: can't get next gid");
2138 	}
2139 	if (!dupgid && getgrgid((gid_t) gid) != NULL) {
2140 		errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
2141 	}
2142 	openlog("groupadd", LOG_PID, LOG_USER);
2143 	if (!creategid(*argv, gid, "")) {
2144 		errx(EXIT_FAILURE, "can't add group: problems with %s file",
2145 		    _PATH_GROUP);
2146 	}
2147 	return EXIT_SUCCESS;
2148 }
2149 
2150 /* remove a group */
2151 int
2152 groupdel(int argc, char **argv)
2153 {
2154 	int	c;
2155 
2156 	while ((c = getopt(argc, argv, "v")) != -1) {
2157 		switch(c) {
2158 		case 'v':
2159 			verbose = 1;
2160 			break;
2161 		default:
2162 			usermgmt_usage("groupdel");
2163 			/* NOTREACHED */
2164 		}
2165 	}
2166 	argc -= optind;
2167 	argv += optind;
2168 	if (argc != 1) {
2169 		usermgmt_usage("groupdel");
2170 	}
2171 	checkeuid();
2172 	openlog("groupdel", LOG_PID, LOG_USER);
2173 	if (getgrnam(*argv) == NULL) {
2174 		warnx("No such group: `%s'", *argv);
2175 		return EXIT_FAILURE;
2176 	}
2177 	if (!modify_gid(*argv, NULL)) {
2178 		err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2179 	}
2180 	return EXIT_SUCCESS;
2181 }
2182 
2183 /* modify a group */
2184 int
2185 groupmod(int argc, char **argv)
2186 {
2187 	struct group	*grp;
2188 	char		buf[LINE_MAX];
2189 	char		*newname;
2190 	char		**cpp;
2191 	int		dupgid;
2192 	int		gid;
2193 	int		cc;
2194 	int		c;
2195 
2196 	gid = GID_MAX;
2197 	dupgid = 0;
2198 	newname = NULL;
2199 	while ((c = getopt(argc, argv, "g:n:ov")) != -1) {
2200 		switch(c) {
2201 		case 'g':
2202 			if (!is_number(optarg)) {
2203 				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2204 			}
2205 			gid = atoi(optarg);
2206 			break;
2207 		case 'o':
2208 			dupgid = 1;
2209 			break;
2210 		case 'n':
2211 			memsave(&newname, optarg, strlen(optarg));
2212 			break;
2213 		case 'v':
2214 			verbose = 1;
2215 			break;
2216 		default:
2217 			usermgmt_usage("groupmod");
2218 			/* NOTREACHED */
2219 		}
2220 	}
2221 	argc -= optind;
2222 	argv += optind;
2223 	if (argc != 1) {
2224 		usermgmt_usage("groupmod");
2225 	}
2226 	checkeuid();
2227 	if (gid < 0 && newname == NULL) {
2228 		errx(EXIT_FAILURE, "Nothing to change");
2229 	}
2230 	if (dupgid && gid < 0) {
2231 		errx(EXIT_FAILURE, "Duplicate which gid?");
2232 	}
2233 	if ((grp = getgrnam(*argv)) == NULL) {
2234 		errx(EXIT_FAILURE, "can't find group `%s' to modify", *argv);
2235 	}
2236 	if (!is_local(*argv, _PATH_GROUP)) {
2237 		errx(EXIT_FAILURE, "Group `%s' must be a local group", *argv);
2238 	}
2239 	if (newname != NULL && !valid_group(newname)) {
2240 		errx(EXIT_FAILURE, "invalid group name `%s'", newname);
2241 	}
2242 	if ((cc = snprintf(buf, sizeof(buf), "%s:%s:%u:",
2243 	    (newname) ? newname : grp->gr_name, grp->gr_passwd,
2244 	    (gid < 0) ? grp->gr_gid : gid)) >= sizeof(buf) || cc < 0)
2245 		errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2246 
2247 	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2248 		cc = strlcat(buf, *cpp, sizeof(buf)) + 1;
2249 		if (cc >= sizeof(buf))
2250 			errx(EXIT_FAILURE, "group `%s' entry too long",
2251 			    grp->gr_name);
2252 		if (cpp[1] != NULL) {
2253 			buf[cc - 1] = ',';
2254 			buf[cc] = '\0';
2255 		}
2256 	}
2257 	cc = strlcat(buf, "\n", sizeof(buf));
2258 	if (cc >= sizeof(buf))
2259 		errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2260 
2261 	openlog("groupmod", LOG_PID, LOG_USER);
2262 	if (!modify_gid(*argv, buf))
2263 		err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2264 	return EXIT_SUCCESS;
2265 }
2266 
2267 /* display user information */
2268 int
2269 userinfo(int argc, char **argv)
2270 {
2271 	struct passwd	*pwp;
2272 	struct group	*grp;
2273 	char		**cpp;
2274 	int		exists;
2275 	int		i;
2276 
2277 	exists = 0;
2278 	while ((i = getopt(argc, argv, "ev")) != -1) {
2279 		switch(i) {
2280 		case 'e':
2281 			exists = 1;
2282 			break;
2283 		case 'v':
2284 			verbose = 1;
2285 			break;
2286 		default:
2287 			usermgmt_usage("userinfo");
2288 			/* NOTREACHED */
2289 		}
2290 	}
2291 	argc -= optind;
2292 	argv += optind;
2293 	if (argc != 1) {
2294 		usermgmt_usage("userinfo");
2295 	}
2296 	pwp = find_user_info(*argv);
2297 	if (exists) {
2298 		exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE);
2299 	}
2300 	if (pwp == NULL) {
2301 		errx(EXIT_FAILURE, "can't find user `%s'", *argv);
2302 	}
2303 	(void) printf("login\t%s\n", pwp->pw_name);
2304 	(void) printf("passwd\t%s\n", pwp->pw_passwd);
2305 	(void) printf("uid\t%u\n", pwp->pw_uid);
2306 	if ((grp = getgrgid(pwp->pw_gid)) == NULL)
2307 		(void) printf("groups\t%u", pwp->pw_gid);
2308 	else
2309 		(void) printf("groups\t%s", grp->gr_name);
2310 	while ((grp = getgrent()) != NULL) {
2311 		for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2312 			if (strcmp(*cpp, pwp->pw_name) == 0 &&
2313 			    grp->gr_gid != pwp->pw_gid)
2314 				(void) printf(" %s", grp->gr_name);
2315 		}
2316 	}
2317 	(void) fputc('\n', stdout);
2318 	(void) printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n");
2319 	(void) printf("class\t%s\n", pwp->pw_class);
2320 	(void) printf("gecos\t%s\n", pwp->pw_gecos);
2321 	(void) printf("dir\t%s\n", pwp->pw_dir);
2322 	(void) printf("shell\t%s\n", pwp->pw_shell);
2323 	(void) printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n");
2324 	return EXIT_SUCCESS;
2325 }
2326 
2327 /* display user information */
2328 int
2329 groupinfo(int argc, char **argv)
2330 {
2331 	struct group	*grp;
2332 	char		**cpp;
2333 	int		exists;
2334 	int		i;
2335 
2336 	exists = 0;
2337 	while ((i = getopt(argc, argv, "ev")) != -1) {
2338 		switch(i) {
2339 		case 'e':
2340 			exists = 1;
2341 			break;
2342 		case 'v':
2343 			verbose = 1;
2344 			break;
2345 		default:
2346 			usermgmt_usage("groupinfo");
2347 			/* NOTREACHED */
2348 		}
2349 	}
2350 	argc -= optind;
2351 	argv += optind;
2352 	if (argc != 1) {
2353 		usermgmt_usage("groupinfo");
2354 	}
2355 	grp = find_group_info(*argv);
2356 	if (exists) {
2357 		exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE);
2358 	}
2359 	if (grp == NULL) {
2360 		errx(EXIT_FAILURE, "can't find group `%s'", *argv);
2361 	}
2362 	(void) printf("name\t%s\n", grp->gr_name);
2363 	(void) printf("passwd\t%s\n", grp->gr_passwd);
2364 	(void) printf("gid\t%u\n", grp->gr_gid);
2365 	(void) printf("members\t");
2366 	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2367 		(void) printf("%s ", *cpp);
2368 	}
2369 	(void) fputc('\n', stdout);
2370 	return EXIT_SUCCESS;
2371 }
2372