167909Seric /*
267909Seric  * Copyright (c) 1993 Eric P. Allman
367909Seric  * Copyright (c) 1993
467909Seric  *	The Regents of the University of California.  All rights reserved.
567909Seric  *
667909Seric  * %sccs.include.redist.c%
767909Seric  */
867909Seric 
967909Seric #ifndef lint
10*68502Seric static char sccsid[] = "@(#)smrsh.c	8.2 (Berkeley) 03/08/95";
1167909Seric #endif /* not lint */
1267909Seric 
1367909Seric /*
1467909Seric **  SMRSH -- sendmail restricted shell
1567909Seric **
1667909Seric **	This is a patch to get around the prog mailer bugs in most
1767909Seric **	versions of sendmail.
1867909Seric **
1967909Seric **	Use this in place of /bin/sh in the "prog" mailer definition
2067909Seric **	in your sendmail.cf file.  You then create CMDDIR (owned by
2167909Seric **	root, mode 755) and put links to any programs you want
2267909Seric **	available to prog mailers in that directory.  This should
2367909Seric **	include things like "vacation" and "procmail", but not "sed"
2467909Seric **	or "sh".
2567909Seric **
2667909Seric **	Leading pathnames are stripped from program names so that
2767909Seric **	existing .forward files that reference things like
2867909Seric **	"/usr/ucb/vacation" will continue to work.
2967909Seric **
3067909Seric **	The following characters are completely illegal:
3167909Seric **		<  >  |  ^  ;  &  $  `  (  ) \n \r
3267909Seric **	This is more restrictive than strictly necessary.
3367909Seric **
3467909Seric **	To use this, edit /etc/sendmail.cf, search for ^Mprog, and
3567909Seric **	change P=/bin/sh to P=/usr/etc/smrsh, where this compiled
3667909Seric **	binary is installed /usr/etc/smrsh.
3767909Seric **
3867909Seric **	This can be used on any version of sendmail.
3967909Seric **
4067909Seric **	In loving memory of RTM.  11/02/93.
4167909Seric */
4267909Seric 
43*68502Seric #include <unistd.h>
4467909Seric #include <stdio.h>
4567909Seric #include <sys/file.h>
4667909Seric #include <string.h>
4767909Seric #include <ctype.h>
4867909Seric #include <sysexits.h>
4967909Seric #include <syslog.h>
5067909Seric 
5167909Seric /* directory in which all commands must reside */
5267909Seric #ifndef CMDDIR
5367909Seric # define CMDDIR		"/usr/adm/sm.bin"
5467909Seric #endif
5567909Seric 
5667909Seric /* characters disallowed in the shell "-c" argument */
5767909Seric #define SPECIALS	"<|>^();&`$\r\n"
5867909Seric 
5967909Seric /* default search path */
6067909Seric #ifndef PATH
6167909Seric # define PATH		"/bin:/usr/bin:/usr/ucb"
6267909Seric #endif
6367909Seric 
main(argc,argv)6467909Seric main(argc, argv)
6567909Seric 	int argc;
6667909Seric 	char **argv;
6767909Seric {
6867909Seric 	register char *p;
6967909Seric 	register char *q;
7067909Seric 	register char *cmd;
7167909Seric 	int i;
7267909Seric 	char *newenv[2];
7367909Seric 	char cmdbuf[1000];
7467909Seric 	char pathbuf[1000];
7567909Seric 
7667909Seric #ifndef LOG_MAIL
7767909Seric 	openlog("smrsh", 0);
7867909Seric #else
7967909Seric 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
8067909Seric #endif
8167909Seric 
8267909Seric 	strcpy(pathbuf, "PATH=");
8367909Seric 	strcat(pathbuf, PATH);
8467909Seric 	newenv[0] = pathbuf;
8567909Seric 	newenv[1] = NULL;
8667909Seric 
8767909Seric 	/*
8867909Seric 	**  Do basic argv usage checking
8967909Seric 	*/
9067909Seric 
9167909Seric 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
9267909Seric 	{
9367909Seric 		fprintf(stderr, "Usage: %s -c command\n", argv[0]);
9467909Seric 		syslog(LOG_ERR, "usage");
9567909Seric 		exit(EX_USAGE);
9667909Seric 	}
9767909Seric 
9867909Seric 	/*
9967909Seric 	**  Disallow special shell syntax.  This is overly restrictive,
10067909Seric 	**  but it should shut down all attacks.
10167909Seric 	**  Be sure to include 8-bit versions, since many shells strip
10267909Seric 	**  the address to 7 bits before checking.
10367909Seric 	*/
10467909Seric 
10567909Seric 	strcpy(cmdbuf, SPECIALS);
10667909Seric 	for (p = cmdbuf; *p != '\0'; p++)
10767909Seric 		*p |= '\200';
10867909Seric 	strcat(cmdbuf, SPECIALS);
10967909Seric 	p = strpbrk(argv[2], cmdbuf);
11067909Seric 	if (p != NULL)
11167909Seric 	{
11267909Seric 		fprintf(stderr, "%s: cannot use %c in command\n",
11367909Seric 			argv[0], *p);
11467909Seric 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
11567909Seric 			getuid(), *p, argv[2]);
11667909Seric 		exit(EX_UNAVAILABLE);
11767909Seric 	}
11867909Seric 
11967909Seric 	/*
12067909Seric 	**  Do a quick sanity check on command line length.
12167909Seric 	*/
12267909Seric 
12367909Seric 	i = strlen(argv[2]);
12467909Seric 	if (i > (sizeof cmdbuf - sizeof CMDDIR - 2))
12567909Seric 	{
12667909Seric 		fprintf(stderr, "%s: command too long: %s\n", argv[0], argv[2]);
12767909Seric 		syslog(LOG_WARNING, "command too long: %.40s", argv[2]);
12867909Seric 		exit(EX_UNAVAILABLE);
12967909Seric 	}
13067909Seric 
13167909Seric 	/*
13267909Seric 	**  Strip off a leading pathname on the command name.  For
13367909Seric 	**  example, change /usr/ucb/vacation to vacation.
13467909Seric 	*/
13567909Seric 
13667909Seric 	/* strip leading spaces */
13767909Seric 	for (q = argv[2]; *q != '\0' && isascii(*q) && isspace(*q); )
13867909Seric 		q++;
13967909Seric 
14067909Seric 	/* find the end of the command name */
14167909Seric 	p = strpbrk(q, " \t");
14267909Seric 	if (p == NULL)
14367909Seric 		cmd = &q[strlen(q)];
14467909Seric 	else
14567909Seric 	{
14667909Seric 		*p = '\0';
14767909Seric 		cmd = p;
14867909Seric 	}
14967909Seric 
15067909Seric 	/* search backwards for last / (allow for 0200 bit) */
15167909Seric 	while (cmd > q)
15267909Seric 	{
15367909Seric 		if ((*--cmd & 0177) == '/')
15467909Seric 		{
15567909Seric 			cmd++;
15667909Seric 			break;
15767909Seric 		}
15867909Seric 	}
15967909Seric 
16067909Seric 	/* cmd now points at final component of path name */
16167909Seric 
16267909Seric 	/*
16367909Seric 	**  Check to see if the command name is legal.
16467909Seric 	*/
16567909Seric 
16667909Seric 	(void) strcpy(cmdbuf, CMDDIR);
16767909Seric 	(void) strcat(cmdbuf, "/");
16867909Seric 	(void) strcat(cmdbuf, cmd);
16967909Seric #ifdef DEBUG
17067909Seric 	printf("Trying %s\n", cmdbuf);
17167909Seric #endif
17267909Seric 	if (access(cmdbuf, X_OK) < 0)
17367909Seric 	{
17467909Seric 		/* oops....  crack attack possiblity */
17567909Seric 		fprintf(stderr, "%s: %s not available for sendmail programs\n",
17667909Seric 			argv[0], cmd);
17767909Seric 		if (p != NULL)
17867909Seric 			*p = ' ';
17967909Seric 		syslog(LOG_CRIT, "uid %d: attempt to use %s", getuid(), cmd);
18067909Seric 		exit(EX_UNAVAILABLE);
18167909Seric 	}
18267909Seric 	if (p != NULL)
18367909Seric 		*p = ' ';
18467909Seric 
18567909Seric 	/*
18667909Seric 	**  Create the actual shell input.
18767909Seric 	*/
18867909Seric 
18967909Seric 	strcpy(cmdbuf, CMDDIR);
19067909Seric 	strcat(cmdbuf, "/");
19167909Seric 	strcat(cmdbuf, cmd);
19267909Seric 
19367909Seric 	/*
19467909Seric 	**  Now invoke the shell
19567909Seric 	*/
19667909Seric 
19767909Seric #ifdef DEBUG
19867909Seric 	printf("%s\n", cmdbuf);
19967909Seric #endif
20067909Seric 	execle("/bin/sh", "/bin/sh", "-c", cmdbuf, NULL, newenv);
20167909Seric 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
20267909Seric 	perror("/bin/sh");
20367909Seric 	exit(EX_OSFILE);
20467909Seric }
205