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