smrsh.c revision 71348
1/* 2 * Copyright (c) 1998-2000 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 15static char copyright[] = 16"@(#) Copyright (c) 1998-2000 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 24static char id[] = "@(#)$Id: smrsh.c,v 8.31.4.6 2000/10/09 20:37:16 gshapiro Exp $"; 25#endif /* ! lint */ 26 27/* $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 71348 2001-01-21 22:21:43Z gshapiro $ */ 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 98extern size_t strlcpy __P((char *, const char *, size_t)); 99extern size_t strlcat __P((char *, const char *, size_t)); 100 101char newcmdbuf[1000]; 102char *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 117void 118addcmd(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 143int 144main(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 par = argv[2]; 179 180 if (argc != 3 || strcmp(argv[1], "-c") != 0) 181 { 182 fprintf(stderr, "Usage: %s -c command\n", prg); 183#ifndef DEBUG 184 syslog(LOG_ERR, "usage"); 185#endif /* ! DEBUG */ 186 exit(EX_USAGE); 187 } 188 189 /* 190 ** Disallow special shell syntax. This is overly restrictive, 191 ** but it should shut down all attacks. 192 ** Be sure to include 8-bit versions, since many shells strip 193 ** the address to 7 bits before checking. 194 */ 195 196 if (strlen(SPECIALS) * 2 >= sizeof specialbuf) 197 { 198#ifndef DEBUG 199 syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); 200#endif /* ! DEBUG */ 201 exit(EX_UNAVAILABLE); 202 } 203 (void) strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 204 for (p = specialbuf; *p != '\0'; p++) 205 *p |= '\200'; 206 (void) strlcat(specialbuf, SPECIALS, sizeof specialbuf); 207 208 /* 209 ** Do a quick sanity check on command line length. 210 */ 211 212 i = strlen(par); 213 if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 214 { 215 fprintf(stderr, "%s: command too long: %s\n", prg, par); 216#ifndef DEBUG 217 syslog(LOG_WARNING, "command too long: %.40s", par); 218#endif /* ! DEBUG */ 219 exit(EX_UNAVAILABLE); 220 } 221 222 q = par; 223 newcmdbuf[0] = '\0'; 224 isexec = FALSE; 225 226 while (*q) 227 { 228 /* 229 ** Strip off a leading pathname on the command name. For 230 ** example, change /usr/ucb/vacation to vacation. 231 */ 232 233 /* strip leading spaces */ 234 while (*q != '\0' && isascii(*q) && isspace(*q)) 235 q++; 236 if (*q == '\0') 237 { 238 if (isexec) 239 { 240 fprintf(stderr, "%s: missing command to exec\n", 241 prg); 242#ifndef DEBUG 243 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 244#endif /* ! DEBUG */ 245 exit(EX_UNAVAILABLE); 246 } 247 break; 248 } 249 250 /* find the end of the command name */ 251 p = strpbrk(q, " \t"); 252 if (p == NULL) 253 cmd = &q[strlen(q)]; 254 else 255 { 256 *p = '\0'; 257 cmd = p; 258 } 259 /* search backwards for last / (allow for 0200 bit) */ 260 while (cmd > q) 261 { 262 if ((*--cmd & 0177) == '/') 263 { 264 cmd++; 265 break; 266 } 267 } 268 /* cmd now points at final component of path name */ 269 270 /* allow a few shell builtins */ 271 if (strcmp(q, "exec") == 0 && p != NULL) 272 { 273 addcmd("exec ", FALSE, strlen("exec ")); 274 /* test _next_ arg */ 275 q = ++p; 276 isexec = TRUE; 277 continue; 278 } 279 else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 280 { 281 addcmd(cmd, FALSE, strlen(cmd)); 282 /* test following chars */ 283 } 284 else 285 { 286 /* 287 ** Check to see if the command name is legal. 288 */ 289 (void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf); 290 (void) strlcat(cmdbuf, "/", sizeof cmdbuf); 291 (void) strlcat(cmdbuf, cmd, sizeof cmdbuf); 292#ifdef DEBUG 293 printf("Trying %s\n", cmdbuf); 294#endif /* DEBUG */ 295 if (access(cmdbuf, X_OK) < 0) 296 { 297 /* oops.... crack attack possiblity */ 298 fprintf(stderr, 299 "%s: %s not available for sendmail programs\n", 300 prg, cmd); 301 if (p != NULL) 302 *p = ' '; 303#ifndef DEBUG 304 syslog(LOG_CRIT, "uid %d: attempt to use %s", 305 getuid(), cmd); 306#endif /* ! DEBUG */ 307 exit(EX_UNAVAILABLE); 308 } 309 310 /* 311 ** Create the actual shell input. 312 */ 313 314 addcmd(cmd, TRUE, strlen(cmd)); 315 } 316 isexec = FALSE; 317 318 if (p != NULL) 319 *p = ' '; 320 else 321 break; 322 323 r = strpbrk(p, specialbuf); 324 if (r == NULL) { 325 addcmd(p, FALSE, strlen(p)); 326 break; 327 } 328#if ALLOWSEMI 329 if (*r == ';') { 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 fprintf(stderr, "%s: cannot use %c in command\n", prg, *r); 344#ifndef DEBUG 345 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 346 getuid(), *r, par); 347#endif /* ! DEBUG */ 348 exit(EX_UNAVAILABLE); 349 } /* end of while *q */ 350 if (isexec) 351 { 352 fprintf(stderr, "%s: missing command to exec\n", prg); 353#ifndef DEBUG 354 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 355#endif /* ! DEBUG */ 356 exit(EX_UNAVAILABLE); 357 } 358 /* make sure we created something */ 359 if (newcmdbuf[0] == '\0') 360 { 361 fprintf(stderr, "Usage: %s -c command\n", prg); 362#ifndef DEBUG 363 syslog(LOG_ERR, "usage"); 364#endif /* ! DEBUG */ 365 exit(EX_USAGE); 366 } 367 368 /* 369 ** Now invoke the shell 370 */ 371 372#ifdef DEBUG 373 printf("%s\n", newcmdbuf); 374#endif /* DEBUG */ 375 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 376 save_errno = errno; 377#ifndef DEBUG 378 syslog(LOG_CRIT, "Cannot exec /bin/sh: %m"); 379#endif /* ! DEBUG */ 380 errno = save_errno; 381 perror("/bin/sh"); 382 exit(EX_OSFILE); 383 /* NOTREACHED */ 384 return EX_OSFILE; 385} 386