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
usage(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
alloc_is_empty(const struct alloc * const alloc)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
alloc_realloc(struct alloc * const alloc)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
alloc_free(struct alloc * const alloc)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
alloc_wrap(struct alloc_wrap_data * const data,struct alloc * alloc)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
wrapped_getpwnam_r(void * data,const struct alloc * alloc)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
alloc_getpwnam(const char * name,struct passwd ** pwdp,struct alloc * const alloc)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
wrapped_getgrnam_r(void * data,const struct alloc * alloc)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
alloc_getgrnam(const char * const name,struct group ** const grpp,struct alloc * const alloc)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
parse_user_pwd(const char * s,struct passwd ** pwdp,struct alloc * allocp)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
parse_user(const char * s)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
parse_group(const char * s)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
group_array_is_empty(const struct group_array * const ga)371 group_array_is_empty(const struct group_array *const ga)
372 {
373 return (ga->nb == 0);
374 }
375
376 static void
realloc_groups(struct group_array * const ga,const u_int diff)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
gidp_cmp(const void * p1,const void * p2)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
sort_uniq_groups(struct group_array * const ga)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
remove_groups(struct group_array * const set,const struct group_array * const remove)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
main(int argc,char ** argv)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 /*
759 * We do not need to determine the current groups if, as for the
760 * '!start_from_current_groups' case, we are going to replace
761 * them entirely, but here also if we do not amend them at all
762 * (because they are by definition already in place).
763 */
764 const int ngroups = getgroups(0, NULL);
765
766 if (ngroups > 0) {
767 realloc_groups(&supp_groups, ngroups);
768
769 if (getgroups(ngroups, supp_groups.groups +
770 supp_groups.nb - ngroups) < 0)
771 err(EXIT_FAILURE, "getgroups() failed");
772 }
773
774 /*
775 * Setting SETCREDF_SUPP_GROUPS here is not necessary, we will
776 * do it below since 'supp_mod_str' != NULL.
777 */
778 }
779
780 if (supp_groups_str != NULL) {
781 char *p = supp_groups_str;
782 char *tok;
783
784 /*
785 * We will set the supplementary groups to exactly the set
786 * passed with '-G', and we took care above not to retrieve
787 * "base" groups (current ones or those from the groups
788 * database) in this case.
789 */
790 assert(group_array_is_empty(&supp_groups));
791
792 /* WARNING: 'supp_groups_str' going to be modified. */
793 while ((tok = strsep(&p, ",")) != NULL) {
794 gid_t g;
795
796 if (*tok == '\0')
797 continue;
798
799 g = parse_group(tok);
800 realloc_groups(&supp_groups, 1);
801 supp_groups.groups[supp_groups.nb - 1] = g;
802 }
803
804 setcred_flags |= SETCREDF_SUPP_GROUPS;
805 }
806
807 if (supp_mod_str != NULL) {
808 char *p = supp_mod_str;
809 char *tok;
810 gid_t gid;
811
812 /* WARNING: 'supp_mod_str' going to be modified. */
813 while ((tok = strsep(&p, ",")) != NULL) {
814 switch (tok[0]) {
815 case '\0':
816 break;
817
818 case '@':
819 if (tok != supp_mod_str)
820 errx(EXIT_FAILURE, "'@' must be "
821 "the first token in '-s' option");
822 /* See same assert() above. */
823 assert(group_array_is_empty(&supp_groups));
824 break;
825
826 case '+':
827 case '-':
828 gid = parse_group(tok + 1);
829 if (tok[0] == '+') {
830 realloc_groups(&supp_groups, 1);
831 supp_groups.groups[supp_groups.nb - 1] = gid;
832 } else {
833 realloc_groups(&supp_rem, 1);
834 supp_rem.groups[supp_rem.nb - 1] = gid;
835 }
836 break;
837
838 default:
839 errx(EXIT_FAILURE,
840 "invalid '-s' token '%s' at index %zu",
841 tok, tok - supp_mod_str);
842 }
843 }
844
845 setcred_flags |= SETCREDF_SUPP_GROUPS;
846 }
847
848 /*
849 * We don't need to pass the kernel a normalized representation of the
850 * new supplementary groups set (array sorted and without duplicates),
851 * so we don't do it here unless we need to remove some groups, where it
852 * enables more efficient algorithms (if the number of groups for some
853 * reason grows out of control).
854 */
855 if (!group_array_is_empty(&supp_groups) &&
856 !group_array_is_empty(&supp_rem)) {
857 sort_uniq_groups(&supp_groups);
858 sort_uniq_groups(&supp_rem);
859 remove_groups(&supp_groups, &supp_rem);
860 }
861
862 if ((setcred_flags & SETCREDF_SUPP_GROUPS) != 0) {
863 wcred.sc_supp_groups = supp_groups.groups;
864 wcred.sc_supp_groups_nb = supp_groups.nb;
865 }
866
867 if (setcred(setcred_flags, &wcred, sizeof(wcred)) != 0)
868 err(EXIT_FAILURE, "setcred()");
869
870 /*
871 * We don't bother freeing memory still allocated at this point as we
872 * are about to exec() or exit.
873 */
874
875 if (*argv == NULL) {
876 const char *sh = getenv("SHELL");
877
878 if (sh == NULL)
879 sh = _PATH_BSHELL;
880 execlp(sh, sh, "-i", NULL);
881 } else {
882 execvp(argv[0], argv);
883 }
884 err(EXIT_FAILURE, "exec failed");
885 }
886