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