138032Speter/*
2261194Sgshapiro * Copyright (c) 1998-2004 Proofpoint, 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 *
1238032Speter */
1338032Speter
1490795Sgshapiro#include <sm/gen.h>
1590795Sgshapiro
1690795SgshapiroSM_IDSTR(copyright,
17261194Sgshapiro"@(#) Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.\n\
1864565Sgshapiro	All rights reserved.\n\
1964565Sgshapiro     Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
2064565Sgshapiro     Copyright (c) 1993\n\
2190795Sgshapiro	The Regents of the University of California.  All rights reserved.\n")
2238032Speter
23266527SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.66 2013-11-22 20:52:00 ca Exp $")
2464565Sgshapiro
2538032Speter/*
2638032Speter**  SMRSH -- sendmail restricted shell
2738032Speter**
2838032Speter**	This is a patch to get around the prog mailer bugs in most
2938032Speter**	versions of sendmail.
3038032Speter**
3138032Speter**	Use this in place of /bin/sh in the "prog" mailer definition
3238032Speter**	in your sendmail.cf file.  You then create CMDDIR (owned by
3338032Speter**	root, mode 755) and put links to any programs you want
3438032Speter**	available to prog mailers in that directory.  This should
3538032Speter**	include things like "vacation" and "procmail", but not "sed"
3638032Speter**	or "sh".
3738032Speter**
3838032Speter**	Leading pathnames are stripped from program names so that
3938032Speter**	existing .forward files that reference things like
4038081Speter**	"/usr/bin/vacation" will continue to work.
4138032Speter**
4238032Speter**	The following characters are completely illegal:
4364565Sgshapiro**		<  >  ^  &  `  (  ) \n \r
4464565Sgshapiro**	The following characters are sometimes illegal:
4564565Sgshapiro**		|  &
4638032Speter**	This is more restrictive than strictly necessary.
4738032Speter**
4864565Sgshapiro**	To use this, add FEATURE(`smrsh') to your .mc file.
4938032Speter**
5038032Speter**	This can be used on any version of sendmail.
5138032Speter**
5238032Speter**	In loving memory of RTM.  11/02/93.
5338032Speter*/
5438032Speter
5538032Speter#include <unistd.h>
5690795Sgshapiro#include <sm/io.h>
5798125Sgshapiro#include <sm/limits.h>
5890795Sgshapiro#include <sm/string.h>
5938032Speter#include <sys/file.h>
60105016Sgshapiro#include <sys/types.h>
61105016Sgshapiro#include <sys/stat.h>
6238032Speter#include <string.h>
6338032Speter#include <ctype.h>
6464565Sgshapiro#include <errno.h>
6538032Speter#ifdef EX_OK
6638032Speter# undef EX_OK
67363466Sgshapiro#endif
6838032Speter#include <sysexits.h>
6938032Speter#include <syslog.h>
7038032Speter#include <stdlib.h>
7138032Speter
7290795Sgshapiro#include <sm/conf.h>
7390795Sgshapiro#include <sm/errstring.h>
7464565Sgshapiro
7538032Speter/* directory in which all commands must reside */
7638032Speter#ifndef CMDDIR
7790795Sgshapiro# ifdef SMRSH_CMDDIR
7890795Sgshapiro#  define CMDDIR	SMRSH_CMDDIR
79363466Sgshapiro# else
8090795Sgshapiro#  define CMDDIR	"/usr/adm/sm.bin"
81363466Sgshapiro# endif
8264565Sgshapiro#endif /* ! CMDDIR */
8338032Speter
8438032Speter/* characters disallowed in the shell "-c" argument */
8538032Speter#define SPECIALS	"<|>^();&`$\r\n"
8638032Speter
8738032Speter/* default search path */
8838032Speter#ifndef PATH
8990795Sgshapiro# ifdef SMRSH_PATH
9090795Sgshapiro#  define PATH		SMRSH_PATH
91363466Sgshapiro# else
9290795Sgshapiro#  define PATH		"/bin:/usr/bin:/usr/ucb"
93363466Sgshapiro# endif
9464565Sgshapiro#endif /* ! PATH */
9538032Speter
9664565Sgshapirochar newcmdbuf[1000];
9764565Sgshapirochar *prg, *par;
9864565Sgshapiro
99141862Sgshapirostatic void	addcmd __P((char *, bool, size_t));
100141862Sgshapiro
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
114141862Sgshapirostatic void
11564565Sgshapiroaddcmd(s, cmd, len)
11664565Sgshapiro	char *s;
11790795Sgshapiro	bool cmd;
11890795Sgshapiro	size_t len;
11964565Sgshapiro{
12064565Sgshapiro	if (s == NULL || *s == '\0')
12164565Sgshapiro		return;
12264565Sgshapiro
123125823Sgshapiro	/* enough space for s (len) and CMDDIR + "/" and '\0'? */
12464565Sgshapiro	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
125125823Sgshapiro	    len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
12664565Sgshapiro	{
12790795Sgshapiro		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
12890795Sgshapiro				    "%s: command too long: %s\n", prg, par);
12964565Sgshapiro#ifndef DEBUG
13064565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
131363466Sgshapiro#endif
13264565Sgshapiro		exit(EX_UNAVAILABLE);
13364565Sgshapiro	}
13464565Sgshapiro	if (cmd)
13598125Sgshapiro		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
136125823Sgshapiro	(void) strncat(newcmdbuf, s, len);
13764565Sgshapiro}
13864565Sgshapiro
13938032Speterint
14038032Spetermain(argc, argv)
14138032Speter	int argc;
14238032Speter	char **argv;
14338032Speter{
14438032Speter	register char *p;
14538032Speter	register char *q;
14664565Sgshapiro	register char *r;
14738032Speter	register char *cmd;
14864565Sgshapiro	int isexec;
14964565Sgshapiro	int save_errno;
15038032Speter	char *newenv[2];
15138032Speter	char pathbuf[1000];
15264565Sgshapiro	char specialbuf[32];
153105016Sgshapiro	struct stat st;
15438032Speter
15564565Sgshapiro#ifndef DEBUG
15664565Sgshapiro# ifndef LOG_MAIL
15738032Speter	openlog("smrsh", 0);
158363466Sgshapiro# else
15938032Speter	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
160363466Sgshapiro# endif
16164565Sgshapiro#endif /* ! DEBUG */
16238032Speter
16398125Sgshapiro	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
16438032Speter	newenv[0] = pathbuf;
16538032Speter	newenv[1] = NULL;
16638032Speter
16738032Speter	/*
16838032Speter	**  Do basic argv usage checking
16938032Speter	*/
17038032Speter
17164565Sgshapiro	prg = argv[0];
17264565Sgshapiro
17338032Speter	if (argc != 3 || strcmp(argv[1], "-c") != 0)
17438032Speter	{
17590795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
17690795Sgshapiro				     "Usage: %s -c command\n", prg);
17764565Sgshapiro#ifndef DEBUG
17838032Speter		syslog(LOG_ERR, "usage");
179363466Sgshapiro#endif
18038032Speter		exit(EX_USAGE);
18138032Speter	}
18238032Speter
18377352Sgshapiro	par = argv[2];
18477352Sgshapiro
18538032Speter	/*
18638032Speter	**  Disallow special shell syntax.  This is overly restrictive,
18738032Speter	**  but it should shut down all attacks.
18838032Speter	**  Be sure to include 8-bit versions, since many shells strip
18938032Speter	**  the address to 7 bits before checking.
19038032Speter	*/
19138032Speter
19264565Sgshapiro	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
19338032Speter	{
19464565Sgshapiro#ifndef DEBUG
19564565Sgshapiro		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
196363466Sgshapiro#endif
19738032Speter		exit(EX_UNAVAILABLE);
19838032Speter	}
19990795Sgshapiro	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
20064565Sgshapiro	for (p = specialbuf; *p != '\0'; p++)
20164565Sgshapiro		*p |= '\200';
20290795Sgshapiro	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
20338032Speter
20438032Speter	/*
20538032Speter	**  Do a quick sanity check on command line length.
20638032Speter	*/
20738032Speter
20890795Sgshapiro	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
20938032Speter	{
21090795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
21190795Sgshapiro				     "%s: command too long: %s\n", prg, par);
21264565Sgshapiro#ifndef DEBUG
21364565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
214363466Sgshapiro#endif
21538032Speter		exit(EX_UNAVAILABLE);
21638032Speter	}
21738032Speter
21864565Sgshapiro	q = par;
21964565Sgshapiro	newcmdbuf[0] = '\0';
22090795Sgshapiro	isexec = false;
22138032Speter
22298125Sgshapiro	while (*q != '\0')
22338032Speter	{
22464565Sgshapiro		/*
22564565Sgshapiro		**  Strip off a leading pathname on the command name.  For
22664565Sgshapiro		**  example, change /usr/ucb/vacation to vacation.
22764565Sgshapiro		*/
22838032Speter
22964565Sgshapiro		/* strip leading spaces */
23064565Sgshapiro		while (*q != '\0' && isascii(*q) && isspace(*q))
23164565Sgshapiro			q++;
23264565Sgshapiro		if (*q == '\0')
23338032Speter		{
23464565Sgshapiro			if (isexec)
23564565Sgshapiro			{
23690795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
23790795Sgshapiro						     "%s: missing command to exec\n",
23890795Sgshapiro						     prg);
23964565Sgshapiro#ifndef DEBUG
24090795Sgshapiro				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
241363466Sgshapiro#endif
24264565Sgshapiro				exit(EX_UNAVAILABLE);
24364565Sgshapiro			}
24438032Speter			break;
24538032Speter		}
24638032Speter
24764565Sgshapiro		/* find the end of the command name */
24864565Sgshapiro		p = strpbrk(q, " \t");
24964565Sgshapiro		if (p == NULL)
25064565Sgshapiro			cmd = &q[strlen(q)];
25164565Sgshapiro		else
25264565Sgshapiro		{
25364565Sgshapiro			*p = '\0';
25464565Sgshapiro			cmd = p;
25564565Sgshapiro		}
25664565Sgshapiro		/* search backwards for last / (allow for 0200 bit) */
25764565Sgshapiro		while (cmd > q)
25864565Sgshapiro		{
25964565Sgshapiro			if ((*--cmd & 0177) == '/')
26064565Sgshapiro			{
26164565Sgshapiro				cmd++;
26264565Sgshapiro				break;
26364565Sgshapiro			}
26464565Sgshapiro		}
26564565Sgshapiro		/* cmd now points at final component of path name */
26638032Speter
26764565Sgshapiro		/* allow a few shell builtins */
26864565Sgshapiro		if (strcmp(q, "exec") == 0 && p != NULL)
26964565Sgshapiro		{
27090795Sgshapiro			addcmd("exec ", false, strlen("exec "));
27198125Sgshapiro
27264565Sgshapiro			/* test _next_ arg */
27364565Sgshapiro			q = ++p;
27490795Sgshapiro			isexec = true;
27564565Sgshapiro			continue;
27664565Sgshapiro		}
27764565Sgshapiro		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
27864565Sgshapiro		{
27990795Sgshapiro			addcmd(cmd, false, strlen(cmd));
28098125Sgshapiro
28164565Sgshapiro			/* test following chars */
28264565Sgshapiro		}
28364565Sgshapiro		else
28464565Sgshapiro		{
28598125Sgshapiro			char cmdbuf[MAXPATHLEN];
28698125Sgshapiro
28764565Sgshapiro			/*
28864565Sgshapiro			**  Check to see if the command name is legal.
28964565Sgshapiro			*/
29098125Sgshapiro
29198125Sgshapiro			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
29298125Sgshapiro					"/", cmd) >= sizeof cmdbuf)
29398125Sgshapiro			{
29498125Sgshapiro				/* too long */
29598125Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
296110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
29798125Sgshapiro						      prg, cmd);
29898125Sgshapiro				if (p != NULL)
29998125Sgshapiro					*p = ' ';
30098125Sgshapiro#ifndef DEBUG
301110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
30298125Sgshapiro				       (int) getuid(), cmd);
303363466Sgshapiro#endif
30498125Sgshapiro				exit(EX_UNAVAILABLE);
30598125Sgshapiro			}
30698125Sgshapiro
30764565Sgshapiro#ifdef DEBUG
30890795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
30990795Sgshapiro					     "Trying %s\n", cmdbuf);
310363466Sgshapiro#endif
311105016Sgshapiro			if (stat(cmdbuf, &st) < 0)
312105016Sgshapiro			{
313105016Sgshapiro				/* can't stat it */
314105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
315110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
316105016Sgshapiro						      prg, cmd);
317105016Sgshapiro				if (p != NULL)
318105016Sgshapiro					*p = ' ';
319105016Sgshapiro#ifndef DEBUG
320110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
321105016Sgshapiro				       (int) getuid(), cmd);
322363466Sgshapiro#endif
323105016Sgshapiro				exit(EX_UNAVAILABLE);
324105016Sgshapiro			}
325105016Sgshapiro			if (!S_ISREG(st.st_mode)
326105016Sgshapiro#ifdef S_ISLNK
327105016Sgshapiro			    && !S_ISLNK(st.st_mode)
328363466Sgshapiro#endif
329105016Sgshapiro			   )
330105016Sgshapiro			{
331105016Sgshapiro				/* can't stat it */
332105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
333110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
334105016Sgshapiro						      prg, cmd);
335105016Sgshapiro				if (p != NULL)
336105016Sgshapiro					*p = ' ';
337105016Sgshapiro#ifndef DEBUG
338110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
339105016Sgshapiro				       (int) getuid(), cmd);
340363466Sgshapiro#endif
341105016Sgshapiro				exit(EX_UNAVAILABLE);
342105016Sgshapiro			}
34364565Sgshapiro			if (access(cmdbuf, X_OK) < 0)
34464565Sgshapiro			{
345363466Sgshapiro				/* oops....  crack attack possibility */
34690795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
347110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs\n",
34890795Sgshapiro						      prg, cmd);
34964565Sgshapiro				if (p != NULL)
35064565Sgshapiro					*p = ' ';
35164565Sgshapiro#ifndef DEBUG
352110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
35390795Sgshapiro				       (int) getuid(), cmd);
354363466Sgshapiro#endif
35564565Sgshapiro				exit(EX_UNAVAILABLE);
35664565Sgshapiro			}
35738032Speter
35864565Sgshapiro			/*
35964565Sgshapiro			**  Create the actual shell input.
36064565Sgshapiro			*/
36164565Sgshapiro
36290795Sgshapiro			addcmd(cmd, true, strlen(cmd));
36364565Sgshapiro		}
36490795Sgshapiro		isexec = false;
36564565Sgshapiro
36638032Speter		if (p != NULL)
36738032Speter			*p = ' ';
36864565Sgshapiro		else
36964565Sgshapiro			break;
37064565Sgshapiro
37164565Sgshapiro		r = strpbrk(p, specialbuf);
37290795Sgshapiro		if (r == NULL)
37390795Sgshapiro		{
37490795Sgshapiro			addcmd(p, false, strlen(p));
37564565Sgshapiro			break;
37664565Sgshapiro		}
37764565Sgshapiro#if ALLOWSEMI
37890795Sgshapiro		if (*r == ';')
37990795Sgshapiro		{
38090795Sgshapiro			addcmd(p, false,  r - p + 1);
38164565Sgshapiro			q = r + 1;
38264565Sgshapiro			continue;
38364565Sgshapiro		}
38464565Sgshapiro#endif /* ALLOWSEMI */
38564565Sgshapiro		if ((*r == '&' && *(r + 1) == '&') ||
38664565Sgshapiro		    (*r == '|' && *(r + 1) == '|'))
38764565Sgshapiro		{
38890795Sgshapiro			addcmd(p, false,  r - p + 2);
38964565Sgshapiro			q = r + 2;
39064565Sgshapiro			continue;
39164565Sgshapiro		}
39264565Sgshapiro
39390795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
39490795Sgshapiro				     "%s: cannot use %c in command\n", prg, *r);
39564565Sgshapiro#ifndef DEBUG
39664565Sgshapiro		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
39790795Sgshapiro		       (int) getuid(), *r, par);
398363466Sgshapiro#endif
39938032Speter		exit(EX_UNAVAILABLE);
40098125Sgshapiro	}
40164565Sgshapiro	if (isexec)
40264565Sgshapiro	{
40390795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
40490795Sgshapiro				     "%s: missing command to exec\n", prg);
40564565Sgshapiro#ifndef DEBUG
40690795Sgshapiro		syslog(LOG_CRIT, "uid %d: missing command to exec",
40790795Sgshapiro		       (int) getuid());
408363466Sgshapiro#endif
40964565Sgshapiro		exit(EX_UNAVAILABLE);
41038032Speter	}
41164565Sgshapiro	/* make sure we created something */
41264565Sgshapiro	if (newcmdbuf[0] == '\0')
41364565Sgshapiro	{
41490795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
41590795Sgshapiro				     "Usage: %s -c command\n", prg);
41664565Sgshapiro#ifndef DEBUG
41764565Sgshapiro		syslog(LOG_ERR, "usage");
418363466Sgshapiro#endif
41964565Sgshapiro		exit(EX_USAGE);
42064565Sgshapiro	}
42138032Speter
42238032Speter	/*
42338032Speter	**  Now invoke the shell
42438032Speter	*/
42538032Speter
42638032Speter#ifdef DEBUG
42790795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
428363466Sgshapiro#endif
429121826Sgshapiro	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
430121826Sgshapiro		      (char *)NULL, newenv);
43164565Sgshapiro	save_errno = errno;
43264565Sgshapiro#ifndef DEBUG
43390795Sgshapiro	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
434363466Sgshapiro#endif
43564565Sgshapiro	errno = save_errno;
43690795Sgshapiro	sm_perror("/bin/sh");
43738032Speter	exit(EX_OSFILE);
43864565Sgshapiro	/* NOTREACHED */
43964565Sgshapiro	return EX_OSFILE;
44038032Speter}
441