smrsh.c revision 110563
138032Speter/* 298125Sgshapiro * Copyright (c) 1998-2002 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 * 12102533Sgshapiro * $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 110563 2003-02-08 20:35:51Z gshapiro $ 13102533Sgshapiro * 1438032Speter */ 1538032Speter 1690795Sgshapiro#include <sm/gen.h> 1790795Sgshapiro 1890795SgshapiroSM_IDSTR(copyright, 1973191Sgshapiro"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\ 2064565Sgshapiro All rights reserved.\n\ 2164565Sgshapiro Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\ 2264565Sgshapiro Copyright (c) 1993\n\ 2390795Sgshapiro The Regents of the University of California. All rights reserved.\n") 2438032Speter 25110563SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.58.2.2 2002/09/24 21:40:05 ca Exp $") 2664565Sgshapiro 2738032Speter/* 2838032Speter** SMRSH -- sendmail restricted shell 2938032Speter** 3038032Speter** This is a patch to get around the prog mailer bugs in most 3138032Speter** versions of sendmail. 3238032Speter** 3338032Speter** Use this in place of /bin/sh in the "prog" mailer definition 3438032Speter** in your sendmail.cf file. You then create CMDDIR (owned by 3538032Speter** root, mode 755) and put links to any programs you want 3638032Speter** available to prog mailers in that directory. This should 3738032Speter** include things like "vacation" and "procmail", but not "sed" 3838032Speter** or "sh". 3938032Speter** 4038032Speter** Leading pathnames are stripped from program names so that 4138032Speter** existing .forward files that reference things like 4238081Speter** "/usr/bin/vacation" will continue to work. 4338032Speter** 4438032Speter** The following characters are completely illegal: 4564565Sgshapiro** < > ^ & ` ( ) \n \r 4664565Sgshapiro** The following characters are sometimes illegal: 4764565Sgshapiro** | & 4838032Speter** This is more restrictive than strictly necessary. 4938032Speter** 5064565Sgshapiro** To use this, add FEATURE(`smrsh') to your .mc file. 5138032Speter** 5238032Speter** This can be used on any version of sendmail. 5338032Speter** 5438032Speter** In loving memory of RTM. 11/02/93. 5538032Speter*/ 5638032Speter 5738032Speter#include <unistd.h> 5890795Sgshapiro#include <sm/io.h> 5998125Sgshapiro#include <sm/limits.h> 6090795Sgshapiro#include <sm/string.h> 6138032Speter#include <sys/file.h> 62105016Sgshapiro#include <sys/types.h> 63105016Sgshapiro#include <sys/stat.h> 6438032Speter#include <string.h> 6538032Speter#include <ctype.h> 6664565Sgshapiro#include <errno.h> 6738032Speter#ifdef EX_OK 6838032Speter# undef EX_OK 6964565Sgshapiro#endif /* EX_OK */ 7038032Speter#include <sysexits.h> 7138032Speter#include <syslog.h> 7238032Speter#include <stdlib.h> 7338032Speter 7490795Sgshapiro#include <sm/conf.h> 7590795Sgshapiro#include <sm/errstring.h> 7664565Sgshapiro 7738032Speter/* directory in which all commands must reside */ 7838032Speter#ifndef CMDDIR 7990795Sgshapiro# ifdef SMRSH_CMDDIR 8090795Sgshapiro# define CMDDIR SMRSH_CMDDIR 8190795Sgshapiro# else /* SMRSH_CMDDIR */ 8290795Sgshapiro# define CMDDIR "/usr/adm/sm.bin" 8390795Sgshapiro# endif /* SMRSH_CMDDIR */ 8464565Sgshapiro#endif /* ! CMDDIR */ 8538032Speter 8638032Speter/* characters disallowed in the shell "-c" argument */ 8738032Speter#define SPECIALS "<|>^();&`$\r\n" 8838032Speter 8938032Speter/* default search path */ 9038032Speter#ifndef PATH 9190795Sgshapiro# ifdef SMRSH_PATH 9290795Sgshapiro# define PATH SMRSH_PATH 9390795Sgshapiro# else /* SMRSH_PATH */ 9490795Sgshapiro# define PATH "/bin:/usr/bin:/usr/ucb" 9590795Sgshapiro# endif /* SMRSH_PATH */ 9664565Sgshapiro#endif /* ! PATH */ 9738032Speter 9864565Sgshapirochar newcmdbuf[1000]; 9964565Sgshapirochar *prg, *par; 10064565Sgshapiro 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 11464565Sgshapirovoid 11564565Sgshapiroaddcmd(s, cmd, len) 11664565Sgshapiro char *s; 11790795Sgshapiro bool cmd; 11890795Sgshapiro size_t len; 11964565Sgshapiro{ 12064565Sgshapiro if (s == NULL || *s == '\0') 12164565Sgshapiro return; 12264565Sgshapiro 12364565Sgshapiro if (sizeof newcmdbuf - strlen(newcmdbuf) <= 12464565Sgshapiro len + (cmd ? (strlen(CMDDIR) + 1) : 0)) 12564565Sgshapiro { 12690795Sgshapiro (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 12790795Sgshapiro "%s: command too long: %s\n", prg, par); 12864565Sgshapiro#ifndef DEBUG 12964565Sgshapiro syslog(LOG_WARNING, "command too long: %.40s", par); 13064565Sgshapiro#endif /* ! DEBUG */ 13164565Sgshapiro exit(EX_UNAVAILABLE); 13264565Sgshapiro } 13364565Sgshapiro if (cmd) 13498125Sgshapiro (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf); 13590795Sgshapiro (void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf); 13664565Sgshapiro} 13764565Sgshapiro 13838032Speterint 13938032Spetermain(argc, argv) 14038032Speter int argc; 14138032Speter char **argv; 14238032Speter{ 14338032Speter register char *p; 14438032Speter register char *q; 14564565Sgshapiro register char *r; 14638032Speter register char *cmd; 14764565Sgshapiro int isexec; 14864565Sgshapiro int save_errno; 14938032Speter char *newenv[2]; 15038032Speter char pathbuf[1000]; 15164565Sgshapiro char specialbuf[32]; 152105016Sgshapiro struct stat st; 15338032Speter 15464565Sgshapiro#ifndef DEBUG 15564565Sgshapiro# ifndef LOG_MAIL 15638032Speter openlog("smrsh", 0); 15764565Sgshapiro# else /* ! LOG_MAIL */ 15838032Speter openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 15964565Sgshapiro# endif /* ! LOG_MAIL */ 16064565Sgshapiro#endif /* ! DEBUG */ 16138032Speter 16298125Sgshapiro (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH); 16338032Speter newenv[0] = pathbuf; 16438032Speter newenv[1] = NULL; 16538032Speter 16638032Speter /* 16738032Speter ** Do basic argv usage checking 16838032Speter */ 16938032Speter 17064565Sgshapiro prg = argv[0]; 17164565Sgshapiro 17238032Speter if (argc != 3 || strcmp(argv[1], "-c") != 0) 17338032Speter { 17490795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 17590795Sgshapiro "Usage: %s -c command\n", prg); 17664565Sgshapiro#ifndef DEBUG 17738032Speter syslog(LOG_ERR, "usage"); 17864565Sgshapiro#endif /* ! DEBUG */ 17938032Speter exit(EX_USAGE); 18038032Speter } 18138032Speter 18277352Sgshapiro par = argv[2]; 18377352Sgshapiro 18438032Speter /* 18538032Speter ** Disallow special shell syntax. This is overly restrictive, 18638032Speter ** but it should shut down all attacks. 18738032Speter ** Be sure to include 8-bit versions, since many shells strip 18838032Speter ** the address to 7 bits before checking. 18938032Speter */ 19038032Speter 19164565Sgshapiro if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 19238032Speter { 19364565Sgshapiro#ifndef DEBUG 19464565Sgshapiro syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 19564565Sgshapiro#endif /* ! DEBUG */ 19638032Speter exit(EX_UNAVAILABLE); 19738032Speter } 19890795Sgshapiro (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 19964565Sgshapiro for (p = specialbuf; *p != '\0'; p++) 20064565Sgshapiro *p |= '\200'; 20190795Sgshapiro (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); 20238032Speter 20338032Speter /* 20438032Speter ** Do a quick sanity check on command line length. 20538032Speter */ 20638032Speter 20790795Sgshapiro if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 20838032Speter { 20990795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 21090795Sgshapiro "%s: command too long: %s\n", prg, par); 21164565Sgshapiro#ifndef DEBUG 21264565Sgshapiro syslog(LOG_WARNING, "command too long: %.40s", par); 21364565Sgshapiro#endif /* ! DEBUG */ 21438032Speter exit(EX_UNAVAILABLE); 21538032Speter } 21638032Speter 21764565Sgshapiro q = par; 21864565Sgshapiro newcmdbuf[0] = '\0'; 21990795Sgshapiro isexec = false; 22038032Speter 22198125Sgshapiro while (*q != '\0') 22238032Speter { 22364565Sgshapiro /* 22464565Sgshapiro ** Strip off a leading pathname on the command name. For 22564565Sgshapiro ** example, change /usr/ucb/vacation to vacation. 22664565Sgshapiro */ 22738032Speter 22864565Sgshapiro /* strip leading spaces */ 22964565Sgshapiro while (*q != '\0' && isascii(*q) && isspace(*q)) 23064565Sgshapiro q++; 23164565Sgshapiro if (*q == '\0') 23238032Speter { 23364565Sgshapiro if (isexec) 23464565Sgshapiro { 23590795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 23690795Sgshapiro "%s: missing command to exec\n", 23790795Sgshapiro prg); 23864565Sgshapiro#ifndef DEBUG 23990795Sgshapiro syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); 24064565Sgshapiro#endif /* ! DEBUG */ 24164565Sgshapiro exit(EX_UNAVAILABLE); 24264565Sgshapiro } 24338032Speter break; 24438032Speter } 24538032Speter 24664565Sgshapiro /* find the end of the command name */ 24764565Sgshapiro p = strpbrk(q, " \t"); 24864565Sgshapiro if (p == NULL) 24964565Sgshapiro cmd = &q[strlen(q)]; 25064565Sgshapiro else 25164565Sgshapiro { 25264565Sgshapiro *p = '\0'; 25364565Sgshapiro cmd = p; 25464565Sgshapiro } 25564565Sgshapiro /* search backwards for last / (allow for 0200 bit) */ 25664565Sgshapiro while (cmd > q) 25764565Sgshapiro { 25864565Sgshapiro if ((*--cmd & 0177) == '/') 25964565Sgshapiro { 26064565Sgshapiro cmd++; 26164565Sgshapiro break; 26264565Sgshapiro } 26364565Sgshapiro } 26464565Sgshapiro /* cmd now points at final component of path name */ 26538032Speter 26664565Sgshapiro /* allow a few shell builtins */ 26764565Sgshapiro if (strcmp(q, "exec") == 0 && p != NULL) 26864565Sgshapiro { 26990795Sgshapiro addcmd("exec ", false, strlen("exec ")); 27098125Sgshapiro 27164565Sgshapiro /* test _next_ arg */ 27264565Sgshapiro q = ++p; 27390795Sgshapiro isexec = true; 27464565Sgshapiro continue; 27564565Sgshapiro } 27664565Sgshapiro else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 27764565Sgshapiro { 27890795Sgshapiro addcmd(cmd, false, strlen(cmd)); 27998125Sgshapiro 28064565Sgshapiro /* test following chars */ 28164565Sgshapiro } 28264565Sgshapiro else 28364565Sgshapiro { 28498125Sgshapiro char cmdbuf[MAXPATHLEN]; 28598125Sgshapiro 28664565Sgshapiro /* 28764565Sgshapiro ** Check to see if the command name is legal. 28864565Sgshapiro */ 28998125Sgshapiro 29098125Sgshapiro if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR, 29198125Sgshapiro "/", cmd) >= sizeof cmdbuf) 29298125Sgshapiro { 29398125Sgshapiro /* too long */ 29498125Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 295110563Sgshapiro "%s: \"%s\" not available for sendmail programs (filename too long)\n", 29698125Sgshapiro prg, cmd); 29798125Sgshapiro if (p != NULL) 29898125Sgshapiro *p = ' '; 29998125Sgshapiro#ifndef DEBUG 300110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)", 30198125Sgshapiro (int) getuid(), cmd); 30298125Sgshapiro#endif /* ! DEBUG */ 30398125Sgshapiro exit(EX_UNAVAILABLE); 30498125Sgshapiro } 30598125Sgshapiro 30664565Sgshapiro#ifdef DEBUG 30790795Sgshapiro (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 30890795Sgshapiro "Trying %s\n", cmdbuf); 30964565Sgshapiro#endif /* DEBUG */ 310105016Sgshapiro if (stat(cmdbuf, &st) < 0) 311105016Sgshapiro { 312105016Sgshapiro /* can't stat it */ 313105016Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 314110563Sgshapiro "%s: \"%s\" not available for sendmail programs (stat failed)\n", 315105016Sgshapiro prg, cmd); 316105016Sgshapiro if (p != NULL) 317105016Sgshapiro *p = ' '; 318105016Sgshapiro#ifndef DEBUG 319110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)", 320105016Sgshapiro (int) getuid(), cmd); 321105016Sgshapiro#endif /* ! DEBUG */ 322105016Sgshapiro exit(EX_UNAVAILABLE); 323105016Sgshapiro } 324105016Sgshapiro if (!S_ISREG(st.st_mode) 325105016Sgshapiro#ifdef S_ISLNK 326105016Sgshapiro && !S_ISLNK(st.st_mode) 327105016Sgshapiro#endif /* S_ISLNK */ 328105016Sgshapiro ) 329105016Sgshapiro { 330105016Sgshapiro /* can't stat it */ 331105016Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 332110563Sgshapiro "%s: \"%s\" not available for sendmail programs (not a file)\n", 333105016Sgshapiro prg, cmd); 334105016Sgshapiro if (p != NULL) 335105016Sgshapiro *p = ' '; 336105016Sgshapiro#ifndef DEBUG 337110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)", 338105016Sgshapiro (int) getuid(), cmd); 339105016Sgshapiro#endif /* ! DEBUG */ 340105016Sgshapiro exit(EX_UNAVAILABLE); 341105016Sgshapiro } 34264565Sgshapiro if (access(cmdbuf, X_OK) < 0) 34364565Sgshapiro { 34464565Sgshapiro /* oops.... crack attack possiblity */ 34590795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 346110563Sgshapiro "%s: \"%s\" not available for sendmail programs\n", 34790795Sgshapiro prg, cmd); 34864565Sgshapiro if (p != NULL) 34964565Sgshapiro *p = ' '; 35064565Sgshapiro#ifndef DEBUG 351110563Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"", 35290795Sgshapiro (int) getuid(), cmd); 35364565Sgshapiro#endif /* ! DEBUG */ 35464565Sgshapiro exit(EX_UNAVAILABLE); 35564565Sgshapiro } 35638032Speter 35764565Sgshapiro /* 35864565Sgshapiro ** Create the actual shell input. 35964565Sgshapiro */ 36064565Sgshapiro 36190795Sgshapiro addcmd(cmd, true, strlen(cmd)); 36264565Sgshapiro } 36390795Sgshapiro isexec = false; 36464565Sgshapiro 36538032Speter if (p != NULL) 36638032Speter *p = ' '; 36764565Sgshapiro else 36864565Sgshapiro break; 36964565Sgshapiro 37064565Sgshapiro r = strpbrk(p, specialbuf); 37190795Sgshapiro if (r == NULL) 37290795Sgshapiro { 37390795Sgshapiro addcmd(p, false, strlen(p)); 37464565Sgshapiro break; 37564565Sgshapiro } 37664565Sgshapiro#if ALLOWSEMI 37790795Sgshapiro if (*r == ';') 37890795Sgshapiro { 37990795Sgshapiro addcmd(p, false, r - p + 1); 38064565Sgshapiro q = r + 1; 38164565Sgshapiro continue; 38264565Sgshapiro } 38364565Sgshapiro#endif /* ALLOWSEMI */ 38464565Sgshapiro if ((*r == '&' && *(r + 1) == '&') || 38564565Sgshapiro (*r == '|' && *(r + 1) == '|')) 38664565Sgshapiro { 38790795Sgshapiro addcmd(p, false, r - p + 2); 38864565Sgshapiro q = r + 2; 38964565Sgshapiro continue; 39064565Sgshapiro } 39164565Sgshapiro 39290795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 39390795Sgshapiro "%s: cannot use %c in command\n", prg, *r); 39464565Sgshapiro#ifndef DEBUG 39564565Sgshapiro syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 39690795Sgshapiro (int) getuid(), *r, par); 39764565Sgshapiro#endif /* ! DEBUG */ 39838032Speter exit(EX_UNAVAILABLE); 39998125Sgshapiro } 40064565Sgshapiro if (isexec) 40164565Sgshapiro { 40290795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 40390795Sgshapiro "%s: missing command to exec\n", prg); 40464565Sgshapiro#ifndef DEBUG 40590795Sgshapiro syslog(LOG_CRIT, "uid %d: missing command to exec", 40690795Sgshapiro (int) getuid()); 40764565Sgshapiro#endif /* ! DEBUG */ 40864565Sgshapiro exit(EX_UNAVAILABLE); 40938032Speter } 41064565Sgshapiro /* make sure we created something */ 41164565Sgshapiro if (newcmdbuf[0] == '\0') 41264565Sgshapiro { 41390795Sgshapiro (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 41490795Sgshapiro "Usage: %s -c command\n", prg); 41564565Sgshapiro#ifndef DEBUG 41664565Sgshapiro syslog(LOG_ERR, "usage"); 41764565Sgshapiro#endif /* ! DEBUG */ 41864565Sgshapiro exit(EX_USAGE); 41964565Sgshapiro } 42038032Speter 42138032Speter /* 42238032Speter ** Now invoke the shell 42338032Speter */ 42438032Speter 42538032Speter#ifdef DEBUG 42690795Sgshapiro (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf); 42764565Sgshapiro#endif /* DEBUG */ 42864565Sgshapiro (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 42964565Sgshapiro save_errno = errno; 43064565Sgshapiro#ifndef DEBUG 43190795Sgshapiro syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 43264565Sgshapiro#endif /* ! DEBUG */ 43364565Sgshapiro errno = save_errno; 43490795Sgshapiro sm_perror("/bin/sh"); 43538032Speter exit(EX_OSFILE); 43664565Sgshapiro /* NOTREACHED */ 43764565Sgshapiro return EX_OSFILE; 43838032Speter} 439