1 /*
2 * Copyright (c) 1998-2004 Proofpoint, 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-2004 Proofpoint, 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.66 2013-11-22 20:52:00 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
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
80 # define CMDDIR "/usr/adm/sm.bin"
81 # endif
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
92 # define PATH "/bin:/usr/bin:/usr/ucb"
93 # endif
94 #endif /* ! PATH */
95
96 char newcmdbuf[1000];
97 char *prg, *par;
98
99 static void addcmd __P((char *, bool, size_t));
100
101 /*
102 ** ADDCMD -- add a string to newcmdbuf, check for overflow
103 **
104 ** Parameters:
105 ** s -- string to add
106 ** cmd -- it's a command: prepend CMDDIR/
107 ** len -- length of string to add
108 **
109 ** Side Effects:
110 ** changes newcmdbuf or exits with a failure.
111 **
112 */
113
114 static void
addcmd(s,cmd,len)115 addcmd(s, cmd, len)
116 char *s;
117 bool cmd;
118 size_t len;
119 {
120 if (s == NULL || *s == '\0')
121 return;
122
123 /* enough space for s (len) and CMDDIR + "/" and '\0'? */
124 if (sizeof newcmdbuf - strlen(newcmdbuf) <=
125 len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
126 {
127 (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
128 "%s: command too long: %s\n", prg, par);
129 #ifndef DEBUG
130 syslog(LOG_WARNING, "command too long: %.40s", par);
131 #endif
132 exit(EX_UNAVAILABLE);
133 }
134 if (cmd)
135 (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
136 (void) strncat(newcmdbuf, s, len);
137 }
138
139 int
main(argc,argv)140 main(argc, argv)
141 int argc;
142 char **argv;
143 {
144 register char *p;
145 register char *q;
146 register char *r;
147 register char *cmd;
148 int isexec;
149 int save_errno;
150 char *newenv[2];
151 char pathbuf[1000];
152 char specialbuf[32];
153 struct stat st;
154
155 #ifndef DEBUG
156 # ifndef LOG_MAIL
157 openlog("smrsh", 0);
158 # else
159 openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
160 # endif
161 #endif /* ! DEBUG */
162
163 (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
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
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
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
215 exit(EX_UNAVAILABLE);
216 }
217
218 q = par;
219 newcmdbuf[0] = '\0';
220 isexec = false;
221
222 while (*q != '\0')
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
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
272 /* test _next_ arg */
273 q = ++p;
274 isexec = true;
275 continue;
276 }
277 else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
278 {
279 addcmd(cmd, false, strlen(cmd));
280
281 /* test following chars */
282 }
283 else
284 {
285 char cmdbuf[MAXPATHLEN];
286
287 /*
288 ** Check to see if the command name is legal.
289 */
290
291 if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
292 "/", cmd) >= sizeof cmdbuf)
293 {
294 /* too long */
295 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
296 "%s: \"%s\" not available for sendmail programs (filename too long)\n",
297 prg, cmd);
298 if (p != NULL)
299 *p = ' ';
300 #ifndef DEBUG
301 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
302 (int) getuid(), cmd);
303 #endif
304 exit(EX_UNAVAILABLE);
305 }
306
307 #ifdef DEBUG
308 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
309 "Trying %s\n", cmdbuf);
310 #endif
311 if (stat(cmdbuf, &st) < 0)
312 {
313 /* can't stat it */
314 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
315 "%s: \"%s\" not available for sendmail programs (stat failed)\n",
316 prg, cmd);
317 if (p != NULL)
318 *p = ' ';
319 #ifndef DEBUG
320 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
321 (int) getuid(), cmd);
322 #endif
323 exit(EX_UNAVAILABLE);
324 }
325 if (!S_ISREG(st.st_mode)
326 #ifdef S_ISLNK
327 && !S_ISLNK(st.st_mode)
328 #endif
329 )
330 {
331 /* can't stat it */
332 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
333 "%s: \"%s\" not available for sendmail programs (not a file)\n",
334 prg, cmd);
335 if (p != NULL)
336 *p = ' ';
337 #ifndef DEBUG
338 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
339 (int) getuid(), cmd);
340 #endif
341 exit(EX_UNAVAILABLE);
342 }
343 if (access(cmdbuf, X_OK) < 0)
344 {
345 /* oops.... crack attack possibility */
346 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
347 "%s: \"%s\" not available for sendmail programs\n",
348 prg, cmd);
349 if (p != NULL)
350 *p = ' ';
351 #ifndef DEBUG
352 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
353 (int) getuid(), cmd);
354 #endif
355 exit(EX_UNAVAILABLE);
356 }
357
358 /*
359 ** Create the actual shell input.
360 */
361
362 addcmd(cmd, true, strlen(cmd));
363 }
364 isexec = false;
365
366 if (p != NULL)
367 *p = ' ';
368 else
369 break;
370
371 r = strpbrk(p, specialbuf);
372 if (r == NULL)
373 {
374 addcmd(p, false, strlen(p));
375 break;
376 }
377 #if ALLOWSEMI
378 if (*r == ';')
379 {
380 addcmd(p, false, r - p + 1);
381 q = r + 1;
382 continue;
383 }
384 #endif /* ALLOWSEMI */
385 if ((*r == '&' && *(r + 1) == '&') ||
386 (*r == '|' && *(r + 1) == '|'))
387 {
388 addcmd(p, false, r - p + 2);
389 q = r + 2;
390 continue;
391 }
392
393 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
394 "%s: cannot use %c in command\n", prg, *r);
395 #ifndef DEBUG
396 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
397 (int) getuid(), *r, par);
398 #endif
399 exit(EX_UNAVAILABLE);
400 }
401 if (isexec)
402 {
403 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
404 "%s: missing command to exec\n", prg);
405 #ifndef DEBUG
406 syslog(LOG_CRIT, "uid %d: missing command to exec",
407 (int) getuid());
408 #endif
409 exit(EX_UNAVAILABLE);
410 }
411 /* make sure we created something */
412 if (newcmdbuf[0] == '\0')
413 {
414 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
415 "Usage: %s -c command\n", prg);
416 #ifndef DEBUG
417 syslog(LOG_ERR, "usage");
418 #endif
419 exit(EX_USAGE);
420 }
421
422 /*
423 ** Now invoke the shell
424 */
425
426 #ifdef DEBUG
427 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
428 #endif
429 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
430 (char *)NULL, newenv);
431 save_errno = errno;
432 #ifndef DEBUG
433 syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
434 #endif
435 errno = save_errno;
436 sm_perror("/bin/sh");
437 exit(EX_OSFILE);
438 /* NOTREACHED */
439 return EX_OSFILE;
440 }
441