#pragma prototyped
#include <ast.h>
#include "FEATURE/externs"
#include <ls.h>
#include <sig.h>
#include <error.h>
#include <sys/wait.h>
#include "version.h"
#define SPECIAL 04100
#define FDIN 10
#undef FDSYNC
#define FDSYNC 11
#define FDVERIFY 12
#undef BLKSIZE
#define BLKSIZE sizeof(char*)*1024
#define THISPROG "/etc/suid_exec"
#define DEFSHELL "/bin/sh"
static void error_exit(const char*);
static int in_dir(const char*, const char*);
static int endsh(const char*);
#ifndef _lib_setregid
# undef _lib_setreuid
#endif
#ifndef _lib_setreuid
static void setids(int,uid_t,gid_t);
static int mycopy(int, int);
static void maketemp(char*);
#else
static void setids(int,int,int);
#endif
static const char version[] = "\n@(#)$Id: suid_exec "SH_RELEASE" $\n";
static const char badopen[] = "cannot open";
static const char badexec[] = "cannot exec";
static const char devfd[] = "/dev/fd/10";
static char tmpname[] = "/tmp/SUIDXXXXXX";
static char **arglist;
static char *shell;
static char *command;
static uid_t ruserid;
static uid_t euserid;
static gid_t rgroupid;
static gid_t egroupid;
static struct stat statb;
int main(int argc,char *argv[])
{
register int m,n;
register char *p;
struct stat statx;
int mode;
uid_t effuid;
gid_t effgid;
NOT_USED(argc);
arglist = argv;
if((command = argv[1]) == 0)
error_exit(badexec);
ruserid = getuid();
euserid = geteuid();
rgroupid = getgid();
egroupid = getegid();
p = argv[0];
#ifndef _lib_setreuid
maketemp(tmpname);
if(strcmp(p,tmpname)==0)
{
if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 ||
(statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0)
error_exit(badexec);
close(FDSYNC);
if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 ||
!S_ISREG(statb.st_mode))
error_exit(badexec);
if(ruserid != euserid &&
((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid))
error_exit(badexec);
goto exec;
}
if(euserid)
error_exit(badexec);
#endif
n = open(p,0);
if(n == FDIN)
{
n = dup(n);
close(FDIN);
}
if(n < 0)
error_exit(badopen);
if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL)
euserid = ruserid;
else
euserid = statb.st_uid;
if(euserid == ruserid && egroupid == rgroupid)
{
if(access(p,X_OK) < 0)
error_exit(badexec);
}
else
{
while(*p++)
{
if(*p == '/' || *p == 0)
{
m = *p;
*p = 0;
if(eaccess(argv[0],X_OK) < 0)
error_exit(badexec);
*p = m;
}
}
p = argv[0];
}
if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode))
error_exit(badopen);
if(stat(p, &statx) < 0 ||
statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev)
error_exit(badexec);
if(stat(THISPROG, &statx) < 0 ||
(statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev))
error_exit(badexec);
close(FDIN);
if(fcntl(n,F_DUPFD,FDIN) != FDIN)
error_exit(badexec);
close(n);
effuid = euserid;
effgid = egroupid;
mode = 0;
if(statb.st_mode & S_ISUID)
effuid = statb.st_uid;
if(statb.st_mode & S_ISGID)
effgid = statb.st_gid;
if(effgid != egroupid)
if(effgid != rgroupid || setgid(rgroupid) < 0)
mode = S_ISGID;
if(mode)
{
if(effuid != ruserid)
mode |= S_ISUID;
}
else if(effuid)
{
if(effuid != ruserid || setuid(ruserid) < 0)
mode = S_ISUID;
}
if(mode)
setids(mode, effuid, effgid);
#ifndef _lib_setreuid
exec:
#endif
shell = getenv("SHELL");
if(shell == 0 || !endsh(shell) || (
!in_dir("/bin",shell) &&
!in_dir("/usr/bin",shell) &&
!in_dir("/usr/lbin",shell) &&
!in_dir("/usr/local/bin",shell)))
shell = DEFSHELL;
argv[0] = command;
argv[1] = (char*)devfd;
execv(shell,argv);
error_exit(badexec);
}
static int endsh(register const char *shell)
{
while(*shell)
shell++;
if(*--shell != 'h' || *--shell != 's')
return(0);
if(*--shell=='/')
return(1);
if(*shell=='k' && *--shell=='/')
return(1);
return(0);
}
static int in_dir(register const char *dir,register const char *shell)
{
while(*dir)
{
if(*dir++ != *shell++)
return(0);
}
return(*shell=='/');
}
static void error_exit(const char *message)
{
sfprintf(sfstdout,"%s: %s\n",command,message);
exit(126);
}
int eaccess(register const char *name, register int mode)
{
struct stat statb;
if (stat(name, &statb) == 0)
{
if(euserid == 0)
{
if(!S_ISREG(statb.st_mode) || mode != 1)
return(0);
mode = (S_IXUSR|S_IXGRP|S_IXOTH);
}
else if(euserid == statb.st_uid)
mode <<= 6;
else if(egroupid == statb.st_gid)
mode <<= 3;
#ifdef _lib_getgroups
else
{
static int maxgroups;
gid_t *groups=0;
register int n;
if(maxgroups==0)
{
if((maxgroups=getgroups(0,groups)) < 0)
{
maxgroups=NGROUPS_MAX;
}
}
groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t));
n = getgroups(maxgroups,groups);
while(--n >= 0)
{
if(groups[n] == statb.st_gid)
{
mode <<= 3;
break;
}
}
}
#endif
if(statb.st_mode & mode)
return(0);
}
return(-1);
}
#ifdef _lib_setreuid
static void setids(int mode,int owner,int group)
{
if(mode & S_ISGID)
setregid(rgroupid,group);
setreuid(ruserid,owner);
}
#else
static void setids(int mode,uid_t owner,gid_t group)
{
register int n,m;
int pv[2];
unlink(tmpname);
#ifdef O_EXCL
if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 ||
unlink(tmpname) < 0)
#else
if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0)
#endif
error_exit(badexec);
if(n != FDVERIFY)
{
close(FDVERIFY);
if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY)
error_exit(badexec);
}
mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6);
if(pipe(pv) < 0)
error_exit(badexec);
if((n=fork()) == 0)
{
close(FDVERIFY);
close(pv[1]);
if((n=fork()) == 0)
{
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
read(pv[0],pv,1);
while(unlink(tmpname) < 0 && errno == ETXTBSY)
sleep(1);
exit(0);
}
else if(n == -1)
exit(1);
else
{
if((m = open(THISPROG, O_RDONLY)) < 0)
exit(1);
if((mode & S_ISGID) && setgid(group) < 0)
exit(1);
if((mode & S_ISUID) && owner && setuid(owner) < 0)
exit(1);
#ifdef O_EXCL
if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0)
#else
unlink(tmpname);
if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
#endif
exit(1);
m = mycopy(m,n);
if(chmod(tmpname,mode) <0)
exit(1);
exit(m);
}
}
else if(n == -1)
error_exit(badexec);
else
{
arglist[0] = (char*)tmpname;
close(pv[0]);
if(pv[1] != FDSYNC)
{
close(FDSYNC);
if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC)
error_exit(badexec);
}
while((m = wait(0)) != n)
if(m == -1 && errno != EINTR)
break;
if(setuid(ruserid) < 0)
error_exit(badexec);
execv(tmpname,arglist);
error_exit(badexec);
}
}
static void maketemp(char *template)
{
register char *cp = template;
register pid_t n = getpid();
while(*++cp);
while(n > 0)
{
*--cp = (n%10) + '0';
n /= 10;
}
}
static int mycopy(int fdi, int fdo)
{
char buffer[BLKSIZE];
register int n;
while((n = read(fdi,buffer,BLKSIZE)) > 0)
if(write(fdo,buffer,n) != n)
break;
close(fdi);
close(fdo);
return n;
}
#endif