smrsh.c revision 64565
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, 1999 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.4 2000/05/25 21:44:29 gshapiro Exp $"; 25#endif /* ! lint */ 26 27/* $FreeBSD: head/contrib/sendmail/smrsh/smrsh.c 64565 2000-08-12 22:19:16Z 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# define CMDDIR "/usr/libexec/sm.bin" 80#endif /* ! CMDDIR */ 81 82/* characters disallowed in the shell "-c" argument */ 83#define SPECIALS "<|>^();&`$\r\n" 84 85/* default search path */ 86#ifndef PATH 87# define PATH "/bin:/usr/bin" 88#endif /* ! PATH */ 89 90#ifndef __P 91# include "sendmail/cdefs.h" 92#endif /* ! __P */ 93 94extern size_t strlcpy __P((char *, const char *, size_t)); 95extern size_t strlcat __P((char *, const char *, size_t)); 96 97char newcmdbuf[1000]; 98char *prg, *par; 99 100/* 101** ADDCMD -- add a string to newcmdbuf, check for overflow 102** 103** Parameters: 104** s -- string to add 105** cmd -- it's a command: prepend CMDDIR/ 106** len -- length of string to add 107** 108** Side Effects: 109** changes newcmdbuf or exits with a failure. 110** 111*/ 112 113void 114addcmd(s, cmd, len) 115 char *s; 116 int cmd; 117 int len; 118{ 119 if (s == NULL || *s == '\0') 120 return; 121 122 if (sizeof newcmdbuf - strlen(newcmdbuf) <= 123 len + (cmd ? (strlen(CMDDIR) + 1) : 0)) 124 { 125 fprintf(stderr, "%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 { 133 (void) strlcat(newcmdbuf, CMDDIR, sizeof newcmdbuf); 134 (void) strlcat(newcmdbuf, "/", sizeof newcmdbuf); 135 } 136 (void) strlcat(newcmdbuf, s, sizeof newcmdbuf); 137} 138 139int 140main(argc, argv) 141 int argc; 142 char **argv; 143{ 144 register char *p; 145 register char *q; 146 register char *r; 147 register char *cmd; 148 int i; 149 int isexec; 150 int save_errno; 151 char *newenv[2]; 152 char cmdbuf[1000]; 153 char pathbuf[1000]; 154 char specialbuf[32]; 155 156#ifndef DEBUG 157# ifndef LOG_MAIL 158 openlog("smrsh", 0); 159# else /* ! LOG_MAIL */ 160 openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); 161# endif /* ! LOG_MAIL */ 162#endif /* ! DEBUG */ 163 164 (void) strlcpy(pathbuf, "PATH=", sizeof pathbuf); 165 (void) strlcat(pathbuf, PATH, sizeof pathbuf); 166 newenv[0] = pathbuf; 167 newenv[1] = NULL; 168 169 /* 170 ** Do basic argv usage checking 171 */ 172 173 prg = argv[0]; 174 par = argv[2]; 175 176 if (argc != 3 || strcmp(argv[1], "-c") != 0) 177 { 178 fprintf(stderr, "Usage: %s -c command\n", prg); 179#ifndef DEBUG 180 syslog(LOG_ERR, "usage"); 181#endif /* ! DEBUG */ 182 exit(EX_USAGE); 183 } 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) strlcpy(specialbuf, SPECIALS, sizeof specialbuf); 200 for (p = specialbuf; *p != '\0'; p++) 201 *p |= '\200'; 202 (void) strlcat(specialbuf, SPECIALS, sizeof specialbuf); 203 204 /* 205 ** Do a quick sanity check on command line length. 206 */ 207 208 i = strlen(par); 209 if (i > (sizeof newcmdbuf - sizeof CMDDIR - 2)) 210 { 211 fprintf(stderr, "%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 fprintf(stderr, "%s: missing command to exec\n", 237 prg); 238#ifndef DEBUG 239 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 240#endif /* ! DEBUG */ 241 exit(EX_UNAVAILABLE); 242 } 243 break; 244 } 245 246 /* find the end of the command name */ 247 p = strpbrk(q, " \t"); 248 if (p == NULL) 249 cmd = &q[strlen(q)]; 250 else 251 { 252 *p = '\0'; 253 cmd = p; 254 } 255 /* search backwards for last / (allow for 0200 bit) */ 256 while (cmd > q) 257 { 258 if ((*--cmd & 0177) == '/') 259 { 260 cmd++; 261 break; 262 } 263 } 264 /* cmd now points at final component of path name */ 265 266 /* allow a few shell builtins */ 267 if (strcmp(q, "exec") == 0 && p != NULL) 268 { 269 addcmd("exec ", FALSE, strlen("exec ")); 270 /* test _next_ arg */ 271 q = ++p; 272 isexec = TRUE; 273 continue; 274 } 275 else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) 276 { 277 addcmd(cmd, FALSE, strlen(cmd)); 278 /* test following chars */ 279 } 280 else 281 { 282 /* 283 ** Check to see if the command name is legal. 284 */ 285 (void) strlcpy(cmdbuf, CMDDIR, sizeof cmdbuf); 286 (void) strlcat(cmdbuf, "/", sizeof cmdbuf); 287 (void) strlcat(cmdbuf, cmd, sizeof cmdbuf); 288#ifdef DEBUG 289 printf("Trying %s\n", cmdbuf); 290#endif /* DEBUG */ 291 if (access(cmdbuf, X_OK) < 0) 292 { 293 /* oops.... crack attack possiblity */ 294 fprintf(stderr, 295 "%s: %s not available for sendmail programs\n", 296 prg, cmd); 297 if (p != NULL) 298 *p = ' '; 299#ifndef DEBUG 300 syslog(LOG_CRIT, "uid %d: attempt to use %s", 301 getuid(), cmd); 302#endif /* ! DEBUG */ 303 exit(EX_UNAVAILABLE); 304 } 305 306 /* 307 ** Create the actual shell input. 308 */ 309 310 addcmd(cmd, TRUE, strlen(cmd)); 311 } 312 isexec = FALSE; 313 314 if (p != NULL) 315 *p = ' '; 316 else 317 break; 318 319 r = strpbrk(p, specialbuf); 320 if (r == NULL) { 321 addcmd(p, FALSE, strlen(p)); 322 break; 323 } 324#if ALLOWSEMI 325 if (*r == ';') { 326 addcmd(p, FALSE, r - p + 1); 327 q = r + 1; 328 continue; 329 } 330#endif /* ALLOWSEMI */ 331 if ((*r == '&' && *(r + 1) == '&') || 332 (*r == '|' && *(r + 1) == '|')) 333 { 334 addcmd(p, FALSE, r - p + 2); 335 q = r + 2; 336 continue; 337 } 338 339 fprintf(stderr, "%s: cannot use %c in command\n", prg, *r); 340#ifndef DEBUG 341 syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", 342 getuid(), *r, par); 343#endif /* ! DEBUG */ 344 exit(EX_UNAVAILABLE); 345 } /* end of while *q */ 346 if (isexec) 347 { 348 fprintf(stderr, "%s: missing command to exec\n", prg); 349#ifndef DEBUG 350 syslog(LOG_CRIT, "uid %d: missing command to exec", getuid()); 351#endif /* ! DEBUG */ 352 exit(EX_UNAVAILABLE); 353 } 354 /* make sure we created something */ 355 if (newcmdbuf[0] == '\0') 356 { 357 fprintf(stderr, "Usage: %s -c command\n", prg); 358#ifndef DEBUG 359 syslog(LOG_ERR, "usage"); 360#endif /* ! DEBUG */ 361 exit(EX_USAGE); 362 } 363 364 /* 365 ** Now invoke the shell 366 */ 367 368#ifdef DEBUG 369 printf("%s\n", newcmdbuf); 370#endif /* DEBUG */ 371 (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf, NULL, newenv); 372 save_errno = errno; 373#ifndef DEBUG 374 syslog(LOG_CRIT, "Cannot exec /bin/sh: %m"); 375#endif /* ! DEBUG */ 376 errno = save_errno; 377 perror("/bin/sh"); 378 exit(EX_OSFILE); 379 /* NOTREACHED */ 380 return EX_OSFILE; 381} 382