Allow default port to be given as a service name or port number. Handle
authormdw <mdw>
Wed, 10 Sep 1997 10:28:05 +0000 (10:28 +0000)
committermdw <mdw>
Wed, 10 Sep 1997 10:28:05 +0000 (10:28 +0000)
groups properly (lots of options here).

src/become.c

index d43bb36..2154e4b 100644 (file)
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: become.c,v 1.8 1997/09/08 13:56:24 mdw Exp $
+ * $Id: become.c,v 1.9 1997/09/10 10:28:05 mdw Exp $
  *
  * Main code for `become'
  *
 /*----- Revision history --------------------------------------------------*
  *
  * $Log: become.c,v $
- * Revision 1.8  1997/09/08 13:56:24  mdw
+ * Revision 1.9  1997/09/10 10:28:05  mdw
+ * Allow default port to be given as a service name or port number.  Handle
+ * groups properly (lots of options here).
+ *
+ * Revision 1.8  1997/09/08  13:56:24  mdw
  * Change criteria for expunging items from the user's PATH: instead of
  * removing things starting with `.', remove things not starting with `/'.
  *
@@ -66,6 +70,7 @@
 
 #include <ctype.h>
 #include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -83,6 +88,7 @@
 #include <arpa/inet.h>
 
 #include <netdb.h>
+#include <grp.h>
 #include <pwd.h>
 #include <syslog.h>
 #include <unistd.h>
@@ -124,10 +130,26 @@ enum {
 
 enum {
   l_preserve,                          /* Preserve the environment */
-  l_user,                              /* Update who I am */
+  l_setuser,                           /* Update who I am */
   l_login                              /* 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;
@@ -230,7 +252,7 @@ static sym_env *bc__putenv(const char *var, const char *val,
 
   if (val) {
     e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
-    if (~e->f & envFlag_preserve || force) {
+    if (!f || ~e->f & envFlag_preserve || force) {
       if (f)
        free(e->val);
       bc__setenv(e, val);
@@ -251,6 +273,50 @@ static sym_env *bc__putenv(const char *var, const char *val,
   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
@@ -321,6 +387,13 @@ static void bc__help(FILE *fp, int suid)
 "-s, --su, --set-user          Set environment variables to reflect USER\n"
 "-l, --login                   Really log in as USER\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"
 "\n"
 "-d, --daemon                  Start a daemon\n"
@@ -370,6 +443,13 @@ int main(int argc, char *argv[])
 
   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 --- */
 
@@ -387,7 +467,8 @@ int main(int argc, char *argv[])
     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_setuid = 16,                     /* We're running setuid */
+    f_havegroup = 32                   /* Set a default group */
   };
 
   /* --- Set up the program name --- */
@@ -426,6 +507,15 @@ int main(int argc, char *argv[])
       { "set-user",    0,              0,      's' },
       { "login",       0,              0,      'l' },
 
+      /* --- Group style options --- */
+
+      { "group",       gFlag_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",     gFlag_argReq,   0,      'c' },
@@ -448,7 +538,20 @@ int main(int argc, char *argv[])
     };
 
     i = mdwopt(argc, argv,
-              "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
+              "-"                      /* 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 */
+              "dp:f:"                  /* Server options */
+#ifdef TRACING
+              "I:T::L::"               /* Tracing options */
+#endif
+              ,
               opts, 0, 0, gFlag_envVar);
     if (i < 0)
       goto done_options;
@@ -476,12 +579,36 @@ int main(int argc, char *argv[])
        style = l_preserve;
        break;
       case 's':
-       style = l_user;
+       style = l_setuser;
        break;
       case 'l':
        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("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':
@@ -491,7 +618,14 @@ int main(int argc, char *argv[])
       /* --- Server options --- */
 
       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("unknown service name `%s'", optarg);
+         port = s->s_port;
+       }
        break;
       case 'd':
        flags |= f_daemon;
@@ -766,6 +900,131 @@ done_options:
     memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
   }
 
+  /* --- 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("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("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 --- */
 
   if (cmd) {
@@ -795,7 +1054,7 @@ done_options:
 
     /* --- An su-like login needs slightly less effort --- */
 
-    case l_user:
+    case l_setuser:
       shell[0] = to_pw->pw_shell;
       shell[1] = 0;
       todo = shell;
@@ -928,7 +1187,7 @@ done_options:
        }
       } /* Fall through */
 
-      case l_user:
+      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);
@@ -1002,7 +1261,7 @@ done_options:
          p = *pp + 1;
          if (memcmp(e->_base.name, p, strlen(p)) == 0)
            goto expunge;
-       } else if (strcmp(e->_base.name, p) == 0)
+       } else if (strcmp(e->_base.name, *pp) == 0)
          goto expunge;
       }
 
@@ -1149,7 +1408,10 @@ done_options:
    *
    * 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.
+   * 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.
    */
 
   if (rq.from == rq.to) {
@@ -1159,7 +1421,7 @@ done_options:
       exit(0);
     }
   } else {
-    int a = check(&rq);
+    int a = (rq.from == 0) || check(&rq);
 
     syslog(LOG_INFO,
           "permission %s for %s to become %s to run `%s'",
@@ -1177,14 +1439,24 @@ done_options:
   if (flags & f_dummy) {
     puts("permission granted");
     return (0);
-  } else {
-    if (setuid(rq.to) == -1)
-      die("couldn't set uid: %s", strerror(errno));
-    fflush(0);
-    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("couldn't set groups: %s", strerror(errno));
+#endif
+
+  if (setgid(group) < 0)
+    die("couldn't set default group: %s", strerror(errno));
+  if (setuid(rq.to) < 0)
+    die("couldn't set uid: %s", strerror(errno));
+
+  /* --- Finally, call the program --- */
+
+  fflush(0);
+  execve(rq.cmd, todo, env);
+  die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+  return (127);
 }
 
 /*----- That's all, folks -------------------------------------------------*/