Add support for different login styles, and environment variable
authormdw <mdw>
Fri, 5 Sep 1997 11:45:19 +0000 (11:45 +0000)
committermdw <mdw>
Fri, 5 Sep 1997 11:45:19 +0000 (11:45 +0000)
manipulation in a safe and useful way.

acconfig.h
configure.in
src/become.c

index 1434352..bc8ad47 100644 (file)
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: acconfig.h,v 1.3 1997/08/07 09:33:05 mdw Exp $
+ * $Id: acconfig.h,v 1.4 1997/09/05 11:45:17 mdw Exp $
  *
  * Default settings for `become' config.h
  *
 /*----- Revision history --------------------------------------------------*
  *
  * $Log: acconfig.h,v $
+ * Revision 1.4  1997/09/05 11:45:17  mdw
+ * Add support for different login styles, and environment variable
+ * manipulation in a safe and useful way.
+ *
  * Revision 1.3  1997/08/07 09:33:05  mdw
  * Added `ElectricFence' support.  Other minor cosmetic things.
  *
@@ -55,6 +59,9 @@
 /* Define to be the size of an int.  */
 #define SIZEOF_INT 4
 
+/* Default login style can be @l_preserve@, @l_user@ or @l_login@ */
+#define DEFAULT_LOGIN_STYLE l_preserve
+
 /* This is replaced by `/' by `configure' -- leave alone for DOSness.  */
 #define PATHSEP '\\'
 
index 69b0582..66491c9 100644 (file)
@@ -1,6 +1,6 @@
 dnl -*-fundamental-*-
 dnl
-dnl $Id: configure.in,v 1.4 1997/08/20 16:10:56 mdw Exp $
+dnl $Id: configure.in,v 1.5 1997/09/05 11:45:18 mdw Exp $
 dnl
 dnl Source for auto configuration for `become'
 dnl
@@ -28,11 +28,15 @@ dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 dnl----- Revision history ---------------------------------------------------
 dnl
 dnl $Log: configure.in,v $
-dnl Revision 1.4  1997/08/20 16:10:56  mdw
-dnl Lowercase `mdw_' prefixes to macros.  Add a `--with-etcdir=PATH'
-dnl option.  Update `stamp-h' as required by the Automake docs (silly
-dnl me).
+dnl Revision 1.5  1997/09/05 11:45:18  mdw
+dnl Add support for different login styles, and environment variable
+dnl manipulation in a safe and useful way.
 dnl
+# Revision 1.4  1997/08/20  16:10:56  mdw
+# Lowercase `mdw_' prefixes to macros.  Add a `--with-etcdir=PATH'
+# option.  Update `stamp-h' as required by the Automake docs (silly
+# me).
+#
 dnl Revision 1.3  1997/08/07 09:34:32  mdw
 dnl Added `ElectricFence' support, and support for the `deep' package
 dnl structure.
@@ -93,6 +97,18 @@ dnl --- Check for some useful functions ---
 
 AC_CHECK_FUNCS(getrusage vtimes)
 
