1/*
2 * Copyright (c) 1998-2004 Proofpoint, 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#include <sm/gen.h>
15
16SM_IDSTR(copyright,
17"@(#) Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.\n\
18	All rights reserved.\n\
19     Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
20     Copyright (c) 1993\n\
21	The Regents of the University of California.  All rights reserved.\n")
22
23SM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.66 2013-11-22 20:52:00 ca Exp $")
24
25/*
26**  SMRSH -- sendmail restricted shell
27**
28**	This is a patch to get around the prog mailer bugs in most
29**	versions of sendmail.
30**
31**	Use this in place of /bin/sh in the "prog" mailer definition
32**	in your sendmail.cf file.  You then create CMDDIR (owned by
33**	root, mode 755) and put links to any programs you want
34**	available to prog mailers in that directory.  This should
35**	include things like "vacation" and "procmail", but not "sed"
36**	or "sh".
37**
38**	Leading pathnames are stripped from program names so that
39**	existing .forward files that reference things like
40**	"/usr/bin/vacation" will continue to work.
41**
42**	The following characters are completely illegal:
43**		<  >  ^  &  `  (  ) \n \r
44**	The following characters are sometimes illegal:
45**		|  &
46**	This is more restrictive than strictly necessary.
47**
48**	To use this, add FEATURE(`smrsh') to your .mc file.
49**
50**	This can be used on any version of sendmail.
51**
52**	In loving memory of RTM.  11/02/93.
53*/
54
55#include <unistd.h>
56#include <sm/io.h>
57#include <sm/limits.h>
58#include <sm/string.h>
59#include <sys/file.h>
60#include <sys/types.h>
61#include <sys/stat.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
99static void	addcmd __P((char *, bool, size_t));
100
101/*
102**  ADDCMD -- add a string to newcmdbuf, check for overflow
103**
104**    Parameters:
105**	s -- string to add
106**	cmd -- it's a command: prepend CMDDIR/
107**	len -- length of string to add
108**
109**    Side Effects:
110**	changes newcmdbuf or exits with a failure.
111**
112*/
113
114static void
115addcmd(s, cmd, len)
116	char *s;
117	bool cmd;
118	size_t len;
119{
120	if (s == NULL || *s == '\0')
121		return;
122
123	/* enough space for s (len) and CMDDIR + "/" and '\0'? */
124	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
125	    len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
126	{
127		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
128				    "%s: command too long: %s\n", prg, par);
129#ifndef DEBUG
130		syslog(LOG_WARNING, "command too long: %.40s", par);
131#endif /* ! DEBUG */
132		exit(EX_UNAVAILABLE);
133	}
134	if (cmd)
135		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
136	(void) strncat(newcmdbuf, s, len);
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 isexec;
149	int save_errno;
150	char *newenv[2];
151	char pathbuf[1000];
152	char specialbuf[32];
153	struct stat st;
154
155#ifndef DEBUG
156# ifndef LOG_MAIL
157	openlog("smrsh", 0);
158# else /* ! LOG_MAIL */
159	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
160# endif /* ! LOG_MAIL */
161#endif /* ! DEBUG */
162
163	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
164	newenv[0] = pathbuf;
165	newenv[1] = NULL;
166
167	/*
168	**  Do basic argv usage checking
169	*/
170
171	prg = argv[0];
172
173	if (argc != 3 || strcmp(argv[1], "-c") != 0)
174	{
175		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
176				     "Usage: %s -c command\n", prg);
177#ifndef DEBUG
178		syslog(LOG_ERR, "usage");
179#endif /* ! DEBUG */
180		exit(EX_USAGE);
181	}
182
183	par = argv[2];
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) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
200	for (p = specialbuf; *p != '\0'; p++)
201		*p |= '\200';
202	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
203
204	/*
205	**  Do a quick sanity check on command line length.
206	*/
207
208	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
209	{
210		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
211				     "%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 != '\0')
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				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
237						     "%s: missing command to exec\n",
238						     prg);
239#ifndef DEBUG
240				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
241#endif /* ! DEBUG */
242				exit(EX_UNAVAILABLE);
243			}
244			break;
245		}
246
247		/* find the end of the command name */
248		p = strpbrk(q, " \t");
249		if (p == NULL)
250			cmd = &q[strlen(q)];
251		else
252		{
253			*p = '\0';
254			cmd = p;
255		}
256		/* search backwards for last / (allow for 0200 bit) */
257		while (cmd > q)
258		{
259			if ((*--cmd & 0177) == '/')
260			{
261				cmd++;
262				break;
263			}
264		}
265		/* cmd now points at final component of path name */
266
267		/* allow a few shell builtins */
268		if (strcmp(q, "exec") == 0 && p != NULL)
269		{
270			addcmd("exec ", false, strlen("exec "));
271
272			/* test _next_ arg */
273			q = ++p;
274			isexec = true;
275			continue;
276		}
277		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
278		{
279			addcmd(cmd, false, strlen(cmd));
280
281			/* test following chars */
282		}
283		else
284		{
285			char cmdbuf[MAXPATHLEN];
286
287			/*
288			**  Check to see if the command name is legal.
289			*/
290
291			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
292					"/", cmd) >= sizeof cmdbuf)
293			{
294				/* too long */
295				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
296						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
297						      prg, cmd);
298				if (p != NULL)
299					*p = ' ';
300#ifndef DEBUG
301				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
302				       (int) getuid(), cmd);
303#endif /* ! DEBUG */
304				exit(EX_UNAVAILABLE);
305			}
306
307#ifdef DEBUG
308			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
309					     "Trying %s\n", cmdbuf);
310#endif /* DEBUG */
311			if (stat(cmdbuf, &st) < 0)
312			{
313				/* can't stat it */
314				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
315						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
316						      prg, cmd);
317				if (p != NULL)
318					*p = ' ';
319#ifndef DEBUG
320				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
321				       (int) getuid(), cmd);
322#endif /* ! DEBUG */
323				exit(EX_UNAVAILABLE);
324			}
325			if (!S_ISREG(st.st_mode)
326#ifdef S_ISLNK
327			    && !S_ISLNK(st.st_mode)
328#endif /* S_ISLNK */
329			   )
330			{
331				/* can't stat it */
332				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
333						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
334						      prg, cmd);
335				if (p != NULL)
336					*p = ' ';
337#ifndef DEBUG
338				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
339				       (int) getuid(), cmd);
340#endif /* ! DEBUG */
341				exit(EX_UNAVAILABLE);
342			}
343			if (access(cmdbuf, X_OK) < 0)
344			{
345				/* oops....  crack attack possiblity */
346				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
347						     "%s: \"%s\" not available for sendmail programs\n",
348						      prg, cmd);
349				if (p != NULL)
350					*p = ' ';
351#ifndef DEBUG
352				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
353				       (int) getuid(), cmd);
354#endif /* ! DEBUG */
355				exit(EX_UNAVAILABLE);
356			}
357
358			/*
359			**  Create the actual shell input.
360			*/
361
362			addcmd(cmd, true, strlen(cmd));
363		}
364		isexec = false;
365
366		if (p != NULL)
367			*p = ' ';
368		else
369			break;
370
371		r = strpbrk(p, specialbuf);
372		if (r == NULL)
373		{
374			addcmd(p, false, strlen(p));
375			break;
376		}
377#if ALLOWSEMI
378		if (*r == ';')
379		{
380			addcmd(p, false,  r - p + 1);
381			q = r + 1;
382			continue;
383		}
384#endif /* ALLOWSEMI */
385		if ((*r == '&' && *(r + 1) == '&') ||
386		    (*r == '|' && *(r + 1) == '|'))
387		{
388			addcmd(p, false,  r - p + 2);
389			q = r + 2;
390			continue;
391		}
392
393		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
394				     "%s: cannot use %c in command\n", prg, *r);
395#ifndef DEBUG
396		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
397		       (int) getuid(), *r, par);
398#endif /* ! DEBUG */
399		exit(EX_UNAVAILABLE);
400	}
401	if (isexec)
402	{
403		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
404				     "%s: missing command to exec\n", prg);
405#ifndef DEBUG
406		syslog(LOG_CRIT, "uid %d: missing command to exec",
407		       (int) getuid());
408#endif /* ! DEBUG */
409		exit(EX_UNAVAILABLE);
410	}
411	/* make sure we created something */
412	if (newcmdbuf[0] == '\0')
413	{
414		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
415				     "Usage: %s -c command\n", prg);
416#ifndef DEBUG
417		syslog(LOG_ERR, "usage");
418#endif /* ! DEBUG */
419		exit(EX_USAGE);
420	}
421
422	/*
423	**  Now invoke the shell
424	*/
425
426#ifdef DEBUG
427	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
428#endif /* DEBUG */
429	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
430		      (char *)NULL, newenv);
431	save_errno = errno;
432#ifndef DEBUG
433	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
434#endif /* ! DEBUG */
435	errno = save_errno;
436	sm_perror("/bin/sh");
437	exit(EX_OSFILE);
438	/* NOTREACHED */
439	return EX_OSFILE;
440}
441