smrsh.c revision 141862
138032Speter/* 2141862Sgshapiro * Copyright (c) 1998-2004 Sendmail, 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 * 12121826Sgshapiro * $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 141862 2005-02-14 02:39:14Z gshapiro $ 1338032Speter */ 1438032Speter 1590795Sgshapiro#include <sm/gen.h> 1690795Sgshapiro 1790795SgshapiroSM_IDSTR(copyright, 18141862Sgshapiro"@(#) Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.\n\ 1964565Sgshapiro All rights reserved.\n\ 2064565Sgshapiro Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\ 2164565Sgshapiro Copyright (c) 1993\n\ 2290795Sgshapiro The Regents of the University of California. All rights reserved.\n") 2338032Speter 24141862SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.65 2004/08/06 18:54:22 ca Exp $") 2564565Sgshapiro 2638032Speter/* 2738032Speter** SMRSH -- sendmail restricted shell 2838032Speter** 2938032Speter** This is a patch to get around the prog mailer bugs in most 3038032Speter** versions of sendmail. 3138032Speter** 3238032Speter** Use this in place of /bin/sh in the "prog" mailer definition 3338032Speter** in your sendmail.cf file. You then create CMDDIR (owned by 3438032Speter** root, mode 755) and put links to any programs you want 3538032Speter** available to prog mailers in that directory. This should 3638032Speter** include things like "vacation" and "procmail", but not "sed" 3738032Speter** or "sh". 3838032Speter** 3938032Speter** Leading pathnames are stripped from program names so that 4038032Speter** existing .forward files that reference things like 4138081Speter** "/usr/bin/vacation" will continue to work. 4238032Speter** 4338032Speter** The following characters are completely illegal: 4464565Sgshapiro** < > ^ & ` ( ) \n \r 4564565Sgshapiro** The following characters are sometimes illegal: 4664565Sgshapiro** | & 4738032Speter** This is more restrictive than strictly necessary. 4838032Speter** 4964565Sgshapiro** To use this, add FEATURE(`smrsh') to your .mc file. 5038032Speter** 5138032Speter** This can be used on any version of sendmail. 5238032Speter** 5338032Speter** In loving memory of RTM. 11/02/93. 5438032Speter*/ 5538032Speter 5638032Speter#include <unistd.h> 5790795Sgshapiro#include <sm/io.h> 5898125Sgshapiro#include <sm/limits.h> 5990795Sgshapiro#include <sm/string.h> 6038032Speter#include <sys/file.h> 61105016Sgshapiro#include <sys/types.h> 62105016Sgshapiro#include <sys/stat.h> 6338032Speter#include <string.h> 6438032Speter#include <ctype.h> 6564565Sgshapiro#include <errno.h> 6638032Speter#ifdef EX_OK 6738032Speter# undef EX_OK 6864565Sgshapiro#endif /* EX_OK */ 6938032Speter#include <sysexits.h> 7038032Speter#include <syslog.h> 7138032Speter#include <stdlib.h> 7238032Speter 7390795Sgshapiro#include <sm/conf.h> 7490795Sgshapiro#include <sm/errstring.h> 7564565Sgshapiro 7638032Speter/* directory in which all commands must reside */ 7738032Speter#ifndef CMDDIR 7890795Sgshapiro# ifdef SMRSH_CMDDIR 7990795Sgshapiro# define CMDDIR SMRSH_CMDDIR 8090795Sgshapiro# else /* SMRSH_CMDDIR */ 8190795Sgshapiro# define CMDDIR "/usr/adm/sm.bin" 8290795Sgshapiro# endif /* SMRSH_CMDDIR */ 8364565Sgshapiro#endif /* ! CMDDIR */ 8438032Speter 8538032Speter/* characters disallowed in the shell "-c" argument */ 8638032Speter#define SPECIALS "<|>^();&`$\r\n" 8738032Speter 8838032Speter/* default search path */ 8938032Speter#ifndef PATH 9090795Sgshapiro# ifdef SMRSH_PATH 9190795Sgshapiro# define PATH SMRSH_PATH 9290795Sgshapiro# else /* SMRSH_PATH */ 9390795Sgshapiro# define PATH "/bin:/usr/bin:/usr/ucb" 9490795Sgshapiro# endif /* SMRSH_PATH */ 9564565Sgshapiro#endif /* ! PATH */ 9638032Speter 9764565Sgshapirochar newcmdbuf[1000]; 9864565Sgshapirochar *prg, *par; 9964565Sgshapiro 100141862Sgshapirostatic void addcmd __P((char *, bool, size_t)); 101141862Sgshapiro 10264565Sgshapiro/* 10364565Sgshapiro** ADDCMD -- add a string to newcmdbuf, check for overflow 10464565Sgshapiro** 10564565Sgshapiro** Parameters: 10664565Sgshapiro** s -- string to add 10764565Sgshapiro** cmd -- it's a command: prepend CMDDIR/ 10864565Sgshapiro** len -- length of string to add 10964565Sgshapiro** 11064565Sgshapiro** Side Effects: 11164565Sgshapiro** changes newcmdbuf or exits with a failure. 11264565Sgshapiro** 11364565Sgshapiro*/ 11464565Sgshapiro 115141862Sgshapirostatic void 11664565Sgshapiroaddcmd(s, cmd, len) 11764565Sgshapiro char *s; 11890795Sgshapiro bool cmd; 11990795Sgshapiro size_t len; 12064565Sgshapiro{ 12164565Sgshapiro if (s == NULL || *s == '\0') 12264565Sgshapiro return; 12364565Sgshapiro 124125823Sgshapiro /* enough space for s (len) and CMDDIR + "/" and '\0'? */ 12564565Sgshapiro if (sizeof newcmdbuf - strlen(newcmdbuf) <= 126125823Sgshapiro len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0)) 12764565Sgshapiro { 12890795Sgshapiro (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 12990795Sgshapiro "%s: command too long: %s\n", prg, par); 13064565Sgshapiro#ifndef DEBUG 13164565Sgshapiro syslog(LOG_WARNING, "command too long: %.40s", par); 13264565Sgshapiro#endif /* ! DEBUG */ 13364565Sgshapiro exit(EX_UNAVAILABLE); 13464565Sgshapiro } 13564565Sgshapiro if (cmd) 13698125Sgshapiro (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf); 137125823Sgshapiro (void) strncat(newcmdbuf, s, len); 13864565Sgshapiro} 13964565Sgshapiro 14038032Speterint 14138032Spetermain(argc, argv) 14238032Speter int argc; 14338032Speter char **argv; 14438032Speter{ 14538032Speter register char *p; 14638032Speter register char *q; 14764565Sgshapiro register char *r; 14838032Speter register char *cmd; 14964565Sgshapiro int isexec; 15064565Sgshapiro int save_errno; 15138032Speter char *newenv[2]; 15238032Speter char pathbuf[1000]; 15364565Sgshapiro char specialbuf[32]; 154105016Sgshapiro struct stat st; 15538032Speter 15664565Sgshapiro#ifndef DEBUG 15764565Sgshapiro# ifndef LOG_MAIL 15838032Speter openlog("smrsh", 0); 15964565Sgshapiro# else /* ! LOG_MAIL */ 16038032Speter openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 16164565Sgshapiro# endif /* ! LOG_MAIL */ 16264565Sgshapiro#endif /* ! DEBUG */ 16338032Speter 16498125Sgshapiro (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH); 16538032Speter newenv[0] = pathbuf; 16638032Speter newenv[1] = NULL; 16738032Speter 16838032Speter /* 16938032Speter ** Do basic argv usage checking 17038032Speter */ 17138032Speter 17264565Sgshapiro prg = argv[0]; 17364565Sgshapiro 17438032Speter if (argc != 3 || strcmp(argv[1], "-c") != 0) 17538032Speter { 17690795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 17790795Sgshapiro "Usage: %s -c command\n", prg); 17864565Sgshapiro#ifndef DEBUG 17938032Speter syslog(LOG_ERR, "usage"); 18064565Sgshapiro#endif /* ! DEBUG */ 18138032Speter exit(EX_USAGE); 18238032Speter } 18338032Speter 18477352Sgshapiro par = argv[2]; 18577352Sgshapiro 18638032Speter /* 18738032Speter ** Disallow special shell syntax. This is overly restrictive, 18838032Speter ** but it should shut down all attacks. 18938032Speter ** Be sure to include 8-bit versions, since many shells strip 19038032Speter ** the address to 7 bits before checking. 19138032Speter */ 19238032Speter 19364565Sgshapiro if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 19438032Speter { 19564565Sgshapiro#ifndef DEBUG 19664565Sgshapiro syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 19764565Sgshapiro#endif /* ! DEBUG */ 19838032Speter exit(EX_UNAVAILABLE); 19938032Speter } 20090795Sgshapiro (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 20164565Sgshapiro for (p = specialbuf; *p != '\0'; p++) 20264565Sgshapiro *p |= '\200'; 20390795Sgshapiro (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); 20438032Speter 20538032Speter /* 20638032Speter ** Do a quick sanity check on command line length. 20738032Speter */ 20838032Speter 20990795Sgshapiro if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 21038032Speter { 21190795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 21290795Sgshapiro "%s: command too long: %s\n", prg, par); 21364565Sgshapiro#ifndef DEBUG 21464565Sgshapiro syslog(LOG_WARNING, "command too long: %.40s", par); 21564565Sgshapiro#endif /* ! DEBUG */ 21638032Speter exit(EX_UNAVAILABLE); 21738032Speter } 21838032Speter 21964565Sgshapiro q = par; 22064565Sgshapiro newcmdbuf[0] = '\0'; 22190795Sgshapiro isexec = false; 22238032Speter 22398125Sgshapiro while (*q != '\0') 22438032Speter { 22564565Sgshapiro /* 22664565Sgshapiro ** Strip off a leading pathname on the command name. For 22764565Sgshapiro ** example, change /usr/ucb/vacation to vacation. 22864565Sgshapiro */ 22938032Speter 23064565Sgshapiro /* strip leading spaces */ 23164565Sgshapiro while (*q != '\0' && isascii(*q) && isspace(*q)) 23264565Sgshapiro q++; 23364565Sgshapiro if (*q == '\0') 23438032Speter { 23564565Sgshapiro if (isexec) 23664565Sgshapiro { 23790795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 23890795Sgshapiro "%s: missing command to exec\n", 23990795Sgshapiro prg); 24064565Sgshapiro#ifndef DEBUG 24190795Sgshapiro syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); 24264565Sgshapiro#endif /* ! DEBUG */ 24364565Sgshapiro exit(EX_UNAVAILABLE); 24464565Sgshapiro } 24538032Speter break; 24638032Speter } 24738032Speter 24864565Sgshapiro /* find the end of the command name */ 24964565Sgshapiro p = strpbrk(q, " \t"); 25064565Sgshapiro if (p == NULL) 25164565Sgshapiro cmd = &q[strlen(q)]; 25264565Sgshapiro else 25364565Sgshapiro { 25464565Sgshapiro *p = '\0'; 25564565Sgshapiro cmd = p; 25664565Sgshapiro } 25764565Sgshapiro /* search backwards for last / (allow for 0200 bit) */ 25864565Sgshapiro while (cmd > q) 25964565Sgshapiro { 26064565Sgshapiro if ((*--cmd & 0177) == '/') 26164565Sgshapiro { 26264565Sgshapiro cmd++; 26364565Sgshapiro break; 26464565Sgshapiro } 26564565Sgshapiro } 26664565Sgshapiro /* cmd now points at final component of path name */ 26738032Speter 26864565Sgshapiro /* allow a few shell builtins */ 26964565Sgshapiro if (strcmp(q, "exec") == 0 && p != NULL) 27064565Sgshapiro { 27190795Sgshapiro addcmd("exec ", false, strlen("exec ")); 27298125Sgshapiro 27364565Sgshapiro /* test _next_ arg */ 27464565Sgshapiro q = ++p; 27590795Sgshapiro isexec = true; 27664565Sgshapiro continue; 27764565Sgshapiro } 27864565Sgshapiro else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 27964565Sgshapiro { 28090795Sgshapiro addcmd(cmd, false, strlen(cmd)); 28198125Sgshapiro 28264565Sgshapiro /* test following chars */ 28364565Sgshapiro } 28464565Sgshapiro else 28564565Sgshapiro { 28698125Sgshapiro char cmdbuf[MAXPATHLEN]; 28798125Sgshapiro 28864565Sgshapiro /* 28964565Sgshapiro ** Check to see if the command name is legal. 29064565Sgshapiro */ 29198125Sgshapiro 29298125Sgshapiro if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR, 29398125Sgshapiro "/", cmd) >= sizeof cmdbuf) 29498125Sgshapiro { 29598125Sgshapiro /* too long */ 29698125Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 297110563Sgshapiro "%s: \"%s\" not available for sendmail programs (filename too long)\n", 29898125Sgshapiro prg, cmd); 29998125Sgshapiro if (p != NULL) 30098125Sgshapiro *p = ' '; 30198125Sgshapiro#ifndef DEBUG 302110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)", 30398125Sgshapiro (int) getuid(), cmd); 30498125Sgshapiro#endif /* ! DEBUG */ 30598125Sgshapiro exit(EX_UNAVAILABLE); 30698125Sgshapiro } 30798125Sgshapiro 30864565Sgshapiro#ifdef DEBUG 30990795Sgshapiro (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 31090795Sgshapiro "Trying %s\n", cmdbuf); 31164565Sgshapiro#endif /* DEBUG */ 312105016Sgshapiro if (stat(cmdbuf, &st) < 0) 313105016Sgshapiro { 314105016Sgshapiro /* can't stat it */ 315105016Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 316110563Sgshapiro "%s: \"%s\" not available for sendmail programs (stat failed)\n", 317105016Sgshapiro prg, cmd); 318105016Sgshapiro if (p != NULL) 319105016Sgshapiro *p = ' '; 320105016Sgshapiro#ifndef DEBUG 321110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)", 322105016Sgshapiro (int) getuid(), cmd); 323105016Sgshapiro#endif /* ! DEBUG */ 324105016Sgshapiro exit(EX_UNAVAILABLE); 325105016Sgshapiro } 326105016Sgshapiro if (!S_ISREG(st.st_mode) 327105016Sgshapiro#ifdef S_ISLNK 328105016Sgshapiro && !S_ISLNK(st.st_mode) 329105016Sgshapiro#endif /* S_ISLNK */ 330105016Sgshapiro ) 331105016Sgshapiro { 332105016Sgshapiro /* can't stat it */ 333105016Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 334110563Sgshapiro "%s: \"%s\" not available for sendmail programs (not a file)\n", 335105016Sgshapiro prg, cmd); 336105016Sgshapiro if (p != NULL) 337105016Sgshapiro *p = ' '; 338105016Sgshapiro#ifndef DEBUG 339110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)", 340105016Sgshapiro (int) getuid(), cmd); 341105016Sgshapiro#endif /* ! DEBUG */ 342105016Sgshapiro exit(EX_UNAVAILABLE); 343105016Sgshapiro } 34464565Sgshapiro if (access(cmdbuf, X_OK) < 0) 34564565Sgshapiro { 34664565Sgshapiro /* oops.... crack attack possiblity */ 34790795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 348110563Sgshapiro "%s: \"%s\" not available for sendmail programs\n", 34990795Sgshapiro prg, cmd); 35064565Sgshapiro if (p != NULL) 35164565Sgshapiro *p = ' '; 35264565Sgshapiro#ifndef DEBUG 353110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"", 35490795Sgshapiro (int) getuid(), cmd); 35564565Sgshapiro#endif /* ! DEBUG */ 35664565Sgshapiro exit(EX_UNAVAILABLE); 35764565Sgshapiro } 35838032Speter 35964565Sgshapiro /* 36064565Sgshapiro ** Create the actual shell input. 36164565Sgshapiro */ 36264565Sgshapiro 36390795Sgshapiro addcmd(cmd, true, strlen(cmd)); 36464565Sgshapiro } 36590795Sgshapiro isexec = false; 36664565Sgshapiro 36738032Speter if (p != NULL) 36838032Speter *p = ' '; 36964565Sgshapiro else 37064565Sgshapiro break; 37164565Sgshapiro 37264565Sgshapiro r = strpbrk(p, specialbuf); 37390795Sgshapiro if (r == NULL) 37490795Sgshapiro { 37590795Sgshapiro addcmd(p, false, strlen(p)); 37664565Sgshapiro break; 37764565Sgshapiro } 37864565Sgshapiro#if ALLOWSEMI 37990795Sgshapiro if (*r == ';') 38090795Sgshapiro { 38190795Sgshapiro addcmd(p, false, r - p + 1); 38264565Sgshapiro q = r + 1; 38364565Sgshapiro continue; 38464565Sgshapiro } 38564565Sgshapiro#endif /* ALLOWSEMI */ 38664565Sgshapiro if ((*r == '&' && *(r + 1) == '&') || 38764565Sgshapiro (*r == '|' && *(r + 1) == '|')) 38864565Sgshapiro { 38990795Sgshapiro addcmd(p, false, r - p + 2); 39064565Sgshapiro q = r + 2; 39164565Sgshapiro continue; 39264565Sgshapiro } 39364565Sgshapiro 39490795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 39590795Sgshapiro "%s: cannot use %c in command\n", prg, *r); 39664565Sgshapiro#ifndef DEBUG 39764565Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 39890795Sgshapiro (int) getuid(), *r, par); 39964565Sgshapiro#endif /* ! DEBUG */ 40038032Speter exit(EX_UNAVAILABLE); 40198125Sgshapiro } 40264565Sgshapiro if (isexec) 40364565Sgshapiro { 40490795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 40590795Sgshapiro "%s: missing command to exec\n", prg); 40664565Sgshapiro#ifndef DEBUG 40790795Sgshapiro syslog(LOG_CRIT, "uid %d: missing command to exec", 40890795Sgshapiro (int) getuid()); 40964565Sgshapiro#endif /* ! DEBUG */ 41064565Sgshapiro exit(EX_UNAVAILABLE); 41138032Speter } 41264565Sgshapiro /* make sure we created something */ 41364565Sgshapiro if (newcmdbuf[0] == '\0') 41464565Sgshapiro { 41590795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 41690795Sgshapiro "Usage: %s -c command\n", prg); 41764565Sgshapiro#ifndef DEBUG 41864565Sgshapiro syslog(LOG_ERR, "usage"); 41964565Sgshapiro#endif /* ! DEBUG */ 42064565Sgshapiro exit(EX_USAGE); 42164565Sgshapiro } 42238032Speter 42338032Speter /* 42438032Speter ** Now invoke the shell 42538032Speter */ 42638032Speter 42738032Speter#ifdef DEBUG 42890795Sgshapiro (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf); 42964565Sgshapiro#endif /* DEBUG */ 430121826Sgshapiro (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, 431121826Sgshapiro (char *)NULL, newenv); 43264565Sgshapiro save_errno = errno; 43364565Sgshapiro#ifndef DEBUG 43490795Sgshapiro syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 43564565Sgshapiro#endif /* ! DEBUG */ 43664565Sgshapiro errno = save_errno; 43790795Sgshapiro sm_perror("/bin/sh"); 43838032Speter exit(EX_OSFILE); 43964565Sgshapiro /* NOTREACHED */ 44064565Sgshapiro return EX_OSFILE; 44138032Speter} 442