
/*****************************************************************/
/* Generic wrapper to prevent exploitation of suid/sgid programs */
/* J. Zbiciak 1997                                               */
/*****************************************************************/

/* Wrapper Configuration Information */

static char hdr_rcsid[]="$Id: wrapper.h,v 2.0 1997/06/16 04:37:03 jzbiciak Exp $";

#ifndef _WRAPPER_H_
#define _WRAPPER_H_

#include <stdio.h>
#include <syslog.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <ctype.h>


/**************************************************************************/
/* To install, configure the variables below to reflect the needs of the  */
/* wrapped program.  Once this is complete, move the wrapped program to a */
/* new location.  (I prefer appending an "_" to the wrapped program's     */
/* name; however, others may wish to move the broken program to a secure  */
/* directory instead.)  Finally, remove the offending permission bit from */
/* the broken program and place it on the wrapper. That should do it! :-) */
/**************************************************************************/

/*------------------------------------------------------------------------*/
/* Tunable values per program being wrapped                               */
/*------------------------------------------------------------------------*/

/* Internal vs. External selection */

#ifndef USE_EXTERNAL_WRAP_PROFILE
#define USE_EXTERNAL_WRAP_PROFILE 0		/* Extern wrap_profile linked in  */
#endif
#ifndef USE_EXTERNAL_ENV_PROFILE
#define USE_EXTERNAL_ENV_PROFILE 0      /* Extern env_profile linked in   */
#endif
#ifndef USE_EXTERNAL_ALLOWED_CHAR
#define USE_EXTERNAL_ALLOWED_CHAR 0     /* Extern allowed_char linked in  */
#endif

/* Test mode (flip with -DTEST=1) */
#ifndef TEST
#define TEST 0
#endif

/* Paths */
typedef struct 
{
        char * base_name;       /* Base name of wrapped program           */
        char * full_path;       /* Full path to program being wrapped     */
} TWrapped;

/* Wrapped program resolution table */
/*
   A "base_name" of NULL corresponds to a "default" basename which matches
   any input basename.  This should always be the *last* base_name in the 
   list.  A "full_path" of NULL corresponds to an "invalid" wrapped program.
   A match which resolves to a NULL full_path will be flagged as an
   exploit attempt.  So, to disallow all non-configured basenames,
   make the final entry in this table "{ NULL, NULL }".  To have a default
   full path which works for any basename, make the final entry something
   like "{ NULL, "/path/to/wrapped/program" }". 
 */

#if !USE_EXTERNAL_WRAP_PROFILE || TEST
TWrapped wrap_profile[]=
{
#if !TEST
	/* place your profile here */
        { "ps", "/usr/local/secure/ps" },   /* map "ps" to secured copy   */
        { NULL, NULL },                     /* disallow all others        */
#else   /* Use this test profile   */
	{ "test1a", "./test_clnt_a" },   /* Only for testing purposes  */
	{ "test1b", "./test_clnt_b" },   /* Only for testing purposes  */
	{ "test2a", "./test_clnt_a" },   /* Only for testing purposes  */
	{ "test2b", "./test_clnt_b" },   /* Only for testing purposes  */
	{ NULL, NULL },
#endif
};
#else
extern TWrapped wrap_profile[];
#endif

/* Wrapper behavior */
#ifndef SYSLOG
#define SYSLOG (1+TEST)         /* Enable/disable SYSLOGging              */
#endif
#ifndef LOG_UIDS
#define LOG_UIDS 1              /* Enable/disable recording uid w/syslog  */
#endif
#ifndef FACILITY
#define FACILITY LOG_LOCAL0     /* Facility to syslog() to                */
#endif
#ifndef PRIORITY
#define PRIORITY LOG_ALERT      /* Priority level for syslog()            */
#endif
#ifndef LOGIDENT
#define LOGIDENT "jz-wrap"      /* How to identify myself to syslog()     */
#endif

/* Wrapper argument control */
#ifndef ARG_MAXLEN
#define ARG_MAXLEN (32)         /* Maximum argv parameter length.         */
#endif
#ifndef ARG_MAXCNT
#define ARG_MAXCNT (6)          /* Maximum argument count.                */
#endif

/* Wrapper name control */
#ifndef ARG0_MAXLEN
#define ARG0_MAXLEN (128)       /* Maximum argv[0] parameter length       */
#endif
#ifndef ARG0_BASELEN
#define ARG0_BASELEN (16)       /* Maximum "basename" length in argv[0]   */
#endif

