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