Expunge revision histories in files.
[become] / src / become.c
index 627eda5..b4956ff 100644 (file)
@@ -1,10 +1,10 @@
 /* -*-c-*-
  *
- * $Id: become.c,v 1.2 1997/08/04 10:24:20 mdw Exp $
+ * $Id: become.c,v 1.26 2004/04/08 01:36:20 mdw Exp $
  *
  * Main code for `become'
  *
- * (c) 1997 EBI
+ * (c) 1998 EBI
  */
 
 /*----- Licensing notice --------------------------------------------------*
  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/*----- Revision history --------------------------------------------------*
- *
- * $Log: become.c,v $
- * Revision 1.2  1997/08/04 10:24:20  mdw
- * Sources placed under CVS control.
- *
- * Revision 1.1  1997/07/21  13:47:54  mdw
- * Initial revision
- *
- */
-
 /*----- Header files ------------------------------------------------------*/
 
 /* --- ANSI headers --- */
 
 #include <ctype.h>
 #include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <arpa/inet.h>
 
 #include <netdb.h>
+#include <grp.h>
 #include <pwd.h>
 #include <syslog.h>
 #include <unistd.h>
+#include <fcntl.h>
 
 extern char **environ;
 
+/* --- mLib --- */
+
+#include <mLib/alloc.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sym.h>
+#include <mLib/trace.h>
+
 /* --- Local headers --- */
 
 #include "become.h"
@@ -73,13 +74,53 @@ extern char **environ;
 #include "check.h"
 #include "daemon.h"
 #include "lexer.h"
-#include "mdwopt.h"
 #include "name.h"
-#include "parser.h"
+#include "parse.h"
 #include "rule.h"
-#include "utils.h"
 #include "userdb.h"
 
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Symbol table entry for an environment variable --- */
+
+typedef struct sym_env {
+  sym_base _base;                      /* Symbol table information */
+  unsigned f;                          /* Flags word (see below) */
+  char *val;                           /* Pointer to variable value */
+} sym_env;
+
+/* --- Environment variable flags --- */
+
+enum {
+  envFlag_preserve = 1
+};
+
+/* --- Login behaviour types --- */
+
+#define l_preserve 0                   /* Preserve the environment */
+#define l_setuser 1                    /* Update who I am */
+#define l_login        2                       /* Do a full login */
+
+/* --- Group behaviour types --- *
+ *
+ * Note that these make a handy bitfield.
+ */
+
+#ifdef HAVE_SETGROUPS
+
+enum {
+  g_unset = 0,                         /* Nobody's set a preference */
+  g_keep = 1,                          /* Leave the group memberships */
+  g_replace = 2,                       /* Replace group memberships */
+  g_merge = (g_keep | g_replace)       /* Merge the group memberships */
+};
+
+#endif
+
+/*----- Static variables --------------------------------------------------*/
+
+static sym_table bc__env;
+
 /*----- Main code ---------------------------------------------------------*/
 
 /* --- @bc__write@ --- *
@@ -102,7 +143,7 @@ static void bc__write(FILE *fp, const char *p)
 
   /* --- Try to be a little efficient --- *
    *
-   * Gather up non-`$' characters using @strcpn@ and spew them out really
+   * Gather up non-`$' characters using @strcspn@ and spew them out really
    * quickly.
    */
 
@@ -118,6 +159,131 @@ static void bc__write(FILE *fp, const char *p)
   }
 }
 
