xref: /freebsd/usr.sbin/pw/pw_group.c (revision f39bffc62c1395bde25d152c7f68fdf7cbaab414)
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 		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 
99 int
100 pw_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 
111 static struct group *
112 getgroup(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  */
132 static struct passwd *
133 lookup_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  */
149 static void
150 delete_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 
171 static gid_t
172 gr_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 
234 static int
235 print_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 
258 int
259 pw_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 
283 int
284 pw_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 
352 int
353 pw_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 
409 static bool
410 grp_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 
420 static void
421 grp_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 
437 int
438 groupadd(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 
482 int
483 pw_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 
564 int
565 pw_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