http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.23.tar.gz
[xscreensaver] / driver / setuid.c
index 83ad494e364edacfcb998ba30f578d1b62a7785b..86d3535afd3f04730c2cb415c99277fdd80ee605 100644 (file)
@@ -1,5 +1,5 @@
 /* setuid.c --- management of runtime privileges.
- * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-1998, 2005 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -41,7 +41,7 @@ uid_gid_string (uid_t uid, gid_t gid)
   struct group *g = 0;
   p = getpwuid (uid);
   g = getgrgid (gid);
-  sprintf (buf, "%s/%s (%ld/%ld)",
+  sprintf (buf, "%.100s/%.100s (%ld/%ld)",
           (p && p->pw_name ? p->pw_name : "???"),
           (g && g->gr_name ? g->gr_name : "???"),
           (long) uid, (long) gid);
@@ -74,13 +74,50 @@ describe_uids (saver_info *si, FILE *out)
 }
 
 
+/* Returns true if we need to call setgroups().
+
+   Without calling setgroups(), the process will retain any supplementary
+   gids associated with the uid, e.g.:
+
+       % groups root
+       root : root bin daemon sys adm disk wheel
+
+   However, setgroups() can only be called by root, and returns EPERM
+   for other users even if the call would be a no-op (e.g., setting the
+   group list to the current list.)  So, to avoid that spurious error,
+   before calling setgroups() we first check whether the current list
+   of groups contains only one element, our target group.  If so, we
+   don't need to call setgroups().
+ */
 static int
