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 * $FreeBSD$ 13 * 14 */ 15 16 #include <sm/gen.h> 17 18 SM_IDSTR(copyright, 19 "@(#) Copyright (c) 1998-2001 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.58 2002/05/25 02:41:31 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 /* 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 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 if (sizeof newcmdbuf - strlen(newcmdbuf) <= 124 len + (cmd ? (strlen(CMDDIR) + 1) : 0)) 125 { 126 (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 127 "%s: command too long: %s\n", prg, par); 128 #ifndef DEBUG 129 syslog(LOG_WARNING, "command too long: %.40s", par); 130 #endif /* ! DEBUG */ 131 exit(EX_UNAVAILABLE); 132 } 133 if (cmd) 134 (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf); 135 (void) sm_strlcat(newcmdbuf, s, sizeof newcmdbuf); 136 } 137 138 int 139 main(argc, argv) 140 int argc; 141 char **argv; 142 { 143 register char *p; 144 register char *q; 145 register char *r; 146 register char *cmd; 147 int isexec; 148 int save_errno; 149 char *newenv[2]; 150 char pathbuf[1000]; 151 char specialbuf[32]; 152 struct stat st; 153 154 #ifndef DEBUG 155 # ifndef LOG_MAIL 156 openlog("smrsh", 0); 157 # else /* ! LOG_MAIL */ 158 openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 159 # endif /* ! LOG_MAIL */ 160 #endif /* ! DEBUG */ 161 162 (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH); 163 newenv[0] = pathbuf; 164 newenv[1] = NULL; 165 166 /* 167 ** Do basic argv usage checking 168 */ 169 170 prg = argv[0]; 171 172 if (argc != 3 || strcmp(argv[1], "-c") != 0) 173 { 174 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 175 "Usage: %s -c command\n", prg); 176 #ifndef DEBUG 177 syslog(LOG_ERR, "usage"); 178 #endif /* ! DEBUG */ 179 exit(EX_USAGE); 180 } 181 182 par = argv[2]; 183 184 /* 185 ** Disallow special shell syntax. This is overly restrictive, 186 ** but it should shut down all attacks. 187 ** Be sure to include 8-bit versions, since many shells strip 188 ** the address to 7 bits before checking. 189 */ 190 191 if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 192 { 193 #ifndef DEBUG 194 syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 195 #endif /* ! DEBUG */ 196 exit(EX_UNAVAILABLE); 197 } 198 (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 199 for (p = specialbuf; *p != '\0'; p++) 200 *p |= '\200'; 201 (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); 202 203 /* 204 ** Do a quick sanity check on command line length. 205 */ 206 207 if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 208 { 209 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 210 "%s: command too long: %s\n", prg, par); 211 #ifndef DEBUG 212 syslog(LOG_WARNING, "command too long: %.40s", par); 213 #endif /* ! DEBUG */ 214 exit(EX_UNAVAILABLE); 215 } 216 217 q = par; 218 newcmdbuf[0] = '\0'; 219 isexec = false; 220 221 while (*q != '\0') 222 { 223 /* 224 ** Strip off a leading pathname on the command name. For 225 ** example, change /usr/ucb/vacation to vacation. 226 */ 227 228 /* strip leading spaces */ 229 while (*q != '\0' && isascii(*q) && isspace(*q)) 230 q++; 231 if (*q == '\0') 232 { 233 if (isexec) 234 { 235 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 236 "%s: missing command to exec\n", 237 prg); 238 #ifndef DEBUG 239 syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); 240 #endif /* ! DEBUG */ 241 exit(EX_UNAVAILABLE); 242 } 243 break; 244 } 245 246 /* find the end of the command name */ 247 p = strpbrk(q, " \t"); 248 if (p == NULL) 249 cmd = &q[strlen(q)]; 250 else 251 { 252 *p = '\0'; 253 cmd = p; 254 } 255 /* search backwards for last / (allow for 0200 bit) */ 256 while (cmd > q) 257 { 258 if ((*--cmd & 0177) == '/') 259 { 260 cmd++; 261 break; 262 } 263 } 264 /* cmd now points at final component of path name */ 265 266 /* allow a few shell builtins */ 267 if (strcmp(q, "exec") == 0 && p != NULL) 268 { 269 addcmd("exec ", false, strlen("exec ")); 270 271 /* test _next_ arg */ 272 q = ++p; 273 isexec = true; 274 continue; 275 } 276 else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 277 { 278 addcmd(cmd, false, strlen(cmd)); 279 280 /* test following chars */ 281 } 282 else 283 { 284 char cmdbuf[MAXPATHLEN]; 285 286 /* 287 ** Check to see if the command name is legal. 288 */ 289 290 if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR, 291 "/", cmd) >= sizeof cmdbuf) 292 { 293 /* too long */ 294 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 295 "%s: %s not available for sendmail programs (filename too long)\n", 296 prg, cmd); 297 if (p != NULL) 298 *p = ' '; 299 #ifndef DEBUG 300 syslog(LOG_CRIT, "uid %d: attempt to use %s (filename too long)", 301 (int) getuid(), cmd); 302 #endif /* ! DEBUG */ 303 exit(EX_UNAVAILABLE); 304 } 305 306 #ifdef DEBUG 307 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 308 "Trying %s\n", cmdbuf); 309 #endif /* DEBUG */ 310 if (stat(cmdbuf, &st) < 0) 311 { 312 /* can't stat it */ 313 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 314 "%s: %s not available for sendmail programs (stat failed)\n", 315 prg, cmd); 316 if (p != NULL) 317 *p = ' '; 318 #ifndef DEBUG 319 syslog(LOG_CRIT, "uid %d: attempt to use %s (stat failed)", 320 (int) getuid(), cmd); 321 #endif /* ! DEBUG */ 322 exit(EX_UNAVAILABLE); 323 } 324 if (!S_ISREG(st.st_mode) 325 #ifdef S_ISLNK 326 && !S_ISLNK(st.st_mode) 327 #endif /* S_ISLNK */ 328 ) 329 { 330 /* can't stat it */ 331 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 332 "%s: %s not available for sendmail programs (not a file)\n", 333 prg, cmd); 334 if (p != NULL) 335 *p = ' '; 336 #ifndef DEBUG 337 syslog(LOG_CRIT, "uid %d: attempt to use %s (not a file)", 338 (int) getuid(), cmd); 339 #endif /* ! DEBUG */ 340 exit(EX_UNAVAILABLE); 341 } 342 if (access(cmdbuf, X_OK) < 0) 343 { 344 /* oops.... crack attack possiblity */ 345 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 346 "%s: %s not available for sendmail programs\n", 347 prg, cmd); 348 if (p != NULL) 349 *p = ' '; 350 #ifndef DEBUG 351 syslog(LOG_CRIT, "uid %d: attempt to use %s", 352 (int) getuid(), cmd); 353 #endif /* ! DEBUG */ 354 exit(EX_UNAVAILABLE); 355 } 356 357 /* 358 ** Create the actual shell input. 359 */ 360 361 addcmd(cmd, true, strlen(cmd)); 362 } 363 isexec = false; 364 365 if (p != NULL) 366 *p = ' '; 367 else 368 break; 369 370 r = strpbrk(p, specialbuf); 371 if (r == NULL) 372 { 373 addcmd(p, false, strlen(p)); 374 break; 375 } 376 #if ALLOWSEMI 377 if (*r == ';') 378 { 379 addcmd(p, false, r - p + 1); 380 q = r + 1; 381 continue; 382 } 383 #endif /* ALLOWSEMI */ 384 if ((*r == '&' && *(r + 1) == '&') || 385 (*r == '|' && *(r + 1) == '|')) 386 { 387 addcmd(p, false, r - p + 2); 388 q = r + 2; 389 continue; 390 } 391 392 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 393 "%s: cannot use %c in command\n", prg, *r); 394 #ifndef DEBUG 395 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 396 (int) getuid(), *r, par); 397 #endif /* ! DEBUG */ 398 exit(EX_UNAVAILABLE); 399 } 400 if (isexec) 401 { 402 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 403 "%s: missing command to exec\n", prg); 404 #ifndef DEBUG 405 syslog(LOG_CRIT, "uid %d: missing command to exec", 406 (int) getuid()); 407 #endif /* ! DEBUG */ 408 exit(EX_UNAVAILABLE); 409 } 410 /* make sure we created something */ 411 if (newcmdbuf[0] == '\0') 412 { 413 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 414 "Usage: %s -c command\n", prg); 415 #ifndef DEBUG 416 syslog(LOG_ERR, "usage"); 417 #endif /* ! DEBUG */ 418 exit(EX_USAGE); 419 } 420 421 /* 422 ** Now invoke the shell 423 */ 424 425 #ifdef DEBUG 426 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf); 427 #endif /* DEBUG */ 428 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 429 save_errno = errno; 430 #ifndef DEBUG 431 syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 432 #endif /* ! DEBUG */ 433 errno = save_errno; 434 sm_perror("/bin/sh"); 435 exit(EX_OSFILE); 436 /* NOTREACHED */ 437 return EX_OSFILE; 438 } 439