xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision f9218d3d4fd34f082473b3a021c6d4d109fb47cf)
1 /*
2  * Copyright (c) 1998-2002 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 #include <sm/gen.h>
15 
16 SM_IDSTR(copyright,
17 "@(#) Copyright (c) 1998-2001 Sendmail, 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.58.2.2 2002/09/24 21:40:05 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 
96 char newcmdbuf[1000];
97 char *prg, *par;
98 
99 /*
100 **  ADDCMD -- add a string to newcmdbuf, check for overflow
101 **
102 **    Parameters:
103 **	s -- string to add
104 **	cmd -- it's a command: prepend CMDDIR/
105 **	len -- length of string to add
106 **
107 **    Side Effects:
108 **	changes newcmdbuf or exits with a failure.
109 **
110 */
111 
112 void
113 addcmd(s, cmd, len)
114 	char *s;
115 	bool cmd;
116 	size_t len;
117 {
118 	if (s == NULL || *s == '\0')
119 		return;
120 
121 	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
122 	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
123 	{
124 		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
125 				    "%s: command too long: %s\n", prg, par);
126 #ifndef DEBUG
127 		syslog(LOG_WARNING, "command too long: %.40s", par);
128 #endif /* ! DEBUG */
129 		exit(EX_UNAVAILABLE);
130 	}
131 	if (cmd)
132 		(void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
133 	(void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf);
134 }
135 
136 int
137 main(argc, argv)
138 	int argc;
139 	char **argv;
140 {
141 	register char *p;
142 	register char *q;
143 	register char *r;
144 	register char *cmd;
145 	int isexec;
146 	int save_errno;
147 	char *newenv[2];
148 	char pathbuf[1000];
149 	char specialbuf[32];
150 	struct stat st;
151 
152 #ifndef DEBUG
153 # ifndef LOG_MAIL
154 	openlog("smrsh", 0);
155 # else /* ! LOG_MAIL */
156 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
157 # endif /* ! LOG_MAIL */
158 #endif /* ! DEBUG */
159 
160 	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
161 	newenv[0] = pathbuf;
162 	newenv[1] = NULL;
163 
164 	/*
165 	**  Do basic argv usage checking
166 	*/
167 
168 	prg = argv[0];
169 
170 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
171 	{
172 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
173 				     "Usage: %s -c command\n", prg);
174 #ifndef DEBUG
175 		syslog(LOG_ERR, "usage");
176 #endif /* ! DEBUG */
177 		exit(EX_USAGE);
178 	}
179 
180 	par = argv[2];
181 
182 	/*
183 	**  Disallow special shell syntax.  This is overly restrictive,
184 	**  but it should shut down all attacks.
185 	**  Be sure to include 8-bit versions, since many shells strip
186 	**  the address to 7 bits before checking.
187 	*/
188 
189 	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
190 	{
191 #ifndef DEBUG
192 		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
193 #endif /* ! DEBUG */
194 		exit(EX_UNAVAILABLE);
195 	}
196 	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
197 	for (p = specialbuf; *p != '\0'; p++)
198 		*p |= '\200';
199 	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
200 
201 	/*
202 	**  Do a quick sanity check on command line length.
203 	*/
204 
205 	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
206 	{
207 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
208 				     "%s: command too long: %s\n", prg, par);
209 #ifndef DEBUG
210 		syslog(LOG_WARNING, "command too long: %.40s", par);
211 #endif /* ! DEBUG */
212 		exit(EX_UNAVAILABLE);
213 	}
214 
215 	q = par;
216 	newcmdbuf[0] = '\0';
217 	isexec = false;
218 
219 	while (*q != '\0')
220 	{
221 		/*
222 		**  Strip off a leading pathname on the command name.  For
223 		**  example, change /usr/ucb/vacation to vacation.
224 		*/
225 
226 		/* strip leading spaces */
227 		while (*q != '\0' && isascii(*q) && isspace(*q))
228 			q++;
229 		if (*q == '\0')
230 		{
231 			if (isexec)
232 			{
233 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
234 						     "%s: missing command to exec\n",
235 						     prg);
236 #ifndef DEBUG
237 				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
238 #endif /* ! DEBUG */
239 				exit(EX_UNAVAILABLE);
240 			}
241 			break;
242 		}
243 
244 		/* find the end of the command name */
245 		p = strpbrk(q, " \t");
246 		if (p == NULL)
247 			cmd = &q[strlen(q)];
248 		else
249 		{
250 			*p = '\0';
251 			cmd = p;
252 		}
253 		/* search backwards for last / (allow for 0200 bit) */
254 		while (cmd > q)
255 		{
256 			if ((*--cmd & 0177) == '/')
257 			{
258 				cmd++;
259 				break;
260 			}
261 		}
262 		/* cmd now points at final component of path name */
263 
264 		/* allow a few shell builtins */
265 		if (strcmp(q, "exec") == 0 && p != NULL)
266 		{
267 			addcmd("exec ", false, strlen("exec "));
268 
269 			/* test _next_ arg */
270 			q = ++p;
271 			isexec = true;
272 			continue;
273 		}
274 		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
275 		{
276 			addcmd(cmd, false, strlen(cmd));
277 
278 			/* test following chars */
279 		}
280 		else
281 		{
282 			char cmdbuf[MAXPATHLEN];
283 
284 			/*
285 			**  Check to see if the command name is legal.
286 			*/
287 
288 			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
289 					"/", cmd) >= sizeof cmdbuf)
290 			{
291 				/* too long */
292 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
293 						     "%s: \"%s\" not available for sendmail programs (filename too long)\n",
294 						      prg, cmd);
295 				if (p != NULL)
296 					*p = ' ';
297 #ifndef DEBUG
298 				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
299 				       (int) getuid(), cmd);
300 #endif /* ! DEBUG */
301 				exit(EX_UNAVAILABLE);
302 			}
303 
304 #ifdef DEBUG
305 			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
306 					     "Trying %s\n", cmdbuf);
307 #endif /* DEBUG */
308 			if (stat(cmdbuf, &st) < 0)
309 			{
310 				/* can't stat it */
311 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
312 						     "%s: \"%s\" not available for sendmail programs (stat failed)\n",
313 						      prg, cmd);
314 				if (p != NULL)
315 					*p = ' ';
316 #ifndef DEBUG
317 				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
318 				       (int) getuid(), cmd);
319 #endif /* ! DEBUG */
320 				exit(EX_UNAVAILABLE);
321 			}
322 			if (!S_ISREG(st.st_mode)
323 #ifdef S_ISLNK
324 			    && !S_ISLNK(st.st_mode)
325 #endif /* S_ISLNK */
326 			   )
327 			{
328 				/* can't stat it */
329 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
330 						     "%s: \"%s\" not available for sendmail programs (not a file)\n",
331 						      prg, cmd);
332 				if (p != NULL)
333 					*p = ' ';
334 #ifndef DEBUG
335 				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
336 				       (int) getuid(), cmd);
337 #endif /* ! DEBUG */
338 				exit(EX_UNAVAILABLE);
339 			}
340 			if (access(cmdbuf, X_OK) < 0)
341 			{
342 				/* oops....  crack attack possiblity */
343 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
344 						     "%s: \"%s\" not available for sendmail programs\n",
345 						      prg, cmd);
346 				if (p != NULL)
347 					*p = ' ';
348 #ifndef DEBUG
349 				syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
350 				       (int) getuid(), cmd);
351 #endif /* ! DEBUG */
352 				exit(EX_UNAVAILABLE);
353 			}
354 
355 			/*
356 			**  Create the actual shell input.
357 			*/
358 
359 			addcmd(cmd, true, strlen(cmd));
360 		}
361 		isexec = false;
362 
363 		if (p != NULL)
364 			*p = ' ';
365 		else
366 			break;
367 
368 		r = strpbrk(p, specialbuf);
369 		if (r == NULL)
370 		{
371 			addcmd(p, false, strlen(p));
372 			break;
373 		}
374 #if ALLOWSEMI
375 		if (*r == ';')
376 		{
377 			addcmd(p, false,  r - p + 1);
378 			q = r + 1;
379 			continue;
380 		}
381 #endif /* ALLOWSEMI */
382 		if ((*r == '&' && *(r + 1) == '&') ||
383 		    (*r == '|' && *(r + 1) == '|'))
384 		{
385 			addcmd(p, false,  r - p + 2);
386 			q = r + 2;
387 			continue;
388 		}
389 
390 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
391 				     "%s: cannot use %c in command\n", prg, *r);
392 #ifndef DEBUG
393 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
394 		       (int) getuid(), *r, par);
395 #endif /* ! DEBUG */
396 		exit(EX_UNAVAILABLE);
397 	}
398 	if (isexec)
399 	{
400 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
401 				     "%s: missing command to exec\n", prg);
402 #ifndef DEBUG
403 		syslog(LOG_CRIT, "uid %d: missing command to exec",
404 		       (int) getuid());
405 #endif /* ! DEBUG */
406 		exit(EX_UNAVAILABLE);
407 	}
408 	/* make sure we created something */
409 	if (newcmdbuf[0] == '\0')
410 	{
411 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
412 				     "Usage: %s -c command\n", prg);
413 #ifndef DEBUG
414 		syslog(LOG_ERR, "usage");
415 #endif /* ! DEBUG */
416 		exit(EX_USAGE);
417 	}
418 
419 	/*
420 	**  Now invoke the shell
421 	*/
422 
423 #ifdef DEBUG
424 	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
425 #endif /* DEBUG */
426 	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
427 	save_errno = errno;
428 #ifndef DEBUG
429 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
430 #endif /* ! DEBUG */
431 	errno = save_errno;
432 	sm_perror("/bin/sh");
433 	exit(EX_OSFILE);
434 	/* NOTREACHED */
435 	return EX_OSFILE;
436 }
437