xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision 8881d206f4e68b564c2c5f50fc717086fc3e827a)
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 
16 SM_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 
23 SM_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
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
80 #  define CMDDIR	"/usr/adm/sm.bin"
81 # endif
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
92 #  define PATH		"/bin:/usr/bin:/usr/ucb"
93 # endif
94 #endif /* ! PATH */
95 
96 char newcmdbuf[1000];
97 char *prg, *par;
98 
99 static 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 
114 static void
115 addcmd(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
132 		exit(EX_UNAVAILABLE);
133 	}
134 	if (cmd)
135 		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
136 	(void) strncat(newcmdbuf, s, len);
137 }
138 
139 int
140 main(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
159 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
160 # endif
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
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
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
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
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
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
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
323 				exit(EX_UNAVAILABLE);
324 			}
325 			if (!S_ISREG(st.st_mode)
326 #ifdef S_ISLNK
327 			    && !S_ISLNK(st.st_mode)
328 #endif
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
341 				exit(EX_UNAVAILABLE);
342 			}
343 			if (access(cmdbuf, X_OK) < 0)
344 			{
345 				/* oops....  crack attack possibility */
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
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
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
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
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
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
435 	errno = save_errno;
436 	sm_perror("/bin/sh");
437 	exit(EX_OSFILE);
438 	/* NOTREACHED */
439 	return EX_OSFILE;
440 }
441