14a198eejoerg/*-
27551d83pfg * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
37551d83pfg *
47b15aedjoerg * Copyright (C) 1996
57b15aedjoerg *	David L. Nugent.  All rights reserved.
64a198eejoerg *
74a198eejoerg * Redistribution and use in source and binary forms, with or without
84a198eejoerg * modification, are permitted provided that the following conditions
94a198eejoerg * are met:
104a198eejoerg * 1. Redistributions of source code must retain the above copyright
117b15aedjoerg *    notice, this list of conditions and the following disclaimer.
124a198eejoerg * 2. Redistributions in binary form must reproduce the above copyright
134a198eejoerg *    notice, this list of conditions and the following disclaimer in the
144a198eejoerg *    documentation and/or other materials provided with the distribution.
154a198eejoerg *
167b15aedjoerg * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
174a198eejoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
184a198eejoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
197b15aedjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
204a198eejoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
214a198eejoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
224a198eejoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
234a198eejoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
244a198eejoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
254a198eejoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
264a198eejoerg * SUCH DAMAGE.
274a198eejoerg */
284a198eejoerg
29c4198a7charnier#ifndef lint
30c4198a7charnierstatic const char rcsid[] =
31efabb9cpeter  "$FreeBSD$";
32c4198a7charnier#endif /* not lint */
33c4198a7charnier
344a198eejoerg#include <ctype.h>
35c4198a7charnier#include <err.h>
364f6bdcfbapt#include <grp.h>
374f6bdcfbapt#include <libutil.h>
384f6bdcfbapt#include <paths.h>
394469579bapt#include <string.h>
404469579bapt#include <sysexits.h>
414f6bdcfbapt#include <termios.h>
42c4198a7charnier#include <unistd.h>
434a198eejoerg
444a198eejoerg#include "pw.h"
454a198eejoerg#include "bitmap.h"
464a198eejoerg
47c88c109scfstatic struct passwd *lookup_pwent(const char *user);
48a2b16f4baptstatic void	delete_members(struct group *grp, char *list);
494f6bdcfbaptstatic int	print_group(struct group * grp, bool pretty);
504f6bdcfbaptstatic gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
514a198eejoerg
52f3cda76baptstatic void
534f6bdcfbaptgrp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
54f3cda76bapt{
55f3cda76bapt	int		 b;
56f3cda76bapt	int		 istty;
57f3cda76bapt	struct termios	 t, n;
58f3cda76bapt	char		*p, line[256];
59f3cda76bapt
604f6bdcfbapt	if (fd == -1)
614f6bdcfbapt		return;
624f6bdcfbapt
634f6bdcfbapt	if (fd == '-') {
64f3cda76bapt		grp->gr_passwd = "*";	/* No access */
65f3cda76bapt		return;
66f3cda76bapt	}
67f3cda76bapt
684f6bdcfbapt	if ((istty = isatty(fd))) {
69f3cda76bapt		n = t;
70f3cda76bapt		/* Disable echo */
71f3cda76bapt		n.c_lflag &= ~(ECHO);
724f6bdcfbapt		tcsetattr(fd, TCSANOW, &n);
73f3cda76bapt		printf("%sassword for group %s:", update ? "New p" : "P",
74f3cda76bapt		    grp->gr_name);
75f3cda76bapt		fflush(stdout);
76f3cda76bapt	}
774f6bdcfbapt	b = read(fd, line, sizeof(line) - 1);
78f3cda76bapt	if (istty) {	/* Restore state */
794f6bdcfbapt		tcsetattr(fd, TCSANOW, &t);
80f3cda76bapt		fputc('\n', stdout);
81f3cda76bapt		fflush(stdout);
82f3cda76bapt	}
83f3cda76bapt	if (b < 0)
84f3cda76bapt		err(EX_OSERR, "-h file descriptor");
85f3cda76bapt	line[b] = '\0';
86f3cda76bapt	if ((p = strpbrk(line, " \t\r\n")) != NULL)
87f3cda76bapt		*p = '\0';
88f3cda76bapt	if (!*line)
89f3cda76bapt		errx(EX_DATAERR, "empty password read on file descriptor %d",
90f3cda76bapt		    conf.fd);
914f6bdcfbapt	if (precrypted) {
92f3cda76bapt		if (strchr(line, ':') != 0)
93f3cda76bapt			errx(EX_DATAERR, "wrong encrypted passwrd");
94f3cda76bapt		grp->gr_passwd = line;
95f3cda76bapt	} else
96f3cda76bapt		grp->gr_passwd = pw_pwcrypt(line);
97f3cda76bapt}
98f3cda76bapt
994a198eejoergint
100032c160baptpw_groupnext(struct userconf *cnf, bool quiet)
101032c160bapt{
102032c160bapt	gid_t next = gr_gidpolicy(cnf, -1);
103032c160bapt
104032c160bapt	if (quiet)
105032c160bapt		return (next);
1065acf147bapt	printf("%ju\n", (uintmax_t)next);
107032c160bapt
108032c160bapt	return (EXIT_SUCCESS);
109032c160bapt}
110032c160bapt
1114f6bdcfbaptstatic struct group *
1124f6bdcfbaptgetgroup(char *name, intmax_t id, bool fatal)
1134c8679fbapt{
1144f6bdcfbapt	struct group *grp;
1154c8679fbapt
1164f6bdcfbapt	if (id < 0 && name == NULL)
1174f6bdcfbapt		errx(EX_DATAERR, "groupname or id required");
1184c8679fbapt	grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
1194c8679fbapt	if (grp == NULL) {
1204f6bdcfbapt		if (!fatal)
1214f6bdcfbapt			return (NULL);
1224c8679fbapt		if (name == NULL)
1234f6bdcfbapt			errx(EX_DATAERR, "unknown gid `%ju'", id);
1244c8679fbapt		errx(EX_DATAERR, "unknown group `%s'", name);
1254c8679fbapt	}
1264f6bdcfbapt	return (grp);
1274c8679fbapt}
1284c8679fbapt
129c88c109scf/*
130c88c109scf * Lookup a passwd entry using a name or UID.
131c88c109scf */
132c88c109scfstatic struct passwd *
133c88c109scflookup_pwent(const char *user)
134c88c109scf{
135c88c109scf	struct passwd *pwd;
136c88c109scf
137c88c109scf	if ((pwd = GETPWNAM(user)) == NULL &&
138c88c109scf	    (!isdigit((unsigned char)*user) ||
139c88c109scf	    (pwd = getpwuid((uid_t) atoi(user))) == NULL))
140c88c109scf		errx(EX_NOUSER, "user `%s' does not exist", user);
141c88c109scf
142c88c109scf	return (pwd);
143c88c109scf}
144c88c109scf
145c88c109scf
146c88c109scf/*
147c88c109scf * Delete requested members from a group.
148c88c109scf */
149c88c109scfstatic void
150a2b16f4baptdelete_members(struct group *grp, char *list)
151c88c109scf{
152a2b16f4bapt	char *p;
153c88c109scf	int k;
154c88c109scf
155d8e87dbjulian	if (grp->gr_mem == NULL)
156d8e87dbjulian		return;
157d8e87dbjulian
158a2b16f4bapt	for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
159a2b16f4bapt		for (k = 0; grp->gr_mem[k] != NULL; k++) {
160a2b16f4bapt			if (strcmp(grp->gr_mem[k], p) == 0)
161c88c109scf				break;
162c88c109scf		}
163a2b16f4bapt		if (grp->gr_mem[k] == NULL) /* No match */
164a2b16f4bapt			continue;
165c88c109scf
166a2b16f4bapt		for (; grp->gr_mem[k] != NULL; k++)
167a2b16f4bapt			grp->gr_mem[k] = grp->gr_mem[k+1];
168c88c109scf	}
169c88c109scf}
170c88c109scf
1714f6bdcfbaptstatic gid_t
1724f6bdcfbaptgr_gidpolicy(struct userconf * cnf, intmax_t id)
1734a198eejoerg{
1744a198eejoerg	struct group   *grp;
1754f6bdcfbapt	struct bitmap   bm;
1764a198eejoerg	gid_t           gid = (gid_t) - 1;
1774a198eejoerg
1784a198eejoerg	/*
1794a198eejoerg	 * Check the given gid, if any
1804a198eejoerg	 */
181302d4b4bapt	if (id > 0) {
182302d4b4bapt		gid = (gid_t) id;
1834a198eejoerg
184302d4b4bapt		if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
1852c026ebbapt			errx(EX_DATAERR, "gid `%ju' has already been allocated",
1862c026ebbapt			    (uintmax_t)grp->gr_gid);
1874f6bdcfbapt		return (gid);
1884f6bdcfbapt	}
1894a198eejoerg
1904f6bdcfbapt	/*
1914f6bdcfbapt	 * We need to allocate the next available gid under one of
1924f6bdcfbapt	 * two policies a) Grab the first unused gid b) Grab the
1934f6bdcfbapt	 * highest possible unused gid
1944f6bdcfbapt	 */
1954f6bdcfbapt	if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
1964f6bdcfbapt		cnf->min_gid = 1000;
1974f6bdcfbapt		cnf->max_gid = 32000;
1984f6bdcfbapt	}
1994f6bdcfbapt	bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
2004a198eejoerg
2014f6bdcfbapt	/*
2024f6bdcfbapt	 * Now, let's fill the bitmap from the password file
2034f6bdcfbapt	 */
2044f6bdcfbapt	SETGRENT();
2054f6bdcfbapt	while ((grp = GETGRENT()) != NULL)
2064f6bdcfbapt		if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
2074f6bdcfbapt		    (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
2084f6bdcfbapt			bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
2094f6bdcfbapt	ENDGRENT();
2104a198eejoerg
2114f6bdcfbapt	/*
2124f6bdcfbapt	 * Then apply the policy, with fallback to reuse if necessary
2134f6bdcfbapt	 */
2144f6bdcfbapt	if (cnf->reuse_gids)
2154f6bdcfbapt		gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
2164f6bdcfbapt	else {
2174f6bdcfbapt		gid = (gid_t) (bm_lastset(&bm) + 1);
2184f6bdcfbapt		if (!bm_isset(&bm, gid))
2194f6bdcfbapt			gid += cnf->min_gid;
2204f6bdcfbapt		else
2214f6bdcfbapt			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
2224a198eejoerg	}
2234a198eejoerg
2244f6bdcfbapt	/*
2254f6bdcfbapt	 * Another sanity check
2264f6bdcfbapt	 */
2274f6bdcfbapt	if (gid < cnf->min_gid || gid > cnf->max_gid)
2282c026ebbapt		errx(EX_SOFTWARE, "unable to allocate a new gid - range fully "
2292c026ebbapt		    "used");
2304f6bdcfbapt	bm_dealloc(&bm);
2314f6bdcfbapt	return (gid);
2324f6bdcfbapt}
2334a198eejoerg
2344a198eejoergstatic int
2354f6bdcfbaptprint_group(struct group * grp, bool pretty)
2364a198eejoerg{
2374f6bdcfbapt	char *buf = NULL;
2384f6bdcfbapt	int i;
2394a198eejoerg
2404f6bdcfbapt	if (pretty) {
2411a543bfdavidn		printf("Group Name: %-15s   #%lu\n"
242ae5c463davidn		       "   Members: ",
2434a198eejoerg		       grp->gr_name, (long) grp->gr_gid);
244d8e87dbjulian		if (grp->gr_mem != NULL) {
245d8e87dbjulian			for (i = 0; grp->gr_mem[i]; i++)
246d8e87dbjulian				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
247d8e87dbjulian		}
2484a198eejoerg		fputs("\n\n", stdout);
2494f6bdcfbapt		return (EXIT_SUCCESS);
2504f6bdcfbapt	}
2514f6bdcfbapt
2524f6bdcfbapt	buf = gr_make(grp);
2534f6bdcfbapt	printf("%s\n", buf);
2544f6bdcfbapt	free(buf);
2554f6bdcfbapt	return (EXIT_SUCCESS);
2564f6bdcfbapt}
2574f6bdcfbapt
2584f6bdcfbaptint
2594f6bdcfbaptpw_group_next(int argc, char **argv, char *arg1 __unused)
2604f6bdcfbapt{
2614f6bdcfbapt	struct userconf *cnf;
2624f6bdcfbapt	const char *cfg = NULL;
2634f6bdcfbapt	int ch;
26402a1e0fngie	bool quiet = false;
2654f6bdcfbapt
2663169b6ebapt	while ((ch = getopt(argc, argv, "C:q")) != -1) {
2674f6bdcfbapt		switch (ch) {
2684f6bdcfbapt		case 'C':
2694f6bdcfbapt			cfg = optarg;
2704f6bdcfbapt			break;
2714f6bdcfbapt		case 'q':
2724f6bdcfbapt			quiet = true;
2734f6bdcfbapt			break;
2744f6bdcfbapt		}
2754a198eejoerg	}
2764f6bdcfbapt
2774f6bdcfbapt	if (quiet)
2784f6bdcfbapt		freopen(_PATH_DEVNULL, "w", stderr);
2794f6bdcfbapt	cnf = get_userconfig(cfg);
2804f6bdcfbapt	return (pw_groupnext(cnf, quiet));
2814f6bdcfbapt}
2824f6bdcfbapt
2834f6bdcfbaptint
2844f6bdcfbaptpw_group_show(int argc, char **argv, char *arg1)
2854f6bdcfbapt{
2864f6bdcfbapt	struct group *grp = NULL;
2871fd84d9bapt	char *name = NULL;
2884f6bdcfbapt	intmax_t id = -1;
2894f6bdcfbapt	int ch;
2904f6bdcfbapt	bool all, force, quiet, pretty;
2914f6bdcfbapt
2924f6bdcfbapt	all = force = quiet = pretty = false;
2934f6bdcfbapt
2944f6bdcfbapt	struct group fakegroup = {
2954f6bdcfbapt		"nogroup",
2964f6bdcfbapt		"*",
2974f6bdcfbapt		-1,
2984f6bdcfbapt		NULL
2994f6bdcfbapt	};
3004f6bdcfbapt
3014f6bdcfbapt	if (arg1 != NULL) {
30208ce463ed		if (arg1[strspn(arg1, "0123456789")] == '\0')
3034f6bdcfbapt			id = pw_checkid(arg1, GID_MAX);
3044f6bdcfbapt		else
3054f6bdcfbapt			name = arg1;
3064f6bdcfbapt	}
3074f6bdcfbapt
3084f6bdcfbapt	while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
3094f6bdcfbapt		switch (ch) {
3104f6bdcfbapt		case 'C':
3114f6bdcfbapt			/* ignore compatibility */
3124f6bdcfbapt			break;
3134f6bdcfbapt		case 'q':
3144f6bdcfbapt			quiet = true;
3154f6bdcfbapt			break;
3164f6bdcfbapt		case 'n':
3174f6bdcfbapt			name = optarg;
3184f6bdcfbapt			break;
3194f6bdcfbapt		case 'g':
3204f6bdcfbapt			id = pw_checkid(optarg, GID_MAX);
3214f6bdcfbapt			break;
3224f6bdcfbapt		case 'F':
3234f6bdcfbapt			force = true;
3244f6bdcfbapt			break;
3254f6bdcfbapt		case 'P':
3264f6bdcfbapt			pretty = true;
3274f6bdcfbapt			break;
3284f6bdcfbapt		case 'a':
3294f6bdcfbapt			all = true;
3304f6bdcfbapt			break;
3314f6bdcfbapt		}
3324f6bdcfbapt	}
3334f6bdcfbapt
3344f6bdcfbapt	if (quiet)
3354f6bdcfbapt		freopen(_PATH_DEVNULL, "w", stderr);
3364f6bdcfbapt
3374f6bdcfbapt	if (all) {
3384f6bdcfbapt		SETGRENT();
3394f6bdcfbapt		while ((grp = GETGRENT()) != NULL)
3404f6bdcfbapt			print_group(grp, pretty);
3414f6bdcfbapt		ENDGRENT();
3424f6bdcfbapt		return (EXIT_SUCCESS);
3434f6bdcfbapt	}
3444f6bdcfbapt
3454f6bdcfbapt	grp = getgroup(name, id, !force);
3464f6bdcfbapt	if (grp == NULL)
3474f6bdcfbapt		grp = &fakegroup;
3484f6bdcfbapt
3494f6bdcfbapt	return (print_group(grp, pretty));
3504f6bdcfbapt}
3514f6bdcfbapt
3524f6bdcfbaptint
3534f6bdcfbaptpw_group_del(int argc, char **argv, char *arg1)
3544f6bdcfbapt{
3554f6bdcfbapt	struct userconf *cnf = NULL;
3564f6bdcfbapt	struct group *grp = NULL;
3574f6bdcfbapt	char *name;
3584f6bdcfbapt	const char *cfg = NULL;
3594f6bdcfbapt	intmax_t id = -1;
3604f6bdcfbapt	int ch, rc;
3614f6bdcfbapt	bool quiet = false;
3624f6bdcfbapt	bool nis = false;
3634f6bdcfbapt
3644f6bdcfbapt	if (arg1 != NULL) {
36508ce463ed		if (arg1[strspn(arg1, "0123456789")] == '\0')
3664f6bdcfbapt			id = pw_checkid(arg1, GID_MAX);
3674f6bdcfbapt		else
3684f6bdcfbapt			name = arg1;
3694f6bdcfbapt	}
3704f6bdcfbapt
3714f6bdcfbapt	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
3724f6bdcfbapt		switch (ch) {
3734f6bdcfbapt		case 'C':
3744f6bdcfbapt			cfg = optarg;
3754f6bdcfbapt			break;
3764f6bdcfbapt		case 'q':
3774f6bdcfbapt			quiet = true;
3784f6bdcfbapt			break;
3794f6bdcfbapt		case 'n':
3804f6bdcfbapt			name = optarg;
3814f6bdcfbapt			break;
3824f6bdcfbapt		case 'g':
3834f6bdcfbapt			id = pw_checkid(optarg, GID_MAX);
3844f6bdcfbapt			break;
3854f6bdcfbapt		case 'Y':
3864f6bdcfbapt			nis = true;
3874f6bdcfbapt			break;
3884f6bdcfbapt		}
3894f6bdcfbapt	}
3904f6bdcfbapt
3914f6bdcfbapt	if (quiet)
3924f6bdcfbapt		freopen(_PATH_DEVNULL, "w", stderr);
3934f6bdcfbapt	grp = getgroup(name, id, true);
3944f6bdcfbapt	cnf = get_userconfig(cfg);
3954f6bdcfbapt	rc = delgrent(grp);
3964f6bdcfbapt	if (rc == -1)
3974f6bdcfbapt		err(EX_IOERR, "group '%s' not available (NIS?)", name);
3984f6bdcfbapt	else if (rc != 0)
3994f6bdcfbapt		err(EX_IOERR, "group update");
4004f6bdcfbapt	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
4014f6bdcfbapt	    (uintmax_t)id);
4024f6bdcfbapt
4034f6bdcfbapt	if (nis && nis_update() == 0)
4044f6bdcfbapt		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
4054f6bdcfbapt
4064f6bdcfbapt	return (EXIT_SUCCESS);
4074f6bdcfbapt}
4084f6bdcfbapt
4094f6bdcfbaptstatic bool
4104f6bdcfbaptgrp_has_member(struct group *grp, const char *name)
4114f6bdcfbapt{
4124f6bdcfbapt	int j;
4134f6bdcfbapt
4144f6bdcfbapt	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
4154f6bdcfbapt		if (strcmp(grp->gr_mem[j], name) == 0)
4164f6bdcfbapt			return (true);
4174f6bdcfbapt	return (false);
4184f6bdcfbapt}
4194f6bdcfbapt
4204f6bdcfbaptstatic void
4214f6bdcfbaptgrp_add_members(struct group **grp, char *members)
4224f6bdcfbapt{
4234f6bdcfbapt	struct passwd *pwd;
4244f6bdcfbapt	char *p;
4254f6bdcfbapt	char tok[] = ", \t";
4264f6bdcfbapt
4274f6bdcfbapt	if (members == NULL)
4284f6bdcfbapt		return;
4294f6bdcfbapt	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
4304f6bdcfbapt		pwd = lookup_pwent(p);
4314f6bdcfbapt		if (grp_has_member(*grp, pwd->pw_name))
4324f6bdcfbapt			continue;
4334f6bdcfbapt		*grp = gr_add(*grp, pwd->pw_name);
4344f6bdcfbapt	}
4354f6bdcfbapt}
4364f6bdcfbapt
4374f6bdcfbaptint
4384f6bdcfbaptgroupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
4394f6bdcfbapt    bool dryrun, bool pretty, bool precrypted)
4404f6bdcfbapt{
4414f6bdcfbapt	struct group *grp;
4424f6bdcfbapt	int rc;
4434f6bdcfbapt
4444f6bdcfbapt	struct group fakegroup = {
4454f6bdcfbapt		"nogroup",
4464f6bdcfbapt		"*",
4474f6bdcfbapt		-1,
4484f6bdcfbapt		NULL
4494f6bdcfbapt	};
4504f6bdcfbapt
4514f6bdcfbapt	grp = &fakegroup;
4524f6bdcfbapt	grp->gr_name = pw_checkname(name, 0);
4534f6bdcfbapt	grp->gr_passwd = "*";
4544f6bdcfbapt	grp->gr_gid = gr_gidpolicy(cnf, id);
4554f6bdcfbapt	grp->gr_mem = NULL;
4564f6bdcfbapt
4574f6bdcfbapt	/*
4584f6bdcfbapt	 * This allows us to set a group password Group passwords is an
4594f6bdcfbapt	 * antique idea, rarely used and insecure (no secure database) Should
4604f6bdcfbapt	 * be discouraged, but it is apparently still supported by some
4614f6bdcfbapt	 * software.
4624f6bdcfbapt	 */
4634f6bdcfbapt	grp_set_passwd(grp, false, fd, precrypted);
4644f6bdcfbapt	grp_add_members(&grp, members);
4654f6bdcfbapt	if (dryrun)
4664f6bdcfbapt		return (print_group(grp, pretty));
4674f6bdcfbapt
4684f6bdcfbapt	if ((rc = addgrent(grp)) != 0) {
4694f6bdcfbapt		if (rc == -1)
4704f6bdcfbapt			errx(EX_IOERR, "group '%s' already exists",
4714f6bdcfbapt			    grp->gr_name);
4724f6bdcfbapt		else
4734f6bdcfbapt			err(EX_IOERR, "group update");
4744f6bdcfbapt	}
4754f6bdcfbapt
4764f6bdcfbapt	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
4774f6bdcfbapt	    (uintmax_t)grp->gr_gid);
4784f6bdcfbapt
4794f6bdcfbapt	return (EXIT_SUCCESS);
4804f6bdcfbapt}
4814f6bdcfbapt
4824f6bdcfbaptint
4834f6bdcfbaptpw_group_add(int argc, char **argv, char *arg1)
4844f6bdcfbapt{
4854f6bdcfbapt	struct userconf *cnf = NULL;
4864f6bdcfbapt	char *name = NULL;
4874f6bdcfbapt	char *members = NULL;
4884f6bdcfbapt	const char *cfg = NULL;
4894f6bdcfbapt	intmax_t id = -1;
4904f6bdcfbapt	int ch, rc, fd = -1;
4914f6bdcfbapt	bool quiet, precrypted, dryrun, pretty, nis;
4924f6bdcfbapt
4934f6bdcfbapt	quiet = precrypted = dryrun = pretty = nis = false;
4944f6bdcfbapt
4954f6bdcfbapt	if (arg1 != NULL) {
49608ce463ed		if (arg1[strspn(arg1, "0123456789")] == '\0')
4974f6bdcfbapt			id = pw_checkid(arg1, GID_MAX);
4984f6bdcfbapt		else
4994f6bdcfbapt			name = arg1;
5004f6bdcfbapt	}
5014f6bdcfbapt
5024f6bdcfbapt	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
5034f6bdcfbapt		switch (ch) {
5044f6bdcfbapt		case 'C':
5054f6bdcfbapt			cfg = optarg;
5064f6bdcfbapt			break;
5074f6bdcfbapt		case 'q':
5084f6bdcfbapt			quiet = true;
5094f6bdcfbapt			break;
5104f6bdcfbapt		case 'n':
5114f6bdcfbapt			name = optarg;
5124f6bdcfbapt			break;
5134f6bdcfbapt		case 'g':
5144f6bdcfbapt			id = pw_checkid(optarg, GID_MAX);
5154f6bdcfbapt			break;
5164f6bdcfbapt		case 'H':
5174f6bdcfbapt			if (fd != -1)
5184f6bdcfbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
5194f6bdcfbapt				    "exclusive options");
5204f6bdcfbapt			fd = pw_checkfd(optarg);
5214f6bdcfbapt			precrypted = true;
5224f6bdcfbapt			if (fd == '-')
5234f6bdcfbapt				errx(EX_USAGE, "-H expects a file descriptor");
5244f6bdcfbapt			break;
5254f6bdcfbapt		case 'h':
5264f6bdcfbapt			if (fd != -1)
5274f6bdcfbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
5284f6bdcfbapt				    "exclusive options");
5294f6bdcfbapt			fd = pw_checkfd(optarg);
5304f6bdcfbapt			break;
5314f6bdcfbapt		case 'M':
5324f6bdcfbapt			members = optarg;
5334f6bdcfbapt			break;
5344f6bdcfbapt		case 'o':
5354f6bdcfbapt			conf.checkduplicate = false;
5364f6bdcfbapt			break;
5374f6bdcfbapt		case 'N':
5384f6bdcfbapt			dryrun = true;
5394f6bdcfbapt			break;
5404f6bdcfbapt		case 'P':
5414f6bdcfbapt			pretty = true;
5424f6bdcfbapt			break;
5434f6bdcfbapt		case 'Y':
5444f6bdcfbapt			nis = true;
5454f6bdcfbapt			break;
5464f6bdcfbapt		}
5474f6bdcfbapt	}
5484f6bdcfbapt
5494f6bdcfbapt	if (quiet)
5504f6bdcfbapt		freopen(_PATH_DEVNULL, "w", stderr);
5514f6bdcfbapt	if (name == NULL)
5524f6bdcfbapt		errx(EX_DATAERR, "group name required");
5534453f16bapt	if (GETGRNAM(name) != NULL)
5544453f16bapt		errx(EX_DATAERR, "group name `%s' already exists", name);
5554f6bdcfbapt	cnf = get_userconfig(cfg);
5564f6bdcfbapt	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
5574f6bdcfbapt	    pretty, precrypted);
5584f6bdcfbapt	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
5594f6bdcfbapt		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
5604f6bdcfbapt
5614f6bdcfbapt	return (rc);
5624f6bdcfbapt}
5634f6bdcfbapt
5644f6bdcfbaptint
5654f6bdcfbaptpw_group_mod(int argc, char **argv, char *arg1)
5664f6bdcfbapt{
5674f6bdcfbapt	struct userconf *cnf;
5684f6bdcfbapt	struct group *grp = NULL;
5694f6bdcfbapt	const char *cfg = NULL;
5704f6bdcfbapt	char *oldmembers = NULL;
5714f6bdcfbapt	char *members = NULL;
5724f6bdcfbapt	char *newmembers = NULL;
5734f6bdcfbapt	char *newname = NULL;
5744f6bdcfbapt	char *name = NULL;
5754f6bdcfbapt	intmax_t id = -1;
5764f6bdcfbapt	int ch, rc, fd = -1;
5774f6bdcfbapt	bool quiet, pretty, dryrun, nis, precrypted;
5784f6bdcfbapt
5794f6bdcfbapt	quiet = pretty = dryrun = nis = precrypted = false;
5804f6bdcfbapt
5814f6bdcfbapt	if (arg1 != NULL) {
58208ce463ed		if (arg1[strspn(arg1, "0123456789")] == '\0')
5834f6bdcfbapt			id = pw_checkid(arg1, GID_MAX);
5844f6bdcfbapt		else
5854f6bdcfbapt			name = arg1;
5864f6bdcfbapt	}
5874f6bdcfbapt
5884f6bdcfbapt	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
5894f6bdcfbapt		switch (ch) {
5904f6bdcfbapt		case 'C':
5914f6bdcfbapt			cfg = optarg;
5924f6bdcfbapt			break;
5934f6bdcfbapt		case 'q':
5944f6bdcfbapt			quiet = true;
5954f6bdcfbapt			break;
5964f6bdcfbapt		case 'n':
5974f6bdcfbapt			name = optarg;
5984f6bdcfbapt			break;
5994f6bdcfbapt		case 'g':
6004f6bdcfbapt			id = pw_checkid(optarg, GID_MAX);
6014f6bdcfbapt			break;
6024f6bdcfbapt		case 'd':
6034f6bdcfbapt			oldmembers = optarg;
6044f6bdcfbapt			break;
6054f6bdcfbapt		case 'l':
6064f6bdcfbapt			newname = optarg;
6074f6bdcfbapt			break;
6084f6bdcfbapt		case 'H':
6094f6bdcfbapt			if (fd != -1)
6104f6bdcfbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
6114f6bdcfbapt				    "exclusive options");
6124f6bdcfbapt			fd = pw_checkfd(optarg);
6134f6bdcfbapt			precrypted = true;
6144f6bdcfbapt			if (fd == '-')
6154f6bdcfbapt				errx(EX_USAGE, "-H expects a file descriptor");
6164f6bdcfbapt			break;
6174f6bdcfbapt		case 'h':
6184f6bdcfbapt			if (fd != -1)
6194f6bdcfbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
6204f6bdcfbapt				    "exclusive options");
6214f6bdcfbapt			fd = pw_checkfd(optarg);
6224f6bdcfbapt			break;
6234f6bdcfbapt		case 'M':
6244f6bdcfbapt			members = optarg;
6254f6bdcfbapt			break;
6264f6bdcfbapt		case 'm':
6274f6bdcfbapt			newmembers = optarg;
6284f6bdcfbapt			break;
6294f6bdcfbapt		case 'N':
6304f6bdcfbapt			dryrun = true;
6314f6bdcfbapt			break;
6324f6bdcfbapt		case 'P':
6334f6bdcfbapt			pretty = true;
6344f6bdcfbapt			break;
6354f6bdcfbapt		case 'Y':
6364f6bdcfbapt			nis = true;
6374f6bdcfbapt			break;
6384f6bdcfbapt		}
6394f6bdcfbapt	}
6404f6bdcfbapt	if (quiet)
6414f6bdcfbapt		freopen(_PATH_DEVNULL, "w", stderr);
6424f6bdcfbapt	cnf = get_userconfig(cfg);
6434f6bdcfbapt	grp = getgroup(name, id, true);
6444f6bdcfbapt	if (name == NULL)
6454f6bdcfbapt		name = grp->gr_name;
6464f6bdcfbapt	if (id > 0)
6474f6bdcfbapt		grp->gr_gid = id;
6484f6bdcfbapt
6494f6bdcfbapt	if (newname != NULL)
6504f6bdcfbapt		grp->gr_name = pw_checkname(newname, 0);
6514f6bdcfbapt
6524f6bdcfbapt	grp_set_passwd(grp, true, fd, precrypted);
6534f6bdcfbapt	/*
6544f6bdcfbapt	 * Keep the same logic as old code for now:
6554f6bdcfbapt	 * if -M is passed, -d and -m are ignored
6564f6bdcfbapt	 * then id -d, -m is ignored
6574f6bdcfbapt	 * last is -m
6584f6bdcfbapt	 */
6594f6bdcfbapt
6604f6bdcfbapt	if (members) {
6614f6bdcfbapt		grp->gr_mem = NULL;
6624f6bdcfbapt		grp_add_members(&grp, members);
6634f6bdcfbapt	} else if (oldmembers) {
6644f6bdcfbapt		delete_members(grp, oldmembers);
6654f6bdcfbapt	} else if (newmembers) {
6664f6bdcfbapt		grp_add_members(&grp, newmembers);
667