-set_ids_by_name (struct passwd *p, struct group *g, char **message_ret)
+setgroups_needed_p (uid_t target_group)
+{
+  gid_t groups[1024];
+  int n = getgroups (sizeof(groups)-1, groups);
+  if (n < 0)
+    {
+      char buf [1024];
+      sprintf (buf, "%s: getgroups(%ld, ...)", blurb(), (long)sizeof(groups)-1);
+      perror (buf);
+      return 1;
+    }
+  else if (n == 0)            /* an empty list means only egid is in effect. */
+    return 0;
+  else if (n == 1 && groups[0] == target_group)   /* one element, the target */
+    return 0;
+  else                        /* more than one, or the wrong one. */
+    return 1;
+}
+
+
+static int
+set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
 {
   int uid_errno = 0;
   int gid_errno = 0;
-  uid_t uid = p->pw_uid;
-  gid_t gid = g->gr_gid;
+  int sgs_errno = 0;
+  struct passwd *p = getpwuid (uid);
+  struct group  *g = getgrgid (gid);
 
   if (message_ret)
     *message_ret = 0;
@@ -90,11 +127,17 @@ set_ids_by_name (struct passwd *p, struct group *g, char **message_ret)
      -1, then that would be Really Bad.  Rumor further has it that such
      systems really ought to be using -2 for "nobody", since that works.
      So, if we get a uid (or gid, for good measure) of -1, switch to -2
-     instead.
+     instead.  Note that this must be done after we've looked up the
+     user/group names with getpwuid(-1) and/or getgrgid(-1).
    */
   if (gid == (gid_t) -1) gid = (gid_t) -2;
   if (uid == (uid_t) -1) uid = (uid_t) -2;
 
+  errno = 0;
+  if (setgroups_needed_p (gid) &&
+      setgroups (1, &gid) < 0)
+    sgs_errno = errno ? errno : -1;
+
   errno = 0;
   if (setgid (gid) != 0)
     gid_errno = errno ? errno : -1;
@@ -103,11 +146,12 @@ set_ids_by_name (struct passwd *p, struct group *g, char **message_ret)
   if (setuid (uid) != 0)
     uid_errno = errno ? errno : -1;
 
-  if (uid_errno == 0 && gid_errno == 0)
+  if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
     {
       static char buf [1024];
-      sprintf (buf, "changed uid/gid to %s/%s (%ld/%ld).",
-              p->pw_name, (g ? g->gr_name : "???"),
+      sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).",
+              (p && p->pw_name ? p->pw_name : "???"),
+               (g && g->gr_name ? g->gr_name : "???"),
               (long) uid, (long) gid);
       if (message_ret)
        *message_ret = buf;
@@ -116,71 +160,76 @@ set_ids_by_name (struct passwd *p, struct group *g, char **message_ret)
   else
     {
       char buf [1024];
+      gid_t groups[1024];
+      int n;
+
+      if (sgs_errno)
+       {
+         sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
+                  blurb(),
+                  (g && g->gr_name ? g->gr_name : "???"),
+                  (long) gid);
+         if (sgs_errno == -1)
+           fprintf(stderr, "%s: unknown error\n", buf);
+         else
+            {
+              errno = sgs_errno;
+              perror(buf);
+            }
+
+         fprintf (stderr, "%s: effective group list: ", blurb());
+          n = getgroups (sizeof(groups)-1, groups);
+          if (n < 0)
+            fprintf (stderr, "unknown!\n");
+          else
+            {
+              int i;
+              fprintf (stderr, "[");
+              for (i = 0; i < n; i++)
+                {
+                  g = getgrgid (groups[i]);
+                  if (i > 0) fprintf (stderr, ", ");
+                  if (g && g->gr_name) fprintf (stderr, "%s", g->gr_name);
+                  else fprintf (stderr, "%ld", (long) groups[i]);
+                }
+              fprintf (stderr, "]\n");
+            }
+        }
+
       if (gid_errno)
        {
-         sprintf (buf, "%s: couldn't set gid to %s (%ld)",
+         sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
                   blurb(),
-                  (g ? g->gr_name : "???"),
+                  (g && g->gr_name ? g->gr_name : "???"),
                   (long) gid);
          if (gid_errno == -1)
            fprintf(stderr, "%s: unknown error\n", buf);
          else
-           perror(buf);
+            {
+              errno = gid_errno;
+              perror(buf);
+            }
        }
 
       if (uid_errno)
        {
-         sprintf (buf, "%s: couldn't set uid to %s (%ld)",
+         sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
                   blurb(),
-                  (p ? p->pw_name : "???"),
+                  (p && p->pw_name ? p->pw_name : "???"),
                   (long) uid);
          if (uid_errno == -1)
            fprintf(stderr, "%s: unknown error\n", buf);
          else
-           perror(buf);
+            {
+              errno = uid_errno;
+              perror(buf);
+            }
        }
 
       return -1;
     }
 }
 
-static int
-set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
-{
-  struct passwd *p;
-  struct group *g;
-
-  errno = 0;
-  p = getpwuid (uid);
-  if (!p)
-    {
-      char buf [1024];
-      sprintf (buf, "%s: error looking up name of user %d", blurb(),
-              (long) uid);
-      if (errno)
-       perror (buf);
-      else
-       fprintf (stderr, "%s: unknown error.\n", buf);
-      return -1;
-    }
-
-  errno = 0;
-  g = getgrgid (gid);
-  if (!g)
-    {
-      char buf [1024];
-      sprintf (buf, "%s: error looking up name of group %d", blurb(),
-              (long) gid);
-      if (errno)
-       perror (buf);
-      else
-       fprintf (stderr, "%s: unknown error.\n", buf);
-      return -1;
-    }
-
-  return set_ids_by_name (p, g, message_ret);
-}
-
 
 /* If we've been run as setuid or setgid to someone else (most likely root)
    turn off the extra permissions so that random user-specified programs
@@ -298,7 +347,7 @@ hack_uid (saver_info *si)
        !strcmp (p->pw_name, "games"))
       {
        static char buf [1024];
-       sprintf (buf, "running as %s",
+       sprintf (buf, "running as %.100s",
                 (p && p->pw_name && *p->pw_name
                  ? p->pw_name : "<unknown>"));
        si->nolock_reason = buf;