Upload to unstable
[pkg-xorg/debian/xorg.git] / debian / local / xserver-wrapper.c
1 /* xserver-wrapper.c - a simple wrapper for X servers that decides whether to
2  * let them be run.
3  *
4  * By Stephen Early
5  *
6  * Stephen Early: modified to use /etc/X11/X symlink with security level of
7  *                'Console' if /etc/X11/Xserver does not exist
8  * Mark W. Eichin: permit non-privileged -showconfig (6 May 1997)
9  * Mark W. Eichin: fix sense of error check for -showconfig (11 May 1997)
10  * Mark W. Eichin: drop privileges on alternate -config, even if we do pass the
11  *                 security check, to prevent using the error handling to read
12  *                 the first line of any protected file (19 Sep 1997)
13  * Erik Troan: prevent buffer overruns (25 Mar 1998)
14  * Topi Miettinen: plug file descriptor leak (26 Apr 1998)
15  * Branden Robinson: only fclose() if file was opened (3 May 1999)
16  * Colin Phipps: minor device number check should be < 64, not < 128, or we
17  *               catch serial terminals (26 Feb 2000)
18  * Branden Robinson: ensure sanity of X server socket directory (13 Jun 2000)
19  * Branden Robinson: make all paths #defines
20  *                   more helpful socket dir error messages (29 Jun 2000)
21  * Branden Robinson: bail out if the config file contains only the silly
22  *                   default X server name (XF86_NONE) (30 Jul 2000)
23  * Branden Robinson: increase verbosity when wrapper config not found
24  *                   (2 Oct 2000)
25  * Branden Robinson:
26  *   - new configuration file, Xwrapper.config, with different format
27  *     (name=value)
28  *   - now just exec's /etc/X11/X; whatever this symlink points
29  *     to will be used as the X server
30  *   - config file specifies allowed user types as before (root only,
31  *     console users, anyone)
32  *   - config file specifies nice value to use for server
33  *   (17 Nov 2000)
34  * Branden Robinson: now accepts hyphens in variable contents (24 Nov 2000)
35  * Branden Robinson: fix dumb errors left over from debugging (3 Dec 2000)
36  * Branden Robinson: let root start the server even if he isn't on a
37  *                    console, and the security level is console (11 Dec 2000)
38  * Branden Robinson: check out the X server symlink with readlink; abort if
39  *                   it's not a symlink, or if it points back to this wrapper
40  *                   (24 Feb 2001)
41  * Branden Robinson: whoops; readlink() doesn't null-terminate the target
42  *                   string (27 Feb 20001)
43  * Branden Robinson: add more info to "suspicious" error messages (16 Mar 2001)
44  * Branden Robinson: also allow unprivileged use of "-version" option
45  *                   (13 Jul 2001)
46  * Branden Robinson: check mode of DRI device directory, if it exists, and warn
47  *                   if it is weird (28 Aug 2001)
48  * Branden Robinson: skip lines in Xwrapper.config that don't match expected
49  *                   format (9 Dec 2001)
50  * Branden Robinson: fix logic that was supposed to also allow unprivileged use
51  *                   of "-version" option but which actually forbade both
52  *                   "-showconfig" and "-verbose"; also let unprivileged users
53  *                   specify "-help" option to get a usage message (26 Dec 2001)
54  * Branden Robinson: change nice() usage to fit SuSv2 semantics; see Debian Bug
55  *                   #140012 (2 Apr 2002)
56  * Branden Robinson: *sigh* Ben Collins changed our FROZEN C library back to
57  *                   pre-SuSv2 nice() semantics, so rewrote the nice() error
58  *                   handling; also correct limits on legal nice values from
59  *                   -20 <= x <= 20 to -20 <= x <= 19 (29 Apr 2002)
60  * Branden Robinson: make the nice() error handling switchable with a #define
61  *                   between SuSv2 semantics and old-style semantics
62  *                   (16 Oct 2002)
63  * Branden Robinson: stop using the GNU extension strnlen() to appease the
64  *                   Debian GNU/NetBSD geeks, who are using BSD's C library
65  *                   (16 Oct 2002)
66  * Branden Robinson: chdir() to the directory where the X server symlink is kept
67  *                   before executing its target, so that relative symlinks work
68  *                   (1 Aug 2003)
69  * Guillem Jover: add console detection support for GNU/kFreeBSD, and some
70  *                messages at build and run time to allow the user to know
71  *                what failed on unsupported systems
72  *                (30 Mar 2007)
73  * Brice Goglin: drop privileges on alternate config file given with
74  *               -xf86config (14 Jun 2007)
75  * Loïc Minier: on Linux, also consider alternate tty devices (major 5 and
76  *              minor < 64) as consoles (24 Sep 2008)
77  * Julien Cristau: remove the nice_value option
78  * Julien Cristau: recognize /usr/bin/X as a path to this wrapper (6 Jun 2009)
79  * Julien Cristau: don't print an error message if Xwrapper.config doesn't exist
80  *                 (11 Aug 2009)
81  * Julien Cristau: allow unprivileged -showDefaultModulePath and
82  *                 -showDefaultLibPath options (11 Aug 2009)
83  * Julien Cristau: don't check the mode of the DRI device directory
84  *                 (11 Aug 2009)
85  * Julien Cristau: also drop group privileges (1 Nov 2011)
86  * Julien Cristau: disallow major 5 again for consoles (15 Dec 2011)
87  *
88  * This is free software; you may redistribute it and/or modify
89  * it under the terms of the GNU General Public License as
90  * published by the Free Software Foundation; either version 2,
91  * or (at your option) any later version.
92  *
93  * This is distributed in the hope that it will be useful, but
94  * WITHOUT ANY WARRANTY; without even the implied warranty of
95  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
96  * GNU General Public License for more details.
97  *
98  * You should have received a copy of the GNU General Public License with
99  * the Debian operating system, in /usr/share/common-licenses/GPL;  if
100  * not, write to the Free Software Foundation, Inc., 59 Temple Place,
101  * Suite 330, Boston, MA 02111-1307 USA
102  *
103  */
104
105 #include <ctype.h>
106 #include <errno.h>
107 #include <fcntl.h>
108 #include <stdio.h>
109 #include <stdlib.h>
110 #include <string.h>
111 #include <unistd.h>
112 #include <sys/stat.h>
113 #include <sys/types.h>
114
115 #if defined(__linux__)
116 #define TTY_MAJOR_DEV 4
117 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
118 #include <sys/consio.h>
119 #endif
120
121 #define X_WRAPPER_CONFIG_FILE "/etc/X11/Xwrapper.config"
122 #define X_SERVER_SYMLINK_DIR "/etc/X11"
123 #define X_SERVER_SYMLINK "/etc/X11/X"
124 #define X_SOCKET_DIR "/tmp/.X11-unix"
125 #define X_SOCKET_DIR_MODE (S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
126
127 #ifndef FALSE
128 #define FALSE 0
129 #endif
130 #ifndef TRUE
131 #define TRUE 1
132 #endif
133
134 typedef enum {
135   RootOnly,
136   Console,
137   Anybody
138 } SecurityLevel;
139
140 static SecurityLevel
141 getSecLevel(char *security)
142 {
143   char *c;
144
145   for (c = security; *c; c++) *c = toupper(*c);
146
147   if (strncmp(security,"ROOTONLY",8) == 0) return RootOnly;
148   if (strncmp(security,"CONSOLE",7) == 0) return Console;
149   if (strncmp(security,"ANYBODY",7) == 0) return Anybody;
150   return RootOnly;
151 }
152
153 static int
154 onConsole()
155 {
156 #if defined(__linux__)
157   struct stat s;
158
159   /* see if stdin is a virtual console device */
160   if (fstat(0, &s) != 0) {
161     (void) fprintf(stderr, "X: cannot stat stdin\n");
162     return FALSE;
163   }
164   if (S_ISCHR(s.st_mode) &&
165         (major(s.st_rdev) == TTY_MAJOR_DEV &&
166          minor(s.st_rdev) < 64)) {
167     return TRUE;
168   }
169 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
170   int idx;
171
172   if (ioctl(0, VT_GETINDEX, &idx) != -1)
173     return TRUE;
174 #else
175 #warning This program needs porting to your kernel.
176   (void) fprintf(stderr, "X: unable to determine if running on a console\n");
177 #endif
178
179   return FALSE;
180 }
181
182 static int
183 checkSecLevel(SecurityLevel level)
184 {
185   switch (level) {
186   case RootOnly:
187     if (getuid() == 0) { /* real uid is root */
188       return TRUE;
189     } else {
190       return FALSE;
191     }
192     break;
193   case Console:
194     if (getuid() == 0) return TRUE; /* root */
195     return onConsole();
196     break;
197   case Anybody:
198     return TRUE;
199   }
200   return FALSE;
201 }
202
203 int
204 main(int argc, char **argv)
205 {
206   FILE *cf;
207   struct stat statbuf;
208   char xserver[1025];
209   char line[1024];
210   char var[65];
211   char value[257];
212   int length;
213   int i;
214   char *val;
215   mode_t mask;
216   SecurityLevel level = RootOnly;
217
218   /* attempt to use our config file */
219   cf = fopen(X_WRAPPER_CONFIG_FILE, "r");
220
221   if (cf) {
222     /* parse it */
223
224     val = fgets(line, 1024, cf);
225
226     while (val != NULL) {
227       var[0] = '\0';
228       value[0] = '\0';
229       if (sscanf(line, " %64[A-Za-z0-9_] = %256[A-Za-z0-9_ -] ",
230                  var, value) > 0) {
231         /* truncate extra spaces at end of value */
232         length = strlen(value);
233         if (length > 256) {
234           length = 256;
235         }
236         for (i = (length - 1); (value[i] == ' '); i--) {
237           value[i] = '\0';
238         }
239         /* DEBUG (void) fprintf(stderr, "var: %s, value: %s.\n", var, value); */
240         if (strncasecmp(var, "allowed_users", 64) == 0) {
241           level = getSecLevel(value);
242           /* DEBUG (void) fprintf(stderr, "security level set to %d\n", level); */
243         }
244       }
245       val = fgets(line, 1024, cf);
246     }
247
248     (void) fclose(cf);
249   } else {
250     /* DEBUG (void) fprintf(stderr, "X: unable to open wrapper config file %s\n",
251                    X_WRAPPER_CONFIG_FILE); */
252   }
253
254   if (lstat(X_SERVER_SYMLINK, &statbuf)) {
255     (void) fprintf(stderr, "X: cannot stat %s (%s), aborting.\n",
256                    X_SERVER_SYMLINK, strerror(errno));
257     exit(1);
258   }
259
260   i = readlink(X_SERVER_SYMLINK, xserver, 1024);
261
262   if (i < 0) {
263     (void) fprintf(stderr, "X: cannot read %s symbolic link (%s), aborting.\n",
264                    X_SERVER_SYMLINK, strerror(errno));
265     exit(1);
266   }
267
268   xserver[i] = '\0'; /* readlink() does not null-terminate the string */
269
270   if ((strcmp(xserver, "/usr/bin/X11/X") == 0) ||
271       (strcmp(xserver, "/usr/X11R6/bin/X") == 0) ||
272       (strcmp(xserver, "/usr/bin/X") == 0)) {
273     (void) fprintf(stderr, "X: %s points back to X wrapper executable, "
274                    "aborting.\n", X_SERVER_SYMLINK);
275     exit(1);
276   }
277
278   if (access(X_SERVER_SYMLINK, X_OK)) { /* access() uses real uid */
279     (void) fprintf(stderr, "%s is not executable\n", X_SERVER_SYMLINK);
280     exit(1);
281   }
282
283   /* do we have permission to run the X server? */
284   if (checkSecLevel(level)) {
285     /* check for a sane server socket dir */
286     mask = umask(0);
287     /* some stupid kernels can't set the sticky bit during a mkdir() */
288     if (!(mkdir(X_SOCKET_DIR, X_SOCKET_DIR_MODE))) {
289       (void) chmod(X_SOCKET_DIR, X_SOCKET_DIR_MODE);
290     }
291     (void) umask(mask);
292
293     /* do paranoid checks on the directory where the X server creates its socket */
294     if (lstat(X_SOCKET_DIR, &statbuf)) {
295       (void) fprintf(stderr, "X: cannot stat %s (%s), aborting.\n",
296                      X_SOCKET_DIR, strerror(errno));
297       exit(1);
298     }
299
300     if ((statbuf.st_uid != 0) || (statbuf.st_gid != 0)) {
301       (void) fprintf(stderr, "X: %s has suspicious ownership (not root:root), "
302                      "aborting.\n", X_SOCKET_DIR);
303       exit(1);
304     }
305
306     if (statbuf.st_mode != (S_IFDIR | X_SOCKET_DIR_MODE)) {
307       (void) fprintf(stderr, "X: %s has suspicious mode (not %o) or is not a "
308                      "directory, aborting.\n", X_SOCKET_DIR, X_SOCKET_DIR_MODE);
309       exit(1);
310     }
311
312     for (i = 1; i < argc; i++) {
313       if (!strcmp(argv[i], "-config") || !strcmp(argv[i], "-xf86config")) {
314         if (setgid(getgid()) || setuid(getuid())) {
315           perror("X unable to drop setuid privileges for alternate config");
316           exit(1);
317         }
318       } else if (strlen(argv[i]) > 256) {
319         if (setgid(getgid()) || setuid(getuid())) {
320           perror("X unable to drop setuid privileges for suspiciously long "
321                  "argument");
322           exit(1);
323         }
324       }
325     }
326
327     /* run the X server */
328     seteuid(0);
329
330     /* DEBUG exit(0); */
331
332     /*
333      * change to the directory where the X server symlink is so that a relative
334      * symlink will work and execute the X server
335      */
336     if (chdir(X_SERVER_SYMLINK_DIR)) {
337       (void) fprintf(stderr, "X: cannot chdir() to %s (%s), aborting.\n",
338                      X_SERVER_SYMLINK_DIR, strerror(errno));
339       exit(1);
340     }
341     (void) execv(xserver, argv);
342     (void) fprintf(stderr, "X: exec of %s failed\n", xserver);
343     exit(1);
344
345   } else {
346       /* DEBUG fprintf(stderr, "argc = %d, argv[1] = \"%s\"\n", argc, argv[1]); */
347       /* DEBUG fprintf(stderr, "strcmp(argv[1], \"-showconfig\") = %d, strcmp(argv[1],
348         \"-version\" = %d\n", (strcmp(argv[1], "-showconfig")), (strcmp(argv[1],
349         "-version"))); */
350       if (argc == 2 && ( (strcmp(argv[1], "-help") == 0) ||
351                          (strcmp(argv[1], "-showconfig") == 0) ||
352                          (strcmp(argv[1], "-version") == 0) ||
353                          (strcmp(argv[1], "-showDefaultModulePath") == 0) ||
354                          (strcmp(argv[1], "-showDefaultLibPath") == 0) ) ) {
355           if (setgid(getgid()) || setuid(getuid())) {
356               perror("X unable to drop setuid privileges");
357               exit(1);
358           }
359           execv(xserver,argv);
360           (void) fprintf(stderr, "X: unprivileged exec of %s failed, "
361                          "aborting.\n", xserver);
362           exit(1);
363       } else {
364           (void) fprintf(stderr, "X: user not authorized to run the X "
365                          "server, aborting.\n");
366           exit(1);
367       }
368   }
369
370   (void) fprintf(stderr, "X: Impossible!  Unreachable statement reached!\n");
371   exit(1);
372 }
373
374 /*
375  * vim:set cindent et fo=tcroq sts=2 sw=2 tw=80:
376  */