xref: /freebsd/contrib/sendmail/smrsh/smrsh.c (revision 09e8dea79366f1e5b3a73e8a271b26e4b6bf2e6a)
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 2002/05/25 02:41:31 ca 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/limits.h>
60 #include <sm/string.h>
61 #include <sys/file.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 
151 #ifndef DEBUG
152 # ifndef LOG_MAIL
153 	openlog("smrsh", 0);
154 # else /* ! LOG_MAIL */
155 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
156 # endif /* ! LOG_MAIL */
157 #endif /* ! DEBUG */
158 
159 	(void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
160 	newenv[0] = pathbuf;
161 	newenv[1] = NULL;
162 
163 	/*
164 	**  Do basic argv usage checking
165 	*/
166 
167 	prg = argv[0];
168 
169 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
170 	{
171 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
172 				     "Usage: %s -c command\n", prg);
173 #ifndef DEBUG
174 		syslog(LOG_ERR, "usage");
175 #endif /* ! DEBUG */
176 		exit(EX_USAGE);
177 	}
178 
179 	par = argv[2];
180 
181 	/*
182 	**  Disallow special shell syntax.  This is overly restrictive,
183 	**  but it should shut down all attacks.
184 	**  Be sure to include 8-bit versions, since many shells strip
185 	**  the address to 7 bits before checking.
186 	*/
187 
188 	if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
189 	{
190 #ifndef DEBUG
191 		syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
192 #endif /* ! DEBUG */
193 		exit(EX_UNAVAILABLE);
194 	}
195 	(void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
196 	for (p = specialbuf; *p != '\0'; p++)
197 		*p |= '\200';
198 	(void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
199 
200 	/*
201 	**  Do a quick sanity check on command line length.
202 	*/
203 
204 	if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
205 	{
206 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
207 				     "%s: command too long: %s\n", prg, par);
208 #ifndef DEBUG
209 		syslog(LOG_WARNING, "command too long: %.40s", par);
210 #endif /* ! DEBUG */
211 		exit(EX_UNAVAILABLE);
212 	}
213 
214 	q = par;
215 	newcmdbuf[0] = '\0';
216 	isexec = false;
217 
218 	while (*q != '\0')
219 	{
220 		/*
221 		**  Strip off a leading pathname on the command name.  For
222 		**  example, change /usr/ucb/vacation to vacation.
223 		*/
224 
225 		/* strip leading spaces */
226 		while (*q != '\0' && isascii(*q) && isspace(*q))
227 			q++;
228 		if (*q == '\0')
229 		{
230 			if (isexec)
231 			{
232 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
233 						     "%s: missing command to exec\n",
234 						     prg);
235 #ifndef DEBUG
236 				syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
237 #endif /* ! DEBUG */
238 				exit(EX_UNAVAILABLE);
239 			}
240 			break;
241 		}
242 
243 		/* find the end of the command name */
244 		p = strpbrk(q, " \t");
245 		if (p == NULL)
246 			cmd = &q[strlen(q)];
247 		else
248 		{
249 			*p = '\0';
250 			cmd = p;
251 		}
252 		/* search backwards for last / (allow for 0200 bit) */
253 		while (cmd > q)
254 		{
255 			if ((*--cmd & 0177) == '/')
256 			{
257 				cmd++;
258 				break;
259 			}
260 		}
261 		/* cmd now points at final component of path name */
262 
263 		/* allow a few shell builtins */
264 		if (strcmp(q, "exec") == 0 && p != NULL)
265 		{
266 			addcmd("exec ", false, strlen("exec "));
267 
268 			/* test _next_ arg */
269 			q = ++p;
270 			isexec = true;
271 			continue;
272 		}
273 		else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
274 		{
275 			addcmd(cmd, false, strlen(cmd));
276 
277 			/* test following chars */
278 		}
279 		else
280 		{
281 			char cmdbuf[MAXPATHLEN];
282 
283 			/*
284 			**  Check to see if the command name is legal.
285 			*/
286 
287 			if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
288 					"/", cmd) >= sizeof cmdbuf)
289 			{
290 				/* too long */
291 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
292 						     "%s: %s not available for sendmail programs (filename too long)\n",
293 						      prg, cmd);
294 				if (p != NULL)
295 					*p = ' ';
296 #ifndef DEBUG
297 				syslog(LOG_CRIT, "uid %d: attempt to use %s (filename too long)",
298 				       (int) getuid(), cmd);
299 #endif /* ! DEBUG */
300 				exit(EX_UNAVAILABLE);
301 			}
302 
303 #ifdef DEBUG
304 			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
305 					     "Trying %s\n", cmdbuf);
306 #endif /* DEBUG */
307 			if (access(cmdbuf, X_OK) < 0)
308 			{
309 				/* oops....  crack attack possiblity */
310 				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
311 						     "%s: %s not available for sendmail programs\n",
312 						      prg, cmd);
313 				if (p != NULL)
314 					*p = ' ';
315 #ifndef DEBUG
316 				syslog(LOG_CRIT, "uid %d: attempt to use %s",
317 				       (int) getuid(), cmd);
318 #endif /* ! DEBUG */
319 				exit(EX_UNAVAILABLE);
320 			}
321 
322 			/*
323 			**  Create the actual shell input.
324 			*/
325 
326 			addcmd(cmd, true, strlen(cmd));
327 		}
328 		isexec = false;
329 
330 		if (p != NULL)
331 			*p = ' ';
332 		else
333 			break;
334 
335 		r = strpbrk(p, specialbuf);
336 		if (r == NULL)
337 		{
338 			addcmd(p, false, strlen(p));
339 			break;
340 		}
341 #if ALLOWSEMI
342 		if (*r == ';')
343 		{
344 			addcmd(p, false,  r - p + 1);
345 			q = r + 1;
346 			continue;
347 		}
348 #endif /* ALLOWSEMI */
349 		if ((*r == '&' && *(r + 1) == '&') ||
350 		    (*r == '|' && *(r + 1) == '|'))
351 		{
352 			addcmd(p, false,  r - p + 2);
353 			q = r + 2;
354 			continue;
355 		}
356 
357 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
358 				     "%s: cannot use %c in command\n", prg, *r);
359 #ifndef DEBUG
360 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
361 		       (int) getuid(), *r, par);
362 #endif /* ! DEBUG */
363 		exit(EX_UNAVAILABLE);
364 	}
365 	if (isexec)
366 	{
367 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
368 				     "%s: missing command to exec\n", prg);
369 #ifndef DEBUG
370 		syslog(LOG_CRIT, "uid %d: missing command to exec",
371 		       (int) getuid());
372 #endif /* ! DEBUG */
373 		exit(EX_UNAVAILABLE);
374 	}
375 	/* make sure we created something */
376 	if (newcmdbuf[0] == '\0')
377 	{
378 		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
379 				     "Usage: %s -c command\n", prg);
380 #ifndef DEBUG
381 		syslog(LOG_ERR, "usage");
382 #endif /* ! DEBUG */
383 		exit(EX_USAGE);
384 	}
385 
386 	/*
387 	**  Now invoke the shell
388 	*/
389 
390 #ifdef DEBUG
391 	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
392 #endif /* DEBUG */
393 	(void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv);
394 	save_errno = errno;
395 #ifndef DEBUG
396 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
397 #endif /* ! DEBUG */
398 	errno = save_errno;
399 	sm_perror("/bin/sh");
400 	exit(EX_OSFILE);
401 	/* NOTREACHED */
402 	return EX_OSFILE;
403 }
404