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 } 441