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