1 /* $NetBSD: su.c,v 1.55 2003/06/18 21:02:03 jrf Exp $ */ 2 3 /* 4 * Copyright (c) 1988 The Regents of the University of California. 5 * 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 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 __COPYRIGHT( 39 "@(#) Copyright (c) 1988 The Regents of the University of California.\n\ 40 All rights reserved.\n"); 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";*/ 46 #else 47 __RCSID("$NetBSD: su.c,v 1.55 2003/06/18 21:02:03 jrf Exp $"); 48 #endif 49 #endif /* not lint */ 50 51 #include <sys/param.h> 52 #include <sys/time.h> 53 #include <sys/resource.h> 54 #include <err.h> 55 #include <errno.h> 56 #include <grp.h> 57 #include <paths.h> 58 #include <pwd.h> 59 #include <stdio.h> 60 #ifdef SKEY 61 #include <skey.h> 62 #endif 63 #include <stdlib.h> 64 #include <string.h> 65 #include <syslog.h> 66 #include <time.h> 67 #include <tzfile.h> 68 #include <unistd.h> 69 70 #ifdef LOGIN_CAP 71 #include <login_cap.h> 72 #endif 73 74 #ifdef KERBEROS 75 #include <des.h> 76 #include <krb.h> 77 #include <netdb.h> 78 79 static int kerberos __P((char *, char *, int)); 80 static int koktologin __P((char *, char *, char *)); 81 82 #endif 83 84 #ifdef KERBEROS5 85 #include <krb5.h> 86 87 static int kerberos5 __P((char *, char *, int)); 88 89 #endif 90 91 #if defined(KERBEROS) || defined(KERBEROS5) 92 93 #define ARGSTRX "-Kdflm" 94 95 int use_kerberos = 1; 96 97 #else 98 #define ARGSTRX "-dflm" 99 #endif 100 101 #ifndef SUGROUP 102 #define SUGROUP "wheel" 103 #endif 104 105 #ifdef LOGIN_CAP 106 #define ARGSTR ARGSTRX "c:" 107 #else 108 #define ARGSTR ARGSTRX 109 #endif 110 111 int main __P((int, char **)); 112 113 static int chshell __P((const char *)); 114 static char *ontty __P((void)); 115 static int check_ingroup __P((int, const char *, const char *, int)); 116 117 118 int 119 main(argc, argv) 120 int argc; 121 char **argv; 122 { 123 extern char **environ; 124 struct passwd *pwd; 125 char *p; 126 #ifdef BSD4_4 127 struct timeval tp; 128 #endif 129 uid_t ruid; 130 int asme, ch, asthem, fastlogin, prio, gohome; 131 enum { UNSET, YES, NO } iscsh = UNSET; 132 char *user, *shell, *avshell, *username, **np; 133 char *userpass, *class; 134 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN]; 135 time_t pw_warntime = _PASSWORD_WARNDAYS * SECSPERDAY; 136 #ifdef LOGIN_CAP 137 login_cap_t *lc; 138 #endif 139 140 asme = asthem = fastlogin = 0; 141 gohome = 1; 142 shell = class = NULL; 143 while ((ch = getopt(argc, argv, ARGSTR)) != -1) 144 switch((char)ch) { 145 #if defined(KERBEROS) || defined(KERBEROS5) 146 case 'K': 147 use_kerberos = 0; 148 break; 149 #endif 150 #ifdef LOGIN_CAP 151 case 'c': 152 class = optarg; 153 break; 154 #endif 155 case 'd': 156 asme = 0; 157 asthem = 1; 158 gohome = 0; 159 break; 160 case 'f': 161 fastlogin = 1; 162 break; 163 case '-': 164 case 'l': 165 asme = 0; 166 asthem = 1; 167 break; 168 case 'm': 169 asme = 1; 170 asthem = 0; 171 break; 172 case '?': 173 default: 174 (void)fprintf(stderr, 175 "Usage: %s [%s] [login [shell arguments]]\n", 176 getprogname(), ARGSTR); 177 exit(1); 178 } 179 argv += optind; 180 181 /* Lower the priority so su runs faster */ 182 errno = 0; 183 prio = getpriority(PRIO_PROCESS, 0); 184 if (errno) 185 prio = 0; 186 if (prio > -2) 187 (void)setpriority(PRIO_PROCESS, 0, -2); 188 openlog("su", 0, LOG_AUTH); 189 190 /* get current login name and shell */ 191 ruid = getuid(); 192 username = getlogin(); 193 if (username == NULL || (pwd = getpwnam(username)) == NULL || 194 pwd->pw_uid != ruid) 195 pwd = getpwuid(ruid); 196 if (pwd == NULL) 197 errx(1, "who are you?"); 198 username = strdup(pwd->pw_name); 199 userpass = strdup(pwd->pw_passwd); 200 if (username == NULL || userpass == NULL) 201 err(1, "strdup"); 202 203 204 if (asme) { 205 if (pwd->pw_shell && *pwd->pw_shell) { 206 strlcpy(shellbuf, pwd->pw_shell, sizeof(shellbuf)); 207 shell = shellbuf; 208 } else { 209 shell = _PATH_BSHELL; 210 iscsh = NO; 211 } 212 } 213 /* get target login information, default to root */ 214 user = *argv ? *argv : "root"; 215 np = *argv ? argv : argv-1; 216 217 if ((pwd = getpwnam(user)) == NULL) 218 errx(1, "unknown login %s", user); 219 220 #ifdef LOGIN_CAP 221 /* force the usage of specified class */ 222 if (class) { 223 if (ruid) 224 errx(1, "Only root may use -c"); 225 226 pwd->pw_class = class; 227 } 228 lc = login_getclass(pwd->pw_class); 229 230 pw_warntime = login_getcaptime(lc, "password-warn", 231 _PASSWORD_WARNDAYS * SECSPERDAY, 232 _PASSWORD_WARNDAYS * SECSPERDAY); 233 #endif 234 235 if (ruid 236 #ifdef KERBEROS5 237 && (!use_kerberos || kerberos5(username, user, pwd->pw_uid)) 238 #endif 239 #ifdef KERBEROS 240 && (!use_kerberos || kerberos(username, user, pwd->pw_uid)) 241 #endif 242 ) { 243 char *pass = pwd->pw_passwd; 244 int ok = pwd->pw_uid != 0; 245 246 #ifdef ROOTAUTH 247 /* 248 * Allow those in group rootauth to su to root, by supplying 249 * their own password. 250 */ 251 if (!ok) { 252 if ((ok = check_ingroup(-1, ROOTAUTH, username, 0))) { 253 pass = userpass; 254 user = username; 255 } 256 } 257 #endif 258 /* 259 * Only allow those in group SUGROUP to su to root, 260 * but only if that group has any members. 261 * If SUGROUP has no members, allow anyone to su root 262 */ 263 if (!ok) { 264 ok = check_ingroup(-1, SUGROUP, username, 1); 265 } 266 if (!ok) 267 errx(1, 268 "you are not listed in the correct secondary group (%s) to su %s.", 269 SUGROUP, user); 270 /* if target requires a password, verify it */ 271 if (*pass) { 272 p = getpass("Password:"); 273 #ifdef SKEY 274 if (strcasecmp(p, "s/key") == 0) { 275 if (skey_haskey(user)) 276 errx(1, "Sorry, you have no s/key."); 277 else { 278 if (skey_authenticate(user)) { 279 goto badlogin; 280 } 281 } 282 283 } else 284 #endif 285 if (strcmp(pass, crypt(p, pass))) { 286 #ifdef SKEY 287 badlogin: 288 #endif 289 fprintf(stderr, "Sorry\n"); 290 syslog(LOG_WARNING, 291 "BAD SU %s to %s%s", username, 292 pwd->pw_name, ontty()); 293 exit(1); 294 } 295 } 296 } 297 298 if (asme) { 299 /* if asme and non-standard target shell, must be root */ 300 if (!chshell(pwd->pw_shell) && ruid) 301 errx(1,"permission denied (shell)."); 302 } else if (pwd->pw_shell && *pwd->pw_shell) { 303 shell = pwd->pw_shell; 304 iscsh = UNSET; 305 } else { 306 shell = _PATH_BSHELL; 307 iscsh = NO; 308 } 309 310 if ((p = strrchr(shell, '/')) != NULL) 311 avshell = p+1; 312 else 313 avshell = shell; 314 315 /* if we're forking a csh, we want to slightly muck the args */ 316 if (iscsh == UNSET) 317 iscsh = strstr(avshell, "csh") ? YES : NO; 318 319 /* set permissions */ 320 #ifdef LOGIN_CAP 321 if (setusercontext(lc, pwd, pwd->pw_uid, 322 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) | 323 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER)) 324 err(1, "setting user context"); 325 #else 326 if (setgid(pwd->pw_gid) < 0) 327 err(1, "setgid"); 328 if (initgroups(user, pwd->pw_gid)) 329 errx(1, "initgroups failed"); 330 if (setuid(pwd->pw_uid) < 0) 331 err(1, "setuid"); 332 #endif 333 334 if (!asme) { 335 if (asthem) { 336 p = getenv("TERM"); 337 /* Create an empty environment */ 338 if ((environ = malloc(sizeof(char *))) == NULL) 339 err(1, NULL); 340 environ[0] = NULL; 341 #ifdef LOGIN_CAP 342 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH)) 343 err(1, "setting user context"); 344 #else 345 (void)setenv("PATH", _PATH_DEFPATH, 1); 346 #endif 347 if (p) 348 (void)setenv("TERM", p, 1); 349 if (gohome && chdir(pwd->pw_dir) < 0) 350 errx(1, "no directory"); 351 } 352 353 if (asthem || pwd->pw_uid) 354 (void)setenv("USER", pwd->pw_name, 1); 355 (void)setenv("HOME", pwd->pw_dir, 1); 356 (void)setenv("SHELL", shell, 1); 357 } 358 (void)setenv("SU_FROM", username, 1); 359 360 if (iscsh == YES) { 361 if (fastlogin) 362 *np-- = "-f"; 363 if (asme) 364 *np-- = "-m"; 365 } else { 366 if (fastlogin) 367 unsetenv("ENV"); 368 } 369 370 if (asthem) { 371 avshellbuf[0] = '-'; 372 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 373 avshell = avshellbuf; 374 } else if (iscsh == YES) { 375 /* csh strips the first character... */ 376 avshellbuf[0] = '_'; 377 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 378 avshell = avshellbuf; 379 } 380 *np = avshell; 381 382 #ifdef BSD4_4 383 if (pwd->pw_change || pwd->pw_expire) 384 (void)gettimeofday(&tp, (struct timezone *)NULL); 385 if (pwd->pw_change) { 386 if (tp.tv_sec >= pwd->pw_change) { 387 (void)printf("%s -- %s's password has expired.\n", 388 (ruid ? "Sorry" : "Note"), user); 389 if (ruid != 0) 390 exit(1); 391 } else if (pwd->pw_change - tp.tv_sec < pw_warntime) 392 (void)printf("Warning: %s's password expires on %s", 393 user, ctime(&pwd->pw_change)); 394 } 395 if (pwd->pw_expire) { 396 if (tp.tv_sec >= pwd->pw_expire) { 397 (void)printf("%s -- %s's account has expired.\n", 398 (ruid ? "Sorry" : "Note"), user); 399 if (ruid != 0) 400 exit(1); 401 } else if (pwd->pw_expire - tp.tv_sec < 402 _PASSWORD_WARNDAYS * SECSPERDAY) 403 (void)printf("Warning: %s's account expires on %s", 404 user, ctime(&pwd->pw_expire)); 405 } 406 #endif 407 if (ruid != 0) 408 syslog(LOG_NOTICE, "%s to %s%s", 409 username, pwd->pw_name, ontty()); 410 411 /* Raise our priority back to what we had before */ 412 (void)setpriority(PRIO_PROCESS, 0, prio); 413 414 execv(shell, np); 415 err(1, "%s", shell); 416 /* NOTREACHED */ 417 } 418 419 static int 420 chshell(sh) 421 const char *sh; 422 { 423 const char *cp; 424 425 setusershell(); 426 while ((cp = getusershell()) != NULL) 427 if (!strcmp(cp, sh)) 428 return (1); 429 return (0); 430 } 431 432 static char * 433 ontty() 434 { 435 char *p; 436 static char buf[MAXPATHLEN + 4]; 437 438 buf[0] = 0; 439 if ((p = ttyname(STDERR_FILENO)) != NULL) 440 (void)snprintf(buf, sizeof buf, " on %s", p); 441 return (buf); 442 } 443 444 #ifdef KERBEROS5 445 static int 446 kerberos5(username, user, uid) 447 char *username, *user; 448 int uid; 449 { 450 krb5_error_code ret; 451 krb5_context context; 452 krb5_principal princ = NULL; 453 krb5_ccache ccache, ccache2; 454 char *cc_name; 455 const char *filename; 456 457 ret = krb5_init_context(&context); 458 if (ret) 459 return (1); 460 461 if (strcmp (user, "root") == 0) 462 ret = krb5_make_principal(context, &princ, 463 NULL, username, "root", NULL); 464 else 465 ret = krb5_make_principal(context, &princ, 466 NULL, user, NULL); 467 if (ret) 468 goto fail; 469 if (!krb5_kuserok(context, princ, user) && !uid) { 470 warnx ("kerberos5: not in %s's ACL.", user); 471 goto fail; 472 } 473 ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &ccache); 474 if (ret) 475 goto fail; 476 ret = krb5_verify_user_lrealm(context, princ, ccache, NULL, TRUE, 477 NULL); 478 if (ret) { 479 krb5_cc_destroy(context, ccache); 480 switch (ret) { 481 case KRB5_LIBOS_PWDINTR : 482 break; 483 case KRB5KRB_AP_ERR_BAD_INTEGRITY: 484 case KRB5KRB_AP_ERR_MODIFIED: 485 krb5_warnx(context, "Password incorrect"); 486 break; 487 default : 488 krb5_warn(context, ret, "krb5_verify_user"); 489 break; 490 } 491 goto fail; 492 } 493 ret = krb5_cc_gen_new(context, &krb5_fcc_ops, &ccache2); 494 if (ret) { 495 krb5_cc_destroy(context, ccache); 496 goto fail; 497 } 498 ret = krb5_cc_copy_cache(context, ccache, ccache2); 499 if (ret) { 500 krb5_cc_destroy(context, ccache); 501 krb5_cc_destroy(context, ccache2); 502 goto fail; 503 } 504 505 filename = krb5_cc_get_name(context, ccache2); 506 asprintf(&cc_name, "%s:%s", krb5_cc_get_type(context, ccache2), 507 filename); 508 if (chown (filename, uid, -1) < 0) { 509 warn("chown %s", filename); 510 free(cc_name); 511 krb5_cc_destroy(context, ccache); 512 krb5_cc_destroy(context, ccache2); 513 goto fail; 514 } 515 516 setenv("KRB5CCNAME", cc_name, 1); 517 free(cc_name); 518 krb5_cc_close(context, ccache2); 519 krb5_cc_destroy(context, ccache); 520 return (0); 521 522 fail: 523 if (princ != NULL) 524 krb5_free_principal (context, princ); 525 krb5_free_context (context); 526 return (1); 527 } 528 #endif 529 530 #ifdef KERBEROS 531 static int 532 kerberos(username, user, uid) 533 char *username, *user; 534 int uid; 535 { 536 KTEXT_ST ticket; 537 AUTH_DAT authdata; 538 struct hostent *hp; 539 int kerno; 540 u_long faddr; 541 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN]; 542 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1]; 543 544 if (krb_get_lrealm(lrealm, 1) != KSUCCESS) 545 return (1); 546 if (koktologin(username, lrealm, user) && !uid) { 547 warnx("kerberos: not in %s's ACL.", user); 548 return (1); 549 } 550 (void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT, 551 user, getuid()); 552 553 (void)setenv("KRBTKFILE", krbtkfile, 1); 554 (void)krb_set_tkt_string(krbtkfile); 555 /* 556 * Set real as well as effective ID to 0 for the moment, 557 * to make the kerberos library do the right thing. 558 */ 559 if (setuid(0) < 0) { 560 warn("setuid"); 561 return (1); 562 } 563 564 /* 565 * Little trick here -- if we are su'ing to root, 566 * we need to get a ticket for "xxx.root", where xxx represents 567 * the name of the person su'ing. Otherwise (non-root case), 568 * we need to get a ticket for "yyy.", where yyy represents 569 * the name of the person being su'd to, and the instance is null 570 * 571 * We should have a way to set the ticket lifetime, 572 * with a system default for root. 573 */ 574 { 575 char prompt[128]; 576 char passw[256]; 577 578 (void)snprintf (prompt, sizeof(prompt), 579 "%s's Password: ", 580 krb_unparse_name_long ((uid == 0 ? username : user), 581 (uid == 0 ? "root" : ""), 582 lrealm)); 583 if (des_read_pw_string (passw, sizeof (passw), prompt, 0)) { 584 memset (passw, 0, sizeof (passw)); 585 return (1); 586 } 587 if (strlen(passw) == 0) 588 return (1); /* Empty passwords are not allowed */ 589 590 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user), 591 (uid == 0 ? "root" : ""), lrealm, 592 KRB_TICKET_GRANTING_TICKET, 593 lrealm, 594 DEFAULT_TKT_LIFE, 595 passw); 596 memset (passw, 0, strlen (passw)); 597 } 598 599 if (kerno != KSUCCESS) { 600 if (kerno == KDC_PR_UNKNOWN) { 601 warnx("kerberos: principal unknown: %s.%s@%s", 602 (uid == 0 ? username : user), 603 (uid == 0 ? "root" : ""), lrealm); 604 return (1); 605 } 606 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]); 607 syslog(LOG_WARNING, 608 "BAD Kerberos SU: %s to %s%s: %s", 609 username, user, ontty(), krb_err_txt[kerno]); 610 return (1); 611 } 612 613 if (chown(krbtkfile, uid, -1) < 0) { 614 warn("chown"); 615 (void)unlink(krbtkfile); 616 return (1); 617 } 618 619 (void)setpriority(PRIO_PROCESS, 0, -2); 620 621 if (gethostname(hostname, sizeof(hostname)) == -1) { 622 warn("gethostname"); 623 dest_tkt(); 624 return (1); 625 } 626 hostname[sizeof(hostname) - 1] = '\0'; 627 628 (void)strlcpy(savehost, krb_get_phost(hostname), sizeof(savehost)); 629 savehost[sizeof(savehost) - 1] = '\0'; 630 631 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33); 632 633 if (kerno == KDC_PR_UNKNOWN) { 634 warnx("Warning: TGT not verified."); 635 syslog(LOG_WARNING, 636 "%s to %s%s, TGT not verified (%s); %s.%s not registered?", 637 username, user, ontty(), krb_err_txt[kerno], 638 "rcmd", savehost); 639 } else if (kerno != KSUCCESS) { 640 warnx("Unable to use TGT: %s", krb_err_txt[kerno]); 641 syslog(LOG_WARNING, "failed su: %s to %s%s: %s", 642 username, user, ontty(), krb_err_txt[kerno]); 643 dest_tkt(); 644 return (1); 645 } else { 646 if (!(hp = gethostbyname(hostname))) { 647 warnx("can't get addr of %s", hostname); 648 dest_tkt(); 649 return (1); 650 } 651 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr)); 652 653 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr, 654 &authdata, "")) != KSUCCESS) { 655 warnx("kerberos: unable to verify rcmd ticket: %s", 656 krb_err_txt[kerno]); 657 syslog(LOG_WARNING, 658 "failed su: %s to %s%s: %s", username, 659 user, ontty(), krb_err_txt[kerno]); 660 dest_tkt(); 661 return (1); 662 } 663 } 664 return (0); 665 } 666 667 static int 668 koktologin(name, realm, toname) 669 char *name, *realm, *toname; 670 { 671 return krb_kuserok(name, 672 strcmp (toname, "root") == 0 ? "root" : "", 673 realm, 674 toname); 675 } 676 #endif 677 678 static int 679 check_ingroup (gid, gname, user, ifempty) 680 int gid; 681 const char *gname; 682 const char *user; 683 int ifempty; 684 { 685 struct group *gr; 686 char **g; 687 #ifdef SU_INDIRECT_GROUP 688 char **gr_mem; 689 int n = 0; 690 int i = 0; 691 #endif 692 int ok = 0; 693 694 if (gname == NULL) 695 gr = getgrgid((gid_t) gid); 696 else 697 gr = getgrnam(gname); 698 699 /* 700 * XXX we are relying on the fact that we only set ifempty when 701 * calling to check for SUGROUP and that is the only time a 702 * missing group is acceptable. 703 */ 704 if (gr == NULL) 705 return ifempty; 706 if (!*gr->gr_mem) /* empty */ 707 return ifempty; 708 709 /* 710 * Ok, first see if user is in gr_mem 711 */ 712 for (g = gr->gr_mem; *g; ++g) { 713 if (strcmp(*g, user) == 0) 714 return 1; /* ok */ 715 #ifdef SU_INDIRECT_GROUP 716 ++n; /* count them */ 717 #endif 718 } 719 #ifdef SU_INDIRECT_GROUP 720 /* 721 * No. 722 * Now we need to duplicate the gr_mem list, and recurse for 723 * each member to see if it is a group, and if so whether user is 724 * in it. 725 */ 726 gr_mem = malloc((n + 1) * sizeof (char *)); 727 for (g = gr->gr_mem, i = 0; *g; ++g) { 728 gr_mem[i] = strdup(*g); 729 if (!gr_mem[i]) 730 err(1, "strdup"); 731 i++; 732 } 733 gr_mem[i++] = NULL; 734 735 for (g = gr_mem; ok == 0 && *g; ++g) { 736 /* 737 * If we get this far we don't accept empty/missing groups. 738 */ 739 ok = check_ingroup(-1, *g, user, 0); 740 } 741 for (g = gr_mem; *g; ++g) { 742 free(*g); 743 } 744 free(gr_mem); 745 #endif 746 return ok; 747 } 748