http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.23.tar.gz
[xscreensaver] / driver / setuid.c
index 343dcf097dba0bb5b0b8a234492420a6ddeb6145..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,11 +74,48 @@ 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
+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;
+  int sgs_errno = 0;
   struct passwd *p = getpwuid (uid);
   struct group  *g = getgrgid (gid);
 
@@ -96,6 +133,11 @@ set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
   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;
@@ -104,10 +146,10 @@ set_ids_by_number (uid_t uid, gid_t gid, 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).",
+      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);
@@ -118,28 +160,70 @@ set_ids_by_number (uid_t uid, gid_t gid, 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->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->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;
@@ -263,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;