1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (C) 1996
5 *	David L. Nugent.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#ifndef lint
30static const char rcsid[] =
31  "$FreeBSD$";
32#endif /* not lint */
33
34#include <ctype.h>
35#include <err.h>
36#include <grp.h>
37#include <libutil.h>
38#include <paths.h>
39#include <string.h>
40#include <sysexits.h>
41#include <termios.h>
42#include <unistd.h>
43
44#include "pw.h"
45#include "bitmap.h"
46
47static struct passwd *lookup_pwent(const char *user);
48static void	delete_members(struct group *grp, char *list);
49static int	print_group(struct group * grp, bool pretty);
50static gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
51
52static void
53grp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
54{
55	int		 b;
56	int		 istty;
57	struct termios	 t, n;
58	char		*p, line[256];
59
60	if (fd == -1)
61		return;
62
63	if (fd == '-') {
64		grp->gr_passwd = "*";	/* No access */
65		return;
66	}
67
68	if ((istty = isatty(fd))) {
69		if (tcgetattr(fd, &t) == -1)
70			istty = 0;
71		else {
72			n = t;
73			/* Disable echo */
74			n.c_lflag &= ~(ECHO);
75			tcsetattr(fd, TCSANOW, &n);
76			printf("%sassword for group %s:",
77			    update ? "New p" : "P",
78			    grp->gr_name);
79			fflush(stdout);
80		}
81	}
82	b = read(fd, line, sizeof(line) - 1);
83	if (istty) {	/* Restore state */
84		tcsetattr(fd, TCSANOW, &t);
85		fputc('\n', stdout);
86		fflush(stdout);
87	}
88	if (b < 0)
89		err(EX_OSERR, "-h file descriptor");
90	line[b] = '\0';
91	if ((p = strpbrk(line, " \t\r\n")) != NULL)
92		*p = '\0';
93	if (!*line)
94		errx(EX_DATAERR, "empty password read on file descriptor %d",
95		    conf.fd);
96	if (precrypted) {
97		if (strchr(line, ':') != 0)
98			errx(EX_DATAERR, "wrong encrypted passwrd");
99		grp->gr_passwd = line;
100	} else
101		grp->gr_passwd = pw_pwcrypt(line);
102}
103
104int
105pw_groupnext(struct userconf *cnf, bool quiet)
106{
107	gid_t next = gr_gidpolicy(cnf, -1);
108
109	if (quiet)
110		return (next);
111	printf("%ju\n", (uintmax_t)next);
112
113	return (EXIT_SUCCESS);
114}
115
116static struct group *
117getgroup(char *name, intmax_t id, bool fatal)
118{
119	struct group *grp;
120
121	if (id < 0 && name == NULL)
122		errx(EX_DATAERR, "groupname or id required");
123	grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
124	if (grp == NULL) {
125		if (!fatal)
126			return (NULL);
127		if (name == NULL)
128			errx(EX_DATAERR, "unknown gid `%ju'", id);
129		errx(EX_DATAERR, "unknown group `%s'", name);
130	}
131	return (grp);
132}
133
134/*
135 * Lookup a passwd entry using a name or UID.
136 */
137static struct passwd *
138lookup_pwent(const char *user)
139{
140	struct passwd *pwd;
141
142	if ((pwd = GETPWNAM(user)) == NULL &&
143	    (!isdigit((unsigned char)*user) ||
144	    (pwd = getpwuid((uid_t) atoi(user))) == NULL))
145		errx(EX_NOUSER, "user `%s' does not exist", user);
146
147	return (pwd);
148}
149
150
151/*
152 * Delete requested members from a group.
153 */
154static void
155delete_members(struct group *grp, char *list)
156{
157	char *p;
158	int k;
159
160	if (grp->gr_mem == NULL)
161		return;
162
163	for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
164		for (k = 0; grp->gr_mem[k] != NULL; k++) {
165			if (strcmp(grp->gr_mem[k], p) == 0)
166				break;
167		}
168		if (grp->gr_mem[k] == NULL) /* No match */
169			continue;
170
171		for (; grp->gr_mem[k] != NULL; k++)
172			grp->gr_mem[k] = grp->gr_mem[k+1];
173	}
174}
175
176static gid_t
177gr_gidpolicy(struct userconf * cnf, intmax_t id)
178{
179	struct group   *grp;
180	struct bitmap   bm;
181	gid_t           gid = (gid_t) - 1;
182
183	/*
184	 * Check the given gid, if any
185	 */
186	if (id > 0) {
187		gid = (gid_t) id;
188
189		if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
190			errx(EX_DATAERR, "gid `%ju' has already been allocated",
191			    (uintmax_t)grp->gr_gid);
192		return (gid);
193	}
194
195	/*
196	 * We need to allocate the next available gid under one of
197	 * two policies a) Grab the first unused gid b) Grab the
198	 * highest possible unused gid
199	 */
200	if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
201		cnf->min_gid = 1000;
202		cnf->max_gid = 32000;
203	}
204	bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
205
206	/*
207	 * Now, let's fill the bitmap from the password file
208	 */
209	SETGRENT();
210	while ((grp = GETGRENT()) != NULL)
211		if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
212		    (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
213			bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
214	ENDGRENT();
215
216	/*
217	 * Then apply the policy, with fallback to reuse if necessary
218	 */
219	if (cnf->reuse_gids)
220		gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
221	else {
222		gid = (gid_t) (bm_lastset(&bm) + 1);
223		if (!bm_isset(&bm, gid))
224			gid += cnf->min_gid;
225		else
226			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
227	}
228
229	/*
230	 * Another sanity check
231	 */
232	if (gid < cnf->min_gid || gid > cnf->max_gid)
233		errx(EX_SOFTWARE, "unable to allocate a new gid - range fully "
234		    "used");
235	bm_dealloc(&bm);
236	return (gid);
237}
238
239static int
240print_group(struct group * grp, bool pretty)
241{
242	char *buf = NULL;
243	int i;
244
245	if (pretty) {
246		printf("Group Name: %-15s   #%lu\n"
247		       "   Members: ",
248		       grp->gr_name, (long) grp->gr_gid);
249		if (grp->gr_mem != NULL) {
250			for (i = 0; grp->gr_mem[i]; i++)
251				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
252		}
253		fputs("\n\n", stdout);
254		return (EXIT_SUCCESS);
255	}
256
257	buf = gr_make(grp);
258	printf("%s\n", buf);
259	free(buf);
260	return (EXIT_SUCCESS);
261}
262
263int
264pw_group_next(int argc, char **argv, char *arg1 __unused)
265{
266	struct userconf *cnf;
267	const char *cfg = NULL;
268	int ch;
269	bool quiet = false;
270
271	while ((ch = getopt(argc, argv, "C:q")) != -1) {
272		switch (ch) {
273		case 'C':
274			cfg = optarg;
275			break;
276		case 'q':
277			quiet = true;
278			break;
279		}
280	}
281
282	if (quiet)
283		freopen(_PATH_DEVNULL, "w", stderr);
284	cnf = get_userconfig(cfg);
285	return (pw_groupnext(cnf, quiet));
286}
287
288int
289pw_group_show(int argc, char **argv, char *arg1)
290{
291	struct group *grp = NULL;
292	char *name = NULL;
293	intmax_t id = -1;
294	int ch;
295	bool all, force, quiet, pretty;
296
297	all = force = quiet = pretty = false;
298
299	struct group fakegroup = {
300		"nogroup",
301		"*",
302		-1,
303		NULL
304	};
305
306	if (arg1 != NULL) {
307		if (arg1[strspn(arg1, "0123456789")] == '\0')
308			id = pw_checkid(arg1, GID_MAX);
309		else
310			name = arg1;
311	}
312
313	while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
314		switch (ch) {
315		case 'C':
316			/* ignore compatibility */
317			break;
318		case 'q':
319			quiet = true;
320			break;
321		case 'n':
322			name = optarg;
323			break;
324		case 'g':
325			id = pw_checkid(optarg, GID_MAX);
326			break;
327		case 'F':
328			force = true;
329			break;
330		case 'P':
331			pretty = true;
332			break;
333		case 'a':
334			all = true;
335			break;
336		}
337	}
338
339	if (quiet)
340		freopen(_PATH_DEVNULL, "w", stderr);
341
342	if (all) {
343		SETGRENT();
344		while ((grp = GETGRENT()) != NULL)
345			print_group(grp, pretty);
346		ENDGRENT();
347		return (EXIT_SUCCESS);
348	}
349
350	grp = getgroup(name, id, !force);
351	if (grp == NULL)
352		grp = &fakegroup;
353
354	return (print_group(grp, pretty));
355}
356
357int
358pw_group_del(int argc, char **argv, char *arg1)
359{
360	struct userconf *cnf = NULL;
361	struct group *grp = NULL;
362	char *name;
363	const char *cfg = NULL;
364	intmax_t id = -1;
365	int ch, rc;
366	bool quiet = false;
367	bool nis = false;
368
369	if (arg1 != NULL) {
370		if (arg1[strspn(arg1, "0123456789")] == '\0')
371			id = pw_checkid(arg1, GID_MAX);
372		else
373			name = arg1;
374	}
375
376	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
377		switch (ch) {
378		case 'C':
379			cfg = optarg;
380			break;
381		case 'q':
382			quiet = true;
383			break;
384		case 'n':
385			name = optarg;
386			break;
387		case 'g':
388			id = pw_checkid(optarg, GID_MAX);
389			break;
390		case 'Y':
391			nis = true;
392			break;
393		}
394	}
395
396	if (quiet)
397		freopen(_PATH_DEVNULL, "w", stderr);
398	grp = getgroup(name, id, true);
399	cnf = get_userconfig(cfg);
400	rc = delgrent(grp);
401	if (rc == -1)
402		err(EX_IOERR, "group '%s' not available (NIS?)", name);
403	else if (rc != 0)
404		err(EX_IOERR, "group update");
405	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
406	    (uintmax_t)id);
407
408	if (nis && nis_update() == 0)
409		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
410
411	return (EXIT_SUCCESS);
412}
413
414static bool
415grp_has_member(struct group *grp, const char *name)
416{
417	int j;
418
419	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
420		if (strcmp(grp->gr_mem[j], name) == 0)
421			return (true);
422	return (false);
423}
424
425static void
426grp_add_members(struct group **grp, char *members)
427{
428	struct passwd *pwd;
429	char *p;
430	char tok[] = ", \t";
431
432	if (members == NULL)
433		return;
434	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
435		pwd = lookup_pwent(p);
436		if (grp_has_member(*grp, pwd->pw_name))
437			continue;
438		*grp = gr_add(*grp, pwd->pw_name);
439	}
440}
441
442int
443groupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
444    bool dryrun, bool pretty, bool precrypted)
445{
446	struct group *grp;
447	int rc;
448
449	struct group fakegroup = {
450		"nogroup",
451		"*",
452		-1,
453		NULL
454	};
455
456	grp = &fakegroup;
457	grp->gr_name = pw_checkname(name, 0);
458	grp->gr_passwd = "*";
459	grp->gr_gid = gr_gidpolicy(cnf, id);
460	grp->gr_mem = NULL;
461
462	/*
463	 * This allows us to set a group password Group passwords is an
464	 * antique idea, rarely used and insecure (no secure database) Should
465	 * be discouraged, but it is apparently still supported by some
466	 * software.
467	 */
468	grp_set_passwd(grp, false, fd, precrypted);
469	grp_add_members(&grp, members);
470	if (dryrun)
471		return (print_group(grp, pretty));
472
473	if ((rc = addgrent(grp)) != 0) {
474		if (rc == -1)
475			errx(EX_IOERR, "group '%s' already exists",
476			    grp->gr_name);
477		else
478			err(EX_IOERR, "group update");
479	}
480
481	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
482	    (uintmax_t)grp->gr_gid);
483
484	return (EXIT_SUCCESS);
485}
486
487int
488pw_group_add(int argc, char **argv, char *arg1)
489{
490	struct userconf *cnf = NULL;
491	char *name = NULL;
492	char *members = NULL;
493	const char *cfg = NULL;
494	intmax_t id = -1;
495	int ch, rc, fd = -1;
496	bool quiet, precrypted, dryrun, pretty, nis;
497
498	quiet = precrypted = dryrun = pretty = nis = false;
499
500	if (arg1 != NULL) {
501		if (arg1[strspn(arg1, "0123456789")] == '\0')
502			id = pw_checkid(arg1, GID_MAX);
503		else
504			name = arg1;
505	}
506
507	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
508		switch (ch) {
509		case 'C':
510			cfg = optarg;
511			break;
512		case 'q':
513			quiet = true;
514			break;
515		case 'n':
516			name = optarg;
517			break;
518		case 'g':
519			id = pw_checkid(optarg, GID_MAX);
520			break;
521		case 'H':
522			if (fd != -1)
523				errx(EX_USAGE, "'-h' and '-H' are mutually "
524				    "exclusive options");
525			fd = pw_checkfd(optarg);
526			precrypted = true;
527			if (fd == '-')
528				errx(EX_USAGE, "-H expects a file descriptor");
529			break;
530		case 'h':
531			if (fd != -1)
532				errx(EX_USAGE, "'-h' and '-H' are mutually "
533				    "exclusive options");
534			fd = pw_checkfd(optarg);
535			break;
536		case 'M':
537			members = optarg;
538			break;
539		case 'o':
540			conf.checkduplicate = false;
541			break;
542		case 'N':
543			dryrun = true;
544			break;
545		case 'P':
546			pretty = true;
547			break;
548		case 'Y':
549			nis = true;
550			break;
551		}
552	}
553
554	if (quiet)
555		freopen(_PATH_DEVNULL, "w", stderr);
556	if (name == NULL)
557		errx(EX_DATAERR, "group name required");
558	if (GETGRNAM(name) != NULL)
559		errx(EX_DATAERR, "group name `%s' already exists", name);
560	cnf = get_userconfig(cfg);
561	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
562	    pretty, precrypted);
563	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
564		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
565
566	return (rc);
567}
568
569int
570pw_group_mod(int argc, char **argv, char *arg1)
571{
572	struct userconf *cnf;
573	struct group *grp = NULL;
574	const char *cfg = NULL;
575	char *oldmembers = NULL;
576	char *members = NULL;
577	char *newmembers = NULL;
578	char *newname = NULL;
579	char *name = NULL;
580	intmax_t id = -1;
581	int ch, rc, fd = -1;
582	bool quiet, pretty, dryrun, nis, precrypted;
583
584	quiet = pretty = dryrun = nis = precrypted = false;
585
586	if (arg1 != NULL) {
587		if (arg1[strspn(arg1, "0123456789")] == '\0')
588			id = pw_checkid(arg1, GID_MAX);
589		else
590			name = arg1;
591	}
592
593	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
594		switch (ch) {
595		case 'C':
596			cfg = optarg;
597			break;
598		case 'q':
599			quiet = true;
600			break;
601		case 'n':
602			name = optarg;
603			break;
604		case 'g':
605			id = pw_checkid(optarg, GID_MAX);
606			break;
607		case 'd':
608			oldmembers = optarg;
609			break;
610		case 'l':
611			newname = optarg;
612			break;
613		case 'H':
614			if (fd != -1)
615				errx(EX_USAGE, "'-h' and '-H' are mutually "
616				    "exclusive options");
617			fd = pw_checkfd(optarg);
618			precrypted = true;
619			if (fd == '-')
620				errx(EX_USAGE, "-H expects a file descriptor");
621			break;
622		case 'h':
623			if (fd != -1)
624				errx(EX_USAGE, "'-h' and '-H' are mutually "
625				    "exclusive options");
626			fd = pw_checkfd(optarg);
627			break;
628		case 'M':
629			members = optarg;
630			break;
631		case 'm':
632			newmembers = optarg;
633			break;
634		case 'N':
635			dryrun = true;
636			break;
637		case 'P':
638			pretty = true;
639			break;
640		case 'Y':
641			nis = true;
642			break;
643		}
644	}
645	if (quiet)
646		freopen(_PATH_DEVNULL, "w", stderr);
647	cnf = get_userconfig(cfg);
648	grp = getgroup(name, id, true);
649	if (name == NULL)
650		name = grp->gr_name;
651	if (id > 0)
652		grp->gr_gid = id;
653
654	if (newname != NULL)
655		grp->gr_name = pw_checkname(newname, 0);
656
657	grp_set_passwd(grp, true, fd, precrypted);
658	/*
659	 * Keep the same logic as old code for now:
660	 * if -M is passed, -d and -m are ignored
661	 * then id -d, -m is ignored
662	 * last is -m
663	 */
664
665	if (members) {
666		grp->gr_mem = NULL;
667		grp_add_members(&grp, members);
668	} else if (oldmembers) {
669		delete_members(grp, oldmembers);
670	} else if (newmembers) {
671		grp_add_members(&grp, newmembers);
672	}
673
674	if (dryrun) {
675		print_group(grp, pretty);
676		return (EXIT_SUCCESS);
677	}
678
679	if ((rc = chggrent(name, grp)) != 0) {
680		if (rc == -1)
681			errx(EX_IOERR, "group '%s' not available (NIS?)",
682			    grp->gr_name);
683		else
684			err(EX_IOERR, "group update");
685	}
686
687	if (newname)
688		name = newname;
689
690	/* grp may have been invalidated */
691	if ((grp = GETGRNAM(name)) == NULL)
692		errx(EX_SOFTWARE, "group disappeared during update");
693
694	pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
695	    (uintmax_t)grp->gr_gid);
696
697	if (nis && nis_update() == 0)
698		pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
699
700	return (EXIT_SUCCESS);
701}
702