xref: /freebsd/usr.bin/mdo/mdo.c (revision 3ca1e69028acdee30739c0e0856692395a36fd21)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
5  * Copyright (c) 2025 Kushagra Srivastava <kushagra1403@gmail.com>
6  * Copyright (c) 2025 The FreeBSD Foundation
7  *
8  * Portions of this software were developed by Olivier Certner
9  * <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
10  * Foundation.
11  */
12 
13 #include <sys/errno.h>
14 #include <sys/limits.h>
15 #include <sys/types.h>
16 #include <sys/ucred.h>
17 
18 #include <assert.h>
19 #include <err.h>
20 #include <getopt.h>
21 #include <grp.h>
22 #include <paths.h>
23 #include <pwd.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 
31 static void
32 usage(void)
33 {
34 	fprintf(stderr,
35 	    "Usage: mdo [options] [--] [command [args...]]\n"
36 	    "\n"
37 	    "Options:\n"
38 	    "  -u <user>         Target user (name or UID; name sets groups)\n"
39 	    "  -k                Keep current user, allows selective overrides "
40 	    "(implies -i)\n"
41 	    "  -i                Keep current groups, unless explicitly overridden\n"
42 	    "  -g <group>        Override primary group (name or GID)\n"
43 	    "  -G <g1,g2,...>    Set supplementary groups (name or GID list)\n"
44 	    "  -s <mods>         Modify supplementary groups using:\n"
45 	    "                      @ (first) to reset, +group to add, -group to remove\n"
46 	    "\n"
47 	    "Advanced UID/GID overrides:\n"
48 	    "  --euid <uid>      Set effective UID\n"
49 	    "  --ruid <uid>      Set real UID\n"
50 	    "  --svuid <uid>     Set saved UID\n"
51 	    "  --egid <gid>      Set effective GID\n"
52 	    "  --rgid <gid>      Set real GID\n"
53 	    "  --svgid <gid>     Set saved GID\n"
54 	    "\n"
55 	    "  -h                Show this help message\n"
56 	    "\n"
57 	    "Examples:\n"
58 	    "  mdo -u alice id\n"
59 	    "  mdo -u 1001 -g wheel -G staff,operator sh\n"
60 	    "  mdo -u bob -s +wheel,+operator id\n"
61 	    "  mdo -k --ruid 1002 --egid 1004 id\n"
62 	);
63 	exit(1);
64 }
65 
66 struct alloc {
67 	void	*start;
68 	size_t	 size;
69 };
70 
71 static const struct alloc ALLOC_INITIALIZER = {
72 	.start = NULL,
73 	.size = 0,
74 };
75 
76 /*
77  * The default value should cover almost all cases.
78  *
79  * For getpwnam_r(), we assume:
80  * - 88 bytes for 'struct passwd'
81  * - Less than 16 bytes for the user name
82  * - A typical shadow hash of 106 bytes
83  * - Less than 16 bytes for the login class name
84  * - Less than 64 bytes for GECOS info
85  * - Less than 128 bytes for the home directory
86  * - Less than 32 bytes for the shell path
87  * Total: 256 + 88 + 106 = 450.
88  *
89  * For getgrnam_r(), we assume:
90  * - 32 bytes for 'struct group'
91  * - Less than 16 bytes for the group name
92  * - Some hash of 106 bytes
93  * - No more than 16 members, each of less than 16 bytes (=> 256 bytes)
94  * Total: 256 + 32 + 16 + 106 = 410.
95  *
96  * We thus choose 512 (leeway, power of 2).
97  */
98 static const size_t ALLOC_FIRST_SIZE = 512;
99 
100 static bool
101 alloc_is_empty(const struct alloc *const alloc)
102 {
103 	if (alloc->size == 0) {
104 		assert(alloc->start == NULL);
105 		return (true);
106 	} else {
107 		assert(alloc->start != NULL);
108 		return (false);
109 	}
110 }
111 
112 static void
113 alloc_realloc(struct alloc *const alloc)
114 {
115 	const size_t old_size = alloc->size;
116 	size_t new_size;
117 
118 	if (old_size == 0) {
119 		assert(alloc->start == NULL);
120 		new_size = ALLOC_FIRST_SIZE;
121 	} else if (old_size < PAGE_SIZE)
122 		new_size = 2 * old_size;
123 	else
124 		/*
125 		 * We never allocate more than a page at a time when reaching
126 		 * a page (except perhaps for the first increment, up to two).
127 		 * Use roundup2() to be immune to previous cases' changes. */
128 		new_size = roundup2(old_size, PAGE_SIZE) + PAGE_SIZE;
129 
130 	alloc->start = realloc(alloc->start, new_size);
131 	if (alloc->start == NULL)
132 		errx(EXIT_FAILURE,
133 		    "cannot realloc allocation (old size: %zu, new: %zu)",
134 		    old_size, new_size);
135 	alloc->size = new_size;
136 }
137 
138 static void
139 alloc_free(struct alloc *const alloc)
140 {
141 	if (!alloc_is_empty(alloc)) {
142 		free(alloc->start);
143 		*alloc = ALLOC_INITIALIZER;
144 	}
145 }
146 
147 struct alloc_wrap_data {
148 	int (*func)(void *data, const struct alloc *alloc);
149 };
150 
151 /*
152  * Wraps functions needing a backing allocation.
153  *
154  * Uses 'alloc' as the starting allocation, and may extend it as necessary.
155  * 'alloc' is never freed, even on failure of the wrapped function.
156  *
157  * The function is expected to return ERANGE if and only if the provided
158  * allocation is not big enough.  All other values are passed through.
159  */
160 static int
161 alloc_wrap(struct alloc_wrap_data *const data, struct alloc *alloc)
162 {
163 	int error;
164 
165 	/* Avoid a systematic ERANGE on first iteration. */
166 	if (alloc_is_empty(alloc))
167 		alloc_realloc(alloc);
168 
169 	for (;;) {
170 		error = data->func(data, alloc);
171 		if (error != ERANGE)
172 			break;
173 		alloc_realloc(alloc);
174 	}
175 
176 	return (error);
177 }
178 
179 struct getpwnam_wrapper_data {
180 	struct alloc_wrap_data	  wrapped;
181 	const char		 *name;
182 	struct passwd		**pwdp;
183 };
184 
185 static int
186 wrapped_getpwnam_r(void *data, const struct alloc *alloc)
187 {
188 	struct passwd *const pwd = alloc->start;
189 	struct passwd *result;
190 	struct getpwnam_wrapper_data *d = data;
191 	int error;
192 
193 	assert(alloc->size >= sizeof(*pwd));
194 
195 	error = getpwnam_r(d->name, pwd, (char *)(pwd + 1),
196 	    alloc->size - sizeof(*pwd), &result);
197 
198 	if (error == 0) {
199 		if (result == NULL)
200 			error = ENOENT;
201 	} else
202 		assert(result == NULL);
203 	*d->pwdp = result;
204 	return (error);
205 }
206 
207 /*
208  * Wraps getpwnam_r(), automatically dealing with memory allocation.
209  *
210  * 'alloc' may be any allocation (even empty), and will be extended as
211  * necessary.  It is not freed on error.
212  *
213  * On success, '*pwdp' is filled with a pointer to the returned 'struct passwd',
214  * and on failure, is set to NULL.
215  */
216 static int
217 alloc_getpwnam(const char *name, struct passwd **pwdp,
218     struct alloc *const alloc)
219 {
220 	struct getpwnam_wrapper_data data;
221 
222 	data.wrapped.func = wrapped_getpwnam_r;
223 	data.name = name;
224 	data.pwdp = pwdp;
225 	return (alloc_wrap((struct alloc_wrap_data *)&data, alloc));
226 }
227 
228 struct getgrnam_wrapper_data {
229 	struct alloc_wrap_data	  wrapped;
230 	const char		 *name;
231 	struct group		**grpp;
232 };
233 
234 static int
235 wrapped_getgrnam_r(void *data, const struct alloc *alloc)
236 {
237 	struct group *grp = alloc->start;
238 	struct group *result;
239 	struct getgrnam_wrapper_data *d = data;
240 	int error;
241 
242 	assert(alloc->size >= sizeof(*grp));
243 
244 	error = getgrnam_r(d->name, grp, (char *)(grp + 1),
245 	    alloc->size - sizeof(*grp), &result);
246 
247 	if (error == 0) {
248 		if (result == NULL)
249 			error = ENOENT;
250 	} else
251 		assert(result == NULL);
252 	*d->grpp = result;
253 	return (error);
254 }
255 
256 /*
257  * Wraps getgrnam_r(), automatically dealing with memory allocation.
258  *
259  * 'alloc' may be any allocation (even empty), and will be extended as
260  * necessary.  It is not freed on error.
261  *
262  * On success, '*grpp' is filled with a pointer to the returned 'struct group',
263  * and on failure, is set to NULL.
264  */
265 static int
266 alloc_getgrnam(const char *const name, struct group **const grpp,
267     struct alloc *const alloc)
268 {
269 	struct getgrnam_wrapper_data data;
270 
271 	data.wrapped.func = wrapped_getgrnam_r;
272 	data.name = name;
273 	data.grpp = grpp;
274 	return (alloc_wrap((struct alloc_wrap_data *)&data, alloc));
275 }
276 
277 /*
278  * Retrieve the UID from a user string.
279  *
280  * Tries first to interpret the string as a user name, then as a numeric ID
281  * (this order is prescribed by POSIX for a number of utilities).
282  *
283  * 'pwdp' and 'allocp' must be NULL or non-NULL together.  If non-NULL, then
284  * 'allocp' can be any allocation (possibly empty) and will be extended to
285  * contain the result if necessary.  It will not be freed (even on failure).
286  */
287 static uid_t
288 parse_user_pwd(const char *s, struct passwd **pwdp, struct alloc *allocp)
289 {
290 	struct passwd *pwd;
291 	struct alloc alloc = ALLOC_INITIALIZER;
292 	const char *errp;
293 	uid_t uid;
294 	int error;
295 
296 	assert((pwdp == NULL && allocp == NULL) ||
297 	    (pwdp != NULL && allocp != NULL));
298 
299 	if (pwdp == NULL) {
300 		pwdp = &pwd;
301 		allocp = &alloc;
302 	}
303 
304 	error = alloc_getpwnam(s, pwdp, allocp);
305 	if (error == 0) {
306 		uid = (*pwdp)->pw_uid;
307 		goto finish;
308 	} else if (error != ENOENT)
309 		errc(EXIT_FAILURE, error,
310 		    "cannot access the password database");
311 
312 	uid = strtonum(s, 0, UID_MAX, &errp);
313 	if (errp != NULL)
314 		errx(EXIT_FAILURE, "invalid UID '%s': %s", s, errp);
315 
316 finish:
317 	if (allocp == &alloc)
318 		alloc_free(allocp);
319 	return (uid);
320 }
321 
322 /* See parse_user_pwd() for the doc. */
323 static uid_t
324 parse_user(const char *s)
325 {
326 	return (parse_user_pwd(s, NULL, NULL));
327 }
328 
329 /*
330  * Retrieve the GID from a group string.
331  *
332  * Tries first to interpret the string as a group name, then as a numeric ID
333  * (this order is prescribed by POSIX for a number of utilities).
334  */
335 static gid_t
336 parse_group(const char *s)
337 {
338 	struct group *grp;
339 	struct alloc alloc = ALLOC_INITIALIZER;
340 	const char *errp;
341 	gid_t gid;
342 	int error;
343 
344 	error = alloc_getgrnam(s, &grp, &alloc);
345 	if (error == 0) {
346 		gid = grp->gr_gid;
347 		goto finish;
348 	} else if (error != ENOENT)
349 		errc(EXIT_FAILURE, error, "cannot access the group database");
350 
351 	gid = strtonum(s, 0, GID_MAX, &errp);
352 	if (errp != NULL)
353 		errx(EXIT_FAILURE, "invalid GID '%s': %s", s, errp);
354 
355 finish:
356 	alloc_free(&alloc);
357 	return (gid);
358 }
359 
360 struct group_array {
361 	u_int	 nb;
362 	gid_t	*groups;
363 };
364 
365 static const struct group_array GROUP_ARRAY_INITIALIZER = {
366 	.nb = 0,
367 	.groups = NULL,
368 };
369 
370 static bool
371 group_array_is_empty(const struct group_array *const ga)
372 {
373 	return (ga->nb == 0);
374 }
375 
376 static void
377 realloc_groups(struct group_array *const ga, const u_int diff)
378 {
379 	const u_int new_nb = ga->nb + diff;
380 	const size_t new_size = new_nb * sizeof(*ga->groups);
381 
382 	assert(new_nb >= diff && new_size >= new_nb);
383 	ga->groups = realloc(ga->groups, new_size);
384 	if (ga->groups == NULL)
385 		err(EXIT_FAILURE, "realloc of groups failed");
386 	ga->nb = new_nb;
387 }
388 
389 static int
390 gidp_cmp(const void *p1, const void *p2)
391 {
392 	const gid_t g1 = *(const gid_t *)p1;
393 	const gid_t g2 = *(const gid_t *)p2;
394 
395 	return ((g1 > g2) - (g1 < g2));
396 }
397 
398 static void
399 sort_uniq_groups(struct group_array *const ga)
400 {
401 	size_t j = 0;
402 
403 	if (ga->nb <= 1)
404 		return;
405 
406 	qsort(ga->groups, ga->nb, sizeof(gid_t), gidp_cmp);
407 
408 	for (size_t i = 1; i < ga->nb; ++i)
409 		if (ga->groups[i] != ga->groups[j])
410 			ga->groups[++j] = ga->groups[i];
411 }
412 
413 /*
414  * Remove elements in 'set' that are in 'remove'.
415  *
416  * Expects both arrays to have been treated with sort_uniq_groups(). Works in
417  * O(n + m), modifying 'set' in place.
418  */
419 static void
420 remove_groups(struct group_array *const set,
421     const struct group_array *const remove)
422 {
423 	u_int from = 0, to = 0, rem = 0;
424 	gid_t cand, to_rem;
425 
426 	if (set->nb == 0 || remove->nb == 0)
427 		/* Nothing to remove. */
428 		return;
429 
430 	cand = set->groups[0];
431 	to_rem = remove->groups[0];
432 
433 	for (;;) {
434 		if (cand < to_rem) {
435 			/* Keep. */
436 			if (to != from)
437 				set->groups[to] = cand;
438 			++to;
439 			cand = set->groups[++from];
440 			if (from == set->nb)
441 				break;
442 		} else if (cand == to_rem) {
443 			cand = set->groups[++from];
444 			if (from == set->nb)
445 				break;
446 			to_rem = remove->groups[++rem]; /* No duplicates. */
447 			if (rem == remove->nb)
448 				break;
449 		} else {
450 			to_rem = remove->groups[++rem];
451 			if (rem == remove->nb)
452 				break;
453 		}
454 	}
455 
456 	/* All remaining groups in 'set' must be kept. */
457 	if (from == to)
458 		/* Nothing was removed.  'set' will stay the same. */
459 		return;
460 	memmove(set->groups + to, set->groups + from,
461 	    (set->nb - from) * sizeof(gid_t));
462 	set->nb = to + (set->nb - from);
463 }
464 
465 int
466 main(int argc, char **argv)
467 {
468 	const char *const default_user = "root";
469 
470 	const char *user_name = NULL;
471 	const char *primary_group = NULL;
472 	char *supp_groups_str = NULL;
473 	char *supp_mod_str = NULL;
474 	bool start_from_current_groups = false;
475 	bool start_from_current_users = false;
476 	const char *euid_str = NULL;
477 	const char *ruid_str = NULL;
478 	const char *svuid_str = NULL;
479 	const char *egid_str = NULL;
480 	const char *rgid_str = NULL;
481 	const char *svgid_str = NULL;
482 	bool need_user = false; /* '-u' or '-k' needed. */
483 
484 	const int go_euid = 1000;
485 	const int go_ruid = 1001;
486 	const int go_svuid = 1002;
487 	const int go_egid = 1003;
488 	const int go_rgid = 1004;
489 	const int go_svgid = 1005;
490 	const struct option longopts[] = {
491 		{"euid", required_argument, NULL, go_euid},
492 		{"ruid", required_argument, NULL, go_ruid},
493 		{"svuid", required_argument, NULL, go_svuid},
494 		{"egid", required_argument, NULL, go_egid},
495 		{"rgid", required_argument, NULL, go_rgid},
496 		{"svgid", required_argument, NULL, go_svgid},
497 		{NULL, 0, NULL, 0}
498 	};
499 	int ch;
500 
501 	struct setcred wcred = SETCRED_INITIALIZER;
502 	u_int setcred_flags = 0;
503 
504 	struct passwd *pw = NULL;
505 	struct alloc pw_alloc = ALLOC_INITIALIZER;
506 	struct group_array supp_groups = GROUP_ARRAY_INITIALIZER;
507 	struct group_array supp_rem = GROUP_ARRAY_INITIALIZER;
508 
509 
510 	/*
511 	 * Process options.
512 	 */
513 	while (ch = getopt_long(argc, argv, "+G:g:hiks:u:", longopts, NULL),
514 	    ch != -1) {
515 		switch (ch) {
516 		case 'G':
517 			supp_groups_str = optarg;
518 			need_user = true;
519 			break;
520 		case 'g':
521 			primary_group = optarg;
522 			need_user = true;
523 			break;
524 		case 'h':
525 			usage();
526 		case 'i':
527 			start_from_current_groups = true;
528 			break;
529 		case 'k':
530 			start_from_current_users = true;
531 			break;
532 		case 's':
533 			supp_mod_str = optarg;
534 			need_user = true;
535 			break;
536 		case 'u':
537 			user_name = optarg;
538 			break;
539 		case go_euid:
540 			euid_str = optarg;
541 			need_user = true;
542 			break;
543 		case go_ruid:
544 			ruid_str = optarg;
545 			need_user = true;
546 			break;
547 		case go_svuid:
548 			svuid_str = optarg;
549 			need_user = true;
550 			break;
551 		case go_egid:
552 			egid_str = optarg;
553 			need_user = true;
554 			break;
555 		case go_rgid:
556 			rgid_str = optarg;
557 			need_user = true;
558 			break;
559 		case go_svgid:
560 			svgid_str = optarg;
561 			need_user = true;
562 			break;
563 		default:
564 			usage();
565 		}
566 	}
567 
568 	argc -= optind;
569 	argv += optind;
570 
571 	/*
572 	 * Determine users.
573 	 *
574 	 * We do that first as in some cases we need to retrieve the
575 	 * corresponding password database entry to be able to set the primary
576 	 * groups.
577 	 */
578 
579 	if (start_from_current_users) {
580 		if (user_name != NULL)
581 			errx(EXIT_FAILURE, "-k incompatible with -u");
582 
583 		/*
584 		 * If starting from the current user(s) as a base, finding one
585 		 * of them in the password database and using its groups would
586 		 * be quite surprising, so we instead let '-k' imply '-i'.
587 		 */
588 		start_from_current_groups = true;
589 	} else {
590 		uid_t uid;
591 
592 		/*
593 		 * In the case of any overrides, we impose an explicit base user
594 		 * via '-u' or '-k' instead of implicitly taking 'root' as the
595 		 * base.
596 		 */
597 		if (user_name == NULL) {
598 			if (need_user)
599 				errx(EXIT_FAILURE,
600 				    "Some overrides specified, "
601 				    "'-u' or '-k' needed.");
602 			user_name = default_user;
603 		}
604 
605 		/*
606 		 * Even if all user overrides are present as well as primary and
607 		 * supplementary groups ones, in which case the final result
608 		 * doesn't depend on '-u', we still call parse_user_pwd() to
609 		 * check that the passed username is correct.
610 		 */
611 		uid = parse_user_pwd(user_name, &pw, &pw_alloc);
612 		wcred.sc_uid = wcred.sc_ruid = wcred.sc_svuid = uid;
613 		setcred_flags |= SETCREDF_UID | SETCREDF_RUID |
614 		    SETCREDF_SVUID;
615 	}
616 
617 	if (euid_str != NULL) {
618 		wcred.sc_uid = parse_user(euid_str);
619 		setcred_flags |= SETCREDF_UID;
620 	}
621 
622 	if (ruid_str != NULL) {
623 		wcred.sc_ruid = parse_user(ruid_str);
624 		setcred_flags |= SETCREDF_RUID;
625 	}
626 
627 	if (svuid_str != NULL) {
628 		wcred.sc_svuid = parse_user(svuid_str);
629 		setcred_flags |= SETCREDF_SVUID;
630 	}
631 
632 	/*
633 	 * Determine primary groups.
634 	 */
635 
636 	/*
637 	 * When not starting from the current groups, we need to set all
638 	 * primary groups.  If '-g' was not passed, we use the primary
639 	 * group from the password database as the "base" to which
640 	 * overrides '--egid', '--rgid' and '--svgid' apply.  But if all
641 	 * overrides were specified, we in fact just don't need the
642 	 * password database at all.
643 	 *
644 	 * '-g' is treated outside of this 'if' as it can also be used
645 	 * as an override.
646 	 */
647 	if (!start_from_current_groups && primary_group == NULL &&
648 	    (egid_str == NULL || rgid_str == NULL || svgid_str == NULL)) {
649 		if (pw == NULL)
650 			errx(EXIT_FAILURE,
651 			    "must specify primary groups or a user name "
652 			    "with an entry in the password database");
653 
654 		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid =
655 		    pw->pw_gid;
656 		setcred_flags |= SETCREDF_GID | SETCREDF_RGID |
657 		    SETCREDF_SVGID;
658 	}
659 
660 	if (primary_group != NULL) {
661 		/*
662 		 * We always call parse_group() even in case all overrides are
663 		 * present to check that the passed group is valid.
664 		 */
665 		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid =
666 		    parse_group(primary_group);
667 		setcred_flags |= SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID;
668 	}
669 
670 	if (egid_str != NULL) {
671 		wcred.sc_gid = parse_group(egid_str);
672 		setcred_flags |= SETCREDF_GID;
673 	}
674 
675 	if (rgid_str != NULL) {
676 		wcred.sc_rgid = parse_group(rgid_str);
677 		setcred_flags |= SETCREDF_RGID;
678 	}
679 
680 	if (svgid_str != NULL) {
681 		wcred.sc_svgid = parse_group(svgid_str);
682 		setcred_flags |= SETCREDF_SVGID;
683 	}
684 
685 	/*
686 	 * Determine supplementary groups.
687 	 */
688 
689 	/*
690 	 * This makes sense to catch user's mistakes.  It is not a strong
691 	 * limitation of the code below (allowing this case is just a matter of,
692 	 * in the block treating '-s' with '@' below, replacing an assert() by
693 	 * a reset of 'supp_groups').
694 	 */
695 	if (supp_groups_str != NULL && supp_mod_str != NULL &&
696 	    supp_mod_str[0] == '@')
697 		errx(EXIT_FAILURE, "'-G' and '-s' with '@' are incompatible");
698 
699 	/*
700 	 * Determine the supplementary groups to start with, but only if we
701 	 * really need to operate on them later (and set them back).
702 	 */
703 	if (!start_from_current_groups) {
704 		assert(!start_from_current_users);
705 
706 		if (supp_groups_str == NULL && (supp_mod_str == NULL ||
707 		    supp_mod_str[0] != '@')) {
708 			/*
709 			 * If we are to replace supplementary groups (i.e.,
710 			 * neither '-i' nor '-k' was specified) and they are not
711 			 * completely specified (with '-g' or '-s' with '@'), we
712 			 * start from those in the groups database if we were
713 			 * passed a user name that is in the password database
714 			 * (this is a protection against erroneous ID/name
715 			 * conflation in the groups database), else we simply
716 			 * error.
717 			 */
718 
719 			if (pw == NULL)
720 				errx(EXIT_FAILURE,
721 				    "must specify the full supplementary "
722 				    "groups set or a user name with an entry "
723 				    "in the password database");
724 
725 			const long ngroups_alloc = sysconf(_SC_NGROUPS_MAX) + 1;
726 			gid_t *groups;
727 			int ngroups;
728 
729 			groups = malloc(sizeof(*groups) * ngroups_alloc);
730 			if (groups == NULL)
731 				errx(EXIT_FAILURE,
732 				    "cannot allocate memory to retrieve "
733 				    "user groups from the groups database");
734 
735 			ngroups = ngroups_alloc;
736 			getgrouplist(user_name, pw->pw_gid, groups, &ngroups);
737 
738 			if (ngroups > ngroups_alloc)
739 				err(EXIT_FAILURE,
740 				    "too many groups for user '%s'",
741 				    user_name);
742 
743 			realloc_groups(&supp_groups, ngroups);
744 			memcpy(supp_groups.groups + supp_groups.nb - ngroups,
745 			    groups, ngroups * sizeof(*groups));
746 			free(groups);
747 
748 			/*
749 			 * Have to set SETCREDF_SUPP_GROUPS here since we may be
750 			 * in the case where neither '-G' nor '-s' was passed,
751 			 * but we still have to set the supplementary groups to
752 			 * those of the groups database.
753 			 */
754 			setcred_flags |= SETCREDF_SUPP_GROUPS;
755 		}
756 	} else if (supp_groups_str == NULL && (supp_mod_str == NULL ||
757 	    supp_mod_str[0] != '@')) {
758 		const int ngroups = getgroups(0, NULL);
759 
760 		if (ngroups > 0) {
761 			realloc_groups(&supp_groups, ngroups);
762 
763 			if (getgroups(ngroups, supp_groups.groups +
764 			    supp_groups.nb - ngroups) < 0)
765 				err(EXIT_FAILURE, "getgroups() failed");
766 		}
767 
768 		/*
769 		 * Setting SETCREDF_SUPP_GROUPS here is not necessary, we will
770 		 * do it below since 'supp_mod_str' != NULL.
771 		 */
772 	}
773 
774 	if (supp_groups_str != NULL) {
775 		char *p = supp_groups_str;
776 		char *tok;
777 
778 		/*
779 		 * We will set the supplementary groups to exactly the set
780 		 * passed with '-G', and we took care above not to retrieve
781 		 * "base" groups (current ones or those from the groups
782 		 * database) in this case.
783 		 */
784 		assert(group_array_is_empty(&supp_groups));
785 
786 		/* WARNING: 'supp_groups_str' going to be modified. */
787 		while ((tok = strsep(&p, ",")) != NULL) {
788 			gid_t g;
789 
790 			if (*tok == '\0')
791 				continue;
792 
793 			g = parse_group(tok);
794 			realloc_groups(&supp_groups, 1);
795 			supp_groups.groups[supp_groups.nb - 1] = g;
796 		}
797 
798 		setcred_flags |= SETCREDF_SUPP_GROUPS;
799 	}
800 
801 	if (supp_mod_str != NULL) {
802 		char *p = supp_mod_str;
803 		char *tok;
804 		gid_t gid;
805 
806 		/* WARNING: 'supp_mod_str' going to be modified. */
807 		while ((tok = strsep(&p, ",")) != NULL) {
808 			switch (tok[0]) {
809 			case '\0':
810 			        break;
811 
812 			case '@':
813 				if (tok != supp_mod_str)
814 					errx(EXIT_FAILURE, "'@' must be "
815 					    "the first token in '-s' option");
816 				/* See same assert() above. */
817 				assert(group_array_is_empty(&supp_groups));
818 				break;
819 
820 			case '+':
821 			case '-':
822 				gid = parse_group(tok + 1);
823 				if (tok[0] == '+') {
824 					realloc_groups(&supp_groups, 1);
825 					supp_groups.groups[supp_groups.nb - 1] = gid;
826 				} else {
827 					realloc_groups(&supp_rem, 1);
828 					supp_rem.groups[supp_rem.nb - 1] = gid;
829 				}
830 				break;
831 
832 			default:
833 				errx(EXIT_FAILURE,
834 				    "invalid '-s' token '%s' at index %zu",
835 				    tok, tok - supp_mod_str);
836 			}
837 		}
838 
839 		setcred_flags |= SETCREDF_SUPP_GROUPS;
840 	}
841 
842 	/*
843 	 * We don't need to pass the kernel a normalized representation of the
844 	 * new supplementary groups set (array sorted and without duplicates),
845 	 * so we don't do it here unless we need to remove some groups, where it
846 	 * enables more efficient algorithms (if the number of groups for some
847 	 * reason grows out of control).
848 	 */
849 	if (!group_array_is_empty(&supp_groups) &&
850 	    !group_array_is_empty(&supp_rem)) {
851 		sort_uniq_groups(&supp_groups);
852 		sort_uniq_groups(&supp_rem);
853 		remove_groups(&supp_groups, &supp_rem);
854 	}
855 
856 	if ((setcred_flags & SETCREDF_SUPP_GROUPS) != 0) {
857 		wcred.sc_supp_groups = supp_groups.groups;
858 		wcred.sc_supp_groups_nb = supp_groups.nb;
859 	}
860 
861 	if (setcred(setcred_flags, &wcred, sizeof(wcred)) != 0)
862 		err(EXIT_FAILURE, "setcred()");
863 
864 	/*
865 	 * We don't bother freeing memory still allocated at this point as we
866 	 * are about to exec() or exit.
867 	 */
868 
869 	if (*argv == NULL) {
870 		const char *sh = getenv("SHELL");
871 
872 		if (sh == NULL)
873 			sh = _PATH_BSHELL;
874 		execlp(sh, sh, "-i", NULL);
875 	} else {
876 		execvp(argv[0], argv);
877 	}
878 	err(EXIT_FAILURE, "exec failed");
879 }
880