xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision 1b6c76a2fe091c74f08427e6c870851025a9cf67)
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
15 static 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
24 static char id[] = "@(#)$Id: smrsh.c,v 8.31.4.9 2001/04/24 04:11:51 ca Exp $";
25 #endif /* ! lint */
26 
27 /* $FreeBSD$ */
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 
98 extern size_t	strlcpy __P((char *, const char *, size_t));
99 extern size_t	strlcat __P((char *, const char *, size_t));
100 
101 char newcmdbuf[1000];
102 char *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 
117 void
118 addcmd(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 
143 int
144 main(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