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