smrsh.c revision 121826
138032Speter/*
298125Sgshapiro * Copyright (c) 1998-2002 Sendmail, 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 *
12121826Sgshapiro * $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 121826 2003-10-31 22:12:09Z gshapiro $
1338032Speter */
1438032Speter
1590795Sgshapiro#include <sm/gen.h>
1690795Sgshapiro
1790795SgshapiroSM_IDSTR(copyright,
1873191Sgshapiro"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
1964565Sgshapiro	All rights reserved.\n\
2064565Sgshapiro     Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
2164565Sgshapiro     Copyright (c) 1993\n\
2290795Sgshapiro	The Regents of the University of California.  All rights reserved.\n")
2338032Speter
24110563SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.58.2.2 2002/09/24 21:40:05 ca Exp $")
2564565Sgshapiro
2638032Speter/*
2738032Speter**  SMRSH -- sendmail restricted shell
2838032Speter**
2938032Speter**	This is a patch to get around the prog mailer bugs in most
3038032Speter**	versions of sendmail.
3138032Speter**
3238032Speter**	Use this in place of /bin/sh in the "prog" mailer definition
3338032Speter**	in your sendmail.cf file.  You then create CMDDIR (owned by
3438032Speter**	root, mode 755) and put links to any programs you want
3538032Speter**	available to prog mailers in that directory.  This should
3638032Speter**	include things like "vacation" and "procmail", but not "sed"
3738032Speter**	or "sh".
3838032Speter**
3938032Speter**	Leading pathnames are stripped from program names so that
4038032Speter**	existing .forward files that reference things like
4138081Speter**	"/usr/bin/vacation" will continue to work.
4238032Speter**
4338032Speter**	The following characters are completely illegal:
4464565Sgshapiro**		<  >  ^  &  `  (  ) \n \r
4564565Sgshapiro**	The following characters are sometimes illegal:
4664565Sgshapiro**		|  &
4738032Speter**	This is more restrictive than strictly necessary.
4838032Speter**
4964565Sgshapiro**	To use this, add FEATURE(`smrsh') to your .mc file.
5038032Speter**
5138032Speter**	This can be used on any version of sendmail.
5238032Speter**
5338032Speter**	In loving memory of RTM.  11/02/93.
5438032Speter*/
5538032Speter
5638032Speter#include <unistd.h>
5790795Sgshapiro#include <sm/io.h>
5898125Sgshapiro#include <sm/limits.h>
5990795Sgshapiro#include <sm/string.h>
6038032Speter#include <sys/file.h>
61105016Sgshapiro#include <sys/types.h>
62105016Sgshapiro#include <sys/stat.h>
6338032Speter#include <string.h>
6438032Speter#include <ctype.h>
6564565Sgshapiro#include <errno.h>
6638032Speter#ifdef EX_OK
6738032Speter# undef EX_OK
6864565Sgshapiro#endif /* EX_OK */
6938032Speter#include <sysexits.h>
7038032Speter#include <syslog.h>
7138032Speter#include <stdlib.h>
7238032Speter
7390795Sgshapiro#include <sm/conf.h>
7490795Sgshapiro#include <sm/errstring.h>
7564565Sgshapiro
7638032Speter/* directory in which all commands must reside */
7738032Speter#ifndef CMDDIR
7890795Sgshapiro# ifdef SMRSH_CMDDIR
7990795Sgshapiro#  define CMDDIR	SMRSH_CMDDIR
8090795Sgshapiro# else /* SMRSH_CMDDIR */
8190795Sgshapiro#  define CMDDIR	"/usr/adm/sm.bin"
8290795Sgshapiro# endif /* SMRSH_CMDDIR */
8364565Sgshapiro#endif /* ! CMDDIR */
8438032Speter
8538032Speter/* characters disallowed in the shell "-c" argument */
8638032Speter#define SPECIALS	"<|>^();&`$\r\n"
8738032Speter
8838032Speter/* default search path */
8938032Speter#ifndef PATH
9090795Sgshapiro# ifdef SMRSH_PATH
9190795Sgshapiro#  define PATH		SMRSH_PATH
9290795Sgshapiro# else /* SMRSH_PATH */
9390795Sgshapiro#  define PATH		"/bin:/usr/bin:/usr/ucb"
9490795Sgshapiro# endif /* SMRSH_PATH */
9564565Sgshapiro#endif /* ! PATH */
9638032Speter
9764565Sgshapirochar newcmdbuf[1000];
9864565Sgshapirochar *prg, *par;
9964565Sgshapiro
10064565Sgshapiro/*
10164565Sgshapiro**  ADDCMD -- add a string to newcmdbuf, check for overflow
10264565Sgshapiro**
10364565Sgshapiro**    Parameters:
10464565Sgshapiro**	s -- string to add
10564565Sgshapiro**	cmd -- it's a command: prepend CMDDIR/
10664565Sgshapiro**	len -- length of string to add
10764565Sgshapiro**
10864565Sgshapiro**    Side Effects:
10964565Sgshapiro**	changes newcmdbuf or exits with a failure.
11064565Sgshapiro**
11164565Sgshapiro*/
11264565Sgshapiro
11364565Sgshapirovoid
11464565Sgshapiroaddcmd(s, cmd, len)
11564565Sgshapiro	char *s;
11690795Sgshapiro	bool cmd;
11790795Sgshapiro	size_t len;
11864565Sgshapiro{
11964565Sgshapiro	if (s == NULL || *s == '\0')
12064565Sgshapiro		return;
12164565Sgshapiro
12264565Sgshapiro	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
12364565Sgshapiro	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
12464565Sgshapiro	{
12590795Sgshapiro		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
12690795Sgshapiro				    "%s: command too long: %s\n", prg, par);
12764565Sgshapiro#ifndef DEBUG
12864565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
12964565Sgshapiro#endif /* ! DEBUG */
13064565Sgshapiro		exit(EX_UNAVAILABLE);
13164565Sgshapiro	}
13264565Sgshapiro	if (cmd)
13398125Sgshapiro		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
13490795Sgshapiro	(void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf);
13564565Sgshapiro}
13664565Sgshapiro
13738032Speterint
13838032Spetermain(argc, argv)
13938032Speter	int argc;
14038032Speter	char **argv;
14138032Speter{
14238032Speter	register char *p;
14338032Speter	register char *q;
14464565Sgshapiro	register char *r;
14538032Speter	register char *cmd;
14664565Sgshapiro	int isexec;
14764565Sgshapiro	int save_errno;
14838032Speter	char *newenv[2];
14938032Speter	char pathbuf[1000];
15064565Sgshapiro	char specialbuf[32];
151105016Sgshapiro	struct stat st;
15238032Speter
15364565Sgshapiro#ifndef DEBUG
15464565Sgshapiro# ifndef LOG_MAIL
15538032Speter	openlog("smrsh", 0);
15664565Sgshapiro# else /* ! LOG_MAIL */
15738032Speter	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
15864565Sgshapiro# endif /* ! LOG_MAIL */
15964565Sgshapiro#endif /* ! DEBUG */
16038032Speter
16198125Sgshapiro	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
16238032Speter	newenv[0] = pathbuf;
16338032Speter	newenv[1] = NULL;
16438032Speter
16538032Speter	/*
16638032Speter	**  Do basic argv usage checking
16738032Speter	*/
16838032Speter
16964565Sgshapiro	prg = argv[0];
17064565Sgshapiro
17138032Speter	if (argc != 3 || strcmp(argv[1], "-c") != 0)
17238032Speter	{
17390795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
17490795Sgshapiro				     "Usage: %s -c command\n", prg);
17564565Sgshapiro#ifndef DEBUG
17638032Speter		syslog(LOG_ERR, "usage");
17764565Sgshapiro#endif /* ! DEBUG */
17838032Speter		exit(EX_USAGE);
17938032Speter	}
18038032Speter
18177352Sgshapiro	par = argv[2];
18277352Sgshapiro
18338032Speter	/*
18438032Speter	**  Disallow special shell syntax.  This is overly restrictive,
18538032Speter	**  but it should shut down all attacks.
18638032Speter	**  Be sure to include 8-bit versions, since many shells strip
18738032Speter	**  the address to 7 bits before checking.
18838032Speter	*/
18938032Speter
19064565Sgshapiro	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
19138032Speter	{
19264565Sgshapiro#ifndef DEBUG
19364565Sgshapiro		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
19464565Sgshapiro#endif /* ! DEBUG */
19538032Speter		exit(EX_UNAVAILABLE);
19638032Speter	}
19790795Sgshapiro	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
19864565Sgshapiro	for (p = specialbuf; *p != '\0'; p++)
19964565Sgshapiro		*p |= '\200';
20090795Sgshapiro	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
20138032Speter
20238032Speter	/*
20338032Speter	**  Do a quick sanity check on command line length.
20438032Speter	*/
20538032Speter
20690795Sgshapiro	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
20738032Speter	{
20890795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
20990795Sgshapiro				     "%s: command too long: %s\n", prg, par);
21064565Sgshapiro#ifndef DEBUG
21164565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
21264565Sgshapiro#endif /* ! DEBUG */
21338032Speter		exit(EX_UNAVAILABLE);
21438032Speter	}
21538032Speter
21664565Sgshapiro	q = par;
21764565Sgshapiro	newcmdbuf[0] = '\0';
21890795Sgshapiro	isexec = false;
21938032Speter
22098125Sgshapiro	while (*q != '\0')
22138032Speter	{
22264565Sgshapiro		/*
22364565Sgshapiro		**  Strip off a leading pathname on the command name.  For
22464565Sgshapiro		**  example, change /usr/ucb/vacation to vacation.
22564565Sgshapiro		*/
22638032Speter
22764565Sgshapiro		/* strip leading spaces */
22864565Sgshapiro		while (*q != '\0' && isascii(*q) && isspace(*q))
22964565Sgshapiro			q++;
23064565Sgshapiro		if (*q == '\0')
23138032Speter		{
23264565Sgshapiro			if (isexec)
23364565Sgshapiro			{
23490795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
23590795Sgshapiro						     "%s: missing command to exec\n",
23690795Sgshapiro						     prg);
23764565Sgshapiro#ifndef DEBUG
23890795Sgshapiro				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
23964565Sgshapiro#endif /* ! DEBUG */
24064565Sgshapiro				exit(EX_UNAVAILABLE);
24164565Sgshapiro			}
24238032Speter			break;
24338032Speter		}
24438032Speter
24564565Sgshapiro		/* find the end of the command name */
24664565Sgshapiro		p = strpbrk(q, " \t");
24764565Sgshapiro		if (p == NULL)
24864565Sgshapiro			cmd = &q[strlen(q)];
24964565Sgshapiro		else
25064565Sgshapiro		{
25164565Sgshapiro			*p = '\0';
25264565Sgshapiro			cmd = p;
25364565Sgshapiro		}
25464565Sgshapiro		/* search backwards for last / (allow for 0200 bit) */
25564565Sgshapiro		while (cmd > q)
25664565Sgshapiro		{
25764565Sgshapiro			if ((*--cmd & 0177) == '/')
25864565Sgshapiro			{
25964565Sgshapiro				cmd++;
26064565Sgshapiro				break;
26164565Sgshapiro			}
26264565Sgshapiro		}
26364565Sgshapiro		/* cmd now points at final component of path name */
26438032Speter
26564565Sgshapiro		/* allow a few shell builtins */
26664565Sgshapiro		if (strcmp(q, "exec") == 0 && p != NULL)
26764565Sgshapiro		{
26890795Sgshapiro			addcmd("exec ", false, strlen("exec "));
26998125Sgshapiro
27064565Sgshapiro			/* test _next_ arg */
27164565Sgshapiro			q = ++p;
27290795Sgshapiro			isexec = true;
27364565Sgshapiro			continue;
27464565Sgshapiro		}
27564565Sgshapiro		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
27664565Sgshapiro		{
27790795Sgshapiro			addcmd(cmd, false, strlen(cmd));
27898125Sgshapiro
27964565Sgshapiro			/* test following chars */
28064565Sgshapiro		}
28164565Sgshapiro		else
28264565Sgshapiro		{
28398125Sgshapiro			char cmdbuf[MAXPATHLEN];
28498125Sgshapiro
28564565Sgshapiro			/*
28664565Sgshapiro			**  Check to see if the command name is legal.
28764565Sgshapiro			*/
28898125Sgshapiro
28998125Sgshapiro			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
29098125Sgshapiro					"/", cmd) >= sizeof cmdbuf)
29198125Sgshapiro			{
29298125Sgshapiro				/* too long */
29398125Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
294110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
29598125Sgshapiro						      prg, cmd);
29698125Sgshapiro				if (p != NULL)
29798125Sgshapiro					*p = ' ';
29898125Sgshapiro#ifndef DEBUG
299110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
30098125Sgshapiro				       (int) getuid(), cmd);
30198125Sgshapiro#endif /* ! DEBUG */
30298125Sgshapiro				exit(EX_UNAVAILABLE);
30398125Sgshapiro			}
30498125Sgshapiro
30564565Sgshapiro#ifdef DEBUG
30690795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
30790795Sgshapiro					     "Trying %s\n", cmdbuf);
30864565Sgshapiro#endif /* DEBUG */
309105016Sgshapiro			if (stat(cmdbuf, &st) < 0)
310105016Sgshapiro			{
311105016Sgshapiro				/* can't stat it */
312105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
313110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
314105016Sgshapiro						      prg, cmd);
315105016Sgshapiro				if (p != NULL)
316105016Sgshapiro					*p = ' ';
317105016Sgshapiro#ifndef DEBUG
318110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
319105016Sgshapiro				       (int) getuid(), cmd);
320105016Sgshapiro#endif /* ! DEBUG */
321105016Sgshapiro				exit(EX_UNAVAILABLE);
322105016Sgshapiro			}
323105016Sgshapiro			if (!S_ISREG(st.st_mode)
324105016Sgshapiro#ifdef S_ISLNK
325105016Sgshapiro			    && !S_ISLNK(st.st_mode)
326105016Sgshapiro#endif /* S_ISLNK */
327105016Sgshapiro			   )
328105016Sgshapiro			{
329105016Sgshapiro				/* can't stat it */
330105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
331110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
332105016Sgshapiro						      prg, cmd);
333105016Sgshapiro				if (p != NULL)
334105016Sgshapiro					*p = ' ';
335105016Sgshapiro#ifndef DEBUG
336110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
337105016Sgshapiro				       (int) getuid(), cmd);
338105016Sgshapiro#endif /* ! DEBUG */
339105016Sgshapiro				exit(EX_UNAVAILABLE);
340105016Sgshapiro			}
34164565Sgshapiro			if (access(cmdbuf, X_OK) < 0)
34264565Sgshapiro			{
34364565Sgshapiro				/* oops....  crack attack possiblity */
34490795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
345110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs\n",
34690795Sgshapiro						      prg, cmd);
34764565Sgshapiro				if (p != NULL)
34864565Sgshapiro					*p = ' ';
34964565Sgshapiro#ifndef DEBUG
350110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
35190795Sgshapiro				       (int) getuid(), cmd);
35264565Sgshapiro#endif /* ! DEBUG */
35364565Sgshapiro				exit(EX_UNAVAILABLE);
35464565Sgshapiro			}
35538032Speter
35664565Sgshapiro			/*
35764565Sgshapiro			**  Create the actual shell input.
35864565Sgshapiro			*/
35964565Sgshapiro
36090795Sgshapiro			addcmd(cmd, true, strlen(cmd));
36164565Sgshapiro		}
36290795Sgshapiro		isexec = false;
36364565Sgshapiro
36438032Speter		if (p != NULL)
36538032Speter			*p = ' ';
36664565Sgshapiro		else
36764565Sgshapiro			break;
36864565Sgshapiro
36964565Sgshapiro		r = strpbrk(p, specialbuf);
37090795Sgshapiro		if (r == NULL)
37190795Sgshapiro		{
37290795Sgshapiro			addcmd(p, false, strlen(p));
37364565Sgshapiro			break;
37464565Sgshapiro		}
37564565Sgshapiro#if ALLOWSEMI
37690795Sgshapiro		if (*r == ';')
37790795Sgshapiro		{
37890795Sgshapiro			addcmd(p, false,  r - p + 1);
37964565Sgshapiro			q = r + 1;
38064565Sgshapiro			continue;
38164565Sgshapiro		}
38264565Sgshapiro#endif /* ALLOWSEMI */
38364565Sgshapiro		if ((*r == '&' && *(r + 1) == '&') ||
38464565Sgshapiro		    (*r == '|' && *(r + 1) == '|'))
38564565Sgshapiro		{
38690795Sgshapiro			addcmd(p, false,  r - p + 2);
38764565Sgshapiro			q = r + 2;
38864565Sgshapiro			continue;
38964565Sgshapiro		}
39064565Sgshapiro
39190795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
39290795Sgshapiro				     "%s: cannot use %c in command\n", prg, *r);
39364565Sgshapiro#ifndef DEBUG
39464565Sgshapiro		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
39590795Sgshapiro		       (int) getuid(), *r, par);
39664565Sgshapiro#endif /* ! DEBUG */
39738032Speter		exit(EX_UNAVAILABLE);
39898125Sgshapiro	}
39964565Sgshapiro	if (isexec)
40064565Sgshapiro	{
40190795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
40290795Sgshapiro				     "%s: missing command to exec\n", prg);
40364565Sgshapiro#ifndef DEBUG
40490795Sgshapiro		syslog(LOG_CRIT, "uid %d: missing command to exec",
40590795Sgshapiro		       (int) getuid());
40664565Sgshapiro#endif /* ! DEBUG */
40764565Sgshapiro		exit(EX_UNAVAILABLE);
40838032Speter	}
40964565Sgshapiro	/* make sure we created something */
41064565Sgshapiro	if (newcmdbuf[0] == '\0')
41164565Sgshapiro	{
41290795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
41390795Sgshapiro				     "Usage: %s -c command\n", prg);
41464565Sgshapiro#ifndef DEBUG
41564565Sgshapiro		syslog(LOG_ERR, "usage");
41664565Sgshapiro#endif /* ! DEBUG */
41764565Sgshapiro		exit(EX_USAGE);
41864565Sgshapiro	}
41938032Speter
42038032Speter	/*
42138032Speter	**  Now invoke the shell
42238032Speter	*/
42338032Speter
42438032Speter#ifdef DEBUG
42590795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
42664565Sgshapiro#endif /* DEBUG */
427121826Sgshapiro	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
428121826Sgshapiro		      (char *)NULL, newenv);
42964565Sgshapiro	save_errno = errno;
43064565Sgshapiro#ifndef DEBUG
43190795Sgshapiro	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
43264565Sgshapiro#endif /* ! DEBUG */
43364565Sgshapiro	errno = save_errno;
43490795Sgshapiro	sm_perror("/bin/sh");
43538032Speter	exit(EX_OSFILE);
43664565Sgshapiro	/* NOTREACHED */
43764565Sgshapiro	return EX_OSFILE;
43838032Speter}
439