/* Wrapper environment control */
#ifndef ENV_CHKUSER
#define ENV_CHKUSER 1           /* Verify USER/LOGNAME against uid        */
#endif
#ifndef ENV_PASSTHRU
#define ENV_PASSTHRU 0          /* Allow var not in profile to pass thru  */
#endif
#ifndef ENV_CHKPASS
#define ENV_CHKPASS 1           /* Check passed-thru vars w/safe_str_scan */
#endif
#ifndef ENV_MAXPASS
#define ENV_MAXPASS (1024)      /* Max length for passed-thru vars        */
#endif

/* Character remapping threshold */
#ifndef MAX_REMAP
#define MAX_REMAP (32)		    /* Remap up to this many chars            */
#endif

/* Check our config #defines... */
#if !defined(SYSLOG)
#       error Define "SYSLOG" to be either 1 or 0 explicitly
#elseif SYSLOG && !defined(LOG_UIDS)
#       error Define "LOG_UIDS" to be either 1 or 0 explicitly
#elseif SYSLOG && !defined(FACILITY)
#       error Define "FACILITY" to be the syslog() facility to log to.
#elseif SYSLOG && !defined(PRIORITY)
#       error Define "PRIORITY" to be the syslog() priority level to log with.
#elseif !defined(ARG_MAXLEN)
#       error Define "ARG_MAXLEN" to be the maximum argument length allowed.
#elseif !defined(ARG_MAXCNT)
#       error Define "ARG_MAXCNT" to be the maximum number args allowed.
#elseif !defined(ARG0_MAXLEN)
#       error Define "ARG0_MAXLEN" to be the maximum argv[0] length allowed.
#elseif !defined(ARG0_BASELEN)
#       error Define "ARG0_BASELEN" to be the max argv[0] basename len allowed.
#elseif !defined(ENV_CHKUSER)
#       error Define "ENV_CHKUSER" to be either 1 or 0 explicitly
#elseif !defined(ENV_PASSTHRU)
#       error Define "ENV_PASSTHRU" to be either 1 or 0 explicitly
#elseif !defined(ENV_CHKPASS)
#       error Define "ENV_PASSTHRU" to be either 1 or 0 explicitly
#endif


typedef struct tEnvInfo
{
        char * env;             /* Environment var name with trailing '=' */
        char * val;             /* Default val to assign (for presetting) */
        int name_len;           /* Length of name (including '=')         */
        int max_len;            /* Max length of value assignable to var  */
        int has_username;       /* Check this variable against username   */
        int allow;              /* Allow this var through if set already  */
        int deny;               /* Deny this var if set already           */
        int preset;             /* (P)reset this var in not set or denied */
        int matched;            /* Flag noting if a field was matched     */
} TEnvInfo;

/* 
   The incoming environment is scanned and compared against the env_profile
   array.  Environment variables matching an entry in this profile are
   processed according to the "allow", "deny", and "preset" fields.  The 
   "allow", "deny", and "preset" fields interact on a match as follows:

   allow  deny  preset    action
     0     0      0       Var is not allowed unless "ENV_PASSTHRU" set.
     0     0      1       Var is replaced/preset to "val" unless 
                             "ENV_PASSTHRU" is set.
     0     1      0       Var is removed from environment.
     0     1      1       Var is replaced with "val".
     1     0      0       Var is allowed through if passes safe_str_scan.
     1     0      1       Var is allowed through if passes safe_str_scan.
     1     1      0       Var is removed from environment, but after being
                             evaluated by "safe_str_scan".  (Can be used
                             to detect possible exploits specifically.)
     1     1      1       Var is removed from environment, but after being
                             evaluated by "safe_str_scan".  Also, variable
                             is replaced with "val".

    For variables not matching an entry in the profile, ENV_PASSTHRU is
    consulted.  If ENV_PASSTHRU is set, the variables are passed through.
    If ENV_PASSTHRU is cleared, these variables are discarded.  For 
    environment variables being passed through, if ENV_CHKPASS is set,
    these variables are checked against ENV_MAXLEN with safe_str_scan.
    Additionally, env_profile entries marked "preset" which were not
    matched are appended to the environment.

    To avoid a potentially dangerous strcpy, the full text of the 
    environment variable setting ("ENV=val") must be present in the 
    "val" field.  This simplifies and safens the code. 
 */

/* aside:  trailing '=' is necessary on variable names to avoid problems  */
/*         with variables whose names prefix each other.                  */

