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