smrsh.c revision 77352
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#ifndef lint
15static char copyright[] =
16"@(#) Copyright (c) 1998-2001 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.9 2001/04/24 04:11:51 ca Exp $";
25#endif /* ! lint */
26
27/* $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 77352 2001-05-28 17:10:35Z 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
179	if (argc != 3 || strcmp(argv[1], "-c") != 0)
180	{
181		fprintf(stderr, "Usage: %s -c command\n", prg);
182#ifndef DEBUG
183		syslog(LOG_ERR, "usage");
184#endif /* ! DEBUG */
185		exit(EX_USAGE);
186	}
187
188	par = argv[2];
189
190	/*
191	**  Disallow special shell syntax.  This is overly restrictive,
192	**  but it should shut down all attacks.
193	**  Be sure to include 8-bit versions, since many shells strip
194	**  the address to 7 bits before checking.
195	*/
196
197	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
198	{
199#ifndef DEBUG
200		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
201#endif /* ! DEBUG */
202		exit(EX_UNAVAILABLE);
203	}
204	(void) strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
205	for (p = specialbuf; *p != '\0'; p++)
206		*p |= '\200';
207	(void) strlcat(specialbuf, SPECIALS, sizeof specialbuf);
208
209	/*
210	**  Do a quick sanity check on command line length.
211	*/
212
213	i = strlen(par);
214	if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2))
215	{
216		fprintf(stderr, "%s: command too long: %s\n", prg, par);
217#ifndef DEBUG
218		syslog(LOG_WARNING, "command too long: %.40s", par);
219#endif /* ! DEBUG */
220		exit(EX_UNAVAILABLE);
221	}
222
223	q = par;
224	newcmdbuf[0] = '\0';
225	isexec = FALSE;
226
227	while (*q)
228	{
229		/*
230		**  Strip off a leading pathname on the command name.  For
231		**  example, change /usr/ucb/vacation to vacation.
232		*/
233
234		/* strip leading spaces */
235		while (*q != '\0' && isascii(*q) && isspace(*q))
236			q++;
237		if (*q == '\0')
238		{
239			if (isexec)
240			{
241				fprintf(stderr, "%s: missing command to exec\n",
242					prg);
243#ifndef DEBUG
244				syslog(LOG_CRIT, "uid %d: missing command to exec", getuid());
245#endif /* ! DEBUG */
246				exit(EX_UNAVAILABLE);
247			}
248			break;
249		}
250
251		/* find the end of the command name */
252		p = strpbrk(q, " \t");
253		if (p == NULL)
254			cmd = &q[strlen(q)];
255		else
256		{
257			*p = '\0';
258			cmd = p;
259		}
260		/* search backwards for last / (allow for 0200 bit) */
261		while (cmd > q)
262		{
263			if ((*--cmd & 0177) == '/')
264			{
265				cmd++;
266				break;
267			}
268		}
269		/* cmd now points at final component of path name */
270
271		/* allow a few shell builtins */
272		if (strcmp(q, "exec") == 0 && p != NULL)
273		{
274			addcmd("exec ", FALSE, strlen("exec "));
275			/* test _next_ arg */
276			q = ++p;
277			isexec = TRUE;
278			continue;
279		}
280		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
281		{
282			addcmd(cmd, FALSE, strlen(cmd));
283			/* test following chars */
284		}
285		else
286		{
287			/*
288			**  Check to see if the command name is legal.
289			*/
290			(void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf);
291			(void) strlcat(cmdbuf, "/", sizeof cmdbuf);
292			(void) strlcat(cmdbuf, cmd, sizeof cmdbuf);
293#ifdef DEBUG
294			printf("Trying %s\n", cmdbuf);
295#endif /* DEBUG */
296			if (access(cmdbuf, X_OK) < 0)
297			{
298				/* oops....  crack attack possiblity */
299				fprintf(stderr,
300					"%s: %s not available for sendmail programs\n",
301					prg, cmd);
302				if (p != NULL)
303					*p = ' ';
304#ifndef DEBUG
305				syslog(LOG_CRIT, "uid %d: attempt to use %s",
306				       getuid(), cmd);
307#endif /* ! DEBUG */
308				exit(EX_UNAVAILABLE);
309			}
310
311			/*
312			**  Create the actual shell input.
313			*/
314
315			addcmd(cmd, TRUE, strlen(cmd));
316		}
317		isexec = FALSE;
318
319		if (p != NULL)
320			*p = ' ';
321		else
322			break;
323
324		r = strpbrk(p, specialbuf);
325		if (r == NULL) {
326			addcmd(p, FALSE, strlen(p));
327			break;
328		}
329#if ALLOWSEMI
330		if (*r == ';') {
331			addcmd(p, FALSE,  r - p + 1);
332			q = r + 1;
333			continue;
334		}
335#endif /* ALLOWSEMI */
336		if ((*r == '&' && *(r + 1) == '&') ||
337		    (*r == '|' && *(r + 1) == '|'))
338		{
339			addcmd(p, FALSE,  r - p + 2);
340			q = r + 2;
341			continue;
342		}
343
344		fprintf(stderr, "%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			getuid(), *r, par);
348#endif /* ! DEBUG */
349		exit(EX_UNAVAILABLE);
350	}		/* end of while *q */
351	if (isexec)
352	{
353		fprintf(stderr, "%s: missing command to exec\n", prg);
354#ifndef DEBUG
355		syslog(LOG_CRIT, "uid %d: missing command to exec", getuid());
356#endif /* ! DEBUG */
357		exit(EX_UNAVAILABLE);
358	}
359	/* make sure we created something */
360	if (newcmdbuf[0] == '\0')
361	{
362		fprintf(stderr, "Usage: %s -c command\n", prg);
363#ifndef DEBUG
364		syslog(LOG_ERR, "usage");
365#endif /* ! DEBUG */
366		exit(EX_USAGE);
367	}
368
369	/*
370	**  Now invoke the shell
371	*/
372
373#ifdef DEBUG
374	printf("%s\n", newcmdbuf);
375#endif /* DEBUG */
376	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
377	save_errno = errno;
378#ifndef DEBUG
379	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
380#endif /* ! DEBUG */
381	errno = save_errno;
382	perror("/bin/sh");
383	exit(EX_OSFILE);
384	/* NOTREACHED */
385	return EX_OSFILE;
386}
387