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