+/* --- @bc__setenv@ --- *
+ *
+ * Arguments:  @sym_env *e@ = pointer to environment variable block
+ *             @const char *val@ = value to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets an environment variable block to the right value.
+ */
+
+static void bc__setenv(sym_env *e, const char *val)
+{
+  e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
+  sprintf(e->val, "%s=%s", e->_base.name, val);
+}
+
+/* --- @bc__putenv@ --- *
+ *
+ * Arguments:  @const char *var@ = name of the variable to set, or 0 if
+ *                     this is embedded in the value string
+ *             @const char *val@ = value to set, or 0 if the variable must
+ *                     be removed
+ *             @unsigned int fl@ = flags to set
+ *             @unsigned int force@ = force overwrite of preserved variables
+ *
+ * Returns:    Pointer to symbol block, or zero if it was deleted.
+ *
+ * Use:                Puts an item into the environment.
+ */
+
+static sym_env *bc__putenv(const char *var, const char *val,
+                          unsigned int fl, unsigned int force)
+{
+  unsigned int f;
+  sym_env *e;
+  char *q = 0;
+  size_t sz;
+
+  /* --- Sort out embedded variable names --- */
+
+  if (!var) {
+    const char *p = strchr(val, '=');
+
+    if (p) {
+      sz = p - val;
+      q = xmalloc(sz + 1);
+      memcpy(q, val, sz);
+      q[sz] = 0;
+      var = q;
+      val = p + 1;
+    } else {
+      var = val;
+      val = 0;
+    }
+  }
+
+  /* --- Find the variable block --- */
+
+  if (val) {
+    e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
+    if (!f || ~e->f & envFlag_preserve || force) {
+      if (f)
+       free(e->val);
+      bc__setenv(e, val);
+    }
+  } else {
+    e = sym_find(&bc__env, var, -1, 0, 0);
+    if (e && (force || ~e->f & envFlag_preserve))
+      sym_remove(&bc__env, e);
+    e = 0;
+  }
+
+  /* --- Tidy up and return --- */
+
+  if (q)
+    free(q);
+  if (e)
+    e->f = fl;
+  return (e);
+}
+
+/* --- @bc__addGroups@ --- *
+ *
+ * Arguments:  @gid_t *g@ = pointer to a group array
+ *             @int *png@ = pointer to number of entries in the array
+ *             @const gid_t *a@ = pointer to groups to add
+ *             @int na@ = number of groups to add
+ *
+ * Returns:    Zero if it was OK, nonzero if we should stop now.
+ *
+ * Use:                Adds groups to a groups array.
+ */
+
+static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
+{
+  int i, j;
+  int ng = *png;
+
+  for (i = 0; i < na; i++) {
+
+    /* --- Ensure this group isn't already in the list --- */
+
+    for (j = 0; j < ng; j++) {
+      if (a[i] == g[j])
+       goto next_group;
+    }
+
+    /* --- See if there's room for more --- */
+
+    if (ng >= NGROUPS_MAX) {
+      moan("too many groups (system limit exceeded) -- some have been lost");
+      *png = ng;
+      return (-1);
+    }
+
+    /* --- Add the group --- */
+
+    g[ng++] = a[i];
+  next_group:;
+  }
+
+  *png = ng;
+  return (0);
+}
+
 /* --- @bc__banner@ --- *
  *
  * Arguments:  @FILE *fp@ = stream to write on
@@ -146,38 +312,24 @@ static void bc__usage(FILE *fp)
   bc__write(fp,
            "Usage: \n"
            "   $ -c <shell-command> <user>\n"
-           "   $ <user> [<command> [<arguments>]...]\n"
-           "   $ -d [-p <port>] [-f <config-file>]\n");
-}
-
-/* --- @bc__makeEnv@ --- *
- *
- * Arguments:  @const char *var@ = name of an environment variable
- *             @const char *value@ = value of the variable
- *
- * Returns:    A pointer to an allocated block assigning the value to the
- *             name.
- *
- * Use:                Constructs environment mappings.
- */
-
-static char *bc__makeEnv(const char *var, const char *value)
-{
-  char *p = xmalloc(strlen(var) + strlen(value) + 2);
-  sprintf(p, "%s=%s", var, value);
-  return (p);
+           "   $ [<env-var>] <user> [<command> [<arguments>]...]\n"
+#ifndef NONETWORK
+           "   $ -d [-p <port>] [-f <config-file>]\n"
+#endif
+    );
 }
 
 /* --- @bc__help@ --- *
  *
  * Arguments:  @FILE *fp@ = stream to write on
+ *             @int suid@ = whether we're running set-uid
  *
  * Returns:    ---
  *
  * Use:                Displays a help message for this excellent piece of software.
  */
 
-static void bc__help(FILE *fp)
+static void bc__help(FILE *fp, int suid)
 {
   bc__banner(fp);
   putc('\n', fp);
@@ -197,19 +349,50 @@ static void bc__help(FILE *fp)
 "\n"
 "Options available are:\n"
 "\n"
-"-h, --help            Display this help text\n"
-"-v, --version         Display the version number of this copy of $\n"
-"-l, --login           Really log in as the user\n"
-"-c, --command=CMD     Run the (Bourne) shell command CMD\n"
-"-d, --daemon          Start up a daemon, to accept requests from clients\n"
-"-p, --port=PORT               In daemon mode, listen on PORT\n"
-"-f, --config-file=FILE        In daemon mode, read config from FILE\n"
-#ifdef TRACING
-"--impersonate=USER    Claim to be USER when asking the server\n"
-"--trace=FILE          Dump trace information to FILE (boring)\n"
-"--trace-level=OPTS    Set level of tracing information\n"
+"-h, --help                    Display this help text\n"
+"-u, --usage                   Display a short usage summary\n"
+"-v, --version                 Display $'s version number\n"
+"\n"
+"-e, --preserve-environment    Try to preserve the current environment\n"
+"-s, --su, --set-user          Set environment variables to reflect USER\n"
+"-l, --login                   Really log in as USER\n"
+"                              [Default is "
+#if DEFAULT_LOGIN_STYLE == l_preserve
+  "preserve-environment"
+#elif DEFAULT_LOGIN_STYLE == l_setuser
+  "set-user"
+#elif DEFAULT_LOGIN_STYLE == l_login
+  "login"
+#else
+  "poorly configured"
+#endif
+"]\n\n"
+"-g GROUP, --group=GROUP       Set primary group-id to be GROUP\n"
+#ifdef HAVE_SETGROUPS
+"-k, --keep-groups             Keep your current set of groups\n"
+"-m, --merge-groups            Merge the lists of groups\n"
+"-r, --replace-groups          Replace the list of groups\n"
+#endif
+"\n"
+"-c CMD, --command=CMD         Run the (Bourne) shell command CMD\n"
+#ifndef NONETWORK
+"\n"
+"-d, --daemon                  Start a daemon\n"
+"-n, --nofork                  In daemon mode, don't fork into background\n"
+"-p PORT, --port=PORT          In daemon mode, listen on PORT\n"
+"-f FILE, --config-file=FILE   In daemon mode, read config from FILE\n"
+#endif
+    );
+#ifndef NTRACE
+  bc__write(fp, "\n");
+  if (!suid) {
+    bc__write(fp,
+"-I USER, --impersonate=USER   Claim to be USER when asking the server\n");
+  }
+  bc__write(fp,
+"-T FILE, --trace=FILE         Dump trace information to FILE (boring)\n"
+"-L OPTS, --trace-level=OPTS   Set level of tracing information\n");
 #endif
