399f7df05ebc391402591f7fe9f1d0c277010ba7
[xscreensaver] / driver / setuid.c
1 /* setuid.c --- management of runtime privileges.
2  * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #ifndef NO_SETUID /* whole file */
18
19 #include <X11/Xlib.h>           /* not used for much... */
20
21 /* This file doesn't need the Xt headers, so stub these types out... */
22 #undef XtPointer
23 #define XtAppContext void*
24 #define XrmDatabase  void*
25 #define XtIntervalId void*
26 #define XtPointer    void*
27 #define Widget       void*
28
29 #include "xscreensaver.h"
30
31 #ifndef EPERM
32 #include <errno.h>
33 #endif
34
35 #include <pwd.h>                /* for getpwnam() and struct passwd */
36 #include <grp.h>                /* for getgrgid() and struct group */
37
38
39 static const char *
40 uid_gid_string(uid_t uid, gid_t gid)
41 {
42   static char buf[255];
43   struct passwd *p = 0;
44   struct group *g = 0;
45   p = getpwuid (uid);
46   g = getgrgid (gid);
47   sprintf (buf, "%s/%s (%ld/%ld)",
48            (p && p->pw_name ? p->pw_name : "???"),
49            (g && g->gr_name ? g->gr_name : "???"),
50            (long) uid, (long) gid);
51   return buf;
52 }
53
54
55 void
56 describe_uids (saver_info *si, FILE *out)
57 {
58   uid_t uid = getuid();
59   gid_t gid = getgid();
60   uid_t euid = geteuid();
61   gid_t egid = getegid();
62   char *s1 = strdup (uid_gid_string (uid, gid));
63   char *s2 = strdup (uid_gid_string (euid, egid));
64
65   if (si->orig_uid && *si->orig_uid &&
66       (!!strcmp (si->orig_uid, s1) ||
67        !!strcmp (si->orig_uid, s2)))
68     fprintf (out, "%s: initial effective uid/gid was %s\n", blurb(),
69              si->orig_uid);
70
71   fprintf (out, "%s: running as %s", blurb(), s1);
72   if (uid != euid || gid != egid)
73     fprintf (out, "; effectively %s", s2);
74   fprintf(out, "\n");
75   free(s1);
76   free(s2);
77 }
78
79
80 static int
81 set_ids_by_name (struct passwd *p, struct group *g, char **message_ret)
82 {
83   int uid_errno = 0;
84   int gid_errno = 0;
85   uid_t uid = p->pw_uid;
86   gid_t gid = g->gr_gid;
87
88   if (message_ret)
89     *message_ret = 0;
90
91   /* Rumor has it that some implementations of of setuid() do nothing
92      when called with -1; therefore, if the "nobody" user has a uid of
93      -1, then that would be Really Bad.  Rumor further has it that such
94      systems really ought to be using -2 for "nobody", since that works.
95      So, if we get a uid (or gid, for good measure) of -1, switch to -2
96      instead.
97    */
98   if (gid == (gid_t) -1) gid = (gid_t) -2;
99   if (uid == (uid_t) -1) uid = (uid_t) -2;
100
101   errno = 0;
102   if (setgid (gid) != 0)
103     gid_errno = errno ? errno : -1;
104
105   errno = 0;
106   if (setuid (uid) != 0)
107     uid_errno = errno ? errno : -1;
108
109   if (uid_errno == 0 && gid_errno == 0)
110     {
111       static char buf [1024];
112       sprintf (buf, "changed uid/gid to %s/%s (%ld/%ld).",
113                p->pw_name, (g ? g->gr_name : "???"),
114                (long) uid, (long) gid);
115       if (message_ret)
116         *message_ret = buf;
117       return 0;
118     }
119   else
120     {
121       char buf [1024];
122       if (gid_errno)
123         {
124           sprintf (buf, "%s: couldn't set gid to %s (%ld)",
125                    blurb(),
126                    (g ? g->gr_name : "???"),
127                    (long) gid);
128           if (gid_errno == -1)
129             fprintf(stderr, "%s: unknown error\n", buf);
130           else
131             perror(buf);
132         }
133
134       if (uid_errno)
135         {
136           sprintf (buf, "%s: couldn't set uid to %s (%ld)",
137                    blurb(),
138                    (p ? p->pw_name : "???"),
139                    (long) uid);
140           if (uid_errno == -1)
141             fprintf(stderr, "%s: unknown error\n", buf);
142           else
143             perror(buf);
144         }
145
146       return -1;
147     }
148 }
149
150 static int
151 set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
152 {
153   struct passwd *p;
154   struct group *g;
155
156   errno = 0;
157   p = getpwuid (uid);
158   if (!p)
159     {
160       char buf [1024];
161       sprintf (buf, "%s: error looking up name of user %d", blurb(),
162                (long) uid);
163       if (errno)
164         perror (buf);
165       else
166         fprintf (stderr, "%s: unknown error.\n", buf);
167       return -1;
168     }
169
170   errno = 0;
171   g = getgrgid (gid);
172   if (!g)
173     {
174       char buf [1024];
175       sprintf (buf, "%s: error looking up name of group %d", blurb(),
176                (long) gid);
177       if (errno)
178         perror (buf);
179       else
180         fprintf (stderr, "%s: unknown error.\n", buf);
181       return -1;
182     }
183
184   return set_ids_by_name (p, g, message_ret);
185 }
186
187
188 /* If we've been run as setuid or setgid to someone else (most likely root)
189    turn off the extra permissions so that random user-specified programs
190    don't get special privileges.  (On some systems it is necessary to install
191    this program as setuid root in order to read the passwd file to implement
192    lock-mode.)
193  */
194 void
195 hack_uid (saver_info *si)
196 {
197
198   /* Discard privileges, and set the effective user/group ids to the
199      real user/group ids.  That is, give up our "chmod +s" rights.
200    */
201   {
202     uid_t euid = geteuid();
203     gid_t egid = getegid();
204     uid_t uid = getuid();
205     gid_t gid = getgid();
206
207     si->orig_uid = strdup (uid_gid_string (euid, egid));
208
209     if (uid != euid || gid != egid)
210       if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
211         saver_exit (si, 1, 0);
212   }
213
214   /* Locking can't work when running as root, because we have no way of
215      knowing what the user id of the logged in user is (so we don't know
216      whose password to prompt for.)
217    */
218   if (getuid() == (uid_t) 0)
219     {
220       si->locking_disabled_p = True;
221       si->nolock_reason = "running as root";
222     }
223
224   /* If we're running as root, switch to a safer user.  This is above and
225      beyond the fact that we've disabling locking, above -- the theory is
226      that running graphics demos as root is just always a stupid thing
227      to do, since they have probably never been security reviewed and are
228      more likely to be buggy than just about any other kind of program.
229    */
230   if (getuid() == (uid_t) 0)
231     {
232       struct passwd *p;
233
234       p = getpwnam ("nobody");
235       if (! p) p = getpwnam ("noaccess");
236       if (! p) p = getpwnam ("daemon");
237       if (! p)
238         {
239           fprintf (stderr,
240                    "%s: running as root, and couldn't find a safer uid.\n",
241                    blurb());
242           saver_exit(si, 1, 0);
243         }
244
245       if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
246         saver_exit (si, -1, 0);
247     }
248
249
250   /* If there's anything even remotely funny looking about the passwd struct,
251      or if we're running as some other user from the list below (a
252      non-comprehensive selection of users known to be privileged in some way,
253      and not normal end-users) then disable locking.  If it was possible,
254      switching to "nobody" would be the thing to do, but only root itself has
255      the privs to do that.
256    */
257   {
258     uid_t uid = getuid ();              /* get it again */
259     struct passwd *p = getpwuid (uid);  /* get it again */
260
261     if (!p ||
262         uid == (uid_t)  0 ||
263         uid == (uid_t) -1 ||
264         uid == (uid_t) -2 ||
265         p->pw_uid == (uid_t)  0 ||
266         p->pw_uid == (uid_t) -1 ||
267         p->pw_uid == (uid_t) -2 ||
268         !p->pw_name ||
269         !*p->pw_name ||
270         !strcmp (p->pw_name, "root") ||
271         !strcmp (p->pw_name, "nobody") ||
272         !strcmp (p->pw_name, "noaccess") ||
273         !strcmp (p->pw_name, "operator") ||
274         !strcmp (p->pw_name, "daemon") ||
275         !strcmp (p->pw_name, "bin") ||
276         !strcmp (p->pw_name, "adm") ||
277         !strcmp (p->pw_name, "sys") ||
278         !strcmp (p->pw_name, "games"))
279       {
280         static char buf [1024];
281         sprintf (buf, "running as %s",
282                  (p && p->pw_name && *p->pw_name
283                   ? p->pw_name : "<unknown>"));
284         si->nolock_reason = buf;
285         si->locking_disabled_p = True;
286         si->dangerous_uid_p = True;
287       }
288   }
289 }
290
291 #else  /* !NO_SETUID */
292
293 void hack_uid (saver_info *si) { }
294 void describe_uids (saver_info *si, FILE *out) { }
295
296 #endif /* NO_SETUID */