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