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