]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/sendmail/smrsh/smrsh.c
This commit was generated by cvs2svn to compensate for changes in r176892,
[FreeBSD/FreeBSD.git] / contrib / sendmail / 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  * $FreeBSD$
13  */
14
15 #include <sm/gen.h>
16
17 SM_IDSTR(copyright,
18 "@(#) Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.\n\
19         All rights reserved.\n\
20      Copyright (c) 1993 Eric P. Allman.  All rights reserved.\n\
21      Copyright (c) 1993\n\
22         The Regents of the University of California.  All rights reserved.\n")
23
24 SM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.65 2004/08/06 18:54:22 ca Exp $")
25
26 /*
27 **  SMRSH -- sendmail restricted shell
28 **
29 **      This is a patch to get around the prog mailer bugs in most
30 **      versions of sendmail.
31 **
32 **      Use this in place of /bin/sh in the "prog" mailer definition
33 **      in your sendmail.cf file.  You then create CMDDIR (owned by
34 **      root, mode 755) and put links to any programs you want
35 **      available to prog mailers in that directory.  This should
36 **      include things like "vacation" and "procmail", but not "sed"
37 **      or "sh".
38 **
39 **      Leading pathnames are stripped from program names so that
40 **      existing .forward files that reference things like
41 **      "/usr/bin/vacation" will continue to work.
42 **
43 **      The following characters are completely illegal:
44 **              <  >  ^  &  `  (  ) \n \r
45 **      The following characters are sometimes illegal:
46 **              |  &
47 **      This is more restrictive than strictly necessary.
48 **
49 **      To use this, add FEATURE(`smrsh') to your .mc file.
50 **
51 **      This can be used on any version of sendmail.
52 **
53 **      In loving memory of RTM.  11/02/93.
54 */
55
56 #include <unistd.h>
57 #include <sm/io.h>
58 #include <sm/limits.h>
59 #include <sm/string.h>
60 #include <sys/file.h>
61 #include <sys/types.h>
62 #include <sys/stat.h>
63 #include <string.h>
64 #include <ctype.h>
65 #include <errno.h>
66 #ifdef EX_OK
67 # undef EX_OK
68 #endif /* EX_OK */
69 #include <sysexits.h>
70 #include <syslog.h>
71 #include <stdlib.h>
72
73 #include <sm/conf.h>
74 #include <sm/errstring.h>
75
76 /* directory in which all commands must reside */
77 #ifndef CMDDIR
78 # ifdef SMRSH_CMDDIR
79 #  define CMDDIR        SMRSH_CMDDIR
80 # else /* SMRSH_CMDDIR */
81 #  define CMDDIR        "/usr/adm/sm.bin"
82 # endif /* SMRSH_CMDDIR */
83 #endif /* ! CMDDIR */
84
85 /* characters disallowed in the shell "-c" argument */
86 #define SPECIALS        "<|>^();&`$\r\n"
87
88 /* default search path */
89 #ifndef PATH
90 # ifdef SMRSH_PATH
91 #  define PATH          SMRSH_PATH
92 # else /* SMRSH_PATH */
93 #  define PATH          "/bin:/usr/bin:/usr/ucb"
94 # endif /* SMRSH_PATH */
95 #endif /* ! PATH */
96
97 char newcmdbuf[1000];
98 char *prg, *par;
99
100 static void     addcmd __P((char *, bool, size_t));
101
102 /*
103 **  ADDCMD -- add a string to newcmdbuf, check for overflow
104 **
105 **    Parameters:
106 **      s -- string to add
107 **      cmd -- it's a command: prepend CMDDIR/
108 **      len -- length of string to add
109 **
110 **    Side Effects:
111 **      changes newcmdbuf or exits with a failure.
112 **
113 */
114
115 static void
116 addcmd(s, cmd, len)
117         char *s;
118         bool cmd;
119         size_t len;
120 {
121         if (s == NULL || *s == '\0')
122                 return;
123
124         /* enough space for s (len) and CMDDIR + "/" and '\0'? */
125         if (sizeof newcmdbuf - strlen(newcmdbuf) <=
126             len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
127         {
128                 (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
129                                     "%s: command too long: %s\n", prg, par);
130 #ifndef DEBUG
131                 syslog(LOG_WARNING, "command too long: %.40s", par);
132 #endif /* ! DEBUG */
133                 exit(EX_UNAVAILABLE);
134         }
135         if (cmd)
136                 (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
137         (void) strncat(newcmdbuf, s, len);
138 }
139
140 int
141 main(argc, argv)
142         int argc;
143         char **argv;
144 {
145         register char *p;
146         register char *q;
147         register char *r;
148         register char *cmd;
149         int isexec;
150         int save_errno;
151         char *newenv[2];
152         char pathbuf[1000];
153         char specialbuf[32];
154         struct stat st;
155
156 #ifndef DEBUG
157 # ifndef LOG_MAIL
158         openlog("smrsh", 0);
159 # else /* ! LOG_MAIL */
160         openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
161 # endif /* ! LOG_MAIL */
162 #endif /* ! DEBUG */
163
164         (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
165         newenv[0] = pathbuf;
166         newenv[1] = NULL;
167
168         /*
169         **  Do basic argv usage checking
170         */
171
172         prg = argv[0];
173
174         if (argc != 3 || strcmp(argv[1], "-c") != 0)
175         {
176                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
177                                      "Usage: %s -c command\n", prg);
178 #ifndef DEBUG
179                 syslog(LOG_ERR, "usage");
180 #endif /* ! DEBUG */
181                 exit(EX_USAGE);
182         }
183
184         par = argv[2];
185
186         /*
187         **  Disallow special shell syntax.  This is overly restrictive,
188         **  but it should shut down all attacks.
189         **  Be sure to include 8-bit versions, since many shells strip
190         **  the address to 7 bits before checking.
191         */
192
193         if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
194         {
195 #ifndef DEBUG
196                 syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
197 #endif /* ! DEBUG */
198                 exit(EX_UNAVAILABLE);
199         }
200         (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
201         for (p = specialbuf; *p != '\0'; p++)
202                 *p |= '\200';
203         (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
204
205         /*
206         **  Do a quick sanity check on command line length.
207         */
208
209         if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
210         {
211                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
212                                      "%s: command too long: %s\n", prg, par);
213 #ifndef DEBUG
214                 syslog(LOG_WARNING, "command too long: %.40s", par);
215 #endif /* ! DEBUG */
216                 exit(EX_UNAVAILABLE);
217         }
218
219         q = par;
220         newcmdbuf[0] = '\0';
221         isexec = false;
222
223         while (*q != '\0')
224         {
225                 /*
226                 **  Strip off a leading pathname on the command name.  For
227                 **  example, change /usr/ucb/vacation to vacation.
228                 */
229
230                 /* strip leading spaces */
231                 while (*q != '\0' && isascii(*q) && isspace(*q))
232                         q++;
233                 if (*q == '\0')
234                 {
235                         if (isexec)
236                         {
237                                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
238                                                      "%s: missing command to exec\n",
239                                                      prg);
240 #ifndef DEBUG
241                                 syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
242 #endif /* ! DEBUG */
243                                 exit(EX_UNAVAILABLE);
244                         }
245                         break;
246                 }
247
248                 /* find the end of the command name */
249                 p = strpbrk(q, " \t");
250                 if (p == NULL)
251                         cmd = &q[strlen(q)];
252                 else
253                 {
254                         *p = '\0';
255                         cmd = p;
256                 }
257                 /* search backwards for last / (allow for 0200 bit) */
258                 while (cmd > q)
259                 {
260                         if ((*--cmd & 0177) == '/')
261                         {
262                                 cmd++;
263                                 break;
264                         }
265                 }
266                 /* cmd now points at final component of path name */
267
268                 /* allow a few shell builtins */
269                 if (strcmp(q, "exec") == 0 && p != NULL)
270                 {
271                         addcmd("exec ", false, strlen("exec "));
272
273                         /* test _next_ arg */
274                         q = ++p;
275                         isexec = true;
276                         continue;
277                 }
278                 else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
279                 {
280                         addcmd(cmd, false, strlen(cmd));
281
282                         /* test following chars */
283                 }
284                 else
285                 {
286                         char cmdbuf[MAXPATHLEN];
287
288                         /*
289                         **  Check to see if the command name is legal.
290                         */
291
292                         if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
293                                         "/", cmd) >= sizeof cmdbuf)
294                         {
295                                 /* too long */
296                                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
297                                                      "%s: \"%s\" not available for sendmail programs (filename too long)\n",
298                                                       prg, cmd);
299                                 if (p != NULL)
300                                         *p = ' ';
301 #ifndef DEBUG
302                                 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
303                                        (int) getuid(), cmd);
304 #endif /* ! DEBUG */
305                                 exit(EX_UNAVAILABLE);
306                         }
307
308 #ifdef DEBUG
309                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
310                                              "Trying %s\n", cmdbuf);
311 #endif /* DEBUG */
312                         if (stat(cmdbuf, &st) < 0)
313                         {
314                                 /* can't stat it */
315                                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
316                                                      "%s: \"%s\" not available for sendmail programs (stat failed)\n",
317                                                       prg, cmd);
318                                 if (p != NULL)
319                                         *p = ' ';
320 #ifndef DEBUG
321                                 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
322                                        (int) getuid(), cmd);
323 #endif /* ! DEBUG */
324                                 exit(EX_UNAVAILABLE);
325                         }
326                         if (!S_ISREG(st.st_mode)
327 #ifdef S_ISLNK
328                             && !S_ISLNK(st.st_mode)
329 #endif /* S_ISLNK */
330                            )
331                         {
332                                 /* can't stat it */
333                                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
334                                                      "%s: \"%s\" not available for sendmail programs (not a file)\n",
335                                                       prg, cmd);
336                                 if (p != NULL)
337                                         *p = ' ';
338 #ifndef DEBUG
339                                 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
340                                        (int) getuid(), cmd);
341 #endif /* ! DEBUG */
342                                 exit(EX_UNAVAILABLE);
343                         }
344                         if (access(cmdbuf, X_OK) < 0)
345                         {
346                                 /* oops....  crack attack possiblity */
347                                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
348                                                      "%s: \"%s\" not available for sendmail programs\n",
349                                                       prg, cmd);
350                                 if (p != NULL)
351                                         *p = ' ';
352 #ifndef DEBUG
353                                 syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
354                                        (int) getuid(), cmd);
355 #endif /* ! DEBUG */
356                                 exit(EX_UNAVAILABLE);
357                         }
358
359                         /*
360                         **  Create the actual shell input.
361                         */
362
363                         addcmd(cmd, true, strlen(cmd));
364                 }
365                 isexec = false;
366
367                 if (p != NULL)
368                         *p = ' ';
369                 else
370                         break;
371
372                 r = strpbrk(p, specialbuf);
373                 if (r == NULL)
374                 {
375                         addcmd(p, false, strlen(p));
376                         break;
377                 }
378 #if ALLOWSEMI
379                 if (*r == ';')
380                 {
381                         addcmd(p, false,  r - p + 1);
382                         q = r + 1;
383                         continue;
384                 }
385 #endif /* ALLOWSEMI */
386                 if ((*r == '&' && *(r + 1) == '&') ||
387                     (*r == '|' && *(r + 1) == '|'))
388                 {
389                         addcmd(p, false,  r - p + 2);
390                         q = r + 2;
391                         continue;
392                 }
393
394                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
395                                      "%s: cannot use %c in command\n", prg, *r);
396 #ifndef DEBUG
397                 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
398                        (int) getuid(), *r, par);
399 #endif /* ! DEBUG */
400                 exit(EX_UNAVAILABLE);
401         }
402         if (isexec)
403         {
404                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
405                                      "%s: missing command to exec\n", prg);
406 #ifndef DEBUG
407                 syslog(LOG_CRIT, "uid %d: missing command to exec",
408                        (int) getuid());
409 #endif /* ! DEBUG */
410                 exit(EX_UNAVAILABLE);
411         }
412         /* make sure we created something */
413         if (newcmdbuf[0] == '\0')
414         {
415                 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
416                                      "Usage: %s -c command\n", prg);
417 #ifndef DEBUG
418                 syslog(LOG_ERR, "usage");
419 #endif /* ! DEBUG */
420                 exit(EX_USAGE);
421         }
422
423         /*
424         **  Now invoke the shell
425         */
426
427 #ifdef DEBUG
428         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
429 #endif /* DEBUG */
430         (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
431                       (char *)NULL, newenv);
432         save_errno = errno;
433 #ifndef DEBUG
434         syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
435 #endif /* ! DEBUG */
436         errno = save_errno;
437         sm_perror("/bin/sh");
438         exit(EX_OSFILE);
439         /* NOTREACHED */
440         return EX_OSFILE;
441 }