138032Speter/* 2261363Sgshapiro * Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers. 364565Sgshapiro * All rights reserved. 438032Speter * Copyright (c) 1993 Eric P. Allman. All rights reserved. 538032Speter * Copyright (c) 1993 638032Speter * The Regents of the University of California. All rights reserved. 738032Speter * 838032Speter * By using this file, you agree to the terms and conditions set 938032Speter * forth in the LICENSE file which can be found at the top level of 1038032Speter * the sendmail distribution. 1138032Speter * 1238032Speter */ 1338032Speter 1490795Sgshapiro#include <sm/gen.h> 1590795Sgshapiro 1690795SgshapiroSM_IDSTR(copyright, 17261363Sgshapiro"@(#) Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.\n\ 1864565Sgshapiro All rights reserved.\n\ 1964565Sgshapiro Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\ 2064565Sgshapiro Copyright (c) 1993\n\ 2190795Sgshapiro The Regents of the University of California. All rights reserved.\n") 2238032Speter 23266692SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.66 2013-11-22 20:52:00 ca Exp $") 2464565Sgshapiro 2538032Speter/* 2638032Speter** SMRSH -- sendmail restricted shell 2738032Speter** 2838032Speter** This is a patch to get around the prog mailer bugs in most 2938032Speter** versions of sendmail. 3038032Speter** 3138032Speter** Use this in place of /bin/sh in the "prog" mailer definition 3238032Speter** in your sendmail.cf file. You then create CMDDIR (owned by 3338032Speter** root, mode 755) and put links to any programs you want 3438032Speter** available to prog mailers in that directory. This should 3538032Speter** include things like "vacation" and "procmail", but not "sed" 3638032Speter** or "sh". 3738032Speter** 3838032Speter** Leading pathnames are stripped from program names so that 3938032Speter** existing .forward files that reference things like 4038081Speter** "/usr/bin/vacation" will continue to work. 4138032Speter** 4238032Speter** The following characters are completely illegal: 4364565Sgshapiro** < > ^ & ` ( ) \n \r 4464565Sgshapiro** The following characters are sometimes illegal: 4564565Sgshapiro** | & 4638032Speter** This is more restrictive than strictly necessary. 4738032Speter** 4864565Sgshapiro** To use this, add FEATURE(`smrsh') to your .mc file. 4938032Speter** 5038032Speter** This can be used on any version of sendmail. 5138032Speter** 5238032Speter** In loving memory of RTM. 11/02/93. 5338032Speter*/ 5438032Speter 5538032Speter#include <unistd.h> 5690795Sgshapiro#include <sm/io.h> 5798125Sgshapiro#include <sm/limits.h> 5890795Sgshapiro#include <sm/string.h> 5938032Speter#include <sys/file.h> 60105016Sgshapiro#include <sys/types.h> 61105016Sgshapiro#include <sys/stat.h> 6238032Speter#include <string.h> 6338032Speter#include <ctype.h> 6464565Sgshapiro#include <errno.h> 6538032Speter#ifdef EX_OK 6638032Speter# undef EX_OK 6764565Sgshapiro#endif /* EX_OK */ 6838032Speter#include <sysexits.h> 6938032Speter#include <syslog.h> 7038032Speter#include <stdlib.h> 7138032Speter 7290795Sgshapiro#include <sm/conf.h> 7390795Sgshapiro#include <sm/errstring.h> 7464565Sgshapiro 7538032Speter/* directory in which all commands must reside */ 7638032Speter#ifndef CMDDIR 7790795Sgshapiro# ifdef SMRSH_CMDDIR 7890795Sgshapiro# define CMDDIR SMRSH_CMDDIR 7990795Sgshapiro# else /* SMRSH_CMDDIR */ 8090795Sgshapiro# define CMDDIR "/usr/adm/sm.bin" 8190795Sgshapiro# endif /* SMRSH_CMDDIR */ 8264565Sgshapiro#endif /* ! CMDDIR */ 8338032Speter 8438032Speter/* characters disallowed in the shell "-c" argument */ 8538032Speter#define SPECIALS "<|>^();&`$\r\n" 8638032Speter 8738032Speter/* default search path */ 8838032Speter#ifndef PATH 8990795Sgshapiro# ifdef SMRSH_PATH 9090795Sgshapiro# define PATH SMRSH_PATH 9190795Sgshapiro# else /* SMRSH_PATH */ 9290795Sgshapiro# define PATH "/bin:/usr/bin:/usr/ucb" 9390795Sgshapiro# endif /* SMRSH_PATH */ 9464565Sgshapiro#endif /* ! PATH */ 9538032Speter 9664565Sgshapirochar newcmdbuf[1000]; 9764565Sgshapirochar *prg, *par; 9864565Sgshapiro 99141862Sgshapirostatic void addcmd __P((char *, bool, size_t)); 100141862Sgshapiro 10164565Sgshapiro/* 10264565Sgshapiro** ADDCMD -- add a string to newcmdbuf, check for overflow 10364565Sgshapiro** 10464565Sgshapiro** Parameters: 10564565Sgshapiro** s -- string to add 10664565Sgshapiro** cmd -- it's a command: prepend CMDDIR/ 10764565Sgshapiro** len -- length of string to add 10864565Sgshapiro** 10964565Sgshapiro** Side Effects: 11064565Sgshapiro** changes newcmdbuf or exits with a failure. 11164565Sgshapiro** 11264565Sgshapiro*/ 11364565Sgshapiro 114141862Sgshapirostatic void 11564565Sgshapiroaddcmd(s, cmd, len) 11664565Sgshapiro char *s; 11790795Sgshapiro bool cmd; 11890795Sgshapiro size_t len; 11964565Sgshapiro{ 12064565Sgshapiro if (s == NULL || *s == '\0') 12164565Sgshapiro return; 12264565Sgshapiro 123125823Sgshapiro /* enough space for s (len) and CMDDIR + "/" and '\0'? */ 12464565Sgshapiro if (sizeof newcmdbuf - strlen(newcmdbuf) <= 125125823Sgshapiro len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0)) 12664565Sgshapiro { 12790795Sgshapiro (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 12890795Sgshapiro "%s: command too long: %s\n", prg, par); 12964565Sgshapiro#ifndef DEBUG 13064565Sgshapiro syslog(LOG_WARNING, "command too long: %.40s", par); 13164565Sgshapiro#endif /* ! DEBUG */ 13264565Sgshapiro exit(EX_UNAVAILABLE); 13364565Sgshapiro } 13464565Sgshapiro if (cmd) 13598125Sgshapiro (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf); 136125823Sgshapiro (void) strncat(newcmdbuf, s, len); 13764565Sgshapiro} 13864565Sgshapiro 13938032Speterint 14038032Spetermain(argc, argv) 14138032Speter int argc; 14238032Speter char **argv; 14338032Speter{ 14438032Speter register char *p; 14538032Speter register char *q; 14664565Sgshapiro register char *r; 14738032Speter register char *cmd; 14864565Sgshapiro int isexec; 14964565Sgshapiro int save_errno; 15038032Speter char *newenv[2]; 15138032Speter char pathbuf[1000]; 15264565Sgshapiro char specialbuf[32]; 153105016Sgshapiro struct stat st; 15438032Speter 15564565Sgshapiro#ifndef DEBUG 15664565Sgshapiro# ifndef LOG_MAIL 15738032Speter openlog("smrsh", 0); 15864565Sgshapiro# else /* ! LOG_MAIL */ 15938032Speter openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 16064565Sgshapiro# endif /* ! LOG_MAIL */ 16164565Sgshapiro#endif /* ! DEBUG */ 16238032Speter 16398125Sgshapiro (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH); 16438032Speter newenv[0] = pathbuf; 16538032Speter newenv[1] = NULL; 16638032Speter 16738032Speter /* 16838032Speter ** Do basic argv usage checking 16938032Speter */ 17038032Speter 17164565Sgshapiro prg = argv[0]; 17264565Sgshapiro 17338032Speter if (argc != 3 || strcmp(argv[1], "-c") != 0) 17438032Speter { 17590795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 17690795Sgshapiro "Usage: %s -c command\n", prg); 17764565Sgshapiro#ifndef DEBUG 17838032Speter syslog(LOG_ERR, "usage"); 17964565Sgshapiro#endif /* ! DEBUG */ 18038032Speter exit(EX_USAGE); 18138032Speter } 18238032Speter 18377352Sgshapiro par = argv[2]; 18477352Sgshapiro 18538032Speter /* 18638032Speter ** Disallow special shell syntax. This is overly restrictive, 18738032Speter ** but it should shut down all attacks. 18838032Speter ** Be sure to include 8-bit versions, since many shells strip 18938032Speter ** the address to 7 bits before checking. 19038032Speter */ 19138032Speter 19264565Sgshapiro if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 19338032Speter { 19464565Sgshapiro#ifndef DEBUG 19564565Sgshapiro syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 19664565Sgshapiro#endif /* ! DEBUG */ 19738032Speter exit(EX_UNAVAILABLE); 19838032Speter } 19990795Sgshapiro (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 20064565Sgshapiro for (p = specialbuf; *p != '\0'; p++) 20164565Sgshapiro *p |= '\200'; 20290795Sgshapiro (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); 20338032Speter 20438032Speter /* 20538032Speter ** Do a quick sanity check on command line length. 20638032Speter */ 20738032Speter 20890795Sgshapiro if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 20938032Speter { 21090795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 21190795Sgshapiro "%s: command too long: %s\n", prg, par); 21264565Sgshapiro#ifndef DEBUG 21364565Sgshapiro syslog(LOG_WARNING, "command too long: %.40s", par); 21464565Sgshapiro#endif /* ! DEBUG */ 21538032Speter exit(EX_UNAVAILABLE); 21638032Speter } 21738032Speter 21864565Sgshapiro q = par; 21964565Sgshapiro newcmdbuf[0] = '\0'; 22090795Sgshapiro isexec = false; 22138032Speter 22298125Sgshapiro while (*q != '\0') 22338032Speter { 22464565Sgshapiro /* 22564565Sgshapiro ** Strip off a leading pathname on the command name. For 22664565Sgshapiro ** example, change /usr/ucb/vacation to vacation. 22764565Sgshapiro */ 22838032Speter 22964565Sgshapiro /* strip leading spaces */ 23064565Sgshapiro while (*q != '\0' && isascii(*q) && isspace(*q)) 23164565Sgshapiro q++; 23264565Sgshapiro if (*q == '\0') 23338032Speter { 23464565Sgshapiro if (isexec) 23564565Sgshapiro { 23690795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 23790795Sgshapiro "%s: missing command to exec\n", 23890795Sgshapiro prg); 23964565Sgshapiro#ifndef DEBUG 24090795Sgshapiro syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); 24164565Sgshapiro#endif /* ! DEBUG */ 24264565Sgshapiro exit(EX_UNAVAILABLE); 24364565Sgshapiro } 24438032Speter break; 24538032Speter } 24638032Speter 24764565Sgshapiro /* find the end of the command name */ 24864565Sgshapiro p = strpbrk(q, " \t"); 24964565Sgshapiro if (p == NULL) 25064565Sgshapiro cmd = &q[strlen(q)]; 25164565Sgshapiro else 25264565Sgshapiro { 25364565Sgshapiro *p = '\0'; 25464565Sgshapiro cmd = p; 25564565Sgshapiro } 25664565Sgshapiro /* search backwards for last / (allow for 0200 bit) */ 25764565Sgshapiro while (cmd > q) 25864565Sgshapiro { 25964565Sgshapiro if ((*--cmd & 0177) == '/') 26064565Sgshapiro { 26164565Sgshapiro cmd++; 26264565Sgshapiro break; 26364565Sgshapiro } 26464565Sgshapiro } 26564565Sgshapiro /* cmd now points at final component of path name */ 26638032Speter 26764565Sgshapiro /* allow a few shell builtins */ 26864565Sgshapiro if (strcmp(q, "exec") == 0 && p != NULL) 26964565Sgshapiro { 27090795Sgshapiro addcmd("exec ", false, strlen("exec ")); 27198125Sgshapiro 27264565Sgshapiro /* test _next_ arg */ 27364565Sgshapiro q = ++p; 27490795Sgshapiro isexec = true; 27564565Sgshapiro continue; 27664565Sgshapiro } 27764565Sgshapiro else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 27864565Sgshapiro { 27990795Sgshapiro addcmd(cmd, false, strlen(cmd)); 28098125Sgshapiro 28164565Sgshapiro /* test following chars */ 28264565Sgshapiro } 28364565Sgshapiro else 28464565Sgshapiro { 28598125Sgshapiro char cmdbuf[MAXPATHLEN]; 28698125Sgshapiro 28764565Sgshapiro /* 28864565Sgshapiro ** Check to see if the command name is legal. 28964565Sgshapiro */ 29098125Sgshapiro 29198125Sgshapiro if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR, 29298125Sgshapiro "/", cmd) >= sizeof cmdbuf) 29398125Sgshapiro { 29498125Sgshapiro /* too long */ 29598125Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 296110563Sgshapiro "%s: \"%s\" not available for sendmail programs (filename too long)\n", 29798125Sgshapiro prg, cmd); 29898125Sgshapiro if (p != NULL) 29998125Sgshapiro *p = ' '; 30098125Sgshapiro#ifndef DEBUG 301110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)", 30298125Sgshapiro (int) getuid(), cmd); 30398125Sgshapiro#endif /* ! DEBUG */ 30498125Sgshapiro exit(EX_UNAVAILABLE); 30598125Sgshapiro } 30698125Sgshapiro 30764565Sgshapiro#ifdef DEBUG 30890795Sgshapiro (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 30990795Sgshapiro "Trying %s\n", cmdbuf); 31064565Sgshapiro#endif /* DEBUG */ 311105016Sgshapiro if (stat(cmdbuf, &st) < 0) 312105016Sgshapiro { 313105016Sgshapiro /* can't stat it */ 314105016Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 315110563Sgshapiro "%s: \"%s\" not available for sendmail programs (stat failed)\n", 316105016Sgshapiro prg, cmd); 317105016Sgshapiro if (p != NULL) 318105016Sgshapiro *p = ' '; 319105016Sgshapiro#ifndef DEBUG 320110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)", 321105016Sgshapiro (int) getuid(), cmd); 322105016Sgshapiro#endif /* ! DEBUG */ 323105016Sgshapiro exit(EX_UNAVAILABLE); 324105016Sgshapiro } 325105016Sgshapiro if (!S_ISREG(st.st_mode) 326105016Sgshapiro#ifdef S_ISLNK 327105016Sgshapiro && !S_ISLNK(st.st_mode) 328105016Sgshapiro#endif /* S_ISLNK */ 329105016Sgshapiro ) 330105016Sgshapiro { 331105016Sgshapiro /* can't stat it */ 332105016Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 333110563Sgshapiro "%s: \"%s\" not available for sendmail programs (not a file)\n", 334105016Sgshapiro prg, cmd); 335105016Sgshapiro if (p != NULL) 336105016Sgshapiro *p = ' '; 337105016Sgshapiro#ifndef DEBUG 338110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)", 339105016Sgshapiro (int) getuid(), cmd); 340105016Sgshapiro#endif /* ! DEBUG */ 341105016Sgshapiro exit(EX_UNAVAILABLE); 342105016Sgshapiro } 34364565Sgshapiro if (access(cmdbuf, X_OK) < 0) 34464565Sgshapiro { 34564565Sgshapiro /* oops.... crack attack possiblity */ 34690795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 347110563Sgshapiro "%s: \"%s\" not available for sendmail programs\n", 34890795Sgshapiro prg, cmd); 34964565Sgshapiro if (p != NULL) 35064565Sgshapiro *p = ' '; 35164565Sgshapiro#ifndef DEBUG 352110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"", 35390795Sgshapiro (int) getuid(), cmd); 35464565Sgshapiro#endif /* ! DEBUG */ 35564565Sgshapiro exit(EX_UNAVAILABLE); 35664565Sgshapiro } 35738032Speter 35864565Sgshapiro /* 35964565Sgshapiro ** Create the actual shell input. 36064565Sgshapiro */ 36164565Sgshapiro 36290795Sgshapiro addcmd(cmd, true, strlen(cmd)); 36364565Sgshapiro } 36490795Sgshapiro isexec = false; 36564565Sgshapiro 36638032Speter if (p != NULL) 36738032Speter *p = ' '; 36864565Sgshapiro else 36964565Sgshapiro break; 37064565Sgshapiro 37164565Sgshapiro r = strpbrk(p, specialbuf); 37290795Sgshapiro if (r == NULL) 37390795Sgshapiro { 37490795Sgshapiro addcmd(p, false, strlen(p)); 37564565Sgshapiro break; 37664565Sgshapiro } 37764565Sgshapiro#if ALLOWSEMI 37890795Sgshapiro if (*r == ';') 37990795Sgshapiro { 38090795Sgshapiro addcmd(p, false, r - p + 1); 38164565Sgshapiro q = r + 1; 38264565Sgshapiro continue; 38364565Sgshapiro } 38464565Sgshapiro#endif /* ALLOWSEMI */ 38564565Sgshapiro if ((*r == '&' && *(r + 1) == '&') || 38664565Sgshapiro (*r == '|' && *(r + 1) == '|')) 38764565Sgshapiro { 38890795Sgshapiro addcmd(p, false, r - p + 2); 38964565Sgshapiro q = r + 2; 39064565Sgshapiro continue; 39164565Sgshapiro } 39264565Sgshapiro 39390795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 39490795Sgshapiro "%s: cannot use %c in command\n", prg, *r); 39564565Sgshapiro#ifndef DEBUG 39664565Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 39790795Sgshapiro (int) getuid(), *r, par); 39864565Sgshapiro#endif /* ! DEBUG */ 39938032Speter exit(EX_UNAVAILABLE); 40098125Sgshapiro } 40164565Sgshapiro if (isexec) 40264565Sgshapiro { 40390795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 40490795Sgshapiro "%s: missing command to exec\n", prg); 40564565Sgshapiro#ifndef DEBUG 40690795Sgshapiro syslog(LOG_CRIT, "uid %d: missing command to exec", 40790795Sgshapiro (int) getuid()); 40864565Sgshapiro#endif /* ! DEBUG */ 40964565Sgshapiro exit(EX_UNAVAILABLE); 41038032Speter } 41164565Sgshapiro /* make sure we created something */ 41264565Sgshapiro if (newcmdbuf[0] == '\0') 41364565Sgshapiro { 41490795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 41590795Sgshapiro "Usage: %s -c command\n", prg); 41664565Sgshapiro#ifndef DEBUG 41764565Sgshapiro syslog(LOG_ERR, "usage"); 41864565Sgshapiro#endif /* ! DEBUG */ 41964565Sgshapiro exit(EX_USAGE); 42064565Sgshapiro } 42138032Speter 42238032Speter /* 42338032Speter ** Now invoke the shell 42438032Speter */ 42538032Speter 42638032Speter#ifdef DEBUG 42790795Sgshapiro (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf); 42864565Sgshapiro#endif /* DEBUG */ 429121826Sgshapiro (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, 430121826Sgshapiro (char *)NULL, newenv); 43164565Sgshapiro save_errno = errno; 43264565Sgshapiro#ifndef DEBUG 43390795Sgshapiro syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 43464565Sgshapiro#endif /* ! DEBUG */ 43564565Sgshapiro errno = save_errno; 43690795Sgshapiro sm_perror("/bin/sh"); 43738032Speter exit(EX_OSFILE); 43864565Sgshapiro /* NOTREACHED */ 43964565Sgshapiro return EX_OSFILE; 44038032Speter} 441