+dnl --- Set default become style ---
+
+AC_ARG_ENABLE([style],
+[  --enable-style=STYLE          set default style to preserve, su, or login],
+[case "$withval" in
+  preserve) style="l_preserve" ;;
+  su|user) style="l_user" ;;
+  login) style="l_login" ;;
+esac],
+[style="l_preserve"])
+AC_DEFINE_UNQUOTED(DEFAULT_LOGIN_STYLE, $style)
+
 dnl --- Set configuration directory ---
 
 AC_ARG_WITH([etcdir],
index 6f5c236..c994974 100644 (file)
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: become.c,v 1.4 1997/08/20 16:15:13 mdw Exp $
+ * $Id: become.c,v 1.5 1997/09/05 11:45:19 mdw Exp $
  *
  * Main code for `become'
  *
 /*----- Revision history --------------------------------------------------*
  *
  * $Log: become.c,v $
- * Revision 1.4  1997/08/20 16:15:13  mdw
+ * Revision 1.5  1997/09/05 11:45:19  mdw
+ * Add support for different login styles, and environment variable
+ * manipulation in a safe and useful way.
+ *
+ * Revision 1.4  1997/08/20  16:15:13  mdw
  * Overhaul of environment handling.  Fix daft bug in path search code.
  *
  * Revision 1.3  1997/08/07 16:28:59  mdw
@@ -83,9 +87,38 @@ extern char **environ;
 #include "name.h"
 #include "parser.h"
 #include "rule.h"
+#include "sym.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 --- */
+
+enum {
+  l_preserve,                          /* Preserve the environment */
+  l_user,                              /* Update who I am */
+  l_login                              /* Do a full login */
+};
+
+/*----- Static variables --------------------------------------------------*/
+
+static sym_table bc__env;
+
 /*----- Main code ---------------------------------------------------------*/
 
 /* --- @bc__write@ --- *
@@ -124,6 +157,87 @@ 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 (~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__banner@ --- *
  *
  * Arguments:  @FILE *fp@ = stream to write on
@@ -152,28 +266,10 @@ static void bc__usage(FILE *fp)
   bc__write(fp,
            "Usage: \n"
            "   $ -c <shell-command> <user>\n"
-           "   $ <user> [<command> [<arguments>]...]\n"
+           "   $ [<env-var>] <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);
-}
-
 /* --- @bc__help@ --- *
  *
  * Arguments:  @FILE *fp@ = stream to write on
@@ -204,21 +300,28 @@ static void bc__help(FILE *fp, int suid)
 "\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");
+"-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"
+"\n"
+"-c CMD, --command=CMD         Run the (Bourne) shell command CMD\n"
+"\n"
+"-d, --daemon                  Start a daemon\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");
 #ifdef TRACING
+  bc__write(fp, "\n");
   if (!suid) {
     bc__write(fp,
-"--impersonate=USER    Claim to be USER when asking the server\n");
+"-I USER, --impersonate=USER   Claim to be USER when asking the server\n");
   }
   bc__write(fp,
-"--trace=FILE          Dump trace information to FILE (boring)\n"
-"--trace-level=OPTS    Set level of tracing information\n");
+"-T FILE, --trace=FILE         Dump trace information to FILE (boring)\n"
+"-L OPTS, --trace-level=OPTS   Set level of tracing information\n");
 #endif
 }
 
@@ -240,7 +343,8 @@ 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 */
 
@@ -252,6 +356,7 @@ int main(int argc, char *argv[])
   /* --- Miscellanous shared variables --- */
 
   unsigned flags = 0;                  /* Various useful flags */
+  int style = DEFAULT_LOGIN_STYLE;     /* Login style */
 
   /* --- Default argument list executes a shell command --- */
 
@@ -279,40 +384,71 @@ int main(int argc, char *argv[])
   if (getuid() != geteuid())
     flags |= f_setuid;
 
+  /* --- Read the environment into a hashtable --- */
+
+  {
+    char **p;
+
+    sym_createTable(&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' },
+      { "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 to run options --- */
+
       { "command",     gFlag_argReq,   0,      'c' },
+
+      /* --- Server options --- */
+
       { "daemon",      0,              0,      'd' },
       { "port",                gFlag_argReq,   0,      'p' },
       { "config-file", gFlag_argReq,   0,      'f' },
+
+      /* --- Tracing options --- */
+
 #ifdef TRACING
       { "impersonate", gFlag_argReq,   0,      'I' },
       { "trace",       gFlag_argOpt,   0,      'T' },
       { "trace-level", gFlag_argOpt,   0,      'L' },
 #endif
+
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
+    i = mdwopt(argc, argv,
+              "-" "huv" "esl" "c:" "dp:f:" T("I:T::L:"),
+              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, flags & f_setuid);
        exit(0);
        break;
-      case 'U':
+      case 'u':
        bc__usage(stdout);
        exit(0);
        break;
@@ -321,14 +457,26 @@ 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_user;
+       break;
       case 'l':
-       flags |= f_login;
+       style = l_login;
        break;
+
+      /* --- Command to run options --- */
+
       case 'c':
        cmd = optarg;
        break;
+
+      /* --- Server options --- */
+
       case 'p':
        port = atoi(optarg);
        break;
@@ -475,6 +623,52 @@ int main(int argc, char *argv[])
 
 #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 --- */
+
+       if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
+         if (who == 0)
+           who = optarg;
+         else {
+           optind--;
+           goto done_options;
+         }
+       }
+
+       /* --- 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;
+
       /* --- Something I didn't understand has occurred --- */
 
       case '?':
@@ -482,6 +676,8 @@ int main(int argc, char *argv[])
        break;
     }
   }
+
+done_options:
   if (flags & f_duff) {
     bc__usage(stderr);
     exit(1);
@@ -502,21 +698,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("unknown user `%s'", who);
     to_pw = userdb_copyUser(pw);
     rq.to = pw->pw_uid;
   }
@@ -558,43 +752,85 @@ int main(int argc, char *argv[])
     binary = todo[0];
   }
 
