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