120253Sjoerg/*-
2330449Seadler * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3330449Seadler *
420302Sjoerg * Copyright (C) 1996
520302Sjoerg *	David L. Nugent.  All rights reserved.
620253Sjoerg *
720253Sjoerg * Redistribution and use in source and binary forms, with or without
820253Sjoerg * modification, are permitted provided that the following conditions
920253Sjoerg * are met:
1020253Sjoerg * 1. Redistributions of source code must retain the above copyright
1120302Sjoerg *    notice, this list of conditions and the following disclaimer.
1220253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright
1320253Sjoerg *    notice, this list of conditions and the following disclaimer in the
1420253Sjoerg *    documentation and/or other materials provided with the distribution.
1520253Sjoerg *
1620302Sjoerg * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
1720253Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1820253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1920302Sjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
2020253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2120253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2220253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2320253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2420253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2520253Sjoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2620253Sjoerg * SUCH DAMAGE.
2720253Sjoerg */
2820253Sjoerg
2930259Scharnier#ifndef lint
3030259Scharnierstatic const char rcsid[] =
3150479Speter  "$FreeBSD: stable/11/usr.sbin/pw/pw_group.c 330449 2018-03-05 07:26:05Z eadler $";
3230259Scharnier#endif /* not lint */
3330259Scharnier
3420253Sjoerg#include <ctype.h>
3530259Scharnier#include <err.h>
36286196Sbapt#include <grp.h>
37286196Sbapt#include <libutil.h>
38286196Sbapt#include <paths.h>
39286201Sbapt#include <string.h>
40286201Sbapt#include <sysexits.h>
4120253Sjoerg#include <termios.h>
4230259Scharnier#include <unistd.h>
4320253Sjoerg
4420253Sjoerg#include "pw.h"
4520253Sjoerg#include "bitmap.h"
4620253Sjoerg
47176474Sscfstatic struct passwd *lookup_pwent(const char *user);
48285411Sbaptstatic void	delete_members(struct group *grp, char *list);
49286196Sbaptstatic int	print_group(struct group * grp, bool pretty);
50286196Sbaptstatic gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
5120253Sjoerg
52285136Sbaptstatic void
53286196Sbaptgrp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
54285136Sbapt{
55285136Sbapt	int		 b;
56285136Sbapt	int		 istty;
57285136Sbapt	struct termios	 t, n;
58285136Sbapt	char		*p, line[256];
59285136Sbapt
60286196Sbapt	if (fd == -1)
61286196Sbapt		return;
62286196Sbapt
63286196Sbapt	if (fd == '-') {
64285136Sbapt		grp->gr_passwd = "*";	/* No access */
65285136Sbapt		return;
66285136Sbapt	}
67285136Sbapt
68286196Sbapt	if ((istty = isatty(fd))) {
69285136Sbapt		n = t;
70285136Sbapt		/* Disable echo */
71285136Sbapt		n.c_lflag &= ~(ECHO);
72286196Sbapt		tcsetattr(fd, TCSANOW, &n);
73285136Sbapt		printf("%sassword for group %s:", update ? "New p" : "P",
74285136Sbapt		    grp->gr_name);
75285136Sbapt		fflush(stdout);
76285136Sbapt	}
77286196Sbapt	b = read(fd, line, sizeof(line) - 1);
78285136Sbapt	if (istty) {	/* Restore state */
79286196Sbapt		tcsetattr(fd, TCSANOW, &t);
80285136Sbapt		fputc('\n', stdout);
81285136Sbapt		fflush(stdout);
82285136Sbapt	}
83285136Sbapt	if (b < 0)
84285136Sbapt		err(EX_OSERR, "-h file descriptor");
85285136Sbapt	line[b] = '\0';
86285136Sbapt	if ((p = strpbrk(line, " \t\r\n")) != NULL)
87285136Sbapt		*p = '\0';
88285136Sbapt	if (!*line)
89285136Sbapt		errx(EX_DATAERR, "empty password read on file descriptor %d",
90285136Sbapt		    conf.fd);
91286196Sbapt	if (precrypted) {
92285136Sbapt		if (strchr(line, ':') != 0)
93285136Sbapt			errx(EX_DATAERR, "wrong encrypted passwrd");
94285136Sbapt		grp->gr_passwd = line;
95285136Sbapt	} else
96285136Sbapt		grp->gr_passwd = pw_pwcrypt(line);
97285136Sbapt}
98285136Sbapt
9920253Sjoergint
100285395Sbaptpw_groupnext(struct userconf *cnf, bool quiet)
101285395Sbapt{
102285395Sbapt	gid_t next = gr_gidpolicy(cnf, -1);
103285395Sbapt
104285395Sbapt	if (quiet)
105285395Sbapt		return (next);
106286150Sbapt	printf("%ju\n", (uintmax_t)next);
107285395Sbapt
108285395Sbapt	return (EXIT_SUCCESS);
109285395Sbapt}
110285395Sbapt
111286196Sbaptstatic struct group *
112286196Sbaptgetgroup(char *name, intmax_t id, bool fatal)
113285398Sbapt{
114286196Sbapt	struct group *grp;
115285398Sbapt
116286196Sbapt	if (id < 0 && name == NULL)
117286196Sbapt		errx(EX_DATAERR, "groupname or id required");
118285398Sbapt	grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
119285398Sbapt	if (grp == NULL) {
120286196Sbapt		if (!fatal)
121286196Sbapt			return (NULL);
122285401Sbapt		if (name == NULL)
123286196Sbapt			errx(EX_DATAERR, "unknown gid `%ju'", id);
124285401Sbapt		errx(EX_DATAERR, "unknown group `%s'", name);
125285401Sbapt	}
126286196Sbapt	return (grp);
127285401Sbapt}
128285401Sbapt
129176474Sscf/*
130176474Sscf * Lookup a passwd entry using a name or UID.
131176474Sscf */
132176474Sscfstatic struct passwd *
133176474Sscflookup_pwent(const char *user)
134176474Sscf{
135176474Sscf	struct passwd *pwd;
136176474Sscf
137176474Sscf	if ((pwd = GETPWNAM(user)) == NULL &&
138176474Sscf	    (!isdigit((unsigned char)*user) ||
139176474Sscf	    (pwd = getpwuid((uid_t) atoi(user))) == NULL))
140176474Sscf		errx(EX_NOUSER, "user `%s' does not exist", user);
141176474Sscf
142176474Sscf	return (pwd);
143176474Sscf}
144176474Sscf
145176474Sscf
146176474Sscf/*
147176474Sscf * Delete requested members from a group.
148176474Sscf */
149176474Sscfstatic void
150285411Sbaptdelete_members(struct group *grp, char *list)
151176474Sscf{
152285411Sbapt	char *p;
153176474Sscf	int k;
154176474Sscf
155262864Sjulian	if (grp->gr_mem == NULL)
156262864Sjulian		return;
157262864Sjulian
158285411Sbapt	for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
159285411Sbapt		for (k = 0; grp->gr_mem[k] != NULL; k++) {
160285411Sbapt			if (strcmp(grp->gr_mem[k], p) == 0)
161176474Sscf				break;
162176474Sscf		}
163285411Sbapt		if (grp->gr_mem[k] == NULL) /* No match */
164285411Sbapt			continue;
165176474Sscf
166285411Sbapt		for (; grp->gr_mem[k] != NULL; k++)
167285411Sbapt			grp->gr_mem[k] = grp->gr_mem[k+1];
168176474Sscf	}
169176474Sscf}
170176474Sscf
171286196Sbaptstatic gid_t
172286196Sbaptgr_gidpolicy(struct userconf * cnf, intmax_t id)
17320253Sjoerg{
17420253Sjoerg	struct group   *grp;
175286196Sbapt	struct bitmap   bm;
17620253Sjoerg	gid_t           gid = (gid_t) - 1;
17720253Sjoerg
17820253Sjoerg	/*
17920253Sjoerg	 * Check the given gid, if any
18020253Sjoerg	 */
181284133Sbapt	if (id > 0) {
182284133Sbapt		gid = (gid_t) id;
18320253Sjoerg
184284133Sbapt		if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
185286203Sbapt			errx(EX_DATAERR, "gid `%ju' has already been allocated",
186286203Sbapt			    (uintmax_t)grp->gr_gid);
187286196Sbapt		return (gid);
188286196Sbapt	}
18920253Sjoerg
190286196Sbapt	/*
191286196Sbapt	 * We need to allocate the next available gid under one of
192286196Sbapt	 * two policies a) Grab the first unused gid b) Grab the
193286196Sbapt	 * highest possible unused gid
194286196Sbapt	 */
195286196Sbapt	if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
196286196Sbapt		cnf->min_gid = 1000;
197286196Sbapt		cnf->max_gid = 32000;
198286196Sbapt	}
199286196Sbapt	bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
20020253Sjoerg
201286196Sbapt	/*
202286196Sbapt	 * Now, let's fill the bitmap from the password file
203286196Sbapt	 */
204286196Sbapt	SETGRENT();
205286196Sbapt	while ((grp = GETGRENT()) != NULL)
206286196Sbapt		if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
207286196Sbapt		    (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
208286196Sbapt			bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
209286196Sbapt	ENDGRENT();
21020253Sjoerg
211286196Sbapt	/*
212286196Sbapt	 * Then apply the policy, with fallback to reuse if necessary
213286196Sbapt	 */
214286196Sbapt	if (cnf->reuse_gids)
215286196Sbapt		gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
216286196Sbapt	else {
217286196Sbapt		gid = (gid_t) (bm_lastset(&bm) + 1);
218286196Sbapt		if (!bm_isset(&bm, gid))
219286196Sbapt			gid += cnf->min_gid;
220286196Sbapt		else
22120253Sjoerg			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
222286196Sbapt	}
22320253Sjoerg
224286196Sbapt	/*
225286196Sbapt	 * Another sanity check
226286196Sbapt	 */
227286196Sbapt	if (gid < cnf->min_gid || gid > cnf->max_gid)
228286203Sbapt		errx(EX_SOFTWARE, "unable to allocate a new gid - range fully "
229286203Sbapt		    "used");
230286196Sbapt	bm_dealloc(&bm);
231286196Sbapt	return (gid);
23220253Sjoerg}
23320253Sjoerg
23420253Sjoergstatic int
235286196Sbaptprint_group(struct group * grp, bool pretty)
23620253Sjoerg{
237286196Sbapt	char *buf = NULL;
238286196Sbapt	int i;
23920253Sjoerg
240286196Sbapt	if (pretty) {
24122398Sdavidn		printf("Group Name: %-15s   #%lu\n"
24220747Sdavidn		       "   Members: ",
24320253Sjoerg		       grp->gr_name, (long) grp->gr_gid);
244262864Sjulian		if (grp->gr_mem != NULL) {
245262864Sjulian			for (i = 0; grp->gr_mem[i]; i++)
246262864Sjulian				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
247262864Sjulian		}
24820253Sjoerg		fputs("\n\n", stdout);
249286196Sbapt		return (EXIT_SUCCESS);
25020253Sjoerg	}
251286196Sbapt
252286196Sbapt	buf = gr_make(grp);
253286196Sbapt	printf("%s\n", buf);
254286196Sbapt	free(buf);
255286196Sbapt	return (EXIT_SUCCESS);
25620253Sjoerg}
257286196Sbapt
258286196Sbaptint
259286196Sbaptpw_group_next(int argc, char **argv, char *arg1 __unused)
260286196Sbapt{
261286196Sbapt	struct userconf *cnf;
262286196Sbapt	const char *cfg = NULL;
263286196Sbapt	int ch;
264289600Sngie	bool quiet = false;
265286196Sbapt
266301367Sbapt	while ((ch = getopt(argc, argv, "C:q")) != -1) {
267286196Sbapt		switch (ch) {
268286196Sbapt		case 'C':
269286196Sbapt			cfg = optarg;
270286196Sbapt			break;
271286196Sbapt		case 'q':
272286196Sbapt			quiet = true;
273286196Sbapt			break;
274286196Sbapt		}
275286196Sbapt	}
276286196Sbapt
277286196Sbapt	if (quiet)
278286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
279286196Sbapt	cnf = get_userconfig(cfg);
280286196Sbapt	return (pw_groupnext(cnf, quiet));
281286196Sbapt}
282286196Sbapt
283286196Sbaptint
284286196Sbaptpw_group_show(int argc, char **argv, char *arg1)
285286196Sbapt{
286286196Sbapt	struct group *grp = NULL;
287309880Sbapt	char *name = NULL;
288286196Sbapt	intmax_t id = -1;
289286196Sbapt	int ch;
290286196Sbapt	bool all, force, quiet, pretty;
291286196Sbapt
292286196Sbapt	all = force = quiet = pretty = false;
293286196Sbapt
294286196Sbapt	struct group fakegroup = {
295286196Sbapt		"nogroup",
296286196Sbapt		"*",
297286196Sbapt		-1,
298286196Sbapt		NULL
299286196Sbapt	};
300286196Sbapt
301286196Sbapt	if (arg1 != NULL) {
302286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
303286196Sbapt			id = pw_checkid(arg1, GID_MAX);
304286196Sbapt		else
305286196Sbapt			name = arg1;
306286196Sbapt	}
307286196Sbapt
308286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
309286196Sbapt		switch (ch) {
310286196Sbapt		case 'C':
311286196Sbapt			/* ignore compatibility */
312286196Sbapt			break;
313286196Sbapt		case 'q':
314286196Sbapt			quiet = true;
315286196Sbapt			break;
316286196Sbapt		case 'n':
317286196Sbapt			name = optarg;
318286196Sbapt			break;
319286196Sbapt		case 'g':
320286196Sbapt			id = pw_checkid(optarg, GID_MAX);
321286196Sbapt			break;
322286196Sbapt		case 'F':
323286196Sbapt			force = true;
324286196Sbapt			break;
325286196Sbapt		case 'P':
326286196Sbapt			pretty = true;
327286196Sbapt			break;
328286196Sbapt		case 'a':
329286196Sbapt			all = true;
330286196Sbapt			break;
331286196Sbapt		}
332286196Sbapt	}
333286196Sbapt
334286196Sbapt	if (quiet)
335286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
336286196Sbapt
337286196Sbapt	if (all) {
338286196Sbapt		SETGRENT();
339286196Sbapt		while ((grp = GETGRENT()) != NULL)
340286196Sbapt			print_group(grp, pretty);
341286196Sbapt		ENDGRENT();
342286196Sbapt		return (EXIT_SUCCESS);
343286196Sbapt	}
344286196Sbapt
345286196Sbapt	grp = getgroup(name, id, !force);
346286196Sbapt	if (grp == NULL)
347286196Sbapt		grp = &fakegroup;
348286196Sbapt
349286196Sbapt	return (print_group(grp, pretty));
350286196Sbapt}
351286196Sbapt
352286196Sbaptint
353286196Sbaptpw_group_del(int argc, char **argv, char *arg1)
354286196Sbapt{
355286196Sbapt	struct userconf *cnf = NULL;
356286196Sbapt	struct group *grp = NULL;
357286196Sbapt	char *name;
358286196Sbapt	const char *cfg = NULL;
359286196Sbapt	intmax_t id = -1;
360286196Sbapt	int ch, rc;
361286196Sbapt	bool quiet = false;
362286196Sbapt	bool nis = false;
363286196Sbapt
364286196Sbapt	if (arg1 != NULL) {
365286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
366286196Sbapt			id = pw_checkid(arg1, GID_MAX);
367286196Sbapt		else
368286196Sbapt			name = arg1;
369286196Sbapt	}
370286196Sbapt
371286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
372286196Sbapt		switch (ch) {
373286196Sbapt		case 'C':
374286196Sbapt			cfg = optarg;
375286196Sbapt			break;
376286196Sbapt		case 'q':
377286196Sbapt			quiet = true;
378286196Sbapt			break;
379286196Sbapt		case 'n':
380286196Sbapt			name = optarg;
381286196Sbapt			break;
382286196Sbapt		case 'g':
383286196Sbapt			id = pw_checkid(optarg, GID_MAX);
384286196Sbapt			break;
385286196Sbapt		case 'Y':
386286196Sbapt			nis = true;
387286196Sbapt			break;
388286196Sbapt		}
389286196Sbapt	}
390286196Sbapt
391286196Sbapt	if (quiet)
392286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
393286196Sbapt	grp = getgroup(name, id, true);
394286196Sbapt	cnf = get_userconfig(cfg);
395286196Sbapt	rc = delgrent(grp);
396286196Sbapt	if (rc == -1)
397286196Sbapt		err(EX_IOERR, "group '%s' not available (NIS?)", name);
398286196Sbapt	else if (rc != 0)
399286196Sbapt		err(EX_IOERR, "group update");
400286196Sbapt	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
401286196Sbapt	    (uintmax_t)id);
402286196Sbapt
403286196Sbapt	if (nis && nis_update() == 0)
404286196Sbapt		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
405286196Sbapt
406286196Sbapt	return (EXIT_SUCCESS);
407286196Sbapt}
408286196Sbapt
409286196Sbaptstatic bool
410286196Sbaptgrp_has_member(struct group *grp, const char *name)
411286196Sbapt{
412286196Sbapt	int j;
413286196Sbapt
414286196Sbapt	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
415286196Sbapt		if (strcmp(grp->gr_mem[j], name) == 0)
416286196Sbapt			return (true);
417286196Sbapt	return (false);
418286196Sbapt}
419286196Sbapt
420286196Sbaptstatic void
421286196Sbaptgrp_add_members(struct group **grp, char *members)
422286196Sbapt{
423286196Sbapt	struct passwd *pwd;
424286196Sbapt	char *p;
425286196Sbapt	char tok[] = ", \t";
426286196Sbapt
427286196Sbapt	if (members == NULL)
428286196Sbapt		return;
429286196Sbapt	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
430286196Sbapt		pwd = lookup_pwent(p);
431286196Sbapt		if (grp_has_member(*grp, pwd->pw_name))
432286196Sbapt			continue;
433286196Sbapt		*grp = gr_add(*grp, pwd->pw_name);
434286196Sbapt	}
435286196Sbapt}
436286196Sbapt
437286196Sbaptint
438286196Sbaptgroupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
439286196Sbapt    bool dryrun, bool pretty, bool precrypted)
440286196Sbapt{
441286196Sbapt	struct group *grp;
442286196Sbapt	int rc;
443286196Sbapt
444286196Sbapt	struct group fakegroup = {
445286196Sbapt		"nogroup",
446286196Sbapt		"*",
447286196Sbapt		-1,
448286196Sbapt		NULL
449286196Sbapt	};
450286196Sbapt
451286196Sbapt	grp = &fakegroup;
452286196Sbapt	grp->gr_name = pw_checkname(name, 0);
453286196Sbapt	grp->gr_passwd = "*";
454286196Sbapt	grp->gr_gid = gr_gidpolicy(cnf, id);
455286196Sbapt	grp->gr_mem = NULL;
456286196Sbapt
457286196Sbapt	/*
458286196Sbapt	 * This allows us to set a group password Group passwords is an
459286196Sbapt	 * antique idea, rarely used and insecure (no secure database) Should
460286196Sbapt	 * be discouraged, but it is apparently still supported by some
461286196Sbapt	 * software.
462286196Sbapt	 */
463286196Sbapt	grp_set_passwd(grp, false, fd, precrypted);
464286196Sbapt	grp_add_members(&grp, members);
465286196Sbapt	if (dryrun)
466286196Sbapt		return (print_group(grp, pretty));
467286196Sbapt
468286196Sbapt	if ((rc = addgrent(grp)) != 0) {
469286196Sbapt		if (rc == -1)
470286196Sbapt			errx(EX_IOERR, "group '%s' already exists",
471286196Sbapt			    grp->gr_name);
472286196Sbapt		else
473286196Sbapt			err(EX_IOERR, "group update");
474286196Sbapt	}
475286196Sbapt
476286196Sbapt	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
477286196Sbapt	    (uintmax_t)grp->gr_gid);
478286196Sbapt
479286196Sbapt	return (EXIT_SUCCESS);
480286196Sbapt}
481286196Sbapt
482286196Sbaptint
483286196Sbaptpw_group_add(int argc, char **argv, char *arg1)
484286196Sbapt{
485286196Sbapt	struct userconf *cnf = NULL;
486286196Sbapt	char *name = NULL;
487286196Sbapt	char *members = NULL;
488286196Sbapt	const char *cfg = NULL;
489286196Sbapt	intmax_t id = -1;
490286196Sbapt	int ch, rc, fd = -1;
491286196Sbapt	bool quiet, precrypted, dryrun, pretty, nis;
492286196Sbapt
493286196Sbapt	quiet = precrypted = dryrun = pretty = nis = false;
494286196Sbapt
495286196Sbapt	if (arg1 != NULL) {
496286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
497286196Sbapt			id = pw_checkid(arg1, GID_MAX);
498286196Sbapt		else
499286196Sbapt			name = arg1;
500286196Sbapt	}
501286196Sbapt
502286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
503286196Sbapt		switch (ch) {
504286196Sbapt		case 'C':
505286196Sbapt			cfg = optarg;
506286196Sbapt			break;
507286196Sbapt		case 'q':
508286196Sbapt			quiet = true;
509286196Sbapt			break;
510286196Sbapt		case 'n':
511286196Sbapt			name = optarg;
512286196Sbapt			break;
513286196Sbapt		case 'g':
514286196Sbapt			id = pw_checkid(optarg, GID_MAX);
515286196Sbapt			break;
516286196Sbapt		case 'H':
517286196Sbapt			if (fd != -1)
518286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
519286196Sbapt				    "exclusive options");
520286196Sbapt			fd = pw_checkfd(optarg);
521286196Sbapt			precrypted = true;
522286196Sbapt			if (fd == '-')
523286196Sbapt				errx(EX_USAGE, "-H expects a file descriptor");
524286196Sbapt			break;
525286196Sbapt		case 'h':
526286196Sbapt			if (fd != -1)
527286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
528286196Sbapt				    "exclusive options");
529286196Sbapt			fd = pw_checkfd(optarg);
530286196Sbapt			break;
531286196Sbapt		case 'M':
532286196Sbapt			members = optarg;
533286196Sbapt			break;
534286196Sbapt		case 'o':
535286196Sbapt			conf.checkduplicate = false;
536286196Sbapt			break;
537286196Sbapt		case 'N':
538286196Sbapt			dryrun = true;
539286196Sbapt			break;
540286196Sbapt		case 'P':
541286196Sbapt			pretty = true;
542286196Sbapt			break;
543286196Sbapt		case 'Y':
544286196Sbapt			nis = true;
545286196Sbapt			break;
546286196Sbapt		}
547286196Sbapt	}
548286196Sbapt
549286196Sbapt	if (quiet)
550286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
551286196Sbapt	if (name == NULL)
552286196Sbapt		errx(EX_DATAERR, "group name required");
553286199Sbapt	if (GETGRNAM(name) != NULL)
554286199Sbapt		errx(EX_DATAERR, "group name `%s' already exists", name);
555286196Sbapt	cnf = get_userconfig(cfg);
556286196Sbapt	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
557286196Sbapt	    pretty, precrypted);
558286196Sbapt	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
559286196Sbapt		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
560286196Sbapt
561286196Sbapt	return (rc);
562286196Sbapt}
563286196Sbapt
564286196Sbaptint
565286196Sbaptpw_group_mod(int argc, char **argv, char *arg1)
566286196Sbapt{
567286196Sbapt	struct userconf *cnf;
568286196Sbapt	struct group *grp = NULL;
569286196Sbapt	const char *cfg = NULL;
570286196Sbapt	char *oldmembers = NULL;
571286196Sbapt	char *members = NULL;
572286196Sbapt	char *newmembers = NULL;
573286196Sbapt	char *newname = NULL;
574286196Sbapt	char *name = NULL;
575286196Sbapt	intmax_t id = -1;
576286196Sbapt	int ch, rc, fd = -1;
577286196Sbapt	bool quiet, pretty, dryrun, nis, precrypted;
578286196Sbapt
579286196Sbapt	quiet = pretty = dryrun = nis = precrypted = false;
580286196Sbapt
581286196Sbapt	if (arg1 != NULL) {
582286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
583286196Sbapt			id = pw_checkid(arg1, GID_MAX);
584286196Sbapt		else
585286196Sbapt			name = arg1;
586286196Sbapt	}
587286196Sbapt
588286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
589286196Sbapt		switch (ch) {
590286196Sbapt		case 'C':
591286196Sbapt			cfg = optarg;
592286196Sbapt			break;
593286196Sbapt		case 'q':
594286196Sbapt			quiet = true;
595286196Sbapt			break;
596286196Sbapt		case 'n':
597286196Sbapt			name = optarg;
598286196Sbapt			break;
599286196Sbapt		case 'g':
600286196Sbapt			id = pw_checkid(optarg, GID_MAX);
601286196Sbapt			break;
602286196Sbapt		case 'd':
603286196Sbapt			oldmembers = optarg;
604286196Sbapt			break;
605286196Sbapt		case 'l':
606286196Sbapt			newname = optarg;
607286196Sbapt			break;
608286196Sbapt		case 'H':
609286196Sbapt			if (fd != -1)
610286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
611286196Sbapt				    "exclusive options");
612286196Sbapt			fd = pw_checkfd(optarg);
613286196Sbapt			precrypted = true;
614286196Sbapt			if (fd == '-')
615286196Sbapt				errx(EX_USAGE, "-H expects a file descriptor");
616286196Sbapt			break;
617286196Sbapt		case 'h':
618286196Sbapt			if (fd != -1)
619286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
620286196Sbapt				    "exclusive options");
621286196Sbapt			fd = pw_checkfd(optarg);
622286196Sbapt			break;
623286196Sbapt		case 'M':
624286196Sbapt			members = optarg;
625286196Sbapt			break;
626286196Sbapt		case 'm':
627286196Sbapt			newmembers = optarg;
628286196Sbapt			break;
629286196Sbapt		case 'N':
630286196Sbapt			dryrun = true;
631286196Sbapt			break;
632286196Sbapt		case 'P':
633286196Sbapt			pretty = true;
634286196Sbapt			break;
635286196Sbapt		case 'Y':
636286196Sbapt			nis = true;
637286196Sbapt			break;
638286196Sbapt		}
639286196Sbapt	}
640286196Sbapt	if (quiet)
641286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
642286196Sbapt	cnf = get_userconfig(cfg);
643286196Sbapt	grp = getgroup(name, id, true);
644286196Sbapt	if (name == NULL)
645286196Sbapt		name = grp->gr_name;
646286196Sbapt	if (id > 0)
647286196Sbapt		grp->gr_gid = id;
648286196Sbapt
649286196Sbapt	if (newname != NULL)
650286196Sbapt		grp->gr_name = pw_checkname(newname, 0);
651286196Sbapt
652286196Sbapt	grp_set_passwd(grp, true, fd, precrypted);
653286196Sbapt	/*
654286196Sbapt	 * Keep the same logic as old code for now:
655286196Sbapt	 * if -M is passed, -d and -m are ignored
656286196Sbapt	 * then id -d, -m is ignored
657286196Sbapt	 * last is -m
658286196Sbapt	 */
659286196Sbapt
660286196Sbapt	if (members) {
661286196Sbapt		grp->gr_mem = NULL;
662286196Sbapt		grp_add_members(&grp, members);
663286196Sbapt	} else if (oldmembers) {
664286196Sbapt		delete_members(grp, oldmembers);
665286196Sbapt	} else if (newmembers) {
666286196Sbapt		grp_add_members(&grp, newmembers);
667286196Sbapt	}
668286196Sbapt
669292846Sbapt	if (dryrun) {
670292846Sbapt		print_group(grp, pretty);
671292846Sbapt		return (EXIT_SUCCESS);
672292846Sbapt	}
673292846Sbapt
674286196Sbapt	if ((rc = chggrent(name, grp)) != 0) {
675286196Sbapt		if (rc == -1)
676286196Sbapt			errx(EX_IOERR, "group '%s' not available (NIS?)",
677286196Sbapt			    grp->gr_name);
678286196Sbapt		else
679286196Sbapt			err(EX_IOERR, "group update");
680286196Sbapt	}
681286196Sbapt
682286196Sbapt	if (newname)
683286196Sbapt		name = newname;
684286196Sbapt
685286196Sbapt	/* grp may have been invalidated */
686286196Sbapt	if ((grp = GETGRNAM(name)) == NULL)
687286196Sbapt		errx(EX_SOFTWARE, "group disappeared during update");
688286196Sbapt
689286196Sbapt	pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
690286196Sbapt	    (uintmax_t)grp->gr_gid);
691286196Sbapt
692286196Sbapt	if (nis && nis_update() == 0)
693286196Sbapt		pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
694286196Sbapt
695286196Sbapt	return (EXIT_SUCCESS);
696286196Sbapt}
697