Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / contrib / sendmail / smrsh / smrsh.c
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.2.2 2002/09/24 21:40:05 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 /* 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 }