xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision eacee0ff7ec955b32e09515246bd97b6edcd2b0f)
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 #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.55 2001/09/11 04:05:22 gshapiro Exp $")
24 
25 /* $FreeBSD$ */
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/string.h>
60 #include <sys/file.h>
61 #include <string.h>
62 #include <ctype.h>
63 #include <errno.h>
64 #ifdef EX_OK
65 # undef EX_OK
66 #endif /* EX_OK */
67 #include <sysexits.h>
68 #include <syslog.h>
69 #include <stdlib.h>
70 
71 #include <sm/conf.h>
72 #include <sm/errstring.h>
73 
74 /* directory in which all commands must reside */
75 #ifndef CMDDIR
76 # ifdef SMRSH_CMDDIR
77 #  define CMDDIR	SMRSH_CMDDIR
78 # else /* SMRSH_CMDDIR */
79 #  define CMDDIR	"/usr/adm/sm.bin"
80 # endif /* SMRSH_CMDDIR */
81 #endif /* ! CMDDIR */
82 
83 /* characters disallowed in the shell "-c" argument */
84 #define SPECIALS	"<|>^();&`$\r\n"
85 
86 /* default search path */
87 #ifndef PATH
88 # ifdef SMRSH_PATH
89 #  define PATH		SMRSH_PATH
90 # else /* SMRSH_PATH */
91 #  define PATH		"/bin:/usr/bin:/usr/ucb"
92 # endif /* SMRSH_PATH */
93 #endif /* ! PATH */
94 
95 char newcmdbuf[1000];
96 char *prg, *par;
97 
98 /*
99 **  ADDCMD -- add a string to newcmdbuf, check for overflow
100 **
101 **    Parameters:
102 **	s -- string to add
103 **	cmd -- it's a command: prepend CMDDIR/
104 **	len -- length of string to add
105 **
106 **    Side Effects:
107 **	changes newcmdbuf or exits with a failure.
108 **
109 */
110 
111 void
112 addcmd(s, cmd, len)
113 	char *s;
114 	bool cmd;
115 	size_t len;
116 {
117 	if (s == NULL || *s == '\0')
118 		return;
119 
120 	if (sizeof newcmdbuf - strlen(newcmdbuf) <=
121 	    len + (cmd ? (strlen(CMDDIR) + 1) : 0))
122 	{
123 		(void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
124 				    "%s: command too long: %s\n", prg, par);
125 #ifndef DEBUG
126 		syslog(LOG_WARNING, "command too long: %.40s", par);
127 #endif /* ! DEBUG */
128 		exit(EX_UNAVAILABLE);
129 	}
130 	if (cmd)
131 	{
132 		(void) sm_strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf);
133 		(void) sm_strlcat(newcmdbuf, "/", sizeof newcmdbuf);
134 	}
135 	(void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf);
136 }
137 
138 int
139 main(argc, argv)
140 	int argc;
141 	char **argv;
142 {
143 	register char *p;
144 	register char *q;
145 	register char *r;
146 	register char *cmd;
147 	int isexec;
148 	int save_errno;
149 	char *newenv[2];
150 	char cmdbuf[1000];
151 	char pathbuf[1000];
152 	char specialbuf[32];
153 
154 #ifndef DEBUG
155 # ifndef LOG_MAIL
156 	openlog("smrsh", 0);
157 # else /* ! LOG_MAIL */
158 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
159 # endif /* ! LOG_MAIL */
160 #endif /* ! DEBUG */
161 
162 	(void) sm_strlcpy(pathbuf, "PATH=", sizeof pathbuf);
163 	(void) sm_strlcat(pathbuf, PATH, sizeof pathbuf);
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* ! DEBUG */
215 		exit(EX_UNAVAILABLE);
216 	}
217 
218 	q = par;
219 	newcmdbuf[0] = '\0';
220 	isexec = false;
221 
222 	while (*q)
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 /* ! DEBUG */
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 			/* test _next_ arg */
272 			q = ++p;
273 			isexec = true;
274 			continue;
275 		}
276 		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
277 		{
278 			addcmd(cmd, false, strlen(cmd));
279 			/* test following chars */
280 		}
281 		else
282 		{
283 			/*
284 			**  Check to see if the command name is legal.
285 			*/
286 			(void) sm_strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf);
287 			(void) sm_strlcat(cmdbuf, "/", sizeof cmdbuf);
288 			(void) sm_strlcat(cmdbuf, cmd, sizeof cmdbuf);
289 #ifdef DEBUG
290 			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
291 					     "Trying %s\n", cmdbuf);
292 #endif /* DEBUG */
293 			if (access(cmdbuf, X_OK) < 0)
294 			{
295 				/* oops....  crack attack possiblity */
296 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
297 						     "%s: %s not available for sendmail programs\n",
298 						      prg, cmd);
299 				if (p != NULL)
300 					*p = ' ';
301 #ifndef DEBUG
302 				syslog(LOG_CRIT, "uid %d: attempt to use %s",
303 				       (int) getuid(), cmd);
304 #endif /* ! DEBUG */
305 				exit(EX_UNAVAILABLE);
306 			}
307 
308 			/*
309 			**  Create the actual shell input.
310 			*/
311 
312 			addcmd(cmd, true, strlen(cmd));
313 		}
314 		isexec = false;
315 
316 		if (p != NULL)
317 			*p = ' ';
318 		else
319 			break;
320 
321 		r = strpbrk(p, specialbuf);
322 		if (r == NULL)
323 		{
324 			addcmd(p, false, strlen(p));
325 			break;
326 		}
327 #if ALLOWSEMI
328 		if (*r == ';')
329 		{
330 			addcmd(p, false,  r - p + 1);
331 			q = r + 1;
332 			continue;
333 		}
334 #endif /* ALLOWSEMI */
335 		if ((*r == '&' && *(r + 1) == '&') ||
336 		    (*r == '|' && *(r + 1) == '|'))
337 		{
338 			addcmd(p, false,  r - p + 2);
339 			q = r + 2;
340 			continue;
341 		}
342 
343 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
344 				     "%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 		       (int) getuid(), *r, par);
348 #endif /* ! DEBUG */
349 		exit(EX_UNAVAILABLE);
350 	}		/* end of while *q */
351 	if (isexec)
352 	{
353 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
354 				     "%s: missing command to exec\n", prg);
355 #ifndef DEBUG
356 		syslog(LOG_CRIT, "uid %d: missing command to exec",
357 		       (int) getuid());
358 #endif /* ! DEBUG */
359 		exit(EX_UNAVAILABLE);
360 	}
361 	/* make sure we created something */
362 	if (newcmdbuf[0] == '\0')
363 	{
364 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
365 				     "Usage: %s -c command\n", prg);
366 #ifndef DEBUG
367 		syslog(LOG_ERR, "usage");
368 #endif /* ! DEBUG */
369 		exit(EX_USAGE);
370 	}
371 
372 	/*
373 	**  Now invoke the shell
374 	*/
375 
376 #ifdef DEBUG
377 	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
378 #endif /* DEBUG */
379 	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
380 	save_errno = errno;
381 #ifndef DEBUG
382 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
383 #endif /* ! DEBUG */
384 	errno = save_errno;
385 	sm_perror("/bin/sh");
386 	exit(EX_OSFILE);
387 	/* NOTREACHED */
388 	return EX_OSFILE;
389 }
390