/*
* Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 1993 Eric P. Allman. All rights reserved.
* Copyright (c) 1993
* The Regents of the University of California. All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*
*/
#include <sm/gen.h>
SM_IDSTR(copyright,
"@(#) Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.\n\
All rights reserved.\n\
Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\
Copyright (c) 1993\n\
The Regents of the University of California. All rights reserved.\n")
SM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.66 2013-11-22 20:52:00 ca Exp $")
/*
** SMRSH -- sendmail restricted shell
**
** This is a patch to get around the prog mailer bugs in most
** versions of sendmail.
**
** Use this in place of /bin/sh in the "prog" mailer definition
** in your sendmail.cf file. You then create CMDDIR (owned by
** root, mode 755) and put links to any programs you want
** available to prog mailers in that directory. This should
** include things like "vacation" and "procmail", but not "sed"
** or "sh".
**
** Leading pathnames are stripped from program names so that
** existing .forward files that reference things like
** "/usr/bin/vacation" will continue to work.
**
** The following characters are completely illegal:
** < > ^ & ` ( ) \n \r
** The following characters are sometimes illegal:
** | &
** This is more restrictive than strictly necessary.
**
** To use this, add FEATURE(`smrsh') to your .mc file.
**
** This can be used on any version of sendmail.
**
** In loving memory of RTM. 11/02/93.
*/
#include <unistd.h>
#include <sm/io.h>
#include <sm/limits.h>
#include <sm/string.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef EX_OK
# undef EX_OK
#endif
#include <sysexits.h>
#include <syslog.h>
#include <stdlib.h>
#include <sm/conf.h>
#include <sm/errstring.h>
/* directory in which all commands must reside */
#ifndef CMDDIR
# ifdef SMRSH_CMDDIR
# define CMDDIR SMRSH_CMDDIR
# else
# define CMDDIR "/usr/adm/sm.bin"
# endif
#endif /* ! CMDDIR */
/* characters disallowed in the shell "-c" argument */
#define SPECIALS "<|>^();&`$\r\n"
/* default search path */
#ifndef PATH
# ifdef SMRSH_PATH
# define PATH SMRSH_PATH
# else
# define PATH "/bin:/usr/bin:/usr/ucb"
# endif
#endif /* ! PATH */
char newcmdbuf[1000];
char *prg, *par;
static void addcmd __P((char *, bool, size_t));
/*
** ADDCMD -- add a string to newcmdbuf, check for overflow
**
** Parameters:
** s -- string to add
** cmd -- it's a command: prepend CMDDIR/
** len -- length of string to add
**
** Side Effects:
** changes newcmdbuf or exits with a failure.
**
*/
static void
addcmd(s, cmd, len)
char *s;
bool cmd;
size_t len;
{
if (s == NULL || *s == '\0')
return;
/* enough space for s (len) and CMDDIR + "/" and '\0'? */
if (sizeof newcmdbuf - strlen(newcmdbuf) <=
len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
{
(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: command too long: %s\n", prg, par);
#ifndef DEBUG
syslog(LOG_WARNING, "command too long: %.40s", par);
#endif
exit(EX_UNAVAILABLE);
}
if (cmd)
(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
(void) strncat(newcmdbuf, s, len);
}
int
main(argc, argv)
int argc;
char **argv;
{
register char *p;
register char *q;
register char *r;
register char *cmd;
int isexec;
int save_errno;
char *newenv[2];
char pathbuf[1000];
char specialbuf[32];
struct stat st;
#ifndef DEBUG
# ifndef LOG_MAIL
openlog("smrsh", 0);
# else
openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
# endif
#endif /* ! DEBUG */
(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
newenv[0] = pathbuf;
newenv[1] = NULL;
/*
** Do basic argv usage checking
*/
prg = argv[0];
if (argc != 3 || strcmp(argv[1], "-c") != 0)
{
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"Usage: %s -c command\n", prg);
#ifndef DEBUG
syslog(LOG_ERR, "usage");
#endif
exit(EX_USAGE);
}
par = argv[2];
/*
** Disallow special shell syntax. This is overly restrictive,
** but it should shut down all attacks.
** Be sure to include 8-bit versions, since many shells strip
** the address to 7 bits before checking.
*/
if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
{
#ifndef DEBUG
syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
#endif
exit(EX_UNAVAILABLE);
}
(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
for (p = specialbuf; *p != '\0'; p++)
*p |= '\200';
(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
/*
** Do a quick sanity check on command line length.
*/
if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
{
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: command too long: %s\n", prg, par);
#ifndef DEBUG
syslog(LOG_WARNING, "command too long: %.40s", par);
#endif
exit(EX_UNAVAILABLE);
}
q = par;
newcmdbuf[0] = '\0';
isexec = false;
while (*q != '\0')
{
/*
** Strip off a leading pathname on the command name. For
** example, change /usr/ucb/vacation to vacation.
*/
/* strip leading spaces */
while (*q != '\0' && isascii(*q) && isspace(*q))
q++;
if (*q == '\0')
{
if (isexec)
{
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: missing command to exec\n",
prg);
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
#endif
exit(EX_UNAVAILABLE);
}
break;
}
/* find the end of the command name */
p = strpbrk(q, " \t");
if (p == NULL)
cmd = &q[strlen(q)];
else
{
*p = '\0';
cmd = p;
}
/* search backwards for last / (allow for 0200 bit) */
while (cmd > q)
{
if ((*--cmd & 0177) == '/')
{
cmd++;
break;
}
}
/* cmd now points at final component of path name */
/* allow a few shell builtins */
if (strcmp(q, "exec") == 0 && p != NULL)
{
addcmd("exec ", false, strlen("exec "));
/* test _next_ arg */
q = ++p;
isexec = true;
continue;
}
else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
{
addcmd(cmd, false, strlen(cmd));
/* test following chars */
}
else
{
char cmdbuf[MAXPATHLEN];
/*
** Check to see if the command name is legal.
*/
if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
"/", cmd) >= sizeof cmdbuf)
{
/* too long */
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: \"%s\" not available for sendmail programs (filename too long)\n",
prg, cmd);
if (p != NULL)
*p = ' ';
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
(int) getuid(), cmd);
#endif
exit(EX_UNAVAILABLE);
}
#ifdef DEBUG
(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
"Trying %s\n", cmdbuf);
#endif
if (stat(cmdbuf, &st) < 0)
{
/* can't stat it */
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: \"%s\" not available for sendmail programs (stat failed)\n",
prg, cmd);
if (p != NULL)
*p = ' ';
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
(int) getuid(), cmd);
#endif
exit(EX_UNAVAILABLE);
}
if (!S_ISREG(st.st_mode)
#ifdef S_ISLNK
&& !S_ISLNK(st.st_mode)
#endif
)
{
/* can't stat it */
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: \"%s\" not available for sendmail programs (not a file)\n",
prg, cmd);
if (p != NULL)
*p = ' ';
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
(int) getuid(), cmd);
#endif
exit(EX_UNAVAILABLE);
}
if (access(cmdbuf, X_OK) < 0)
{
/* oops.... crack attack possibility */
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: \"%s\" not available for sendmail programs\n",
prg, cmd);
if (p != NULL)
*p = ' ';
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
(int) getuid(), cmd);
#endif
exit(EX_UNAVAILABLE);
}
/*
** Create the actual shell input.
*/
addcmd(cmd, true, strlen(cmd));
}
isexec = false;
if (p != NULL)
*p = ' ';
else
break;
r = strpbrk(p, specialbuf);
if (r == NULL)
{
addcmd(p, false, strlen(p));
break;
}
#if ALLOWSEMI
if (*r == ';')
{
addcmd(p, false, r - p + 1);
q = r + 1;
continue;
}
#endif /* ALLOWSEMI */
if ((*r == '&' && *(r + 1) == '&') ||
(*r == '|' && *(r + 1) == '|'))
{
addcmd(p, false, r - p + 2);
q = r + 2;
continue;
}
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: cannot use %c in command\n", prg, *r);
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
(int) getuid(), *r, par);
#endif
exit(EX_UNAVAILABLE);
}
if (isexec)
{
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"%s: missing command to exec\n", prg);
#ifndef DEBUG
syslog(LOG_CRIT, "uid %d: missing command to exec",
(int) getuid());
#endif
exit(EX_UNAVAILABLE);
}
/* make sure we created something */
if (newcmdbuf[0] == '\0')
{
(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
"Usage: %s -c command\n", prg);
#ifndef DEBUG
syslog(LOG_ERR, "usage");
#endif
exit(EX_USAGE);
}
/*
** Now invoke the shell
*/
#ifdef DEBUG
(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
#endif
(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
(char *)NULL, newenv);
save_errno = errno;
#ifndef DEBUG
syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
#endif
errno = save_errno;
sm_perror("/bin/sh");
exit(EX_OSFILE);
/* NOTREACHED */
return EX_OSFILE;
}