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
addcmd(s,cmd,len)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
main(argc,argv)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