smrsh.c revision 110563
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 *
12102533Sgshapiro * $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 110563 2003-02-08 20:35:51Z gshapiro $
13102533Sgshapiro *
1438032Speter */
1538032Speter
1690795Sgshapiro#include <sm/gen.h>
1790795Sgshapiro
1890795SgshapiroSM_IDSTR(copyright,
1973191Sgshapiro"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
2064565Sgshapiro	All rights reserved.\n\
2164565Sgshapiro     Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
2264565Sgshapiro     Copyright (c) 1993\n\
2390795Sgshapiro	The Regents of the University of California.  All rights reserved.\n")
2438032Speter
25110563SgshapiroSM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.58.2.2 2002/09/24 21:40:05 ca Exp $")
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>
62105016Sgshapiro#include <sys/types.h>
63105016Sgshapiro#include <sys/stat.h>
6438032Speter#include <string.h>
6538032Speter#include <ctype.h>
6664565Sgshapiro#include <errno.h>
6738032Speter#ifdef EX_OK
6838032Speter# undef EX_OK
6964565Sgshapiro#endif /* EX_OK */
7038032Speter#include <sysexits.h>
7138032Speter#include <syslog.h>
7238032Speter#include <stdlib.h>
7338032Speter
7490795Sgshapiro#include <sm/conf.h>
7590795Sgshapiro#include <sm/errstring.h>
7664565Sgshapiro
7738032Speter/* directory in which all commands must reside */
7838032Speter#ifndef CMDDIR
7990795Sgshapiro# ifdef SMRSH_CMDDIR
8090795Sgshapiro#  define CMDDIR	SMRSH_CMDDIR
8190795Sgshapiro# else /* SMRSH_CMDDIR */
8290795Sgshapiro#  define CMDDIR	"/usr/adm/sm.bin"
8390795Sgshapiro# endif /* SMRSH_CMDDIR */
8464565Sgshapiro#endif /* ! CMDDIR */
8538032Speter
8638032Speter/* characters disallowed in the shell "-c" argument */
8738032Speter#define SPECIALS	"<|>^();&`$\r\n"
8838032Speter
8938032Speter/* default search path */
9038032Speter#ifndef PATH
9190795Sgshapiro# ifdef SMRSH_PATH
9290795Sgshapiro#  define PATH		SMRSH_PATH
9390795Sgshapiro# else /* SMRSH_PATH */
9490795Sgshapiro#  define PATH		"/bin:/usr/bin:/usr/ucb"
9590795Sgshapiro# endif /* SMRSH_PATH */
9664565Sgshapiro#endif /* ! PATH */
9738032Speter
9864565Sgshapirochar newcmdbuf[1000];
9964565Sgshapirochar *prg, *par;
10064565Sgshapiro
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
11464565Sgshapirovoid
11564565Sgshapiroaddcmd(s, cmd, len)
11664565Sgshapiro	char *s;
11790795Sgshapiro	bool cmd;
11890795Sgshapiro	size_t len;
11964565Sgshapiro{
12064565Sgshapiro	if (s == NULL || *s == '\0')
12164565Sgshapiro		return;
12264565Sgshapiro
12364565Sgshapiro	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
12464565Sgshapiro	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
12564565Sgshapiro	{
12690795Sgshapiro		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
12790795Sgshapiro				    "%s: command too long: %s\n", prg, par);
12864565Sgshapiro#ifndef DEBUG
12964565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
13064565Sgshapiro#endif /* ! DEBUG */
13164565Sgshapiro		exit(EX_UNAVAILABLE);
13264565Sgshapiro	}
13364565Sgshapiro	if (cmd)
13498125Sgshapiro		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
13590795Sgshapiro	(void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf);
13664565Sgshapiro}
13764565Sgshapiro
13838032Speterint
13938032Spetermain(argc, argv)
14038032Speter	int argc;
14138032Speter	char **argv;
14238032Speter{
14338032Speter	register char *p;
14438032Speter	register char *q;
14564565Sgshapiro	register char *r;
14638032Speter	register char *cmd;
14764565Sgshapiro	int isexec;
14864565Sgshapiro	int save_errno;
14938032Speter	char *newenv[2];
15038032Speter	char pathbuf[1000];
15164565Sgshapiro	char specialbuf[32];
152105016Sgshapiro	struct stat st;
15338032Speter
15464565Sgshapiro#ifndef DEBUG
15564565Sgshapiro# ifndef LOG_MAIL
15638032Speter	openlog("smrsh", 0);
15764565Sgshapiro# else /* ! LOG_MAIL */
15838032Speter	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
15964565Sgshapiro# endif /* ! LOG_MAIL */
16064565Sgshapiro#endif /* ! DEBUG */
16138032Speter
16298125Sgshapiro	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
16338032Speter	newenv[0] = pathbuf;
16438032Speter	newenv[1] = NULL;
16538032Speter
16638032Speter	/*
16738032Speter	**  Do basic argv usage checking
16838032Speter	*/
16938032Speter
17064565Sgshapiro	prg = argv[0];
17164565Sgshapiro
17238032Speter	if (argc != 3 || strcmp(argv[1], "-c") != 0)
17338032Speter	{
17490795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
17590795Sgshapiro				     "Usage: %s -c command\n", prg);
17664565Sgshapiro#ifndef DEBUG
17738032Speter		syslog(LOG_ERR, "usage");
17864565Sgshapiro#endif /* ! DEBUG */
17938032Speter		exit(EX_USAGE);
18038032Speter	}
18138032Speter
18277352Sgshapiro	par = argv[2];
18377352Sgshapiro
18438032Speter	/*
18538032Speter	**  Disallow special shell syntax.  This is overly restrictive,
18638032Speter	**  but it should shut down all attacks.
18738032Speter	**  Be sure to include 8-bit versions, since many shells strip
18838032Speter	**  the address to 7 bits before checking.
18938032Speter	*/
19038032Speter
19164565Sgshapiro	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
19238032Speter	{
19364565Sgshapiro#ifndef DEBUG
19464565Sgshapiro		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
19564565Sgshapiro#endif /* ! DEBUG */
19638032Speter		exit(EX_UNAVAILABLE);
19738032Speter	}
19890795Sgshapiro	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
19964565Sgshapiro	for (p = specialbuf; *p != '\0'; p++)
20064565Sgshapiro		*p |= '\200';
20190795Sgshapiro	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
20238032Speter
20338032Speter	/*
20438032Speter	**  Do a quick sanity check on command line length.
20538032Speter	*/
20638032Speter
20790795Sgshapiro	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
20838032Speter	{
20990795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
21090795Sgshapiro				     "%s: command too long: %s\n", prg, par);
21164565Sgshapiro#ifndef DEBUG
21264565Sgshapiro		syslog(LOG_WARNING, "command too long: %.40s", par);
21364565Sgshapiro#endif /* ! DEBUG */
21438032Speter		exit(EX_UNAVAILABLE);
21538032Speter	}
21638032Speter
21764565Sgshapiro	q = par;
21864565Sgshapiro	newcmdbuf[0] = '\0';
21990795Sgshapiro	isexec = false;
22038032Speter
22198125Sgshapiro	while (*q != '\0')
22238032Speter	{
22364565Sgshapiro		/*
22464565Sgshapiro		**  Strip off a leading pathname on the command name.  For
22564565Sgshapiro		**  example, change /usr/ucb/vacation to vacation.
22664565Sgshapiro		*/
22738032Speter
22864565Sgshapiro		/* strip leading spaces */
22964565Sgshapiro		while (*q != '\0' && isascii(*q) && isspace(*q))
23064565Sgshapiro			q++;
23164565Sgshapiro		if (*q == '\0')
23238032Speter		{
23364565Sgshapiro			if (isexec)
23464565Sgshapiro			{
23590795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
23690795Sgshapiro						     "%s: missing command to exec\n",
23790795Sgshapiro						     prg);
23864565Sgshapiro#ifndef DEBUG
23990795Sgshapiro				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
24064565Sgshapiro#endif /* ! DEBUG */
24164565Sgshapiro				exit(EX_UNAVAILABLE);
24264565Sgshapiro			}
24338032Speter			break;
24438032Speter		}
24538032Speter
24664565Sgshapiro		/* find the end of the command name */
24764565Sgshapiro		p = strpbrk(q, " \t");
24864565Sgshapiro		if (p == NULL)
24964565Sgshapiro			cmd = &q[strlen(q)];
25064565Sgshapiro		else
25164565Sgshapiro		{
25264565Sgshapiro			*p = '\0';
25364565Sgshapiro			cmd = p;
25464565Sgshapiro		}
25564565Sgshapiro		/* search backwards for last / (allow for 0200 bit) */
25664565Sgshapiro		while (cmd > q)
25764565Sgshapiro		{
25864565Sgshapiro			if ((*--cmd & 0177) == '/')
25964565Sgshapiro			{
26064565Sgshapiro				cmd++;
26164565Sgshapiro				break;
26264565Sgshapiro			}
26364565Sgshapiro		}
26464565Sgshapiro		/* cmd now points at final component of path name */
26538032Speter
26664565Sgshapiro		/* allow a few shell builtins */
26764565Sgshapiro		if (strcmp(q, "exec") == 0 && p != NULL)
26864565Sgshapiro		{
26990795Sgshapiro			addcmd("exec ", false, strlen("exec "));
27098125Sgshapiro
27164565Sgshapiro			/* test _next_ arg */
27264565Sgshapiro			q = ++p;
27390795Sgshapiro			isexec = true;
27464565Sgshapiro			continue;
27564565Sgshapiro		}
27664565Sgshapiro		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
27764565Sgshapiro		{
27890795Sgshapiro			addcmd(cmd, false, strlen(cmd));
27998125Sgshapiro
28064565Sgshapiro			/* test following chars */
28164565Sgshapiro		}
28264565Sgshapiro		else
28364565Sgshapiro		{
28498125Sgshapiro			char cmdbuf[MAXPATHLEN];
28598125Sgshapiro
28664565Sgshapiro			/*
28764565Sgshapiro			**  Check to see if the command name is legal.
28864565Sgshapiro			*/
28998125Sgshapiro
29098125Sgshapiro			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
29198125Sgshapiro					"/", cmd) >= sizeof cmdbuf)
29298125Sgshapiro			{
29398125Sgshapiro				/* too long */
29498125Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
295110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
29698125Sgshapiro						      prg, cmd);
29798125Sgshapiro				if (p != NULL)
29898125Sgshapiro					*p = ' ';
29998125Sgshapiro#ifndef DEBUG
300110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
30198125Sgshapiro				       (int) getuid(), cmd);
30298125Sgshapiro#endif /* ! DEBUG */
30398125Sgshapiro				exit(EX_UNAVAILABLE);
30498125Sgshapiro			}
30598125Sgshapiro
30664565Sgshapiro#ifdef DEBUG
30790795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
30890795Sgshapiro					     "Trying %s\n", cmdbuf);
30964565Sgshapiro#endif /* DEBUG */
310105016Sgshapiro			if (stat(cmdbuf, &st) < 0)
311105016Sgshapiro			{
312105016Sgshapiro				/* can't stat it */
313105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
314110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
315105016Sgshapiro						      prg, cmd);
316105016Sgshapiro				if (p != NULL)
317105016Sgshapiro					*p = ' ';
318105016Sgshapiro#ifndef DEBUG
319110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
320105016Sgshapiro				       (int) getuid(), cmd);
321105016Sgshapiro#endif /* ! DEBUG */
322105016Sgshapiro				exit(EX_UNAVAILABLE);
323105016Sgshapiro			}
324105016Sgshapiro			if (!S_ISREG(st.st_mode)
325105016Sgshapiro#ifdef S_ISLNK
326105016Sgshapiro			    && !S_ISLNK(st.st_mode)
327105016Sgshapiro#endif /* S_ISLNK */
328105016Sgshapiro			   )
329105016Sgshapiro			{
330105016Sgshapiro				/* can't stat it */
331105016Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
332110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
333105016Sgshapiro						      prg, cmd);
334105016Sgshapiro				if (p != NULL)
335105016Sgshapiro					*p = ' ';
336105016Sgshapiro#ifndef DEBUG
337110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
338105016Sgshapiro				       (int) getuid(), cmd);
339105016Sgshapiro#endif /* ! DEBUG */
340105016Sgshapiro				exit(EX_UNAVAILABLE);
341105016Sgshapiro			}
34264565Sgshapiro			if (access(cmdbuf, X_OK) < 0)
34364565Sgshapiro			{
34464565Sgshapiro				/* oops....  crack attack possiblity */
34590795Sgshapiro				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
346110563Sgshapiro						     "%s: \"%s\" not available for sendmail programs\n",
34790795Sgshapiro						      prg, cmd);
34864565Sgshapiro				if (p != NULL)
34964565Sgshapiro					*p = ' ';
35064565Sgshapiro#ifndef DEBUG
351110563Sgshapiro				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
35290795Sgshapiro				       (int) getuid(), cmd);
35364565Sgshapiro#endif /* ! DEBUG */
35464565Sgshapiro				exit(EX_UNAVAILABLE);
35564565Sgshapiro			}
35638032Speter
35764565Sgshapiro			/*
35864565Sgshapiro			**  Create the actual shell input.
35964565Sgshapiro			*/
36064565Sgshapiro
36190795Sgshapiro			addcmd(cmd, true, strlen(cmd));
36264565Sgshapiro		}
36390795Sgshapiro		isexec = false;
36464565Sgshapiro
36538032Speter		if (p != NULL)
36638032Speter			*p = ' ';
36764565Sgshapiro		else
36864565Sgshapiro			break;
36964565Sgshapiro
37064565Sgshapiro		r = strpbrk(p, specialbuf);
37190795Sgshapiro		if (r == NULL)
37290795Sgshapiro		{
37390795Sgshapiro			addcmd(p, false, strlen(p));
37464565Sgshapiro			break;
37564565Sgshapiro		}
37664565Sgshapiro#if ALLOWSEMI
37790795Sgshapiro		if (*r == ';')
37890795Sgshapiro		{
37990795Sgshapiro			addcmd(p, false,  r - p + 1);
38064565Sgshapiro			q = r + 1;
38164565Sgshapiro			continue;
38264565Sgshapiro		}
38364565Sgshapiro#endif /* ALLOWSEMI */
38464565Sgshapiro		if ((*r == '&' && *(r + 1) == '&') ||
38564565Sgshapiro		    (*r == '|' && *(r + 1) == '|'))
38664565Sgshapiro		{
38790795Sgshapiro			addcmd(p, false,  r - p + 2);
38864565Sgshapiro			q = r + 2;
38964565Sgshapiro			continue;
39064565Sgshapiro		}
39164565Sgshapiro
39290795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
39390795Sgshapiro				     "%s: cannot use %c in command\n", prg, *r);
39464565Sgshapiro#ifndef DEBUG
39564565Sgshapiro		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
39690795Sgshapiro		       (int) getuid(), *r, par);
39764565Sgshapiro#endif /* ! DEBUG */
39838032Speter		exit(EX_UNAVAILABLE);
39998125Sgshapiro	}
40064565Sgshapiro	if (isexec)
40164565Sgshapiro	{
40290795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
40390795Sgshapiro				     "%s: missing command to exec\n", prg);
40464565Sgshapiro#ifndef DEBUG
40590795Sgshapiro		syslog(LOG_CRIT, "uid %d: missing command to exec",
40690795Sgshapiro		       (int) getuid());
40764565Sgshapiro#endif /* ! DEBUG */
40864565Sgshapiro		exit(EX_UNAVAILABLE);
40938032Speter	}
41064565Sgshapiro	/* make sure we created something */
41164565Sgshapiro	if (newcmdbuf[0] == '\0')
41264565Sgshapiro	{
41390795Sgshapiro		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
41490795Sgshapiro				     "Usage: %s -c command\n", prg);
41564565Sgshapiro#ifndef DEBUG
41664565Sgshapiro		syslog(LOG_ERR, "usage");
41764565Sgshapiro#endif /* ! DEBUG */
41864565Sgshapiro		exit(EX_USAGE);
41964565Sgshapiro	}
42038032Speter
42138032Speter	/*
42238032Speter	**  Now invoke the shell
42338032Speter	*/
42438032Speter
42538032Speter#ifdef DEBUG
42690795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
42764565Sgshapiro#endif /* DEBUG */
42864565Sgshapiro	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, 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