This patch for procmail-3.11pre7 provides smrsh-like (Sendmail Restricted Shell) capabilities to procmail. Normally procmail either allows all binaries to be executed, or none whatsoever. This patch will only allow binaries in a predefined directory to be executed. If a path is specified with the name of a binary, the version in the predefined directory will still be executed. Note that unless you allow a shell to be executed by placing it into the predefined directory, pipes and redirects will not work. The only way around this is to create a shell script or C binary that executes the desired commands, then place these within the predefined directory. Allowing shells to be executed totally overrides any security that this patch might otherwise provide. To configure, there are three new #define settings in config.h: RESTRICT_SHELL the name of the directory to check for allowed binaries RESTRICTCHARS characters that can't be found in the commandline arguments for executed binaries PRINTRESTRICTCHARS basically RESTRICTCHARS with escaped \n \r so that it can be printed (ugly) UID_UNRESTRICT if the effective userid is below or equal to this value, do not perform RESTRICT_SHELL checking (useful for /etc/procmailrc, system auto-responders, etc) A typo in the original RESTRICT_EXEC code is also fixed. Thanks to Stephen R. van den Berg for writing the best UNIX mail processor ever. Questions/comments should be directed to lindsey@ncsa.uiuc.edu (Christopher Lindsey) ---------------------------------------------------------------------- *** ../procmail-3.11pre7/config.h Sun Apr 27 19:27:41 1997 --- config.h Fri Mar 20 01:00:12 1998 *************** *** 50,55 **** --- 50,60 ---- /*#define NO_flock_LOCK /* use of those kernel-locking methods */ /*#define RESTRICT_EXEC /* don't allow everyone to fork programs */ + /* don't allow everyone to run certain programs*/ + #define RESTRICT_SHELL "/var/adm/procmail.bin" + #define RESTRICTCHARS ";&$<>\r\n" + #define PRINTRESTRICTCHARS ";&$<>\\r\\n" + #define UID_UNRESTRICT 100 /* max id that isn't checked for restrictions */ /*#define NO_NFS_ATIME_HACK /* uncomment if you're definitely not using NFS mounted filesystems and can't afford diff -c ../procmail-3.11pre7/src/common.c src/common.c *** ../procmail-3.11pre7/src/common.c Sun Apr 27 19:27:45 1997 --- src/common.c Mon Mar 16 11:36:38 1998 *************** *** 9,33 **** "$Id: common.c,v 1.24 1997/04/28 00:27:45 srb Exp $"; #endif #include "procmail.h" #include "sublib.h" #include "robust.h" #include "shell.h" - #include "misc.h" #include "common.h" void shexec(argv)const char*const*argv; { int i;char**newargv;const char**p; #ifdef SIGXCPU signal(SIGXCPU,SIG_DFL);signal(SIGXFSZ,SIG_DFL); #endif #ifdef SIGLOST signal(SIGLOST,SIG_DFL); #endif /* or is it a shell script ? */ ! signal(SIGPIPE,SIG_DFL);execvp(*argv,(char*const*)argv); ! for(p=(const char**)argv,i=1;i++,*p++;); /* count the arguments */ newargv=malloc(i*sizeof*p); /* no shell script? -> trouble */ ! for(*(p=(const char**)newargv)=binsh;*++p= *argv++;); ! execv(*newargv,newargv);free(newargv);nlog("Failed to execute"); logqnl(*argv); exit(EX_UNAVAILABLE); } --- 9,34 ---- "$Id: common.c,v 1.24 1997/04/28 00:27:45 srb Exp $"; #endif #include "procmail.h" + #include "misc.h" #include "sublib.h" #include "robust.h" #include "shell.h" #include "common.h" void shexec(argv)const char*const*argv; { int i;char**newargv;const char**p; + #ifdef SIGXCPU signal(SIGXCPU,SIG_DFL);signal(SIGXFSZ,SIG_DFL); #endif #ifdef SIGLOST signal(SIGLOST,SIG_DFL); #endif /* or is it a shell script ? */ ! signal(SIGPIPE,SIG_DFL); execvp(*argv,(char*const*)argv); ! for(p=(const char**)argv,i=1;i++,*p++;); /* count the arguments */ newargv=malloc(i*sizeof*p); /* no shell script? -> trouble */ ! for (*(p=(const char**)newargv)=binsh;*++p= *argv++;); ! execv(*newargv,newargv); free(newargv);nlog("Failed to execute"); logqnl(*argv); exit(EX_UNAVAILABLE); } diff -c ../procmail-3.11pre7/src/pipes.c src/pipes.c *** ../procmail-3.11pre7/src/pipes.c Sun Apr 27 19:27:47 1997 --- src/pipes.c Fri Mar 20 01:28:25 1998 *************** *** 73,90 **** { rclose(STDIN);rdup(pip);rclose(pip); } static void callnewprog(newname)const char*const newname; { #ifdef RESTRICT_EXEC ! syslog(LOG_ERR,slogstr,"Attempt to execute",newname) nlog("Insufficient permission to execute");logqnl(newname); return; #endif if(sh) /* should we start a shell? */ { const char*newargv[4]; yell(executing,newname);newargv[3]=0;newargv[2]=newname; ! newargv[1]=shellflags;*newargv=tgetenv(shell);shexec(newargv); } ;{ register const char*p;int argc; argc=1;p=newname; /* If no shell, chop up the arguments ourselves */ if(verbose) --- 73,177 ---- { rclose(STDIN);rdup(pip);rclose(pip); } + #ifdef RESTRICT_SHELL + + /* + * check if a UID is just allowed to execute anything + */ + + int checkAllowUID (void) { + if (geteuid() <= UID_UNRESTRICT) { + return 0; + } + return -1; + } + + /* + * check an individual command for 'okayedness' + * still need to prune white space in front? + */ + + char *checkCommand (const char *binaryName) { + const char *lastSlash; + char *newBinary; + + lastSlash = strrchr(binaryName, '/'); + if (lastSlash == NULL) { + lastSlash = binaryName; + } else { + lastSlash ++; + } + newBinary = (char *) malloc (sizeof (char) * (strlen (RESTRICT_SHELL) + + strlen (lastSlash) + 2)); + if (newBinary == NULL) return NULL; + strcpy (newBinary, RESTRICT_SHELL); + strcat (newBinary, DIRSEP); + strcat (newBinary, lastSlash); + + /* now check to see if file is in RESTRICT_SHELL directory */ + if (access (newBinary, X_OK) < 0) { + free (newBinary); + return NULL; + } else { + return newBinary; + } + } + + int checkRestricted (const char**commandLine, int args) { + char *newBinary; int i; + + if (checkAllowUID() == 0 || strlen (commandLine[0]) == 0) { + return 0; + } + + /* Assume that the first argument is the binary to execute */ + newBinary = checkCommand (commandLine[0]); + if (newBinary == NULL) { + syslog(LOG_ERR, "uid %d: procmail attempted to use \"%s\"", + geteuid(), commandLine[0]); + nlog("Insufficient permission to execute");logqnl(commandLine[0]); + return 1; + } else { + nlog ("Converting "),elog(commandLine[0]); + elog(" to"),logqnl(newBinary); + commandLine[0] = newBinary; + } + + /* If we got this far, then let's check the args for invalid chars */ + for (i = 0; i < args; i++) { + if (strpbrk (RESTRICTCHARS, commandLine[i]) != NULL) { + syslog(LOG_ERR, "uid %d: special character (one of \"%s\") used", + geteuid(), PRINTRESTRICTCHARS); + nlog("Special character used from ");elog(PRINTRESTRICTCHARS); + elog(" subset\n"); + return 1; + } + } + return 0; + } + #endif + static void callnewprog(newname)const char*const newname; { #ifdef RESTRICT_EXEC ! syslog(LOG_ERR,slogstr,"Attempt to execute",newname); nlog("Insufficient permission to execute");logqnl(newname); return; #endif if(sh) /* should we start a shell? */ { const char*newargv[4]; yell(executing,newname);newargv[3]=0;newargv[2]=newname; ! newargv[1]=shellflags;*newargv=tgetenv(shell); ! #ifdef RESTRICT_SHELL ! if (checkRestricted (newargv, 4) == 0) { ! shexec(newargv); ! } else { ! return; ! } } + #else + shexec(newargv); + #endif ;{ register const char*p;int argc; argc=1;p=newname; /* If no shell, chop up the arguments ourselves */ if(verbose) *************** *** 126,132 **** } } while(p!=Tmnate); ! newargv[argc]=0;shexec(newargv); } } } --- 213,228 ---- } } while(p!=Tmnate); ! newargv[argc]=0; ! #ifdef RESTRICT_SHELL ! if (checkRestricted (newargv, argc) == 0) { ! shexec(newargv); ! } else { ! return; ! } ! #else ! shexec(newargv); ! #endif } } } diff -c ../procmail-3.11pre7/src/procmail.c src/procmail.c *** ../procmail-3.11pre7/src/procmail.c Sun Apr 27 19:27:48 1997 --- src/procmail.c Fri Mar 20 00:33:48 1998 *************** *** 114,119 **** --- 114,126 ---- elog("\nYour system mailbox:\t"); elog(auth_mailboxname(auth_finduid(getuid(),0))); elog(newline); + #ifdef RESTRICT_SHELL + elog("\n "); + elog("RESTRICTED SHELL IN EFFECT -- CONTACT YOUR SYSTEM\n"); + elog(" "); + elog("ADMINISTRATOR TO FIND OUT WHICH BINARIES PROCMAIL CAN RUN\n"); + elog(newline); + #endif return EXIT_SUCCESS; case HELPOPT1:case HELPOPT2:elog(pmusage);elog(PM_HELP); elog(PM_QREFERENCE);