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