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