smrsh.c revision 42580
1/*
2 * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3 * Copyright (c) 1993 Eric P. Allman.  All rights reserved.
4 * Copyright (c) 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * By using this file, you agree to the terms and conditions set
8 * forth in the LICENSE file which can be found at the top level of
9 * the sendmail distribution.
10 *
11 */
12
13#ifndef lint
14static char sccsid[] = "@(#)smrsh.c	8.11 (Berkeley) 5/19/1998";
15#endif /* not lint */
16
17/*
18**  SMRSH -- sendmail restricted shell
19**
20**	This is a patch to get around the prog mailer bugs in most
21**	versions of sendmail.
22**
23**	Use this in place of /bin/sh in the "prog" mailer definition
24**	in your sendmail.cf file.  You then create CMDDIR (owned by
25**	root, mode 755) and put links to any programs you want
26**	available to prog mailers in that directory.  This should
27**	include things like "vacation" and "procmail", but not "sed"
28**	or "sh".
29**
30**	Leading pathnames are stripped from program names so that
31**	existing .forward files that reference things like
32**	"/usr/bin/vacation" will continue to work.
33**
34**	The following characters are completely illegal:
35**		<  >  |  ^  ;  &  $  `  (  ) \n \r
36**	This is more restrictive than strictly necessary.
37**
38**	To use this, edit /etc/sendmail.cf, search for ^Mprog, and
39**	change P=/bin/sh to P=/usr/libexec/smrsh, where this compiled
40**	binary is installed /usr/libexec/smrsh.
41**
42**	This can be used on any version of sendmail.
43**
44**	In loving memory of RTM.  11/02/93.
45*/
46
47#include <unistd.h>
48#include <stdio.h>
49#include <sys/file.h>
50#include <string.h>
51#include <ctype.h>
52#ifdef EX_OK
53# undef EX_OK
54#endif
55#include <sysexits.h>
56#include <syslog.h>
57#include <stdlib.h>
58
59/* directory in which all commands must reside */
60#ifndef CMDDIR
61# define CMDDIR		"/usr/libexec/sm.bin"
62#endif
63
64/* characters disallowed in the shell "-c" argument */
65#define SPECIALS	"<|>^();&`$\r\n"
66
67/* default search path */
68#ifndef PATH
69# define PATH		"/bin:/usr/bin"
70#endif
71
72int
73main(argc, argv)
74	int argc;
75	char **argv;
76{
77	register char *p;
78	register char *q;
79	register char *cmd;
80	int i;
81	char *newenv[2];
82	char cmdbuf[1000];
83	char pathbuf[1000];
84
85#ifndef LOG_MAIL
86	openlog("smrsh", 0);
87#else
88	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
89#endif
90
91	strcpy(pathbuf, "PATH=");
92	strcat(pathbuf, PATH);
93	newenv[0] = pathbuf;
94	newenv[1] = NULL;
95
96	/*
97	**  Do basic argv usage checking
98	*/
99
100	if (argc != 3 || strcmp(argv[1], "-c") != 0)
101	{
102		fprintf(stderr, "Usage: %s -c command\n", argv[0]);
103		syslog(LOG_ERR, "usage");
104		exit(EX_USAGE);
105	}
106
107	/*
108	**  Disallow special shell syntax.  This is overly restrictive,
109	**  but it should shut down all attacks.
110	**  Be sure to include 8-bit versions, since many shells strip
111	**  the address to 7 bits before checking.
112	*/
113
114	strcpy(cmdbuf, SPECIALS);
115	for (p = cmdbuf; *p != '\0'; p++)
116		*p |= '\200';
117	strcat(cmdbuf, SPECIALS);
118	p = strpbrk(argv[2], cmdbuf);
119	if (p != NULL)
120	{
121		fprintf(stderr, "%s: cannot use %c in command\n",
122			argv[0], *p);
123		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
124			getuid(), *p, argv[2]);
125		exit(EX_UNAVAILABLE);
126	}
127
128	/*
129	**  Do a quick sanity check on command line length.
130	*/
131
132	i = strlen(argv[2]);
133	if (i > (sizeof cmdbuf - sizeof CMDDIR - 2))
134	{
135		fprintf(stderr, "%s: command too long: %s\n", argv[0], argv[2]);
136		syslog(LOG_WARNING, "command too long: %.40s", argv[2]);
137		exit(EX_UNAVAILABLE);
138	}
139
140	/*
141	**  Strip off a leading pathname on the command name.  For
142	**  example, change /usr/ucb/vacation to vacation.
143	*/
144
145	/* strip leading spaces */
146	for (q = argv[2]; *q != '\0' && isascii(*q) && isspace(*q); )
147		q++;
148
149	/* find the end of the command name */
150	p = strpbrk(q, " \t");
151	if (p == NULL)
152		cmd = &q[strlen(q)];
153	else
154	{
155		*p = '\0';
156		cmd = p;
157	}
158
159	/* search backwards for last / (allow for 0200 bit) */
160	while (cmd > q)
161	{
162		if ((*--cmd & 0177) == '/')
163		{
164			cmd++;
165			break;
166		}
167	}
168
169	/* cmd now points at final component of path name */
170
171	/*
172	**  Check to see if the command name is legal.
173	*/
174
175	(void) strcpy(cmdbuf, CMDDIR);
176	(void) strcat(cmdbuf, "/");
177	(void) strcat(cmdbuf, cmd);
178#ifdef DEBUG
179	printf("Trying %s\n", cmdbuf);
180#endif
181	if (access(cmdbuf, X_OK) < 0)
182	{
183		/* oops....  crack attack possiblity */
184		fprintf(stderr, "%s: %s not available for sendmail programs\n",
185			argv[0], cmd);
186		if (p != NULL)
187			*p = ' ';
188		syslog(LOG_CRIT, "uid %d: attempt to use %s", getuid(), cmd);
189		exit(EX_UNAVAILABLE);
190	}
191	if (p != NULL)
192		*p = ' ';
193
194	/*
195	**  Create the actual shell input.
196	*/
197
198	strcpy(cmdbuf, CMDDIR);
199	strcat(cmdbuf, "/");
200	strcat(cmdbuf, cmd);
201
202	/*
203	**  Now invoke the shell
204	*/
205
206#ifdef DEBUG
207	printf("%s\n", cmdbuf);
208#endif
209	execle("/bin/sh", "/bin/sh", "-c", cmdbuf, NULL, newenv);
210	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
211	perror("/bin/sh");
212	exit(EX_OSFILE);
213}
214