xref: /netbsd-src/usr.sbin/user/user.c (revision e4d7c2e329d54c97e0c0bd3016bbe74f550c3d5e)
1 /* $NetBSD: user.c,v 1.11 2000/02/02 15:12:10 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(
37 	"@(#) Copyright (c) 1999 \
38 	        The NetBSD Foundation, Inc.  All rights reserved.");
39 __RCSID("$NetBSD: user.c,v 1.11 2000/02/02 15:12:10 agc Exp $");
40 #endif
41 
42 #include <sys/types.h>
43 #include <sys/param.h>
44 #include <sys/stat.h>
45 
46 #include <ctype.h>
47 #include <dirent.h>
48 #include <err.h>
49 #include <fcntl.h>
50 #include <grp.h>
51 #include <pwd.h>
52 #include <stdarg.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <time.h>
57 #include <unistd.h>
58 #include <util.h>
59 
60 #include "defs.h"
61 #include "usermgmt.h"
62 
63 /* this struct describes a uid range */
64 typedef struct range_t {
65 	int	r_from;		/* low uid */
66 	int	r_to;		/* high uid */
67 } range_t;
68 
69 /* this struct encapsulates the user information */
70 typedef struct user_t {
71 	int		u_uid;			/* uid of user */
72 	char		*u_password;		/* encrypted password */
73 	char		*u_comment;		/* comment field */
74 	int		u_homeset;		/* home dir has been set */
75 	char		*u_home;		/* home directory */
76 	char		*u_primgrp;		/* primary group */
77 	int		u_groupc;		/* # of secondary groups */
78 	char		*u_groupv[NGROUPS_MAX];	/* secondary groups */
79 	char		*u_shell;		/* user's shell */
80 	char		*u_basedir;		/* base directory for home */
81 	char		*u_expire;		/* when password will expire */
82 	int		u_inactive;		/* inactive */
83 	int		u_mkdir;		/* make the home directory */
84 	int		u_dupuid;		/* duplicate uids are allowed */
85 	char		*u_skeldir;		/* directory for startup files */
86 	unsigned	u_rsize;		/* size of range array */
87 	unsigned	u_rc;			/* # of ranges */
88 	range_t		*u_rv;			/* the ranges */
89 	unsigned	u_defrc;		/* # of ranges in defaults */
90 	int		u_preserve;		/* preserve uids on deletion */
91 } user_t;
92 
93 #define CONFFILE	"/etc/usermgmt.conf"
94 
95 #ifndef DEF_GROUP
96 #define DEF_GROUP	"users"
97 #endif
98 
99 #ifndef DEF_BASEDIR
100 #define DEF_BASEDIR	"/home"
101 #endif
102 
103 #ifndef DEF_SKELDIR
104 #define DEF_SKELDIR	"/etc/skel"
105 #endif
106 
107 #ifndef DEF_SHELL
108 #define DEF_SHELL	"/bin/csh"
109 #endif
110 
111 #ifndef DEF_COMMENT
112 #define DEF_COMMENT	""
113 #endif
114 
115 #ifndef DEF_LOWUID
116 #define DEF_LOWUID	1000
117 #endif
118 
119 #ifndef DEF_HIGHUID
120 #define DEF_HIGHUID	60000
121 #endif
122 
123 #ifndef DEF_INACTIVE
124 #define DEF_INACTIVE	0
125 #endif
126 
127 #ifndef DEF_EXPIRE
128 #define DEF_EXPIRE	(char *) NULL
129 #endif
130 
131 #ifndef MASTER
132 #define MASTER		"/etc/master.passwd"
133 #endif
134 
135 #ifndef ETCGROUP
136 #define ETCGROUP	"/etc/group"
137 #endif
138 
139 #ifndef WAITSECS
140 #define WAITSECS	10
141 #endif
142 
143 #ifndef NOBODY_UID
144 #define NOBODY_UID	32767
145 #endif
146 
147 /* some useful constants */
148 enum {
149 	MaxShellNameLen = 256,
150 	MaxFileNameLen = MAXPATHLEN,
151 	MaxUserNameLen = 32,
152 	MaxFieldNameLen = 32,
153 	MaxCommandLen = 2048,
154 	MaxEntryLen = 2048,
155 	PasswordLength = 13,
156 
157 	LowGid = DEF_LOWUID,
158 	HighGid = DEF_HIGHUID
159 };
160 
161 /* Full paths of programs used here */
162 #define CHOWN		"/usr/sbin/chown"
163 #define MKDIR		"/bin/mkdir"
164 #define MV		"/bin/mv"
165 #define NOLOGIN		"/sbin/nologin"
166 #define PAX		"/bin/pax"
167 #define RM		"/bin/rm"
168 
169 #define UNSET_EXPIRY	"Null (unset)"
170 
171 static int	verbose;
172 
173 /* if *cpp is non-null, free it, then assign `n' chars of `s' to it */
174 static void
175 memsave(char **cpp, char *s, size_t n)
176 {
177 	if (*cpp != (char *) NULL) {
178 		FREE(*cpp);
179 	}
180 	NEWARRAY(char, *cpp, n + 1, exit(1));
181 	(void) memcpy(*cpp, s, n);
182 	(*cpp)[n] = 0;
183 }
184 
185 /* a replacement for system(3) */
186 static int
187 asystem(char *fmt, ...)
188 {
189 	va_list	vp;
190 	char	buf[MaxCommandLen];
191 	int	ret;
192 
193 	va_start(vp, fmt);
194 	(void) vsnprintf(buf, sizeof(buf), fmt, vp);
195 	va_end(vp);
196 	if (verbose) {
197 		(void) printf("Command: %s\n", buf);
198 	}
199 	if ((ret = system(buf)) != 0) {
200 		warnx("[Warning] can't system `%s'", buf);
201 	}
202 	return ret;
203 }
204 
205 #define NetBSD_1_4_K	104110000
206 
207 #if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_4_K)
208 /* bounds checking strncpy */
209 static int
210 strlcpy(char *to, char *from, size_t tosize)
211 {
212 	size_t	n;
213 	int	fromsize;
214 
215 	fromsize = strlen(from);
216 	n = MIN(tosize - 1, fromsize);
217 	(void) memcpy(to, from, n);
218 	to[n] = 0;
219 	return fromsize;
220 }
221 #endif
222 
223 #ifdef EXTENSIONS
224 /* return 1 if all of `s' is numeric */
225 static int
226 is_number(char *s)
227 {
228 	for ( ; *s ; s++) {
229 		if (!isdigit(*s)) {
230 			return 0;
231 		}
232 	}
233 	return 1;
234 }
235 #endif
236 
237 /*
238  * check that the effective uid is 0 - called from funcs which will
239  * modify data and config files.
240  */
241 static void
242 checkeuid(void)
243 {
244 	if (geteuid() != 0) {
245 		errx(EXIT_FAILURE, "Program must be run as root");
246 	}
247 }
248 
249 /* copy any dot files into the user's home directory */
250 static int
251 copydotfiles(char *skeldir, int uid, int gid, char *dir)
252 {
253 	struct dirent	*dp;
254 	DIR		*dirp;
255 	int		n;
256 
257 	if ((dirp = opendir(skeldir)) == (DIR *) NULL) {
258 		warn("can't open source . files dir `%s'", skeldir);
259 		return 0;
260 	}
261 	for (n = 0; (dp = readdir(dirp)) != (struct dirent *) NULL && n == 0 ; ) {
262 		if (strcmp(dp->d_name, ".") == 0 ||
263 		    strcmp(dp->d_name, "..") == 0) {
264 			continue;
265 		}
266 		n = 1;
267 	}
268 	(void) closedir(dirp);
269 	if (n == 0) {
270 		warnx("No \"dot\" initialisation files found");
271 	} else {
272 		(void) asystem("cd %s; %s -rw -pe %s . %s",
273 				skeldir, PAX, (verbose) ? "-v" : "", dir);
274 	}
275 	(void) asystem("%s -R -h %d:%d %s", CHOWN, uid, gid, dir);
276 	return n;
277 }
278 
279 /* create a group entry with gid `gid' */
280 static int
281 creategid(char *group, int gid, char *name)
282 {
283 	struct stat	st;
284 	FILE		*from;
285 	FILE		*to;
286 	char		buf[MaxEntryLen];
287 	char		f[MaxFileNameLen];
288 	int		fd;
289 	int		cc;
290 
291 	if ((from = fopen(ETCGROUP, "r")) == (FILE *) NULL) {
292 		warn("can't create gid for %s: can't open %s", name, ETCGROUP);
293 		return 0;
294 	}
295 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
296 		warn("can't lock `%s'", ETCGROUP);
297 	}
298 	(void) fstat(fileno(from), &st);
299 	(void) snprintf(f, sizeof(f), "%s.XXXXXX", ETCGROUP);
300 	if ((fd = mkstemp(f)) < 0) {
301 		(void) fclose(from);
302 		warn("can't create gid: mkstemp failed");
303 		return 0;
304 	}
305 	if ((to = fdopen(fd, "w")) == (FILE *) NULL) {
306 		(void) fclose(from);
307 		(void) close(fd);
308 		(void) unlink(f);
309 		warn("can't create gid: fdopen `%s' failed", f);
310 		return 0;
311 	}
312 	while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) {
313 		if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
314 			(void) fclose(from);
315 			(void) close(fd);
316 			(void) unlink(f);
317 			warn("can't create gid: short write to `%s'", f);
318 			return 0;
319 		}
320 	}
321 	(void) fprintf(to, "%s:*:%d:%s\n", group, gid, name);
322 	(void) fclose(from);
323 	(void) fclose(to);
324 	if (rename(f, ETCGROUP) < 0) {
325 		warn("can't create gid: can't rename `%s' to `%s'", f, ETCGROUP);
326 		return 0;
327 	}
328 	(void) chmod(ETCGROUP, st.st_mode & 07777);
329 	return 1;
330 }
331 
332 /* modify the group entry with name `group' to be newent */
333 static int
334 modify_gid(char *group, char *newent)
335 {
336 	struct stat	st;
337 	FILE		*from;
338 	FILE		*to;
339 	char		buf[MaxEntryLen];
340 	char		f[MaxFileNameLen];
341 	char		*colon;
342 	int		groupc;
343 	int		entc;
344 	int		fd;
345 	int		cc;
346 
347 	if ((from = fopen(ETCGROUP, "r")) == (FILE *) NULL) {
348 		warn("can't create gid for %s: can't open %s", group, ETCGROUP);
349 		return 0;
350 	}
351 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
352 		warn("can't lock `%s'", ETCGROUP);
353 	}
354 	(void) fstat(fileno(from), &st);
355 	(void) snprintf(f, sizeof(f), "%s.XXXXXX", ETCGROUP);
356 	if ((fd = mkstemp(f)) < 0) {
357 		(void) fclose(from);
358 		warn("can't create gid: mkstemp failed");
359 		return 0;
360 	}
361 	if ((to = fdopen(fd, "w")) == (FILE *) NULL) {
362 		(void) fclose(from);
363 		(void) close(fd);
364 		(void) unlink(f);
365 		warn("can't create gid: fdopen `%s' failed", f);
366 		return 0;
367 	}
368 	groupc = strlen(group);
369 	while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) {
370 		if ((colon = strchr(buf, ':')) == (char *) NULL) {
371 			warn("badly formed entry `%s'", buf);
372 			continue;
373 		}
374 		entc = (int)(colon - buf);
375 		if (entc == groupc && strncmp(group, buf, (unsigned) entc) == 0) {
376 			if (newent == (char *) NULL) {
377 				continue;
378 			} else {
379 				cc = strlen(newent);
380 				(void) strlcpy(buf, newent, sizeof(buf));
381 			}
382 		}
383 		if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
384 			(void) fclose(from);
385 			(void) close(fd);
386 			(void) unlink(f);
387 			warn("can't create gid: short write to `%s'", f);
388 			return 0;
389 		}
390 	}
391 	(void) fclose(from);
392 	(void) fclose(to);
393 	if (rename(f, ETCGROUP) < 0) {
394 		warn("can't create gid: can't rename `%s' to `%s'", f, ETCGROUP);
395 		return 0;
396 	}
397 	(void) chmod(ETCGROUP, st.st_mode & 07777);
398 	return 1;
399 }
400 
401 /* return 1 if `login' is a valid login name */
402 static int
403 valid_login(char *login)
404 {
405 	char	*cp;
406 
407 	for (cp = login ; *cp ; cp++) {
408 		if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-') {
409 			return 0;
410 		}
411 	}
412 	return 1;
413 }
414 
415 /* return 1 if `group' is a valid group name */
416 static int
417 valid_group(char *group)
418 {
419 	char	*cp;
420 
421 	for (cp = group ; *cp ; cp++) {
422 		if (!isalnum(*cp)) {
423 			return 0;
424 		}
425 	}
426 	return 1;
427 }
428 
429 /* find the next gid in the range lo .. hi */
430 static int
431 getnextgid(int *gidp, int lo, int hi)
432 {
433 	for (*gidp = lo ; *gidp < hi ; *gidp += 1) {
434 		if (getgrgid((gid_t)*gidp) == (struct group *) NULL) {
435 			return 1;
436 		}
437 	}
438 	return 0;
439 }
440 
441 #ifdef EXTENSIONS
442 /* save a range of uids */
443 static int
444 save_range(user_t *up, char *cp)
445 {
446 	int	from;
447 	int	to;
448 	int	i;
449 
450 	if (up->u_rsize == 0) {
451 		up->u_rsize = 32;
452 		NEWARRAY(range_t, up->u_rv, up->u_rsize, return(0));
453 	} else if (up->u_rc == up->u_rsize) {
454 		up->u_rsize *= 2;
455 		RENEW(range_t, up->u_rv, up->u_rsize, return(0));
456 	}
457 	if (up->u_rv && sscanf(cp, "%d..%d", &from, &to) == 2) {
458 		for (i = 0 ; i < up->u_rc ; i++) {
459 			if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) {
460 				break;
461 			}
462 		}
463 		if (i == up->u_rc) {
464 			up->u_rv[up->u_rc].r_from = from;
465 			up->u_rv[up->u_rc].r_to = to;
466 			up->u_rc += 1;
467 		}
468 	} else {
469 		warnx("Bad range `%s'", cp);
470 		return 0;
471 	}
472 	return 1;
473 }
474 #endif
475 
476 /* set the defaults in the defaults file */
477 static int
478 setdefaults(user_t *up)
479 {
480 	char	template[MaxFileNameLen];
481 	FILE	*fp;
482 	int	ret;
483 	int	fd;
484 	int	i;
485 
486 	(void) snprintf(template, sizeof(template), "%s.XXXXXX", CONFFILE);
487 	if ((fd = mkstemp(template)) < 0) {
488 		warnx("can't mkstemp `%s' for writing", CONFFILE);
489 		return 0;
490 	}
491 	if ((fp = fdopen(fd, "w")) == (FILE *) NULL) {
492 		warn("can't fdopen `%s' for writing", CONFFILE);
493 		return 0;
494 	}
495 	ret = 1;
496 	if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 ||
497 	    fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 ||
498 	    fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 ||
499 	    fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 ||
500 	    fprintf(fp, "inactive\t%d\n", up->u_inactive) <= 0 ||
501 	    fprintf(fp, "expire\t\t%s\n", (up->u_expire == (char *) NULL) ? UNSET_EXPIRY : up->u_expire) <= 0) {
502 		warn("can't write to `%s'", CONFFILE);
503 		ret = 0;
504 	}
505 #ifdef EXTENSIONS
506 	for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) {
507 		if (fprintf(fp, "range\t\t%d..%d\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) {
508 			warn("can't write to `%s'", CONFFILE);
509 			ret = 0;
510 		}
511 	}
512 #endif
513 	(void) fclose(fp);
514 	if (ret) {
515 		ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0));
516 	}
517 	return ret;
518 }
519 
520 /* read the defaults file */
521 static void
522 read_defaults(user_t *up)
523 {
524 	struct stat	st;
525 	size_t		lineno;
526 	size_t		len;
527 	FILE		*fp;
528 	char		*cp;
529 	char		*s;
530 
531 	memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP));
532 	memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR));
533 	memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR));
534 	memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL));
535 	memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT));
536 	up->u_rsize = 16;
537 	NEWARRAY(range_t, up->u_rv, up->u_rsize, exit(1));
538 	up->u_inactive = DEF_INACTIVE;
539 	up->u_expire = DEF_EXPIRE;
540 	if ((fp = fopen(CONFFILE, "r")) == (FILE *) NULL) {
541 		if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) {
542 			warn("can't create `%s' defaults file", CONFFILE);
543 		}
544 		fp = fopen(CONFFILE, "r");
545 	}
546 	if (fp != (FILE *) NULL) {
547 		while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != (char *) NULL) {
548 			if (strncmp(s, "group", 5) == 0) {
549 				for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
550 				}
551 				memsave(&up->u_primgrp, cp, strlen(cp));
552 			} else if (strncmp(s, "base_dir", 8) == 0) {
553 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
554 				}
555 				memsave(&up->u_basedir, cp, strlen(cp));
556 			} else if (strncmp(s, "skel_dir", 8) == 0) {
557 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
558 				}
559 				memsave(&up->u_skeldir, cp, strlen(cp));
560 			} else if (strncmp(s, "shell", 5) == 0) {
561 				for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
562 				}
563 				memsave(&up->u_shell, cp, strlen(cp));
564 			} else if (strncmp(s, "inactive", 8) == 0) {
565 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
566 				}
567 				up->u_inactive = atoi(cp);
568 #ifdef EXTENSIONS
569 			} else if (strncmp(s, "range", 5) == 0) {
570 				for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
571 				}
572 				(void) save_range(up, cp);
573 #endif
574 #ifdef EXTENSIONS
575 			} else if (strncmp(s, "preserve", 8) == 0) {
576 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
577 				}
578 				up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 :
579 						  (strncmp(cp, "yes", 3) == 0) ? 1 :
580 						   atoi(cp);
581 #endif
582 			} else if (strncmp(s, "expire", 6) == 0) {
583 				for (cp = s + 6 ; *cp && isspace(*cp) ; cp++) {
584 				}
585 				if (strcmp(cp, UNSET_EXPIRY) == 0) {
586 					if (up->u_expire) {
587 						FREE(up->u_expire);
588 					}
589 					up->u_expire = (char *) NULL;
590 				} else {
591 					memsave(&up->u_expire, cp, strlen(cp));
592 				}
593 			}
594 			(void) free(s);
595 		}
596 		(void) fclose(fp);
597 	}
598 	if (up->u_rc == 0) {
599 		up->u_rv[up->u_rc].r_from = DEF_LOWUID;
600 		up->u_rv[up->u_rc].r_to = DEF_HIGHUID;
601 		up->u_rc += 1;
602 	}
603 	up->u_defrc = up->u_rc;
604 }
605 
606 /* return the next valid unused uid */
607 static int
608 getnextuid(int sync_uid_gid, int *uid, int low_uid, int high_uid)
609 {
610 	for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) {
611 		if (getpwuid((uid_t)(*uid)) == (struct passwd *) NULL && *uid != NOBODY_UID) {
612 			if (sync_uid_gid) {
613 				if (getgrgid((gid_t)(*uid)) == (struct group *) NULL) {
614 					return 1;
615 				}
616 			} else {
617 				return 1;
618 			}
619 		}
620 	}
621 	return 0;
622 }
623 
624 /* add a user */
625 static int
626 adduser(char *login, user_t *up)
627 {
628 	struct group	*grp;
629 	struct stat	st;
630 	struct tm	tm;
631 	time_t		expire;
632 	char		password[PasswordLength + 1];
633 	char		home[MaxFileNameLen];
634 	char		buf[MaxFileNameLen];
635 	int		sync_uid_gid;
636 	int		masterfd;
637 	int		ptmpfd;
638 	int		gid;
639 	int		cc;
640 	int		i;
641 
642 	if (!valid_login(login)) {
643 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login);
644 	}
645 	if ((masterfd = open(MASTER, O_RDONLY)) < 0) {
646 		err(EXIT_FAILURE, "can't open `%s'", MASTER);
647 	}
648 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
649 		err(EXIT_FAILURE, "can't lock `%s'", MASTER);
650 	}
651 	pw_init();
652 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
653 		(void) close(masterfd);
654 		err(EXIT_FAILURE, "can't obtain pw_lock");
655 	}
656 	while ((cc = read(masterfd, buf, sizeof(buf))) > 0) {
657 		if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
658 			(void) close(masterfd);
659 			(void) close(ptmpfd);
660 			(void) pw_abort();
661 			err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc);
662 		}
663 	}
664 	/* if no uid was specified, get next one in [low_uid..high_uid] range */
665 	sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
666 	if (up->u_uid == -1) {
667 		for (i = 0 ; i < up->u_rc ; i++) {
668 			if (getnextuid(sync_uid_gid, &up->u_uid, up->u_rv[i].r_from, up->u_rv[i].r_to)) {
669 				break;
670 			}
671 		}
672 		if (i == up->u_rc) {
673 			(void) close(ptmpfd);
674 			(void) pw_abort();
675 			errx(EXIT_FAILURE, "can't get next uid for %d", up->u_uid);
676 		}
677 	}
678 	/* check uid isn't already allocated */
679 	if (!up->u_dupuid && getpwuid((uid_t)(up->u_uid)) != (struct passwd *) NULL) {
680 		(void) close(ptmpfd);
681 		(void) pw_abort();
682 		errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid);
683 	}
684 	/* if -g=uid was specified, check gid is unused */
685 	if (sync_uid_gid) {
686 		if (getgrgid((gid_t)(up->u_uid)) != (struct group *) NULL) {
687 			(void) close(ptmpfd);
688 			(void) pw_abort();
689 			errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid);
690 		}
691 		gid = up->u_uid;
692 	} else if ((grp = getgrnam(up->u_primgrp)) != (struct group *) NULL) {
693 		gid = grp->gr_gid;
694 	} else if (is_number(up->u_primgrp) &&
695 		   (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != (struct group *) NULL) {
696 		gid = grp->gr_gid;
697 	} else {
698 		(void) close(ptmpfd);
699 		(void) pw_abort();
700 		errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
701 	}
702 	/* check name isn't already in use */
703 	if (!up->u_dupuid && getpwnam(login) != (struct passwd *) NULL) {
704 		(void) close(ptmpfd);
705 		(void) pw_abort();
706 		errx(EXIT_FAILURE, "already a `%s' user", login);
707 	}
708 	/* if home directory hasn't been given, make it up */
709 	if (!up->u_homeset) {
710 		(void) snprintf(home, sizeof(home), "%s/%s", up->u_basedir, login);
711 	}
712 	expire = 0;
713 	if (up->u_expire != (char *) NULL) {
714 		(void) memset(&tm, 0, sizeof(tm));
715 		if (strptime(up->u_expire, "%c", &tm) == (char *) NULL) {
716 			warnx("invalid time format `%s'", optarg);
717 		} else {
718 			expire = mktime(&tm);
719 		}
720 	}
721 	password[PasswordLength] = 0;
722 	if (up->u_password != (char *) NULL &&
723 	    strlen(up->u_password) == PasswordLength) {
724 		(void) memcpy(password, up->u_password, PasswordLength);
725 	} else {
726 		(void) memset(password, '*', PasswordLength);
727 		if (up->u_password != (char *) NULL) {
728 			warnx("Password `%s' is invalid: setting it to `%s'",
729 				up->u_password, password);
730 		}
731 	}
732 	cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d::%d:%ld:%s:%s:%s\n",
733 			login,
734 			password,
735 			up->u_uid,
736 			gid,
737 			up->u_inactive,
738 			(long) expire,
739 			up->u_comment,
740 			home,
741 			up->u_shell);
742 	if (write(ptmpfd, buf, (size_t) cc) != cc) {
743 		(void) close(ptmpfd);
744 		(void) pw_abort();
745 		err(EXIT_FAILURE, "can't add `%s'", buf);
746 	}
747 	if (up->u_mkdir) {
748 		if (lstat(home, &st) < 0 && asystem("%s -p %s", MKDIR, home) != 0) {
749 			(void) close(ptmpfd);
750 			(void) pw_abort();
751 			err(EXIT_FAILURE, "can't mkdir `%s'", home);
752 		}
753 		(void) copydotfiles(up->u_skeldir, up->u_uid, gid, home);
754 	}
755 	if (strcmp(up->u_primgrp, "=uid") == 0 &&
756 	    getgrnam(login) == (struct group *) NULL &&
757 	    !creategid(login, gid, login)) {
758 		(void) close(ptmpfd);
759 		(void) pw_abort();
760 		err(EXIT_FAILURE, "can't create gid %d for login name %s", gid, login);
761 	}
762 	(void) close(ptmpfd);
763 	if (pw_mkdb() < 0) {
764 		err(EXIT_FAILURE, "pw_mkdb failed");
765 	}
766 	return 1;
767 }
768 
769 /* modify a user */
770 static int
771 moduser(char *login, char *newlogin, user_t *up)
772 {
773 	struct passwd	*pwp;
774 	struct group	*grp;
775 	struct tm	tm;
776 	time_t		expire;
777 	size_t		loginc;
778 	size_t		colonc;
779 	FILE		*master;
780 	char		password[PasswordLength + 1];
781 	char		oldhome[MaxFileNameLen];
782 	char		home[MaxFileNameLen];
783 	char		buf[MaxFileNameLen];
784 	char		*colon;
785 	int		masterfd;
786 	int		ptmpfd;
787 	int		gid;
788 	int		cc;
789 
790 	if (!valid_login(newlogin)) {
791 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login);
792 	}
793 	if ((pwp = getpwnam(login)) == (struct passwd *) NULL) {
794 		errx(EXIT_FAILURE, "No such user `%s'", login);
795 	}
796 	if ((masterfd = open(MASTER, O_RDONLY)) < 0) {
797 		err(EXIT_FAILURE, "can't open `%s'", MASTER);
798 	}
799 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
800 		err(EXIT_FAILURE, "can't lock `%s'", MASTER);
801 	}
802 	pw_init();
803 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
804 		(void) close(masterfd);
805 		err(EXIT_FAILURE, "can't obtain pw_lock");
806 	}
807 	if ((master = fdopen(masterfd, "r")) == (FILE *) NULL) {
808 		(void) close(masterfd);
809 		(void) close(ptmpfd);
810 		(void) pw_abort();
811 		err(EXIT_FAILURE, "can't fdopen fd for %s", MASTER);
812 	}
813 	if (up != (user_t *) NULL) {
814 		if (up->u_mkdir) {
815 			(void) strcpy(oldhome, pwp->pw_dir);
816 		}
817 		if (up->u_uid == -1) {
818 			up->u_uid = pwp->pw_uid;
819 		}
820 		/* if -g=uid was specified, check gid is unused */
821 		if (strcmp(up->u_primgrp, "=uid") == 0) {
822 			if (getgrgid((gid_t)(up->u_uid)) != (struct group *) NULL) {
823 				(void) close(ptmpfd);
824 				(void) pw_abort();
825 				errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid);
826 			}
827 			gid = up->u_uid;
828 		} else if ((grp = getgrnam(up->u_primgrp)) != (struct group *) NULL) {
829 			gid = grp->gr_gid;
830 		} else if (is_number(up->u_primgrp) &&
831 			   (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != (struct group *) NULL) {
832 			gid = grp->gr_gid;
833 		} else {
834 			(void) close(ptmpfd);
835 			(void) pw_abort();
836 			errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
837 		}
838 		/* if changing name, check new name isn't already in use */
839 		if (strcmp(login, newlogin) != 0 && getpwnam(newlogin) != (struct passwd *) NULL) {
840 			(void) close(ptmpfd);
841 			(void) pw_abort();
842 			errx(EXIT_FAILURE, "already a `%s' user", newlogin);
843 		}
844 		/* if home directory hasn't been given, use the old one */
845 		if (!up->u_homeset) {
846 			(void) strcpy(home, pwp->pw_dir);
847 		}
848 		expire = 0;
849 		if (up->u_expire != (char *) NULL) {
850 			(void) memset(&tm, 0, sizeof(tm));
851 			if (strptime(up->u_expire, "%c", &tm) == (char *) NULL) {
852 				warnx("invalid time format `%s'", optarg);
853 			} else {
854 				expire = mktime(&tm);
855 			}
856 		}
857 		password[PasswordLength] = 0;
858 		if (up->u_password != (char *) NULL &&
859 		    strlen(up->u_password) == PasswordLength) {
860 			(void) memcpy(password, up->u_password, PasswordLength);
861 		} else {
862 			(void) memcpy(password, pwp->pw_passwd, PasswordLength);
863 		}
864 		if (strcmp(up->u_comment, DEF_COMMENT) == 0) {
865 			memsave(&up->u_comment, pwp->pw_gecos, strlen(pwp->pw_gecos));
866 		}
867 		if (strcmp(up->u_shell, DEF_SHELL) == 0 && strcmp(pwp->pw_shell, DEF_SHELL) != 0) {
868 			memsave(&up->u_comment, pwp->pw_shell, strlen(pwp->pw_shell));
869 		}
870 	}
871 	loginc = strlen(login);
872 	while (fgets(buf, sizeof(buf), master) != (char *) NULL) {
873 		cc = strlen(buf);
874 		if ((colon = strchr(buf, ':')) == (char *) NULL) {
875 			warnx("Malformed entry `%s'. Skipping", buf);
876 			continue;
877 		}
878 		colonc = (size_t)(colon - buf);
879 		if (strncmp(login, buf, loginc) == 0 && loginc == colonc) {
880 			if (up != (user_t *) NULL) {
881 				cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d::%d:%ld:%s:%s:%s\n",
882 					newlogin,
883 					password,
884 					up->u_uid,
885 					gid,
886 					up->u_inactive,
887 					(long) expire,
888 					up->u_comment,
889 					home,
890 					up->u_shell);
891 				if (write(ptmpfd, buf, (size_t) cc) != cc) {
892 					(void) close(ptmpfd);
893 					(void) pw_abort();
894 					err(EXIT_FAILURE, "can't add `%s'", buf);
895 				}
896 			}
897 		} else if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
898 			(void) close(masterfd);
899 			(void) close(ptmpfd);
900 			(void) pw_abort();
901 			err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc);
902 		}
903 	}
904 	if (up != (user_t *) NULL &&
905 	    up->u_mkdir &&
906 	    asystem("%s %s %s", MV, oldhome, home) != 0) {
907 		(void) close(ptmpfd);
908 		(void) pw_abort();
909 		err(EXIT_FAILURE, "can't move `%s' to `%s'", oldhome, home);
910 	}
911 	(void) close(ptmpfd);
912 	if (pw_mkdb() < 0) {
913 		err(EXIT_FAILURE, "pw_mkdb failed");
914 	}
915 	return 1;
916 }
917 
918 
919 #ifdef EXTENSIONS
920 /* see if we can find out the user struct */
921 static struct passwd *
922 find_user_info(char *name)
923 {
924 	struct passwd	*pwp;
925 
926 	if ((pwp = getpwnam(name)) != (struct passwd *) NULL) {
927 		return pwp;
928 	}
929 	if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != (struct passwd *) NULL) {
930 		return pwp;
931 	}
932 	return (struct passwd *) NULL;
933 }
934 #endif
935 
936 #ifdef EXTENSIONS
937 /* see if we can find out the group struct */
938 static struct group *
939 find_group_info(char *name)
940 {
941 	struct group	*grp;
942 
943 	if ((grp = getgrnam(name)) != (struct group *) NULL) {
944 		return grp;
945 	}
946 	if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != (struct group *) NULL) {
947 		return grp;
948 	}
949 	return (struct group *) NULL;
950 }
951 #endif
952 
953 /* print out usage message, and then exit */
954 void
955 usermgmt_usage(char *prog)
956 {
957 	if (strcmp(prog, "useradd") == 0) {
958 		(void) fprintf(stderr, "Usage: %s -D [-b basedir] [-e expiry] [-f inactive] [-g group] [-r lowuid..highuid] [-s shell]\n", prog);
959 		(void) fprintf(stderr, "Usage: %s [-G group] [-b basedir] [-c comment] [-d homedir] [-e expiry] [-f inactive]\n\t[-g group] [-k skeletondir] [-m] [-o] [-p password] [-r lowuid..highuid] [-s shell]\n\t[-u uid] [-v] user\n", prog);
960 	} else if (strcmp(prog, "usermod") == 0) {
961 		(void) fprintf(stderr, "Usage: %s [-G group] [-c comment] [-d homedir] [-e expire] [-f inactive] [-g group] [-l newname] [-m] [-o] [-p password] [-s shell] [-u uid] [-v] user\n", prog);
962 	} else if (strcmp(prog, "userdel") == 0) {
963 		(void) fprintf(stderr, "Usage: %s -D [-p preserve]\n", prog);
964 		(void) fprintf(stderr, "Usage: %s [-p preserve] [-r] [-v] user\n", prog);
965 #ifdef EXTENSIONS
966 	} else if (strcmp(prog, "userinfo") == 0) {
967 		(void) fprintf(stderr, "Usage: %s [-e] [-v] user\n", prog);
968 #endif
969 	} else if (strcmp(prog, "groupadd") == 0) {
970 		(void) fprintf(stderr, "Usage: %s [-g gid] [-o] [-v] group\n", prog);
971 	} else if (strcmp(prog, "groupdel") == 0) {
972 		(void) fprintf(stderr, "Usage: %s [-v] group\n", prog);
973 	} else if (strcmp(prog, "groupmod") == 0) {
974 		(void) fprintf(stderr, "Usage: %s [-g gid] [-o] [-n newname] [-v] group\n", prog);
975 	} else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) {
976 		(void) fprintf(stderr, "Usage: %s ( add | del | mod ) ...\n", prog);
977 #ifdef EXTENSIONS
978 	} else if (strcmp(prog, "groupinfo") == 0) {
979 		(void) fprintf(stderr, "Usage: %s [-e] [-v] group\n", prog);
980 #endif
981 	}
982 	exit(EXIT_FAILURE);
983 	/* NOTREACHED */
984 }
985 
986 extern int	optind;
987 extern char	*optarg;
988 
989 #ifdef EXTENSIONS
990 #define ADD_OPT_EXTENSIONS	"p:r:v"
991 #else
992 #define ADD_OPT_EXTENSIONS
993 #endif
994 
995 int
996 useradd(int argc, char **argv)
997 {
998 	user_t	u;
999 	int	defaultfield;
1000 	int	bigD;
1001 	int	c;
1002 	int	i;
1003 
1004 	(void) memset(&u, 0, sizeof(u));
1005 	read_defaults(&u);
1006 	u.u_uid = -1;
1007 	defaultfield = bigD = 0;
1008 	while ((c = getopt(argc, argv, "DG:b:c:d:e:f:g:k:mou:s:" ADD_OPT_EXTENSIONS)) != -1) {
1009 		switch(c) {
1010 		case 'D':
1011 			bigD = 1;
1012 			break;
1013 		case 'G':
1014 			memsave(&u.u_groupv[u.u_groupc++], optarg, strlen(optarg));
1015 			break;
1016 		case 'b':
1017 			defaultfield = 1;
1018 			memsave(&u.u_basedir, optarg, strlen(optarg));
1019 			break;
1020 		case 'c':
1021 			memsave(&u.u_comment, optarg, strlen(optarg));
1022 			break;
1023 		case 'd':
1024 			u.u_homeset = 1;
1025 			memsave(&u.u_home, optarg, strlen(optarg));
1026 			break;
1027 		case 'e':
1028 			defaultfield = 1;
1029 			memsave(&u.u_expire, optarg, strlen(optarg));
1030 			break;
1031 		case 'f':
1032 			defaultfield = 1;
1033 			u.u_inactive = atoi(optarg);
1034 			break;
1035 		case 'g':
1036 			defaultfield = 1;
1037 			memsave(&u.u_primgrp, optarg, strlen(optarg));
1038 			break;
1039 		case 'k':
1040 			memsave(&u.u_skeldir, optarg, strlen(optarg));
1041 			break;
1042 		case 'm':
1043 			u.u_mkdir = 1;
1044 			break;
1045 		case 'o':
1046 			u.u_dupuid = 1;
1047 			break;
1048 #ifdef EXTENSIONS
1049 		case 'p':
1050 			memsave(&u.u_password, optarg, strlen(optarg));
1051 			break;
1052 #endif
1053 #ifdef EXTENSIONS
1054 		case 'r':
1055 			defaultfield = 1;
1056 			(void) save_range(&u, optarg);
1057 			break;
1058 #endif
1059 		case 's':
1060 			defaultfield = 1;
1061 			memsave(&u.u_shell, optarg, strlen(optarg));
1062 			break;
1063 		case 'u':
1064 			if (!is_number(optarg)) {
1065 				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1066 			}
1067 			u.u_uid = atoi(optarg);
1068 			break;
1069 #ifdef EXTENSIONS
1070 		case 'v':
1071 			verbose = 1;
1072 			break;
1073 #endif
1074 		}
1075 	}
1076 	if (bigD) {
1077 		if (defaultfield) {
1078 			checkeuid();
1079 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
1080 		}
1081 		(void) printf("group\t\t%s\n", u.u_primgrp);
1082 		(void) printf("base_dir\t%s\n", u.u_basedir);
1083 		(void) printf("skel_dir\t%s\n", u.u_skeldir);
1084 		(void) printf("shell\t\t%s\n", u.u_shell);
1085 		(void) printf("inactive\t%d\n", u.u_inactive);
1086 		(void) printf("expire\t\t%s\n", (u.u_expire == (char *) NULL) ? UNSET_EXPIRY : u.u_expire);
1087 #ifdef EXTENSIONS
1088 		for (i = 0 ; i < u.u_rc ; i++) {
1089 			(void) printf("range\t\t%d..%d\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
1090 		}
1091 #endif
1092 		return EXIT_SUCCESS;
1093 	}
1094 	if (argc == optind) {
1095 		usermgmt_usage("useradd");
1096 	}
1097 	checkeuid();
1098 	return adduser(argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1099 }
1100 
1101 #ifdef EXTENSIONS
1102 #define MOD_OPT_EXTENSIONS	"p:v"
1103 #else
1104 #define MOD_OPT_EXTENSIONS
1105 #endif
1106 
1107 int
1108 usermod(int argc, char **argv)
1109 {
1110 	user_t	u;
1111 	char	newuser[MaxUserNameLen + 1];
1112 	int	have_new_user;
1113 	int	c;
1114 
1115 	checkeuid();
1116 	(void) memset(&u, 0, sizeof(u));
1117 	(void) memset(newuser, 0, sizeof(newuser));
1118 	read_defaults(&u);
1119 	u.u_uid = -1;
1120 	have_new_user = 0;
1121 	while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) {
1122 		switch(c) {
1123 		case 'G':
1124 			memsave(&u.u_groupv[u.u_groupc++], optarg, strlen(optarg));
1125 			break;
1126 		case 'c':
1127 			memsave(&u.u_comment, optarg, strlen(optarg));
1128 			break;
1129 		case 'd':
1130 			u.u_homeset = 1;
1131 			memsave(&u.u_home, optarg, strlen(optarg));
1132 			break;
1133 		case 'e':
1134 			memsave(&u.u_expire, optarg, strlen(optarg));
1135 			break;
1136 		case 'f':
1137 			u.u_inactive = atoi(optarg);
1138 			break;
1139 		case 'g':
1140 			memsave(&u.u_primgrp, optarg, strlen(optarg));
1141 			break;
1142 		case 'l':
1143 			have_new_user = 1;
1144 			(void) strlcpy(newuser, optarg, sizeof(newuser));
1145 			break;
1146 		case 'm':
1147 			u.u_mkdir = 1;
1148 			break;
1149 		case 'o':
1150 			u.u_dupuid = 1;
1151 			break;
1152 #ifdef EXTENSIONS
1153 		case 'p':
1154 			memsave(&u.u_password, optarg, strlen(optarg));
1155 			break;
1156 #endif
1157 		case 's':
1158 			memsave(&u.u_shell, optarg, strlen(optarg));
1159 			break;
1160 		case 'u':
1161 			if (!is_number(optarg)) {
1162 				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1163 			}
1164 			u.u_uid = atoi(optarg);
1165 			break;
1166 #ifdef EXTENSIONS
1167 		case 'v':
1168 			verbose = 1;
1169 			break;
1170 #endif
1171 		}
1172 	}
1173 	if (argc == optind) {
1174 		usermgmt_usage("usermod");
1175 	}
1176 	return moduser(argv[optind], (have_new_user) ? newuser : argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1177 }
1178 
1179 #ifdef EXTENSIONS
1180 #define DEL_OPT_EXTENSIONS	"Dp:v"
1181 #else
1182 #define DEL_OPT_EXTENSIONS
1183 #endif
1184 
1185 int
1186 userdel(int argc, char **argv)
1187 {
1188 	struct passwd	*pwp;
1189 	struct stat	st;
1190 	user_t		u;
1191 	char		password[PasswordLength + 1];
1192 	int		defaultfield;
1193 	int		rmhome;
1194 	int		bigD;
1195 	int		c;
1196 
1197 	if (geteuid() != 0) {
1198 		errx(EXIT_FAILURE, "Program must be run as root");
1199 	}
1200 	(void) memset(&u, 0, sizeof(u));
1201 	read_defaults(&u);
1202 	defaultfield = bigD = rmhome = 0;
1203 	while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) {
1204 		switch(c) {
1205 #ifdef EXTENSIONS
1206 		case 'D':
1207 			bigD = 1;
1208 			break;
1209 #endif
1210 #ifdef EXTENSIONS
1211 		case 'p':
1212 			defaultfield = 1;
1213 			u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
1214 					(strcmp(optarg, "yes") == 0) ? 1 :
1215 					 atoi(optarg);
1216 			break;
1217 #endif
1218 		case 'r':
1219 			rmhome = 1;
1220 			break;
1221 #ifdef EXTENSIONS
1222 		case 'v':
1223 			verbose = 1;
1224 			break;
1225 #endif
1226 		}
1227 	}
1228 #ifdef EXTENSIONS
1229 	if (bigD) {
1230 		if (defaultfield) {
1231 			checkeuid();
1232 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
1233 		}
1234 		(void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
1235 		return EXIT_SUCCESS;
1236 	}
1237 #endif
1238 	if (argc == optind) {
1239 		usermgmt_usage("userdel");
1240 	}
1241 	checkeuid();
1242 	if ((pwp = getpwnam(argv[optind])) == (struct passwd *) NULL) {
1243 		warnx("No such user `%s'", argv[optind]);
1244 		return EXIT_FAILURE;
1245 	}
1246 	if (rmhome) {
1247 		if (stat(pwp->pw_dir, &st) < 0) {
1248 			warn("Home directory `%s' does not exist", pwp->pw_dir);
1249 			return EXIT_FAILURE;
1250 		}
1251 		(void) asystem("%s -rf %s", RM, pwp->pw_dir);
1252 	}
1253 	if (u.u_preserve) {
1254 		memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN));
1255 		(void) memset(password, '*', PasswordLength);
1256 		password[PasswordLength] = 0;
1257 		memsave(&u.u_password, password, PasswordLength);
1258 		return moduser(argv[optind], argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1259 	}
1260 	return moduser(argv[optind], argv[optind], (user_t *) NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
1261 }
1262 
1263 #ifdef EXTENSIONS
1264 #define GROUP_ADD_OPT_EXTENSIONS	"v"
1265 #else
1266 #define GROUP_ADD_OPT_EXTENSIONS
1267 #endif
1268 
1269 /* add a group */
1270 int
1271 groupadd(int argc, char **argv)
1272 {
1273 	int	dupgid;
1274 	int	gid;
1275 	int	c;
1276 
1277 	checkeuid();
1278 	gid = -1;
1279 	dupgid = 0;
1280 	while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) {
1281 		switch(c) {
1282 		case 'g':
1283 			if (!is_number(optarg)) {
1284 				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
1285 			}
1286 			gid = atoi(optarg);
1287 			break;
1288 		case 'o':
1289 			dupgid = 1;
1290 			break;
1291 #ifdef EXTENSIONS
1292 		case 'v':
1293 			verbose = 1;
1294 			break;
1295 #endif
1296 		}
1297 	}
1298 	if (argc == optind) {
1299 		usermgmt_usage("groupadd");
1300 	}
1301 	if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
1302 		err(EXIT_FAILURE, "can't add group: can't get next gid");
1303 	}
1304 	if (!dupgid && getgrgid((gid_t) gid) != (struct group *) NULL) {
1305 		errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
1306 	}
1307 	if (!valid_group(argv[optind])) {
1308 		warnx("warning - invalid group name `%s'", argv[optind]);
1309 	}
1310 	if (!creategid(argv[optind], gid, "")) {
1311 		err(EXIT_FAILURE, "can't add group: problems with %s file", ETCGROUP);
1312 	}
1313 	return EXIT_SUCCESS;
1314 }
1315 
1316 #ifdef EXTENSIONS
1317 #define GROUP_DEL_OPT_EXTENSIONS	"v"
1318 #else
1319 #define GROUP_DEL_OPT_EXTENSIONS
1320 #endif
1321 
1322 /* remove a group */
1323 int
1324 groupdel(int argc, char **argv)
1325 {
1326 	int	c;
1327 
1328 	checkeuid();
1329 	while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) {
1330 		switch(c) {
1331 #ifdef EXTENSIONS
1332 		case 'v':
1333 			verbose = 1;
1334 			break;
1335 #endif
1336 		}
1337 	}
1338 	if (argc == optind) {
1339 		usermgmt_usage("groupdel");
1340 	}
1341 	if (!modify_gid(argv[optind], (char *) NULL)) {
1342 		err(EXIT_FAILURE, "can't change %s file", ETCGROUP);
1343 	}
1344 	return EXIT_SUCCESS;
1345 }
1346 
1347 #ifdef EXTENSIONS
1348 #define GROUP_MOD_OPT_EXTENSIONS	"v"
1349 #else
1350 #define GROUP_MOD_OPT_EXTENSIONS
1351 #endif
1352 
1353 /* modify a group */
1354 int
1355 groupmod(int argc, char **argv)
1356 {
1357 	struct group	*grp;
1358 	char		buf[MaxEntryLen];
1359 	char		*newname;
1360 	char		**cpp;
1361 	int		dupgid;
1362 	int		gid;
1363 	int		cc;
1364 	int		c;
1365 
1366 	checkeuid();
1367 	gid = -1;
1368 	dupgid = 0;
1369 	newname = (char *) NULL;
1370 	while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) {
1371 		switch(c) {
1372 		case 'g':
1373 			if (!is_number(optarg)) {
1374 				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
1375 			}
1376 			gid = atoi(optarg);
1377 			break;
1378 		case 'o':
1379 			dupgid = 1;
1380 			break;
1381 		case 'n':
1382 			memsave(&newname, optarg, strlen(optarg));
1383 			break;
1384 #ifdef EXTENSIONS
1385 		case 'v':
1386 			verbose = 1;
1387 			break;
1388 #endif
1389 		}
1390 	}
1391 	if (argc == optind) {
1392 		usermgmt_usage("groupmod");
1393 	}
1394 	if (gid < 0 && newname == (char *) NULL) {
1395 		err(EXIT_FAILURE, "Nothing to change");
1396 	}
1397 	if (dupgid && gid < 0) {
1398 		err(EXIT_FAILURE, "Duplicate which gid?");
1399 	}
1400 	if ((grp = getgrnam(argv[optind])) == (struct group *) NULL) {
1401 		err(EXIT_FAILURE, "can't find group `%s' to modify", argv[optind]);
1402 	}
1403 	if (newname != (char *) NULL && !valid_group(newname)) {
1404 		warn("warning - invalid group name `%s'", newname);
1405 	}
1406 	cc = snprintf(buf, sizeof(buf), "%s:%s:%d:",
1407 			(newname) ? newname : grp->gr_name,
1408 			grp->gr_passwd,
1409 			(gid < 0) ? grp->gr_gid : gid);
1410 	for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) {
1411 		cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp,
1412 			(cpp[1] == (char *) NULL) ? "" : ",");
1413 	}
1414 	if (!modify_gid(argv[optind], buf)) {
1415 		err(EXIT_FAILURE, "can't change %s file", ETCGROUP);
1416 	}
1417 	return EXIT_SUCCESS;
1418 }
1419 
1420 #ifdef EXTENSIONS
1421 /* display user information */
1422 int
1423 userinfo(int argc, char **argv)
1424 {
1425 	struct passwd	*pwp;
1426 	struct group	*grp;
1427 	char		buf[MaxEntryLen];
1428 	char		**cpp;
1429 	int		exists;
1430 	int		cc;
1431 	int		i;
1432 
1433 	exists = 0;
1434 	while ((i = getopt(argc, argv, "ev")) != -1) {
1435 		switch(i) {
1436 		case 'e':
1437 			exists = 1;
1438 			break;
1439 		case 'v':
1440 			verbose = 1;
1441 			break;
1442 		}
1443 	}
1444 	if (argc == optind) {
1445 		usermgmt_usage("userinfo");
1446 	}
1447 	pwp = find_user_info(argv[optind]);
1448 	if (exists) {
1449 		exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE);
1450 	}
1451 	if (pwp == (struct passwd *) NULL) {
1452 		errx(EXIT_FAILURE, "can't find user `%s'", argv[optind]);
1453 	}
1454 	(void) printf("login\t%s\n", pwp->pw_name);
1455 	(void) printf("passwd\t%s\n", pwp->pw_passwd);
1456 	(void) printf("uid\t%d\n", pwp->pw_uid);
1457 	for (cc = 0 ; (grp = getgrent()) != (struct group *) NULL ; ) {
1458 		for (cpp = grp->gr_mem ; *cpp ; cpp++) {
1459 			if (strcmp(*cpp, argv[optind]) == 0 && grp->gr_gid != pwp->pw_gid) {
1460 				cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s ", grp->gr_name);
1461 			}
1462 		}
1463 	}
1464 	if ((grp = getgrgid(pwp->pw_gid)) == (struct group *) NULL) {
1465 		(void) printf("groups\t%d %s\n", pwp->pw_gid, buf);
1466 	} else {
1467 		(void) printf("groups\t%s %s\n", grp->gr_name, buf);
1468 	}
1469 	(void) printf("change\t%s", ctime(&pwp->pw_change));
1470 	(void) printf("class\t%s\n", pwp->pw_class);
1471 	(void) printf("gecos\t%s\n", pwp->pw_gecos);
1472 	(void) printf("dir\t%s\n", pwp->pw_dir);
1473 	(void) printf("shell\t%s\n", pwp->pw_shell);
1474 	(void) printf("expire\t%s", ctime(&pwp->pw_expire));
1475 	return EXIT_SUCCESS;
1476 }
1477 #endif
1478 
1479 #ifdef EXTENSIONS
1480 /* display user information */
1481 int
1482 groupinfo(int argc, char **argv)
1483 {
1484 	struct group	*grp;
1485 	char		**cpp;
1486 	int		exists;
1487 	int		i;
1488 
1489 	exists = 0;
1490 	while ((i = getopt(argc, argv, "ev")) != -1) {
1491 		switch(i) {
1492 		case 'e':
1493 			exists = 1;
1494 			break;
1495 		case 'v':
1496 			verbose = 1;
1497 			break;
1498 		}
1499 	}
1500 	if (argc == optind) {
1501 		usermgmt_usage("groupinfo");
1502 	}
1503 	grp = find_group_info(argv[optind]);
1504 	if (exists) {
1505 		exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE);
1506 	}
1507 	if (grp == (struct group *) NULL) {
1508 		errx(EXIT_FAILURE, "can't find group `%s'", argv[optind]);
1509 	}
1510 	(void) printf("name\t%s\n", grp->gr_name);
1511 	(void) printf("passwd\t%s\n", grp->gr_passwd);
1512 	(void) printf("gid\t%d\n", grp->gr_gid);
1513 	(void) printf("members\t");
1514 	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
1515 		(void) printf("%s ", *cpp);
1516 	}
1517 	(void) fputc('\n', stdout);
1518 	return EXIT_SUCCESS;
1519 }
1520 #endif
1521