pw_group.c revision 286196
1270631Sjfv/*-
2270631Sjfv * Copyright (C) 1996
3292095Ssmh *	David L. Nugent.  All rights reserved.
4270631Sjfv *
5270631Sjfv * Redistribution and use in source and binary forms, with or without
6270631Sjfv * modification, are permitted provided that the following conditions
7270631Sjfv * are met:
8270631Sjfv * 1. Redistributions of source code must retain the above copyright
9270631Sjfv *    notice, this list of conditions and the following disclaimer.
10270631Sjfv * 2. Redistributions in binary form must reproduce the above copyright
11270631Sjfv *    notice, this list of conditions and the following disclaimer in the
12270631Sjfv *    documentation and/or other materials provided with the distribution.
13270631Sjfv *
14270631Sjfv * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15270631Sjfv * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16270631Sjfv * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17270631Sjfv * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18270631Sjfv * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19270631Sjfv * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20270631Sjfv * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21270631Sjfv * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22270631Sjfv * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23270631Sjfv * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24270631Sjfv * SUCH DAMAGE.
25270631Sjfv */
26270631Sjfv
27270631Sjfv#ifndef lint
28270631Sjfvstatic const char rcsid[] =
29270631Sjfv  "$FreeBSD: head/usr.sbin/pw/pw_group.c 286196 2015-08-02 12:47:50Z bapt $";
30270631Sjfv#endif /* not lint */
31270631Sjfv
32270631Sjfv#include <ctype.h>
33270631Sjfv#include <err.h>
34270631Sjfv#include <grp.h>
35270631Sjfv#include <inttypes.h>
36270631Sjfv#include <libutil.h>
37270631Sjfv#include <paths.h>
38270631Sjfv#include <stdbool.h>
39270631Sjfv#include <termios.h>
40270631Sjfv#include <unistd.h>
41270631Sjfv
42270631Sjfv#include "pw.h"
43270631Sjfv#include "bitmap.h"
44270631Sjfv
45270631Sjfvstatic struct passwd *lookup_pwent(const char *user);
46270631Sjfvstatic void	delete_members(struct group *grp, char *list);
47270631Sjfvstatic int	print_group(struct group * grp, bool pretty);
48270631Sjfvstatic gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
49270631Sjfv
50270919Sjfvstatic void
51270631Sjfvgrp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
52270631Sjfv{
53270919Sjfv	int		 b;
54270631Sjfv	int		 istty;
55270631Sjfv	struct termios	 t, n;
56270631Sjfv	char		*p, line[256];
57270631Sjfv
58270631Sjfv	if (fd == -1)
59270631Sjfv		return;
60270631Sjfv
61270631Sjfv	if (fd == '-') {
62270631Sjfv		grp->gr_passwd = "*";	/* No access */
63270631Sjfv		return;
64270631Sjfv	}
65270631Sjfv
66270631Sjfv	if ((istty = isatty(fd))) {
67270631Sjfv		n = t;
68270631Sjfv		/* Disable echo */
69270631Sjfv		n.c_lflag &= ~(ECHO);
70270631Sjfv		tcsetattr(fd, TCSANOW, &n);
71270631Sjfv		printf("%sassword for group %s:", update ? "New p" : "P",
72270631Sjfv		    grp->gr_name);
73270631Sjfv		fflush(stdout);
74270631Sjfv	}
75270631Sjfv	b = read(fd, line, sizeof(line) - 1);
76270631Sjfv	if (istty) {	/* Restore state */
77270631Sjfv		tcsetattr(fd, TCSANOW, &t);
78270631Sjfv		fputc('\n', stdout);
79270631Sjfv		fflush(stdout);
80270631Sjfv	}
81270631Sjfv	if (b < 0)
82270631Sjfv		err(EX_OSERR, "-h file descriptor");
83270631Sjfv	line[b] = '\0';
84270631Sjfv	if ((p = strpbrk(line, " \t\r\n")) != NULL)
85270631Sjfv		*p = '\0';
86270631Sjfv	if (!*line)
87270631Sjfv		errx(EX_DATAERR, "empty password read on file descriptor %d",
88270631Sjfv		    conf.fd);
89270631Sjfv	if (precrypted) {
90270631Sjfv		if (strchr(line, ':') != 0)
91270631Sjfv			errx(EX_DATAERR, "wrong encrypted passwrd");
92270631Sjfv		grp->gr_passwd = line;
93292097Ssmh	} else
94292097Ssmh		grp->gr_passwd = pw_pwcrypt(line);
95292097Ssmh}
96292097Ssmh
97292097Ssmhint
98270631Sjfvpw_groupnext(struct userconf *cnf, bool quiet)
99270631Sjfv{
100270631Sjfv	gid_t next = gr_gidpolicy(cnf, -1);
101292094Ssmh
102270631Sjfv	if (quiet)
103270631Sjfv		return (next);
104270631Sjfv	printf("%ju\n", (uintmax_t)next);
105270631Sjfv
106270631Sjfv	return (EXIT_SUCCESS);
107270631Sjfv}
108270631Sjfv
109292094Ssmhstatic struct group *
110270631Sjfvgetgroup(char *name, intmax_t id, bool fatal)
111292094Ssmh{
112292094Ssmh	struct group *grp;
113292094Ssmh
114292094Ssmh	if (id < 0 && name == NULL)
115292094Ssmh		errx(EX_DATAERR, "groupname or id required");
116270631Sjfv	grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
117270631Sjfv	if (grp == NULL) {
118270631Sjfv		if (!fatal)
119270631Sjfv			return (NULL);
120270631Sjfv		if (name == NULL)
121270631Sjfv			errx(EX_DATAERR, "unknown gid `%ju'", id);
122270631Sjfv		errx(EX_DATAERR, "unknown group `%s'", name);
123270631Sjfv	}
124270631Sjfv	return (grp);
125270631Sjfv}
126270631Sjfv
127270631Sjfv/*
128270631Sjfv * Lookup a passwd entry using a name or UID.
129270631Sjfv */
130270631Sjfvstatic struct passwd *
131270631Sjfvlookup_pwent(const char *user)
132270631Sjfv{
133270631Sjfv	struct passwd *pwd;
134270631Sjfv
135270631Sjfv	if ((pwd = GETPWNAM(user)) == NULL &&
136270631Sjfv	    (!isdigit((unsigned char)*user) ||
137270631Sjfv	    (pwd = getpwuid((uid_t) atoi(user))) == NULL))
138270631Sjfv		errx(EX_NOUSER, "user `%s' does not exist", user);
139270631Sjfv
140270631Sjfv	return (pwd);
141270631Sjfv}
142292094Ssmh
143270631Sjfv
144270631Sjfv/*
145270631Sjfv * Delete requested members from a group.
146270631Sjfv */
147270631Sjfvstatic void
148270631Sjfvdelete_members(struct group *grp, char *list)
149270631Sjfv{
150270631Sjfv	char *p;
151270631Sjfv	int k;
152270631Sjfv
153270631Sjfv	if (grp->gr_mem == NULL)
154270631Sjfv		return;
155270631Sjfv
156270631Sjfv	for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
157270631Sjfv		for (k = 0; grp->gr_mem[k] != NULL; k++) {
158292094Ssmh			if (strcmp(grp->gr_mem[k], p) == 0)
159270631Sjfv				break;
160270631Sjfv		}
161270631Sjfv		if (grp->gr_mem[k] == NULL) /* No match */
162270631Sjfv			continue;
163270631Sjfv
164270631Sjfv		for (; grp->gr_mem[k] != NULL; k++)
165270631Sjfv			grp->gr_mem[k] = grp->gr_mem[k+1];
166270631Sjfv	}
167270631Sjfv}
168270631Sjfv
169270631Sjfvstatic gid_t
170270631Sjfvgr_gidpolicy(struct userconf * cnf, intmax_t id)
171270631Sjfv{
172270631Sjfv	struct group   *grp;
173270631Sjfv	struct bitmap   bm;
174270631Sjfv	gid_t           gid = (gid_t) - 1;
175270631Sjfv
176274360Sjfv	/*
177274360Sjfv	 * Check the given gid, if any
178274360Sjfv	 */
179270631Sjfv	if (id > 0) {
180270631Sjfv		gid = (gid_t) id;
181270631Sjfv
182270631Sjfv		if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
183270631Sjfv			errx(EX_DATAERR, "gid `%ju' has already been allocated", (uintmax_t)grp->gr_gid);
184270631Sjfv		return (gid);
185270631Sjfv	}
186270631Sjfv
187270631Sjfv	/*
188270631Sjfv	 * We need to allocate the next available gid under one of
189270631Sjfv	 * two policies a) Grab the first unused gid b) Grab the
190270631Sjfv	 * highest possible unused gid
191270631Sjfv	 */
192270631Sjfv	if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
193270631Sjfv		cnf->min_gid = 1000;
194270631Sjfv		cnf->max_gid = 32000;
195270631Sjfv	}
196270631Sjfv	bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
197270631Sjfv
198270631Sjfv	/*
199270631Sjfv	 * Now, let's fill the bitmap from the password file
200270631Sjfv	 */
201270631Sjfv	SETGRENT();
202270631Sjfv	while ((grp = GETGRENT()) != NULL)
203270631Sjfv		if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
204270631Sjfv		    (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
205270631Sjfv			bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
206270631Sjfv	ENDGRENT();
207270631Sjfv
208270631Sjfv	/*
209270631Sjfv	 * Then apply the policy, with fallback to reuse if necessary
210274360Sjfv	 */
211270631Sjfv	if (cnf->reuse_gids)
212270631Sjfv		gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
213270631Sjfv	else {
214270631Sjfv		gid = (gid_t) (bm_lastset(&bm) + 1);
215270631Sjfv		if (!bm_isset(&bm, gid))
216292095Ssmh			gid += cnf->min_gid;
217270631Sjfv		else
218292095Ssmh			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
219270631Sjfv	}
220270631Sjfv
221270631Sjfv	/*
222270631Sjfv	 * Another sanity check
223270631Sjfv	 */
224270631Sjfv	if (gid < cnf->min_gid || gid > cnf->max_gid)
225270631Sjfv		errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used");
226274360Sjfv	bm_dealloc(&bm);
227270631Sjfv	return (gid);
228270631Sjfv}
229270631Sjfv
230292094Ssmhstatic int
231270631Sjfvprint_group(struct group * grp, bool pretty)
232292097Ssmh{
233292097Ssmh	char *buf = NULL;
234292097Ssmh	int i;
235292097Ssmh
236270631Sjfv	if (pretty) {
237270631Sjfv		printf("Group Name: %-15s   #%lu\n"
238270631Sjfv		       "   Members: ",
239270631Sjfv		       grp->gr_name, (long) grp->gr_gid);
240270631Sjfv		if (grp->gr_mem != NULL) {
241270631Sjfv			for (i = 0; grp->gr_mem[i]; i++)
242270631Sjfv				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
243270631Sjfv		}
244270631Sjfv		fputs("\n\n", stdout);
245270631Sjfv		return (EXIT_SUCCESS);
246270631Sjfv	}
247270631Sjfv
248270631Sjfv	buf = gr_make(grp);
249270631Sjfv	printf("%s\n", buf);
250270631Sjfv	free(buf);
251270631Sjfv	return (EXIT_SUCCESS);
252270631Sjfv}
253270631Sjfv
254270631Sjfvint
255270631Sjfvpw_group_next(int argc, char **argv, char *arg1 __unused)
256270631Sjfv{
257270631Sjfv	struct userconf *cnf;
258270631Sjfv	const char *cfg = NULL;
259270631Sjfv	int ch;
260270631Sjfv	bool quiet;
261270631Sjfv
262270631Sjfv	while ((ch = getopt(argc, argv, "Cq")) != -1) {
263270631Sjfv		switch (ch) {
264270631Sjfv		case 'C':
265270631Sjfv			cfg = optarg;
266270631Sjfv			break;
267270631Sjfv		case 'q':
268270631Sjfv			quiet = true;
269270631Sjfv			break;
270270631Sjfv		}
271270631Sjfv	}
272270631Sjfv
273270631Sjfv	if (quiet)
274270631Sjfv		freopen(_PATH_DEVNULL, "w", stderr);
275270631Sjfv	cnf = get_userconfig(cfg);
276270631Sjfv	return (pw_groupnext(cnf, quiet));
277292097Ssmh}
278292097Ssmh
279292097Ssmhint
280292097Ssmhpw_group_show(int argc, char **argv, char *arg1)
281292097Ssmh{
282292097Ssmh	struct group *grp = NULL;
283292097Ssmh	char *name;
284292097Ssmh	intmax_t id = -1;
285292097Ssmh	int ch;
286292097Ssmh	bool all, force, quiet, pretty;
287292097Ssmh
288292097Ssmh	all = force = quiet = pretty = false;
289292097Ssmh
290292097Ssmh	struct group fakegroup = {
291292097Ssmh		"nogroup",
292292097Ssmh		"*",
293292097Ssmh		-1,
294292097Ssmh		NULL
295292097Ssmh	};
296292097Ssmh
297292097Ssmh	if (arg1 != NULL) {
298292097Ssmh		if (strspn(arg1, "0123456789") == strlen(arg1))
299292097Ssmh			id = pw_checkid(arg1, GID_MAX);
300292097Ssmh		else
301292097Ssmh			name = arg1;
302292097Ssmh	}
303292097Ssmh
304292097Ssmh	while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
305292097Ssmh		switch (ch) {
306270631Sjfv		case 'C':
307270631Sjfv			/* ignore compatibility */
308270631Sjfv			break;
309270631Sjfv		case 'q':
310270631Sjfv			quiet = true;
311270631Sjfv			break;
312270631Sjfv		case 'n':
313270631Sjfv			name = optarg;
314270631Sjfv			break;
315270631Sjfv		case 'g':
316292095Ssmh			id = pw_checkid(optarg, GID_MAX);
317274360Sjfv			break;
318274360Sjfv		case 'F':
319274360Sjfv			force = true;
320274360Sjfv			break;
321274360Sjfv		case 'P':
322274360Sjfv			pretty = true;
323274360Sjfv			break;
324274360Sjfv		case 'a':
325274360Sjfv			all = true;
326274360Sjfv			break;
327274360Sjfv		}
328274360Sjfv	}
329274360Sjfv
330274360Sjfv	if (quiet)
331274360Sjfv		freopen(_PATH_DEVNULL, "w", stderr);
332274360Sjfv
333274360Sjfv	if (all) {
334274360Sjfv		SETGRENT();
335274360Sjfv		while ((grp = GETGRENT()) != NULL)
336274360Sjfv			print_group(grp, pretty);
337274360Sjfv		ENDGRENT();
338274360Sjfv		return (EXIT_SUCCESS);
339274360Sjfv	}
340274360Sjfv
341274360Sjfv	grp = getgroup(name, id, !force);
342274360Sjfv	if (grp == NULL)
343274360Sjfv		grp = &fakegroup;
344270631Sjfv
345270631Sjfv	return (print_group(grp, pretty));
346270631Sjfv}
347270631Sjfv
348270631Sjfvint
349270631Sjfvpw_group_del(int argc, char **argv, char *arg1)
350270631Sjfv{
351270631Sjfv	struct userconf *cnf = NULL;
352270631Sjfv	struct group *grp = NULL;
353270631Sjfv	char *name;
354270631Sjfv	const char *cfg = NULL;
355270631Sjfv	intmax_t id = -1;
356270631Sjfv	int ch, rc;
357270631Sjfv	bool quiet = false;
358270631Sjfv	bool nis = false;
359270631Sjfv
360270631Sjfv	if (arg1 != NULL) {
361270631Sjfv		if (strspn(arg1, "0123456789") == strlen(arg1))
362270631Sjfv			id = pw_checkid(arg1, GID_MAX);
363270631Sjfv		else
364270631Sjfv			name = arg1;
365270631Sjfv	}
366270631Sjfv
367270631Sjfv	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
368270631Sjfv		switch (ch) {
369270631Sjfv		case 'C':
370270631Sjfv			cfg = optarg;
371270631Sjfv			break;
372270631Sjfv		case 'q':
373270631Sjfv			quiet = true;
374270631Sjfv			break;
375270631Sjfv		case 'n':
376270631Sjfv			name = optarg;
377270631Sjfv			break;
378270631Sjfv		case 'g':
379270631Sjfv			id = pw_checkid(optarg, GID_MAX);
380270631Sjfv			break;
381270631Sjfv		case 'Y':
382270631Sjfv			nis = true;
383270631Sjfv			break;
384270631Sjfv		}
385270631Sjfv	}
386270631Sjfv
387270631Sjfv	if (quiet)
388270631Sjfv		freopen(_PATH_DEVNULL, "w", stderr);
389270631Sjfv	grp = getgroup(name, id, true);
390270631Sjfv	cnf = get_userconfig(cfg);
391270631Sjfv	rc = delgrent(grp);
392270631Sjfv	if (rc == -1)
393270631Sjfv		err(EX_IOERR, "group '%s' not available (NIS?)", name);
394270631Sjfv	else if (rc != 0)
395270631Sjfv		err(EX_IOERR, "group update");
396270631Sjfv	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
397270631Sjfv	    (uintmax_t)id);
398270631Sjfv
399270631Sjfv	if (nis && nis_update() == 0)
400270631Sjfv		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
401270631Sjfv
402270631Sjfv	return (EXIT_SUCCESS);
403270631Sjfv}
404270631Sjfv
405270631Sjfvstatic bool
406270631Sjfvgrp_has_member(struct group *grp, const char *name)
407270631Sjfv{
408270631Sjfv	int j;
409270631Sjfv
410270631Sjfv	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
411270631Sjfv		if (strcmp(grp->gr_mem[j], name) == 0)
412270631Sjfv			return (true);
413270631Sjfv	return (false);
414270631Sjfv}
415270631Sjfv
416270631Sjfvstatic void
417270631Sjfvgrp_add_members(struct group **grp, char *members)
418270631Sjfv{
419270631Sjfv	struct passwd *pwd;
420270631Sjfv	char *p;
421270631Sjfv	char tok[] = ", \t";
422270631Sjfv
423270631Sjfv	if (members == NULL)
424270631Sjfv		return;
425270631Sjfv	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
426270631Sjfv		pwd = lookup_pwent(p);
427270631Sjfv		if (grp_has_member(*grp, pwd->pw_name))
428270631Sjfv			continue;
429270631Sjfv		*grp = gr_add(*grp, pwd->pw_name);
430270631Sjfv	}
431270631Sjfv}
432270631Sjfv
433270631Sjfvint
434270631Sjfvgroupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
435270631Sjfv    bool dryrun, bool pretty, bool precrypted)
436270631Sjfv{
437270631Sjfv	struct group *grp;
438270631Sjfv	int rc;
439270631Sjfv
440270631Sjfv	struct group fakegroup = {
441270631Sjfv		"nogroup",
442270631Sjfv		"*",
443270631Sjfv		-1,
444270631Sjfv		NULL
445270631Sjfv	};
446270631Sjfv
447270631Sjfv	grp = &fakegroup;
448270631Sjfv	grp->gr_name = pw_checkname(name, 0);
449270631Sjfv	grp->gr_passwd = "*";
450270631Sjfv	grp->gr_gid = gr_gidpolicy(cnf, id);
451270631Sjfv	grp->gr_mem = NULL;
452270631Sjfv
453270631Sjfv	/*
454270631Sjfv	 * This allows us to set a group password Group passwords is an
455270631Sjfv	 * antique idea, rarely used and insecure (no secure database) Should
456270631Sjfv	 * be discouraged, but it is apparently still supported by some
457270631Sjfv	 * software.
458270631Sjfv	 */
459270631Sjfv	grp_set_passwd(grp, false, fd, precrypted);
460270631Sjfv	grp_add_members(&grp, members);
461270631Sjfv	if (dryrun)
462270631Sjfv		return (print_group(grp, pretty));
463270631Sjfv
464270631Sjfv	if ((rc = addgrent(grp)) != 0) {
465270631Sjfv		if (rc == -1)
466270631Sjfv			errx(EX_IOERR, "group '%s' already exists",
467270631Sjfv			    grp->gr_name);
468270631Sjfv		else
469270631Sjfv			err(EX_IOERR, "group update");
470270631Sjfv	}
471270631Sjfv
472270631Sjfv	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
473270631Sjfv	    (uintmax_t)grp->gr_gid);
474270631Sjfv
475270631Sjfv	return (EXIT_SUCCESS);
476270631Sjfv}
477270631Sjfv
478270631Sjfvint
479270631Sjfvpw_group_add(int argc, char **argv, char *arg1)
480270631Sjfv{
481270631Sjfv	struct userconf *cnf = NULL;
482270631Sjfv	char *name = NULL;
483270631Sjfv	char *members = NULL;
484270631Sjfv	const char *cfg = NULL;
485270631Sjfv	intmax_t id = -1;
486270631Sjfv	int ch, rc, fd = -1;
487270631Sjfv	bool quiet, precrypted, dryrun, pretty, nis;
488270631Sjfv
489270631Sjfv	quiet = precrypted = dryrun = pretty = nis = false;
490270631Sjfv
491270631Sjfv	if (arg1 != NULL) {
492270631Sjfv		if (strspn(arg1, "0123456789") == strlen(arg1))
493270631Sjfv			id = pw_checkid(arg1, GID_MAX);
494270631Sjfv		else
495270631Sjfv			name = arg1;
496270631Sjfv	}
497270631Sjfv
498270631Sjfv	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
499270631Sjfv		switch (ch) {
500270631Sjfv		case 'C':
501270631Sjfv			cfg = optarg;
502292097Ssmh			break;
503270631Sjfv		case 'q':
504292097Ssmh			quiet = true;
505270631Sjfv			break;
506270631Sjfv		case 'n':
507270631Sjfv			name = optarg;
508270631Sjfv			break;
509270631Sjfv		case 'g':
510270631Sjfv			id = pw_checkid(optarg, GID_MAX);
511292097Ssmh			break;
512292097Ssmh		case 'H':
513270631Sjfv			if (fd != -1)
514270631Sjfv				errx(EX_USAGE, "'-h' and '-H' are mutually "
515270631Sjfv				    "exclusive options");
516270631Sjfv			fd = pw_checkfd(optarg);
517292097Ssmh			precrypted = true;
518270631Sjfv			if (fd == '-')
519270631Sjfv				errx(EX_USAGE, "-H expects a file descriptor");
520270631Sjfv			break;
521270631Sjfv		case 'h':
522270631Sjfv			if (fd != -1)
523270631Sjfv				errx(EX_USAGE, "'-h' and '-H' are mutually "
524270631Sjfv				    "exclusive options");
525270631Sjfv			fd = pw_checkfd(optarg);
526270631Sjfv			break;
527270631Sjfv		case 'M':
528270631Sjfv			members = optarg;
529274360Sjfv			break;
530274360Sjfv		case 'o':
531274360Sjfv			conf.checkduplicate = false;
532274360Sjfv			break;
533274360Sjfv		case 'N':
534274360Sjfv			dryrun = true;
535274360Sjfv			break;
536274360Sjfv		case 'P':
537274360Sjfv			pretty = true;
538274360Sjfv			break;
539274360Sjfv		case 'Y':
540274360Sjfv			nis = true;
541270631Sjfv			break;
542270631Sjfv		}
543270631Sjfv	}
544270631Sjfv
545270631Sjfv	if (quiet)
546270631Sjfv		freopen(_PATH_DEVNULL, "w", stderr);
547270631Sjfv	if (name == NULL)
548270631Sjfv		errx(EX_DATAERR, "group name required");
549292097Ssmh	cnf = get_userconfig(cfg);
550270631Sjfv	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
551270631Sjfv	    pretty, precrypted);
552270631Sjfv	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
553270631Sjfv		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
554270631Sjfv
555270631Sjfv	return (rc);
556270631Sjfv}
557270631Sjfv
558270631Sjfvint
559270631Sjfvpw_group_mod(int argc, char **argv, char *arg1)
560270631Sjfv{
561270631Sjfv	struct userconf *cnf;
562270631Sjfv	struct group *grp = NULL;
563270631Sjfv	const char *cfg = NULL;
564270631Sjfv	char *oldmembers = NULL;
565270631Sjfv	char *members = NULL;
566270631Sjfv	char *newmembers = NULL;
567270631Sjfv	char *newname = NULL;
568270631Sjfv	char *name = NULL;
569270631Sjfv	intmax_t id = -1;
570270631Sjfv	int ch, rc, fd = -1;
571270631Sjfv	bool quiet, pretty, dryrun, nis, precrypted;
572270631Sjfv
573270631Sjfv	quiet = pretty = dryrun = nis = precrypted = false;
574270631Sjfv
575270631Sjfv	if (arg1 != NULL) {
576270631Sjfv		if (strspn(arg1, "0123456789") == strlen(arg1))
577270631Sjfv			id = pw_checkid(arg1, GID_MAX);
578274360Sjfv		else
579274360Sjfv			name = arg1;
580270631Sjfv	}
581270631Sjfv
582270631Sjfv	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
583270631Sjfv		switch (ch) {
584270631Sjfv		case 'C':
585270631Sjfv			cfg = optarg;
586270631Sjfv			break;
587270631Sjfv		case 'q':
588292097Ssmh			quiet = true;
589270631Sjfv			break;
590270631Sjfv		case 'n':
591270631Sjfv			name = optarg;
592270631Sjfv			break;
593270631Sjfv		case 'g':
594270631Sjfv			id = pw_checkid(optarg, GID_MAX);
595270631Sjfv			break;
596270631Sjfv		case 'd':
597270631Sjfv			oldmembers = optarg;
598270631Sjfv			break;
599270631Sjfv		case 'l':
600270631Sjfv			newname = optarg;
601270631Sjfv			break;
602270631Sjfv		case 'H':
603270631Sjfv			if (fd != -1)
604270631Sjfv				errx(EX_USAGE, "'-h' and '-H' are mutually "
605270631Sjfv				    "exclusive options");
606270631Sjfv			fd = pw_checkfd(optarg);
607270631Sjfv			precrypted = true;
608270631Sjfv			if (fd == '-')
609270631Sjfv				errx(EX_USAGE, "-H expects a file descriptor");
610270631Sjfv			break;
611270631Sjfv		case 'h':
612270631Sjfv			if (fd != -1)
613270631Sjfv				errx(EX_USAGE, "'-h' and '-H' are mutually "
614270631Sjfv				    "exclusive options");
615270631Sjfv			fd = pw_checkfd(optarg);
616270631Sjfv			break;
617270631Sjfv		case 'M':
618270631Sjfv			members = optarg;
619270631Sjfv			break;
620270631Sjfv		case 'm':
621270631Sjfv			newmembers = optarg;
622270631Sjfv			break;
623270631Sjfv		case 'N':
624270631Sjfv			dryrun = true;
625270631Sjfv			break;
626270631Sjfv		case 'P':
627270631Sjfv			pretty = true;
628270631Sjfv			break;
629270631Sjfv		case 'Y':
630270631Sjfv			nis = true;
631270631Sjfv			break;
632270631Sjfv		}
633270631Sjfv	}
634270631Sjfv	if (quiet)
635270631Sjfv		freopen(_PATH_DEVNULL, "w", stderr);
636270631Sjfv	cnf = get_userconfig(cfg);
637270631Sjfv	grp = getgroup(name, id, true);
638270631Sjfv	if (name == NULL)
639270631Sjfv		name = grp->gr_name;
640270631Sjfv	if (id > 0)
641270631Sjfv		grp->gr_gid = id;
642270631Sjfv
643270631Sjfv	if (newname != NULL)
644270631Sjfv		grp->gr_name = pw_checkname(newname, 0);
645270631Sjfv
646270631Sjfv	grp_set_passwd(grp, true, fd, precrypted);
647270631Sjfv	/*
648270631Sjfv	 * Keep the same logic as old code for now:
649270631Sjfv	 * if -M is passed, -d and -m are ignored
650274360Sjfv	 * then id -d, -m is ignored
651274360Sjfv	 * last is -m
652274360Sjfv	 */
653270631Sjfv
654270631Sjfv	if (members) {
655		grp->gr_mem = NULL;
656		grp_add_members(&grp, members);
657	} else if (oldmembers) {
658		delete_members(grp, oldmembers);
659	} else if (newmembers) {
660		grp_add_members(&grp, newmembers);
661	}
662
663	if ((rc = chggrent(name, grp)) != 0) {
664		if (rc == -1)
665			errx(EX_IOERR, "group '%s' not available (NIS?)",
666			    grp->gr_name);
667		else
668			err(EX_IOERR, "group update");
669	}
670
671	if (newname)
672		name = newname;
673
674	/* grp may have been invalidated */
675	if ((grp = GETGRNAM(name)) == NULL)
676		errx(EX_SOFTWARE, "group disappeared during update");
677
678	pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
679	    (uintmax_t)grp->gr_gid);
680
681	if (nis && nis_update() == 0)
682		pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
683
684	return (EXIT_SUCCESS);
685}
686