xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision 13058a916175518dfbac6ce66b9b8e22ecf43155)
1 /*
2  * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  * Copyright (c) 1993 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * By using this file, you agree to the terms and conditions set
9  * forth in the LICENSE file which can be found at the top level of
10  * the sendmail distribution.
11  *
12  */
13 
14 #ifndef lint
15 static char copyright[] =
16 "@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
17 	All rights reserved.\n\
18      Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
19      Copyright (c) 1993\n\
20 	The Regents of the University of California.  All rights reserved.\n";
21 #endif /* ! lint */
22 
23 #ifndef lint
24 static char id[] = "@(#)$Id: smrsh.c,v 8.31.4.9 2001/04/24 04:11:51 ca Exp $";
25 #endif /* ! lint */
26 
27 /*
28 **  SMRSH -- sendmail restricted shell
29 **
30 **	This is a patch to get around the prog mailer bugs in most
31 **	versions of sendmail.
32 **
33 **	Use this in place of /bin/sh in the "prog" mailer definition
34 **	in your sendmail.cf file.  You then create CMDDIR (owned by
35 **	root, mode 755) and put links to any programs you want
36 **	available to prog mailers in that directory.  This should
37 **	include things like "vacation" and "procmail", but not "sed"
38 **	or "sh".
39 **
40 **	Leading pathnames are stripped from program names so that
41 **	existing .forward files that reference things like
42 **	"/usr/bin/vacation" will continue to work.
43 **
44 **	The following characters are completely illegal:
45 **		<  >  ^  &  `  (  ) \n \r
46 **	The following characters are sometimes illegal:
47 **		|  &
48 **	This is more restrictive than strictly necessary.
49 **
50 **	To use this, add FEATURE(`smrsh') to your .mc file.
51 **
52 **	This can be used on any version of sendmail.
53 **
54 **	In loving memory of RTM.  11/02/93.
55 */
56 
57 #include <unistd.h>
58 #include <stdio.h>
59 #include <sys/file.h>
60 #include <string.h>
61 #include <ctype.h>
62 #include <errno.h>
63 #ifdef EX_OK
64 # undef EX_OK
65 #endif /* EX_OK */
66 #include <sysexits.h>
67 #include <syslog.h>
68 #include <stdlib.h>
69 
70 #ifndef TRUE
71 # define TRUE	1
72 # define FALSE	0
73 #endif /* ! TRUE */
74 
75 /* directory in which all commands must reside */
76 #ifndef CMDDIR
77 # if defined(HPUX10) || defined(HPUX11) || SOLARIS >= 20800
78 #  define CMDDIR	"/var/adm/sm.bin"
79 # else /* HPUX10 || HPUX11 || SOLARIS >= 20800 */
80 #  define CMDDIR	"/usr/adm/sm.bin"
81 # endif /* HPUX10 || HPUX11 || SOLARIS >= 20800 */
82 #endif /* ! CMDDIR */
83 
84 /* characters disallowed in the shell "-c" argument */
85 #define SPECIALS	"<|>^();&`$\r\n"
86 
87 /* default search path */
88 #ifndef PATH
89 # define PATH		"/bin:/usr/bin:/usr/ucb"
90 #endif /* ! PATH */
91 
92 #ifndef __P
93 # include "sendmail/cdefs.h"
94 #endif /* ! __P */
95 
96 extern size_t	strlcpy __P((char *, const char *, size_t));
97 extern size_t	strlcat __P((char *, const char *, size_t));
98 
99 char newcmdbuf[1000];
100 char *prg, *par;
101 
102 /*
103 **  ADDCMD -- add a string to newcmdbuf, check for overflow
104 **
105 **    Parameters:
106 **	s -- string to add
107 **	cmd -- it's a command: prepend CMDDIR/
108 **	len -- length of string to add
109 **
110 **    Side Effects:
111 **	changes newcmdbuf or exits with a failure.
112 **
113 */
114 
115 void
116 addcmd(s, cmd, len)
117 	char *s;
118 	int cmd;
119 	int len;
120 {
121 	if (s == NULL || *s == '\0')
122 		return;
123 
124 	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
125 	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
126 	{
127 		fprintf(stderr, "%s: command too long: %s\n", prg, par);
128 #ifndef DEBUG
129 		syslog(LOG_WARNING, "command too long: %.40s", par);
130 #endif /* ! DEBUG */
131 		exit(EX_UNAVAILABLE);
132 	}
133 	if (cmd)
134 	{
135 		(void) strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf);
136 		(void) strlcat(newcmdbuf, "/", sizeof newcmdbuf);
137 	}
138 	(void) strlcat(newcmdbuf, s, sizeof newcmdbuf);
139 }
140 
141 int
142 main(argc, argv)
143 	int argc;
144 	char **argv;
145 {
146 	register char *p;
147 	register char *q;
148 	register char *r;
149 	register char *cmd;
150 	int i;
151 	int isexec;
152 	int save_errno;
153 	char *newenv[2];
154 	char cmdbuf[1000];
155 	char pathbuf[1000];
156 	char specialbuf[32];
157 
158 #ifndef DEBUG
159 # ifndef LOG_MAIL
160 	openlog("smrsh", 0);
161 # else /* ! LOG_MAIL */
162 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
163 # endif /* ! LOG_MAIL */
164 #endif /* ! DEBUG */
165 
166 	(void) strlcpy(pathbuf, "PATH=", sizeof pathbuf);
167 	(void) strlcat(pathbuf, PATH, sizeof pathbuf);
168 	newenv[0] = pathbuf;
169 	newenv[1] = NULL;
170 
171 	/*
172 	**  Do basic argv usage checking
173 	*/
174 
175 	prg = argv[0];
176 
177 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
178 	{
179 		fprintf(stderr, "Usage: %s -c command\n", prg);
180 #ifndef DEBUG
181 		syslog(LOG_ERR, "usage");
182 #endif /* ! DEBUG */
183 		exit(EX_USAGE);
184 	}
185 
186 	par = argv[2];
187 
188 	/*
189 	**  Disallow special shell syntax.  This is overly restrictive,
190 	**  but it should shut down all attacks.
191 	**  Be sure to include 8-bit versions, since many shells strip
192 	**  the address to 7 bits before checking.
193 	*/
194 
195 	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
196 	{
197 #ifndef DEBUG
198 		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
199 #endif /* ! DEBUG */
200 		exit(EX_UNAVAILABLE);
201 	}
202 	(void) strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
203 	for (p = specialbuf; *p != '\0'; p++)
204 		*p |= '\200';
205 	(void) strlcat(specialbuf, SPECIALS, sizeof specialbuf);
206 
207 	/*
208 	**  Do a quick sanity check on command line length.
209 	*/
210 
211 	i = strlen(par);
212 	if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2))
213 	{
214 		fprintf(stderr, "%s: command too long: %s\n", prg, par);
215 #ifndef DEBUG
216 		syslog(LOG_WARNING, "command too long: %.40s", par);
217 #endif /* ! DEBUG */
218 		exit(EX_UNAVAILABLE);
219 	}
220 
221 	q = par;
222 	newcmdbuf[0] = '\0';
223 	isexec = FALSE;
224 
225 	while (*q)
226 	{
227 		/*
228 		**  Strip off a leading pathname on the command name.  For
229 		**  example, change /usr/ucb/vacation to vacation.
230 		*/
231 
232 		/* strip leading spaces */
233 		while (*q != '\0' && isascii(*q) && isspace(*q))
234 			q++;
235 		if (*q == '\0')
236 		{
237 			if (isexec)
238 			{
239 				fprintf(stderr, "%s: missing command to exec\n",
240 					prg);
241 #ifndef DEBUG
242 				syslog(LOG_CRIT, "uid %d: missing command to exec", getuid());
243 #endif /* ! DEBUG */
244 				exit(EX_UNAVAILABLE);
245 			}
246 			break;
247 		}
248 
249 		/* find the end of the command name */
250 		p = strpbrk(q, " \t");
251 		if (p == NULL)
252 			cmd = &q[strlen(q)];
253 		else
254 		{
255 			*p = '\0';
256 			cmd = p;
257 		}
258 		/* search backwards for last / (allow for 0200 bit) */
259 		while (cmd > q)
260 		{
261 			if ((*--cmd & 0177) == '/')
262 			{
263 				cmd++;
264 				break;
265 			}
266 		}
267 		/* cmd now points at final component of path name */
268 
269 		/* allow a few shell builtins */
270 		if (strcmp(q, "exec") == 0 && p != NULL)
271 		{
272 			addcmd("exec ", FALSE, strlen("exec "));
273 			/* test _next_ arg */
274 			q = ++p;
275 			isexec = TRUE;
276 			continue;
277 		}
278 		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
279 		{
280 			addcmd(cmd, FALSE, strlen(cmd));
281 			/* test following chars */
282 		}
283 		else
284 		{
285 			/*
286 			**  Check to see if the command name is legal.
287 			*/
288 			(void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf);
289 			(void) strlcat(cmdbuf, "/", sizeof cmdbuf);
290 			(void) strlcat(cmdbuf, cmd, sizeof cmdbuf);
291 #ifdef DEBUG
292 			printf("Trying %s\n", cmdbuf);
293 #endif /* DEBUG */
294 			if (access(cmdbuf, X_OK) < 0)
295 			{
296 				/* oops....  crack attack possiblity */
297 				fprintf(stderr,
298 					"%s: %s not available for sendmail programs\n",
299 					prg, cmd);
300 				if (p != NULL)
301 					*p = ' ';
302 #ifndef DEBUG
303 				syslog(LOG_CRIT, "uid %d: attempt to use %s",
304 				       getuid(), cmd);
305 #endif /* ! DEBUG */
306 				exit(EX_UNAVAILABLE);
307 			}
308 
309 			/*
310 			**  Create the actual shell input.
311 			*/
312 
313 			addcmd(cmd, TRUE, strlen(cmd));
314 		}
315 		isexec = FALSE;
316 
317 		if (p != NULL)
318 			*p = ' ';
319 		else
320 			break;
321 
322 		r = strpbrk(p, specialbuf);
323 		if (r == NULL) {
324 			addcmd(p, FALSE, strlen(p));
325 			break;
326 		}
327 #if ALLOWSEMI
328 		if (*r == ';') {
329 			addcmd(p, FALSE,  r - p + 1);
330 			q = r + 1;
331 			continue;
332 		}
333 #endif /* ALLOWSEMI */
334 		if ((*r == '&' && *(r + 1) == '&') ||
335 		    (*r == '|' && *(r + 1) == '|'))
336 		{
337 			addcmd(p, FALSE,  r - p + 2);
338 			q = r + 2;
339 			continue;
340 		}
341 
342 		fprintf(stderr, "%s: cannot use %c in command\n", prg, *r);
343 #ifndef DEBUG
344 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
345 			getuid(), *r, par);
346 #endif /* ! DEBUG */
347 		exit(EX_UNAVAILABLE);
348 	}		/* end of while *q */
349 	if (isexec)
350 	{
351 		fprintf(stderr, "%s: missing command to exec\n", prg);
352 #ifndef DEBUG
353 		syslog(LOG_CRIT, "uid %d: missing command to exec", getuid());
354 #endif /* ! DEBUG */
355 		exit(EX_UNAVAILABLE);
356 	}
357 	/* make sure we created something */
358 	if (newcmdbuf[0] == '\0')
359 	{
360 		fprintf(stderr, "Usage: %s -c command\n", prg);
361 #ifndef DEBUG
362 		syslog(LOG_ERR, "usage");
363 #endif /* ! DEBUG */
364 		exit(EX_USAGE);
365 	}
366 
367 	/*
368 	**  Now invoke the shell
369 	*/
370 
371 #ifdef DEBUG
372 	printf("%s\n", newcmdbuf);
373 #endif /* DEBUG */
374 	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
375 	save_errno = errno;
376 #ifndef DEBUG
377 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
378 #endif /* ! DEBUG */
379 	errno = save_errno;
380 	perror("/bin/sh");
381 	exit(EX_OSFILE);
382 	/* NOTREACHED */
383 	return EX_OSFILE;
384 }
385