pw_group.c revision 301367
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: head/usr.sbin/pw/pw_group.c 301367 2016-06-04 16:31:33Z bapt $";
3030259Scharnier#endif /* not lint */
3130259Scharnier
3220253Sjoerg#include <ctype.h>
3330259Scharnier#include <err.h>
34286196Sbapt#include <grp.h>
35286196Sbapt#include <libutil.h>
36286196Sbapt#include <paths.h>
37286201Sbapt#include <string.h>
38286201Sbapt#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);
46285411Sbaptstatic void	delete_members(struct group *grp, char *list);
47286196Sbaptstatic int	print_group(struct group * grp, bool pretty);
48286196Sbaptstatic gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
4920253Sjoerg
50285136Sbaptstatic void
51286196Sbaptgrp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
52285136Sbapt{
53285136Sbapt	int		 b;
54285136Sbapt	int		 istty;
55285136Sbapt	struct termios	 t, n;
56285136Sbapt	char		*p, line[256];
57285136Sbapt
58286196Sbapt	if (fd == -1)
59286196Sbapt		return;
60286196Sbapt
61286196Sbapt	if (fd == '-') {
62285136Sbapt		grp->gr_passwd = "*";	/* No access */
63285136Sbapt		return;
64285136Sbapt	}
65285136Sbapt
66286196Sbapt	if ((istty = isatty(fd))) {
67285136Sbapt		n = t;
68285136Sbapt		/* Disable echo */
69285136Sbapt		n.c_lflag &= ~(ECHO);
70286196Sbapt		tcsetattr(fd, TCSANOW, &n);
71285136Sbapt		printf("%sassword for group %s:", update ? "New p" : "P",
72285136Sbapt		    grp->gr_name);
73285136Sbapt		fflush(stdout);
74285136Sbapt	}
75286196Sbapt	b = read(fd, line, sizeof(line) - 1);
76285136Sbapt	if (istty) {	/* Restore state */
77286196Sbapt		tcsetattr(fd, TCSANOW, &t);
78285136Sbapt		fputc('\n', stdout);
79285136Sbapt		fflush(stdout);
80285136Sbapt	}
81285136Sbapt	if (b < 0)
82285136Sbapt		err(EX_OSERR, "-h file descriptor");
83285136Sbapt	line[b] = '\0';
84285136Sbapt	if ((p = strpbrk(line, " \t\r\n")) != NULL)
85285136Sbapt		*p = '\0';
86285136Sbapt	if (!*line)
87285136Sbapt		errx(EX_DATAERR, "empty password read on file descriptor %d",
88285136Sbapt		    conf.fd);
89286196Sbapt	if (precrypted) {
90285136Sbapt		if (strchr(line, ':') != 0)
91285136Sbapt			errx(EX_DATAERR, "wrong encrypted passwrd");
92285136Sbapt		grp->gr_passwd = line;
93285136Sbapt	} else
94285136Sbapt		grp->gr_passwd = pw_pwcrypt(line);
95285136Sbapt}
96285136Sbapt
9720253Sjoergint
98285395Sbaptpw_groupnext(struct userconf *cnf, bool quiet)
99285395Sbapt{
100285395Sbapt	gid_t next = gr_gidpolicy(cnf, -1);
101285395Sbapt
102285395Sbapt	if (quiet)
103285395Sbapt		return (next);
104286150Sbapt	printf("%ju\n", (uintmax_t)next);
105285395Sbapt
106285395Sbapt	return (EXIT_SUCCESS);
107285395Sbapt}
108285395Sbapt
109286196Sbaptstatic struct group *
110286196Sbaptgetgroup(char *name, intmax_t id, bool fatal)
111285398Sbapt{
112286196Sbapt	struct group *grp;
113285398Sbapt
114286196Sbapt	if (id < 0 && name == NULL)
115286196Sbapt		errx(EX_DATAERR, "groupname or id required");
116285398Sbapt	grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
117285398Sbapt	if (grp == NULL) {
118286196Sbapt		if (!fatal)
119286196Sbapt			return (NULL);
120285401Sbapt		if (name == NULL)
121286196Sbapt			errx(EX_DATAERR, "unknown gid `%ju'", id);
122285401Sbapt		errx(EX_DATAERR, "unknown group `%s'", name);
123285401Sbapt	}
124286196Sbapt	return (grp);
125285401Sbapt}
126285401Sbapt
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
148285411Sbaptdelete_members(struct group *grp, char *list)
149176474Sscf{
150285411Sbapt	char *p;
151176474Sscf	int k;
152176474Sscf
153262864Sjulian	if (grp->gr_mem == NULL)
154262864Sjulian		return;
155262864Sjulian
156285411Sbapt	for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
157285411Sbapt		for (k = 0; grp->gr_mem[k] != NULL; k++) {
158285411Sbapt			if (strcmp(grp->gr_mem[k], p) == 0)
159176474Sscf				break;
160176474Sscf		}
161285411Sbapt		if (grp->gr_mem[k] == NULL) /* No match */
162285411Sbapt			continue;
163176474Sscf
164285411Sbapt		for (; grp->gr_mem[k] != NULL; k++)
165285411Sbapt			grp->gr_mem[k] = grp->gr_mem[k+1];
166176474Sscf	}
167176474Sscf}
168176474Sscf
169286196Sbaptstatic gid_t
170286196Sbaptgr_gidpolicy(struct userconf * cnf, intmax_t id)
17120253Sjoerg{
17220253Sjoerg	struct group   *grp;
173286196Sbapt	struct bitmap   bm;
17420253Sjoerg	gid_t           gid = (gid_t) - 1;
17520253Sjoerg
17620253Sjoerg	/*
17720253Sjoerg	 * Check the given gid, if any
17820253Sjoerg	 */
179284133Sbapt	if (id > 0) {
180284133Sbapt		gid = (gid_t) id;
18120253Sjoerg
182284133Sbapt		if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
183286203Sbapt			errx(EX_DATAERR, "gid `%ju' has already been allocated",
184286203Sbapt			    (uintmax_t)grp->gr_gid);
185286196Sbapt		return (gid);
186286196Sbapt	}
18720253Sjoerg
188286196Sbapt	/*
189286196Sbapt	 * We need to allocate the next available gid under one of
190286196Sbapt	 * two policies a) Grab the first unused gid b) Grab the
191286196Sbapt	 * highest possible unused gid
192286196Sbapt	 */
193286196Sbapt	if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
194286196Sbapt		cnf->min_gid = 1000;
195286196Sbapt		cnf->max_gid = 32000;
196286196Sbapt	}
197286196Sbapt	bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
19820253Sjoerg
199286196Sbapt	/*
200286196Sbapt	 * Now, let's fill the bitmap from the password file
201286196Sbapt	 */
202286196Sbapt	SETGRENT();
203286196Sbapt	while ((grp = GETGRENT()) != NULL)
204286196Sbapt		if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
205286196Sbapt		    (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
206286196Sbapt			bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
207286196Sbapt	ENDGRENT();
20820253Sjoerg
209286196Sbapt	/*
210286196Sbapt	 * Then apply the policy, with fallback to reuse if necessary
211286196Sbapt	 */
212286196Sbapt	if (cnf->reuse_gids)
213286196Sbapt		gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
214286196Sbapt	else {
215286196Sbapt		gid = (gid_t) (bm_lastset(&bm) + 1);
216286196Sbapt		if (!bm_isset(&bm, gid))
217286196Sbapt			gid += cnf->min_gid;
218286196Sbapt		else
21920253Sjoerg			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
220286196Sbapt	}
22120253Sjoerg
222286196Sbapt	/*
223286196Sbapt	 * Another sanity check
224286196Sbapt	 */
225286196Sbapt	if (gid < cnf->min_gid || gid > cnf->max_gid)
226286203Sbapt		errx(EX_SOFTWARE, "unable to allocate a new gid - range fully "
227286203Sbapt		    "used");
228286196Sbapt	bm_dealloc(&bm);
229286196Sbapt	return (gid);
23020253Sjoerg}
23120253Sjoerg
23220253Sjoergstatic int
233286196Sbaptprint_group(struct group * grp, bool pretty)
23420253Sjoerg{
235286196Sbapt	char *buf = NULL;
236286196Sbapt	int i;
23720253Sjoerg
238286196Sbapt	if (pretty) {
23922398Sdavidn		printf("Group Name: %-15s   #%lu\n"
24020747Sdavidn		       "   Members: ",
24120253Sjoerg		       grp->gr_name, (long) grp->gr_gid);
242262864Sjulian		if (grp->gr_mem != NULL) {
243262864Sjulian			for (i = 0; grp->gr_mem[i]; i++)
244262864Sjulian				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
245262864Sjulian		}
24620253Sjoerg		fputs("\n\n", stdout);
247286196Sbapt		return (EXIT_SUCCESS);
24820253Sjoerg	}
249286196Sbapt
250286196Sbapt	buf = gr_make(grp);
251286196Sbapt	printf("%s\n", buf);
252286196Sbapt	free(buf);
253286196Sbapt	return (EXIT_SUCCESS);
25420253Sjoerg}
255286196Sbapt
256286196Sbaptint
257286196Sbaptpw_group_next(int argc, char **argv, char *arg1 __unused)
258286196Sbapt{
259286196Sbapt	struct userconf *cnf;
260286196Sbapt	const char *cfg = NULL;
261286196Sbapt	int ch;
262289600Sngie	bool quiet = false;
263286196Sbapt
264301367Sbapt	while ((ch = getopt(argc, argv, "C:q")) != -1) {
265286196Sbapt		switch (ch) {
266286196Sbapt		case 'C':
267286196Sbapt			cfg = optarg;
268286196Sbapt			break;
269286196Sbapt		case 'q':
270286196Sbapt			quiet = true;
271286196Sbapt			break;
272286196Sbapt		}
273286196Sbapt	}
274286196Sbapt
275286196Sbapt	if (quiet)
276286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
277286196Sbapt	cnf = get_userconfig(cfg);
278286196Sbapt	return (pw_groupnext(cnf, quiet));
279286196Sbapt}
280286196Sbapt
281286196Sbaptint
282286196Sbaptpw_group_show(int argc, char **argv, char *arg1)
283286196Sbapt{
284286196Sbapt	struct group *grp = NULL;
285286196Sbapt	char *name;
286286196Sbapt	intmax_t id = -1;
287286196Sbapt	int ch;
288286196Sbapt	bool all, force, quiet, pretty;
289286196Sbapt
290286196Sbapt	all = force = quiet = pretty = false;
291286196Sbapt
292286196Sbapt	struct group fakegroup = {
293286196Sbapt		"nogroup",
294286196Sbapt		"*",
295286196Sbapt		-1,
296286196Sbapt		NULL
297286196Sbapt	};
298286196Sbapt
299286196Sbapt	if (arg1 != NULL) {
300286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
301286196Sbapt			id = pw_checkid(arg1, GID_MAX);
302286196Sbapt		else
303286196Sbapt			name = arg1;
304286196Sbapt	}
305286196Sbapt
306286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
307286196Sbapt		switch (ch) {
308286196Sbapt		case 'C':
309286196Sbapt			/* ignore compatibility */
310286196Sbapt			break;
311286196Sbapt		case 'q':
312286196Sbapt			quiet = true;
313286196Sbapt			break;
314286196Sbapt		case 'n':
315286196Sbapt			name = optarg;
316286196Sbapt			break;
317286196Sbapt		case 'g':
318286196Sbapt			id = pw_checkid(optarg, GID_MAX);
319286196Sbapt			break;
320286196Sbapt		case 'F':
321286196Sbapt			force = true;
322286196Sbapt			break;
323286196Sbapt		case 'P':
324286196Sbapt			pretty = true;
325286196Sbapt			break;
326286196Sbapt		case 'a':
327286196Sbapt			all = true;
328286196Sbapt			break;
329286196Sbapt		}
330286196Sbapt	}
331286196Sbapt
332286196Sbapt	if (quiet)
333286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
334286196Sbapt
335286196Sbapt	if (all) {
336286196Sbapt		SETGRENT();
337286196Sbapt		while ((grp = GETGRENT()) != NULL)
338286196Sbapt			print_group(grp, pretty);
339286196Sbapt		ENDGRENT();
340286196Sbapt		return (EXIT_SUCCESS);
341286196Sbapt	}
342286196Sbapt
343286196Sbapt	grp = getgroup(name, id, !force);
344286196Sbapt	if (grp == NULL)
345286196Sbapt		grp = &fakegroup;
346286196Sbapt
347286196Sbapt	return (print_group(grp, pretty));
348286196Sbapt}
349286196Sbapt
350286196Sbaptint
351286196Sbaptpw_group_del(int argc, char **argv, char *arg1)
352286196Sbapt{
353286196Sbapt	struct userconf *cnf = NULL;
354286196Sbapt	struct group *grp = NULL;
355286196Sbapt	char *name;
356286196Sbapt	const char *cfg = NULL;
357286196Sbapt	intmax_t id = -1;
358286196Sbapt	int ch, rc;
359286196Sbapt	bool quiet = false;
360286196Sbapt	bool nis = false;
361286196Sbapt
362286196Sbapt	if (arg1 != NULL) {
363286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
364286196Sbapt			id = pw_checkid(arg1, GID_MAX);
365286196Sbapt		else
366286196Sbapt			name = arg1;
367286196Sbapt	}
368286196Sbapt
369286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
370286196Sbapt		switch (ch) {
371286196Sbapt		case 'C':
372286196Sbapt			cfg = optarg;
373286196Sbapt			break;
374286196Sbapt		case 'q':
375286196Sbapt			quiet = true;
376286196Sbapt			break;
377286196Sbapt		case 'n':
378286196Sbapt			name = optarg;
379286196Sbapt			break;
380286196Sbapt		case 'g':
381286196Sbapt			id = pw_checkid(optarg, GID_MAX);
382286196Sbapt			break;
383286196Sbapt		case 'Y':
384286196Sbapt			nis = true;
385286196Sbapt			break;
386286196Sbapt		}
387286196Sbapt	}
388286196Sbapt
389286196Sbapt	if (quiet)
390286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
391286196Sbapt	grp = getgroup(name, id, true);
392286196Sbapt	cnf = get_userconfig(cfg);
393286196Sbapt	rc = delgrent(grp);
394286196Sbapt	if (rc == -1)
395286196Sbapt		err(EX_IOERR, "group '%s' not available (NIS?)", name);
396286196Sbapt	else if (rc != 0)
397286196Sbapt		err(EX_IOERR, "group update");
398286196Sbapt	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
399286196Sbapt	    (uintmax_t)id);
400286196Sbapt
401286196Sbapt	if (nis && nis_update() == 0)
402286196Sbapt		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
403286196Sbapt
404286196Sbapt	return (EXIT_SUCCESS);
405286196Sbapt}
406286196Sbapt
407286196Sbaptstatic bool
408286196Sbaptgrp_has_member(struct group *grp, const char *name)
409286196Sbapt{
410286196Sbapt	int j;
411286196Sbapt
412286196Sbapt	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
413286196Sbapt		if (strcmp(grp->gr_mem[j], name) == 0)
414286196Sbapt			return (true);
415286196Sbapt	return (false);
416286196Sbapt}
417286196Sbapt
418286196Sbaptstatic void
419286196Sbaptgrp_add_members(struct group **grp, char *members)
420286196Sbapt{
421286196Sbapt	struct passwd *pwd;
422286196Sbapt	char *p;
423286196Sbapt	char tok[] = ", \t";
424286196Sbapt
425286196Sbapt	if (members == NULL)
426286196Sbapt		return;
427286196Sbapt	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
428286196Sbapt		pwd = lookup_pwent(p);
429286196Sbapt		if (grp_has_member(*grp, pwd->pw_name))
430286196Sbapt			continue;
431286196Sbapt		*grp = gr_add(*grp, pwd->pw_name);
432286196Sbapt	}
433286196Sbapt}
434286196Sbapt
435286196Sbaptint
436286196Sbaptgroupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
437286196Sbapt    bool dryrun, bool pretty, bool precrypted)
438286196Sbapt{
439286196Sbapt	struct group *grp;
440286196Sbapt	int rc;
441286196Sbapt
442286196Sbapt	struct group fakegroup = {
443286196Sbapt		"nogroup",
444286196Sbapt		"*",
445286196Sbapt		-1,
446286196Sbapt		NULL
447286196Sbapt	};
448286196Sbapt
449286196Sbapt	grp = &fakegroup;
450286196Sbapt	grp->gr_name = pw_checkname(name, 0);
451286196Sbapt	grp->gr_passwd = "*";
452286196Sbapt	grp->gr_gid = gr_gidpolicy(cnf, id);
453286196Sbapt	grp->gr_mem = NULL;
454286196Sbapt
455286196Sbapt	/*
456286196Sbapt	 * This allows us to set a group password Group passwords is an
457286196Sbapt	 * antique idea, rarely used and insecure (no secure database) Should
458286196Sbapt	 * be discouraged, but it is apparently still supported by some
459286196Sbapt	 * software.
460286196Sbapt	 */
461286196Sbapt	grp_set_passwd(grp, false, fd, precrypted);
462286196Sbapt	grp_add_members(&grp, members);
463286196Sbapt	if (dryrun)
464286196Sbapt		return (print_group(grp, pretty));
465286196Sbapt
466286196Sbapt	if ((rc = addgrent(grp)) != 0) {
467286196Sbapt		if (rc == -1)
468286196Sbapt			errx(EX_IOERR, "group '%s' already exists",
469286196Sbapt			    grp->gr_name);
470286196Sbapt		else
471286196Sbapt			err(EX_IOERR, "group update");
472286196Sbapt	}
473286196Sbapt
474286196Sbapt	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
475286196Sbapt	    (uintmax_t)grp->gr_gid);
476286196Sbapt
477286196Sbapt	return (EXIT_SUCCESS);
478286196Sbapt}
479286196Sbapt
480286196Sbaptint
481286196Sbaptpw_group_add(int argc, char **argv, char *arg1)
482286196Sbapt{
483286196Sbapt	struct userconf *cnf = NULL;
484286196Sbapt	char *name = NULL;
485286196Sbapt	char *members = NULL;
486286196Sbapt	const char *cfg = NULL;
487286196Sbapt	intmax_t id = -1;
488286196Sbapt	int ch, rc, fd = -1;
489286196Sbapt	bool quiet, precrypted, dryrun, pretty, nis;
490286196Sbapt
491286196Sbapt	quiet = precrypted = dryrun = pretty = nis = false;
492286196Sbapt
493286196Sbapt	if (arg1 != NULL) {
494286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
495286196Sbapt			id = pw_checkid(arg1, GID_MAX);
496286196Sbapt		else
497286196Sbapt			name = arg1;
498286196Sbapt	}
499286196Sbapt
500286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
501286196Sbapt		switch (ch) {
502286196Sbapt		case 'C':
503286196Sbapt			cfg = optarg;
504286196Sbapt			break;
505286196Sbapt		case 'q':
506286196Sbapt			quiet = true;
507286196Sbapt			break;
508286196Sbapt		case 'n':
509286196Sbapt			name = optarg;
510286196Sbapt			break;
511286196Sbapt		case 'g':
512286196Sbapt			id = pw_checkid(optarg, GID_MAX);
513286196Sbapt			break;
514286196Sbapt		case 'H':
515286196Sbapt			if (fd != -1)
516286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
517286196Sbapt				    "exclusive options");
518286196Sbapt			fd = pw_checkfd(optarg);
519286196Sbapt			precrypted = true;
520286196Sbapt			if (fd == '-')
521286196Sbapt				errx(EX_USAGE, "-H expects a file descriptor");
522286196Sbapt			break;
523286196Sbapt		case 'h':
524286196Sbapt			if (fd != -1)
525286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
526286196Sbapt				    "exclusive options");
527286196Sbapt			fd = pw_checkfd(optarg);
528286196Sbapt			break;
529286196Sbapt		case 'M':
530286196Sbapt			members = optarg;
531286196Sbapt			break;
532286196Sbapt		case 'o':
533286196Sbapt			conf.checkduplicate = false;
534286196Sbapt			break;
535286196Sbapt		case 'N':
536286196Sbapt			dryrun = true;
537286196Sbapt			break;
538286196Sbapt		case 'P':
539286196Sbapt			pretty = true;
540286196Sbapt			break;
541286196Sbapt		case 'Y':
542286196Sbapt			nis = true;
543286196Sbapt			break;
544286196Sbapt		}
545286196Sbapt	}
546286196Sbapt
547286196Sbapt	if (quiet)
548286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
549286196Sbapt	if (name == NULL)
550286196Sbapt		errx(EX_DATAERR, "group name required");
551286199Sbapt	if (GETGRNAM(name) != NULL)
552286199Sbapt		errx(EX_DATAERR, "group name `%s' already exists", name);
553286196Sbapt	cnf = get_userconfig(cfg);
554286196Sbapt	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
555286196Sbapt	    pretty, precrypted);
556286196Sbapt	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
557286196Sbapt		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
558286196Sbapt
559286196Sbapt	return (rc);
560286196Sbapt}
561286196Sbapt
562286196Sbaptint
563286196Sbaptpw_group_mod(int argc, char **argv, char *arg1)
564286196Sbapt{
565286196Sbapt	struct userconf *cnf;
566286196Sbapt	struct group *grp = NULL;
567286196Sbapt	const char *cfg = NULL;
568286196Sbapt	char *oldmembers = NULL;
569286196Sbapt	char *members = NULL;
570286196Sbapt	char *newmembers = NULL;
571286196Sbapt	char *newname = NULL;
572286196Sbapt	char *name = NULL;
573286196Sbapt	intmax_t id = -1;
574286196Sbapt	int ch, rc, fd = -1;
575286196Sbapt	bool quiet, pretty, dryrun, nis, precrypted;
576286196Sbapt
577286196Sbapt	quiet = pretty = dryrun = nis = precrypted = false;
578286196Sbapt
579286196Sbapt	if (arg1 != NULL) {
580286259Sed		if (arg1[strspn(arg1, "0123456789")] == '\0')
581286196Sbapt			id = pw_checkid(arg1, GID_MAX);
582286196Sbapt		else
583286196Sbapt			name = arg1;
584286196Sbapt	}
585286196Sbapt
586286196Sbapt	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
587286196Sbapt		switch (ch) {
588286196Sbapt		case 'C':
589286196Sbapt			cfg = optarg;
590286196Sbapt			break;
591286196Sbapt		case 'q':
592286196Sbapt			quiet = true;
593286196Sbapt			break;
594286196Sbapt		case 'n':
595286196Sbapt			name = optarg;
596286196Sbapt			break;
597286196Sbapt		case 'g':
598286196Sbapt			id = pw_checkid(optarg, GID_MAX);
599286196Sbapt			break;
600286196Sbapt		case 'd':
601286196Sbapt			oldmembers = optarg;
602286196Sbapt			break;
603286196Sbapt		case 'l':
604286196Sbapt			newname = optarg;
605286196Sbapt			break;
606286196Sbapt		case 'H':
607286196Sbapt			if (fd != -1)
608286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
609286196Sbapt				    "exclusive options");
610286196Sbapt			fd = pw_checkfd(optarg);
611286196Sbapt			precrypted = true;
612286196Sbapt			if (fd == '-')
613286196Sbapt				errx(EX_USAGE, "-H expects a file descriptor");
614286196Sbapt			break;
615286196Sbapt		case 'h':
616286196Sbapt			if (fd != -1)
617286196Sbapt				errx(EX_USAGE, "'-h' and '-H' are mutually "
618286196Sbapt				    "exclusive options");
619286196Sbapt			fd = pw_checkfd(optarg);
620286196Sbapt			break;
621286196Sbapt		case 'M':
622286196Sbapt			members = optarg;
623286196Sbapt			break;
624286196Sbapt		case 'm':
625286196Sbapt			newmembers = optarg;
626286196Sbapt			break;
627286196Sbapt		case 'N':
628286196Sbapt			dryrun = true;
629286196Sbapt			break;
630286196Sbapt		case 'P':
631286196Sbapt			pretty = true;
632286196Sbapt			break;
633286196Sbapt		case 'Y':
634286196Sbapt			nis = true;
635286196Sbapt			break;
636286196Sbapt		}
637286196Sbapt	}
638286196Sbapt	if (quiet)
639286196Sbapt		freopen(_PATH_DEVNULL, "w", stderr);
640286196Sbapt	cnf = get_userconfig(cfg);
641286196Sbapt	grp = getgroup(name, id, true);
642286196Sbapt	if (name == NULL)
643286196Sbapt		name = grp->gr_name;
644286196Sbapt	if (id > 0)
645286196Sbapt		grp->gr_gid = id;
646286196Sbapt
647286196Sbapt	if (newname != NULL)
648286196Sbapt		grp->gr_name = pw_checkname(newname, 0);
649286196Sbapt
650286196Sbapt	grp_set_passwd(grp, true, fd, precrypted);
651286196Sbapt	/*
652286196Sbapt	 * Keep the same logic as old code for now:
653286196Sbapt	 * if -M is passed, -d and -m are ignored
654286196Sbapt	 * then id -d, -m is ignored
655286196Sbapt	 * last is -m
656286196Sbapt	 */
657286196Sbapt
658286196Sbapt	if (members) {
659286196Sbapt		grp->gr_mem = NULL;
660286196Sbapt		grp_add_members(&grp, members);
661286196Sbapt	} else if (oldmembers) {
662286196Sbapt		delete_members(grp, oldmembers);
663286196Sbapt	} else if (newmembers) {
664286196Sbapt		grp_add_members(&grp, newmembers);
665286196Sbapt	}
666286196Sbapt
667292846Sbapt	if (dryrun) {
668292846Sbapt		print_group(grp, pretty);
669292846Sbapt		return (EXIT_SUCCESS);
670292846Sbapt	}
671292846Sbapt
672286196Sbapt	if ((rc = chggrent(name, grp)) != 0) {
673286196Sbapt		if (rc == -1)
674286196Sbapt			errx(EX_IOERR, "group '%s' not available (NIS?)",
675286196Sbapt			    grp->gr_name);
676286196Sbapt		else
677286196Sbapt			err(EX_IOERR, "group update");
678286196Sbapt	}
679286196Sbapt
680286196Sbapt	if (newname)
681286196Sbapt		name = newname;
682286196Sbapt
683286196Sbapt	/* grp may have been invalidated */
684286196Sbapt	if ((grp = GETGRNAM(name)) == NULL)
685286196Sbapt		errx(EX_SOFTWARE, "group disappeared during update");
686286196Sbapt
687286196Sbapt	pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
688286196Sbapt	    (uintmax_t)grp->gr_gid);
689286196Sbapt
690286196Sbapt	if (nis && nis_update() == 0)
691286196Sbapt		pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
692286196Sbapt
693286196Sbapt	return (EXIT_SUCCESS);
694286196Sbapt}
695