120253Sjoerg/*-
220302Sjoerg * Copyright (C) 1996
320302Sjoerg *	David L. Nugent.  All rights reserved.
420253Sjoerg *
520253Sjoerg * Redistribution and use in source and binary forms, with or without
620253Sjoerg * modification, are permitted provided that the following conditions
720253Sjoerg * are met:
820253Sjoerg * 1. Redistributions of source code must retain the above copyright
920302Sjoerg *    notice, this list of conditions and the following disclaimer.
1020253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright
1120253Sjoerg *    notice, this list of conditions and the following disclaimer in the
1220253Sjoerg *    documentation and/or other materials provided with the distribution.
1320253Sjoerg *
1420302Sjoerg * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
1520253Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1620253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1720302Sjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
1820253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1920253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2020253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2120253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2220253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2320253Sjoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2420253Sjoerg * SUCH DAMAGE.
2520253Sjoerg */
2620253Sjoerg
2730259Scharnier#ifndef lint
2830259Scharnierstatic const char rcsid[] =
2950479Speter  "$FreeBSD: stable/10/usr.sbin/pw/pw_group.c 309881 2016-12-12 07:03:10Z bapt $";
3030259Scharnier#endif /* not lint */
3130259Scharnier
3220253Sjoerg#include <ctype.h>
3330259Scharnier#include <err.h>
34287084Sbapt#include <grp.h>
35287084Sbapt#include <libutil.h>
36287084Sbapt#include <paths.h>
37287084Sbapt#include <string.h>
38287084Sbapt#include <sysexits.h>
3920253Sjoerg#include <termios.h>
4030259Scharnier#include <unistd.h>
4120253Sjoerg
4220253Sjoerg#include "pw.h"
4320253Sjoerg#include "bitmap.h"
4420253Sjoerg
45176474Sscfstatic struct passwd *lookup_pwent(const char *user);
46287084Sbaptstatic void	delete_members(struct group *grp, char *list);
47287084Sbaptstatic int	print_group(struct group * grp, bool pretty);
48287084Sbaptstatic gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
4920253Sjoerg
50287084Sbaptstatic void
51287084Sbaptgrp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
5220253Sjoerg{
53287084Sbapt	int		 b;
54287084Sbapt	int		 istty;
55287084Sbapt	struct termios	 t, n;
56287084Sbapt	char		*p, line[256];
5720253Sjoerg
58287084Sbapt	if (fd == -1)
59287084Sbapt		return;
6020253Sjoerg
61287084Sbapt	if (fd == '-') {
62287084Sbapt		grp->gr_passwd = "*";	/* No access */
63287084Sbapt		return;
6420267Sjoerg	}
65287084Sbapt
66287084Sbapt	if ((istty = isatty(fd))) {
67287084Sbapt		n = t;
68287084Sbapt		/* Disable echo */
69287084Sbapt		n.c_lflag &= ~(ECHO);
70287084Sbapt		tcsetattr(fd, TCSANOW, &n);
71287084Sbapt		printf("%sassword for group %s:", update ? "New p" : "P",
72287084Sbapt		    grp->gr_name);
73287084Sbapt		fflush(stdout);
7420253Sjoerg	}
75287084Sbapt	b = read(fd, line, sizeof(line) - 1);
76287084Sbapt	if (istty) {	/* Restore state */
77287084Sbapt		tcsetattr(fd, TCSANOW, &t);
78287084Sbapt		fputc('\n', stdout);
79287084Sbapt		fflush(stdout);
8020253Sjoerg	}
81287084Sbapt	if (b < 0)
82287084Sbapt		err(EX_OSERR, "-h file descriptor");
83287084Sbapt	line[b] = '\0';
84287084Sbapt	if ((p = strpbrk(line, " \t\r\n")) != NULL)
85287084Sbapt		*p = '\0';
86287084Sbapt	if (!*line)
87287084Sbapt		errx(EX_DATAERR, "empty password read on file descriptor %d",
88287084Sbapt		    conf.fd);
89287084Sbapt	if (precrypted) {
90287084Sbapt		if (strchr(line, ':') != 0)
91287084Sbapt			errx(EX_DATAERR, "wrong encrypted passwrd");
92287084Sbapt		grp->gr_passwd = line;
93287084Sbapt	} else
94287084Sbapt		grp->gr_passwd = pw_pwcrypt(line);
95287084Sbapt}
9620253Sjoerg
97287084Sbaptint
98287084Sbaptpw_groupnext(struct userconf *cnf, bool quiet)
99287084Sbapt{
100287084Sbapt	gid_t next = gr_gidpolicy(cnf, -1);
10120253Sjoerg
102287084Sbapt	if (quiet)
103287084Sbapt		return (next);
104287084Sbapt	printf("%ju\n", (uintmax_t)next);
10520253Sjoerg
106287084Sbapt	return (EXIT_SUCCESS);
107287084Sbapt}
10820253Sjoerg
109287084Sbaptstatic struct group *
110287084Sbaptgetgroup(char *name, intmax_t id, bool fatal)
111287084Sbapt{
112287084Sbapt	struct group *grp;
11320267Sjoerg
114287084Sbapt	if (id < 0 && name == NULL)
115287084Sbapt		errx(EX_DATAERR, "groupname or id required");
116287084Sbapt	grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
117287084Sbapt	if (grp == NULL) {
118287084Sbapt		if (!fatal)
119287084Sbapt			return (NULL);
120287084Sbapt		if (name == NULL)
121287084Sbapt			errx(EX_DATAERR, "unknown gid `%ju'", id);
122287084Sbapt		errx(EX_DATAERR, "unknown group `%s'", name);
12320267Sjoerg	}
124287084Sbapt	return (grp);
12520253Sjoerg}
12620253Sjoerg
127176474Sscf/*
128176474Sscf * Lookup a passwd entry using a name or UID.
129176474Sscf */
130176474Sscfstatic struct passwd *
131176474Sscflookup_pwent(const char *user)
132176474Sscf{
133176474Sscf	struct passwd *pwd;
134176474Sscf
135176474Sscf	if ((pwd = GETPWNAM(user)) == NULL &&
136176474Sscf	    (!isdigit((unsigned char)*user) ||
137176474Sscf	    (pwd = getpwuid((uid_t) atoi(user))) == NULL))
138176474Sscf		errx(EX_NOUSER, "user `%s' does not exist", user);
139176474Sscf
140176474Sscf	return (pwd);
141176474Sscf}
142176474Sscf
143176474Sscf
144176474Sscf/*
145176474Sscf * Delete requested members from a group.
146176474Sscf */
147176474Sscfstatic void
148287084Sbaptdelete_members(struct group *grp, char *list)
149176474Sscf{
150287084Sbapt	char *p;
151176474Sscf	int k;
152176474Sscf
153272192Sdteske	if (grp->gr_mem == NULL)
154272192Sdteske		return;
155272192Sdteske
156287084Sbapt	for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
157287084Sbapt		for (k = 0; grp->gr_mem[k] != NULL; k++) {
158287084Sbapt			if (strcmp(grp->gr_mem[k], p) == 0)
159176474Sscf				break;
160176474Sscf		}
161287084Sbapt		if (grp->gr_mem[k] == NULL) /* No match */
162287084Sbapt			continue;
163176474Sscf
164287084Sbapt		for (; grp->gr_mem[k] != NULL; k++)
165287084Sbapt			grp->gr_mem[k] = grp->gr_mem[k+1];
166176474Sscf	}
167176474Sscf}
168176474Sscf
169287084Sbaptstatic gid_t
170287084Sbaptgr_gidpolicy(struct userconf * cnf, intmax_t id)
17120253Sjoerg{
17220253Sjoerg	struct group   *grp;
173287084Sbapt	struct bitmap   bm;
17420253Sjoerg	gid_t           gid = (gid_t) - 1;
17520253Sjoerg
17620253Sjoerg	/*
17720253Sjoerg	 * Check the given gid, if any
17820253Sjoerg	 */
179285092Sbapt	if (id > 0) {
180285092Sbapt		gid = (gid_t) id;
18120253Sjoerg
182285092Sbapt		if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
183287084Sbapt			errx(EX_DATAERR, "gid `%ju' has already been allocated",
184287084Sbapt			    (uintmax_t)grp->gr_gid);
185287084Sbapt		return (gid);
186287084Sbapt	}
18720253Sjoerg
188287084Sbapt	/*
189287084Sbapt	 * We need to allocate the next available gid under one of
190287084Sbapt	 * two policies a) Grab the first unused gid b) Grab the
191287084Sbapt	 * highest possible unused gid
192287084Sbapt	 */
193287084Sbapt	if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
194287084Sbapt		cnf->min_gid = 1000;
195287084Sbapt		cnf->max_gid = 32000;
196287084Sbapt	}
197287084Sbapt	bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
19820253Sjoerg
199287084Sbapt	/*
200287084Sbapt	 * Now, let's fill the bitmap from the password file
201287084Sbapt	 */
202287084Sbapt	SETGRENT();
203287084Sbapt	while ((grp = GETGRENT()) != NULL)
204287084Sbapt		if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
205287084Sbapt		    (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
206287084Sbapt			bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
207287084Sbapt	ENDGRENT();
20820253Sjoerg
209287084Sbapt	/*
210287084Sbapt	 * Then apply the policy, with fallback to reuse if necessary
211287084Sbapt	 */
212287084Sbapt	if (cnf->reuse_gids)
213287084Sbapt		gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
214287084Sbapt	else {
215287084Sbapt		gid = (gid_t) (bm_lastset(&bm) + 1);
216287084Sbapt		if (!bm_isset(&bm, gid))
217287084Sbapt			gid += cnf->min_gid;
218287084Sbapt		else
21920253Sjoerg			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
220287084Sbapt	}
22120253Sjoerg
222287084Sbapt	/*
223287084Sbapt	 * Another sanity check
224287084Sbapt	 */
225287084Sbapt	if (gid < cnf->min_gid || gid > cnf->max_gid)
226287084Sbapt		errx(EX_SOFTWARE, "unable to allocate a new gid - range fully "
227287084Sbapt		    "used");
228287084Sbapt	bm_dealloc(&bm);
229287084Sbapt	return (gid);
23020253Sjoerg}
23120253Sjoerg
23220253Sjoergstatic int
233287084Sbaptprint_group(struct group * grp, bool pretty)
23420253Sjoerg{
235287084Sbapt	char *buf = NULL;
236287084Sbapt	int i;
23720253Sjoerg
238287084Sbapt	if (pretty) {
23922398Sdavidn		printf("Group Name: %-15s   #%lu\n"
24020747Sdavidn		       "   Members: ",
24120253Sjoerg		       grp->gr_name, (long) grp->gr_gid);
242272192Sdteske		if (grp->gr_mem != NULL) {
243272192Sdteske			for (i = 0; grp->gr_mem[i]; i++)
244272192Sdteske				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
245272192Sdteske		}
24620253Sjoerg		fputs("\n\n", stdout);
247287084Sbapt		return (EXIT_SUCCESS);
24820253Sjoerg	}
249287084Sbapt
250287084Sbapt	buf = gr_make(grp);
251287084Sbapt	printf("%s\n", buf);
252287084Sbapt	free(buf);
253287084Sbapt	return (EXIT_SUCCESS);
25420253Sjoerg}
255287084Sbapt
256287084Sbaptint
257287084Sbaptpw_group_next(int argc, char **argv, char *arg1 __unused)
258287084Sbapt{
259287084Sbapt	struct userconf *cnf;
260287084Sbapt	const char *cfg = NULL;
261287084Sbapt	int ch;
262289978Sngie	bool quiet = false;
263287084Sbapt
264287084Sbapt	while ((ch = getopt(argc, argv, "Cq")) != -1) {
265287084Sbapt		switch (ch) {
266287084Sbapt		case 'C':
267287084Sbapt			cfg = optarg;
268287084Sbapt			break;
269287084Sbapt		case 'q':
270287084Sbapt			quiet = true;
271287084Sbapt			break;
272287084Sbapt		}
273287084Sbapt	}
274287084Sbapt
275287084Sbapt	if (quiet)
276287084Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
277287084Sbapt	cnf = get_userconfig(cfg);
278287084Sbapt	return (pw_groupnext(cnf, quiet));
279287084Sbapt}
280287084Sbapt
281287084Sbaptint
282287084Sbaptpw_group_show(int argc, char **argv, char *arg1)
283287084Sbapt{
284287084Sbapt	struct group *grp = NULL;
285309881Sbapt	char *name = NULL;
286287084Sbapt	intmax_t id = -1;
287287084Sbapt	int ch;
288287084Sbapt	bool all, force, quiet, pretty;
289287084Sbapt
290287084Sbapt	all = force = quiet = pretty = false;
291287084Sbapt
292287084Sbapt	struct group fakegroup = {
293287084Sbapt		"nogroup",
294287084Sbapt		"*",
295287084Sbapt		-1,
296287084Sbapt		NULL
297287084Sbapt	};
298287084Sbapt
299287084Sbapt	if (arg1 != NULL) {
300287084Sbapt		if (arg1[strspn(arg1, "0123456789")] == '\0')
301287084Sbapt			id = pw_checkid(arg1, GID_MAX);
302287084Sbapt		else
303287084Sbapt			name = arg1;
304287084Sbapt	}
305287084Sbapt
306287084Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
307287084Sbapt		switch (ch) {
308287084Sbapt		case 'C':
309287084Sbapt			/* ignore compatibility */
310287084Sbapt			break;
311287084Sbapt		case 'q':
312287084Sbapt			quiet = true;
313287084Sbapt			break;
314287084Sbapt		case 'n':
315287084Sbapt			name = optarg;
316287084Sbapt			break;
317287084Sbapt		case 'g':
318287084Sbapt			id = pw_checkid(optarg, GID_MAX);
319287084Sbapt			break;
320287084Sbapt		case 'F':
321287084Sbapt			force = true;
322287084Sbapt			break;
323287084Sbapt		case 'P':
324287084Sbapt			pretty = true;
325287084Sbapt			break;
326287084Sbapt		case 'a':
327287084Sbapt			all = true;
328287084Sbapt			break;
329287084Sbapt		}
330287084Sbapt	}
331287084Sbapt
332287084Sbapt	if (quiet)
333287084Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
334287084Sbapt
335287084Sbapt	if (all) {
336287084Sbapt		SETGRENT();
337287084Sbapt		while ((grp = GETGRENT()) != NULL)
338287084Sbapt			print_group(grp, pretty);
339287084Sbapt		ENDGRENT();
340287084Sbapt		return (EXIT_SUCCESS);
341287084Sbapt	}
342287084Sbapt
343287084Sbapt	grp = getgroup(name, id, !force);
344287084Sbapt	if (grp == NULL)
345287084Sbapt		grp = &fakegroup;
346287084Sbapt
347287084Sbapt	return (print_group(grp, pretty));
348287084Sbapt}
349287084Sbapt
350287084Sbaptint
351287084Sbaptpw_group_del(int argc, char **argv, char *arg1)
352287084Sbapt{
353287084Sbapt	struct userconf *cnf = NULL;
354287084Sbapt	struct group *grp = NULL;
355287084Sbapt	char *name;
356287084Sbapt	const char *cfg = NULL;
357287084Sbapt	intmax_t id = -1;
358287084Sbapt	int ch, rc;
359287084Sbapt	bool quiet = false;
360287084Sbapt	bool nis = false;
361287084Sbapt
362287084Sbapt	if (arg1 != NULL) {
363287084Sbapt		if (arg1[strspn(arg1, "0123456789")] == '\0')
364287084Sbapt			id = pw_checkid(arg1, GID_MAX);
365287084Sbapt		else
366287084Sbapt			name = arg1;
367287084Sbapt	}
368287084Sbapt
369287084Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
370287084Sbapt		switch (ch) {
371287084Sbapt		case 'C':
372287084Sbapt			cfg = optarg;
373287084Sbapt			break;
374287084Sbapt		case 'q':
375287084Sbapt			quiet = true;
376287084Sbapt			break;
377287084Sbapt		case 'n':
378287084Sbapt			name = optarg;
379287084Sbapt			break;
380287084Sbapt		case 'g':
381287084Sbapt			id = pw_checkid(optarg, GID_MAX);
382287084Sbapt			break;
383287084Sbapt		case 'Y':
384287084Sbapt			nis = true;
385287084Sbapt			break;
386287084Sbapt		}
387287084Sbapt	}
388287084Sbapt
389287084Sbapt	if (quiet)
390287084Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
391287084Sbapt	grp = getgroup(name, id, true);
392287084Sbapt	cnf = get_userconfig(cfg);
393287084Sbapt	rc = delgrent(grp);
394287084Sbapt	if (rc == -1)
395287084Sbapt		err(EX_IOERR, "group '%s' not available (NIS?)", name);
396287084Sbapt	else if (rc != 0)
397287084Sbapt		err(EX_IOERR, "group update");
398287084Sbapt	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
399287084Sbapt	    (uintmax_t)id);
400287084Sbapt
401287084Sbapt	if (nis && nis_update() == 0)
402287084Sbapt		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
403287084Sbapt
404287084Sbapt	return (EXIT_SUCCESS);
405287084Sbapt}
406287084Sbapt
407287084Sbaptstatic bool
408287084Sbaptgrp_has_member(struct group *grp, const char *name)
409287084Sbapt{
410287084Sbapt	int j;
411287084Sbapt
412287084Sbapt	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
413287084Sbapt		if (strcmp(grp->gr_mem[j], name) == 0)
414287084Sbapt			return (true);
415287084Sbapt	return (false);
416287084Sbapt}
417287084Sbapt
418287084Sbaptstatic void
419287084Sbaptgrp_add_members(struct group **grp, char *members)
420287084Sbapt{
421287084Sbapt	struct passwd *pwd;
422287084Sbapt	char *p;
423287084Sbapt	char tok[] = ", \t";
424287084Sbapt
425287084Sbapt	if (members == NULL)
426287084Sbapt		return;
427287084Sbapt	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
428287084Sbapt		pwd = lookup_pwent(p);
429287084Sbapt		if (grp_has_member(*grp, pwd->pw_name))
430287084Sbapt			continue;
431287084Sbapt		*grp = gr_add(*grp, pwd->pw_name);
432287084Sbapt	}
433287084Sbapt}
434287084Sbapt
435287084Sbaptint
436287084Sbaptgroupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
437287084Sbapt    bool dryrun, bool pretty, bool precrypted)
438287084Sbapt{
439287084Sbapt	struct group *grp;
440287084Sbapt	int rc;
441287084Sbapt
442287084Sbapt	struct group fakegroup = {
443287084Sbapt		"nogroup",
444287084Sbapt		"*",
445287084Sbapt		-1,
446287084Sbapt		NULL
447287084Sbapt	};
448287084Sbapt
449287084Sbapt	grp = &fakegroup;
450287084Sbapt	grp->gr_name = pw_checkname(name, 0);
451287084Sbapt	grp->gr_passwd = "*";
452287084Sbapt	grp->gr_gid = gr_gidpolicy(cnf, id);
453287084Sbapt	grp->gr_mem = NULL;
454287084Sbapt
455287084Sbapt	/*
456287084Sbapt	 * This allows us to set a group password Group passwords is an
457287084Sbapt	 * antique idea, rarely used and insecure (no secure database) Should
458287084Sbapt	 * be discouraged, but it is apparently still supported by some
459287084Sbapt	 * software.
460287084Sbapt	 */
461287084Sbapt	grp_set_passwd(grp, false, fd, precrypted);
462287084Sbapt	grp_add_members(&grp, members);
463287084Sbapt	if (dryrun)
464287084Sbapt		return (print_group(grp, pretty));
465287084Sbapt
466287084Sbapt	if ((rc = addgrent(grp)) != 0) {
467287084Sbapt		if (rc == -1)
468287084Sbapt			errx(EX_IOERR, "group '%s' already exists",
469287084Sbapt			    grp->gr_name);
470287084Sbapt		else
471287084Sbapt			err(EX_IOERR, "group update");
472287084Sbapt	}
473287084Sbapt
474287084Sbapt	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
475287084Sbapt	    (uintmax_t)grp->gr_gid);
476287084Sbapt
477287084Sbapt	return (EXIT_SUCCESS);
478287084Sbapt}
479287084Sbapt
480287084Sbaptint
481287084Sbaptpw_group_add(int argc, char **argv, char *arg1)
482287084Sbapt{
483287084Sbapt	struct userconf *cnf = NULL;
484287084Sbapt	char *name = NULL;
485287084Sbapt	char *members = NULL;
486287084Sbapt	const char *cfg = NULL;
487287084Sbapt	intmax_t id = -1;
488287084Sbapt	int ch, rc, fd = -1;
489287084Sbapt	bool quiet, precrypted, dryrun, pretty, nis;
490287084Sbapt
491287084Sbapt	quiet = precrypted = dryrun = pretty = nis = false;
492287084Sbapt
493287084Sbapt	if (arg1 != NULL) {
494287084Sbapt		if (arg1[strspn(arg1, "0123456789")] == '\0')
495287084Sbapt			id = pw_checkid(arg1, GID_MAX);
496287084Sbapt		else
497287084Sbapt			name = arg1;
498287084Sbapt	}
499287084Sbapt
500287084Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
501287084Sbapt		switch (ch) {
502287084Sbapt		case 'C':
503287084Sbapt			cfg = optarg;
504287084Sbapt			break;
505287084Sbapt		case 'q':
506287084Sbapt			quiet = true;
507287084Sbapt			break;
508287084Sbapt		case 'n':
509287084Sbapt			name = optarg;
510287084Sbapt			break;
511287084Sbapt		case 'g':
512287084Sbapt			id = pw_checkid(optarg, GID_MAX);
513287084Sbapt			break;
514287084Sbapt		case 'H':
515287084Sbapt			if (fd != -1)
516287084Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
517287084Sbapt				    "exclusive options");
518287084Sbapt			fd = pw_checkfd(optarg);
519287084Sbapt			precrypted = true;
520287084Sbapt			if (fd == '-')
521287084Sbapt				errx(EX_USAGE, "-H expects a file descriptor");
522287084Sbapt			break;
523287084Sbapt		case 'h':
524287084Sbapt			if (fd != -1)
525287084Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
526287084Sbapt				    "exclusive options");
527287084Sbapt			fd = pw_checkfd(optarg);
528287084Sbapt			break;
529287084Sbapt		case 'M':
530287084Sbapt			members = optarg;
531287084Sbapt			break;
532287084Sbapt		case 'o':
533287084Sbapt			conf.checkduplicate = false;
534287084Sbapt			break;
535287084Sbapt		case 'N':
536287084Sbapt			dryrun = true;
537287084Sbapt			break;
538287084Sbapt		case 'P':
539287084Sbapt			pretty = true;
540287084Sbapt			break;
541287084Sbapt		case 'Y':
542287084Sbapt			nis = true;
543287084Sbapt			break;
544287084Sbapt		}
545287084Sbapt	}
546287084Sbapt
547287084Sbapt	if (quiet)
548287084Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
549287084Sbapt	if (name == NULL)
550287084Sbapt		errx(EX_DATAERR, "group name required");
551287084Sbapt	if (GETGRNAM(name) != NULL)
552287084Sbapt		errx(EX_DATAERR, "group name `%s' already exists", name);
553287084Sbapt	cnf = get_userconfig(cfg);
554287084Sbapt	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
555287084Sbapt	    pretty, precrypted);
556287084Sbapt	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
557287084Sbapt		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
558287084Sbapt
559287084Sbapt	return (rc);
560287084Sbapt}
561287084Sbapt
562287084Sbaptint
563287084Sbaptpw_group_mod(int argc, char **argv, char *arg1)
564287084Sbapt{
565287084Sbapt	struct userconf *cnf;
566287084Sbapt	struct group *grp = NULL;
567287084Sbapt	const char *cfg = NULL;
568287084Sbapt	char *oldmembers = NULL;
569287084Sbapt	char *members = NULL;
570287084Sbapt	char *newmembers = NULL;
571287084Sbapt	char *newname = NULL;
572287084Sbapt	char *name = NULL;
573287084Sbapt	intmax_t id = -1;
574287084Sbapt	int ch, rc, fd = -1;
575287084Sbapt	bool quiet, pretty, dryrun, nis, precrypted;
576287084Sbapt
577287084Sbapt	quiet = pretty = dryrun = nis = precrypted = false;
578287084Sbapt
579287084Sbapt	if (arg1 != NULL) {
580287084Sbapt		if (arg1[strspn(arg1, "0123456789")] == '\0')
581287084Sbapt			id = pw_checkid(arg1, GID_MAX);
582287084Sbapt		else
583287084Sbapt			name = arg1;
584287084Sbapt	}
585287084Sbapt
586287084Sbapt	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
587287084Sbapt		switch (ch) {
588287084Sbapt		case 'C':
589287084Sbapt			cfg = optarg;
590287084Sbapt			break;
591287084Sbapt		case 'q':
592287084Sbapt			quiet = true;
593287084Sbapt			break;
594287084Sbapt		case 'n':
595287084Sbapt			name = optarg;
596287084Sbapt			break;
597287084Sbapt		case 'g':
598287084Sbapt			id = pw_checkid(optarg, GID_MAX);
599287084Sbapt			break;
600287084Sbapt		case 'd':
601287084Sbapt			oldmembers = optarg;
602287084Sbapt			break;
603287084Sbapt		case 'l':
604287084Sbapt			newname = optarg;
605287084Sbapt			break;
606287084Sbapt		case 'H':
607287084Sbapt			if (fd != -1)
608287084Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
609287084Sbapt				    "exclusive options");
610287084Sbapt			fd = pw_checkfd(optarg);
611287084Sbapt			precrypted = true;
612287084Sbapt			if (fd == '-')
613287084Sbapt				errx(EX_USAGE, "-H expects a file descriptor");
614287084Sbapt			break;
615287084Sbapt		case 'h':
616287084Sbapt			if (fd != -1)
617287084Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
618287084Sbapt				    "exclusive options");
619287084Sbapt			fd = pw_checkfd(optarg);
620287084Sbapt			break;
621287084Sbapt		case 'M':
622287084Sbapt			members = optarg;
623287084Sbapt			break;
624287084Sbapt		case 'm':
625287084Sbapt			newmembers = optarg;
626287084Sbapt			break;
627287084Sbapt		case 'N':
628287084Sbapt			dryrun = true;
629287084Sbapt			break;
630287084Sbapt		case 'P':
631287084Sbapt			pretty = true;
632287084Sbapt			break;
633287084Sbapt		case 'Y':
634287084Sbapt			nis = true;
635287084Sbapt			break;
636287084Sbapt		}
637287084Sbapt	}
638287084Sbapt	if (quiet)
639287084Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
640287084Sbapt	cnf = get_userconfig(cfg);
641287084Sbapt	grp = getgroup(name, id, true);
642287084Sbapt	if (name == NULL)
643287084Sbapt		name = grp->gr_name;
644287084Sbapt	if (id > 0)
645287084Sbapt		grp->gr_gid = id;
646287084Sbapt
647287084Sbapt	if (newname != NULL)
648287084Sbapt		grp->gr_name = pw_checkname(newname, 0);
649287084Sbapt
650287084Sbapt	grp_set_passwd(grp, true, fd, precrypted);
651287084Sbapt	/*
652287084Sbapt	 * Keep the same logic as old code for now:
653287084Sbapt	 * if -M is passed, -d and -m are ignored
654287084Sbapt	 * then id -d, -m is ignored
655287084Sbapt	 * last is -m
656287084Sbapt	 */
657287084Sbapt
658287084Sbapt	if (members) {
659287084Sbapt		grp->gr_mem = NULL;
660287084Sbapt		grp_add_members(&grp, members);
661287084Sbapt	} else if (oldmembers) {
662287084Sbapt		delete_members(grp, oldmembers);
663287084Sbapt	} else if (newmembers) {
664287084Sbapt		grp_add_members(&grp, newmembers);
665287084Sbapt	}
666287084Sbapt
667292965Sbapt	if (dryrun) {
668292965Sbapt		print_group(grp, pretty);
669292965Sbapt		return (EXIT_SUCCESS);
670292965Sbapt	}
671292965Sbapt
672287084Sbapt	if ((rc = chggrent(name, grp)) != 0) {
673287084Sbapt		if (rc == -1)
674287084Sbapt			errx(EX_IOERR, "group '%s' not available (NIS?)",
675287084Sbapt			    grp->gr_name);
676287084Sbapt		else
677287084Sbapt			err(EX_IOERR, "group update");
678287084Sbapt	}
679287084Sbapt
680287084Sbapt	if (newname)
681287084Sbapt		name = newname;
682287084Sbapt
683287084Sbapt	/* grp may have been invalidated */
684287084Sbapt	if ((grp = GETGRNAM(name)) == NULL)
685287084Sbapt		errx(EX_SOFTWARE, "group disappeared during update");
686287084Sbapt
687287084Sbapt	pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
688287084Sbapt	    (uintmax_t)grp->gr_gid);
689287084Sbapt
690287084Sbapt	if (nis && nis_update() == 0)
691287084Sbapt		pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
692287084Sbapt
693287084Sbapt	return (EXIT_SUCCESS);
694287084Sbapt}
695