smrsh.c revision 64565
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, 1999 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.4 2000/05/25 21:44:29 gshapiro Exp $";
25#endif /* ! lint */
26
27/* $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 64565 2000-08-12 22:19:16Z 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# define CMDDIR		"/usr/libexec/sm.bin"
80#endif /* ! CMDDIR */
81
82/* characters disallowed in the shell "-c" argument */
83#define SPECIALS	"<|>^();&`$\r\n"
84
85/* default search path */
86#ifndef PATH
87# define PATH		"/bin:/usr/bin"
88#endif /* ! PATH */
89
90#ifndef __P
91# include "sendmail/cdefs.h"
92#endif /* ! __P */
93
94extern size_t	strlcpy __P((char *, const char *, size_t));
95extern size_t	strlcat __P((char *, const char *, size_t));
96
97char newcmdbuf[1000];
98char *prg, *par;
99
100/*
101**  ADDCMD -- add a string to newcmdbuf, check for overflow
102**
103**    Parameters:
104**	s -- string to add
105**	cmd -- it's a command: prepend CMDDIR/
106**	len -- length of string to add
107**
108**    Side Effects:
109**	changes newcmdbuf or exits with a failure.
110**
111*/
112
113void
114addcmd(s, cmd, len)
115	char *s;
116	int cmd;
117	int len;
118{
119	if (s == NULL || *s == '\0')
120		return;
121
122	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
123	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
124	{
125		fprintf(stderr, "%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	{
133		(void) strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf);
134		(void) strlcat(newcmdbuf, "/", sizeof newcmdbuf);
135	}
136	(void) strlcat(newcmdbuf, s, sizeof newcmdbuf);
137}
138
139int
140main(argc, argv)
141	int argc;
142	char **argv;
143{
144	register char *p;
145	register char *q;
146	register char *r;
147	register char *cmd;
148	int i;
149	int isexec;
150	int save_errno;
151	char *newenv[2];
152	char cmdbuf[1000];
153	char pathbuf[1000];
154	char specialbuf[32];
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) strlcpy(pathbuf, "PATH=", sizeof pathbuf);
165	(void) strlcat(pathbuf, PATH, sizeof pathbuf);
166	newenv[0] = pathbuf;
167	newenv[1] = NULL;
168
169	/*
170	**  Do basic argv usage checking
171	*/
172
173	prg = argv[0];
174	par = argv[2];
175
176	if (argc != 3 || strcmp(argv[1], "-c") != 0)
177	{
178		fprintf(stderr, "Usage: %s -c command\n", prg);
179#ifndef DEBUG
180		syslog(LOG_ERR, "usage");
181#endif /* ! DEBUG */
182		exit(EX_USAGE);
183	}
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) strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
200	for (p = specialbuf; *p != '\0'; p++)
201		*p |= '\200';
202	(void) strlcat(specialbuf, SPECIALS, sizeof specialbuf);
203
204	/*
205	**  Do a quick sanity check on command line length.
206	*/
207
208	i = strlen(par);
209	if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2))
210	{
211		fprintf(stderr, "%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				fprintf(stderr, "%s: missing command to exec\n",
237					prg);
238#ifndef DEBUG
239				syslog(LOG_CRIT, "uid %d: missing command to exec", 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			/* test _next_ arg */
271			q = ++p;
272			isexec = TRUE;
273			continue;
274		}
275		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
276		{
277			addcmd(cmd, FALSE, strlen(cmd));
278			/* test following chars */
279		}
280		else
281		{
282			/*
283			**  Check to see if the command name is legal.
284			*/
285			(void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf);
286			(void) strlcat(cmdbuf, "/", sizeof cmdbuf);
287			(void) strlcat(cmdbuf, cmd, sizeof cmdbuf);
288#ifdef DEBUG
289			printf("Trying %s\n", cmdbuf);
290#endif /* DEBUG */
291			if (access(cmdbuf, X_OK) < 0)
292			{
293				/* oops....  crack attack possiblity */
294				fprintf(stderr,
295					"%s: %s not available for sendmail programs\n",
296					prg, cmd);
297				if (p != NULL)
298					*p = ' ';
299#ifndef DEBUG
300				syslog(LOG_CRIT, "uid %d: attempt to use %s",
301				       getuid(), cmd);
302#endif /* ! DEBUG */
303				exit(EX_UNAVAILABLE);
304			}
305
306			/*
307			**  Create the actual shell input.
308			*/
309
310			addcmd(cmd, TRUE, strlen(cmd));
311		}
312		isexec = FALSE;
313
314		if (p != NULL)
315			*p = ' ';
316		else
317			break;
318
319		r = strpbrk(p, specialbuf);
320		if (r == NULL) {
321			addcmd(p, FALSE, strlen(p));
322			break;
323		}
324#if ALLOWSEMI
325		if (*r == ';') {
326			addcmd(p, FALSE,  r - p + 1);
327			q = r + 1;
328			continue;
329		}
330#endif /* ALLOWSEMI */
331		if ((*r == '&' && *(r + 1) == '&') ||
332		    (*r == '|' && *(r + 1) == '|'))
333		{
334			addcmd(p, FALSE,  r - p + 2);
335			q = r + 2;
336			continue;
337		}
338
339		fprintf(stderr, "%s: cannot use %c in command\n", prg, *r);
340#ifndef DEBUG
341		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
342			getuid(), *r, par);
343#endif /* ! DEBUG */
344		exit(EX_UNAVAILABLE);
345	}		/* end of while *q */
346	if (isexec)
347	{
348		fprintf(stderr, "%s: missing command to exec\n", prg);
349#ifndef DEBUG
350		syslog(LOG_CRIT, "uid %d: missing command to exec", getuid());
351#endif /* ! DEBUG */
352		exit(EX_UNAVAILABLE);
353	}
354	/* make sure we created something */
355	if (newcmdbuf[0] == '\0')
356	{
357		fprintf(stderr, "Usage: %s -c command\n", prg);
358#ifndef DEBUG
359		syslog(LOG_ERR, "usage");
360#endif /* ! DEBUG */
361		exit(EX_USAGE);
362	}
363
364	/*
365	**  Now invoke the shell
366	*/
367
368#ifdef DEBUG
369	printf("%s\n", newcmdbuf);
370#endif /* DEBUG */
371	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
372	save_errno = errno;
373#ifndef DEBUG
374	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
375#endif /* ! DEBUG */
376	errno = save_errno;
377	perror("/bin/sh");
378	exit(EX_OSFILE);
379	/* NOTREACHED */
380	return EX_OSFILE;
381}
382