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