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 /* $FreeBSD$ */ 28 29 /* 30 ** SMRSH -- sendmail restricted shell 31 ** 32 ** This is a patch to get around the prog mailer bugs in most 33 ** versions of sendmail. 34 ** 35 ** Use this in place of /bin/sh in the "prog" mailer definition 36 ** in your sendmail.cf file. You then create CMDDIR (owned by 37 ** root, mode 755) and put links to any programs you want 38 ** available to prog mailers in that directory. This should 39 ** include things like "vacation" and "procmail", but not "sed" 40 ** or "sh". 41 ** 42 ** Leading pathnames are stripped from program names so that 43 ** existing .forward files that reference things like 44 ** "/usr/bin/vacation" will continue to work. 45 ** 46 ** The following characters are completely illegal: 47 ** < > ^ & ` ( ) \n \r 48 ** The following characters are sometimes illegal: 49 ** | & 50 ** This is more restrictive than strictly necessary. 51 ** 52 ** To use this, add FEATURE(`smrsh') to your .mc file. 53 ** 54 ** This can be used on any version of sendmail. 55 ** 56 ** In loving memory of RTM. 11/02/93. 57 */ 58 59 #include <unistd.h> 60 #include <stdio.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 #ifndef TRUE 73 # define TRUE 1 74 # define FALSE 0 75 #endif /* ! TRUE */ 76 77 /* directory in which all commands must reside */ 78 #ifndef CMDDIR 79 # if defined(HPUX10) || defined(HPUX11) || SOLARIS >= 20800 80 # define CMDDIR "/var/adm/sm.bin" 81 # else /* HPUX10 || HPUX11 || SOLARIS >= 20800 */ 82 # define CMDDIR "/usr/libexec/sm.bin" 83 # endif /* HPUX10 || HPUX11 || SOLARIS >= 20800 */ 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 # define PATH "/bin:/usr/bin" 92 #endif /* ! PATH */ 93 94 #ifndef __P 95 # include "sendmail/cdefs.h" 96 #endif /* ! __P */ 97 98 extern size_t strlcpy __P((char *, const char *, size_t)); 99 extern size_t strlcat __P((char *, const char *, size_t)); 100 101 char newcmdbuf[1000]; 102 char *prg, *par; 103 104 /* 105 ** ADDCMD -- add a string to newcmdbuf, check for overflow 106 ** 107 ** Parameters: 108 ** s -- string to add 109 ** cmd -- it's a command: prepend CMDDIR/ 110 ** len -- length of string to add 111 ** 112 ** Side Effects: 113 ** changes newcmdbuf or exits with a failure. 114 ** 115 */ 116 117 void 118 addcmd(s, cmd, len) 119 char *s; 120 int cmd; 121 int len; 122 { 123 if (s == NULL || *s == '\0') 124 return; 125 126 if (sizeof newcmdbuf - strlen(newcmdbuf) <= 127 len + (cmd ? (strlen(CMDDIR) + 1) : 0)) 128 { 129 fprintf(stderr, "%s: command too long: %s\n", prg, par); 130 #ifndef DEBUG 131 syslog(LOG_WARNING, "command too long: %.40s", par); 132 #endif /* ! DEBUG */ 133 exit(EX_UNAVAILABLE); 134 } 135 if (cmd) 136 { 137 (void) strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf); 138 (void) strlcat(newcmdbuf, "/", sizeof newcmdbuf); 139 } 140 (void) strlcat(newcmdbuf, s, sizeof newcmdbuf); 141 } 142 143 int 144 main(argc, argv) 145 int argc; 146 char **argv; 147 { 148 register char *p; 149 register char *q; 150 register char *r; 151 register char *cmd; 152 int i; 153 int isexec; 154 int save_errno; 155 char *newenv[2]; 156 char cmdbuf[1000]; 157 char pathbuf[1000]; 158 char specialbuf[32]; 159 160 #ifndef DEBUG 161 # ifndef LOG_MAIL 162 openlog("smrsh", 0); 163 # else /* ! LOG_MAIL */ 164 openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 165 # endif /* ! LOG_MAIL */ 166 #endif /* ! DEBUG */ 167 168 (void) strlcpy(pathbuf, "PATH=", sizeof pathbuf); 169 (void) strlcat(pathbuf, PATH, sizeof pathbuf); 170 newenv[0] = pathbuf; 171 newenv[1] = NULL; 172 173 /* 174 ** Do basic argv usage checking 175 */ 176 177 prg = argv[0]; 178 179 if (argc != 3 || strcmp(argv[1], "-c") != 0) 180 { 181 fprintf(stderr, "Usage: %s -c command\n", prg); 182 #ifndef DEBUG 183 syslog(LOG_ERR, "usage"); 184 #endif /* ! DEBUG */ 185 exit(EX_USAGE); 186 } 187 188 par = argv[2]; 189 190 /* 191 ** Disallow special shell syntax. This is overly restrictive, 192 ** but it should shut down all attacks. 193 ** Be sure to include 8-bit versions, since many shells strip 194 ** the address to 7 bits before checking. 195 */ 196 197 if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 198 { 199 #ifndef DEBUG 200 syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 201 #endif /* ! DEBUG */ 202 exit(EX_UNAVAILABLE); 203 } 204 (void) strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 205 for (p = specialbuf; *p != '\0'; p++) 206 *p |= '\200'; 207 (void) strlcat(specialbuf, SPECIALS, sizeof specialbuf); 208 209 /* 210 ** Do a quick sanity check on command line length. 211 */ 212 213 i = strlen(par); 214 if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 215 { 216 fprintf(stderr, "%s: command too long: %s\n", prg, par); 217 #ifndef DEBUG 218 syslog(LOG_WARNING, "command too long: %.40s", par); 219 #endif /* ! DEBUG */ 220 exit(EX_UNAVAILABLE); 221 } 222 223 q = par; 224 newcmdbuf[0] = '\0'; 225 isexec = FALSE; 226 227 while (*q) 228 { 229 /* 230 ** Strip off a leading pathname on the command name. For 231 ** example, change /usr/ucb/vacation to vacation. 232 */ 233 234 /* strip leading spaces */ 235 while (*q != '\0' && isascii(*q) && isspace(*q)) 236 q++; 237 if (*q == '\0') 238 { 239 if (isexec) 240 { 241 fprintf(stderr, "%s: missing command to exec\n", 242 prg); 243 #ifndef DEBUG 244 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 245 #endif /* ! DEBUG */ 246 exit(EX_UNAVAILABLE); 247 } 248 break; 249 } 250 251 /* find the end of the command name */ 252 p = strpbrk(q, " \t"); 253 if (p == NULL) 254 cmd = &q[strlen(q)]; 255 else 256 { 257 *p = '\0'; 258 cmd = p; 259 } 260 /* search backwards for last / (allow for 0200 bit) */ 261 while (cmd > q) 262 { 263 if ((*--cmd & 0177) == '/') 264 { 265 cmd++; 266 break; 267 } 268 } 269 /* cmd now points at final component of path name */ 270 271 /* allow a few shell builtins */ 272 if (strcmp(q, "exec") == 0 && p != NULL) 273 { 274 addcmd("exec ", FALSE, strlen("exec ")); 275 /* test _next_ arg */ 276 q = ++p; 277 isexec = TRUE; 278 continue; 279 } 280 else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 281 { 282 addcmd(cmd, FALSE, strlen(cmd)); 283 /* test following chars */ 284 } 285 else 286 { 287 /* 288 ** Check to see if the command name is legal. 289 */ 290 (void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf); 291 (void) strlcat(cmdbuf, "/", sizeof cmdbuf); 292 (void) strlcat(cmdbuf, cmd, sizeof cmdbuf); 293 #ifdef DEBUG 294 printf("Trying %s\n", cmdbuf); 295 #endif /* DEBUG */ 296 if (access(cmdbuf, X_OK) < 0) 297 { 298 /* oops.... crack attack possiblity */ 299 fprintf(stderr, 300 "%s: %s not available for sendmail programs\n", 301 prg, cmd); 302 if (p != NULL) 303 *p = ' '; 304 #ifndef DEBUG 305 syslog(LOG_CRIT, "uid %d: attempt to use %s", 306 getuid(), cmd); 307 #endif /* ! DEBUG */ 308 exit(EX_UNAVAILABLE); 309 } 310 311 /* 312 ** Create the actual shell input. 313 */ 314 315 addcmd(cmd, TRUE, strlen(cmd)); 316 } 317 isexec = FALSE; 318 319 if (p != NULL) 320 *p = ' '; 321 else 322 break; 323 324 r = strpbrk(p, specialbuf); 325 if (r == NULL) { 326 addcmd(p, FALSE, strlen(p)); 327 break; 328 } 329 #if ALLOWSEMI 330 if (*r == ';') { 331 addcmd(p, FALSE, r - p + 1); 332 q = r + 1; 333 continue; 334 } 335 #endif /* ALLOWSEMI */ 336 if ((*r == '&' && *(r + 1) == '&') || 337 (*r == '|' && *(r + 1) == '|')) 338 { 339 addcmd(p, FALSE, r - p + 2); 340 q = r + 2; 341 continue; 342 } 343 344 fprintf(stderr, "%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 getuid(), *r, par); 348 #endif /* ! DEBUG */ 349 exit(EX_UNAVAILABLE); 350 } /* end of while *q */ 351 if (isexec) 352 { 353 fprintf(stderr, "%s: missing command to exec\n", prg); 354 #ifndef DEBUG 355 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 356 #endif /* ! DEBUG */ 357 exit(EX_UNAVAILABLE); 358 } 359 /* make sure we created something */ 360 if (newcmdbuf[0] == '\0') 361 { 362 fprintf(stderr, "Usage: %s -c command\n", prg); 363 #ifndef DEBUG 364 syslog(LOG_ERR, "usage"); 365 #endif /* ! DEBUG */ 366 exit(EX_USAGE); 367 } 368 369 /* 370 ** Now invoke the shell 371 */ 372 373 #ifdef DEBUG 374 printf("%s\n", newcmdbuf); 375 #endif /* DEBUG */ 376 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 377 save_errno = errno; 378 #ifndef DEBUG 379 syslog(LOG_CRIT, "Cannot exec /bin/sh: %m"); 380 #endif /* ! DEBUG */ 381 errno = save_errno; 382 perror("/bin/sh"); 383 exit(EX_OSFILE); 384 /* NOTREACHED */ 385 return EX_OSFILE; 386 } 387