Home | History | Annotate | Line # | Download | only in user
user.c revision 1.1
      1 /* $NetBSD: user.c,v 1.1 1999/12/06 21:31:47 agc Exp $ */
      2 
      3 /*
      4  * Copyright  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/types.h>
     34 #include <sys/param.h>
     35 #include <sys/stat.h>
     36 
     37 #include <stdio.h>
     38 #include <stdlib.h>
     39 #include <unistd.h>
     40 #include <dirent.h>
     41 #include <string.h>
     42 #include <stdarg.h>
     43 #include <ctype.h>
     44 #include <fcntl.h>
     45 #include <time.h>
     46 #include <util.h>
     47 #include <pwd.h>
     48 #include <grp.h>
     49 #include <err.h>
     50 
     51 #include "defs.h"
     52 #include "usermgmt.h"
     53 
     54 static int	verbose;
     55 
     56 /* if *cpp is non-null, free it, then assign `n' chars of `s' to it */
     57 static void
     58 memsave(char **cpp, char *s, size_t n)
     59 {
     60 	if (*cpp != (char *) NULL) {
     61 		FREE(*cpp);
     62 	}
     63 	NEWARRAY(char, *cpp, n + 1, exit(1));
     64 	(void) memcpy(*cpp, s, n);
     65 	(*cpp)[n] = 0;
     66 }
     67 
     68 /* a replacement for system(3) */
     69 static int
     70 asystem(char *fmt, ...)
     71 {
     72 	va_list	vp;
     73 	char	buf[MaxCommandLen];
     74 	int	ret;
     75 
     76 	va_start(vp, fmt);
     77 	(void) vsnprintf(buf, sizeof(buf), fmt, vp);
     78 	va_end(vp);
     79 	if (verbose) {
     80 		(void) printf("Command: %s\n", buf);
     81 	}
     82 	if ((ret = system(buf)) != 0) {
     83 		warnx("[Warning] can't system `%s'", buf);
     84 	}
     85 	return ret;
     86 }
     87 
     88 /* bounds checking strncpy */
     89 static char *
     90 strnncpy(char *to, size_t tosize, char *from, size_t fromsize)
     91 {
     92 	size_t	n = MIN(tosize - 1, fromsize);
     93 
     94 	(void) memcpy(to, from, n);
     95 	to[n] = 0;
     96 	return to;
     97 }
     98 
     99 /* copy any dot files into the user's home directory */
    100 static int
    101 copydotfiles(char *skeldir, int uid, int gid, char *dir)
    102 {
    103 	struct dirent	*dp;
    104 	DIR		*dirp;
    105 	int		n;
    106 
    107 	if ((dirp = opendir(skeldir)) == (DIR *) NULL) {
    108 		warn("can't open source . files dir `%s'", skeldir);
    109 		return 0;
    110 	}
    111 	for (n = 0; (dp = readdir(dirp)) != (struct dirent *) NULL && n == 0 ; ) {
    112 		if (strcmp(dp->d_name, ".") == 0 ||
    113 		    strcmp(dp->d_name, "..") == 0) {
    114 			continue;
    115 		}
    116 		if (dp->d_name[0] == '.' && isalnum(dp->d_name[1])) {
    117 			n = 1;
    118 		}
    119 	}
    120 	(void) closedir(dirp);
    121 	if (n == 0) {
    122 		warnx("No \"dot\" initialisation files found");
    123 	} else {
    124 		(void) asystem("%s -p -R %s/.[A-z]* %s", CP, skeldir, dir);
    125 	}
    126 	(void) asystem("%s -R %d:%d %s", CHOWN, uid, gid, dir);
    127 	return n;
    128 }
    129 
    130 /* create a group entry with gid `gid' */
    131 static int
    132 creategid(char *group, int gid, char *name)
    133 {
    134 	struct stat	st;
    135 	FILE		*from;
    136 	FILE		*to;
    137 	char		buf[MaxEntryLen];
    138 	char		f[MaxFileNameLen];
    139 	int		fd;
    140 	int		cc;
    141 
    142 	if ((from = fopen(ETCGROUP, "r")) == (FILE *) NULL) {
    143 		warn("can't create gid for %s: can't open %s", name, ETCGROUP);
    144 		return 0;
    145 	}
    146 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
    147 		warn("can't lock `%s'", ETCGROUP);
    148 	}
    149 	(void) fstat(fileno(from), &st);
    150 	(void) snprintf(f, sizeof(f), "%s.XXXXXX", ETCGROUP);
    151 	if ((fd = mkstemp(f)) < 0) {
    152 		(void) fclose(from);
    153 		warn("can't create gid: mkstemp failed");
    154 		return 0;
    155 	}
    156 	if ((to = fdopen(fd, "w")) == (FILE *) NULL) {
    157 		(void) fclose(from);
    158 		(void) close(fd);
    159 		(void) unlink(f);
    160 		warn("can't create gid: fdopen `%s' failed", f);
    161 		return 0;
    162 	}
    163 	while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) {
    164 		if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
    165 			(void) fclose(from);
    166 			(void) close(fd);
    167 			(void) unlink(f);
    168 			warn("can't create gid: short write to `%s'", f);
    169 			return 0;
    170 		}
    171 	}
    172 	(void) fprintf(to, "%s:*:%d:%s\n", group, gid, name);
    173 	(void) fclose(from);
    174 	(void) fclose(to);
    175 	if (rename(f, ETCGROUP) < 0) {
    176 		warn("can't create gid: can't rename `%s' to `%s'", f, ETCGROUP);
    177 		return 0;
    178 	}
    179 	(void) chmod(ETCGROUP, st.st_mode & 07777);
    180 	return 1;
    181 }
    182 
    183 /* modify the group entry with name `group' to be newent */
    184 static int
    185 modify_gid(char *group, char *newent)
    186 {
    187 	struct stat	st;
    188 	FILE		*from;
    189 	FILE		*to;
    190 	char		buf[MaxEntryLen];
    191 	char		f[MaxFileNameLen];
    192 	char		*colon;
    193 	int		groupc;
    194 	int		entc;
    195 	int		fd;
    196 	int		cc;
    197 
    198 	if ((from = fopen(ETCGROUP, "r")) == (FILE *) NULL) {
    199 		warn("can't create gid for %s: can't open %s", group, ETCGROUP);
    200 		return 0;
    201 	}
    202 	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
    203 		warn("can't lock `%s'", ETCGROUP);
    204 	}
    205 	(void) fstat(fileno(from), &st);
    206 	(void) snprintf(f, sizeof(f), "%s.XXXXXX", ETCGROUP);
    207 	if ((fd = mkstemp(f)) < 0) {
    208 		(void) fclose(from);
    209 		warn("can't create gid: mkstemp failed");
    210 		return 0;
    211 	}
    212 	if ((to = fdopen(fd, "w")) == (FILE *) NULL) {
    213 		(void) fclose(from);
    214 		(void) close(fd);
    215 		(void) unlink(f);
    216 		warn("can't create gid: fdopen `%s' failed", f);
    217 		return 0;
    218 	}
    219 	groupc = strlen(group);
    220 	while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) {
    221 		if ((colon = strchr(buf, ':')) == (char *) NULL) {
    222 			warn("badly formed entry `%s'", buf);
    223 			continue;
    224 		}
    225 		entc = (int)(colon - buf);
    226 		if (entc == groupc && strncmp(group, buf, (unsigned) entc) == 0) {
    227 			if (newent == (char *) NULL) {
    228 				continue;
    229 			} else {
    230 				cc = strlen(newent);
    231 				(void) strnncpy(buf, sizeof(buf), newent, (unsigned) cc);
    232 			}
    233 		}
    234 		if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) {
    235 			(void) fclose(from);
    236 			(void) close(fd);
    237 			(void) unlink(f);
    238 			warn("can't create gid: short write to `%s'", f);
    239 			return 0;
    240 		}
    241 	}
    242 	(void) fclose(from);
    243 	(void) fclose(to);
    244 	if (rename(f, ETCGROUP) < 0) {
    245 		warn("can't create gid: can't rename `%s' to `%s'", f, ETCGROUP);
    246 		return 0;
    247 	}
    248 	(void) chmod(ETCGROUP, st.st_mode & 07777);
    249 	return 1;
    250 }
    251 
    252 /* return 1 if `login' is a valid login name */
    253 static int
    254 valid_login(char *login)
    255 {
    256 	char	*cp;
    257 
    258 	for (cp = login ; *cp ; cp++) {
    259 		if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-') {
    260 			return 0;
    261 		}
    262 	}
    263 	return 1;
    264 }
    265 
    266 /* return 1 if `group' is a valid group name */
    267 static int
    268 valid_group(char *group)
    269 {
    270 	char	*cp;
    271 
    272 	for (cp = group ; *cp ; cp++) {
    273 		if (!isalnum(*cp)) {
    274 			return 0;
    275 		}
    276 	}
    277 	return 1;
    278 }
    279 
    280 /* find the next gid in the range lo .. hi */
    281 static int
    282 getnextgid(int *gidp, int lo, int hi)
    283 {
    284 	for (*gidp = lo ; *gidp < hi ; *gidp += 1) {
    285 		if (getgrgid((gid_t)*gidp) == (struct group *) NULL) {
    286 			return 1;
    287 		}
    288 	}
    289 	return 0;
    290 }
    291 
    292 #ifdef EXTENSIONS
    293 /* save a range of uids */
    294 static int
    295 save_range(user_t *up, char *cp)
    296 {
    297 	int	from;
    298 	int	to;
    299 	int	i;
    300 
    301 	if (up->u_rsize == 0) {
    302 		up->u_rsize = 32;
    303 		NEWARRAY(range_t, up->u_rv, up->u_rsize, return(0));
    304 	} else if (up->u_rc == up->u_rsize) {
    305 		up->u_rsize *= 2;
    306 		RENEW(range_t, up->u_rv, up->u_rsize, return(0));
    307 	}
    308 	if (up->u_rv && sscanf(cp, "%d..%d", &from, &to) == 2) {
    309 		for (i = 0 ; i < up->u_rc ; i++) {
    310 			if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) {
    311 				break;
    312 			}
    313 		}
    314 		if (i == up->u_rc) {
    315 			up->u_rv[up->u_rc].r_from = from;
    316 			up->u_rv[up->u_rc].r_to = to;
    317 			up->u_rc += 1;
    318 		}
    319 	} else {
    320 		warnx("Bad range `%s'", cp);
    321 		return 0;
    322 	}
    323 	return 1;
    324 }
    325 #endif
    326 
    327 /* set the defaults in the defaults file */
    328 static int
    329 setdefaults(user_t *up)
    330 {
    331 	char	template[MaxFileNameLen];
    332 	FILE	*fp;
    333 	int	ret;
    334 	int	fd;
    335 	int	i;
    336 
    337 	(void) snprintf(template, sizeof(template), "%s.XXXXXX", CONFFILE);
    338 	if ((fd = mkstemp(template)) < 0) {
    339 		warnx("can't mkstemp `%s' for writing", CONFFILE);
    340 		return 0;
    341 	}
    342 	if ((fp = fdopen(fd, "w")) == (FILE *) NULL) {
    343 		warn("can't fdopen `%s' for writing", CONFFILE);
    344 		return 0;
    345 	}
    346 	ret = 1;
    347 	if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 ||
    348 	    fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 ||
    349 	    fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 ||
    350 	    fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 ||
    351 	    fprintf(fp, "inactive\t%d\n", up->u_inactive) <= 0 ||
    352 	    fprintf(fp, "expire\t\t%s\n", (up->u_expire == (char *) NULL) ? UNSET_EXPIRY : up->u_expire) <= 0) {
    353 		warn("can't write to `%s'", CONFFILE);
    354 		ret = 0;
    355 	}
    356 #ifdef EXTENSIONS
    357 	for (i = 0 ; i < up->u_rc ; i++) {
    358 		if (fprintf(fp, "range\t\t%d..%d\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) {
    359 			warn("can't write to `%s'", CONFFILE);
    360 			ret = 0;
    361 		}
    362 	}
    363 #endif
    364 	(void) fclose(fp);
    365 	if (ret) {
    366 		ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0));
    367 	}
    368 	return ret;
    369 }
    370 
    371 /* read the defaults file */
    372 static void
    373 read_defaults(user_t *up)
    374 {
    375 	struct stat	st;
    376 	size_t		lineno;
    377 	size_t		len;
    378 	FILE		*fp;
    379 	char		*cp;
    380 	char		*s;
    381 
    382 	memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP));
    383 	memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR));
    384 	memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR));
    385 	memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL));
    386 	memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT));
    387 	up->u_inactive = DEF_INACTIVE;
    388 	up->u_rsize = 16;
    389 	NEWARRAY(range_t, up->u_rv, up->u_rsize, exit(1));
    390 	up->u_rv[up->u_rc].r_from = DEF_LOWUID;
    391 	up->u_rv[up->u_rc].r_to = DEF_HIGHUID;
    392 	up->u_rc += 1;
    393 	up->u_expire = DEF_EXPIRE;
    394 	if ((fp = fopen(CONFFILE, "r")) == (FILE *) NULL) {
    395 		if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) {
    396 			warn("can't create `%s' defaults file", CONFFILE);
    397 		}
    398 		fp = fopen(CONFFILE, "r");
    399 	}
    400 	if (fp != (FILE *) NULL) {
    401 		while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != (char *) NULL) {
    402 			if (strncmp(s, "group", 5) == 0) {
    403 				for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
    404 				}
    405 				memsave(&up->u_primgrp, cp, strlen(cp));
    406 			} else if (strncmp(s, "base_dir", 8) == 0) {
    407 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
    408 				}
    409 				memsave(&up->u_basedir, cp, strlen(cp));
    410 			} else if (strncmp(s, "skel_dir", 8) == 0) {
    411 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
    412 				}
    413 				memsave(&up->u_skeldir, cp, strlen(cp));
    414 			} else if (strncmp(s, "shell", 5) == 0) {
    415 				for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
    416 				}
    417 				memsave(&up->u_shell, cp, strlen(cp));
    418 			} else if (strncmp(s, "inactive", 8) == 0) {
    419 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
    420 				}
    421 				up->u_inactive = atoi(cp);
    422 #ifdef EXTENSIONS
    423 			} else if (strncmp(s, "range", 5) == 0) {
    424 				for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) {
    425 				}
    426 				(void) save_range(up, cp);
    427 #endif
    428 #ifdef EXTENSIONS
    429 			} else if (strncmp(s, "preserve", 8) == 0) {
    430 				for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) {
    431 				}
    432 				up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 :
    433 						  (strncmp(cp, "yes", 3) == 0) ? 1 :
    434 						   atoi(cp);
    435 #endif
    436 			} else if (strncmp(s, "expire", 6) == 0) {
    437 				for (cp = s + 6 ; *cp && isspace(*cp) ; cp++) {
    438 				}
    439 				if (strcmp(cp, UNSET_EXPIRY) == 0) {
    440 					if (up->u_expire) {
    441 						FREE(up->u_expire);
    442 					}
    443 					up->u_expire = (char *) NULL;
    444 				} else {
    445 					memsave(&up->u_expire, cp, strlen(cp));
    446 				}
    447 			}
    448 			(void) free(s);
    449 		}
    450 		(void) fclose(fp);
    451 	}
    452 }
    453 
    454 /* return the next valid unused uid */
    455 static int
    456 getnextuid(int sync_uid_gid, int *uid, int low_uid, int high_uid)
    457 {
    458 	for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) {
    459 		if (getpwuid((uid_t)(*uid)) == (struct passwd *) NULL && *uid != NOBODY_UID) {
    460 			if (sync_uid_gid) {
    461 				if (getgrgid((gid_t)(*uid)) == (struct group *) NULL) {
    462 					return 1;
    463 				}
    464 			} else {
    465 				return 1;
    466 			}
    467 		}
    468 	}
    469 	return 0;
    470 }
    471 
    472 /* add a user */
    473 static int
    474 adduser(char *login, user_t *up)
    475 {
    476 	struct group	*grp;
    477 	struct stat	st;
    478 	struct tm	tm;
    479 	time_t		expire;
    480 	char		password[PasswordLength + 1];
    481 	char		home[MaxFileNameLen];
    482 	char		buf[MaxFileNameLen];
    483 	int		sync_uid_gid;
    484 	int		masterfd;
    485 	int		ptmpfd;
    486 	int		gid;
    487 	int		cc;
    488 	int		i;
    489 
    490 	if (!valid_login(login)) {
    491 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login);
    492 	}
    493 	if ((masterfd = open(MASTER, O_RDONLY)) < 0) {
    494 		err(EXIT_FAILURE, "can't open `%s'", MASTER);
    495 	}
    496 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
    497 		err(EXIT_FAILURE, "can't lock `%s'", MASTER);
    498 	}
    499 	pw_init();
    500 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
    501 		(void) close(masterfd);
    502 		err(EXIT_FAILURE, "can't obtain pw_lock");
    503 	}
    504 	while ((cc = read(masterfd, buf, sizeof(buf))) > 0) {
    505 		if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
    506 			(void) close(masterfd);
    507 			(void) close(ptmpfd);
    508 			(void) pw_abort();
    509 			err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc);
    510 		}
    511 	}
    512 	/* if no uid was specified, get next one in [low_uid..high_uid] range */
    513 	sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
    514 	if (up->u_uid == -1) {
    515 		for (i = 0 ; i < up->u_rc ; i++) {
    516 			if (getnextuid(sync_uid_gid, &up->u_uid, up->u_rv[i].r_from, up->u_rv[i].r_to)) {
    517 				break;
    518 			}
    519 		}
    520 		if (i == up->u_rc) {
    521 			(void) close(ptmpfd);
    522 			(void) pw_abort();
    523 			errx(EXIT_FAILURE, "can't get next uid for %d", up->u_uid);
    524 		}
    525 	}
    526 	/* check uid isn't already allocated */
    527 	if (!up->u_dupuid && getpwuid((uid_t)(up->u_uid)) != (struct passwd *) NULL) {
    528 		(void) close(ptmpfd);
    529 		(void) pw_abort();
    530 		errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid);
    531 	}
    532 	/* if -g=uid was specified, check gid is unused */
    533 	if (sync_uid_gid) {
    534 		if (getgrgid((gid_t)(up->u_uid)) != (struct group *) NULL) {
    535 			(void) close(ptmpfd);
    536 			(void) pw_abort();
    537 			errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid);
    538 		}
    539 		gid = up->u_uid;
    540 	} else if ((grp = getgrnam(up->u_primgrp)) != (struct group *) NULL) {
    541 		gid = grp->gr_gid;
    542 	} else if ((grp = getgrgid((gid_t)atoi(up->u_primgrp))) != (struct group *) NULL) {
    543 		gid = grp->gr_gid;
    544 	} else {
    545 		(void) close(ptmpfd);
    546 		(void) pw_abort();
    547 		errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
    548 	}
    549 	/* check name isn't already in use */
    550 	if (!up->u_dupuid && getpwnam(login) != (struct passwd *) NULL) {
    551 		(void) close(ptmpfd);
    552 		(void) pw_abort();
    553 		errx(EXIT_FAILURE, "already a `%s' user", login);
    554 	}
    555 	/* if home directory hasn't been given, make it up */
    556 	if (!up->u_homeset) {
    557 		(void) snprintf(home, sizeof(home), "%s/%s", up->u_basedir, login);
    558 	}
    559 	expire = 0;
    560 	if (up->u_expire != (char *) NULL) {
    561 		(void) memset(&tm, 0, sizeof(tm));
    562 		if (strptime(up->u_expire, "%c", &tm) == (char *) NULL) {
    563 			warnx("invalid time format `%s'", optarg);
    564 		} else {
    565 			expire = mktime(&tm);
    566 		}
    567 	}
    568 	password[PasswordLength] = 0;
    569 	if (up->u_password != (char *) NULL &&
    570 	    strlen(up->u_password) == PasswordLength) {
    571 		(void) memcpy(password, up->u_password, PasswordLength);
    572 	} else {
    573 		(void) memset(password, '*', PasswordLength);
    574 		if (up->u_password != (char *) NULL) {
    575 			warnx("Password `%s' is invalid: setting it to `%s'", password);
    576 		}
    577 	}
    578 	cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d::%d:%d:%s:%s:%s\n",
    579 			login,
    580 			password,
    581 			up->u_uid,
    582 			gid,
    583 			up->u_inactive,
    584 			expire,
    585 			up->u_comment,
    586 			home,
    587 			up->u_shell);
    588 	if (write(ptmpfd, buf, (size_t) cc) != cc) {
    589 		(void) close(ptmpfd);
    590 		(void) pw_abort();
    591 		err(EXIT_FAILURE, "can't add `%s'", buf);
    592 	}
    593 	if (up->u_mkdir) {
    594 		if (lstat(home, &st) < 0 && asystem("%s -p %s", MKDIR, home) != 0) {
    595 			(void) close(ptmpfd);
    596 			(void) pw_abort();
    597 			err(EXIT_FAILURE, "can't mkdir `%s'", home);
    598 		}
    599 		(void) copydotfiles(up->u_skeldir, up->u_uid, gid, home);
    600 	}
    601 	if (strcmp(up->u_primgrp, "=uid") == 0 &&
    602 	    getgrnam(login) == (struct group *) NULL &&
    603 	    !creategid(login, gid, login)) {
    604 		(void) close(ptmpfd);
    605 		(void) pw_abort();
    606 		err(EXIT_FAILURE, "can't create gid %d for login name %s", gid, login);
    607 	}
    608 	(void) close(ptmpfd);
    609 	if (pw_mkdb() < 0) {
    610 		err(EXIT_FAILURE, "pw_mkdb failed");
    611 	}
    612 	return 1;
    613 }
    614 
    615 /* modify a user */
    616 static int
    617 moduser(char *login, char *newlogin, user_t *up)
    618 {
    619 	struct passwd	*pwp;
    620 	struct group	*grp;
    621 	struct tm	tm;
    622 	time_t		expire;
    623 	size_t		loginc;
    624 	size_t		colonc;
    625 	FILE		*master;
    626 	char		password[PasswordLength + 1];
    627 	char		oldhome[MaxFileNameLen];
    628 	char		home[MaxFileNameLen];
    629 	char		buf[MaxFileNameLen];
    630 	char		*colon;
    631 	int		masterfd;
    632 	int		ptmpfd;
    633 	int		gid;
    634 	int		cc;
    635 
    636 	if (!valid_login(newlogin)) {
    637 		errx(EXIT_FAILURE, "`%s' is not a valid login name", login);
    638 	}
    639 	if ((pwp = getpwnam(login)) == (struct passwd *) NULL) {
    640 		err(EXIT_FAILURE, "No such user `%s'", login);
    641 	}
    642 	if ((masterfd = open(MASTER, O_RDONLY)) < 0) {
    643 		err(EXIT_FAILURE, "can't open `%s'", MASTER);
    644 	}
    645 	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
    646 		err(EXIT_FAILURE, "can't lock `%s'", MASTER);
    647 	}
    648 	pw_init();
    649 	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
    650 		(void) close(masterfd);
    651 		err(EXIT_FAILURE, "can't obtain pw_lock");
    652 	}
    653 	if ((master = fdopen(masterfd, "r")) == (FILE *) NULL) {
    654 		(void) close(masterfd);
    655 		(void) close(ptmpfd);
    656 		(void) pw_abort();
    657 		err(EXIT_FAILURE, "can't fdopen fd for %s", MASTER);
    658 	}
    659 	if (up != (user_t *) NULL) {
    660 		if (up->u_mkdir) {
    661 			(void) strcpy(oldhome, pwp->pw_dir);
    662 		}
    663 		if (up->u_uid == -1) {
    664 			up->u_uid = pwp->pw_uid;
    665 		}
    666 		/* if -g=uid was specified, check gid is unused */
    667 		if (strcmp(up->u_primgrp, "=uid") == 0) {
    668 			if (getgrgid((gid_t)(up->u_uid)) != (struct group *) NULL) {
    669 				(void) close(ptmpfd);
    670 				(void) pw_abort();
    671 				errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid);
    672 			}
    673 			gid = up->u_uid;
    674 		} else if ((grp = getgrnam(up->u_primgrp)) != (struct group *) NULL) {
    675 			gid = grp->gr_gid;
    676 		} else if ((grp = getgrgid((gid_t)atoi(up->u_primgrp))) != (struct group *) NULL) {
    677 			gid = grp->gr_gid;
    678 		} else {
    679 			(void) close(ptmpfd);
    680 			(void) pw_abort();
    681 			errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
    682 		}
    683 		/* if changing name, check new name isn't already in use */
    684 		if (strcmp(login, newlogin) != 0 && getpwnam(newlogin) != (struct passwd *) NULL) {
    685 			(void) close(ptmpfd);
    686 			(void) pw_abort();
    687 			errx(EXIT_FAILURE, "already a `%s' user", newlogin);
    688 		}
    689 		/* if home directory hasn't been given, use the old one */
    690 		if (!up->u_homeset) {
    691 			(void) strcpy(home, pwp->pw_dir);
    692 		}
    693 		expire = 0;
    694 		if (up->u_expire != (char *) NULL) {
    695 			(void) memset(&tm, 0, sizeof(tm));
    696 			if (strptime(up->u_expire, "%c", &tm) == (char *) NULL) {
    697 				warnx("invalid time format `%s'", optarg);
    698 			} else {
    699 				expire = mktime(&tm);
    700 			}
    701 		}
    702 		password[PasswordLength] = 0;
    703 		if (up->u_password != (char *) NULL &&
    704 		    strlen(up->u_password) == PasswordLength) {
    705 			(void) memcpy(password, up->u_password, PasswordLength);
    706 		} else {
    707 			(void) memcpy(password, pwp->pw_passwd, PasswordLength);
    708 		}
    709 		if (strcmp(up->u_comment, DEF_COMMENT) == 0) {
    710 			memsave(&up->u_comment, pwp->pw_gecos, strlen(pwp->pw_gecos));
    711 		}
    712 		if (strcmp(up->u_shell, DEF_SHELL) == 0 && strcmp(pwp->pw_shell, DEF_SHELL) != 0) {
    713 			memsave(&up->u_comment, pwp->pw_shell, strlen(pwp->pw_shell));
    714 		}
    715 	}
    716 	loginc = strlen(login);
    717 	while (fgets(buf, sizeof(buf), master) != (char *) NULL) {
    718 		cc = strlen(buf);
    719 		if ((colon = strchr(buf, ':')) == (char *) NULL) {
    720 			warnx("Malformed entry `%s'. Skipping", buf);
    721 			continue;
    722 		}
    723 		colonc = (size_t)(colon - buf);
    724 		if (strncmp(login, buf, loginc) == 0 && loginc == colonc) {
    725 			if (up != (user_t *) NULL) {
    726 				cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d::%d:%d:%s:%s:%s\n",
    727 					newlogin,
    728 					password,
    729 					up->u_uid,
    730 					gid,
    731 					up->u_inactive,
    732 					expire,
    733 					up->u_comment,
    734 					home,
    735 					up->u_shell);
    736 				if (write(ptmpfd, buf, (size_t) cc) != cc) {
    737 					(void) close(ptmpfd);
    738 					(void) pw_abort();
    739 					err(EXIT_FAILURE, "can't add `%s'", buf);
    740 				}
    741 			}
    742 		} else if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
    743 			(void) close(masterfd);
    744 			(void) close(ptmpfd);
    745 			(void) pw_abort();
    746 			err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc);
    747 		}
    748 	}
    749 	if (up != (user_t *) NULL &&
    750 	    up->u_mkdir &&
    751 	    asystem("%s %s %s", MV, oldhome, home) != 0) {
    752 		(void) close(ptmpfd);
    753 		(void) pw_abort();
    754 		err(EXIT_FAILURE, "can't move `%s' to `%s'", oldhome, home);
    755 	}
    756 	(void) close(ptmpfd);
    757 	if (pw_mkdb() < 0) {
    758 		err(EXIT_FAILURE, "pw_mkdb failed");
    759 	}
    760 	return 1;
    761 }
    762 
    763 /* print out usage message, and then exit */
    764 static void
    765 usage(char *prog)
    766 {
    767 	if (strcmp(prog, "useradd") == 0) {
    768 		(void) fprintf(stderr, "%s -D [-bbasedir] [-eexpiry] [-finactive] [-ggroup] [-rrange] [-sshell]\n", prog);
    769 		(void) fprintf(stderr, "%s [-Ggroup] [-bbasedir] [-ccomment] [-dhomedir] [-eexpiry] [-finactive]\n\t[-ggroup] [-kskeletondir] [-m] [-o] [-ppassword] [-rrange] [-sshell]\n\t[-uuid] [-v] user\n", prog);
    770 	} else if (strcmp(prog, "usermod") == 0) {
    771 		(void) fprintf(stderr, "%s [-Ggroup] [-ccomment] [-dhomedir] [-eexpire] [-finactive] [-ggroup] [-lnewname] [-m] [-o] [-ppassword] [-sshell] [-uuid] [-v] user\n", prog);
    772 	} else if (strcmp(prog, "userdel") == 0) {
    773 		(void) fprintf(stderr, "%s -D [-ppreserve]\n", prog);
    774 		(void) fprintf(stderr, "%s [-ppreserve] [-r] [-v] user\n", prog);
    775 	} else if (strcmp(prog, "groupadd") == 0) {
    776 		(void) fprintf(stderr, "%s [-ggid] [-o] [-v] group\n", prog);
    777 	} else if (strcmp(prog, "groupdel") == 0) {
    778 		(void) fprintf(stderr, "%s [-v] group\n", prog);
    779 	} else if (strcmp(prog, "groupmod") == 0) {
    780 		(void) fprintf(stderr, "%s [-ggid] [-o] [-nnewname] [-v] group\n", prog);
    781 	}
    782 	exit(EXIT_FAILURE);
    783 	/* NOTREACHED */
    784 }
    785 
    786 extern int	optind;
    787 extern char	*optarg;
    788 
    789 #ifdef EXTENSIONS
    790 #define ADD_OPT_EXTENSIONS	"p:r:v"
    791 #else
    792 #define ADD_OPT_EXTENSIONS
    793 #endif
    794 
    795 int
    796 useradd(int argc, char **argv)
    797 {
    798 	user_t	u;
    799 	int	defaultfield;
    800 	int	bigD;
    801 	int	c;
    802 	int	i;
    803 
    804 	(void) memset(&u, 0, sizeof(u));
    805 	read_defaults(&u);
    806 	u.u_uid = -1;
    807 	defaultfield = bigD = 0;
    808 	while ((c = getopt(argc, argv, "DG:b:c:d:e:f:g:k:mou:s:" ADD_OPT_EXTENSIONS)) != -1) {
    809 		switch(c) {
    810 		case 'D':
    811 			bigD = 1;
    812 			break;
    813 		case 'G':
    814 			memsave(&u.u_groupv[u.u_groupc++], optarg, strlen(optarg));
    815 			break;
    816 		case 'b':
    817 			defaultfield = 1;
    818 			memsave(&u.u_basedir, optarg, strlen(optarg));
    819 			break;
    820 		case 'c':
    821 			memsave(&u.u_comment, optarg, strlen(optarg));
    822 			break;
    823 		case 'd':
    824 			u.u_homeset = 1;
    825 			memsave(&u.u_home, optarg, strlen(optarg));
    826 			break;
    827 		case 'e':
    828 			defaultfield = 1;
    829 			memsave(&u.u_expire, optarg, strlen(optarg));
    830 			break;
    831 		case 'f':
    832 			defaultfield = 1;
    833 			u.u_inactive = atoi(optarg);
    834 			break;
    835 		case 'g':
    836 			defaultfield = 1;
    837 			memsave(&u.u_primgrp, optarg, strlen(optarg));
    838 			break;
    839 		case 'k':
    840 			memsave(&u.u_skeldir, optarg, strlen(optarg));
    841 			break;
    842 		case 'm':
    843 			u.u_mkdir = 1;
    844 			break;
    845 		case 'o':
    846 			u.u_dupuid = 1;
    847 			break;
    848 #ifdef EXTENSIONS
    849 		case 'p':
    850 			memsave(&u.u_password, optarg, strlen(optarg));
    851 			break;
    852 #endif
    853 #ifdef EXTENSIONS
    854 		case 'r':
    855 			defaultfield = 1;
    856 			(void) save_range(&u, optarg);
    857 			break;
    858 #endif
    859 		case 's':
    860 			defaultfield = 1;
    861 			memsave(&u.u_shell, optarg, strlen(optarg));
    862 			break;
    863 		case 'u':
    864 			u.u_uid = atoi(optarg);
    865 			break;
    866 #ifdef EXTENSIONS
    867 		case 'v':
    868 			verbose = 1;
    869 			break;
    870 #endif
    871 		}
    872 	}
    873 	if (bigD) {
    874 		if (defaultfield) {
    875 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
    876 		}
    877 		(void) printf("group\t\t%s\n", u.u_primgrp);
    878 		(void) printf("base_dir\t%s\n", u.u_basedir);
    879 		(void) printf("skel_dir\t%s\n", u.u_skeldir);
    880 		(void) printf("shell\t\t%s\n", u.u_shell);
    881 		(void) printf("inactive\t%d\n", u.u_inactive);
    882 		(void) printf("expire\t\t%s\n", (u.u_expire == (char *) NULL) ? UNSET_EXPIRY : u.u_expire);
    883 #ifdef EXTENSIONS
    884 		for (i = 0 ; i < u.u_rc ; i++) {
    885 			(void) printf("range\t\t%d..%d\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
    886 		}
    887 #endif
    888 		return EXIT_SUCCESS;
    889 	}
    890 	if (argc == optind) {
    891 		usage("useradd");
    892 	}
    893 	return adduser(argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE;
    894 }
    895 
    896 #ifdef EXTENSIONS
    897 #define MOD_OPT_EXTENSIONS	"p:v"
    898 #else
    899 #define MOD_OPT_EXTENSIONS
    900 #endif
    901 
    902 int
    903 usermod(int argc, char **argv)
    904 {
    905 	user_t	u;
    906 	char	newuser[MaxUserNameLen + 1];
    907 	int	have_new_user;
    908 	int	c;
    909 
    910 	(void) memset(&u, 0, sizeof(u));
    911 	(void) memset(newuser, 0, sizeof(newuser));
    912 	read_defaults(&u);
    913 	u.u_uid = -1;
    914 	have_new_user = 0;
    915 	while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) {
    916 		switch(c) {
    917 		case 'G':
    918 			memsave(&u.u_groupv[u.u_groupc++], optarg, strlen(optarg));
    919 			break;
    920 		case 'c':
    921 			memsave(&u.u_comment, optarg, strlen(optarg));
    922 			break;
    923 		case 'd':
    924 			u.u_homeset = 1;
    925 			memsave(&u.u_home, optarg, strlen(optarg));
    926 			break;
    927 		case 'e':
    928 			memsave(&u.u_expire, optarg, strlen(optarg));
    929 			break;
    930 		case 'f':
    931 			u.u_inactive = atoi(optarg);
    932 			break;
    933 		case 'g':
    934 			memsave(&u.u_primgrp, optarg, strlen(optarg));
    935 			break;
    936 		case 'l':
    937 			have_new_user = 1;
    938 			(void) strnncpy(newuser, sizeof(newuser), optarg, strlen(optarg));
    939 			break;
    940 		case 'm':
    941 			u.u_mkdir = 1;
    942 			break;
    943 		case 'o':
    944 			u.u_dupuid = 1;
    945 			break;
    946 #ifdef EXTENSIONS
    947 		case 'p':
    948 			memsave(&u.u_password, optarg, strlen(optarg));
    949 			break;
    950 #endif
    951 		case 's':
    952 			memsave(&u.u_shell, optarg, strlen(optarg));
    953 			break;
    954 		case 'u':
    955 			u.u_uid = atoi(optarg);
    956 			break;
    957 #ifdef EXTENSIONS
    958 		case 'v':
    959 			verbose = 1;
    960 			break;
    961 #endif
    962 		}
    963 	}
    964 	if (argc == optind) {
    965 		usage("usermod");
    966 	}
    967 	return moduser(argv[optind], (have_new_user) ? newuser : argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE;
    968 }
    969 
    970 #ifdef EXTENSIONS
    971 #define DEL_OPT_EXTENSIONS	"Dp:v"
    972 #else
    973 #define DEL_OPT_EXTENSIONS
    974 #endif
    975 
    976 int
    977 userdel(int argc, char **argv)
    978 {
    979 	struct passwd	*pwp;
    980 	struct stat	st;
    981 	user_t		u;
    982 	char		password[PasswordLength + 1];
    983 	int		defaultfield;
    984 	int		rmhome;
    985 	int		bigD;
    986 	int		c;
    987 
    988 	(void) memset(&u, 0, sizeof(u));
    989 	read_defaults(&u);
    990 	defaultfield = bigD = rmhome = 0;
    991 	while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) {
    992 		switch(c) {
    993 #ifdef EXTENSIONS
    994 		case 'D':
    995 			bigD = 1;
    996 			break;
    997 #endif
    998 #ifdef EXTENSIONS
    999 		case 'p':
   1000 			defaultfield = 1;
   1001 			u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
   1002 					(strcmp(optarg, "yes") == 0) ? 1 :
   1003 					 atoi(optarg);
   1004 			break;
   1005 #endif
   1006 		case 'r':
   1007 			rmhome = 1;
   1008 			break;
   1009 #ifdef EXTENSIONS
   1010 		case 'v':
   1011 			verbose = 1;
   1012 			break;
   1013 #endif
   1014 		}
   1015 	}
   1016 #ifdef EXTENSIONS
   1017 	if (bigD) {
   1018 		if (defaultfield) {
   1019 			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
   1020 		}
   1021 		(void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
   1022 		return EXIT_SUCCESS;
   1023 	}
   1024 #endif
   1025 	if (argc == optind) {
   1026 		usage("usermod");
   1027 	}
   1028 	if ((pwp = getpwnam(argv[optind])) == (struct passwd *) NULL) {
   1029 		warn("No such user `%s'", argv[optind]);
   1030 		return EXIT_FAILURE;
   1031 	}
   1032 	if (rmhome) {
   1033 		if (stat(pwp->pw_dir, &st) < 0) {
   1034 			warn("Home directory `%s' does not exist", pwp->pw_dir);
   1035 			return EXIT_FAILURE;
   1036 		}
   1037 		(void) asystem("%s -rf %s", RM, pwp->pw_dir);
   1038 	}
   1039 	if (u.u_preserve) {
   1040 		memsave(&u.u_shell, FALSE_PROG, strlen(FALSE_PROG));
   1041 		(void) memset(password, '*', PasswordLength);
   1042 		password[PasswordLength] = 0;
   1043 		memsave(&u.u_password, password, PasswordLength);
   1044 		return moduser(argv[optind], argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE;
   1045 	}
   1046 	return moduser(argv[optind], argv[optind], (user_t *) NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
   1047 }
   1048 
   1049 #ifdef EXTENSIONS
   1050 #define GROUP_ADD_OPT_EXTENSIONS	"v"
   1051 #else
   1052 #define GROUP_ADD_OPT_EXTENSIONS
   1053 #endif
   1054 
   1055 /* add a group */
   1056 int
   1057 groupadd(int argc, char **argv)
   1058 {
   1059 	int	dupgid;
   1060 	int	gid;
   1061 	int	c;
   1062 
   1063 	gid = -1;
   1064 	dupgid = 0;
   1065 	while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) {
   1066 		switch(c) {
   1067 		case 'g':
   1068 			gid = atoi(optarg);
   1069 			break;
   1070 		case 'o':
   1071 			dupgid = 1;
   1072 			break;
   1073 #ifdef EXTENSIONS
   1074 		case 'v':
   1075 			verbose = 1;
   1076 			break;
   1077 #endif
   1078 		}
   1079 	}
   1080 	if (argc == optind) {
   1081 		usage("groupadd");
   1082 	}
   1083 	if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
   1084 		err(EXIT_FAILURE, "can't add group: can't get next gid");
   1085 	}
   1086 	if (!dupgid && getgrgid((gid_t) gid) != (struct group *) NULL) {
   1087 		err(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
   1088 	}
   1089 	if (!valid_group(argv[optind])) {
   1090 		warn("warning - invalid group name `%s'", argv[optind]);
   1091 	}
   1092 	if (!creategid(argv[optind], gid, "")) {
   1093 		err(EXIT_FAILURE, "can't add group: problems with %s file", ETCGROUP);
   1094 	}
   1095 	return EXIT_SUCCESS;
   1096 }
   1097 
   1098 #ifdef EXTENSIONS
   1099 #define GROUP_DEL_OPT_EXTENSIONS	"v"
   1100 #else
   1101 #define GROUP_DEL_OPT_EXTENSIONS
   1102 #endif
   1103 
   1104 /* remove a group */
   1105 int
   1106 groupdel(int argc, char **argv)
   1107 {
   1108 	int	c;
   1109 
   1110 	while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) {
   1111 		switch(c) {
   1112 #ifdef EXTENSIONS
   1113 		case 'v':
   1114 			verbose = 1;
   1115 			break;
   1116 #endif
   1117 		}
   1118 	}
   1119 	if (argc == optind) {
   1120 		usage("groupdel");
   1121 	}
   1122 	if (!modify_gid(argv[optind], (char *) NULL)) {
   1123 		err(EXIT_FAILURE, "can't change %s file", ETCGROUP);
   1124 	}
   1125 	return EXIT_SUCCESS;
   1126 }
   1127 
   1128 #ifdef EXTENSIONS
   1129 #define GROUP_MOD_OPT_EXTENSIONS	"v"
   1130 #else
   1131 #define GROUP_MOD_OPT_EXTENSIONS
   1132 #endif
   1133 
   1134 /* modify a group */
   1135 int
   1136 groupmod(int argc, char **argv)
   1137 {
   1138 	struct group	*grp;
   1139 	char		buf[MaxEntryLen];
   1140 	char		*newname;
   1141 	char		**cpp;
   1142 	int		dupgid;
   1143 	int		gid;
   1144 	int		cc;
   1145 	int		c;
   1146 
   1147 	gid = -1;
   1148 	dupgid = 0;
   1149 	newname = (char *) NULL;
   1150 	while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) {
   1151 		switch(c) {
   1152 		case 'g':
   1153 			gid = atoi(optarg);
   1154 			break;
   1155 		case 'o':
   1156 			dupgid = 1;
   1157 			break;
   1158 		case 'n':
   1159 			memsave(&newname, optarg, strlen(optarg));
   1160 			break;
   1161 #ifdef EXTENSIONS
   1162 		case 'v':
   1163 			verbose = 1;
   1164 			break;
   1165 #endif
   1166 		}
   1167 	}
   1168 	if (argc == optind) {
   1169 		usage("userdel");
   1170 	}
   1171 	if (gid < 0 && newname == (char *) NULL) {
   1172 		err(EXIT_FAILURE, "Nothing to change");
   1173 	}
   1174 	if (dupgid && gid < 0) {
   1175 		err(EXIT_FAILURE, "Duplicate which gid?");
   1176 	}
   1177 	if ((grp = getgrnam(argv[optind])) == (struct group *) NULL) {
   1178 		err(EXIT_FAILURE, "can't find group `%s' to modify", argv[optind]);
   1179 	}
   1180 	if (newname != (char *) NULL && !valid_group(newname)) {
   1181 		warn("warning - invalid group name `%s'", newname);
   1182 	}
   1183 	cc = snprintf(buf, sizeof(buf), "%s:%s:%d:",
   1184 			(newname) ? newname : grp->gr_name,
   1185 			grp->gr_passwd,
   1186 			(gid < 0) ? grp->gr_gid : gid);
   1187 	for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) {
   1188 		cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp,
   1189 			(cpp[1] == (char *) NULL) ? "" : ",");
   1190 	}
   1191 	if (!modify_gid(argv[optind], buf)) {
   1192 		err(EXIT_FAILURE, "can't change %s file", ETCGROUP);
   1193 	}
   1194 	return EXIT_SUCCESS;
   1195 }
   1196 
   1197 /* this struct describes a command */
   1198 typedef struct cmd_t {
   1199 	char	*c_progname;	/* program name */
   1200 	char	*c_word1;	/* alternative program name */
   1201 	char	*c_word2;	/* alternative command word */
   1202 	int	(*c_func)(int argc, char **argv);	/* called function */
   1203 } cmd_t;
   1204 
   1205 /* despatch table for commands */
   1206 static cmd_t	cmds[] = {
   1207 	{	"useradd",	"user",		"add",	useradd		},
   1208 	{	"usermod",	"user",		"mod",	usermod		},
   1209 	{	"userdel",	"user",		"del",	userdel		},
   1210 	{	"groupadd",	"group",	"add",	groupadd	},
   1211 	{	"groupmod",	"group",	"mod",	groupmod	},
   1212 	{	"groupdel",	"group",	"del",	groupdel	},
   1213 	{	NULL	}
   1214 };
   1215 
   1216 extern char	*__progname;
   1217 
   1218 int
   1219 main(int argc, char **argv)
   1220 {
   1221 	cmd_t	*cmdp;
   1222 
   1223 	for (cmdp = cmds ; cmdp->c_progname ; cmdp++) {
   1224 		if (strcmp(__progname, cmdp->c_progname) == 0) {
   1225 			return (*cmdp->c_func)(argc, argv);
   1226 		} else if (strcmp(__progname, cmdp->c_word1) == 0 &&
   1227 			   strcmp(argv[1], cmdp->c_word2) == 0) {
   1228 			return (*cmdp->c_func)(argc - 1, argv + 1);
   1229 		}
   1230 	}
   1231 	errx(EXIT_FAILURE, "Program `%s' not recognised", __progname);
   1232 	/* NOTREACHED */
   1233 }
   1234