Fix for daemon mode. Oops.
[become] / src / become.c
index e73f5da..60cb1f3 100644 (file)
@@ -1,10 +1,10 @@
 /* -*-c-*-
  *
- * $Id: become.c,v 1.11 1997/09/24 09:48:45 mdw Exp $
+ * $Id: become.c,v 1.23 2003/10/12 10:00:06 mdw Exp $
  *
  * Main code for `become'
  *
- * (c) 1997 EBI
+ * (c) 1998 EBI
  */
 
 /*----- Licensing notice --------------------------------------------------*
 /*----- Revision history --------------------------------------------------*
  *
  * $Log: become.c,v $
- * Revision 1.11  1997/09/24 09:48:45  mdw
+ * Revision 1.23  2003/10/12 10:00:06  mdw
+ * Fix for daemon mode.  Oops.
+ *
+ * Revision 1.22  2003/10/12 00:14:55  mdw
+ * Major overhaul.  Now uses DSA signatures rather than the bogus symmetric
+ * encrypt-and-hope thing.  Integrated with mLib and Catacomb.
+ *
+ * Revision 1.21  1999/07/28 09:31:01  mdw
+ * Empty path components are equivalent to `.'.
+ *
+ * Revision 1.20  1999/05/04 16:17:11  mdw
+ * Change to header file name for parser.  See log for `parse.h' for
+ * details.
+ *
+ * Revision 1.19  1998/06/29  13:10:41  mdw
+ * Add some commentary regarding an issue in `sudo' which affects `become';
+ * I'm not fixing it yet because I don't think it's important.
+ *
+ * Fixed the PATH lookup code to use the right binary name: `binary' rather
+ * than `todo[0]'.  The two only differ when `style' is `l_login', in which
+ * case `binary' has an initial `/' anyway, and the PATH lookup code is
+ * never invoked.  The name is used in a buffer-overflow precheck, though,
+ * and auditing is easier if the naming is consistent.
+ *
+ * Revision 1.18  1998/06/26 10:32:54  mdw
+ * Cosmetic change: use sizeof(destination) in memcpy.
+ *
+ * Revision 1.17  1998/06/18 15:06:59  mdw
+ * Close log before execing program to avoid leaving a socket open.
+ *
+ * Revision 1.16  1998/04/23 13:21:04  mdw
+ * Small tweaks.  Support no-network configuration option, and rearrange
+ * the help text a little.
+ *
+ * Revision 1.15  1998/01/13 11:10:44  mdw
+ * Add `TZ' to the list of variables to be preserved.
+ *
+ * Revision 1.14  1998/01/12 16:45:39  mdw
+ * Fix copyright date.
+ *
+ * Revision 1.13  1997/09/26 09:14:57  mdw
+ * Merged blowfish branch into trunk.
+ *
+ * Revision 1.12  1997/09/25 16:04:48  mdw
+ * Change directory after becoming someone else, instead of before.  This
+ * avoids problems with root-squashed NFS mounts.
+ *
+ * Revision 1.11.2.1  1997/09/26 09:07:58  mdw
+ * Use the Blowfish encryption algorithm instead of IDEA.  This is partly
+ * because I prefer Blowfish (without any particularly strong evidence) but
+ * mainly because IDEA is patented and Blowfish isn't.
+ *
+ * Revision 1.11  1997/09/24  09:48:45  mdw
  * Fix (scary) overrun bug in group allocation stuff.
  *
  * Revision 1.10  1997/09/17  10:14:10  mdw
 
 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"
@@ -108,12 +169,9 @@ 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 "sym.h"
-#include "utils.h"
 #include "userdb.h"
 
 /*----- Type definitions --------------------------------------------------*/
