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