smrsh.c revision 90795
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#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.55 2001/09/11 04:05:22 gshapiro Exp $")
24
25/* $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 90795 2002-02-17 21:58:34Z gshapiro $ */
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/string.h>
60#include <sys/file.h>
61#include <string.h>
62#include <ctype.h>
63#include <errno.h>
64#ifdef EX_OK
65# undef EX_OK
66#endif /* EX_OK */
67#include <sysexits.h>
68#include <syslog.h>
69#include <stdlib.h>
70
71#include <sm/conf.h>
72#include <sm/errstring.h>
73
74/* directory in which all commands must reside */
75#ifndef CMDDIR
76# ifdef SMRSH_CMDDIR
77#  define CMDDIR	SMRSH_CMDDIR
78# else /* SMRSH_CMDDIR */
79#  define CMDDIR	"/usr/adm/sm.bin"
80# endif /* SMRSH_CMDDIR */
81#endif /* ! CMDDIR */
82
83/* characters disallowed in the shell "-c" argument */
84#define SPECIALS	"<|>^();&`$\r\n"
85
86/* default search path */
87#ifndef PATH
88# ifdef SMRSH_PATH
89#  define PATH		SMRSH_PATH
90# else /* SMRSH_PATH */
91#  define PATH		"/bin:/usr/bin:/usr/ucb"
92# endif /* SMRSH_PATH */
93#endif /* ! PATH */
94
95char newcmdbuf[1000];
96char *prg, *par;
97
98/*
99**  ADDCMD -- add a string to newcmdbuf, check for overflow
100**
101**    Parameters:
102**	s -- string to add
103**	cmd -- it's a command: prepend CMDDIR/
104**	len -- length of string to add
105**
106**    Side Effects:
107**	changes newcmdbuf or exits with a failure.
108**
109*/
110
111void
112addcmd(s, cmd, len)
113	char *s;
114	bool cmd;
115	size_t len;
116{
117	if (s == NULL || *s == '\0')
118		return;
119
120	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
121	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
122	{
123		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
124				    "%s: command too long: %s\n", prg, par);
125#ifndef DEBUG
126		syslog(LOG_WARNING, "command too long: %.40s", par);
127#endif /* ! DEBUG */
128		exit(EX_UNAVAILABLE);
129	}
130	if (cmd)
131	{
132		(void) sm_strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf);
133		(void) sm_strlcat(newcmdbuf, "/", sizeof newcmdbuf);
134	}
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 cmdbuf[1000];
151	char pathbuf[1000];
152	char specialbuf[32];
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_strlcpy(pathbuf, "PATH=", sizeof pathbuf);
163	(void) sm_strlcat(pathbuf, PATH, sizeof pathbuf);
164	newenv[0] = pathbuf;
165	newenv[1] = NULL;
166
167	/*
168	**  Do basic argv usage checking
169	*/
170
171	prg = argv[0];
172
173	if (argc != 3 || strcmp(argv[1], "-c") != 0)
174	{
175		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
176				     "Usage: %s -c command\n", prg);
177#ifndef DEBUG
178		syslog(LOG_ERR, "usage");
179#endif /* ! DEBUG */
180		exit(EX_USAGE);
181	}
182
183	par = argv[2];
184
185	/*
186	**  Disallow special shell syntax.  This is overly restrictive,
187	**  but it should shut down all attacks.
188	**  Be sure to include 8-bit versions, since many shells strip
189	**  the address to 7 bits before checking.
190	*/
191
192	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
193	{
194#ifndef DEBUG
195		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
196#endif /* ! DEBUG */
197		exit(EX_UNAVAILABLE);
198	}
199	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
200	for (p = specialbuf; *p != '\0'; p++)
201		*p |= '\200';
202	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
203
204	/*
205	**  Do a quick sanity check on command line length.
206	*/
207
208	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
209	{
210		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
211				     "%s: command too long: %s\n", prg, par);
212#ifndef DEBUG
213		syslog(LOG_WARNING, "command too long: %.40s", par);
214#endif /* ! DEBUG */
215		exit(EX_UNAVAILABLE);
216	}
217
218	q = par;
219	newcmdbuf[0] = '\0';
220	isexec = false;
221
222	while (*q)
223	{
224		/*
225		**  Strip off a leading pathname on the command name.  For
226		**  example, change /usr/ucb/vacation to vacation.
227		*/
228
229		/* strip leading spaces */
230		while (*q != '\0' && isascii(*q) && isspace(*q))
231			q++;
232		if (*q == '\0')
233		{
234			if (isexec)
235			{
236				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
237						     "%s: missing command to exec\n",
238						     prg);
239#ifndef DEBUG
240				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
241#endif /* ! DEBUG */
242				exit(EX_UNAVAILABLE);
243			}
244			break;
245		}
246
247		/* find the end of the command name */
248		p = strpbrk(q, " \t");
249		if (p == NULL)
250			cmd = &q[strlen(q)];
251		else
252		{
253			*p = '\0';
254			cmd = p;
255		}
256		/* search backwards for last / (allow for 0200 bit) */
257		while (cmd > q)
258		{
259			if ((*--cmd & 0177) == '/')
260			{
261				cmd++;
262				break;
263			}
264		}
265		/* cmd now points at final component of path name */
266
267		/* allow a few shell builtins */
268		if (strcmp(q, "exec") == 0 && p != NULL)
269		{
270			addcmd("exec ", false, strlen("exec "));
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			/* test following chars */
280		}
281		else
282		{
283			/*
284			**  Check to see if the command name is legal.
285			*/
286			(void) sm_strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf);
287			(void) sm_strlcat(cmdbuf, "/", sizeof cmdbuf);
288			(void) sm_strlcat(cmdbuf, cmd, sizeof cmdbuf);
289#ifdef DEBUG
290			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
291					     "Trying %s\n", cmdbuf);
292#endif /* DEBUG */
293			if (access(cmdbuf, X_OK) < 0)
294			{
295				/* oops....  crack attack possiblity */
296				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
297						     "%s: %s not available for sendmail programs\n",
298						      prg, cmd);
299				if (p != NULL)
300					*p = ' ';
301#ifndef DEBUG
302				syslog(LOG_CRIT, "uid %d: attempt to use %s",
303				       (int) getuid(), cmd);
304#endif /* ! DEBUG */
305				exit(EX_UNAVAILABLE);
306			}
307
308			/*
309			**  Create the actual shell input.
310			*/
311
312			addcmd(cmd, true, strlen(cmd));
313		}
314		isexec = false;
315
316		if (p != NULL)
317			*p = ' ';
318		else
319			break;
320
321		r = strpbrk(p, specialbuf);
322		if (r == NULL)
323		{
324			addcmd(p, false, strlen(p));
325			break;
326		}
327#if ALLOWSEMI
328		if (*r == ';')
329		{
330			addcmd(p, false,  r - p + 1);
331			q = r + 1;
332			continue;
333		}
334#endif /* ALLOWSEMI */
335		if ((*r == '&' && *(r + 1) == '&') ||
336		    (*r == '|' && *(r + 1) == '|'))
337		{
338			addcmd(p, false,  r - p + 2);
339			q = r + 2;
340			continue;
341		}
342
343		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
344				     "%s: cannot use %c in command\n", prg, *r);
345#ifndef DEBUG
346		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
347		       (int) getuid(), *r, par);
348#endif /* ! DEBUG */
349		exit(EX_UNAVAILABLE);
350	}		/* end of while *q */
351	if (isexec)
352	{
353		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
354				     "%s: missing command to exec\n", prg);
355#ifndef DEBUG
356		syslog(LOG_CRIT, "uid %d: missing command to exec",
357		       (int) getuid());
358#endif /* ! DEBUG */
359		exit(EX_UNAVAILABLE);
360	}
361	/* make sure we created something */
362	if (newcmdbuf[0] == '\0')
363	{
364		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
365				     "Usage: %s -c command\n", prg);
366#ifndef DEBUG
367		syslog(LOG_ERR, "usage");
368#endif /* ! DEBUG */
369		exit(EX_USAGE);
370	}
371
372	/*
373	**  Now invoke the shell
374	*/
375
376#ifdef DEBUG
377	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
378#endif /* DEBUG */
379	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
380	save_errno = errno;
381#ifndef DEBUG
382	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
383#endif /* ! DEBUG */
384	errno = save_errno;
385	sm_perror("/bin/sh");
386	exit(EX_OSFILE);
387	/* NOTREACHED */
388	return EX_OSFILE;
389}
390