1*67909Seric /*
2*67909Seric  * Copyright (c) 1993 Eric P. Allman
3*67909Seric  * Copyright (c) 1993
4*67909Seric  *	The Regents of the University of California.  All rights reserved.
5*67909Seric  *
6*67909Seric  * %sccs.include.redist.c%
7*67909Seric  */
8*67909Seric 
9*67909Seric #ifndef lint
10*67909Seric static char sccsid[] = "@(#)smrsh.c	8.1 (Berkeley) 11/13/94";
11*67909Seric #endif /* not lint */
12*67909Seric 
13*67909Seric /*
14*67909Seric **  SMRSH -- sendmail restricted shell
15*67909Seric **
16*67909Seric **	This is a patch to get around the prog mailer bugs in most
17*67909Seric **	versions of sendmail.
18*67909Seric **
19*67909Seric **	Use this in place of /bin/sh in the "prog" mailer definition
20*67909Seric **	in your sendmail.cf file.  You then create CMDDIR (owned by
21*67909Seric **	root, mode 755) and put links to any programs you want
22*67909Seric **	available to prog mailers in that directory.  This should
23*67909Seric **	include things like "vacation" and "procmail", but not "sed"
24*67909Seric **	or "sh".
25*67909Seric **
26*67909Seric **	Leading pathnames are stripped from program names so that
27*67909Seric **	existing .forward files that reference things like
28*67909Seric **	"/usr/ucb/vacation" will continue to work.
29*67909Seric **
30*67909Seric **	The following characters are completely illegal:
31*67909Seric **		<  >  |  ^  ;  &  $  `  (  ) \n \r
32*67909Seric **	This is more restrictive than strictly necessary.
33*67909Seric **
34*67909Seric **	To use this, edit /etc/sendmail.cf, search for ^Mprog, and
35*67909Seric **	change P=/bin/sh to P=/usr/etc/smrsh, where this compiled
36*67909Seric **	binary is installed /usr/etc/smrsh.
37*67909Seric **
38*67909Seric **	This can be used on any version of sendmail.
39*67909Seric **
40*67909Seric **	In loving memory of RTM.  11/02/93.
41*67909Seric */
42*67909Seric 
43*67909Seric #include <stdio.h>
44*67909Seric #include <sys/file.h>
45*67909Seric #include <string.h>
46*67909Seric #include <ctype.h>
47*67909Seric #include <sysexits.h>
48*67909Seric #include <syslog.h>
49*67909Seric 
50*67909Seric /* directory in which all commands must reside */
51*67909Seric #ifndef CMDDIR
52*67909Seric # define CMDDIR		"/usr/adm/sm.bin"
53*67909Seric #endif
54*67909Seric 
55*67909Seric /* characters disallowed in the shell "-c" argument */
56*67909Seric #define SPECIALS	"<|>^();&`$\r\n"
57*67909Seric 
58*67909Seric /* default search path */
59*67909Seric #ifndef PATH
60*67909Seric # define PATH		"/bin:/usr/bin:/usr/ucb"
61*67909Seric #endif
62*67909Seric 
63*67909Seric main(argc, argv)
64*67909Seric 	int argc;
65*67909Seric 	char **argv;
66*67909Seric {
67*67909Seric 	register char *p;
68*67909Seric 	register char *q;
69*67909Seric 	register char *cmd;
70*67909Seric 	int i;
71*67909Seric 	char *newenv[2];
72*67909Seric 	char cmdbuf[1000];
73*67909Seric 	char pathbuf[1000];
74*67909Seric 
75*67909Seric #ifndef LOG_MAIL
76*67909Seric 	openlog("smrsh", 0);
77*67909Seric #else
78*67909Seric 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
79*67909Seric #endif
80*67909Seric 
81*67909Seric 	strcpy(pathbuf, "PATH=");
82*67909Seric 	strcat(pathbuf, PATH);
83*67909Seric 	newenv[0] = pathbuf;
84*67909Seric 	newenv[1] = NULL;
85*67909Seric 
86*67909Seric 	/*
87*67909Seric 	**  Do basic argv usage checking
88*67909Seric 	*/
89*67909Seric 
90*67909Seric 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
91*67909Seric 	{
92*67909Seric 		fprintf(stderr, "Usage: %s -c command\n", argv[0]);
93*67909Seric 		syslog(LOG_ERR, "usage");
94*67909Seric 		exit(EX_USAGE);
95*67909Seric 	}
96*67909Seric 
97*67909Seric 	/*
98*67909Seric 	**  Disallow special shell syntax.  This is overly restrictive,
99*67909Seric 	**  but it should shut down all attacks.
100*67909Seric 	**  Be sure to include 8-bit versions, since many shells strip
101*67909Seric 	**  the address to 7 bits before checking.
102*67909Seric 	*/
103*67909Seric 
104*67909Seric 	strcpy(cmdbuf, SPECIALS);
105*67909Seric 	for (p = cmdbuf; *p != '\0'; p++)
106*67909Seric 		*p |= '\200';
107*67909Seric 	strcat(cmdbuf, SPECIALS);
108*67909Seric 	p = strpbrk(argv[2], cmdbuf);
109*67909Seric 	if (p != NULL)
110*67909Seric 	{
111*67909Seric 		fprintf(stderr, "%s: cannot use %c in command\n",
112*67909Seric 			argv[0], *p);
113*67909Seric 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
114*67909Seric 			getuid(), *p, argv[2]);
115*67909Seric 		exit(EX_UNAVAILABLE);
116*67909Seric 	}
117*67909Seric 
118*67909Seric 	/*
119*67909Seric 	**  Do a quick sanity check on command line length.
120*67909Seric 	*/
121*67909Seric 
122*67909Seric 	i = strlen(argv[2]);
123*67909Seric 	if (i > (sizeof cmdbuf - sizeof CMDDIR - 2))
124*67909Seric 	{
125*67909Seric 		fprintf(stderr, "%s: command too long: %s\n", argv[0], argv[2]);
126*67909Seric 		syslog(LOG_WARNING, "command too long: %.40s", argv[2]);
127*67909Seric 		exit(EX_UNAVAILABLE);
128*67909Seric 	}
129*67909Seric 
130*67909Seric 	/*
131*67909Seric 	**  Strip off a leading pathname on the command name.  For
132*67909Seric 	**  example, change /usr/ucb/vacation to vacation.
133*67909Seric 	*/
134*67909Seric 
135*67909Seric 	/* strip leading spaces */
136*67909Seric 	for (q = argv[2]; *q != '\0' && isascii(*q) && isspace(*q); )
137*67909Seric 		q++;
138*67909Seric 
139*67909Seric 	/* find the end of the command name */
140*67909Seric 	p = strpbrk(q, " \t");
141*67909Seric 	if (p == NULL)
142*67909Seric 		cmd = &q[strlen(q)];
143*67909Seric 	else
144*67909Seric 	{
145*67909Seric 		*p = '\0';
146*67909Seric 		cmd = p;
147*67909Seric 	}
148*67909Seric 
149*67909Seric 	/* search backwards for last / (allow for 0200 bit) */
150*67909Seric 	while (cmd > q)
151*67909Seric 	{
152*67909Seric 		if ((*--cmd & 0177) == '/')
153*67909Seric 		{
154*67909Seric 			cmd++;
155*67909Seric 			break;
156*67909Seric 		}
157*67909Seric 	}
158*67909Seric 
159*67909Seric 	/* cmd now points at final component of path name */
160*67909Seric 
161*67909Seric 	/*
162*67909Seric 	**  Check to see if the command name is legal.
163*67909Seric 	*/
164*67909Seric 
165*67909Seric 	(void) strcpy(cmdbuf, CMDDIR);
166*67909Seric 	(void) strcat(cmdbuf, "/");
167*67909Seric 	(void) strcat(cmdbuf, cmd);
168*67909Seric #ifdef DEBUG
169*67909Seric 	printf("Trying %s\n", cmdbuf);
170*67909Seric #endif
171*67909Seric 	if (access(cmdbuf, X_OK) < 0)
172*67909Seric 	{
173*67909Seric 		/* oops....  crack attack possiblity */
174*67909Seric 		fprintf(stderr, "%s: %s not available for sendmail programs\n",
175*67909Seric 			argv[0], cmd);
176*67909Seric 		if (p != NULL)
177*67909Seric 			*p = ' ';
178*67909Seric 		syslog(LOG_CRIT, "uid %d: attempt to use %s", getuid(), cmd);
179*67909Seric 		exit(EX_UNAVAILABLE);
180*67909Seric 	}
181*67909Seric 	if (p != NULL)
182*67909Seric 		*p = ' ';
183*67909Seric 
184*67909Seric 	/*
185*67909Seric 	**  Create the actual shell input.
186*67909Seric 	*/
187*67909Seric 
188*67909Seric 	strcpy(cmdbuf, CMDDIR);
189*67909Seric 	strcat(cmdbuf, "/");
190*67909Seric 	strcat(cmdbuf, cmd);
191*67909Seric 
192*67909Seric 	/*
193*67909Seric 	**  Now invoke the shell
194*67909Seric 	*/
195*67909Seric 
196*67909Seric #ifdef DEBUG
197*67909Seric 	printf("%s\n", cmdbuf);
198*67909Seric #endif
199*67909Seric 	execle("/bin/sh", "/bin/sh", "-c", cmdbuf, NULL, newenv);
200*67909Seric 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
201*67909Seric 	perror("/bin/sh");
202*67909Seric 	exit(EX_OSFILE);
203*67909Seric }
204