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