smrsh.c revision 102533
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 * $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 102533 2002-08-28 18:12:33Z gshapiro $
13 *
14 */
15
16#include <sm/gen.h>
17
18SM_IDSTR(copyright,
19"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
20	All rights reserved.\n\
21     Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
22     Copyright (c) 1993\n\
23	The Regents of the University of California.  All rights reserved.\n")
24
25SM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.58 2002/05/25 02:41:31 ca Exp $")
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/limits.h>
60#include <sm/string.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#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
151#ifndef DEBUG
152# ifndef LOG_MAIL
153	openlog("smrsh", 0);
154# else /* ! LOG_MAIL */
155	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
156# endif /* ! LOG_MAIL */
157#endif /* ! DEBUG */
158
159	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
160	newenv[0] = pathbuf;
161	newenv[1] = NULL;
162
163	/*
164	**  Do basic argv usage checking
165	*/
166
167	prg = argv[0];
168
169	if (argc != 3 || strcmp(argv[1], "-c") != 0)
170	{
171		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
172				     "Usage: %s -c command\n", prg);
173#ifndef DEBUG
174		syslog(LOG_ERR, "usage");
175#endif /* ! DEBUG */
176		exit(EX_USAGE);
177	}
178
179	par = argv[2];
180
181	/*
182	**  Disallow special shell syntax.  This is overly restrictive,
183	**  but it should shut down all attacks.
184	**  Be sure to include 8-bit versions, since many shells strip
185	**  the address to 7 bits before checking.
186	*/
187
188	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
189	{
190#ifndef DEBUG
191		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
192#endif /* ! DEBUG */
193		exit(EX_UNAVAILABLE);
194	}
195	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
196	for (p = specialbuf; *p != '\0'; p++)
197		*p |= '\200';
198	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
199
200	/*
201	**  Do a quick sanity check on command line length.
202	*/
203
204	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
205	{
206		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
207				     "%s: command too long: %s\n", prg, par);
208#ifndef DEBUG
209		syslog(LOG_WARNING, "command too long: %.40s", par);
210#endif /* ! DEBUG */
211		exit(EX_UNAVAILABLE);
212	}
213
214	q = par;
215	newcmdbuf[0] = '\0';
216	isexec = false;
217
218	while (*q != '\0')
219	{
220		/*
221		**  Strip off a leading pathname on the command name.  For
222		**  example, change /usr/ucb/vacation to vacation.
223		*/
224
225		/* strip leading spaces */
226		while (*q != '\0' && isascii(*q) && isspace(*q))
227			q++;
228		if (*q == '\0')
229		{
230			if (isexec)
231			{
232				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
233						     "%s: missing command to exec\n",
234						     prg);
235#ifndef DEBUG
236				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
237#endif /* ! DEBUG */
238				exit(EX_UNAVAILABLE);
239			}
240			break;
241		}
242
243		/* find the end of the command name */
244		p = strpbrk(q, " \t");
245		if (p == NULL)
246			cmd = &q[strlen(q)];
247		else
248		{
249			*p = '\0';
250			cmd = p;
251		}
252		/* search backwards for last / (allow for 0200 bit) */
253		while (cmd > q)
254		{
255			if ((*--cmd & 0177) == '/')
256			{
257				cmd++;
258				break;
259			}
260		}
261		/* cmd now points at final component of path name */
262
263		/* allow a few shell builtins */
264		if (strcmp(q, "exec") == 0 && p != NULL)
265		{
266			addcmd("exec ", false, strlen("exec "));
267
268			/* test _next_ arg */
269			q = ++p;
270			isexec = true;
271			continue;
272		}
273		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
274		{
275			addcmd(cmd, false, strlen(cmd));
276
277			/* test following chars */
278		}
279		else
280		{
281			char cmdbuf[MAXPATHLEN];
282
283			/*
284			**  Check to see if the command name is legal.
285			*/
286
287			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
288					"/", cmd) >= sizeof cmdbuf)
289			{
290				/* too long */
291				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
292						     "%s: %s not available for sendmail programs (filename too long)\n",
293						      prg, cmd);
294				if (p != NULL)
295					*p = ' ';
296#ifndef DEBUG
297				syslog(LOG_CRIT, "uid %d: attempt to use %s (filename too long)",
298				       (int) getuid(), cmd);
299#endif /* ! DEBUG */
300				exit(EX_UNAVAILABLE);
301			}
302
303#ifdef DEBUG
304			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
305					     "Trying %s\n", cmdbuf);
306#endif /* DEBUG */
307			if (access(cmdbuf, X_OK) < 0)
308			{
309				/* oops....  crack attack possiblity */
310				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
311						     "%s: %s not available for sendmail programs\n",
312						      prg, cmd);
313				if (p != NULL)
314					*p = ' ';
315#ifndef DEBUG
316				syslog(LOG_CRIT, "uid %d: attempt to use %s",
317				       (int) getuid(), cmd);
318#endif /* ! DEBUG */
319				exit(EX_UNAVAILABLE);
320			}
321
322			/*
323			**  Create the actual shell input.
324			*/
325
326			addcmd(cmd, true, strlen(cmd));
327		}
328		isexec = false;
329
330		if (p != NULL)
331			*p = ' ';
332		else
333			break;
334
335		r = strpbrk(p, specialbuf);
336		if (r == NULL)
337		{
338			addcmd(p, false, strlen(p));
339			break;
340		}
341#if ALLOWSEMI
342		if (*r == ';')
343		{
344			addcmd(p, false,  r - p + 1);
345			q = r + 1;
346			continue;
347		}
348#endif /* ALLOWSEMI */
349		if ((*r == '&' && *(r + 1) == '&') ||
350		    (*r == '|' && *(r + 1) == '|'))
351		{
352			addcmd(p, false,  r - p + 2);
353			q = r + 2;
354			continue;
355		}
356
357		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
358				     "%s: cannot use %c in command\n", prg, *r);
359#ifndef DEBUG
360		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
361		       (int) getuid(), *r, par);
362#endif /* ! DEBUG */
363		exit(EX_UNAVAILABLE);
364	}
365	if (isexec)
366	{
367		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
368				     "%s: missing command to exec\n", prg);
369#ifndef DEBUG
370		syslog(LOG_CRIT, "uid %d: missing command to exec",
371		       (int) getuid());
372#endif /* ! DEBUG */
373		exit(EX_UNAVAILABLE);
374	}
375	/* make sure we created something */
376	if (newcmdbuf[0] == '\0')
377	{
378		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
379				     "Usage: %s -c command\n", prg);
380#ifndef DEBUG
381		syslog(LOG_ERR, "usage");
382#endif /* ! DEBUG */
383		exit(EX_USAGE);
384	}
385
386	/*
387	**  Now invoke the shell
388	*/
389
390#ifdef DEBUG
391	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
392#endif /* DEBUG */
393	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
394	save_errno = errno;
395#ifndef DEBUG
396	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
397#endif /* ! DEBUG */
398	errno = save_errno;
399	sm_perror("/bin/sh");
400	exit(EX_OSFILE);
401	/* NOTREACHED */
402	return EX_OSFILE;
403}
404