smrsh.c revision 98125
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 *
1238032Speter */
1338032Speter
1490795Sgshapiro#include <sm/gen.h>
1590795Sgshapiro
1690795SgshapiroSM_IDSTR(copyright,
1773191Sgshapiro"@(#) Copyright (c) 1998-2001 Sendmail, 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
2398125SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.58 2002/05/25 02:41:31 ca Exp $")
2464565Sgshapiro
2564565Sgshapiro/* $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 98125 2002-06-11 21:16:51Z gshapiro $ */
2664565Sgshapiro
2738032Speter/*
2838032Speter**  SMRSH -- sendmail restricted shell
2938032Speter**
3038032Speter**	This is a patch to get around the prog mailer bugs in most
3138032Speter**	versions of sendmail.
3238032Speter**
3338032Speter**	Use this in place of /bin/sh in the "prog" mailer definition
3438032Speter**	in your sendmail.cf file.  You then create CMDDIR (owned by
3538032Speter**	root, mode 755) and put links to any programs you want
3638032Speter**	available to prog mailers in that directory.  This should
3738032Speter**	include things like "vacation" and "procmail", but not "sed"
3838032Speter**	or "sh".
3938032Speter**
4038032Speter**	Leading pathnames are stripped from program names so that
4138032Speter**	existing .forward files that reference things like
4238081Speter**	"/usr/bin/vacation" will continue to work.
4338032Speter**
4438032Speter**	The following characters are completely illegal:
4564565Sgshapiro**		<  >  ^  &  `  (  ) \n \r
4664565Sgshapiro**	The following characters are sometimes illegal:
4764565Sgshapiro**		|  &
4838032Speter**	This is more restrictive than strictly necessary.
4938032Speter**
5064565Sgshapiro**	To use this, add FEATURE(`smrsh') to your .mc file.
5138032Speter**
5238032Speter**	This can be used on any version of sendmail.
5338032Speter**
5438032Speter**	In loving memory of RTM.  11/02/93.
5538032Speter*/
5638032Speter
5738032Speter#include <unistd.h>
5890795Sgshapiro#include <sm/io.h>
5998125Sgshapiro#include <sm/limits.h>
6090795Sgshapiro#include <sm/string.h>
6138032Speter#include <sys/file.h>
6238032Speter#include <string.h>
6338032Speter#include <ctype.h>
6464565Sgshapiro#include <errno.h>
6538032Speter#ifdef EX_OK
6638032Speter# undef EX_OK
6764565Sgshapiro#endif /* EX_OK */
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
7990795Sgshapiro# else /* SMRSH_CMDDIR */
8090795Sgshapiro#  define CMDDIR	"/usr/adm/sm.bin"
8190795Sgshapiro# endif /* SMRSH_CMDDIR */
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
9190795Sgshapiro# else /* SMRSH_PATH */
9290795Sgshapiro#  define PATH		"/bin:/usr/bin:/usr/ucb"
9390795Sgshapiro# endif /* SMRSH_PATH */
9464565Sgshapiro#endif /* ! PATH */
9538032Speter
9664565Sgshapirochar newcmdbuf[1000];
9764565Sgshapirochar *prg, *par;
9864565Sgshapiro
9964565Sgshapiro/*
10064565Sgshapiro**  ADDCMD -- add a string to newcmdbuf, check for overflow
10164565Sgshapiro**
10264565Sgshapiro**    Parameters:
10364565Sgshapiro**	s -- string to add
10464565Sgshapiro**	cmd -- it's a command: prepend CMDDIR/
10564565Sgshapiro**	len -- length of string to add
10664565Sgshapiro**
10764565Sgshapiro**    Side Effects:
10864565Sgshapiro**	changes newcmdbuf or exits with a failure.
10964565Sgshapiro**
11064565Sgshapiro*/
11164565Sgshapiro
11264565Sgshapirovoid
11364565Sgshapiroaddcmd(s, cmd, len)
11464565Sgshapiro	char *s;
11590795Sgshapiro	bool cmd;
11690795Sgshapiro	size_t len;
11764565Sgshapiro{
11864565Sgshapiro	if (s == NULL || *s == '\0')
11964565Sgshapiro		return;
12064565Sgshapiro
12164565Sgshapiro	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
12264565Sgshapiro	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
12364565Sgshapiro	{
12490795Sgshapiro		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
12590795Sgshapiro				    "%s: command too long: %s\n", prg, par);
12664565Sgshapiro#ifndef DEBUG
12764565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
12864565Sgshapiro#endif /* ! DEBUG */
12964565Sgshapiro		exit(EX_UNAVAILABLE);
13064565Sgshapiro	}
13164565Sgshapiro	if (cmd)
13298125Sgshapiro		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
13390795Sgshapiro	(void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf);
13464565Sgshapiro}
13564565Sgshapiro
13638032Speterint
13738032Spetermain(argc, argv)
13838032Speter	int argc;
13938032Speter	char **argv;
14038032Speter{
14138032Speter	register char *p;
14238032Speter	register char *q;
14364565Sgshapiro	register char *r;
14438032Speter	register char *cmd;
14564565Sgshapiro	int isexec;
14664565Sgshapiro	int save_errno;
14738032Speter	char *newenv[2];
14838032Speter	char pathbuf[1000];
14964565Sgshapiro	char specialbuf[32];
15038032Speter
15164565Sgshapiro#ifndef DEBUG
15264565Sgshapiro# ifndef LOG_MAIL
15338032Speter	openlog("smrsh", 0);
15464565Sgshapiro# else /* ! LOG_MAIL */
15538032Speter	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
15664565Sgshapiro# endif /* ! LOG_MAIL */
15764565Sgshapiro#endif /* ! DEBUG */
15838032Speter
15998125Sgshapiro	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
16038032Speter	newenv[0] = pathbuf;
16138032Speter	newenv[1] = NULL;
16238032Speter
16338032Speter	/*
16438032Speter	**  Do basic argv usage checking
16538032Speter	*/
16638032Speter
16764565Sgshapiro	prg = argv[0];
16864565Sgshapiro
16938032Speter	if (argc != 3 || strcmp(argv[1], "-c") != 0)
17038032Speter	{
17190795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
17290795Sgshapiro				     "Usage: %s -c command\n", prg);
17364565Sgshapiro#ifndef DEBUG
17438032Speter		syslog(LOG_ERR, "usage");
17564565Sgshapiro#endif /* ! DEBUG */
17638032Speter		exit(EX_USAGE);
17738032Speter	}
17838032Speter
17977352Sgshapiro	par = argv[2];
18077352Sgshapiro
18138032Speter	/*
18238032Speter	**  Disallow special shell syntax.  This is overly restrictive,
18338032Speter	**  but it should shut down all attacks.
18438032Speter	**  Be sure to include 8-bit versions, since many shells strip
18538032Speter	**  the address to 7 bits before checking.
18638032Speter	*/
18738032Speter
18864565Sgshapiro	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
18938032Speter	{
19064565Sgshapiro#ifndef DEBUG
19164565Sgshapiro		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
19264565Sgshapiro#endif /* ! DEBUG */
19338032Speter		exit(EX_UNAVAILABLE);
19438032Speter	}
19590795Sgshapiro	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
19664565Sgshapiro	for (p = specialbuf; *p != '\0'; p++)
19764565Sgshapiro		*p |= '\200';
19890795Sgshapiro	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
19938032Speter
20038032Speter	/*
20138032Speter	**  Do a quick sanity check on command line length.
20238032Speter	*/
20338032Speter
20490795Sgshapiro	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
20538032Speter	{
20690795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
20790795Sgshapiro				     "%s: command too long: %s\n", prg, par);
20864565Sgshapiro#ifndef DEBUG
20964565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
21064565Sgshapiro#endif /* ! DEBUG */
21138032Speter		exit(EX_UNAVAILABLE);
21238032Speter	}
21338032Speter
21464565Sgshapiro	q = par;
21564565Sgshapiro	newcmdbuf[0] = '\0';
21690795Sgshapiro	isexec = false;
21738032Speter
21898125Sgshapiro	while (*q != '\0')
21938032Speter	{
22064565Sgshapiro		/*
22164565Sgshapiro		**  Strip off a leading pathname on the command name.  For
22264565Sgshapiro		**  example, change /usr/ucb/vacation to vacation.
22364565Sgshapiro		*/
22438032Speter
22564565Sgshapiro		/* strip leading spaces */
22664565Sgshapiro		while (*q != '\0' && isascii(*q) && isspace(*q))
22764565Sgshapiro			q++;
22864565Sgshapiro		if (*q == '\0')
22938032Speter		{
23064565Sgshapiro			if (isexec)
23164565Sgshapiro			{
23290795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
23390795Sgshapiro						     "%s: missing command to exec\n",
23490795Sgshapiro						     prg);
23564565Sgshapiro#ifndef DEBUG
23690795Sgshapiro				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
23764565Sgshapiro#endif /* ! DEBUG */
23864565Sgshapiro				exit(EX_UNAVAILABLE);
23964565Sgshapiro			}
24038032Speter			break;
24138032Speter		}
24238032Speter
24364565Sgshapiro		/* find the end of the command name */
24464565Sgshapiro		p = strpbrk(q, " \t");
24564565Sgshapiro		if (p == NULL)
24664565Sgshapiro			cmd = &q[strlen(q)];
24764565Sgshapiro		else
24864565Sgshapiro		{
24964565Sgshapiro			*p = '\0';
25064565Sgshapiro			cmd = p;
25164565Sgshapiro		}
25264565Sgshapiro		/* search backwards for last / (allow for 0200 bit) */
25364565Sgshapiro		while (cmd > q)
25464565Sgshapiro		{
25564565Sgshapiro			if ((*--cmd & 0177) == '/')
25664565Sgshapiro			{
25764565Sgshapiro				cmd++;
25864565Sgshapiro				break;
25964565Sgshapiro			}
26064565Sgshapiro		}
26164565Sgshapiro		/* cmd now points at final component of path name */
26238032Speter
26364565Sgshapiro		/* allow a few shell builtins */
26464565Sgshapiro		if (strcmp(q, "exec") == 0 && p != NULL)
26564565Sgshapiro		{
26690795Sgshapiro			addcmd("exec ", false, strlen("exec "));
26798125Sgshapiro
26864565Sgshapiro			/* test _next_ arg */
26964565Sgshapiro			q = ++p;
27090795Sgshapiro			isexec = true;
27164565Sgshapiro			continue;
27264565Sgshapiro		}
27364565Sgshapiro		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
27464565Sgshapiro		{
27590795Sgshapiro			addcmd(cmd, false, strlen(cmd));
27698125Sgshapiro
27764565Sgshapiro			/* test following chars */
27864565Sgshapiro		}
27964565Sgshapiro		else
28064565Sgshapiro		{
28198125Sgshapiro			char cmdbuf[MAXPATHLEN];
28298125Sgshapiro
28364565Sgshapiro			/*
28464565Sgshapiro			**  Check to see if the command name is legal.
28564565Sgshapiro			*/
28698125Sgshapiro
28798125Sgshapiro			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
28898125Sgshapiro					"/", cmd) >= sizeof cmdbuf)
28998125Sgshapiro			{
29098125Sgshapiro				/* too long */
29198125Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
29298125Sgshapiro						     "%s: %s not available for sendmail programs (filename too long)\n",
29398125Sgshapiro						      prg, cmd);
29498125Sgshapiro				if (p != NULL)
29598125Sgshapiro					*p = ' ';
29698125Sgshapiro#ifndef DEBUG
29798125Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use %s (filename too long)",
29898125Sgshapiro				       (int) getuid(), cmd);
29998125Sgshapiro#endif /* ! DEBUG */
30098125Sgshapiro				exit(EX_UNAVAILABLE);
30198125Sgshapiro			}
30298125Sgshapiro
30364565Sgshapiro#ifdef DEBUG
30490795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
30590795Sgshapiro					     "Trying %s\n", cmdbuf);
30664565Sgshapiro#endif /* DEBUG */
30764565Sgshapiro			if (access(cmdbuf, X_OK) < 0)
30864565Sgshapiro			{
30964565Sgshapiro				/* oops....  crack attack possiblity */
31090795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
31190795Sgshapiro						     "%s: %s not available for sendmail programs\n",
31290795Sgshapiro						      prg, cmd);
31364565Sgshapiro				if (p != NULL)
31464565Sgshapiro					*p = ' ';
31564565Sgshapiro#ifndef DEBUG
31664565Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use %s",
31790795Sgshapiro				       (int) getuid(), cmd);
31864565Sgshapiro#endif /* ! DEBUG */
31964565Sgshapiro				exit(EX_UNAVAILABLE);
32064565Sgshapiro			}
32138032Speter
32264565Sgshapiro			/*
32364565Sgshapiro			**  Create the actual shell input.
32464565Sgshapiro			*/
32564565Sgshapiro
32690795Sgshapiro			addcmd(cmd, true, strlen(cmd));
32764565Sgshapiro		}
32890795Sgshapiro		isexec = false;
32964565Sgshapiro
33038032Speter		if (p != NULL)
33138032Speter			*p = ' ';
33264565Sgshapiro		else
33364565Sgshapiro			break;
33464565Sgshapiro
33564565Sgshapiro		r = strpbrk(p, specialbuf);
33690795Sgshapiro		if (r == NULL)
33790795Sgshapiro		{
33890795Sgshapiro			addcmd(p, false, strlen(p));
33964565Sgshapiro			break;
34064565Sgshapiro		}
34164565Sgshapiro#if ALLOWSEMI
34290795Sgshapiro		if (*r == ';')
34390795Sgshapiro		{
34490795Sgshapiro			addcmd(p, false,  r - p + 1);
34564565Sgshapiro			q = r + 1;
34664565Sgshapiro			continue;
34764565Sgshapiro		}
34864565Sgshapiro#endif /* ALLOWSEMI */
34964565Sgshapiro		if ((*r == '&' && *(r + 1) == '&') ||
35064565Sgshapiro		    (*r == '|' && *(r + 1) == '|'))
35164565Sgshapiro		{
35290795Sgshapiro			addcmd(p, false,  r - p + 2);
35364565Sgshapiro			q = r + 2;
35464565Sgshapiro			continue;
35564565Sgshapiro		}
35664565Sgshapiro
35790795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
35890795Sgshapiro				     "%s: cannot use %c in command\n", prg, *r);
35964565Sgshapiro#ifndef DEBUG
36064565Sgshapiro		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
36190795Sgshapiro		       (int) getuid(), *r, par);
36264565Sgshapiro#endif /* ! DEBUG */
36338032Speter		exit(EX_UNAVAILABLE);
36498125Sgshapiro	}
36564565Sgshapiro	if (isexec)
36664565Sgshapiro	{
36790795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
36890795Sgshapiro				     "%s: missing command to exec\n", prg);
36964565Sgshapiro#ifndef DEBUG
37090795Sgshapiro		syslog(LOG_CRIT, "uid %d: missing command to exec",
37190795Sgshapiro		       (int) getuid());
37264565Sgshapiro#endif /* ! DEBUG */
37364565Sgshapiro		exit(EX_UNAVAILABLE);
37438032Speter	}
37564565Sgshapiro	/* make sure we created something */
37664565Sgshapiro	if (newcmdbuf[0] == '\0')
37764565Sgshapiro	{
37890795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
37990795Sgshapiro				     "Usage: %s -c command\n", prg);
38064565Sgshapiro#ifndef DEBUG
38164565Sgshapiro		syslog(LOG_ERR, "usage");
38264565Sgshapiro#endif /* ! DEBUG */
38364565Sgshapiro		exit(EX_USAGE);
38464565Sgshapiro	}
38538032Speter
38638032Speter	/*
38738032Speter	**  Now invoke the shell
38838032Speter	*/
38938032Speter
39038032Speter#ifdef DEBUG
39190795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
39264565Sgshapiro#endif /* DEBUG */
39364565Sgshapiro	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
39464565Sgshapiro	save_errno = errno;
39564565Sgshapiro#ifndef DEBUG
39690795Sgshapiro	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
39764565Sgshapiro#endif /* ! DEBUG */
39864565Sgshapiro	errno = save_errno;
39990795Sgshapiro	sm_perror("/bin/sh");
40038032Speter	exit(EX_OSFILE);
40164565Sgshapiro	/* NOTREACHED */
40264565Sgshapiro	return EX_OSFILE;
40338032Speter}
404