xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision 51a9219f5780e61e1437d25220bf8750d9df7f8b)
1 /*
2  * Copyright (c) 1998-2002 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  * $FreeBSD$
13  *
14  */
15 
16 #include <sm/gen.h>
17 
18 SM_IDSTR(copyright,
19 "@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
20 	All rights reserved.\n\
21      Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
22      Copyright (c) 1993\n\
23 	The Regents of the University of California.  All rights reserved.\n")
24 
25 SM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.58 2002/05/25 02:41:31 ca Exp $")
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 <sm/io.h>
59 #include <sm/limits.h>
60 #include <sm/string.h>
61 #include <sys/file.h>
62 #include <sys/types.h>
63 #include <sys/stat.h>
64 #include <string.h>
65 #include <ctype.h>
66 #include <errno.h>
67 #ifdef EX_OK
68 # undef EX_OK
69 #endif /* EX_OK */
70 #include <sysexits.h>
71 #include <syslog.h>
72 #include <stdlib.h>
73 
74 #include <sm/conf.h>
75 #include <sm/errstring.h>
76 
77 /* directory in which all commands must reside */
78 #ifndef CMDDIR
79 # ifdef SMRSH_CMDDIR
80 #  define CMDDIR	SMRSH_CMDDIR
81 # else /* SMRSH_CMDDIR */
82 #  define CMDDIR	"/usr/adm/sm.bin"
83 # endif /* SMRSH_CMDDIR */
84 #endif /* ! CMDDIR */
85 
86 /* characters disallowed in the shell "-c" argument */
87 #define SPECIALS	"<|>^();&`$\r\n"
88 
89 /* default search path */
90 #ifndef PATH
91 # ifdef SMRSH_PATH
92 #  define PATH		SMRSH_PATH
93 # else /* SMRSH_PATH */
94 #  define PATH		"/bin:/usr/bin:/usr/ucb"
95 # endif /* SMRSH_PATH */
96 #endif /* ! PATH */
97 
98 char newcmdbuf[1000];
99 char *prg, *par;
100 
101 /*
102 **  ADDCMD -- add a string to newcmdbuf, check for overflow
103 **
104 **    Parameters:
105 **	s -- string to add
106 **	cmd -- it's a command: prepend CMDDIR/
107 **	len -- length of string to add
108 **
109 **    Side Effects:
110 **	changes newcmdbuf or exits with a failure.
111 **
112 */
113 
114 void
115 addcmd(s, cmd, len)
116 	char *s;
117 	bool cmd;
118 	size_t len;
119 {
120 	if (s == NULL || *s == '\0')
121 		return;
122 
123 	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
124 	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
125 	{
126 		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
127 				    "%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 		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
135 	(void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf);
136 }
137 
138 int
139 main(argc, argv)
140 	int argc;
141 	char **argv;
142 {
143 	register char *p;
144 	register char *q;
145 	register char *r;
146 	register char *cmd;
147 	int isexec;
148 	int save_errno;
149 	char *newenv[2];
150 	char pathbuf[1000];
151 	char specialbuf[32];
152 	struct stat st;
153 
154 #ifndef DEBUG
155 # ifndef LOG_MAIL
156 	openlog("smrsh", 0);
157 # else /* ! LOG_MAIL */
158 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
159 # endif /* ! LOG_MAIL */
160 #endif /* ! DEBUG */
161 
162 	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
163 	newenv[0] = pathbuf;
164 	newenv[1] = NULL;
165 
166 	/*
167 	**  Do basic argv usage checking
168 	*/
169 
170 	prg = argv[0];
171 
172 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
173 	{
174 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
175 				     "Usage: %s -c command\n", prg);
176 #ifndef DEBUG
177 		syslog(LOG_ERR, "usage");
178 #endif /* ! DEBUG */
179 		exit(EX_USAGE);
180 	}
181 
182 	par = argv[2];
183 
184 	/*
185 	**  Disallow special shell syntax.  This is overly restrictive,
186 	**  but it should shut down all attacks.
187 	**  Be sure to include 8-bit versions, since many shells strip
188 	**  the address to 7 bits before checking.
189 	*/
190 
191 	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
192 	{
193 #ifndef DEBUG
194 		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
195 #endif /* ! DEBUG */
196 		exit(EX_UNAVAILABLE);
197 	}
198 	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
199 	for (p = specialbuf; *p != '\0'; p++)
200 		*p |= '\200';
201 	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
202 
203 	/*
204 	**  Do a quick sanity check on command line length.
205 	*/
206 
207 	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
208 	{
209 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
210 				     "%s: command too long: %s\n", prg, par);
211 #ifndef DEBUG
212 		syslog(LOG_WARNING, "command too long: %.40s", par);
213 #endif /* ! DEBUG */
214 		exit(EX_UNAVAILABLE);
215 	}
216 
217 	q = par;
218 	newcmdbuf[0] = '\0';
219 	isexec = false;
220 
221 	while (*q != '\0')
222 	{
223 		/*
224 		**  Strip off a leading pathname on the command name.  For
225 		**  example, change /usr/ucb/vacation to vacation.
226 		*/
227 
228 		/* strip leading spaces */
229 		while (*q != '\0' && isascii(*q) && isspace(*q))
230 			q++;
231 		if (*q == '\0')
232 		{
233 			if (isexec)
234 			{
235 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
236 						     "%s: missing command to exec\n",
237 						     prg);
238 #ifndef DEBUG
239 				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
240 #endif /* ! DEBUG */
241 				exit(EX_UNAVAILABLE);
242 			}
243 			break;
244 		}
245 
246 		/* find the end of the command name */
247 		p = strpbrk(q, " \t");
248 		if (p == NULL)
249 			cmd = &q[strlen(q)];
250 		else
251 		{
252 			*p = '\0';
253 			cmd = p;
254 		}
255 		/* search backwards for last / (allow for 0200 bit) */
256 		while (cmd > q)
257 		{
258 			if ((*--cmd & 0177) == '/')
259 			{
260 				cmd++;
261 				break;
262 			}
263 		}
264 		/* cmd now points at final component of path name */
265 
266 		/* allow a few shell builtins */
267 		if (strcmp(q, "exec") == 0 && p != NULL)
268 		{
269 			addcmd("exec ", false, strlen("exec "));
270 
271 			/* test _next_ arg */
272 			q = ++p;
273 			isexec = true;
274 			continue;
275 		}
276 		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
277 		{
278 			addcmd(cmd, false, strlen(cmd));
279 
280 			/* test following chars */
281 		}
282 		else
283 		{
284 			char cmdbuf[MAXPATHLEN];
285 
286 			/*
287 			**  Check to see if the command name is legal.
288 			*/
289 
290 			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
291 					"/", cmd) >= sizeof cmdbuf)
292 			{
293 				/* too long */
294 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
295 						     "%s: %s not available for sendmail programs (filename too long)\n",
296 						      prg, cmd);
297 				if (p != NULL)
298 					*p = ' ';
299 #ifndef DEBUG
300 				syslog(LOG_CRIT, "uid %d: attempt to use %s (filename too long)",
301 				       (int) getuid(), cmd);
302 #endif /* ! DEBUG */
303 				exit(EX_UNAVAILABLE);
304 			}
305 
306 #ifdef DEBUG
307 			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
308 					     "Trying %s\n", cmdbuf);
309 #endif /* DEBUG */
310 			if (stat(cmdbuf, &st) < 0)
311 			{
312 				/* can't stat it */
313 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
314 						     "%s: %s not available for sendmail programs (stat failed)\n",
315 						      prg, cmd);
316 				if (p != NULL)
317 					*p = ' ';
318 #ifndef DEBUG
319 				syslog(LOG_CRIT, "uid %d: attempt to use %s (stat failed)",
320 				       (int) getuid(), cmd);
321 #endif /* ! DEBUG */
322 				exit(EX_UNAVAILABLE);
323 			}
324 			if (!S_ISREG(st.st_mode)
325 #ifdef S_ISLNK
326 			    && !S_ISLNK(st.st_mode)
327 #endif /* S_ISLNK */
328 			   )
329 			{
330 				/* can't stat it */
331 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
332 						     "%s: %s not available for sendmail programs (not a file)\n",
333 						      prg, cmd);
334 				if (p != NULL)
335 					*p = ' ';
336 #ifndef DEBUG
337 				syslog(LOG_CRIT, "uid %d: attempt to use %s (not a file)",
338 				       (int) getuid(), cmd);
339 #endif /* ! DEBUG */
340 				exit(EX_UNAVAILABLE);
341 			}
342 			if (access(cmdbuf, X_OK) < 0)
343 			{
344 				/* oops....  crack attack possiblity */
345 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
346 						     "%s: %s not available for sendmail programs\n",
347 						      prg, cmd);
348 				if (p != NULL)
349 					*p = ' ';
350 #ifndef DEBUG
351 				syslog(LOG_CRIT, "uid %d: attempt to use %s",
352 				       (int) getuid(), cmd);
353 #endif /* ! DEBUG */
354 				exit(EX_UNAVAILABLE);
355 			}
356 
357 			/*
358 			**  Create the actual shell input.
359 			*/
360 
361 			addcmd(cmd, true, strlen(cmd));
362 		}
363 		isexec = false;
364 
365 		if (p != NULL)
366 			*p = ' ';
367 		else
368 			break;
369 
370 		r = strpbrk(p, specialbuf);
371 		if (r == NULL)
372 		{
373 			addcmd(p, false, strlen(p));
374 			break;
375 		}
376 #if ALLOWSEMI
377 		if (*r == ';')
378 		{
379 			addcmd(p, false,  r - p + 1);
380 			q = r + 1;
381 			continue;
382 		}
383 #endif /* ALLOWSEMI */
384 		if ((*r == '&' && *(r + 1) == '&') ||
385 		    (*r == '|' && *(r + 1) == '|'))
386 		{
387 			addcmd(p, false,  r - p + 2);
388 			q = r + 2;
389 			continue;
390 		}
391 
392 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
393 				     "%s: cannot use %c in command\n", prg, *r);
394 #ifndef DEBUG
395 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
396 		       (int) getuid(), *r, par);
397 #endif /* ! DEBUG */
398 		exit(EX_UNAVAILABLE);
399 	}
400 	if (isexec)
401 	{
402 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
403 				     "%s: missing command to exec\n", prg);
404 #ifndef DEBUG
405 		syslog(LOG_CRIT, "uid %d: missing command to exec",
406 		       (int) getuid());
407 #endif /* ! DEBUG */
408 		exit(EX_UNAVAILABLE);
409 	}
410 	/* make sure we created something */
411 	if (newcmdbuf[0] == '\0')
412 	{
413 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
414 				     "Usage: %s -c command\n", prg);
415 #ifndef DEBUG
416 		syslog(LOG_ERR, "usage");
417 #endif /* ! DEBUG */
418 		exit(EX_USAGE);
419 	}
420 
421 	/*
422 	**  Now invoke the shell
423 	*/
424 
425 #ifdef DEBUG
426 	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
427 #endif /* DEBUG */
428 	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
429 	save_errno = errno;
430 #ifndef DEBUG
431 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
432 #endif /* ! DEBUG */
433 	errno = save_errno;
434 	sm_perror("/bin/sh");
435 	exit(EX_OSFILE);
436 	/* NOTREACHED */
437 	return EX_OSFILE;
438 }
439