smrsh.c revision 141862
138032Speter/*
2141862Sgshapiro * Copyright (c) 1998-2004 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 141862 2005-02-14 02:39:14Z gshapiro $
1338032Speter */
1438032Speter
1590795Sgshapiro#include <sm/gen.h>
1690795Sgshapiro
1790795SgshapiroSM_IDSTR(copyright,
18141862Sgshapiro"@(#) Copyright (c) 1998-2004 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
24141862SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.65 2004/08/06 18:54:22 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
100141862Sgshapirostatic void	addcmd __P((char *, bool, size_t));
101141862Sgshapiro
10264565Sgshapiro/*
10364565Sgshapiro**  ADDCMD -- add a string to newcmdbuf, check for overflow
10464565Sgshapiro**
10564565Sgshapiro**    Parameters:
10664565Sgshapiro**	s -- string to add
10764565Sgshapiro**	cmd -- it's a command: prepend CMDDIR/
10864565Sgshapiro**	len -- length of string to add
10964565Sgshapiro**
11064565Sgshapiro**    Side Effects:
11164565Sgshapiro**	changes newcmdbuf or exits with a failure.
11264565Sgshapiro**
11364565Sgshapiro*/
11464565Sgshapiro
115141862Sgshapirostatic void
11664565Sgshapiroaddcmd(s, cmd, len)
11764565Sgshapiro	char *s;
11890795Sgshapiro	bool cmd;
11990795Sgshapiro	size_t len;
12064565Sgshapiro{
12164565Sgshapiro	if (s == NULL || *s == '\0')
12264565Sgshapiro		return;
12364565Sgshapiro
124125823Sgshapiro	/* enough space for s (len) and CMDDIR + "/" and '\0'? */
12564565Sgshapiro	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
126125823Sgshapiro	    len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
12764565Sgshapiro	{
12890795Sgshapiro		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
12990795Sgshapiro				    "%s: command too long: %s\n", prg, par);
13064565Sgshapiro#ifndef DEBUG
13164565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
13264565Sgshapiro#endif /* ! DEBUG */
13364565Sgshapiro		exit(EX_UNAVAILABLE);
13464565Sgshapiro	}
13564565Sgshapiro	if (cmd)
13698125Sgshapiro		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
137125823Sgshapiro	(void) strncat(newcmdbuf, s, len);
13864565Sgshapiro}
13964565Sgshapiro
14038032Speterint
14138032Spetermain(argc, argv)
14238032Speter	int argc;
14338032Speter	char **argv;
14438032Speter{
14538032Speter	register char *p;
14638032Speter	register char *q;
14764565Sgshapiro	register char *r;
14838032Speter	register char *cmd;
14964565Sgshapiro	int isexec;
15064565Sgshapiro	int save_errno;
15138032Speter	char *newenv[2];
15238032Speter	char pathbuf[1000];
15364565Sgshapiro	char specialbuf[32];
154105016Sgshapiro	struct stat st;
15538032Speter
15664565Sgshapiro#ifndef DEBUG
15764565Sgshapiro# ifndef LOG_MAIL
15838032Speter	openlog("smrsh", 0);
15964565Sgshapiro# else /* ! LOG_MAIL */
16038032Speter	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
16164565Sgshapiro# endif /* ! LOG_MAIL */
16264565Sgshapiro#endif /* ! DEBUG */
16338032Speter
16498125Sgshapiro	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
16538032Speter	newenv[0] = pathbuf;
16638032Speter	newenv[1] = NULL;
16738032Speter
16838032Speter	/*
16938032Speter	**  Do basic argv usage checking
17038032Speter	*/
17138032Speter
17264565Sgshapiro	prg = argv[0];
17364565Sgshapiro
17438032Speter	if (argc != 3 || strcmp(argv[1], "-c") != 0)
17538032Speter	{
17690795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
17790795Sgshapiro				     "Usage: %s -c command\n", prg);
17864565Sgshapiro#ifndef DEBUG
17938032Speter		syslog(LOG_ERR, "usage");
18064565Sgshapiro#endif /* ! DEBUG */
18138032Speter		exit(EX_USAGE);
18238032Speter	}
18338032Speter
18477352Sgshapiro	par = argv[2];
18577352Sgshapiro
18638032Speter	/*
18738032Speter	**  Disallow special shell syntax.  This is overly restrictive,
18838032Speter	**  but it should shut down all attacks.
18938032Speter	**  Be sure to include 8-bit versions, since many shells strip
19038032Speter	**  the address to 7 bits before checking.
19138032Speter	*/
19238032Speter
19364565Sgshapiro	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
19438032Speter	{
19564565Sgshapiro#ifndef DEBUG
19664565Sgshapiro		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
19764565Sgshapiro#endif /* ! DEBUG */
19838032Speter		exit(EX_UNAVAILABLE);
19938032Speter	}
20090795Sgshapiro	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
20164565Sgshapiro	for (p = specialbuf; *p != '\0'; p++)
20264565Sgshapiro		*p |= '\200';
20390795Sgshapiro	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
20438032Speter
20538032Speter	/*
20638032Speter	**  Do a quick sanity check on command line length.
20738032Speter	*/
20838032Speter
20990795Sgshapiro	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
21038032Speter	{
21190795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
21290795Sgshapiro				     "%s: command too long: %s\n", prg, par);
21364565Sgshapiro#ifndef DEBUG
21464565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
21564565Sgshapiro#endif /* ! DEBUG */
21638032Speter		exit(EX_UNAVAILABLE);
21738032Speter	}
21838032Speter
21964565Sgshapiro	q = par;
22064565Sgshapiro	newcmdbuf[0] = '\0';
22190795Sgshapiro	isexec = false;
22238032Speter
22398125Sgshapiro	while (*q != '\0')
22438032Speter	{
22564565Sgshapiro		/*
22664565Sgshapiro		**  Strip off a leading pathname on the command name.  For
22764565Sgshapiro		**  example, change /usr/ucb/vacation to vacation.
22864565Sgshapiro		*/
22938032Speter
23064565Sgshapiro		/* strip leading spaces */
23164565Sgshapiro		while (*q != '\0' && isascii(*q) && isspace(*q))
23264565Sgshapiro			q++;
23364565Sgshapiro		if (*q == '\0')
23438032Speter		{
23564565Sgshapiro			if (isexec)
23664565Sgshapiro			{
23790795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
23890795Sgshapiro						     "%s: missing command to exec\n",
23990795Sgshapiro						     prg);
24064565Sgshapiro#ifndef DEBUG
24190795Sgshapiro				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
24264565Sgshapiro#endif /* ! DEBUG */
24364565Sgshapiro				exit(EX_UNAVAILABLE);
24464565Sgshapiro			}
24538032Speter			break;
24638032Speter		}
24738032Speter
24864565Sgshapiro		/* find the end of the command name */
24964565Sgshapiro		p = strpbrk(q, " \t");
25064565Sgshapiro		if (p == NULL)
25164565Sgshapiro			cmd = &q[strlen(q)];
25264565Sgshapiro		else
25364565Sgshapiro		{
25464565Sgshapiro			*p = '\0';
25564565Sgshapiro			cmd = p;
25664565Sgshapiro		}
25764565Sgshapiro		/* search backwards for last / (allow for 0200 bit) */
25864565Sgshapiro		while (cmd > q)
25964565Sgshapiro		{
26064565Sgshapiro			if ((*--cmd & 0177) == '/')
26164565Sgshapiro			{
26264565Sgshapiro				cmd++;
26364565Sgshapiro				break;
26464565Sgshapiro			}
26564565Sgshapiro		}
26664565Sgshapiro		/* cmd now points at final component of path name */
26738032Speter
26864565Sgshapiro		/* allow a few shell builtins */
26964565Sgshapiro		if (strcmp(q, "exec") == 0 && p != NULL)
27064565Sgshapiro		{
27190795Sgshapiro			addcmd("exec ", false, strlen("exec "));
27298125Sgshapiro
27364565Sgshapiro			/* test _next_ arg */
27464565Sgshapiro			q = ++p;
27590795Sgshapiro			isexec = true;
27664565Sgshapiro			continue;
27764565Sgshapiro		}
27864565Sgshapiro		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
27964565Sgshapiro		{
28090795Sgshapiro			addcmd(cmd, false, strlen(cmd));
28198125Sgshapiro
28264565Sgshapiro			/* test following chars */
28364565Sgshapiro		}
28464565Sgshapiro		else
28564565Sgshapiro		{
28698125Sgshapiro			char cmdbuf[MAXPATHLEN];
28798125Sgshapiro
28864565Sgshapiro			/*
28964565Sgshapiro			**  Check to see if the command name is legal.
29064565Sgshapiro			*/
29198125Sgshapiro
29298125Sgshapiro			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
29398125Sgshapiro					"/", cmd) >= sizeof cmdbuf)
29498125Sgshapiro			{
29598125Sgshapiro				/* too long */
29698125Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
297110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
29898125Sgshapiro						      prg, cmd);
29998125Sgshapiro				if (p != NULL)
30098125Sgshapiro					*p = ' ';
30198125Sgshapiro#ifndef DEBUG
302110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
30398125Sgshapiro				       (int) getuid(), cmd);
30498125Sgshapiro#endif /* ! DEBUG */
30598125Sgshapiro				exit(EX_UNAVAILABLE);
30698125Sgshapiro			}
30798125Sgshapiro
30864565Sgshapiro#ifdef DEBUG
30990795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
31090795Sgshapiro					     "Trying %s\n", cmdbuf);
31164565Sgshapiro#endif /* DEBUG */
312105016Sgshapiro			if (stat(cmdbuf, &st) < 0)
313105016Sgshapiro			{
314105016Sgshapiro				/* can't stat it */
315105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
316110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
317105016Sgshapiro						      prg, cmd);
318105016Sgshapiro				if (p != NULL)
319105016Sgshapiro					*p = ' ';
320105016Sgshapiro#ifndef DEBUG
321110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
322105016Sgshapiro				       (int) getuid(), cmd);
323105016Sgshapiro#endif /* ! DEBUG */
324105016Sgshapiro				exit(EX_UNAVAILABLE);
325105016Sgshapiro			}
326105016Sgshapiro			if (!S_ISREG(st.st_mode)
327105016Sgshapiro#ifdef S_ISLNK
328105016Sgshapiro			    && !S_ISLNK(st.st_mode)
329105016Sgshapiro#endif /* S_ISLNK */
330105016Sgshapiro			   )
331105016Sgshapiro			{
332105016Sgshapiro				/* can't stat it */
333105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
334110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
335105016Sgshapiro						      prg, cmd);
336105016Sgshapiro				if (p != NULL)
337105016Sgshapiro					*p = ' ';
338105016Sgshapiro#ifndef DEBUG
339110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
340105016Sgshapiro				       (int) getuid(), cmd);
341105016Sgshapiro#endif /* ! DEBUG */
342105016Sgshapiro				exit(EX_UNAVAILABLE);
343105016Sgshapiro			}
34464565Sgshapiro			if (access(cmdbuf, X_OK) < 0)
34564565Sgshapiro			{
34664565Sgshapiro				/* oops....  crack attack possiblity */
34790795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
348110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs\n",
34990795Sgshapiro						      prg, cmd);
35064565Sgshapiro				if (p != NULL)
35164565Sgshapiro					*p = ' ';
35264565Sgshapiro#ifndef DEBUG
353110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
35490795Sgshapiro				       (int) getuid(), cmd);
35564565Sgshapiro#endif /* ! DEBUG */
35664565Sgshapiro				exit(EX_UNAVAILABLE);
35764565Sgshapiro			}
35838032Speter
35964565Sgshapiro			/*
36064565Sgshapiro			**  Create the actual shell input.
36164565Sgshapiro			*/
36264565Sgshapiro
36390795Sgshapiro			addcmd(cmd, true, strlen(cmd));
36464565Sgshapiro		}
36590795Sgshapiro		isexec = false;
36664565Sgshapiro
36738032Speter		if (p != NULL)
36838032Speter			*p = ' ';
36964565Sgshapiro		else
37064565Sgshapiro			break;
37164565Sgshapiro
37264565Sgshapiro		r = strpbrk(p, specialbuf);
37390795Sgshapiro		if (r == NULL)
37490795Sgshapiro		{
37590795Sgshapiro			addcmd(p, false, strlen(p));
37664565Sgshapiro			break;
37764565Sgshapiro		}
37864565Sgshapiro#if ALLOWSEMI
37990795Sgshapiro		if (*r == ';')
38090795Sgshapiro		{
38190795Sgshapiro			addcmd(p, false,  r - p + 1);
38264565Sgshapiro			q = r + 1;
38364565Sgshapiro			continue;
38464565Sgshapiro		}
38564565Sgshapiro#endif /* ALLOWSEMI */
38664565Sgshapiro		if ((*r == '&' && *(r + 1) == '&') ||
38764565Sgshapiro		    (*r == '|' && *(r + 1) == '|'))
38864565Sgshapiro		{
38990795Sgshapiro			addcmd(p, false,  r - p + 2);
39064565Sgshapiro			q = r + 2;
39164565Sgshapiro			continue;
39264565Sgshapiro		}
39364565Sgshapiro
39490795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
39590795Sgshapiro				     "%s: cannot use %c in command\n", prg, *r);
39664565Sgshapiro#ifndef DEBUG
39764565Sgshapiro		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
39890795Sgshapiro		       (int) getuid(), *r, par);
39964565Sgshapiro#endif /* ! DEBUG */
40038032Speter		exit(EX_UNAVAILABLE);
40198125Sgshapiro	}
40264565Sgshapiro	if (isexec)
40364565Sgshapiro	{
40490795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
40590795Sgshapiro				     "%s: missing command to exec\n", prg);
40664565Sgshapiro#ifndef DEBUG
40790795Sgshapiro		syslog(LOG_CRIT, "uid %d: missing command to exec",
40890795Sgshapiro		       (int) getuid());
40964565Sgshapiro#endif /* ! DEBUG */
41064565Sgshapiro		exit(EX_UNAVAILABLE);
41138032Speter	}
41264565Sgshapiro	/* make sure we created something */
41364565Sgshapiro	if (newcmdbuf[0] == '\0')
41464565Sgshapiro	{
41590795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
41690795Sgshapiro				     "Usage: %s -c command\n", prg);
41764565Sgshapiro#ifndef DEBUG
41864565Sgshapiro		syslog(LOG_ERR, "usage");
41964565Sgshapiro#endif /* ! DEBUG */
42064565Sgshapiro		exit(EX_USAGE);
42164565Sgshapiro	}
42238032Speter
42338032Speter	/*
42438032Speter	**  Now invoke the shell
42538032Speter	*/
42638032Speter
42738032Speter#ifdef DEBUG
42890795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
42964565Sgshapiro#endif /* DEBUG */
430121826Sgshapiro	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
431121826Sgshapiro		      (char *)NULL, newenv);
43264565Sgshapiro	save_errno = errno;
43364565Sgshapiro#ifndef DEBUG
43490795Sgshapiro	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
43564565Sgshapiro#endif /* ! DEBUG */
43664565Sgshapiro	errno = save_errno;
43790795Sgshapiro	sm_perror("/bin/sh");
43838032Speter	exit(EX_OSFILE);
43964565Sgshapiro	/* NOTREACHED */
44064565Sgshapiro	return EX_OSFILE;
44138032Speter}
442