1 /* 2 * Copyright (c) 1998-2001 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.55 2001/09/11 04:05:22 gshapiro 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/string.h> 60 #include <sys/file.h> 61 #include <string.h> 62 #include <ctype.h> 63 #include <errno.h> 64 #ifdef EX_OK 65 # undef EX_OK 66 #endif /* EX_OK */ 67 #include <sysexits.h> 68 #include <syslog.h> 69 #include <stdlib.h> 70 71 #include <sm/conf.h> 72 #include <sm/errstring.h> 73 74 /* directory in which all commands must reside */ 75 #ifndef CMDDIR 76 # ifdef SMRSH_CMDDIR 77 # define CMDDIR SMRSH_CMDDIR 78 # else /* SMRSH_CMDDIR */ 79 # define CMDDIR "/usr/adm/sm.bin" 80 # endif /* SMRSH_CMDDIR */ 81 #endif /* ! CMDDIR */ 82 83 /* characters disallowed in the shell "-c" argument */ 84 #define SPECIALS "<|>^();&`$\r\n" 85 86 /* default search path */ 87 #ifndef PATH 88 # ifdef SMRSH_PATH 89 # define PATH SMRSH_PATH 90 # else /* SMRSH_PATH */ 91 # define PATH "/bin:/usr/bin:/usr/ucb" 92 # endif /* SMRSH_PATH */ 93 #endif /* ! PATH */ 94 95 char newcmdbuf[1000]; 96 char *prg, *par; 97 98 /* 99 ** ADDCMD -- add a string to newcmdbuf, check for overflow 100 ** 101 ** Parameters: 102 ** s -- string to add 103 ** cmd -- it's a command: prepend CMDDIR/ 104 ** len -- length of string to add 105 ** 106 ** Side Effects: 107 ** changes newcmdbuf or exits with a failure. 108 ** 109 */ 110 111 void 112 addcmd(s, cmd, len) 113 char *s; 114 bool cmd; 115 size_t len; 116 { 117 if (s == NULL || *s == '\0') 118 return; 119 120 if (sizeof newcmdbuf - strlen(newcmdbuf) <= 121 len + (cmd ? (strlen(CMDDIR) + 1) : 0)) 122 { 123 (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 124 "%s: command too long: %s\n", prg, par); 125 #ifndef DEBUG 126 syslog(LOG_WARNING, "command too long: %.40s", par); 127 #endif /* ! DEBUG */ 128 exit(EX_UNAVAILABLE); 129 } 130 if (cmd) 131 { 132 (void) sm_strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf); 133 (void) sm_strlcat(newcmdbuf, "/", sizeof newcmdbuf); 134 } 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 cmdbuf[1000]; 151 char pathbuf[1000]; 152 char specialbuf[32]; 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_strlcpy(pathbuf, "PATH=", sizeof pathbuf); 163 (void) sm_strlcat(pathbuf, PATH, sizeof pathbuf); 164 newenv[0] = pathbuf; 165 newenv[1] = NULL; 166 167 /* 168 ** Do basic argv usage checking 169 */ 170 171 prg = argv[0]; 172 173 if (argc != 3 || strcmp(argv[1], "-c") != 0) 174 { 175 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 176 "Usage: %s -c command\n", prg); 177 #ifndef DEBUG 178 syslog(LOG_ERR, "usage"); 179 #endif /* ! DEBUG */ 180 exit(EX_USAGE); 181 } 182 183 par = argv[2]; 184 185 /* 186 ** Disallow special shell syntax. This is overly restrictive, 187 ** but it should shut down all attacks. 188 ** Be sure to include 8-bit versions, since many shells strip 189 ** the address to 7 bits before checking. 190 */ 191 192 if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 193 { 194 #ifndef DEBUG 195 syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 196 #endif /* ! DEBUG */ 197 exit(EX_UNAVAILABLE); 198 } 199 (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 200 for (p = specialbuf; *p != '\0'; p++) 201 *p |= '\200'; 202 (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); 203 204 /* 205 ** Do a quick sanity check on command line length. 206 */ 207 208 if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 209 { 210 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 211 "%s: command too long: %s\n", prg, par); 212 #ifndef DEBUG 213 syslog(LOG_WARNING, "command too long: %.40s", par); 214 #endif /* ! DEBUG */ 215 exit(EX_UNAVAILABLE); 216 } 217 218 q = par; 219 newcmdbuf[0] = '\0'; 220 isexec = false; 221 222 while (*q) 223 { 224 /* 225 ** Strip off a leading pathname on the command name. For 226 ** example, change /usr/ucb/vacation to vacation. 227 */ 228 229 /* strip leading spaces */ 230 while (*q != '\0' && isascii(*q) && isspace(*q)) 231 q++; 232 if (*q == '\0') 233 { 234 if (isexec) 235 { 236 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 237 "%s: missing command to exec\n", 238 prg); 239 #ifndef DEBUG 240 syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); 241 #endif /* ! DEBUG */ 242 exit(EX_UNAVAILABLE); 243 } 244 break; 245 } 246 247 /* find the end of the command name */ 248 p = strpbrk(q, " \t"); 249 if (p == NULL) 250 cmd = &q[strlen(q)]; 251 else 252 { 253 *p = '\0'; 254 cmd = p; 255 } 256 /* search backwards for last / (allow for 0200 bit) */ 257 while (cmd > q) 258 { 259 if ((*--cmd & 0177) == '/') 260 { 261 cmd++; 262 break; 263 } 264 } 265 /* cmd now points at final component of path name */ 266 267 /* allow a few shell builtins */ 268 if (strcmp(q, "exec") == 0 && p != NULL) 269 { 270 addcmd("exec ", false, strlen("exec ")); 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 /* test following chars */ 280 } 281 else 282 { 283 /* 284 ** Check to see if the command name is legal. 285 */ 286 (void) sm_strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf); 287 (void) sm_strlcat(cmdbuf, "/", sizeof cmdbuf); 288 (void) sm_strlcat(cmdbuf, cmd, sizeof cmdbuf); 289 #ifdef DEBUG 290 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 291 "Trying %s\n", cmdbuf); 292 #endif /* DEBUG */ 293 if (access(cmdbuf, X_OK) < 0) 294 { 295 /* oops.... crack attack possiblity */ 296 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 297 "%s: %s not available for sendmail programs\n", 298 prg, cmd); 299 if (p != NULL) 300 *p = ' '; 301 #ifndef DEBUG 302 syslog(LOG_CRIT, "uid %d: attempt to use %s", 303 (int) getuid(), cmd); 304 #endif /* ! DEBUG */ 305 exit(EX_UNAVAILABLE); 306 } 307 308 /* 309 ** Create the actual shell input. 310 */ 311 312 addcmd(cmd, true, strlen(cmd)); 313 } 314 isexec = false; 315 316 if (p != NULL) 317 *p = ' '; 318 else 319 break; 320 321 r = strpbrk(p, specialbuf); 322 if (r == NULL) 323 { 324 addcmd(p, false, strlen(p)); 325 break; 326 } 327 #if ALLOWSEMI 328 if (*r == ';') 329 { 330 addcmd(p, false, r - p + 1); 331 q = r + 1; 332 continue; 333 } 334 #endif /* ALLOWSEMI */ 335 if ((*r == '&' && *(r + 1) == '&') || 336 (*r == '|' && *(r + 1) == '|')) 337 { 338 addcmd(p, false, r - p + 2); 339 q = r + 2; 340 continue; 341 } 342 343 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 344 "%s: cannot use %c in command\n", prg, *r); 345 #ifndef DEBUG 346 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 347 (int) getuid(), *r, par); 348 #endif /* ! DEBUG */ 349 exit(EX_UNAVAILABLE); 350 } /* end of while *q */ 351 if (isexec) 352 { 353 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 354 "%s: missing command to exec\n", prg); 355 #ifndef DEBUG 356 syslog(LOG_CRIT, "uid %d: missing command to exec", 357 (int) getuid()); 358 #endif /* ! DEBUG */ 359 exit(EX_UNAVAILABLE); 360 } 361 /* make sure we created something */ 362 if (newcmdbuf[0] == '\0') 363 { 364 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 365 "Usage: %s -c command\n", prg); 366 #ifndef DEBUG 367 syslog(LOG_ERR, "usage"); 368 #endif /* ! DEBUG */ 369 exit(EX_USAGE); 370 } 371 372 /* 373 ** Now invoke the shell 374 */ 375 376 #ifdef DEBUG 377 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf); 378 #endif /* DEBUG */ 379 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 380 save_errno = errno; 381 #ifndef DEBUG 382 syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno)); 383 #endif /* ! DEBUG */ 384 errno = save_errno; 385 sm_perror("/bin/sh"); 386 exit(EX_OSFILE); 387 /* NOTREACHED */ 388 return EX_OSFILE; 389 } 390