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