xref: /freebsd/usr.sbin/pw/pw_group.c (revision 96190b4fef3b4a0cc3ca0606b0c4e3e69a5e6717)
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 
42 static struct passwd *lookup_pwent(const char *user);
43 static void	delete_members(struct group *grp, char *list);
44 static int	print_group(struct group * grp, bool pretty);
45 static gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
46 
47 static void
48 grp_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 
100 int
101 pw_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 
112 static struct group *
113 getgroup(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  */
133 static struct passwd *
134 lookup_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  */
150 static void
151 delete_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 
172 static gid_t
173 gr_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 
235 static int
236 print_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 
259 int
260 pw_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 
290 int
291 pw_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 
365 int
366 pw_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 
428 bool
429 grp_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 
439 static void
440 grp_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 
456 int
457 groupadd(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 
501 int
502 pw_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 
589 int
590 pw_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