#if USE_EXTERNAL_ENV_PROFILE
extern TEnvInfo env_profile[];
#else
TEnvInfo env_profile [] =       /* Environ. vars we allow program to see  */
{
  /* Name               Val    Name  Max  User  Allow  Deny  Preset  Matched */
  /*                           len   len                             (zero)  */
  { "IFS=",             NULL,  4,    0,    0,     0,     1,    0,       0   },
  { "ROWS=",            NULL,  5,    4,    0,     1,     0,    0,       0   },
  { "COLUMNS=",         NULL,  8,    4,    0,     1,     0,    0,       0   },
  { "LC_CTYPE=",        NULL,  9,    64,   0,     1,     0,    0,       0   },
  { "LC_MESSAGES=",     NULL,  12,   64,   0,     1,     0,    0,       0   },
  { "LC_TIME=",         NULL,  8,    64,   0,     1,     0,    0,       0   },
  { "LD_DEBUG=",        NULL,  9,    0,    0,     0,     1,    0,       0   },
  { "LD_OPTIONS=",      NULL,  11,   0,    0,     0,     1,    0,       0   },
  { "LD_PRELOAD=",      NULL,  11,   0,    0,     0,     1,    0,       0   },
  { "LD_PROFILE=",      NULL,  16,   0,    0,     0,     1,    0,       0   },
  { "LD_PROFILE_OUTPUT=",NULL, 23,   0,    0,     0,     1,    0,       0   },
  { "LD_RUN_PATH=",     NULL,  17,   0,    0,     0,     1,    0,       0   },
  { "LOGNAME=",         NULL,  8,    16,   1,     1,     0,    0,       0   },
  { "PATH=",            "PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin",
                               5,    512,  0,     0,     1,    1,       0   },
  { "TERM=",            "TERM=dumb",
                               5,    16,   0,     1,     0,    1,       0   },
  { "USER=",            NULL,  5,    16,   1,     1,     0,    0,       0   },

#if TEST
  { "TEST0=",       "TEST0=0", 6,    8,    0,     0,     0,    0,       0   },
  { "TEST1=",       "TEST1=1", 6,    8,    0,     0,     0,    1,       0   },
  { "TEST2=",       "TEST2=2", 6,    8,    0,     0,     1,    0,       0   },
  { "TEST3=",       "TEST3=3", 6,    8,    0,     0,     1,    1,       0   },
  { "TEST4=",       "TEST4=4", 6,    8,    0,     1,     0,    0,       0   },
  { "TEST5=",       "TEST5=5", 6,    8,    0,     1,     0,    1,       0   },
  { "TEST6=",       "TEST6=6", 6,    8,    0,     1,     1,    0,       0   },
  { "TEST7=",       "TEST7=7", 6,    8,    0,     1,     1,    1,       0   },
#endif

  { NULL,               NULL,  0,    0,    0,     0,     0,    0,       0   }
};
#endif



