xref: /freebsd/usr.sbin/pw/pw_group.c (revision 7543a9c0280a0f4262489671936a6e03b9b2c563)
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
30 static 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 
47 static struct passwd *lookup_pwent(const char *user);
48 static void	delete_members(struct group *grp, char *list);
49 static int	print_group(struct group * grp, bool pretty);
50 static gid_t	gr_gidpolicy(struct userconf * cnf, intmax_t id);
51 
52 static void
53 grp_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 
104 int
105 pw_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 
116 static struct group *
117 getgroup(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  */
137 static struct passwd *
138 lookup_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  */
154 static void
155 delete_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 
176 static gid_t
177 gr_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 
239 static int
240 print_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 
263 int
264 pw_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 		default:
280 			exit(EX_USAGE);
281 		}
282 	}
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 			exit(EX_USAGE);
340 		}
341 	}
342 
343 	if (quiet)
344 		freopen(_PATH_DEVNULL, "w", stderr);
345 
346 	if (all) {
347 		SETGRENT();
348 		while ((grp = GETGRENT()) != NULL)
349 			print_group(grp, pretty);
350 		ENDGRENT();
351 		return (EXIT_SUCCESS);
352 	}
353 
354 	grp = getgroup(name, id, !force);
355 	if (grp == NULL)
356 		grp = &fakegroup;
357 
358 	return (print_group(grp, pretty));
359 }
360 
361 int
362 pw_group_del(int argc, char **argv, char *arg1)
363 {
364 	struct userconf *cnf = NULL;
365 	struct group *grp = NULL;
366 	char *name;
367 	const char *cfg = NULL;
368 	intmax_t id = -1;
369 	int ch, rc;
370 	bool quiet = false;
371 	bool nis = false;
372 
373 	if (arg1 != NULL) {
374 		if (arg1[strspn(arg1, "0123456789")] == '\0')
375 			id = pw_checkid(arg1, GID_MAX);
376 		else
377 			name = arg1;
378 	}
379 
380 	while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
381 		switch (ch) {
382 		case 'C':
383 			cfg = optarg;
384 			break;
385 		case 'q':
386 			quiet = true;
387 			break;
388 		case 'n':
389 			name = optarg;
390 			break;
391 		case 'g':
392 			id = pw_checkid(optarg, GID_MAX);
393 			break;
394 		case 'Y':
395 			nis = true;
396 			break;
397 		default:
398 			exit(EX_USAGE);
399 		}
400 	}
401 
402 	if (quiet)
403 		freopen(_PATH_DEVNULL, "w", stderr);
404 	grp = getgroup(name, id, true);
405 	cnf = get_userconfig(cfg);
406 	rc = delgrent(grp);
407 	if (rc == -1)
408 		err(EX_IOERR, "group '%s' not available (NIS?)", name);
409 	else if (rc != 0)
410 		err(EX_IOERR, "group update");
411 	pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
412 	    (uintmax_t)id);
413 
414 	if (nis && nis_update() == 0)
415 		pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
416 
417 	return (EXIT_SUCCESS);
418 }
419 
420 static bool
421 grp_has_member(struct group *grp, const char *name)
422 {
423 	int j;
424 
425 	for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
426 		if (strcmp(grp->gr_mem[j], name) == 0)
427 			return (true);
428 	return (false);
429 }
430 
431 static void
432 grp_add_members(struct group **grp, char *members)
433 {
434 	struct passwd *pwd;
435 	char *p;
436 	char tok[] = ", \t";
437 
438 	if (members == NULL)
439 		return;
440 	for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
441 		pwd = lookup_pwent(p);
442 		if (grp_has_member(*grp, pwd->pw_name))
443 			continue;
444 		*grp = gr_add(*grp, pwd->pw_name);
445 	}
446 }
447 
448 int
449 groupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
450     bool dryrun, bool pretty, bool precrypted)
451 {
452 	struct group *grp;
453 	int rc;
454 
455 	struct group fakegroup = {
456 		"nogroup",
457 		"*",
458 		-1,
459 		NULL
460 	};
461 
462 	grp = &fakegroup;
463 	grp->gr_name = pw_checkname(name, 0);
464 	grp->gr_passwd = "*";
465 	grp->gr_gid = gr_gidpolicy(cnf, id);
466 	grp->gr_mem = NULL;
467 
468 	/*
469 	 * This allows us to set a group password Group passwords is an
470 	 * antique idea, rarely used and insecure (no secure database) Should
471 	 * be discouraged, but it is apparently still supported by some
472 	 * software.
473 	 */
474 	grp_set_passwd(grp, false, fd, precrypted);
475 	grp_add_members(&grp, members);
476 	if (dryrun)
477 		return (print_group(grp, pretty));
478 
479 	if ((rc = addgrent(grp)) != 0) {
480 		if (rc == -1)
481 			errx(EX_IOERR, "group '%s' already exists",
482 			    grp->gr_name);
483 		else
484 			err(EX_IOERR, "group update");
485 	}
486 
487 	pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
488 	    (uintmax_t)grp->gr_gid);
489 
490 	return (EXIT_SUCCESS);
491 }
492 
493 int
494 pw_group_add(int argc, char **argv, char *arg1)
495 {
496 	struct userconf *cnf = NULL;
497 	char *name = NULL;
498 	char *members = NULL;
499 	const char *cfg = NULL;
500 	intmax_t id = -1;
501 	int ch, rc, fd = -1;
502 	bool quiet, precrypted, dryrun, pretty, nis;
503 
504 	quiet = precrypted = dryrun = pretty = nis = false;
505 
506 	if (arg1 != NULL) {
507 		if (arg1[strspn(arg1, "0123456789")] == '\0')
508 			id = pw_checkid(arg1, GID_MAX);
509 		else
510 			name = arg1;
511 	}
512 
513 	while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
514 		switch (ch) {
515 		case 'C':
516 			cfg = optarg;
517 			break;
518 		case 'q':
519 			quiet = true;
520 			break;
521 		case 'n':
522 			name = optarg;
523 			break;
524 		case 'g':
525 			id = pw_checkid(optarg, GID_MAX);
526 			break;
527 		case 'H':
528 			if (fd != -1)
529 				errx(EX_USAGE, "'-h' and '-H' are mutually "
530 				    "exclusive options");
531 			fd = pw_checkfd(optarg);
532 			precrypted = true;
533 			if (fd == '-')
534 				errx(EX_USAGE, "-H expects a file descriptor");
535 			break;
536 		case 'h':
537 			if (fd != -1)
538 				errx(EX_USAGE, "'-h' and '-H' are mutually "
539 				    "exclusive options");
540 			fd = pw_checkfd(optarg);
541 			break;
542 		case 'M':
543 			members = optarg;
544 			break;
545 		case 'o':
546 			conf.checkduplicate = false;
547 			break;
548 		case 'N':
549 			dryrun = true;
550 			break;
551 		case 'P':
552 			pretty = true;
553 			break;
554 		case 'Y':
555 			nis = true;
556 			break;
557 		default:
558 			exit(EX_USAGE);
559 		}
560 	}
561 
562 	if (quiet)
563 		freopen(_PATH_DEVNULL, "w", stderr);
564 	if (name == NULL)
565 		errx(EX_DATAERR, "group name required");
566 	if (GETGRNAM(name) != NULL)
567 		errx(EX_DATAERR, "group name `%s' already exists", name);
568 	cnf = get_userconfig(cfg);
569 	rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
570 	    pretty, precrypted);
571 	if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
572 		pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
573 
574 	return (rc);
575 }
576 
577 int
578 pw_group_mod(int argc, char **argv, char *arg1)
579 {
580 	struct userconf *cnf;
581 	struct group *grp = NULL;
582 	const char *cfg = NULL;
583 	char *oldmembers = NULL;
584 	char *members = NULL;
585 	char *newmembers = NULL;
586 	char *newname = NULL;
587 	char *name = NULL;
588 	intmax_t id = -1;
589 	int ch, rc, fd = -1;
590 	bool quiet, pretty, dryrun, nis, precrypted;
591 
592 	quiet = pretty = dryrun = nis = precrypted = false;
593 
594 	if (arg1 != NULL) {
595 		if (arg1[strspn(arg1, "0123456789")] == '\0')
596 			id = pw_checkid(arg1, GID_MAX);
597 		else
598 			name = arg1;
599 	}
600 
601 	while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
602 		switch (ch) {
603 		case 'C':
604 			cfg = optarg;
605 			break;
606 		case 'q':
607 			quiet = true;
608 			break;
609 		case 'n':
610 			name = optarg;
611 			break;
612 		case 'g':
613 			id = pw_checkid(optarg, GID_MAX);
614 			break;
615 		case 'd':
616 			oldmembers = optarg;
617 			break;
618 		case 'l':
619 			newname = optarg;
620 			break;
621 		case 'H':
622 			if (fd != -1)
623 				errx(EX_USAGE, "'-h' and '-H' are mutually "
624 				    "exclusive options");
625 			fd = pw_checkfd(optarg);
626 			precrypted = true;
627 			if (fd == '-')
628 				errx(EX_USAGE, "-H expects a file descriptor");
629 			break;
630 		case 'h':
631 			if (fd != -1)
632 				errx(EX_USAGE, "'-h' and '-H' are mutually "
633 				    "exclusive options");
634 			fd = pw_checkfd(optarg);
635 			break;
636 		case 'M':
637 			members = optarg;
638 			break;
639 		case 'm':
640 			newmembers = optarg;
641 			break;
642 		case 'N':
643 			dryrun = true;
644 			break;
645 		case 'P':
646 			pretty = true;
647 			break;
648 		case 'Y':
649 			nis = true;
650 			break;
651 		default:
652 			exit(EX_USAGE);
653 		}
654 	}
655 	if (quiet)
656 		freopen(_PATH_DEVNULL, "w", stderr);
657 	cnf = get_userconfig(cfg);
658 	grp = getgroup(name, id, true);
659 	if (name == NULL)
660 		name = grp->gr_name;
661 	if (id > 0)
662 		grp->gr_gid = id;
663 
664 	if (newname != NULL)
665 		grp->gr_name = pw_checkname(newname, 0);
666 
667 	grp_set_passwd(grp, true, fd, precrypted);
668 	/*
669 	 * Keep the same logic as old code for now:
670 	 * if -M is passed, -d and -m are ignored
671 	 * then id -d, -m is ignored
672 	 * last is -m
673 	 */
674 
675 	if (members) {
676 		grp->gr_mem = NULL;
677 		grp_add_members(&grp, members);
678 	} else if (oldmembers) {
679 		delete_members(grp, oldmembers);
680 	} else if (newmembers) {
681 		grp_add_members(&grp, newmembers);
682 	}
683 
684 	if (dryrun) {
685 		print_group(grp, pretty);
686 		return (EXIT_SUCCESS);
687 	}
688 
689 	if ((rc = chggrent(name, grp)) != 0) {
690 		if (rc == -1)
691 			errx(EX_IOERR, "group '%s' not available (NIS?)",
692 			    grp->gr_name);
693 		else
694 			err(EX_IOERR, "group update");
695 	}
696 
697 	if (newname)
698 		name = newname;
699 
700 	/* grp may have been invalidated */
701 	if ((grp = GETGRNAM(name)) == NULL)
702 		errx(EX_SOFTWARE, "group disappeared during update");
703 
704 	pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
705 	    (uintmax_t)grp->gr_gid);
706 
707 	if (nis && nis_update() == 0)
708 		pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
709 
710 	return (EXIT_SUCCESS);
711 }
712