smrsh.c revision 105016
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: head/contrib/sendmail/smrsh/smrsh.c 105016 2002-10-13 00:56:58Z gshapiro $
13 *
14 */
15
16#include <sm/gen.h>
17
18SM_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
25SM_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
98char newcmdbuf[1000];
99char *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
114void
115addcmd(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
138int
139main(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