date and time created 88/07/21 17:34:21 by marc
authorMarc Teitelbaum <marc@ucbvax.Berkeley.EDU>
Fri, 22 Jul 1988 08:34:21 +0000 (00:34 -0800)
committerMarc Teitelbaum <marc@ucbvax.Berkeley.EDU>
Fri, 22 Jul 1988 08:34:21 +0000 (00:34 -0800)
SCCS-vsn: local/toolchest/ksh/sh/suid_exec.c 1.1

usr/src/local/toolchest/ksh/sh/suid_exec.c [new file with mode: 0644]

diff --git a/usr/src/local/toolchest/ksh/sh/suid_exec.c b/usr/src/local/toolchest/ksh/sh/suid_exec.c
new file mode 100644 (file)
index 0000000..7378407
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * This is a program to execute 'execute only' and suid/sgid shell scripts.
+ * This program must be owned by root and  must have the suid bit set.
+ * This program must be installed as define parameter THISPROG for this to work
+ *  on system V machines
+ *
+ *  Written by David Korn
+ *  AT&T Bell Laboratories
+ *  ulysses!dgk
+ */
+
+/* The file name of the script to execute is argv[0]
+ * Argv[1] is the  program name
+ * The basic idea is to open the script as standard input, set the effective
+ *   user and group id correctly, and then exec the shell.
+ * The complicated part is getting the effective uid of the caller and 
+ *   setting the effective uid/gid.  The program which execs this program
+ *   must pass file descriptor FDIN as an open file with mode SPECIAL if
+ *   the effective user id is not the real user id.  The effective
+ *   user id for authentication purposes will be the owner of this
+ *   open file.  On systems without the setreuid() call, e[ug]id is set
+ *   by copying this program to a /tmp/file, making it a suid and/or sgid
+ *   program, and then execing this program.
+ * A forked version of this program waits until it can unlink the /tmp
+ *   file and then exits.  Actually, we fork() twice so the parent can
+ *   wait for the child to complete.  A pipe is used to guarantee that we
+ *   do not remove the /tmp file too soon.
+ */
+
+#ifdef BSD
+# ifdef BSD_4_2
+#  include     <fcntl.h>
+#  include     <sys/param.h>
+# else
+#  define fcntl(a,b,c) dup2(a,c)
+#  include     <sys/types.h>
+# endif /* BSD_4_2 */
+#else
+#  include     <fcntl.h>
+# include      <sys/types.h>
+#endif /* BSD */
+#include       <sys/stat.h>
+#include       <errno.h>
+
+#define SPECIAL                04100   /* setuid execute only by owner */
+#define FDIN           10      /* must be same as /dev/fd below */
+#define FDSYNC         11      /* used on sys5 to syncronize cleanup */
+#define BLKSIZE                sizeof(char*)*1024
+#define THISPROG       "/etc/suid_exec"
+#define DEFSHELL       "/bin/sh"
+
+extern char *getenv();
+extern int errno;
+
+static int error();
+static int in_dir();
+static int endsh();
+#ifndef BSD_4_2
+static int copy();
+static void mktemp();
+#endif /* BSD_4_2 */
+
+static char version[]  = "@(#)suid_exec 06/03/86a";
+static char badopen[] = "cannot open";
+static char badexec[] = "cannot exec";
+static char tmpname[] = "/tmp/SUIDXXXXXX";
+static char devfd[]    = "/dev/fd/10";
+static char **arglist;
+
+static char *shell;
+static char *command;
+static int created;
+static int ruserid;
+static int euserid;
+static int rgroupid;
+static int egroupid;
+static struct stat statb;
+
+main(argc,argv)
+char *argv[];
+{
+       register int n;
+       register char *p;
+       int mode;
+       int effuid;
+       int effgid;
+       int priv = 0;
+       arglist = argv;
+       command = argv[1];
+       ruserid = getuid();
+       euserid = geteuid();
+       rgroupid = getgid();
+       egroupid = getegid();
+       p = argv[0];
+#ifndef BSD_4_2
+       mktemp(tmpname);
+       if(strcmp(p,tmpname)==0)
+       {
+               /* This enables the grandchild to clean up /tmp file */
+               close(FDSYNC);
+               /* make sure that this is a valid invocation of /tmp prog */
+               /* the /tmp file must be owned by root, setuid, and not a link */
+               if(euserid==0 && ruserid!=0)
+               {
+                       /* keep out forgers */
+                       if(stat(tmpname,&statb) < 0)
+                               error(badexec);
+                       /* don't trust /tmp file unless suid root */
+                       if((statb.st_mode&S_ISUID)==0 || statb.st_uid
+                               || statb.st_nlink!=1)
+                                       error(badexec);
+               }
+               goto exec;
+       }
+       /* make sure that this is the real setuid program, not the clone */
+       if(euserid || command==(char*)0)
+               error(badexec);
+#endif /* BSD_4_2 */
+       /* validate execution rights to this script */
+       if(fstat(FDIN,&statb)<0)
+       {
+               effuid++;
+               euserid = ruserid;
+       }
+       else
+       {
+               priv++;
+               if((statb.st_mode&~S_IFMT) != SPECIAL)
+                       error(badexec);
+               euserid = statb.st_uid;
+       }
+       /* do it the easy way if you can */
+       if(euserid == ruserid && egroupid==rgroupid)
+       {
+               if(access(p,1) < 0)
+                       error(badexec);
+       }
+       else
+       {
+               /* have to check access on each component */
+               while(*p++)
+               {
+                       if(*p == '/' || *p==0)
+                       {
+                               n = *p;
+                               *p = 0;
+                               if(eaccess(argv[0],1) < 0)
+                                       error(badexec);
+                               *p = n;
+                       }
+               }
+               p = argv[0];
+       }
+       /* open the script for reading and make it be FDIN */
+       n = open(p,0);
+       if(n < 0)
+               error(badopen);
+       if(fstat(n,&statb)<0)
+               error(badopen);
+       close(FDIN);
+       if(fcntl(n,F_DUPFD,FDIN)!=FDIN)
+               error(badexec);
+       close(n);
+       /* compute the desired new effective user and group id */
+       effuid = euserid;
+       effgid = egroupid;
+       mode = 0;
+       if(priv && statb.st_mode & S_ISUID)
+               effuid = statb.st_uid;
+       if(priv && statb.st_mode & S_ISGID)
+               effgid = statb.st_gid;
+       /* see if group needs setting */
+       if(effgid  != egroupid)
+               if(effgid != rgroupid || setgid(rgroupid)<0)
+                       mode = S_ISGID;
+               
+       /* now see if the uid needs setting */
+       if(effuid)
+               if(mode || effuid != ruserid || setuid(ruserid)<0)
+                       mode |= S_ISUID;
+       if(mode)
+               setids(mode,effuid,effgid);
+exec:
+       /* only use SHELL if file is in trusted directory and ends in sh */
+       shell = getenv("SHELL");
+       if(shell==0 || !endsh(shell) || (
+               !in_dir("/bin",shell) &&
+               !in_dir("/usr/bin",shell) &&
+               !in_dir("/usr/lbin",shell)))
+                       shell = DEFSHELL;
+       argv[0] = command;
+       argv[1] = devfd;
+       execv(shell,argv);
+       error(badexec);
+}
+
+/*
+ * return true of shell ends in sh
+ */
+
+static int endsh(shell)
+register char *shell;
+{
+       while(*shell)
+               shell++;
+       if(*--shell != 'h' || *--shell != 's')
+               return(0);
+       return(1);
+}
+
+
+/*
+ * return true of shell is in <dir> directory
+ */
+
+static int in_dir(dir,shell)
+register char *dir;
+register char *shell;
+{
+       while(*dir)
+       {
+               if(*dir++ != *shell++)
+                       return(0);
+       }
+       /* return true if next character is a '/' */
+       return(*shell=='/');
+}
+
+static int error(message)
+{
+       printf("%s: %s\n",command,message);
+       if(created)
+               unlink(tmpname);
+       exit(1);
+}
+
+
+/*
+ * This version of access checks against effective uid and effective gid
+ */
+
+eaccess(name, mode)
+register char  *name;
+register int mode;
+{      
+       struct stat statb;
+       if (stat(name, &statb) == 0)
+       {
+               if(euserid == 0)
+               {
+                       if((statb.st_mode&S_IFMT) != S_IFREG || mode != 1)
+                               return(0);
+                       /* root needs execute permission for someone */
+                       mode = (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6));
+               }
+               else if(euserid == statb.st_uid)
+                       mode <<= 6;
+               else if(egroupid == statb.st_gid)
+                       mode <<= 3;
+#ifdef BSD
+# ifdef BSD_4_2
+               /* in BSD_4_2 you can be in several groups */
+               else
+               {
+                       int groups[NGROUPS];
+                       register int n;
+                       n = getgroups(NGROUPS,groups);
+                       while(--n >= 0)
+                       {
+                               if(groups[n] == statb.st_gid)
+                               {
+                                       mode <<= 3;
+                                       break;
+                               }
+                       }
+               }
+# endif /* BSD_4_2 */
+#endif /* BSD */
+               if(statb.st_mode & mode)
+                       return(0);
+       }
+       return(-1);
+}
+
+#ifdef BSD_4_2
+setids(mode,owner,group)
+{
+       if(mode & S_ISGID)
+               setregid(rgroupid,group);
+       if(mode & S_ISUID)
+               setreuid(ruserid,owner);
+}
+
+#else
+/*
+ * This version of setids creats a /tmp file and copies this program into
+ * it.  The /tmp file is made executable with appropriate suid/sgid bits.
+ *  Finally, the /tmp file is exec'ed.  The /tmp file is unlinked by a
+ * grandchild of this program, who waits until the text is free
+ */
+
+setids(mode,owner,group)
+{
+       register int n;
+       int pv[2];
+       /* create a setuid program with a copy of this program without RW */
+       if((n=creat(tmpname,mode|SPECIAL)) < 0)
+               error(badexec);
+       created++;
+       if(chown(tmpname,owner,group) < 0)
+               error(badexec);
+       copy(n);
+       /* create a pipe for syncronization */
+       pv[0] = pv[1] = -1;
+       pipe(pv);
+       /* pipe failure could cause the /tmp file to be removed prematurely */
+       /*  This is not worth waiting for */
+       if((n=fork())==0)
+       {
+               char buf[2];
+               if(fork())
+                       exit(0);
+               /* the grandchild has to clean up the text file */
+               close(pv[1]);
+               /* wait until the parent closes the pipe */
+               read(pv[0],buf,1);
+               while(1)
+               {
+                       /* sleep granularity too crude to trust sleep(1) */
+                       sleep(1);
+                       if(unlink(tmpname) >= 0)
+                               exit(0);
+                       else if(errno != ETXTBSY)
+                               exit(1);
+               }
+       }
+       else if(n == -1)
+               error(badexec);
+       else
+       {
+               arglist[0] = tmpname;
+               close(pv[0]);
+               while(wait(0)!= -1);
+               /* put write end of pipe into FDSYNC */
+               close(FDSYNC);
+               if(pv[1] != FDSYNC)
+                       n = fcntl(pv[1],F_DUPFD,FDSYNC);
+               if(n != FDSYNC)
+                       close(n);
+               close(pv[1]);
+               execv(tmpname,arglist);
+               error(badexec);
+       }
+}
+
+/*
+ * create a unique name into the <template>
+ */
+
+static void mktemp(template)
+char *template;
+{
+       register char *cp = template;
+       register int n = getpid();
+       /* skip to end of string */
+       while(*++cp);
+       /* convert process id to string */
+       while(n > 0)
+       {
+               *--cp = (n%10) + '0';
+               n /= 10;
+       }
+       
+}
+
+/*
+ *  copy THISPROG into the open file number <fdo> and close <fdo>
+ */
+
+static int copy(fdo)
+int fdo;
+{
+       char buffer[BLKSIZE];
+       register int n;
+       int fdi;
+       if((fdi = open(THISPROG,0)) < 0)
+               error(badexec);
+       while((n = read(fdi,buffer,BLKSIZE))!=0)
+       {
+               if(n < 0)
+                       error(badexec);
+               write(fdo,buffer,n);
+       }
+       close(fdi);
+       return(close(fdo));
+}
+
+#endif /* BSD_4_2 */