/* Set an entry to what a char maps to, if allowed, or 0 if it's not.     */
/* This table pretty well assumes an ASCII representation is being used.  */
#if USE_EXTERNAL_ALLOWED_CHAR
extern unsigned char allowed_char[];
#else
unsigned char allowed_char [] =
{
  /* Default table disallows all but a handful of certain "well known"    */
  /* ASCII control characters.  Otherwise, only alpha, numeric, and       */
  /* punctuation characters are allowed through.  By default, disallowed  */
  /* characters are mapped to the character defined by "NA".  If a        */
  /* character is marked with a zero, it causes the wrapper to abort.     */
  /* If a character is marked with NA, it's merely remapped.  To disable  */
  /* remapping entirely (causing any disallowed char to abort), define NA */
  /* to be 0.  The variable MAX_REMAP is used by the main body of the     */
  /* program to control how many remapped characters you'll permit per    */
  /* each string. (environment variable, argument, etc.)                  */

#define NA '_'

  /* Control characters:    */
  /* Permits BEL, BS, TAB, NL, VF, CR, ESC, and remaps the rest. */
  /* ^@ -- ^G  (  0 --   7) */    0,  NA,  NA,  NA,  NA,  NA,  NA,'\a',
  /* ^H -- ^O  (  8 --  15) */ '\b','\t','\n',  NA,'\v','\r',  NA,  NA, 
  /* ^P -- ^W  ( 16 --  23) */   NA,  NA,  NA,  NA,  NA,  NA,  NA,  NA,
  /* ^X -- ^_  ( 24 --  31) */   NA,  NA,  NA,  27,  NA,  NA,  NA,  NA,

  /* Alphabetics, numerics, and punctuation: */
  /* Permits all of them by default, except for the DEL character (127) */
  /*           ( 32 --  39) */  ' ', '!', '"', '#', '$', '%', '&','\'', 
  /*           ( 40 --  47) */  '(', ')', '*', '+', ',', '-', '.', '/', 
  /*           ( 48 --  55) */  '0', '1', '2', '3', '4', '5', '6', '7', 
  /*           ( 56 --  63) */  '8', '9', ':', ';', '<', '=', '>', '?', 
  /*           ( 64 --  71) */  '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 
  /*           ( 72 --  79) */  'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 
  /*           ( 80 --  87) */  'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 
  /*           ( 88 --  95) */  'X', 'Y', 'Z', '[','\\', ']', '^', '_', 
  /*           ( 96 -- 103) */  '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 
  /*           (104 -- 111) */  'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 
  /*           (112 -- 119) */  'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 
  /*           (120 -- 127) */  'x', 'y', 'z', '{', '|', '}', '~', NA,

  /* Extended ASCII:  Flatly disallowed. */
  /*           (128 -- 143) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (144 -- 159) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (160 -- 175) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (176 -- 191) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (192 -- 207) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (208 -- 223) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (224 -- 239) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  /*           (240 -- 255) */  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
#endif

/*------------------------------------------------------------------------*/
/* Internal wrapper use only -- shouldn't need to adjust, generally       */
/*------------------------------------------------------------------------*/
#ifndef MSG_LEN
#define MSG_LEN (192)          /* Maximum output message length.          */
#endif
#ifndef MAX_LOG
#define MAX_LOG (48)           /* Maximum length per call to syslog()     */
#endif

/* Side note:  The reason the maximum syslog line length is only 48 is so */
/* that on my system the syslog lines don't wrap around the screen edge.  */
/* You're welcome to bump this up to whatever you want.                   */



#ifdef STATIC_CFGTBL

/* If the user defines "STATIC_CFGTBL", we will store a static list of all */
/* the compile-time config options that are #defined above.  The RCS prog. */
/* "ident" should be able to grok these from the compiled wrapper, making  */
/* it easy to see what a compiled version of the wrapper has compiled in.  */

#define DEREF(a) #a
#define QUOTE(a) " '" DEREF(a) "' "
static char *cfgtbl[]=
{
	"$define: LOG_UIDS" QUOTE(LOG_UIDS) "$",
	"$define: FACILITY" QUOTE(FACILITY) "$",
	"$define: PRIORITY" QUOTE(PRIORITY) "$",
	"$define: LOGIDENT" QUOTE(LOGIDENT) "$",
	"$define: ARG_MAXLEN" QUOTE(ARG_MAXLEN) "$",
	"$define: ARG_MAXCNT" QUOTE(ARG_MAXCNT) "$",
	"$define: ARG0_MAXLEN" QUOTE(ARG0_MAXLEN) "$",
	"$define: ARG0_BASELEN" QUOTE(ARG0_BASELEN) "$",
	"$define: ENV_CHKUSER" QUOTE(ENV_CHKUSER) "$",
	"$define: ENV_PASSTHRU" QUOTE(ENV_PASSTHRU) "$",
	"$define: ENV_CHKPASS" QUOTE(ENV_CHKPASS) "$",
	"$define: ENV_MAXLEN" QUOTE(ENV_MAXLEN) "$",
	"$define: MAX_REMAP" QUOTE(MAX_REMAP) "$",
	"$define: SYSLOG" QUOTE(SYSLOG) "$",
	"$define: USE_EXTERNAL_WRAP_PROFILE " QUOTE(USE_EXTERNAL_WRAP_PROFILE) "$",
	"$define: USE_EXTERNAL_ENV_PROFILE" QUOTE(USE_EXTERNAL_ENV_PROFILE) "$",
	"$define: USE_EXTERNAL_ALLOWED_CHAR" QUOTE(USE_EXTERNAL_ALLOWED_CHAR) "$",
	"$define: NA" QUOTE(NA) "$",
	"$define: MSG_LEN" QUOTE(MSG_LEN) "$",
	"$define: MAX_LOG" QUOTE(MAX_LOG) "$",
	"$define: TEST" QUOTE(TEST) "$"
};

#endif /*STATIC_CFGTBL*/

#endif /*WRAPPER_H*/