-);
 }
 
 /* --- @main@ --- *
@@ -230,18 +413,29 @@ int main(int argc, char *argv[])
   char *cmd = 0;                       /* Shell command to execute */
   char *binary = "/bin/sh";            /* Default binary to execute */
   char **env = environ;                        /* Default environment to pass */
-  char **todo;                         /* Pointer to argument list */
+  char **todo = 0;                     /* Pointer to argument list */
+  char *who = 0;                       /* Who we're meant to become */
   struct passwd *from_pw = 0;          /* User we are right now */
   struct passwd *to_pw = 0;            /* User we want to become */
 
   /* --- Become server setup parameters --- */
 
+#ifndef NONETWORK
   char *conffile = file_RULES;         /* Default config file for daemon */
-  int port = -1;                       /* Default port for daemon */
+  int port = 0;                                /* Default port for daemon */
+#endif
 
   /* --- Miscellanous shared variables --- */
 
   unsigned flags = 0;                  /* Various useful flags */
+  int style = DEFAULT_LOGIN_STYLE;     /* Login style */
+  gid_t group = -1;                    /* Default group to set */
+  int gstyle = g_unset;                        /* No group style set yet */
+
+#ifdef HAVE_SETGROUPS
+  gid_t groups[NGROUPS_MAX];           /* Set of groups */
+  int ngroups;                         /* Number of groups in the set */
+#endif
 
   /* --- Default argument list executes a shell command --- */
 
@@ -252,25 +446,15 @@ int main(int argc, char *argv[])
     0                                  /* Terminator */
   };
 
-  /* --- Login request replaces the environment with this --- */
-
-  static char *mangled_env[] = {
-    "PATH=/bin:/usr/bin",              /* Default `clean' path*/
-    0,                                 /* User's name (`USER') */
-    0,                                 /* User's name (`LOGNAME') */
-    0,                                 /* Home directory (`HOME') */
-    0                                  /* Terminator */
-  };
-
   /* --- Definitions for the various flags --- */
 
-  enum {
-    f_daemon = 1,                      /* Start up in daemon mode */
-    f_duff = 2,                                /* Fault in arguments */
-    f_login = 4,                       /* Execute as a login shell */
-    f_dummy = 8,                       /* Don't actually do anything */
-    f_setuid = 16                      /* We're running setuid */
-  };
+#define f_daemon 1u                    /* Start up in daemon mode */
+#define f_duff 2u                      /* Fault in arguments */
+#define f_shell 4u                     /* Run a default shell */
+#define f_dummy 8u                     /* Don't actually do anything */
+#define f_setuid 16u                   /* We're running setuid */
+#define f_havegroup 32u                        /* Set a default group */
+#define f_nofork 64u                   /* Don't fork into background */
 
   /* --- Set up the program name --- */
 
@@ -279,36 +463,110 @@ int main(int argc, char *argv[])
   if (getuid() != geteuid())
     flags |= f_setuid;
 
