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