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