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