1/*
2 * Copyright © 2014 Red Hat, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 * Author: Hans de Goede <hdegoede@redhat.com>
24 */
25
26#include "dix-config.h"
27#include "xorg-config.h"
28
29#include <errno.h>
30#include <fcntl.h>
31#include <limits.h>
32#include <stdint.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <sys/ioctl.h>
37#include <sys/stat.h>
38#ifdef HAVE_SYS_SYSMACROS_H
39#include <sys/sysmacros.h>
40#endif
41#include <sys/types.h>
42#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
43#include <sys/consio.h>
44#endif
45#include <unistd.h>
46#ifdef WITH_LIBDRM
47#include <drm.h>
48#include <xf86drm.h> /* For DRM_DEV_NAME */
49#endif
50
51#include "misc.h"
52
53#define CONFIG_FILE SYSCONFDIR "/X11/Xwrapper.config"
54
55static const char *progname;
56
57enum { ROOT_ONLY, CONSOLE_ONLY, ANYBODY };
58
59/* KISS non locale / LANG parsing isspace version */
60static int is_space(char c)
61{
62    return c == ' ' || c == '\t' || c == '\n';
63}
64
65static char *strip(char *s)
66{
67    int i;
68
69    /* Strip leading whitespace */
70    while (s[0] && is_space(s[0]))
71        s++;
72
73    /* Strip trailing whitespace */
74    i = strlen(s) - 1;
75    while (i >= 0 && is_space(s[i])) {
76        s[i] = 0;
77        i--;
78    }
79
80    return s;
81}
82
83static void parse_config(int *allowed, int *needs_root_rights)
84{
85    FILE *f;
86    char buf[1024];
87    char *stripped, *equals, *key, *value;
88    int line = 0;
89
90    f = fopen(CONFIG_FILE, "r");
91    if (!f)
92        return;
93
94    while (fgets(buf, sizeof(buf), f)) {
95        line++;
96
97        /* Skip comments and empty lines */
98        stripped = strip(buf);
99        if (stripped[0] == '#' || stripped[0] == 0)
100            continue;
101
102        /* Split in a key + value pair */
103        equals = strchr(stripped, '=');
104        if (!equals) {
105            fprintf(stderr, "%s: Syntax error at %s line %d\n", progname,
106                CONFIG_FILE, line);
107            exit(1);
108        }
109        *equals = 0;
110        key   = strip(stripped);   /* To remove trailing whitespace from key */
111        value = strip(equals + 1); /* To remove leading whitespace from val */
112        if (!key[0]) {
113            fprintf(stderr, "%s: Missing key at %s line %d\n", progname,
114                CONFIG_FILE, line);
115            exit(1);
116        }
117        if (!value[0]) {
118            fprintf(stderr, "%s: Missing value at %s line %d\n", progname,
119                CONFIG_FILE, line);
120            exit(1);
121        }
122
123        /* And finally process */
124        if (strcmp(key, "allowed_users") == 0) {
125            if (strcmp(value, "rootonly") == 0)
126                *allowed = ROOT_ONLY;
127            else if (strcmp(value, "console") == 0)
128                *allowed = CONSOLE_ONLY;
129            else if (strcmp(value, "anybody") == 0)
130                *allowed = ANYBODY;
131            else {
132                fprintf(stderr,
133                    "%s: Invalid value '%s' for 'allowed_users' at %s line %d\n",
134                    progname, value, CONFIG_FILE, line);
135                exit(1);
136            }
137        }
138        else if (strcmp(key, "needs_root_rights") == 0) {
139            if (strcmp(value, "yes") == 0)
140                *needs_root_rights = 1;
141            else if (strcmp(value, "no") == 0)
142                *needs_root_rights = 0;
143            else if (strcmp(value, "auto") == 0)
144                *needs_root_rights = -1;
145            else {
146                fprintf(stderr,
147                    "%s: Invalid value '%s' for 'needs_root_rights' at %s line %d\n",
148                    progname, value, CONFIG_FILE, line);
149                exit(1);
150            }
151        }
152        else if (strcmp(key, "nice_value") == 0) {
153            /* Backward compatibility with older Debian Xwrapper, ignore */
154        }
155        else {
156            fprintf(stderr, "%s: Invalid key '%s' at %s line %d\n", key,
157                progname, CONFIG_FILE, line);
158            exit(1);
159        }
160    }
161    fclose(f);
162}
163
164static int on_console(int fd)
165{
166#if defined(__linux__)
167    struct stat st;
168    int r;
169
170    r = fstat(fd, &st);
171    if (r == 0 && S_ISCHR(st.st_mode) && major(st.st_rdev) == 4)
172      return 1;
173#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
174    int idx;
175
176    if (ioctl(fd, VT_GETINDEX, &idx) != -1)
177        return 1;
178#else
179#warning This program needs porting to your kernel.
180    static int seen;
181
182    if (!seen) {
183        fprintf(stderr, "%s: Unable to determine if running on a console\n",
184            progname);
185        seen = 1;
186    }
187#endif
188
189    return 0;
190}
191
192int main(int argc, char *argv[])
193{
194#ifdef WITH_LIBDRM
195    struct drm_mode_card_res res;
196#endif
197    char buf[PATH_MAX];
198    int i, r, fd;
199    int kms_cards = 0;
200    int total_cards = 0;
201    int allowed = CONSOLE_ONLY;
202    int needs_root_rights = -1;
203    char *const empty_envp[1] = { NULL, };
204
205    progname = argv[0];
206
207    parse_config(&allowed, &needs_root_rights);
208
209    /* For non root users check if they are allowed to run the X server */
210    if (getuid() != 0) {
211        switch (allowed) {
212        case ROOT_ONLY:
213            /* Already checked above */
214            fprintf(stderr, "%s: Only root is allowed to run the X server\n", argv[0]);
215            exit(1);
216            break;
217        case CONSOLE_ONLY:
218            /* Some of stdin / stdout / stderr maybe redirected to a file */
219            for (i = STDIN_FILENO; i <= STDERR_FILENO; i++) {
220                if (on_console(i))
221                    break;
222            }
223            if (i > STDERR_FILENO) {
224                fprintf(stderr, "%s: Only console users are allowed to run the X server\n", argv[0]);
225                exit(1);
226            }
227            break;
228        case ANYBODY:
229            break;
230        }
231    }
232
233#ifdef WITH_LIBDRM
234    /* Detect if we need root rights, except when overridden by the config */
235    if (needs_root_rights == -1) {
236        for (i = 0; i < 16; i++) {
237            snprintf(buf, sizeof(buf), DRM_DEV_NAME, DRM_DIR_NAME, i);
238            fd = open(buf, O_RDWR);
239            if (fd == -1)
240                continue;
241
242            total_cards++;
243
244            memset(&res, 0, sizeof(struct drm_mode_card_res));
245            r = ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
246            if (r == 0)
247                kms_cards++;
248
249            close(fd);
250        }
251    }
252#endif
253
254    /* If we've found cards, and all cards support kms, drop root rights */
255    if (needs_root_rights == 0 || (total_cards && kms_cards == total_cards)) {
256        gid_t realgid = getgid();
257        uid_t realuid = getuid();
258
259        if (setresgid(-1, realgid, realgid) != 0) {
260            fprintf(stderr, "%s: Could not drop setgid privileges: %s\n",
261                progname, strerror(errno));
262            exit(1);
263        }
264        if (setresuid(-1, realuid, realuid) != 0) {
265            fprintf(stderr, "%s: Could not drop setuid privileges: %s\n",
266                progname, strerror(errno));
267            exit(1);
268        }
269    }
270
271    snprintf(buf, sizeof(buf), "%s/Xorg", SUID_WRAPPER_DIR);
272
273    /* Check if the server is executable by our real uid */
274    if (access(buf, X_OK) != 0) {
275        fprintf(stderr, "%s: Missing execute permissions for %s: %s\n",
276            progname, buf, strerror(errno));
277        exit(1);
278    }
279
280    argv[0] = buf;
281    if (getuid() == geteuid())
282        (void) execv(argv[0], argv);
283    else
284        (void) execve(argv[0], argv, empty_envp);
285    fprintf(stderr, "%s: Failed to execute %s: %s\n",
286        progname, buf, strerror(errno));
287    exit(1);
288}
289