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 97char newcmdbuf[1000]; 98char *prg, *par; 99 100/* 101** ADDCMD -- add a string to newcmdbuf, check for overflow 102** 103** Parameters: 104** s -- string to add 105** cmd -- it's a command: prepend CMDDIR/ 106** len -- length of string to add 107** 108** Side Effects: 109** changes newcmdbuf or exits with a failure. 110** 111*/ 112 113void 114addcmd(s, cmd, len) 115 char *s; 116 bool cmd; 117 size_t len; 118{ 119 if (s == NULL || *s == '\0') 120 return; 121 122 /* enough space for s (len) and CMDDIR + "/" and '\0'? */ 123 if (sizeof newcmdbuf - strlen(newcmdbuf) <= 124 len + 1 + (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) strncat(newcmdbuf, s, len); 136} 137 138int 139main(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, 429 (char *)NULL, newenv); 430 save_errno = errno; 431#ifndef DEBUG 432 syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 433#endif /* ! DEBUG */ 434 errno = save_errno; 435 sm_perror("/bin/sh"); 436 exit(EX_OSFILE); 437 /* NOTREACHED */ 438 return EX_OSFILE; 439}
|