@@ -134,11 +192,9 @@ enum {
 
 /* --- Login behaviour types --- */
 
-enum {
-  l_preserve,                          /* Preserve the environment */
-  l_setuser,                           /* Update who I am */
-  l_login                              /* Do a full login */
-};
+#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 --- *
  *
@@ -352,7 +408,10 @@ static void bc__usage(FILE *fp)
            "Usage: \n"
            "   $ -c <shell-command> <user>\n"
            "   $ [<env-var>] <user> [<command> [<arguments>]...]\n"
-           "   $ -d [-p <port>] [-f <config-file>]\n");
+#ifndef NONETWORK
+           "   $ -d [-p <port>] [-f <config-file>]\n"
+#endif
+    );
 }
 
 /* --- @bc__help@ --- *
@@ -392,7 +451,17 @@ static void bc__help(FILE *fp, int suid)
 "-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"
+"                              [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"
@@ -401,11 +470,15 @@ static void bc__help(FILE *fp, int suid)
 #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");
-#ifdef TRACING
+"-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,
@@ -442,8 +515,10 @@ int main(int argc, char *argv[])
 
   /* --- Become server setup parameters --- */
 
+#ifndef NONETWORK
   char *conffile = file_RULES;         /* Default config file for daemon */
   int port = 0;                                /* Default port for daemon */
+#endif
 
   /* --- Miscellanous shared variables --- */
 
@@ -468,14 +543,13 @@ int main(int argc, char *argv[])
 
   /* --- 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 */
-    f_havegroup = 32                   /* Set a default group */
-  };
+#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 --- */
 
@@ -489,7 +563,7 @@ int main(int argc, char *argv[])
   {
     char **p;
 
-    sym_createTable(&bc__env);
+    sym_create(&bc__env);
     for (p = environ; *p; p++)
       bc__putenv(0, *p, 0, 0);
   }
@@ -528,13 +602,16 @@ int main(int argc, char *argv[])
 
       /* --- Server options --- */
 
+#ifndef NONETWORK
       { "daemon",      0,              0,      'd' },
+      { "nofork",      0,              0,      'n' },
       { "port",                gFlag_argReq,   0,      'p' },
       { "config-file", gFlag_argReq,   0,      'f' },
+#endif
 
       /* --- Tracing options --- */
 
-#ifdef TRACING
+#ifndef NTRACE
       { "impersonate", gFlag_argReq,   0,      'I' },
       { "trace",       gFlag_argOpt,   0,      'T' },
       { "trace-level", gFlag_argOpt,   0,      'L' },
@@ -553,8 +630,10 @@ int main(int argc, char *argv[])
               "g:"                     /* Group (without @setgroups@) */
 #endif
               "c:"                     /* Command to run options */
-              "dp:f:"                  /* Server options */
-#ifdef TRACING
+#ifndef NONETWORK
+              "dnp:f:"                 /* Server options */
+#endif
+#ifndef NTRACE
               "I:T::L::"               /* Tracing options */
 #endif
               ,
@@ -599,7 +678,7 @@ int main(int argc, char *argv[])
        else {
          struct group *gr = getgrnam(optarg);
          if (!gr)
-           die("unknown group `%s'", optarg);
+           die(1, "unknown group `%s'", optarg);
          group = gr->gr_gid;
        }
        flags |= f_havegroup;
@@ -623,22 +702,27 @@ int main(int argc, char *argv[])
 
       /* --- Server options --- */
 
+#ifndef NONETWORK
       case 'p':
        if (isdigit((unsigned char)optarg[0]))
          port = htons(atoi(optarg));
        else {
          struct servent *s = getservbyname(optarg, "udp");
          if (!s)
-           die("unknown service name `%s'", optarg);
+           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 --- *
        *
@@ -646,7 +730,7 @@ 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)
@@ -658,7 +742,7 @@ 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;
@@ -673,7 +757,7 @@ int main(int argc, char *argv[])
        * to!
        */
 
-#ifdef TRACING
+#ifndef TRACE
 
       case 'T': {
        FILE *fp;
@@ -689,12 +773,12 @@ int main(int argc, char *argv[])
          if (seteuid(ru))
 #endif
          {
-           die("couldn't temporarily give up privileges: %s",
+           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));
          }
 
@@ -703,9 +787,9 @@ int main(int argc, char *argv[])
 #else
           if (seteuid(eu))
 #endif
-           die("couldn't regain privileges: %s", strerror(errno));
+           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;
 
@@ -713,80 +797,33 @@ 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 various\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);
-       }
-
-       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);
-         }
-         p++;
-       }
-
-       tracesetlvl(lvl);
-       yydebug = ((lvl & TRACE_YACC) != 0);
+       trace_level(traceopt(lvltbl, optarg, TRACE_DFL,
+                            (flags & f_setuid) ? TRACE_PRIV : 0));
       } break;
 
 #endif
