sendmail transition: Do not pre-generate sendmail.cf
[dragonfly.git] / contrib / sendmail-8.14 / smrsh / smrsh.c
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 #include <sm/gen.h>
15
16 SM_IDSTR(copyright,
17 "@(#) Copyright (c) 1998-2004 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.65 2004/08/06 18:54:22 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 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
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 /* ! DEBUG */
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
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 /* ! LOG_MAIL */
159         openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
160 # endif /* ! LOG_MAIL */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* DEBUG */
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 /* ! DEBUG */
323                                 exit(EX_UNAVAILABLE);
324                         }
325                         if (!S_ISREG(st.st_mode)
326 #ifdef S_ISLNK
327                             && !S_ISLNK(st.st_mode)
328 #endif /* S_ISLNK */
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 /* ! DEBUG */
341                                 exit(EX_UNAVAILABLE);
342                         }
343                         if (access(cmdbuf, X_OK) < 0)
344                         {
345                                 /* oops....  crack attack possiblity */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* ! DEBUG */
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 /* DEBUG */
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 /* ! DEBUG */
435         errno = save_errno;
436         sm_perror("/bin/sh");
437         exit(EX_OSFILE);
438         /* NOTREACHED */
439         return EX_OSFILE;
440 }