-  /* --- A login request needs a little bit of work --- */
+  else switch (style) {
 
-  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;
-  }
+    /* --- An unadorned becoming requires little work --- */
+
+    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 unadorned becoming requires little work --- */
+    /* --- An su-like login needs slightly less effort --- */
 
-  else {
-    shell[0] = getenv("SHELL");
-    if (!shell[0])
-      shell[0] = from_pw->pw_shell;
-    shell[1] = 0;
-    todo = shell;
-    binary = todo[0];
+    case l_user:
+      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;
+      chdir(to_pw->pw_dir);
+    } break;
   }
 
   /* --- Mangle the environment --- *
    *
-   * This keeps getting more complicated all the time.
+   * 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.
    */
 
   {
-    size_t i, j, b;
-    int pass;
+    /* --- 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.
+     */
 
-    /* --- Expunge some environment variables --- *
+    static char *preserve[] = {
+      "TERM", "DISPLAY", 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.
@@ -604,80 +840,158 @@ int main(int argc, char *argv[])
      * other well-known dangerous environment variables.
      */
 
-    static const char *banned[] = {
-      "LD_", "SHLIB_PATH=", "LIBPATH=", "_RLD_",
-      "IFS=", "ENV=", "BASH_ENV=", "KRB_CONF=",
+    static char *banned[] = {
+      "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
+      "IFS", "ENV", "BASH_ENV", "KRB_CONF",
       0
     };
 
-    /* --- Do this in two passes --- *
-     *
-     * The first pass works out how big the environment block needs to be.
-     * The second actually fills it.
-     */
+    /* --- Other useful variables --- */
 
-    for (pass = 0; pass < 2; pass++) {
-      i = j = 0;
+    sym_env *e;
+    char *p, *q, *r;
+    char **pp, **qq;
+    size_t sz;
+    unsigned f;
+    sym_iter i;
+      
+    /* --- Stage one.  Preserve display-specific variables --- */
 
-      if (flags & f_login) {
+    for (pp = preserve; *pp; pp++) {
+      if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
+       e->f |= envFlag_preserve;
+    }
 
-       /* --- This is a login request --- *
-        *
-        * Erase the existing environment and build a new one.
-        */
+    /* --- Stage two.  Set Become's own variables --- */
 
-       if (!pass)
-         i += 4;
-       else {
-         env[i++] = "PATH=/usr/bin:/bin";
-         env[i++] = bc__makeEnv("USER", to_pw->pw_name);
-         env[i++] = bc__makeEnv("LOGNAME", to_pw->pw_name);
-         env[i++] = bc__makeEnv("HOME", to_pw->pw_dir);
-       }
-      } else {
+    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;
 
-       /* --- Normal request --- *
-        *
-        * Remove dangerous variables from the list.
-        */
+    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);
 
-       for (j = 0; environ[j]; j++) {
-         for (b = 0; banned[b]; b++) {
-           if (memcmp(environ[j], banned[b], strlen(banned[b])) == 0)
-             goto skip_var;
+    /* --- 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;
          }
-         if (pass)
-           env[i] = environ[j];
-         i++;
-       skip_var:;
        }
-      }
+      } /* Fall through */
+
+      case l_user:
+       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 --- */
 
-      /* --- Now add our own variables --- *
+    {
+      /* --- Find an existing path --- *
        *
-       * The following are supplied only to help people construct startup
-       * scripts.  Anyone who relies on them being accurate for
-       * authentication purposes will get exactly what they deserve.
+       * 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!
        */
 
-      if (!pass)
-       i += 4;
-      else {
-       env[i++] = bc__makeEnv("BECOME_OLDUSER", from_pw->pw_name);
-       env[i++] = bc__makeEnv("BECOME_OLDHOME", from_pw->pw_dir);
-       env[i++] = bc__makeEnv("BECOME_USER", to_pw->pw_name);
-       env[i++] = bc__makeEnv("BECOME_HOME", to_pw->pw_dir);
+      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 {
+
+       /* --- 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;
 
-      /* --- Allocate memory after the first pass is complete --- */
+    for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
 
-      if (pass)
-       env[i] = 0;
-      i++;
+      /* --- Login style expunges all unpreserved variables --- */
 
-      if (!pass)
-       env = xmalloc(i * sizeof(env[0]));
+      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 (memcmp(e->_base.name, p, strlen(p)) == 0)
+           goto expunge;
+       } else if (strcmp(e->_base.name, p) == 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_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
+      *qq++ = e->val;
+    *qq++ = 0;
   }
 
   /* --- Trace the command --- */