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