1*4887Schin /***********************************************************************
2*4887Schin *                                                                      *
3*4887Schin *               This software is part of the ast package               *
4*4887Schin *           Copyright (c) 1982-2007 AT&T Knowledge Ventures            *
5*4887Schin *                      and is licensed under the                       *
6*4887Schin *                  Common Public License, Version 1.0                  *
7*4887Schin *                      by AT&T Knowledge Ventures                      *
8*4887Schin *                                                                      *
9*4887Schin *                A copy of the License is available at                 *
10*4887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
11*4887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*4887Schin *                                                                      *
13*4887Schin *              Information and Software Systems Research               *
14*4887Schin *                            AT&T Research                             *
15*4887Schin *                           Florham Park NJ                            *
16*4887Schin *                                                                      *
17*4887Schin *                  David Korn <dgk@research.att.com>                   *
18*4887Schin *                                                                      *
19*4887Schin ***********************************************************************/
20*4887Schin #pragma prototyped
21*4887Schin /*
22*4887Schin  * This is a program to execute 'execute only' and suid/sgid shell scripts.
23*4887Schin  * This program must be owned by root and must have the set uid bit set.
24*4887Schin  * It must not have the set group id bit set.  This program must be installed
25*4887Schin  * where the define parameter THISPROG indicates to work correctly on system V
26*4887Schin  *
27*4887Schin  *  Written by David Korn
28*4887Schin  *  AT&T Labs
29*4887Schin  *  Enhanced by Rob Stampfli
30*4887Schin  */
31*4887Schin 
32*4887Schin /* The file name of the script to execute is argv[0]
33*4887Schin  * Argv[1] is the  program name
34*4887Schin  * The basic idea is to open the script as standard input, set the effective
35*4887Schin  *   user and group id correctly, and then exec the shell.
36*4887Schin  * The complicated part is getting the effective uid of the caller and
37*4887Schin  *   setting the effective uid/gid.  The program which execs this program
38*4887Schin  *   may pass file descriptor FDIN as an open file with mode SPECIAL if
39*4887Schin  *   the effective user id is not the real user id.  The effective
40*4887Schin  *   user id for authentication purposes will be the owner of this
41*4887Schin  *   open file.  On systems without the setreuid() call, e[ug]id is set
42*4887Schin  *   by copying this program to a /tmp/file, making it a suid and/or sgid
43*4887Schin  *   program, and then execing this program.
44*4887Schin  * A forked version of this program waits until it can unlink the /tmp
45*4887Schin  *   file and then exits.  Actually, we fork() twice so the parent can
46*4887Schin  *   wait for the child to complete.  A pipe is used to guarantee that we
47*4887Schin  *   do not remove the /tmp file too soon.
48*4887Schin  */
49*4887Schin 
50*4887Schin #include	<ast.h>
51*4887Schin #include	"FEATURE/externs"
52*4887Schin #include	<ls.h>
53*4887Schin #include	<sig.h>
54*4887Schin #include	<error.h>
55*4887Schin #include	<sys/wait.h>
56*4887Schin #include	"version.h"
57*4887Schin 
58*4887Schin #define SPECIAL		04100	/* setuid execute only by owner */
59*4887Schin #define FDIN		10	/* must be same as /dev/fd below */
60*4887Schin #undef FDSYNC
61*4887Schin #define FDSYNC		11	/* used on sys5 to synchronize cleanup */
62*4887Schin #define FDVERIFY	12	/* used to validate /tmp process */
63*4887Schin #undef BLKSIZE
64*4887Schin #define BLKSIZE		sizeof(char*)*1024
65*4887Schin #define THISPROG	"/etc/suid_exec"
66*4887Schin #define DEFSHELL	"/bin/sh"
67*4887Schin 
68*4887Schin static void error_exit(const char*);
69*4887Schin static int in_dir(const char*, const char*);
70*4887Schin static int endsh(const char*);
71*4887Schin #ifndef _lib_setregid
72*4887Schin #   undef _lib_setreuid
73*4887Schin #endif
74*4887Schin #ifndef _lib_setreuid
75*4887Schin     static void setids(int,uid_t,gid_t);
76*4887Schin     static int mycopy(int, int);
77*4887Schin     static void maketemp(char*);
78*4887Schin #else
79*4887Schin     static void setids(int,int,int);
80*4887Schin #endif /* _lib_setreuid */
81*4887Schin 
82*4887Schin static const char version[]	= "\n@(#)$Id: suid_exec "SH_RELEASE" $\n";
83*4887Schin static const char badopen[]	= "cannot open";
84*4887Schin static const char badexec[]	= "cannot exec";
85*4887Schin static const char devfd[]	= "/dev/fd/10";	/* must match FDIN above */
86*4887Schin static char tmpname[]		= "/tmp/SUIDXXXXXX";
87*4887Schin static char **arglist;
88*4887Schin 
89*4887Schin static char *shell;
90*4887Schin static char *command;
91*4887Schin static uid_t ruserid;
92*4887Schin static uid_t euserid;
93*4887Schin static gid_t rgroupid;
94*4887Schin static gid_t egroupid;
95*4887Schin static struct stat statb;
96*4887Schin 
97*4887Schin int main(int argc,char *argv[])
98*4887Schin {
99*4887Schin 	register int m,n;
100*4887Schin 	register char *p;
101*4887Schin 	struct stat statx;
102*4887Schin 	int mode;
103*4887Schin 	uid_t effuid;
104*4887Schin 	gid_t effgid;
105*4887Schin 	NOT_USED(argc);
106*4887Schin 	arglist = argv;
107*4887Schin 	if((command = argv[1]) == 0)
108*4887Schin 		error_exit(badexec);
109*4887Schin 	ruserid = getuid();
110*4887Schin 	euserid = geteuid();
111*4887Schin 	rgroupid = getgid();
112*4887Schin 	egroupid = getegid();
113*4887Schin 	p = argv[0];
114*4887Schin #ifndef _lib_setreuid
115*4887Schin 	maketemp(tmpname);
116*4887Schin 	if(strcmp(p,tmpname)==0)
117*4887Schin 	{
118*4887Schin 		/* At this point, the presumption is that we are the
119*4887Schin 		 * version of THISPROG copied into /tmp, with the owner,
120*4887Schin 		 * group, and setuid/gid bits correctly set.  This copy of
121*4887Schin 		 * the program is executable by anyone, so we must be careful
122*4887Schin 		 * not to allow just any invocation of it to succeed, since
123*4887Schin 		 * it is setuid/gid.  Validate the proper execution by
124*4887Schin 		 * examining the FDVERIFY file descriptor -- if it is owned
125*4887Schin 		 * by root and is mode SPECIAL, then this is proof that it was
126*4887Schin 		 * passed by a program with superuser privileges -- hence we
127*4887Schin 		 * can presume legitimacy.  Otherwise, bail out, as we suspect
128*4887Schin 		 * an impostor.
129*4887Schin 		 */
130*4887Schin 		if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 ||
131*4887Schin 		    (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0)
132*4887Schin 			error_exit(badexec);
133*4887Schin 		/* This enables the grandchild to clean up /tmp file */
134*4887Schin 		close(FDSYNC);
135*4887Schin 		/* Make sure that this is a valid invocation of the clone.
136*4887Schin 		 * Perhaps unnecessary, given FDVERIFY, but what the heck...
137*4887Schin 		 */
138*4887Schin 		if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 ||
139*4887Schin 		    !S_ISREG(statb.st_mode))
140*4887Schin 			error_exit(badexec);
141*4887Schin 		if(ruserid != euserid &&
142*4887Schin 		  ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid))
143*4887Schin 			error_exit(badexec);
144*4887Schin 		goto exec;
145*4887Schin 	}
146*4887Schin 	/* Make sure that this is the real setuid program, not the clone.
147*4887Schin 	 * It is possible by clever hacking to get past this point in the
148*4887Schin 	 * clone, but it doesn't do the hacker any good that I can see.
149*4887Schin 	 */
150*4887Schin 	if(euserid)
151*4887Schin 		error_exit(badexec);
152*4887Schin #endif /* _lib_setreuid */
153*4887Schin 	/* Open the script for reading first and then validate it.  This
154*4887Schin 	 * prevents someone from pulling a switcheroo while we are validating.
155*4887Schin 	 */
156*4887Schin 	n = open(p,0);
157*4887Schin 	if(n == FDIN)
158*4887Schin 	{
159*4887Schin 		n = dup(n);
160*4887Schin 		close(FDIN);
161*4887Schin 	}
162*4887Schin 	if(n < 0)
163*4887Schin 		error_exit(badopen);
164*4887Schin 	/* validate execution rights to this script */
165*4887Schin 	if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL)
166*4887Schin 		euserid = ruserid;
167*4887Schin 	else
168*4887Schin 		euserid = statb.st_uid;
169*4887Schin 	/* do it the easy way if you can */
170*4887Schin 	if(euserid == ruserid && egroupid == rgroupid)
171*4887Schin 	{
172*4887Schin 		if(access(p,X_OK) < 0)
173*4887Schin 			error_exit(badexec);
174*4887Schin 	}
175*4887Schin 	else
176*4887Schin 	{
177*4887Schin 		/* have to check access on each component */
178*4887Schin 		while(*p++)
179*4887Schin 		{
180*4887Schin 			if(*p == '/' || *p == 0)
181*4887Schin 			{
182*4887Schin 				m = *p;
183*4887Schin 				*p = 0;
184*4887Schin 				if(eaccess(argv[0],X_OK) < 0)
185*4887Schin 					error_exit(badexec);
186*4887Schin 				*p = m;
187*4887Schin 			}
188*4887Schin 		}
189*4887Schin 		p = argv[0];
190*4887Schin 	}
191*4887Schin 	if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode))
192*4887Schin 		error_exit(badopen);
193*4887Schin 	if(stat(p, &statx) < 0 ||
194*4887Schin 	  statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev)
195*4887Schin 		error_exit(badexec);
196*4887Schin 	if(stat(THISPROG, &statx) < 0 ||
197*4887Schin 	  (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev))
198*4887Schin 		error_exit(badexec);
199*4887Schin 	close(FDIN);
200*4887Schin 	if(fcntl(n,F_DUPFD,FDIN) != FDIN)
201*4887Schin 		error_exit(badexec);
202*4887Schin 	close(n);
203*4887Schin 
204*4887Schin 	/* compute the desired new effective user and group id */
205*4887Schin 	effuid = euserid;
206*4887Schin 	effgid = egroupid;
207*4887Schin 	mode = 0;
208*4887Schin 	if(statb.st_mode & S_ISUID)
209*4887Schin 		effuid = statb.st_uid;
210*4887Schin 	if(statb.st_mode & S_ISGID)
211*4887Schin 		effgid = statb.st_gid;
212*4887Schin 
213*4887Schin 	/* see if group needs setting */
214*4887Schin 	if(effgid != egroupid)
215*4887Schin 		if(effgid != rgroupid || setgid(rgroupid) < 0)
216*4887Schin 			mode = S_ISGID;
217*4887Schin 
218*4887Schin 	/* now see if the uid needs setting */
219*4887Schin 	if(mode)
220*4887Schin 	{
221*4887Schin 		if(effuid != ruserid)
222*4887Schin 			mode |= S_ISUID;
223*4887Schin 	}
224*4887Schin 	else if(effuid)
225*4887Schin 	{
226*4887Schin 		if(effuid != ruserid || setuid(ruserid) < 0)
227*4887Schin 			mode = S_ISUID;
228*4887Schin 	}
229*4887Schin 
230*4887Schin 	if(mode)
231*4887Schin 		setids(mode, effuid, effgid);
232*4887Schin #ifndef _lib_setreuid
233*4887Schin exec:
234*4887Schin #endif /* _lib_setreuid */
235*4887Schin 	/* only use SHELL if file is in trusted directory and ends in sh */
236*4887Schin 	shell = getenv("SHELL");
237*4887Schin 	if(shell == 0 || !endsh(shell) || (
238*4887Schin 		!in_dir("/bin",shell) &&
239*4887Schin 		!in_dir("/usr/bin",shell) &&
240*4887Schin 		!in_dir("/usr/lbin",shell) &&
241*4887Schin 		!in_dir("/usr/local/bin",shell)))
242*4887Schin 			shell = DEFSHELL;
243*4887Schin 	argv[0] = command;
244*4887Schin 	argv[1] = (char*)devfd;
245*4887Schin 	execv(shell,argv);
246*4887Schin 	error_exit(badexec);
247*4887Schin }
248*4887Schin 
249*4887Schin /*
250*4887Schin  * return true of shell ends in sh of ksh
251*4887Schin  */
252*4887Schin 
253*4887Schin static int endsh(register const char *shell)
254*4887Schin {
255*4887Schin 	while(*shell)
256*4887Schin 		shell++;
257*4887Schin 	if(*--shell != 'h' || *--shell != 's')
258*4887Schin 		return(0);
259*4887Schin 	if(*--shell=='/')
260*4887Schin 		return(1);
261*4887Schin 	if(*shell=='k' && *--shell=='/')
262*4887Schin 		return(1);
263*4887Schin 	return(0);
264*4887Schin }
265*4887Schin 
266*4887Schin 
267*4887Schin /*
268*4887Schin  * return true of shell is in <dir> directory
269*4887Schin  */
270*4887Schin 
271*4887Schin static int in_dir(register const char *dir,register const char *shell)
272*4887Schin {
273*4887Schin 	while(*dir)
274*4887Schin 	{
275*4887Schin 		if(*dir++ != *shell++)
276*4887Schin 			return(0);
277*4887Schin 	}
278*4887Schin 	/* return true if next character is a '/' */
279*4887Schin 	return(*shell=='/');
280*4887Schin }
281*4887Schin 
282*4887Schin static void error_exit(const char *message)
283*4887Schin {
284*4887Schin 	sfprintf(sfstdout,"%s: %s\n",command,message);
285*4887Schin 	exit(126);
286*4887Schin }
287*4887Schin 
288*4887Schin 
289*4887Schin /*
290*4887Schin  * This version of access checks against effective uid and effective gid
291*4887Schin  */
292*4887Schin 
293*4887Schin int eaccess(register const char *name, register int mode)
294*4887Schin {
295*4887Schin 	struct stat statb;
296*4887Schin 	if (stat(name, &statb) == 0)
297*4887Schin 	{
298*4887Schin 		if(euserid == 0)
299*4887Schin 		{
300*4887Schin 			if(!S_ISREG(statb.st_mode) || mode != 1)
301*4887Schin 				return(0);
302*4887Schin 		    	/* root needs execute permission for someone */
303*4887Schin 			mode = (S_IXUSR|S_IXGRP|S_IXOTH);
304*4887Schin 		}
305*4887Schin 		else if(euserid == statb.st_uid)
306*4887Schin 			mode <<= 6;
307*4887Schin 		else if(egroupid == statb.st_gid)
308*4887Schin 			mode <<= 3;
309*4887Schin #ifdef _lib_getgroups
310*4887Schin 		/* on some systems you can be in several groups */
311*4887Schin 		else
312*4887Schin 		{
313*4887Schin 			static int maxgroups;
314*4887Schin 			gid_t *groups=0;
315*4887Schin 			register int n;
316*4887Schin 			if(maxgroups==0)
317*4887Schin 			{
318*4887Schin 				/* first time */
319*4887Schin 				if((maxgroups=getgroups(0,groups)) < 0)
320*4887Schin 				{
321*4887Schin 					/* pre-POSIX system */
322*4887Schin 					maxgroups=NGROUPS_MAX;
323*4887Schin 				}
324*4887Schin 			}
325*4887Schin 			groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t));
326*4887Schin 			n = getgroups(maxgroups,groups);
327*4887Schin 			while(--n >= 0)
328*4887Schin 			{
329*4887Schin 				if(groups[n] == statb.st_gid)
330*4887Schin 				{
331*4887Schin 					mode <<= 3;
332*4887Schin 					break;
333*4887Schin 				}
334*4887Schin 			}
335*4887Schin 		}
336*4887Schin #endif /* _lib_getgroups */
337*4887Schin 		if(statb.st_mode & mode)
338*4887Schin 			return(0);
339*4887Schin 	}
340*4887Schin 	return(-1);
341*4887Schin }
342*4887Schin 
343*4887Schin #ifdef _lib_setreuid
344*4887Schin static void setids(int mode,int owner,int group)
345*4887Schin {
346*4887Schin 	if(mode & S_ISGID)
347*4887Schin 		setregid(rgroupid,group);
348*4887Schin 
349*4887Schin 	/* set effective uid even if S_ISUID is not set.  This is because
350*4887Schin 	 * we are *really* executing EUID root at this point.  Even if S_ISUID
351*4887Schin 	 * is not set, the value for owner that is passsed should be correct.
352*4887Schin 	 */
353*4887Schin 	setreuid(ruserid,owner);
354*4887Schin }
355*4887Schin 
356*4887Schin #else
357*4887Schin /*
358*4887Schin  * This version of setids creats a /tmp file and copies itself into it.
359*4887Schin  * The "clone" file is made executable with appropriate suid/sgid bits.
360*4887Schin  * Finally, the clone is exec'ed.  This file is unlinked by a grandchild
361*4887Schin  * of this program, who waits around until the text is free.
362*4887Schin  */
363*4887Schin 
364*4887Schin static void setids(int mode,uid_t owner,gid_t group)
365*4887Schin {
366*4887Schin 	register int n,m;
367*4887Schin 	int pv[2];
368*4887Schin 
369*4887Schin 	/*
370*4887Schin 	 * Create a token to pass to the new program for validation.
371*4887Schin 	 * This token can only be procured by someone running with an
372*4887Schin 	 * effective userid of root, and hence gives the clone a way to
373*4887Schin 	 * certify that it was really invoked by THISPROG.  Someone who
374*4887Schin 	 * is already root could spoof us, but why would they want to?
375*4887Schin 	 *
376*4887Schin 	 * Since we are root here, we must be careful:  What if someone
377*4887Schin 	 * linked a valuable file to tmpname?
378*4887Schin 	 */
379*4887Schin 	unlink(tmpname);	/* should normally fail */
380*4887Schin #ifdef O_EXCL
381*4887Schin 	if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 ||
382*4887Schin 		unlink(tmpname) < 0)
383*4887Schin #else
384*4887Schin 	if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0)
385*4887Schin #endif
386*4887Schin 		error_exit(badexec);
387*4887Schin 	if(n != FDVERIFY)
388*4887Schin 	{
389*4887Schin 		close(FDVERIFY);
390*4887Schin 		if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY)
391*4887Schin 			error_exit(badexec);
392*4887Schin 	}
393*4887Schin 	mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6);
394*4887Schin 	/* create a pipe for synchronization */
395*4887Schin 	if(pipe(pv) < 0)
396*4887Schin 		error_exit(badexec);
397*4887Schin 	if((n=fork()) == 0)
398*4887Schin 	{	/* child */
399*4887Schin 		close(FDVERIFY);
400*4887Schin 		close(pv[1]);
401*4887Schin 		if((n=fork()) == 0)
402*4887Schin 		{	/* grandchild -- cleans up clone file */
403*4887Schin 			signal(SIGHUP, SIG_IGN);
404*4887Schin 			signal(SIGINT, SIG_IGN);
405*4887Schin 			signal(SIGQUIT, SIG_IGN);
406*4887Schin 			signal(SIGTERM, SIG_IGN);
407*4887Schin 			read(pv[0],pv,1); /* wait for clone to close pipe */
408*4887Schin 			while(unlink(tmpname) < 0 && errno == ETXTBSY)
409*4887Schin 				sleep(1);
410*4887Schin 			exit(0);
411*4887Schin 	    	}
412*4887Schin 		else if(n == -1)
413*4887Schin 			exit(1);
414*4887Schin 		else
415*4887Schin 		{
416*4887Schin 			/* Create a set[ug]id file that will become the clone.
417*4887Schin 			 * To make this atomic, without need for chown(), the
418*4887Schin 			 * child takes on desired user and group.  The only
419*4887Schin 			 * downsize of this that I can see is that it may
420*4887Schin 			 * screw up some per- * user accounting.
421*4887Schin 			 */
422*4887Schin 			if((m = open(THISPROG, O_RDONLY)) < 0)
423*4887Schin 				exit(1);
424*4887Schin 			if((mode & S_ISGID) && setgid(group) < 0)
425*4887Schin 				exit(1);
426*4887Schin 			if((mode & S_ISUID) && owner && setuid(owner) < 0)
427*4887Schin 				exit(1);
428*4887Schin #ifdef O_EXCL
429*4887Schin 			if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0)
430*4887Schin #else
431*4887Schin 			unlink(tmpname);
432*4887Schin 			if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
433*4887Schin #endif /* O_EXCL */
434*4887Schin 				exit(1);
435*4887Schin 			/* populate the clone */
436*4887Schin 			m = mycopy(m,n);
437*4887Schin 			if(chmod(tmpname,mode) <0)
438*4887Schin 				exit(1);
439*4887Schin 			exit(m);
440*4887Schin 		}
441*4887Schin 	}
442*4887Schin 	else if(n == -1)
443*4887Schin 		error_exit(badexec);
444*4887Schin 	else
445*4887Schin 	{
446*4887Schin 		arglist[0] = (char*)tmpname;
447*4887Schin 		close(pv[0]);
448*4887Schin 		/* move write end of pipe into FDSYNC */
449*4887Schin 		if(pv[1] != FDSYNC)
450*4887Schin 		{
451*4887Schin 			close(FDSYNC);
452*4887Schin 			if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC)
453*4887Schin 				error_exit(badexec);
454*4887Schin 		}
455*4887Schin 		/* wait for child to die */
456*4887Schin 		while((m = wait(0)) != n)
457*4887Schin 			if(m == -1 && errno != EINTR)
458*4887Schin 				break;
459*4887Schin 		/* Kill any setuid status at this point.  That way, if the
460*4887Schin 		 * clone is not setuid, we won't exec it as root.  Also, don't
461*4887Schin 		 * neglect to consider that someone could have switched the
462*4887Schin 		 * clone file on us.
463*4887Schin 		 */
464*4887Schin 		if(setuid(ruserid) < 0)
465*4887Schin 			error_exit(badexec);
466*4887Schin 		execv(tmpname,arglist);
467*4887Schin 		error_exit(badexec);
468*4887Schin 	}
469*4887Schin }
470*4887Schin 
471*4887Schin /*
472*4887Schin  * create a unique name into the <template>
473*4887Schin  */
474*4887Schin 
475*4887Schin static void maketemp(char *template)
476*4887Schin {
477*4887Schin 	register char *cp = template;
478*4887Schin 	register pid_t n = getpid();
479*4887Schin 	/* skip to end of string */
480*4887Schin 	while(*++cp);
481*4887Schin 	/* convert process id to string */
482*4887Schin 	while(n > 0)
483*4887Schin 	{
484*4887Schin 		*--cp = (n%10) + '0';
485*4887Schin 		n /= 10;
486*4887Schin 	}
487*4887Schin 
488*4887Schin }
489*4887Schin 
490*4887Schin /*
491*4887Schin  *  copy THISPROG into the open file number <fdo> and close <fdo>
492*4887Schin  */
493*4887Schin 
494*4887Schin static int mycopy(int fdi, int fdo)
495*4887Schin {
496*4887Schin 	char buffer[BLKSIZE];
497*4887Schin 	register int n;
498*4887Schin 
499*4887Schin 	while((n = read(fdi,buffer,BLKSIZE)) > 0)
500*4887Schin 		if(write(fdo,buffer,n) != n)
501*4887Schin 			break;
502*4887Schin 	close(fdi);
503*4887Schin 	close(fdo);
504*4887Schin 	return n;
505*4887Schin }
506*4887Schin 
507*4887Schin #endif /* _lib_setreuid */
508*4887Schin 
509*4887Schin 
510