@@ -854,11 +891,13 @@ done_options:
 
   /* --- 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 --- */
 
@@ -879,7 +918,7 @@ done_options:
     else
       pw = getpwnam(who);
     if (!pw)
-      die("unknown user `%s'", who);
+      die(1, "unknown user `%s'", who);
     to_pw = userdb_copyUser(pw);
     rq.to = pw->pw_uid;
   }
@@ -892,7 +931,7 @@ done_options:
     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);
   }
 
@@ -903,8 +942,8 @@ done_options:
     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 --- */
@@ -925,7 +964,7 @@ done_options:
     /* --- Check that it's valid --- */
 
     if (group != from_pw->pw_gid && group != to_pw->pw_gid)
-      die("invalid default group");
+      die(1, "invalid default group");
 
 #else
 
@@ -984,7 +1023,7 @@ done_options:
        if (group == to_gr[i])
          goto group_ok;
       }
-      die("invalid default group");
+      die(1, "invalid default group");
     group_ok:;
     }
 
@@ -1011,7 +1050,7 @@ done_options:
       ngroups = 0;
       (void)(bc__addGroups(groups, &ngroups, ga, i) ||
             ((gstyle & g_keep) &&
-             bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
+             bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
             ((gstyle & g_replace) &&
              bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
     }
@@ -1046,45 +1085,48 @@ done_options:
     binary = todo[0];
   }
 
-  else switch (style) {
+  else {
+    flags |= f_shell;
 
-    /* --- An unadorned becoming requires little work --- */
+    switch (style) {
 
-    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 --- */
+
+      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 --- */
+      /* --- 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;
+      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;
-      chdir(to_pw->pw_dir);
-    } 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 --- *
@@ -1121,7 +1163,7 @@ done_options:
      */
 
     static char *preserve[] = {
-      "TERM", "DISPLAY", 0
+      "TERM", "DISPLAY", "TZ", 0
     };
 
     /* --- Variables to be expunged --- *
@@ -1254,7 +1296,7 @@ done_options:
 
     sz = 0;
 
-    for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
+    for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
 
       /* --- Login style expunges all unpreserved variables --- */
 
@@ -1283,7 +1325,7 @@ done_options:
 
     env = qq = xmalloc((sz + 1) * sizeof(*qq));
 
-    for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
+    for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; )
       *qq++ = e->val;
     *qq++ = 0;
   }
@@ -1319,9 +1361,21 @@ done_options:
       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 */
          S_ISREG(st.st_mode))          /* Check it's a file */
@@ -1329,7 +1383,7 @@ done_options:
     }
 
     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);
   }
@@ -1348,7 +1402,7 @@ done_options:
     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++ = '/';
     }
@@ -1364,7 +1418,7 @@ done_options:
        */
 
       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 --- */
 
@@ -1423,7 +1477,7 @@ done_options:
 
   if (rq.from == rq.to) {
     moan("you already are `%s'!", to_pw->pw_name);
-    if (!cmd && todo == shell) {
+    if (flags & f_shell) {
       moan("(to prevent confusion, I'm not spawning a shell)");
       exit(0);
     }
@@ -1436,7 +1490,7 @@ done_options:
           rq.cmd);
 
     if (!a)
-      die("permission denied");
+      die(1, "permission denied");
   }
 
   /* --- Now do the job --- */
@@ -1450,19 +1504,29 @@ done_options:
 
 #ifdef HAVE_SETGROUPS
   if (setgroups(ngroups, groups) < 0)
-    die("couldn't set groups: %s", strerror(errno));
+    die(1, "couldn't set groups: %s", strerror(errno));
 #endif
 
   if (setgid(group) < 0)
-    die("couldn't set default group: %s", strerror(errno));
+    die(1, "couldn't set default group: %s", strerror(errno));
   if (setuid(rq.to) < 0)
-    die("couldn't set uid: %s", strerror(errno));
+    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("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+  die(1, "couldn't exec `%s': %s", rq.cmd, strerror(errno));
   return (127);
 }