1/*
2 * Copyright (c) 1998-2004 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#pragma ident	"%Z%%M%	%I%	%E% SMI"
15
16#include <sm/gen.h>
17
18SM_IDSTR(copyright,
19"@(#) Copyright (c) 1998-2004 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.65 2004/08/06 18:54:22 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 <sys/types.h>
63#include <sys/stat.h>
64#include <string.h>
65#include <ctype.h>
66#include <errno.h>
67#ifdef EX_OK
68# undef EX_OK
69#endif /* EX_OK */
70#include <sysexits.h>
71#include <syslog.h>
72#include <stdlib.h>
73
74#include <sm/conf.h>
75#include <sm/errstring.h>
76
77/* directory in which all commands must reside */
78#ifndef CMDDIR
79# ifdef SMRSH_CMDDIR
80#  define CMDDIR	SMRSH_CMDDIR
81# else /* SMRSH_CMDDIR */
82#  define CMDDIR	"/usr/adm/sm.bin"
83# endif /* SMRSH_CMDDIR */
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# ifdef SMRSH_PATH
92#  define PATH		SMRSH_PATH
93# else /* SMRSH_PATH */
94#  define PATH		"/bin:/usr/bin:/usr/ucb"
95# endif /* SMRSH_PATH */
96#endif /* ! PATH */
97
98char newcmdbuf[1000];
99char *prg, *par;
100
101static void	addcmd __P((char *, bool, size_t));
102
103/*
104**  ADDCMD -- add a string to newcmdbuf, check for overflow
105**
106**    Parameters:
107**	s -- string to add
108**	cmd -- it's a command: prepend CMDDIR/
109**	len -- length of string to add
110**
111**    Side Effects:
112**	changes newcmdbuf or exits with a failure.
113**
114*/
115
116static void
117addcmd(s, cmd, len)
118	char *s;
119	bool cmd;
120	size_t len;
121{
122	if (s == NULL || *s == '\0')
123		return;
124
125	/* enough space for s (len) and CMDDIR + "/" and '\0'? */
126	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
127	    len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
128	{
129		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
130				    "%s: command too long: %s\n", prg, par);
131#ifndef DEBUG
132		syslog(LOG_WARNING, "command too long: %.40s", par);
133#endif /* ! DEBUG */
134		exit(EX_UNAVAILABLE);
135	}
136	if (cmd)
137		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
138	(void) strncat(newcmdbuf, s, len);
139}
140
141int
142main(argc, argv)
143	int argc;
144	char **argv;
145{
146	register char *p;
147	register char *q;
148	register char *r;
149	register char *cmd;
150	int isexec;
151	int save_errno;
152	char *newenv[2];
153	char pathbuf[1000];
154	char specialbuf[32];
155	struct stat st;
156
157#ifndef DEBUG
158# ifndef LOG_MAIL
159	openlog("smrsh", 0);
160# else /* ! LOG_MAIL */
161	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
162# endif /* ! LOG_MAIL */
163#endif /* ! DEBUG */
164
165	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
166	newenv[0] = pathbuf;
167	newenv[1] = NULL;
168
169	/*
170	**  Do basic argv usage checking
171	*/
172
173	prg = argv[0];
174
175	if (argc != 3 || strcmp(argv[1], "-c") != 0)
176	{
177		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
178				     "Usage: %s -c command\n", prg);
179#ifndef DEBUG
180		syslog(LOG_ERR, "usage");
181#endif /* ! DEBUG */
182		exit(EX_USAGE);
183	}
184
185	par = argv[2];
186
187	/*
188	**  Disallow special shell syntax.  This is overly restrictive,
189	**  but it should shut down all attacks.
190	**  Be sure to include 8-bit versions, since many shells strip
191	**  the address to 7 bits before checking.
192	*/
193
194	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
195	{
196#ifndef DEBUG
197		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
198#endif /* ! DEBUG */
199		exit(EX_UNAVAILABLE);
200	}
201	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
202	for (p = specialbuf; *p != '\0'; p++)
203		*p |= '\200';
204	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
205
206	/*
207	**  Do a quick sanity check on command line length.
208	*/
209
210	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
211	{
212		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
213				     "%s: command too long: %s\n", prg, par);
214#ifndef DEBUG
215		syslog(LOG_WARNING, "command too long: %.40s", par);
216#endif /* ! DEBUG */
217		exit(EX_UNAVAILABLE);
218	}
219
220	q = par;
221	newcmdbuf[0] = '\0';
222	isexec = false;
223
224	while (*q != '\0')
225	{
226		/*
227		**  Strip off a leading pathname on the command name.  For
228		**  example, change /usr/ucb/vacation to vacation.
229		*/
230
231		/* strip leading spaces */
232		while (*q != '\0' && isascii(*q) && isspace(*q))
233			q++;
234		if (*q == '\0')
235		{
236			if (isexec)
237			{
238				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
239						     "%s: missing command to exec\n",
240						     prg);
241#ifndef DEBUG
242				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
243#endif /* ! DEBUG */
244				exit(EX_UNAVAILABLE);
245			}
246			break;
247		}
248
249		/* find the end of the command name */
250		p = strpbrk(q, " \t");
251		if (p == NULL)
252			cmd = &q[strlen(q)];
253		else
254		{
255			*p = '\0';
256			cmd = p;
257		}
258		/* search backwards for last / (allow for 0200 bit) */
259		while (cmd > q)
260		{
261			if ((*--cmd & 0177) == '/')
262			{
263				cmd++;
264				break;
265			}
266		}
267		/* cmd now points at final component of path name */
268
269		/* allow a few shell builtins */
270		if (strcmp(q, "exec") == 0 && p != NULL)
271		{
272			addcmd("exec ", false, strlen("exec "));
273
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
283			/* test following chars */
284		}
285		else
286		{
287			char cmdbuf[MAXPATHLEN];
288
289			/*
290			**  Check to see if the command name is legal.
291			*/
292
293			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
294					"/", cmd) >= sizeof cmdbuf)
295			{
296				/* too long */
297				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
298						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
299						      prg, cmd);
300				if (p != NULL)
301					*p = ' ';
302#ifndef DEBUG
303				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
304				       (int) getuid(), cmd);
305#endif /* ! DEBUG */
306				exit(EX_UNAVAILABLE);
307			}
308
309#ifdef DEBUG
310			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
311					     "Trying %s\n", cmdbuf);
312#endif /* DEBUG */
313			if (stat(cmdbuf, &st) < 0)
314			{
315				/* can't stat it */
316				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
317						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
318						      prg, cmd);
319				if (p != NULL)
320					*p = ' ';
321#ifndef DEBUG
322				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
323				       (int) getuid(), cmd);
324#endif /* ! DEBUG */
325				exit(EX_UNAVAILABLE);
326			}
327			if (!S_ISREG(st.st_mode)
328#ifdef S_ISLNK
329			    && !S_ISLNK(st.st_mode)
330#endif /* S_ISLNK */
331			   )
332			{
333				/* can't stat it */
334				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
335						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
336						      prg, cmd);
337				if (p != NULL)
338					*p = ' ';
339#ifndef DEBUG
340				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
341				       (int) getuid(), cmd);
342#endif /* ! DEBUG */
343				exit(EX_UNAVAILABLE);
344			}
345			if (access(cmdbuf, X_OK) < 0)
346			{
347				/* oops....  crack attack possiblity */
348				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
349						     "%s: \"%s\" not available for sendmail programs\n",
350						      prg, cmd);
351				if (p != NULL)
352					*p = ' ';
353#ifndef DEBUG
354				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
355				       (int) getuid(), cmd);
356#endif /* ! DEBUG */
357				exit(EX_UNAVAILABLE);
358			}
359
360			/*
361			**  Create the actual shell input.
362			*/
363
364			addcmd(cmd, true, strlen(cmd));
365		}
366		isexec = false;
367
368		if (p != NULL)
369			*p = ' ';
370		else
371			break;
372
373		r = strpbrk(p, specialbuf);
374		if (r == NULL)
375		{
376			addcmd(p, false, strlen(p));
377			break;
378		}
379#if ALLOWSEMI
380		if (*r == ';')
381		{
382			addcmd(p, false,  r - p + 1);
383			q = r + 1;
384			continue;
385		}
386#endif /* ALLOWSEMI */
387		if ((*r == '&' && *(r + 1) == '&') ||
388		    (*r == '|' && *(r + 1) == '|'))
389		{
390			addcmd(p, false,  r - p + 2);
391			q = r + 2;
392			continue;
393		}
394
395		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
396				     "%s: cannot use %c in command\n", prg, *r);
397#ifndef DEBUG
398		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
399		       (int) getuid(), *r, par);
400#endif /* ! DEBUG */
401		exit(EX_UNAVAILABLE);
402	}
403	if (isexec)
404	{
405		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
406				     "%s: missing command to exec\n", prg);
407#ifndef DEBUG
408		syslog(LOG_CRIT, "uid %d: missing command to exec",
409		       (int) getuid());
410#endif /* ! DEBUG */
411		exit(EX_UNAVAILABLE);
412	}
413	/* make sure we created something */
414	if (newcmdbuf[0] == '\0')
415	{
416		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
417				     "Usage: %s -c command\n", prg);
418#ifndef DEBUG
419		syslog(LOG_ERR, "usage");
420#endif /* ! DEBUG */
421		exit(EX_USAGE);
422	}
423
424	/*
425	**  Now invoke the shell
426	*/
427
428#ifdef DEBUG
429	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
430#endif /* DEBUG */
431	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
432		      (char *)NULL, newenv);
433	save_errno = errno;
434#ifndef DEBUG
435	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
436#endif /* ! DEBUG */
437	errno = save_errno;
438	sm_perror("/bin/sh");
439	exit(EX_OSFILE);
440	/* NOTREACHED */
441	return EX_OSFILE;
442}
443