smrsh.c revision 261194
180016Sobrien/* 280016Sobrien * Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers. 380016Sobrien * All rights reserved. 480016Sobrien * Copyright (c) 1993 Eric P. Allman. All rights reserved. 580016Sobrien * Copyright (c) 1993 680016Sobrien * The Regents of the University of California. All rights reserved. 780016Sobrien * 880016Sobrien * By using this file, you agree to the terms and conditions set 980016Sobrien * forth in the LICENSE file which can be found at the top level of 1080016Sobrien * the sendmail distribution. 1180016Sobrien * 1280016Sobrien */ 1380016Sobrien 1480016Sobrien#include <sm/gen.h> 1580016Sobrien 1680016SobrienSM_IDSTR(copyright, 1780016Sobrien"@(#) Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.\n\ 1880016Sobrien All rights reserved.\n\ 1980016Sobrien Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\ 2080016Sobrien Copyright (c) 1993\n\ 2180016Sobrien The Regents of the University of California. All rights reserved.\n") 2280016Sobrien 2380016SobrienSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.66 2013/11/22 20:52:00 ca Exp $") 2480016Sobrien 2580016Sobrien/* 2680016Sobrien** SMRSH -- sendmail restricted shell 2780016Sobrien** 2880016Sobrien** This is a patch to get around the prog mailer bugs in most 2980016Sobrien** versions of sendmail. 3080016Sobrien** 3180016Sobrien** Use this in place of /bin/sh in the "prog" mailer definition 3280016Sobrien** in your sendmail.cf file. You then create CMDDIR (owned by 3380016Sobrien** root, mode 755) and put links to any programs you want 3480016Sobrien** available to prog mailers in that directory. This should 3580016Sobrien** include things like "vacation" and "procmail", but not "sed" 3680016Sobrien** or "sh". 3780016Sobrien** 3869159Sobrien** Leading pathnames are stripped from program names so that 3969159Sobrien** existing .forward files that reference things like 4069159Sobrien** "/usr/bin/vacation" will continue to work. 4169159Sobrien** 4269159Sobrien** The following characters are completely illegal: 4369159Sobrien** < > ^ & ` ( ) \n \r 4469159Sobrien** The following characters are sometimes illegal: 4569159Sobrien** | & 4669159Sobrien** This is more restrictive than strictly necessary. 4780016Sobrien** 4860484Sobrien** To use this, add FEATURE(`smrsh') to your .mc file. 4980016Sobrien** 5060484Sobrien** This can be used on any version of sendmail. 5180016Sobrien** 5260484Sobrien** In loving memory of RTM. 11/02/93. 5380016Sobrien*/ 5480016Sobrien 5580016Sobrien#include <unistd.h> 5680016Sobrien#include <sm/io.h> 5760484Sobrien#include <sm/limits.h> 5880016Sobrien#include <sm/string.h> 5960484Sobrien#include <sys/file.h> 6080016Sobrien#include <sys/types.h> 6160484Sobrien#include <sys/stat.h> 6280016Sobrien#include <string.h> 6380016Sobrien#include <ctype.h> 6480016Sobrien#include <errno.h> 6580016Sobrien#ifdef EX_OK 6680016Sobrien# undef EX_OK 6780016Sobrien#endif /* EX_OK */ 6880016Sobrien#include <sysexits.h> 6980016Sobrien#include <syslog.h> 7080016Sobrien#include <stdlib.h> 7180016Sobrien 7280016Sobrien#include <sm/conf.h> 7380016Sobrien#include <sm/errstring.h> 7480016Sobrien 7580016Sobrien/* directory in which all commands must reside */ 7680016Sobrien#ifndef CMDDIR 7780016Sobrien# ifdef SMRSH_CMDDIR 7880016Sobrien# define CMDDIR SMRSH_CMDDIR 7980016Sobrien# else /* SMRSH_CMDDIR */ 8080016Sobrien# define CMDDIR "/usr/adm/sm.bin" 8180016Sobrien# endif /* SMRSH_CMDDIR */ 8280016Sobrien#endif /* ! CMDDIR */ 8380016Sobrien 8480016Sobrien/* characters disallowed in the shell "-c" argument */ 8580016Sobrien#define SPECIALS "<|>^();&`$\r\n" 8680016Sobrien 8780016Sobrien/* default search path */ 8880016Sobrien#ifndef PATH 8980016Sobrien# ifdef SMRSH_PATH 9080016Sobrien# define PATH SMRSH_PATH 9180016Sobrien# else /* SMRSH_PATH */ 9280016Sobrien# define PATH "/bin:/usr/bin:/usr/ucb" 9380016Sobrien# endif /* SMRSH_PATH */ 9480016Sobrien#endif /* ! PATH */ 9580016Sobrien 9680016Sobrienchar newcmdbuf[1000]; 9780016Sobrienchar *prg, *par; 9880016Sobrien 9980016Sobrienstatic void addcmd __P((char *, bool, size_t)); 10080016Sobrien 10180016Sobrien/* 10280016Sobrien** ADDCMD -- add a string to newcmdbuf, check for overflow 10380016Sobrien** 10480016Sobrien** Parameters: 10580016Sobrien** s -- string to add 10680016Sobrien** cmd -- it's a command: prepend CMDDIR/ 10780016Sobrien** len -- length of string to add 10880016Sobrien** 10980016Sobrien** Side Effects: 11080016Sobrien** changes newcmdbuf or exits with a failure. 11180016Sobrien** 11280016Sobrien*/ 11380016Sobrien 11480016Sobrienstatic void 11580016Sobrienaddcmd(s, cmd, len) 11680016Sobrien char *s; 11780016Sobrien bool cmd; 11880016Sobrien size_t len; 11980016Sobrien{ 12080016Sobrien if (s == NULL || *s == '\0') 12180016Sobrien return; 12280016Sobrien 12380016Sobrien /* enough space for s (len) and CMDDIR + "/" and '\0'? */ 12480016Sobrien if (sizeof newcmdbuf - strlen(newcmdbuf) <= 12580016Sobrien len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0)) 12680016Sobrien { 12780016Sobrien (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 12880016Sobrien "%s: command too long: %s\n", prg, par); 12980016Sobrien#ifndef DEBUG 13080016Sobrien syslog(LOG_WARNING, "command too long: %.40s", par); 13180016Sobrien#endif /* ! DEBUG */ 13280016Sobrien exit(EX_UNAVAILABLE); 13380016Sobrien } 13480016Sobrien if (cmd) 13580016Sobrien (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf); 13680016Sobrien (void) strncat(newcmdbuf, s, len); 13780016Sobrien} 13880016Sobrien 13980016Sobrienint 14080016Sobrienmain(argc, argv) 14180016Sobrien int argc; 14280016Sobrien char **argv; 14380016Sobrien{ 14480016Sobrien register char *p; 14580016Sobrien register char *q; 14680016Sobrien register char *r; 14780016Sobrien register char *cmd; 14880016Sobrien int isexec; 14980016Sobrien int save_errno; 15080016Sobrien char *newenv[2]; 15180016Sobrien char pathbuf[1000]; 15280016Sobrien char specialbuf[32]; 15380016Sobrien struct stat st; 15480016Sobrien 15580016Sobrien#ifndef DEBUG 15680016Sobrien# ifndef LOG_MAIL 15780016Sobrien openlog("smrsh", 0); 15880016Sobrien# else /* ! LOG_MAIL */ 15980016Sobrien openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 16080016Sobrien# endif /* ! LOG_MAIL */ 16180016Sobrien#endif /* ! DEBUG */ 16280016Sobrien 16380016Sobrien (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH); 16480016Sobrien newenv[0] = pathbuf; 16580016Sobrien newenv[1] = NULL; 16680016Sobrien 16780016Sobrien /* 16880016Sobrien ** Do basic argv usage checking 16980016Sobrien */ 17080016Sobrien 17180016Sobrien prg = argv[0]; 17280016Sobrien 17380016Sobrien if (argc != 3 || strcmp(argv[1], "-c") != 0) 17480016Sobrien { 17580016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 17680016Sobrien "Usage: %s -c command\n", prg); 17780016Sobrien#ifndef DEBUG 17880016Sobrien syslog(LOG_ERR, "usage"); 17980016Sobrien#endif /* ! DEBUG */ 18080016Sobrien exit(EX_USAGE); 18180016Sobrien } 18280016Sobrien 18380016Sobrien par = argv[2]; 18480016Sobrien 18580016Sobrien /* 18680016Sobrien ** Disallow special shell syntax. This is overly restrictive, 18780016Sobrien ** but it should shut down all attacks. 18880016Sobrien ** Be sure to include 8-bit versions, since many shells strip 18980016Sobrien ** the address to 7 bits before checking. 19080016Sobrien */ 19180016Sobrien 19280016Sobrien if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 19380016Sobrien { 19480016Sobrien#ifndef DEBUG 19580016Sobrien syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 19680016Sobrien#endif /* ! DEBUG */ 19780016Sobrien exit(EX_UNAVAILABLE); 19880016Sobrien } 19980016Sobrien (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 20080016Sobrien for (p = specialbuf; *p != '\0'; p++) 20180016Sobrien *p |= '\200'; 20280016Sobrien (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); 20380016Sobrien 20480016Sobrien /* 20580016Sobrien ** Do a quick sanity check on command line length. 20680016Sobrien */ 20780016Sobrien 20880016Sobrien if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 20980016Sobrien { 21080016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 21180016Sobrien "%s: command too long: %s\n", prg, par); 21280016Sobrien#ifndef DEBUG 21380016Sobrien syslog(LOG_WARNING, "command too long: %.40s", par); 21480016Sobrien#endif /* ! DEBUG */ 21580016Sobrien exit(EX_UNAVAILABLE); 21680016Sobrien } 21780016Sobrien 21880016Sobrien q = par; 21980016Sobrien newcmdbuf[0] = '\0'; 22080016Sobrien isexec = false; 22180016Sobrien 22280016Sobrien while (*q != '\0') 22380016Sobrien { 22480016Sobrien /* 22580016Sobrien ** Strip off a leading pathname on the command name. For 22680016Sobrien ** example, change /usr/ucb/vacation to vacation. 22780016Sobrien */ 22880016Sobrien 22980016Sobrien /* strip leading spaces */ 23080016Sobrien while (*q != '\0' && isascii(*q) && isspace(*q)) 23180016Sobrien q++; 23280016Sobrien if (*q == '\0') 23380016Sobrien { 23480016Sobrien if (isexec) 23580016Sobrien { 23680016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 23780016Sobrien "%s: missing command to exec\n", 23880016Sobrien prg); 23980016Sobrien#ifndef DEBUG 24080016Sobrien syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); 24180016Sobrien#endif /* ! DEBUG */ 24280016Sobrien exit(EX_UNAVAILABLE); 24380016Sobrien } 24480016Sobrien break; 24580016Sobrien } 24680016Sobrien 24780016Sobrien /* find the end of the command name */ 24880016Sobrien p = strpbrk(q, " \t"); 24980016Sobrien if (p == NULL) 25080016Sobrien cmd = &q[strlen(q)]; 25180016Sobrien else 25280016Sobrien { 25380016Sobrien *p = '\0'; 25480016Sobrien cmd = p; 25580016Sobrien } 25680016Sobrien /* search backwards for last / (allow for 0200 bit) */ 25780016Sobrien while (cmd > q) 25880016Sobrien { 25980016Sobrien if ((*--cmd & 0177) == '/') 26080016Sobrien { 26180016Sobrien cmd++; 26280016Sobrien break; 26380016Sobrien } 26480016Sobrien } 26580016Sobrien /* cmd now points at final component of path name */ 26680016Sobrien 26780016Sobrien /* allow a few shell builtins */ 26880016Sobrien if (strcmp(q, "exec") == 0 && p != NULL) 26980016Sobrien { 27080016Sobrien addcmd("exec ", false, strlen("exec ")); 27180016Sobrien 27280016Sobrien /* test _next_ arg */ 27380016Sobrien q = ++p; 27480016Sobrien isexec = true; 27580016Sobrien continue; 27680016Sobrien } 27780016Sobrien else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 27880016Sobrien { 27980016Sobrien addcmd(cmd, false, strlen(cmd)); 28080016Sobrien 28180016Sobrien /* test following chars */ 28280016Sobrien } 28380016Sobrien else 28480016Sobrien { 28580016Sobrien char cmdbuf[MAXPATHLEN]; 28680016Sobrien 28780016Sobrien /* 28880016Sobrien ** Check to see if the command name is legal. 28980016Sobrien */ 29080016Sobrien 29180016Sobrien if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR, 29280016Sobrien "/", cmd) >= sizeof cmdbuf) 29380016Sobrien { 29480016Sobrien /* too long */ 29580016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 29680016Sobrien "%s: \"%s\" not available for sendmail programs (filename too long)\n", 29780016Sobrien prg, cmd); 29880016Sobrien if (p != NULL) 29980016Sobrien *p = ' '; 30080016Sobrien#ifndef DEBUG 30180016Sobrien syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)", 30280016Sobrien (int) getuid(), cmd); 30380016Sobrien#endif /* ! DEBUG */ 30480016Sobrien exit(EX_UNAVAILABLE); 30580016Sobrien } 30680016Sobrien 30780016Sobrien#ifdef DEBUG 30880016Sobrien (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 30980016Sobrien "Trying %s\n", cmdbuf); 31080016Sobrien#endif /* DEBUG */ 31180016Sobrien if (stat(cmdbuf, &st) < 0) 31280016Sobrien { 31380016Sobrien /* can't stat it */ 31480016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 31580016Sobrien "%s: \"%s\" not available for sendmail programs (stat failed)\n", 31680016Sobrien prg, cmd); 31780016Sobrien if (p != NULL) 31880016Sobrien *p = ' '; 31980016Sobrien#ifndef DEBUG 32080016Sobrien syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)", 32180016Sobrien (int) getuid(), cmd); 32280016Sobrien#endif /* ! DEBUG */ 32380016Sobrien exit(EX_UNAVAILABLE); 32480016Sobrien } 32580016Sobrien if (!S_ISREG(st.st_mode) 32680016Sobrien#ifdef S_ISLNK 32780016Sobrien && !S_ISLNK(st.st_mode) 32880016Sobrien#endif /* S_ISLNK */ 32980016Sobrien ) 33080016Sobrien { 33180016Sobrien /* can't stat it */ 33280016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 33380016Sobrien "%s: \"%s\" not available for sendmail programs (not a file)\n", 33480016Sobrien prg, cmd); 33580016Sobrien if (p != NULL) 33680016Sobrien *p = ' '; 33780016Sobrien#ifndef DEBUG 33880016Sobrien syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)", 33980016Sobrien (int) getuid(), cmd); 34080016Sobrien#endif /* ! DEBUG */ 34180016Sobrien exit(EX_UNAVAILABLE); 34280016Sobrien } 34380016Sobrien if (access(cmdbuf, X_OK) < 0) 34480016Sobrien { 34580016Sobrien /* oops.... crack attack possiblity */ 34680016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 34780016Sobrien "%s: \"%s\" not available for sendmail programs\n", 34880016Sobrien prg, cmd); 34980016Sobrien if (p != NULL) 35080016Sobrien *p = ' '; 35180016Sobrien#ifndef DEBUG 35280016Sobrien syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"", 35380016Sobrien (int) getuid(), cmd); 35480016Sobrien#endif /* ! DEBUG */ 35580016Sobrien exit(EX_UNAVAILABLE); 35680016Sobrien } 35780016Sobrien 35880016Sobrien /* 35980016Sobrien ** Create the actual shell input. 36080016Sobrien */ 36180016Sobrien 36280016Sobrien addcmd(cmd, true, strlen(cmd)); 36380016Sobrien } 36480016Sobrien isexec = false; 36580016Sobrien 36680016Sobrien if (p != NULL) 36780016Sobrien *p = ' '; 36880016Sobrien else 36980016Sobrien break; 37080016Sobrien 37180016Sobrien r = strpbrk(p, specialbuf); 37280016Sobrien if (r == NULL) 37380016Sobrien { 37480016Sobrien addcmd(p, false, strlen(p)); 37580016Sobrien break; 37680016Sobrien } 37780016Sobrien#if ALLOWSEMI 37880016Sobrien if (*r == ';') 37980016Sobrien { 38080016Sobrien addcmd(p, false, r - p + 1); 38180016Sobrien q = r + 1; 38280016Sobrien continue; 38380016Sobrien } 38480016Sobrien#endif /* ALLOWSEMI */ 38580016Sobrien if ((*r == '&' && *(r + 1) == '&') || 38680016Sobrien (*r == '|' && *(r + 1) == '|')) 38780016Sobrien { 38880016Sobrien addcmd(p, false, r - p + 2); 38980016Sobrien q = r + 2; 39080016Sobrien continue; 39180016Sobrien } 39280016Sobrien 39380016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 39480016Sobrien "%s: cannot use %c in command\n", prg, *r); 39580016Sobrien#ifndef DEBUG 39680016Sobrien syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 39780016Sobrien (int) getuid(), *r, par); 39880016Sobrien#endif /* ! DEBUG */ 39980016Sobrien exit(EX_UNAVAILABLE); 40080016Sobrien } 40180016Sobrien if (isexec) 40280016Sobrien { 40380016Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 40480016Sobrien "%s: missing command to exec\n", prg); 40580016Sobrien#ifndef DEBUG 40660484Sobrien syslog(LOG_CRIT, "uid %d: missing command to exec", 40760484Sobrien (int) getuid()); 40860484Sobrien#endif /* ! DEBUG */ 40960484Sobrien exit(EX_UNAVAILABLE); 41060484Sobrien } 41160484Sobrien /* make sure we created something */ 41280016Sobrien if (newcmdbuf[0] == '\0') 41360484Sobrien { 41460484Sobrien (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 41560484Sobrien "Usage: %s -c command\n", prg); 41660484Sobrien#ifndef DEBUG 41760484Sobrien syslog(LOG_ERR, "usage"); 41860484Sobrien#endif /* ! DEBUG */ 41960484Sobrien exit(EX_USAGE); 42060484Sobrien } 42160484Sobrien 42260484Sobrien /* 42360484Sobrien ** Now invoke the shell 42460484Sobrien */ 42560484Sobrien 42660484Sobrien#ifdef DEBUG 42760484Sobrien (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf); 42860484Sobrien#endif /* DEBUG */ 42960484Sobrien (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, 43080016Sobrien (char *)NULL, newenv); 43160484Sobrien save_errno = errno; 43260484Sobrien#ifndef DEBUG 43360484Sobrien syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 43460484Sobrien#endif /* ! DEBUG */ 43560484Sobrien errno = save_errno; 43660484Sobrien sm_perror("/bin/sh"); 43760484Sobrien exit(EX_OSFILE); 43860484Sobrien /* NOTREACHED */ 43960484Sobrien return EX_OSFILE; 44060484Sobrien} 44160484Sobrien