+  /* --- Make sure standard file descriptors are open --- */
+
+  {
+    int fd;
+    do {
+      if ((fd = open("/dev/null", O_RDWR)) < 0)
+       die(1, "couldn't open /dev/null: %s", strerror(errno));
+    } while (fd <= STDERR_FILENO);
+    close(fd);
+  }
+
+  /* --- Read the environment into a hashtable --- */
+
+  {
+    char **p;
+
+    sym_create(&bc__env);
+    for (p = environ; *p; p++)
+      bc__putenv(0, *p, 0, 0);
+  }
+
   /* --- Parse some command line arguments --- */
 
   for (;;) {
     int i;
     static struct option opts[] = {
+
+      /* --- Asking for help --- */
+
       { "help",                0,              0,      'h' },
+      { "usage",       0,              0,      'u' },
       { "version",     0,              0,      'v' },
+
+      /* --- Login style options --- */
+
+      { "preserve-environment", 0,     0,      'e' },
+      { "su",          0,              0,      's' },
+      { "set-user",    0,              0,      's' },
       { "login",       0,              0,      'l' },
-      { "command",     gFlag_argReq,   0,      'c' },
+
+      /* --- Group style options --- */
+
+      { "group",       OPTF_ARGREQ,    0,      'g' },
+#ifdef HAVE_SETGROUPS
+      { "keep-groups", 0,              0,      'k' },
+      { "merge-groups",        0,              0,      'm' },
+      { "replace-groups", 0,           0,      'r' },
+#endif
+
+      /* --- Command to run options --- */
+
+      { "command",     OPTF_ARGREQ,    0,      'c' },
+
+      /* --- Server options --- */
+
+#ifndef NONETWORK
       { "daemon",      0,              0,      'd' },
-      { "port",                gFlag_argReq,   0,      'p' },
-      { "config-file", gFlag_argReq,   0,      'f' },
-#ifdef TRACING
-      { "impersonate", gFlag_argReq,   0,      'I' },
-      { "trace",       gFlag_argOpt,   0,      'T' },
-      { "trace-level", gFlag_argOpt,   0,      'L' },
+      { "nofork",      0,              0,      'n' },
+      { "port",                OPTF_ARGREQ,    0,      'p' },
+      { "config-file", OPTF_ARGREQ,    0,      'f' },
 #endif
+
+      /* --- Tracing options --- */
+
+#ifndef NTRACE
+      { "impersonate", OPTF_ARGREQ,    0,      'I' },
+      { "trace",       OPTF_ARGOPT,    0,      'T' },
+      { "trace-level", OPTF_ARGOPT,    0,      'L' },
+#endif
+
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
+    i = mdwopt(argc, argv,
+              "-"                      /* Return non-options as options */
+              "huv"                    /* Asking for help */
+              "esl"                    /* Login style options */
+#ifdef HAVE_SETGROUPS
+              "g:kmr"                  /* Group style options */
+#else
+              "g:"                     /* Group (without @setgroups@) */
+#endif
+              "c:"                     /* Command to run options */
+#ifndef NONETWORK
+              "dnp:f:"                 /* Server options */
+#endif
+#ifndef NTRACE
+              "I:T::L::"               /* Tracing options */
+#endif
+              ,
+              opts, 0, 0, gFlag_envVar);
     if (i < 0)
-      break;
+      goto done_options;
 
     switch (i) {
 
-      /* --- Standard help and version options --- */
+      /* --- Asking for help --- */
 
       case 'h':
-       bc__help(stdout);
+       bc__help(stdout, flags & f_setuid);
+       exit(0);
+       break;
+      case 'u':
+       bc__usage(stdout);
        exit(0);
        break;
       case 'v':
@@ -316,23 +574,71 @@ int main(int argc, char *argv[])
        exit(0);
        break;
 
-      /* --- Various simple options --- */
+      /* --- Login style options --- */
 
+      case 'e':
+       style = l_preserve;
+       break;
+      case 's':
+       style = l_setuser;
+       break;
       case 'l':
-       flags |= f_login;
+       style = l_login;
+       break;
+
+      /* --- Group style options --- */
+
+      case 'g':
+       if (isdigit((unsigned char)optarg[0]))
+         group = atoi(optarg);
+       else {
+         struct group *gr = getgrnam(optarg);
+         if (!gr)
+           die(1, "unknown group `%s'", optarg);
+         group = gr->gr_gid;
+       }
+       flags |= f_havegroup;
+       break;
+
+      case 'k':
+       gstyle = g_keep;
+       break;
+      case 'm':
+       gstyle = g_merge;
        break;
+      case 'r':
+       gstyle = g_replace;
+       break;
+
+      /* --- Command to run options --- */
+
       case 'c':
        cmd = optarg;
        break;
+
+      /* --- Server options --- */
+
+#ifndef NONETWORK
       case 'p':
-       port = atoi(optarg);
+       if (isdigit((unsigned char)optarg[0]))
+         port = htons(atoi(optarg));
+       else {
+         struct servent *s = getservbyname(optarg, "udp");
+         if (!s)
+           die(1, "unknown service name `%s'", optarg);
+         port = s->s_port;
+       }
        break;
       case 'd':
        flags |= f_daemon;
        break;
+      case 'n':
+       flags |= f_nofork;
+       break;
       case 'f':
        conffile = optarg;
        break;
+#endif
 
       /* --- Pretend to be a different user --- *
        *
@@ -340,7 +646,8 @@ int main(int argc, char *argv[])
        * allow it if we're running setuid.  Disable the actual login anyway.
        */
 
-#ifdef TRACING
+#ifndef NTRACE
+
       case 'I':
        if (flags & f_setuid)
          moan("shan't allow impersonation while running setuid");
@@ -351,12 +658,13 @@ int main(int argc, char *argv[])
          else
            pw = getpwnam(optarg);
          if (!pw)
-           die("can't impersonate unknown user `%s'", optarg);
+           die(1, "can't impersonate unknown user `%s'", optarg);
          from_pw = userdb_copyUser(pw);
          rq.from = from_pw->pw_uid;
          flags |= f_dummy;
        }
        break;
+
 #endif
 
       /* --- Tracing support --- *
@@ -365,7 +673,7 @@ int main(int argc, char *argv[])
        * to!
        */
 
-#ifdef TRACING
+#ifndef TRACE
 
       case 'T': {
        FILE *fp;
@@ -373,16 +681,31 @@ int main(int argc, char *argv[])
        if (optarg == 0 || strcmp(optarg, "-") == 0)
          fp = stdout;
        else {
-         if ((flags & f_setuid) && access(optarg, W_OK)) {
-           die("no write permission for trace file file `%s': %s",
-               optarg, strerror(errno));
+         uid_t eu = geteuid(), ru = getuid();
+
+#ifdef HAVE_SETREUID
+         if (setreuid(eu, ru))
+#else
+         if (seteuid(ru))
+#endif
+         {
+           die(1, "couldn't temporarily give up privileges: %s",
+               strerror(errno));
          }
+
          if ((fp = fopen(optarg, "w")) == 0) {
-           die("couldn't open trace file `%s' for writing: %s",
+           die(1, "couldn't open trace file `%s' for writing: %s",
                optarg, strerror(errno));
          }
+
+#ifdef HAVE_SETREUID
+          if (setreuid(ru, eu))
+#else
+          if (seteuid(eu))
+#endif
+           die(1, "couldn't regain privileges: %s", strerror(errno));
        }
-       traceon(fp, TRACE_DFL);
+       trace_on(fp, TRACE_DFL);
        trace(TRACE_MISC, "become: tracing enabled");
       } break;
 
@@ -390,84 +713,84 @@ int main(int argc, char *argv[])
 
       /* --- Setting trace levels --- */
 
-#ifdef TRACING
+#ifndef NTRACE
 
       case 'L': {
-       int sense = 1;
-       unsigned int lvl = 0, l;
-       const char *p = optarg;
 
        /* --- Table of tracing facilities --- */
 
-       typedef struct tr {
-         char ch;
-         unsigned int l;
-         const char *help;
-       } tr;
-
-       static tr lvltbl[] = {
+       static trace_opt lvltbl[] = {
          { 'm', TRACE_MISC,    "miscellaneous messages" },
          { 's', TRACE_SETUP,   "building the request block" },
-         { 'd', TRACE_DAEMON,  "server process" },
          { 'r', TRACE_RULE,    "ruleset scanning" },
          { 'c', TRACE_CHECK,   "request checking" },
+#ifndef NONETWORK
+         { 'd', TRACE_DAEMON,  "server process" },
          { 'l', TRACE_CLIENT,  "client process" },
          { 'R', TRACE_RAND,    "random number generator" },
          { 'C', TRACE_CRYPTO,  "cryptographic processing of requests" },
+#endif
          { 'y', TRACE_YACC,    "parsing configuration file" },
          { 'D', TRACE_DFL,     "default tracing options" },
          { 'A', TRACE_ALL,     "all tracing options" },
          { 0,   0,             0 }
        };
-       tr *tp;
 
        /* --- Output some help if there's no arguemnt --- */
 
-       if (!optarg) {
-         bc__banner(stdout);
-         bc__write(stdout,
-                   "\n"
-                   "Tracing options:\n"
-                   "\n");
-         for (tp = lvltbl; tp->l; tp++) {
-           if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
-             printf("%c -- %s\n", tp->ch, tp->help);
-         }
-         bc__write(stdout,
-"\n"
-"Also, `+' and `-' options are recognised to turn on and off vavrious\n"
-"tracing options.  For example, `A-r' enables everything except ruleset\n"
-"tracing, and `A-D+c' is everything except the defaults, but with request\n"
-"check tracing.\n"
-);
-         exit(0);
-       }
+       trace_level(traceopt(lvltbl, optarg, TRACE_DFL,
+                            (flags & f_setuid) ? TRACE_PRIV : 0));
+      } break;
+
+#endif
+
+      /* --- Something that wasn't an option --- *
+       *
+       * The following nasties are supported:
+       *
+       *   * NAME=VALUE                -- preserve NAME, and give it a VALUE
+       *   * NAME=             -- preserve NAME, and give it an empty value
+       *   * NAME-             -- delete NAME
+       *   * NAME!             -- preserve NAME with existing value
+       *
+       * Anything else is either the user name (which is OK) or the start of
+       * the command (in which case I stop and read the rest of the command).
+       */
+
+      case 0: {
+       size_t sz = strcspn(optarg, "=-!");
+       sym_env *e;
+
+       /* --- None of the above --- */
 
-       while (*p) {
-         if (*p == '+')
-           sense = 1;
-         else if (*p == '-')
-           sense = 0;
-         else {
-           for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
-             ;
-           l = tp->l;
-           if (flags & f_setuid)
-             l &= ~TRACE_PRIV;
-           if (l)
-             lvl = sense ? (lvl | l) : (lvl & ~l);
-           else
-             moan("unknown trace option `%c'", *p);
+       if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
+         if (!who) {
+           who = optarg;
+           break;
+         } else {
+           optind--;
+           goto done_options;
          }
-         p++;
        }
 
-       tracesetlvl(lvl);
-       yydebug = ((lvl & TRACE_YACC) != 0);
+       /* --- Do the appropriate thing --- */
+
+       switch (optarg[sz]) {
+         case '=':
+           bc__putenv(0, optarg, envFlag_preserve, 1);
+           break;
+         case '-':
+           optarg[sz] = 0;
+           bc__putenv(optarg, 0, 0, 1);
+           break;
+         case '!':
+           optarg[sz] = 0;
+           if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
+             e->f |= envFlag_preserve;
+           break;
+       }
       } break;
 
-#endif
-
       /* --- Something I didn't understand has occurred --- */
 
       case '?':
@@ -475,6 +798,8 @@ int main(int argc, char *argv[])
        break;
     }
   }
+
+done_options:
   if (flags & f_duff) {
     bc__usage(stderr);
     exit(1);
@@ -482,11 +807,13 @@ int main(int argc, char *argv[])
 
   /* --- Switch to daemon mode if requested --- */
 
+#ifndef NONETWORK
   if (flags & f_daemon) {
     T( trace(TRACE_MISC, "become: daemon mode requested"); )
-    daemon_init(conffile, port);
+    daemon_init(conffile, port, (flags & f_nofork) ? df_nofork : 0);
     exit(0);
   }
+#endif
 
   /* --- Open a syslog --- */
 
@@ -495,21 +822,19 @@ int main(int argc, char *argv[])
   /* --- Pick out the uid --- */
 
   {
-    const char *u;
     struct passwd *pw;
 
-    if (optind >= argc) {
+    if (!who) {
       bc__usage(stderr);
       exit(1);
     }
-    u = argv[optind++];
 
-    if (isdigit((unsigned char)u[0]))
-      pw = getpwuid(atoi(u));
+    if (isdigit((unsigned char)who[0]))
+      pw = getpwuid(atoi(who));
     else
-      pw = getpwnam(u);
+      pw = getpwnam(who);
     if (!pw)
-      die("unknown user `%s'", u);
+      die(1, "unknown user `%s'", who);
     to_pw = userdb_copyUser(pw);
     rq.to = pw->pw_uid;
   }
@@ -522,7 +847,7 @@ int main(int argc, char *argv[])
     rq.from = getuid();
     pw = getpwuid(rq.from);
     if (!pw)
-      die("who are you? (can't find user %li)", (long)rq.from);
+      die(1, "who are you? (can't find user %li)", (long)rq.from);
     from_pw = userdb_copyUser(pw);
   }
 
@@ -533,8 +858,133 @@ int main(int argc, char *argv[])
     struct hostent *he;
     uname(&u);
     if ((he = gethostbyname(u.nodename)) == 0)
-      die("who am I? (can't resolve `%s')", u.nodename);
-    memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
+      die(1, "who am I? (can't resolve `%s')", u.nodename);
+    memcpy(&rq.host, he->h_addr, sizeof(rq.host));
+  }
+
+  /* --- Fiddle with group ownerships a bit --- */
+
+  {
+#ifdef HAVE_SETGROUPS
+    gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
+    int n_fgr, n_tgr;
+#endif
+
+    /* --- Set the default login group, if there is one --- */
+
+    if (~flags & f_havegroup)
+      group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
+
+#ifndef HAVE_SETGROUPS
+
+    /* --- Check that it's valid --- */
+
+    if (group != from_pw->pw_gid && group != to_pw->pw_gid)
+      die(1, "invalid default group");
+
+#else
+
+    /* --- Set the default group style --- */
+
+    if (gstyle == g_unset)
+      gstyle = (style == l_login) ? g_replace : g_merge;
+
+    /* --- Read in my current set of groups --- */
+
+    n_fgr = getgroups(NGROUPS_MAX, from_gr);
+
+    /* --- Now read the groups for the target user --- *
+     *
+     * Do this even if I'm using the @g_keep@ style -- I'll use it for
+     * checking that @group@ is valid.
+     */
+
+    {
+      struct group *gr;
+      char **p;
+
+      n_tgr = 0;
+      to_gr[n_tgr++] = to_pw->pw_gid;
+
+      setgrent();
+      while ((gr = getgrent()) != 0) {
+       if (gr->gr_gid == to_gr[0])
+         continue;
+       for (p = gr->gr_mem; *p; p++) {
+         if (strcmp(to_pw->pw_name, *p) == 0) {
+           to_gr[n_tgr++] = gr->gr_gid;
+           if (n_tgr >= NGROUPS_MAX)
+             goto done_groups;
+           break;
+         }
+       }
+      }
+
+    done_groups:
+      endgrent();
+    }
+
+    /* --- Check that @group@ is reasonable --- */
+
+    {
+      int i;
+
+      if (group == getgid() || group == from_pw->pw_gid)
+       goto group_ok;
+      for (i = 0; i < n_fgr; i++) {
+       if (group == from_gr[i])
+         goto group_ok;
+      }
+      for (i = 0; i < n_tgr; i++) {
+       if (group == to_gr[i])
+         goto group_ok;
+      }
+      die(1, "invalid default group");
+    group_ok:;
+    }
+
+    /* --- Phew.  Now comes the hard bit --- */
+
+    {
+      gid_t ga[4];
+      int i;
+
+      i = 0;
+      ga[i++] = group;
+      if (gstyle & g_keep) {
+       ga[i++] = getgid();
+       ga[i++] = from_pw->pw_gid;
+      }
+      if (gstyle & g_replace)
+       ga[i++] = to_pw->pw_gid;
+
+      /* --- Style authorities will become apoplectic if shown this --- *
+       *
+       * As far as I can see, it's the neatest way of writing it.
+       */
+
+      ngroups = 0;
+      (void)(bc__addGroups(groups, &ngroups, ga, i) ||
+            ((gstyle & g_keep) &&
+             bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
+            ((gstyle & g_replace) &&
+             bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
+    }
+
+#endif
+
+    /* --- Trace the results of all this --- */
+
+    T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
+
+#ifdef HAVE_SETGROUPS
+    IF_TRACING(TRACE_SETUP, {
+      int i;
+
+      for (i = 1; i < ngroups; i++)
+       trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
+    })
+#endif      
   }
 
   /* --- Shell commands are easy --- */
@@ -551,38 +1001,249 @@ int main(int argc, char *argv[])
     binary = todo[0];
   }
 
-  /* --- A login request needs a little bit of work --- */
+  else {
+    flags |= f_shell;
 
-  else if (flags & f_login) {
-    const char *p = strrchr(to_pw->pw_shell, '/');
-    if (p)
-      p++;
-    else
-      p = to_pw->pw_shell;
-    shell[0] = xmalloc(strlen(p) + 2);
-    shell[0][0] = '-';
-    strcpy(shell[0] + 1, p);
-    shell[1] = 0;
-    todo = shell;
-    binary = to_pw->pw_shell;
-  }
+    switch (style) {
 
-  /* --- An unadorned becoming requires little work --- */
+      /* --- An unadorned becoming requires little work --- */
 
-  else {
-    shell[0] = from_pw->pw_shell;
-    shell[1] = 0;
-    todo = shell;
-    binary = todo[0];
+      case l_preserve:
+       shell[0] = getenv("SHELL");
+       if (!shell[0])
+         shell[0] = from_pw->pw_shell;
+       shell[1] = 0;
+       todo = shell;
+       binary = todo[0];
+       break;
+
+      /* --- An su-like login needs slightly less effort --- */
+
+      case l_setuser:
+       shell[0] = to_pw->pw_shell;
+       shell[1] = 0;
+       todo = shell;
+       binary = todo[0];
+       break;
+
+      /* --- A login request needs a little bit of work --- */
+
+      case l_login: {
+       const char *p = strrchr(to_pw->pw_shell, '/');
+
+       if (p)
+         p++;
+       else
+         p = to_pw->pw_shell;
+       shell[0] = xmalloc(strlen(p) + 2);
+       shell[0][0] = '-';
+       strcpy(shell[0] + 1, p);
+       shell[1] = 0;
+       todo = shell;
+       binary = to_pw->pw_shell;
+      } break;
+    }
   }
 
-  /* --- Mangle the environment if login flag given --- */
+  /* --- Mangle the environment --- *
+   *
+   * This keeps getting more complicated all the time.  (How true.  Now I've
+   * got all sorts of nasty environment mangling to do.)
+   *
+   * The environment stuff now happens in seven phases:
+   *
+   *   1. Mark very special variables to be preserved.  Currently only TERM
+   *      and DISPLAY are treated in this way.
+   *
+   *   2. Set and preserve Become's own environment variables.
+   *
+   *   3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
+   *      SHELL and MAIL) if we're being `su'-like or `login'-like.
+   *
+   *   4. If we're preserving the environment or being `su'-like, process the
+   *      PATH variable a little.  Otherwise reset it to something
+   *      appropriate.
+   *
+   *   5. If we're being `login'-like, expunge all unpreserved variables.
+   *
+   *   6. Expunge any security-critical variables.
+   *
+   *   7. Build a new environment table to pass to child processes.
+   */
+
+  {
+    /* --- Variables to be preserved always --- *
+     *
+     * A user can explicitly expunge a variable in this list, in which case
+     * we never get to see it here.
+     */
+
+    static char *preserve[] = {
+      "TERM", "DISPLAY", "TZ", 0
+    };
+
+    /* --- Variables to be expunged --- *
+     *
+     * Any environment string which has one of the following as a prefix will
+     * be expunged from the environment passed to the called process.  The
+     * first line lists variables which have been used to list search paths
+     * for shared libraries: by manipulating these, an attacker could replace
+     * a standard library with one of his own.  The second line lists other
+     * well-known dangerous environment variables.
+     */
+
+    static char *banned[] = {
+      "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
+      "IFS", "ENV", "BASH_ENV", "KRB_CONF",
+      0
+    };
+
+    /* --- Other useful variables --- */
+
+    sym_env *e;
+    char *p, *q, *r;
+    char **pp, **qq;
+    size_t sz;
+    unsigned f;
+    sym_iter i;
+
+    /* --- Stage one.  Preserve display-specific variables --- */
+
+    for (pp = preserve; *pp; pp++) {
+      if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
+       e->f |= envFlag_preserve;
+    }
+
+    /* --- Stage two.  Set Become's own variables --- */
+
+    e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
+    if (!f)
+      bc__setenv(e, from_pw->pw_name);
+    e->f |= envFlag_preserve;
+
+    e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
+    if (!f)
+      bc__setenv(e, from_pw->pw_dir);
+    e->f |= envFlag_preserve;
+
+    bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
+    bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
+    bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
+    bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
+
+    /* --- Stage three.  Set user identity --- */
+
+    switch (style) {
+      case l_login: {
+       static char *maildirs[] = {
+         "/var/spool/mail", "/var/mail",
+         "/usr/spool/mail", "/usr/mail",
+         0
+       };
+       struct stat s;
+       char b[128];
+
+       for (pp = maildirs; *pp; pp++) {
+         if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
+           sprintf(b, "%s/%s", *pp, to_pw->pw_name);
+           bc__putenv("MAIL", b, envFlag_preserve, 0);
+           break;
+         }
+       }
+      } /* Fall through */
+
+      case l_setuser:
+       bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
+       bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
+       bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
+       bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
+       break;
+    }
+
+    /* --- Stage four.  Set the user's PATH properly --- */
+
+    {
+      /* --- Find an existing path --- *
+       *
+       * If there's no path, or this is a login, then set a default path,
+       * unless we're meant to preserve the existing one.  Whew!
+       */
+
+      e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
+
+      if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
+       bc__putenv("PATH",
+                  rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
+                  envFlag_preserve, 0);
+      } else {
 
-  if (flags & f_login) {
-    env = mangled_env;
-    env[1] = bc__makeEnv("USER", to_pw->pw_name);
-    env[2] = bc__makeEnv("LOGNAME", to_pw->pw_name);
-    env[3] = bc__makeEnv("HOME", to_pw->pw_dir);
+       /* --- Find the string --- */
+
+       e->f = envFlag_preserve;
+       p = strchr(e->val, '=') + 1;
+       r = e->val;
+
+       /* --- Write the new version to a dynamically allocated buffer --- */
+
+       e->val = xmalloc(4 + 1 + strlen(p) + 1);
+       strcpy(e->val, "PATH=");
+       q = e->val + 5;
+
+       for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
+         if (p[0] != '/')
+           continue;
+         while (*p)
+           *q++ = *p++;
+         *q++ = ':';
+       }
+       q[-1] = 0;
+
+       /* --- Done! --- */
+
+       free(r);
+      }
+    }
+
+    /* --- Stages five and six.  Expunge variables and count numbers --- *
+     *
+     * Folded together, so I only need one pass through the table.  Also
+     * count the number of variables needed at this time.
+     */
+
+    sz = 0;
+
+    for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
+
+      /* --- Login style expunges all unpreserved variables --- */
+
+      if (style == l_login && ~e->f & envFlag_preserve)
+       goto expunge;
+
+      /* --- Otherwise just check the name against the list --- */
+
+      for (pp = banned; *pp; pp++) {
+       if (**pp == '-') {
+         p = *pp + 1;
+         if (strncmp(e->_base.name, p, strlen(p)) == 0)
+           goto expunge;
+       } else if (strcmp(e->_base.name, *pp) == 0)
+         goto expunge;
+      }
+
+      sz++;
+      continue;
+
+    expunge:
+      sym_remove(&bc__env, e);
+    }
+
+    /* --- Stage seven.  Build the new environment block --- */
+
+    env = qq = xmalloc((sz + 1) * sizeof(*qq));
+
+    for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; )
+      *qq++ = e->val;
+    *qq++ = 0;
   }
 
   /* --- Trace the command --- */
@@ -609,24 +1270,36 @@ int main(int argc, char *argv[])
       p = "/bin:/usr/bin";
     path = xstrdup(p);
 
-    for (p = strtok(path, ":"); (p = strtok(0, ":")) != 0; ) {
+    for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
 
-      /* --- SECURITY: check length of string before copying --- */
+      /* --- Check length of string before copying --- */
 
       if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
        continue;
 
-      /* --- Now build the pathname and check it --- */
+      /* --- Now build the pathname and check it --- *
+       *
+       * Issue: user can take advantage of these privileges to decide whether
+       * a program with a given name exists.  I'm not sure that's
+       * particularly significant: it only works on regular files with
+       * execute permissions, and if you're relying on the names of these
+       * being secret to keep your security up, then you're doing something
+       * deeply wrong anyway.  On the other hand, it's useful to allow people
+       * to be able to execute programs and scripts which they wouldn't
+       * otherwise have access to.  [This problem was brought up on
+       * Bugtraq, as a complaint against sudo.]
+       */
 
-      sprintf(rq.cmd, "%s/%s", p, todo[0]);
+      if (!*p) p = ".";
+      sprintf(rq.cmd, "%s/%s", p, binary);
       if (stat(rq.cmd, &st) == 0 &&    /* Check it exists */
          st.st_mode & 0111 &&          /* Check it's executable */
-         (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
+         S_ISREG(st.st_mode))          /* Check it's a file */
        break;
     }
 
     if (!p)
-      die("couldn't find `%s' in path", todo[0]);
+      die(1, "couldn't find `%s' in path", todo[0]);
     binary = rq.cmd;
     free(path);
   }
@@ -645,7 +1318,7 @@ int main(int argc, char *argv[])
     q = binary;
     if (*q != '/') {
       if (!getcwd(b, sizeof(b)))
-       die("couldn't read current directory: %s", strerror(errno));
+       die(1, "couldn't read current directory: %s", strerror(errno));
       p += strlen(p);
       *p++ = '/';
     }
@@ -654,14 +1327,14 @@ int main(int argc, char *argv[])
 
     while (*q) {
 
-      /* --- SECURITY: check for buffer overflows here --- *
+      /* --- Check for buffer overflows here --- *
        *
        * I write at most one byte per iteration so this is OK.  Remember to
        * allow one for the null byte.
        */
 
       if (p >= b + sizeof(b) - 1)
-       die("internal error: buffer overflow while canonifying path");
+       die(1, "internal error: buffer overflow while canonifying path");
 
       /* --- Reduce multiple slashes to just one --- */
 
@@ -708,10 +1381,24 @@ int main(int argc, char *argv[])
   }
   T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
 
-  /* --- Run the check --- */
+  /* --- Run the check --- *
+   *
+   * If the user is already what she wants to be, then print a warning.
+   * Then, if I was just going to spawn a shell, quit, to reduce user
+   * confusion.  Otherwise, do what was wanted anyway.  Also, don't bother
+   * checking if we're already root -- root can do anything anyway, and at
+   * least this way we get some logging done, and offer a more friendly
+   * front-end.
+   */
 
-  {
-    int a = check(&rq);
+  if (rq.from == rq.to) {
+    moan("you already are `%s'!", to_pw->pw_name);
+    if (flags & f_shell) {
+      moan("(to prevent confusion, I'm not spawning a shell)");
+      exit(0);
+    }
+  } else {
+    int a = (rq.from == 0) || check(&rq);
 
     syslog(LOG_INFO,
           "permission %s for %s to become %s to run `%s'",
@@ -719,7 +1406,7 @@ int main(int argc, char *argv[])
           rq.cmd);
 
     if (!a)
-      die("permission denied");
+      die(1, "permission denied");
   }
 
   /* --- Now do the job --- */
@@ -729,13 +1416,34 @@ int main(int argc, char *argv[])
   if (flags & f_dummy) {
     puts("permission granted");
     return (0);
-  } else {
-    if (setuid(rq.to) == -1 || seteuid(rq.to) == -1)
-      die("couldn't set uid: %s", strerror(errno));
-    execve(rq.cmd, todo, env);
-    die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
-    return (127);
   }
+
+#ifdef HAVE_SETGROUPS
+  if (setgroups(ngroups, groups) < 0)
+    die(1, "couldn't set groups: %s", strerror(errno));
+#endif
+
+  if (setgid(group) < 0)
+    die(1, "couldn't set default group: %s", strerror(errno));
+  if (setuid(rq.to) < 0)
+    die(1, "couldn't set uid: %s", strerror(errno));
+
+  /* --- If this was a login, change current directory --- */
+
+  if ((flags & f_shell) &&
+      style == l_login &&
+      chdir(to_pw->pw_dir) < 0) {
+    moan("couldn't change directory to `%s': %s",
+        to_pw->pw_dir, strerror(errno));
+  }
+
+  /* --- Finally, call the program --- */
+
+  fflush(0);
+  closelog();
+  execve(rq.cmd, todo, env);
+  die(1, "couldn't exec `%s': %s", rq.cmd, strerror(errno));
+  return (127);
 }
 
 /*----- That's all, folks -------------------------------------------------*/