1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * inject_randr1.5.c: An X11 proxy which interprets RandR 1.5 GetMonitors
8  * requests and overwrites their reply with a custom reply.
9  *
10  * This tool can be refactored as necessary in order to perform the same
11  * purpose for other request types. The RandR 1.5 specific portions of the code
12  * have been marked as such to make such a refactoring easier.
13  *
14  */
15 #include "all.h"
16 
17 #include <ev.h>
18 #include <fcntl.h>
19 #include <getopt.h>
20 #include <libgen.h>
21 #include <sys/mman.h>
22 #include <sys/resource.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <sys/un.h>
28 #include <sys/wait.h>
29 #include <unistd.h>
30 
31 static void uds_connection_cb(EV_P_ ev_io *w, int revents);
32 static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents);
33 static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents);
34 static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents);
35 static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents);
36 
37 static char *sun_path = NULL;
38 
cleanup_socket(void)39 static void cleanup_socket(void) {
40     if (sun_path != NULL) {
41         unlink(sun_path);
42         free(sun_path);
43         sun_path = NULL;
44     }
45 }
46 
47 struct injected_reply {
48     void *buf;
49     off_t len;
50 };
51 
52 /* BEGIN RandR 1.5 specific */
53 static struct injected_reply getmonitors_reply = {NULL, 0};
54 static struct injected_reply getoutputinfo_reply = {NULL, 0};
55 /* END RandR 1.5 specific */
56 
57 #define XCB_PAD(i) (-(i)&3)
58 
59 struct connstate {
60     /* clientw is a libev watcher for the connection which we accept()ed. */
61     ev_io *clientw;
62 
63     /* serverw is a libev watcher for the connection to X11 which we initiated
64      * on behalf of the client. */
65     ev_io *serverw;
66 
67     /* sequence is the client-side sequence number counter. In X11’s wire
68      * encoding, sequence counters are not included in requests, only in
69      * replies. */
70     int sequence;
71 
72     /* BEGIN RandR 1.5 specific */
73     /* sequence number of the most recent GetExtension request for RANDR */
74     int getext_randr;
75     /* sequence number of the most recent RRGetMonitors request */
76     int getmonitors;
77     /* sequence number of the most recent RRGetOutputInfo request */
78     int getoutputinfo;
79 
80     int randr_major_opcode;
81     /* END RandR 1.5 specific */
82 };
83 
84 /*
85  * Returns 0 on EOF
86  * Returns -1 on error (with errno from read() untouched)
87  *
88  */
readall_into(void * buffer,const size_t len,int fd)89 static size_t readall_into(void *buffer, const size_t len, int fd) {
90     size_t read_bytes = 0;
91     while (read_bytes < len) {
92         ssize_t n = read(fd, buffer + read_bytes, len - read_bytes);
93         if (n <= 0) {
94             return n;
95         }
96         read_bytes += (size_t)n;
97     }
98     return read_bytes;
99 }
100 
101 /*
102  * Exits the program with an error if the read failed.
103  *
104  */
must_read(int n)105 static void must_read(int n) {
106     if (n == -1) {
107         err(EXIT_FAILURE, "read()");
108     }
109     if (n == 0) {
110         errx(EXIT_FAILURE, "EOF");
111     }
112 }
113 
114 /*
115  * Exits the program with an error if the write failed.
116  *
117  */
must_write(int n)118 static void must_write(int n) {
119     if (n == -1) {
120         err(EXIT_FAILURE, "write()");
121     }
122 }
123 
uds_connection_cb(EV_P_ ev_io * w,int revents)124 static void uds_connection_cb(EV_P_ ev_io *w, int revents) {
125     struct sockaddr_un addr;
126     socklen_t addrlen = sizeof(addr);
127     const int clientfd = accept(w->fd, (struct sockaddr *)&addr, &addrlen);
128     if (clientfd == -1) {
129         if (errno == EINTR) {
130             return;
131         }
132         err(EXIT_FAILURE, "accept()");
133     }
134 
135     struct connstate *connstate = scalloc(1, sizeof(struct connstate));
136 
137     ev_io *clientw = scalloc(1, sizeof(ev_io));
138     connstate->clientw = clientw;
139     clientw->data = connstate;
140     ev_io_init(clientw, read_client_setup_request_cb, clientfd, EV_READ);
141     ev_io_start(EV_A_ clientw);
142 }
143 
144 // https://www.x.org/releases/current/doc/xproto/x11protocol.html#Encoding::Connection_Setup
read_client_setup_request_cb(EV_P_ ev_io * w,int revents)145 static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents) {
146     ev_io_stop(EV_A_ w);
147     struct connstate *connstate = (struct connstate *)w->data;
148 
149     /* Read X11 setup request in its entirety. */
150     xcb_setup_request_t setup_request;
151     must_read(readall_into(&setup_request, sizeof(setup_request), w->fd));
152 
153     /* Establish a connection to X11. */
154     int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
155     if (fd == -1) {
156         err(EXIT_FAILURE, "socket()");
157     }
158 
159     char *host;
160     int displayp;
161     if (xcb_parse_display(getenv("DISPLAY"), &host, &displayp, NULL) == 0) {
162         errx(EXIT_FAILURE, "Could not parse DISPLAY=%s", getenv("DISPLAY"));
163     }
164     free(host);
165 
166     struct sockaddr_un addr;
167     memset(&addr, 0, sizeof(addr));
168     addr.sun_family = AF_LOCAL;
169     snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", displayp);
170     if (connect(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
171         err(EXIT_FAILURE, "connect(%s)", addr.sun_path);
172     }
173 
174     /* Relay setup request. */
175     must_write(writeall(fd, &setup_request, sizeof(setup_request)));
176 
177     if (setup_request.authorization_protocol_name_len > 0 ||
178         setup_request.authorization_protocol_data_len > 0) {
179         const size_t authlen = setup_request.authorization_protocol_name_len +
180                                XCB_PAD(setup_request.authorization_protocol_name_len) +
181                                setup_request.authorization_protocol_data_len +
182                                XCB_PAD(setup_request.authorization_protocol_data_len);
183         void *buf = smalloc(authlen);
184         must_read(readall_into(buf, authlen, w->fd));
185         must_write(writeall(fd, buf, authlen));
186         free(buf);
187     }
188 
189     /* Wait for a response from the X11 server. */
190     ev_io *serverw = scalloc(1, sizeof(ev_io));
191     connstate->serverw = serverw;
192     serverw->data = connstate;
193     ev_io_init(serverw, read_server_setup_reply_cb, fd, EV_READ);
194     ev_io_start(EV_A_ serverw);
195 }
196 
read_server_setup_reply_cb(EV_P_ ev_io * w,int revents)197 static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents) {
198     struct connstate *connstate = (struct connstate *)w->data;
199     xcb_setup_failed_t setup_failed;
200     must_read(readall_into(&setup_failed, sizeof(setup_failed), w->fd));
201 
202     switch (setup_failed.status) {
203         case 0:
204             errx(EXIT_FAILURE, "error authenticating at the X11 server");
205 
206         case 2:
207             errx(EXIT_FAILURE, "two-factor auth not implemented");
208 
209         case 1:
210             must_write(writeall(connstate->clientw->fd, &setup_failed, sizeof(xcb_setup_failed_t)));
211             const size_t len = (setup_failed.length * 4);
212             void *buf = smalloc(len);
213             must_read(readall_into(buf, len, w->fd));
214             must_write(writeall(connstate->clientw->fd, buf, len));
215             free(buf);
216 
217             ev_set_cb(connstate->clientw, read_client_x11_packet_cb);
218             ev_set_cb(connstate->serverw, read_server_x11_packet_cb);
219             ev_io_start(EV_A_ connstate->clientw);
220             break;
221 
222         default:
223             errx(EXIT_FAILURE, "X11 protocol error: expected setup_failed.status in [0..2], got %d", setup_failed.status);
224     }
225 }
226 
227 // https://www.x.org/releases/current/doc/xproto/x11protocol.html#request_format
228 typedef struct {
229     uint8_t opcode;
230     uint8_t pad0;
231     uint16_t length;
232 } generic_x11_request_t;
233 
234 // https://www.x.org/releases/current/doc/xproto/x11protocol.html#reply_format
235 typedef struct {
236     uint8_t code; /* if 1, this is a reply. if 0, this is an error. else, an event */
237     uint8_t pad0;
238     uint16_t sequence;
239     uint32_t length;
240 } generic_x11_reply_t;
241 
read_client_x11_packet_cb(EV_P_ ev_io * w,int revents)242 static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) {
243     struct connstate *connstate = (struct connstate *)w->data;
244 
245     void *request = smalloc(sizeof(generic_x11_request_t));
246     must_read(readall_into(request, sizeof(generic_x11_request_t), connstate->clientw->fd));
247     const size_t len = (((generic_x11_request_t *)request)->length * 4);
248     if (len > sizeof(generic_x11_request_t)) {
249         request = srealloc(request, len);
250         must_read(readall_into(request + sizeof(generic_x11_request_t),
251                                len - sizeof(generic_x11_request_t),
252                                connstate->clientw->fd));
253     }
254 
255     // XXX: sequence counter wrapping is not implemented, but should not be
256     // necessary given that this tool is scoped for test cases.
257     connstate->sequence++;
258 
259     /* BEGIN RandR 1.5 specific */
260     const uint8_t opcode = ((generic_x11_request_t *)request)->opcode;
261     if (opcode == XCB_QUERY_EXTENSION) {
262         xcb_query_extension_request_t *req = request;
263         const char *name = request + sizeof(xcb_query_extension_request_t);
264         if (req->name_len == strlen("RANDR") &&
265             strncmp(name, "RANDR", strlen("RANDR")) == 0) {
266             connstate->getext_randr = connstate->sequence;
267         }
268     } else if (opcode == connstate->randr_major_opcode) {
269         const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0;
270         if (randr_opcode == XCB_RANDR_GET_MONITORS) {
271             connstate->getmonitors = connstate->sequence;
272         } else if (randr_opcode == XCB_RANDR_GET_OUTPUT_INFO) {
273             connstate->getoutputinfo = connstate->sequence;
274         }
275     }
276     /* END RandR 1.5 specific */
277 
278     must_write(writeall(connstate->serverw->fd, request, len));
279     free(request);
280 }
281 
handle_sequence(struct connstate * connstate,uint16_t sequence)282 static bool handle_sequence(struct connstate *connstate, uint16_t sequence) {
283     /* BEGIN RandR 1.5 specific */
284     if (sequence == connstate->getmonitors) {
285         printf("RRGetMonitors reply!\n");
286         if (getmonitors_reply.buf != NULL) {
287             printf("injecting reply\n");
288             ((generic_x11_reply_t *)getmonitors_reply.buf)->sequence = sequence;
289             must_write(writeall(connstate->clientw->fd, getmonitors_reply.buf, getmonitors_reply.len));
290             return true;
291         }
292     }
293 
294     if (sequence == connstate->getoutputinfo) {
295         printf("RRGetOutputInfo reply!\n");
296         if (getoutputinfo_reply.buf != NULL) {
297             printf("injecting reply\n");
298             ((generic_x11_reply_t *)getoutputinfo_reply.buf)->sequence = sequence;
299             must_write(writeall(connstate->clientw->fd, getoutputinfo_reply.buf, getoutputinfo_reply.len));
300             return true;
301         }
302     }
303     /* END RandR 1.5 specific */
304 
305     return false;
306 }
307 
read_server_x11_packet_cb(EV_P_ ev_io * w,int revents)308 static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
309     struct connstate *connstate = (struct connstate *)w->data;
310     // all packets from the server are at least 32 bytes in length
311     size_t len = 32;
312     void *packet = smalloc(len);
313     must_read(readall_into(packet, len, connstate->serverw->fd));
314     switch (((generic_x11_reply_t *)packet)->code) {
315         case 0: {  // error
316             const uint16_t sequence = ((xcb_request_error_t *)packet)->sequence;
317             if (handle_sequence(connstate, sequence)) {
318                 free(packet);
319                 return;
320             }
321             break;
322         }
323         case 1:  // reply
324             len += ((generic_x11_reply_t *)packet)->length * 4;
325             if (len > 32) {
326                 packet = srealloc(packet, len);
327                 must_read(readall_into(packet + 32, len - 32, connstate->serverw->fd));
328             }
329 
330             /* BEGIN RandR 1.5 specific */
331             const uint16_t sequence = ((generic_x11_reply_t *)packet)->sequence;
332 
333             if (sequence == connstate->getext_randr) {
334                 xcb_query_extension_reply_t *reply = packet;
335                 connstate->randr_major_opcode = reply->major_opcode;
336             }
337             /* END RandR 1.5 specific */
338 
339             if (handle_sequence(connstate, sequence)) {
340                 free(packet);
341                 return;
342             }
343 
344             break;
345 
346         default:  // event
347             break;
348     }
349     must_write(writeall(connstate->clientw->fd, packet, len));
350     free(packet);
351 }
352 
child_cb(EV_P_ ev_child * w,int revents)353 static void child_cb(EV_P_ ev_child *w, int revents) {
354     ev_child_stop(EV_A_ w);
355     if (WIFEXITED(w->rstatus)) {
356         exit(WEXITSTATUS(w->rstatus));
357     } else {
358         exit(WTERMSIG(w->rstatus) + 128);
359     }
360 }
361 
must_read_reply(const char * filename,struct injected_reply * reply)362 static void must_read_reply(const char *filename, struct injected_reply *reply) {
363     FILE *f;
364     if ((f = fopen(filename, "r")) == NULL) {
365         err(EXIT_FAILURE, "fopen(%s)", filename);
366     }
367     struct stat stbuf;
368     if (fstat(fileno(f), &stbuf) != 0) {
369         err(EXIT_FAILURE, "fstat(%s)", filename);
370     }
371     reply->len = stbuf.st_size;
372     reply->buf = smalloc(stbuf.st_size);
373     int n = fread(reply->buf, 1, stbuf.st_size, f);
374     if (n != stbuf.st_size) {
375         err(EXIT_FAILURE, "fread(%s)", filename);
376     }
377     fclose(f);
378 }
379 
main(int argc,char * argv[])380 int main(int argc, char *argv[]) {
381     static struct option long_options[] = {
382         {"getmonitors_reply", required_argument, 0, 0},
383         {"getoutputinfo_reply", required_argument, 0, 0},
384         {0, 0, 0, 0},
385     };
386     char *options_string = "";
387     int opt;
388     int option_index = 0;
389 
390     while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
391         switch (opt) {
392             case 0: {
393                 const char *option_name = long_options[option_index].name;
394                 if (strcmp(option_name, "getmonitors_reply") == 0) {
395                     must_read_reply(optarg, &getmonitors_reply);
396                 } else if (strcmp(option_name, "getoutputinfo_reply") == 0) {
397                     must_read_reply(optarg, &getoutputinfo_reply);
398                 }
399                 break;
400             }
401             default:
402                 exit(EXIT_FAILURE);
403         }
404     }
405 
406     if (optind >= argc) {
407         errx(EXIT_FAILURE, "syntax: %s [options] <command>", argv[0]);
408     }
409 
410     int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
411     if (fd == -1) {
412         err(EXIT_FAILURE, "socket(AF_UNIX)");
413     }
414 
415     if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
416         warn("Could not set FD_CLOEXEC");
417     }
418 
419     struct sockaddr_un addr;
420     memset(&addr, 0, sizeof(struct sockaddr_un));
421     addr.sun_family = AF_UNIX;
422     int i;
423     bool bound = false;
424     for (i = 0; i < 100; i++) {
425         /* XXX: The path to X11 sockets differs on some platforms (e.g. Trusted
426          * Solaris, HPUX), but since libxcb doesn’t provide a function to
427          * generate the path, we’ll just have to hard-code it for now. */
428         snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", i);
429 
430         if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
431             warn("bind(%s)", addr.sun_path);
432         } else {
433             bound = true;
434             /* Let the user know bind() was successful, so that they know the
435              * error messages can be disregarded. */
436             fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
437             sun_path = sstrdup(addr.sun_path);
438             break;
439         }
440     }
441 
442     if (!bound) {
443         err(EXIT_FAILURE, "bind()");
444     }
445 
446     atexit(cleanup_socket);
447 
448     /* This program will be started for each testcase which requires it, so we
449      * expect precisely one connection. */
450     if (listen(fd, 1) == -1) {
451         err(EXIT_FAILURE, "listen()");
452     }
453 
454     pid_t child = fork();
455     if (child == -1) {
456         err(EXIT_FAILURE, "fork()");
457     }
458     if (child == 0) {
459         char *display;
460         sasprintf(&display, ":%d", i);
461         setenv("DISPLAY", display, 1);
462         free(display);
463 
464         char **child_args = argv + optind;
465         execvp(child_args[0], child_args);
466         err(EXIT_FAILURE, "exec()");
467     }
468 
469     struct ev_loop *loop = ev_default_loop(0);
470 
471     ev_child cw;
472     ev_child_init(&cw, child_cb, child, 0);
473     ev_child_start(loop, &cw);
474 
475     ev_io watcher;
476     ev_io_init(&watcher, uds_connection_cb, fd, EV_READ);
477     ev_io_start(loop, &watcher);
478 
479     ev_run(loop, 0);
480 }
481