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 #ifndef lint 15 static char copyright[] = 16 "@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\ 17 All rights reserved.\n\ 18 Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\ 19 Copyright (c) 1993\n\ 20 The Regents of the University of California. All rights reserved.\n"; 21 #endif /* ! lint */ 22 23 #ifndef lint 24 static char id[] = "@(#)$Id: smrsh.c,v 8.31.4.9 2001/04/24 04:11:51 ca Exp $"; 25 #endif /* ! lint */ 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 <stdio.h> 59 #include <sys/file.h> 60 #include <string.h> 61 #include <ctype.h> 62 #include <errno.h> 63 #ifdef EX_OK 64 # undef EX_OK 65 #endif /* EX_OK */ 66 #include <sysexits.h> 67 #include <syslog.h> 68 #include <stdlib.h> 69 70 #ifndef TRUE 71 # define TRUE 1 72 # define FALSE 0 73 #endif /* ! TRUE */ 74 75 /* directory in which all commands must reside */ 76 #ifndef CMDDIR 77 # if defined(HPUX10) || defined(HPUX11) || SOLARIS >= 20800 78 # define CMDDIR "/var/adm/sm.bin" 79 # else /* HPUX10 || HPUX11 || SOLARIS >= 20800 */ 80 # define CMDDIR "/usr/adm/sm.bin" 81 # endif /* HPUX10 || HPUX11 || SOLARIS >= 20800 */ 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 # define PATH "/bin:/usr/bin:/usr/ucb" 90 #endif /* ! PATH */ 91 92 #ifndef __P 93 # include "sendmail/cdefs.h" 94 #endif /* ! __P */ 95 96 extern size_t strlcpy __P((char *, const char *, size_t)); 97 extern size_t strlcat __P((char *, const char *, size_t)); 98 99 char newcmdbuf[1000]; 100 char *prg, *par; 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 void 116 addcmd(s, cmd, len) 117 char *s; 118 int cmd; 119 int len; 120 { 121 if (s == NULL || *s == '\0') 122 return; 123 124 if (sizeof newcmdbuf - strlen(newcmdbuf) <= 125 len + (cmd ? (strlen(CMDDIR) + 1) : 0)) 126 { 127 fprintf(stderr, "%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 { 135 (void) strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf); 136 (void) strlcat(newcmdbuf, "/", sizeof newcmdbuf); 137 } 138 (void) strlcat(newcmdbuf, s, sizeof newcmdbuf); 139 } 140 141 int 142 main(argc, argv) 143 int argc; 144 char **argv; 145 { 146 register char *p; 147 register char *q; 148 register char *r; 149 register char *cmd; 150 int i; 151 int isexec; 152 int save_errno; 153 char *newenv[2]; 154 char cmdbuf[1000]; 155 char pathbuf[1000]; 156 char specialbuf[32]; 157 158 #ifndef DEBUG 159 # ifndef LOG_MAIL 160 openlog("smrsh", 0); 161 # else /* ! LOG_MAIL */ 162 openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 163 # endif /* ! LOG_MAIL */ 164 #endif /* ! DEBUG */ 165 166 (void) strlcpy(pathbuf, "PATH=", sizeof pathbuf); 167 (void) strlcat(pathbuf, PATH, sizeof pathbuf); 168 newenv[0] = pathbuf; 169 newenv[1] = NULL; 170 171 /* 172 ** Do basic argv usage checking 173 */ 174 175 prg = argv[0]; 176 177 if (argc != 3 || strcmp(argv[1], "-c") != 0) 178 { 179 fprintf(stderr, "Usage: %s -c command\n", prg); 180 #ifndef DEBUG 181 syslog(LOG_ERR, "usage"); 182 #endif /* ! DEBUG */ 183 exit(EX_USAGE); 184 } 185 186 par = argv[2]; 187 188 /* 189 ** Disallow special shell syntax. This is overly restrictive, 190 ** but it should shut down all attacks. 191 ** Be sure to include 8-bit versions, since many shells strip 192 ** the address to 7 bits before checking. 193 */ 194 195 if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 196 { 197 #ifndef DEBUG 198 syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 199 #endif /* ! DEBUG */ 200 exit(EX_UNAVAILABLE); 201 } 202 (void) strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 203 for (p = specialbuf; *p != '\0'; p++) 204 *p |= '\200'; 205 (void) strlcat(specialbuf, SPECIALS, sizeof specialbuf); 206 207 /* 208 ** Do a quick sanity check on command line length. 209 */ 210 211 i = strlen(par); 212 if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 213 { 214 fprintf(stderr, "%s: command too long: %s\n", prg, par); 215 #ifndef DEBUG 216 syslog(LOG_WARNING, "command too long: %.40s", par); 217 #endif /* ! DEBUG */ 218 exit(EX_UNAVAILABLE); 219 } 220 221 q = par; 222 newcmdbuf[0] = '\0'; 223 isexec = FALSE; 224 225 while (*q) 226 { 227 /* 228 ** Strip off a leading pathname on the command name. For 229 ** example, change /usr/ucb/vacation to vacation. 230 */ 231 232 /* strip leading spaces */ 233 while (*q != '\0' && isascii(*q) && isspace(*q)) 234 q++; 235 if (*q == '\0') 236 { 237 if (isexec) 238 { 239 fprintf(stderr, "%s: missing command to exec\n", 240 prg); 241 #ifndef DEBUG 242 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 243 #endif /* ! DEBUG */ 244 exit(EX_UNAVAILABLE); 245 } 246 break; 247 } 248 249 /* find the end of the command name */ 250 p = strpbrk(q, " \t"); 251 if (p == NULL) 252 cmd = &q[strlen(q)]; 253 else 254 { 255 *p = '\0'; 256 cmd = p; 257 } 258 /* search backwards for last / (allow for 0200 bit) */ 259 while (cmd > q) 260 { 261 if ((*--cmd & 0177) == '/') 262 { 263 cmd++; 264 break; 265 } 266 } 267 /* cmd now points at final component of path name */ 268 269 /* allow a few shell builtins */ 270 if (strcmp(q, "exec") == 0 && p != NULL) 271 { 272 addcmd("exec ", FALSE, strlen("exec ")); 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 /* test following chars */ 282 } 283 else 284 { 285 /* 286 ** Check to see if the command name is legal. 287 */ 288 (void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf); 289 (void) strlcat(cmdbuf, "/", sizeof cmdbuf); 290 (void) strlcat(cmdbuf, cmd, sizeof cmdbuf); 291 #ifdef DEBUG 292 printf("Trying %s\n", cmdbuf); 293 #endif /* DEBUG */ 294 if (access(cmdbuf, X_OK) < 0) 295 { 296 /* oops.... crack attack possiblity */ 297 fprintf(stderr, 298 "%s: %s not available for sendmail programs\n", 299 prg, cmd); 300 if (p != NULL) 301 *p = ' '; 302 #ifndef DEBUG 303 syslog(LOG_CRIT, "uid %d: attempt to use %s", 304 getuid(), cmd); 305 #endif /* ! DEBUG */ 306 exit(EX_UNAVAILABLE); 307 } 308 309 /* 310 ** Create the actual shell input. 311 */ 312 313 addcmd(cmd, TRUE, strlen(cmd)); 314 } 315 isexec = FALSE; 316 317 if (p != NULL) 318 *p = ' '; 319 else 320 break; 321 322 r = strpbrk(p, specialbuf); 323 if (r == NULL) { 324 addcmd(p, FALSE, strlen(p)); 325 break; 326 } 327 #if ALLOWSEMI 328 if (*r == ';') { 329 addcmd(p, FALSE, r - p + 1); 330 q = r + 1; 331 continue; 332 } 333 #endif /* ALLOWSEMI */ 334 if ((*r == '&' && *(r + 1) == '&') || 335 (*r == '|' && *(r + 1) == '|')) 336 { 337 addcmd(p, FALSE, r - p + 2); 338 q = r + 2; 339 continue; 340 } 341 342 fprintf(stderr, "%s: cannot use %c in command\n", prg, *r); 343 #ifndef DEBUG 344 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 345 getuid(), *r, par); 346 #endif /* ! DEBUG */ 347 exit(EX_UNAVAILABLE); 348 } /* end of while *q */ 349 if (isexec) 350 { 351 fprintf(stderr, "%s: missing command to exec\n", prg); 352 #ifndef DEBUG 353 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 354 #endif /* ! DEBUG */ 355 exit(EX_UNAVAILABLE); 356 } 357 /* make sure we created something */ 358 if (newcmdbuf[0] == '\0') 359 { 360 fprintf(stderr, "Usage: %s -c command\n", prg); 361 #ifndef DEBUG 362 syslog(LOG_ERR, "usage"); 363 #endif /* ! DEBUG */ 364 exit(EX_USAGE); 365 } 366 367 /* 368 ** Now invoke the shell 369 */ 370 371 #ifdef DEBUG 372 printf("%s\n", newcmdbuf); 373 #endif /* DEBUG */ 374 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 375 save_errno = errno; 376 #ifndef DEBUG 377 syslog(LOG_CRIT, "Cannot exec /bin/sh: %m"); 378 #endif /* ! DEBUG */ 379 errno = save_errno; 380 perror("/bin/sh"); 381 exit(EX_OSFILE); 382 /* NOTREACHED */ 383 return EX_OSFILE; 384 } 385