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