1 /*
2  * Copyright © 2019 Manuel Stoeckl
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the
13  * next paragraph) shall be included in all copies or substantial
14  * portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 
26 #include "main.h"
27 
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <inttypes.h>
31 #include <poll.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/socket.h>
36 #include <sys/un.h>
37 #include <sys/wait.h>
38 #include <unistd.h>
39 
conntoken_version(uint32_t header)40 static inline uint32_t conntoken_version(uint32_t header)
41 {
42 	return header >> 16;
43 }
44 
check_conn_header(uint32_t header,const struct main_config * config,char * err,size_t err_size)45 static int check_conn_header(uint32_t header, const struct main_config *config,
46 		char *err, size_t err_size)
47 {
48 	if ((header >> 16) != WAYPIPE_PROTOCOL_VERSION) {
49 		const char *endian_warning = "";
50 		if ((header & CONN_FIXED_BIT) == 0 &&
51 				(header & CONN_UNSET_BIT) != 0) {
52 			endian_warning =
53 					" It is also possible that server endianness does not match client";
54 		}
55 
56 		snprintf(err, err_size,
57 				"Waypipe client is rejecting connection header %08" PRIx32
58 				"; as Waypipe server (application-side) protocol version (%u) is incompatible with Waypipe client protocol version (%u, from waypipe %s). Check that both sides have compatible versions of Waypipe.%s",
59 				header, conntoken_version(header),
60 				WAYPIPE_PROTOCOL_VERSION, WAYPIPE_VERSION,
61 				endian_warning);
62 		return -1;
63 	}
64 
65 	/* Skip the following checks if config is null
66 	 * (i.e., called from reconnection loop) */
67 	if (!config) {
68 		return 0;
69 	}
70 
71 	/* For now, reject mismatches in compression format and video coding
72 	 * setting, and print an error. Adopting whatever the server asks for
73 	 * is a minor security issue -- e.g., video handling is a good target
74 	 * for exploits, and compression can cost CPU time, especially if the
75 	 * initial connection mechanism were to be expanded to allow setting
76 	 * compression level. */
77 	if ((header & CONN_COMPRESSION_MASK) == CONN_ZSTD_COMPRESSION) {
78 		if (config->compression != COMP_ZSTD) {
79 			snprintf(err, err_size,
80 					"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=ZSTD the Waypipe server expected",
81 					compression_mode_to_str(
82 							config->compression));
83 			return -1;
84 		}
85 	} else if ((header & CONN_COMPRESSION_MASK) == CONN_LZ4_COMPRESSION) {
86 		if (config->compression != COMP_LZ4) {
87 			snprintf(err, err_size,
88 					"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=LZ4 the Waypipe server expected",
89 					compression_mode_to_str(
90 							config->compression));
91 			return -1;
92 		}
93 	} else if ((header & CONN_COMPRESSION_MASK) == CONN_NO_COMPRESSION) {
94 		if (config->compression != COMP_NONE) {
95 			snprintf(err, err_size,
96 					"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=NONE the Waypipe server expected",
97 					compression_mode_to_str(
98 							config->compression));
99 			return -1;
100 		}
101 	} else if ((header & CONN_COMPRESSION_MASK) != 0) {
102 		snprintf(err, err_size,
103 				"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the unidentified compression type the Waypipe server expected",
104 				compression_mode_to_str(config->compression));
105 		return -1;
106 	}
107 
108 	if ((header & CONN_VIDEO_MASK) == CONN_VP9_VIDEO) {
109 		if (!config->video_if_possible) {
110 			snprintf(err, err_size,
111 					"Waypipe client is rejecting connection, Waypipe client was not run with video encoding enabled, unlike Waypipe server");
112 			return -1;
113 		}
114 		if (config->video_fmt != VIDEO_VP9) {
115 			snprintf(err, err_size,
116 					"Waypipe client is rejecting connection, Waypipe client was not configured for the VP9 video coding format requested by the Waypipe server");
117 			return -1;
118 		}
119 	} else if ((header & CONN_VIDEO_MASK) == CONN_H264_VIDEO) {
120 		if (!config->video_if_possible) {
121 			snprintf(err, err_size,
122 					"Waypipe client is rejecting connection, Waypipe client was not run with video encoding enabled, unlike Waypipe server");
123 			return -1;
124 		}
125 		if (config->video_fmt != VIDEO_H264) {
126 			snprintf(err, err_size,
127 					"Waypipe client is rejecting connection, Waypipe client was not configured for the VP9 video coding format requested by the Waypipe server");
128 			return -1;
129 		}
130 	} else if ((header & CONN_VIDEO_MASK) == CONN_NO_VIDEO) {
131 		if (config->video_if_possible) {
132 			snprintf(err, err_size,
133 					"Waypipe client is rejecting connection, Waypipe client has video encoding enabled, but Waypipe server does not");
134 			return -1;
135 		}
136 	} else if ((header & CONN_VIDEO_MASK) != 0) {
137 		snprintf(err, err_size,
138 				"Waypipe client is rejecting connection, Waypipe client was not configured for the unidentified video coding format requested by the Waypipe server");
139 		return -1;
140 	}
141 
142 	return 0;
143 }
apply_conn_header(uint32_t header,struct main_config * config)144 static void apply_conn_header(uint32_t header, struct main_config *config)
145 {
146 	if (header & CONN_NO_DMABUF_SUPPORT) {
147 		if (config) {
148 			config->no_gpu = true;
149 		}
150 	}
151 	// todo: consider allowing to disable video encoding
152 }
153 
write_rejection_message(int channel_fd,char * msg)154 static void write_rejection_message(int channel_fd, char *msg)
155 {
156 	char buf[512];
157 	size_t len = print_wrapped_error(buf, sizeof(buf), msg);
158 	if (!len) {
159 		wp_error("Failed to print wrapped error for message of length %zu, not enough space",
160 				strlen(msg));
161 		return;
162 	}
163 	ssize_t written = write(channel_fd, buf, len);
164 	if (written != (ssize_t)len) {
165 		wp_error("Failed to send rejection message, only %d bytes of %d written",
166 				(int)written, (int)len);
167 	}
168 }
169 
key_match(const uint32_t key1[static3],const uint32_t key2[static3])170 static inline bool key_match(
171 		const uint32_t key1[static 3], const uint32_t key2[static 3])
172 {
173 	return key1[0] == key2[0] && key1[1] == key2[1] && key1[2] == key2[2];
174 }
get_inherited_socket(const char * wayland_socket)175 static int get_inherited_socket(const char *wayland_socket)
176 {
177 	uint32_t val;
178 	if (parse_uint32(wayland_socket, &val) == -1 || ((int)val) < 0) {
179 		wp_error("Failed to parse \"%s\" (value of WAYLAND_SOCKET) as a nonnegative integer, exiting",
180 				wayland_socket);
181 		return -1;
182 	}
183 	int fd = (int)val;
184 	int flags = fcntl(fd, F_GETFL, 0);
185 	if (flags == -1 && errno == EBADF) {
186 		wp_error("The file descriptor WAYLAND_SOCKET=%d was invalid, exiting",
187 				fd);
188 		return -1;
189 	}
190 	return fd;
191 }
192 
get_display_path(struct sockaddr_un * addr)193 static int get_display_path(struct sockaddr_un *addr)
194 {
195 	const char *display = getenv("WAYLAND_DISPLAY");
196 	if (!display) {
197 		wp_error("WAYLAND_DISPLAY is not set, exiting");
198 		return -1;
199 	}
200 	if (display[0] != '/') {
201 		const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
202 		if (!xdg_runtime_dir) {
203 			wp_error("XDG_RUNTIME_DIR is not set, exiting");
204 			return -1;
205 		}
206 		if (strlen(display) + 1 + strlen(xdg_runtime_dir) >=
207 				sizeof(addr->sun_path)) {
208 			wp_error("The Wayland socket path '%s/%s' is too long (%zu + 1 + %zu bytes >= %zu)",
209 					xdg_runtime_dir, display,
210 					strlen(xdg_runtime_dir),
211 					strlen(display),
212 					sizeof(addr->sun_path) - 1);
213 			return -1;
214 		}
215 
216 		multi_strcat(addr->sun_path, sizeof(addr->sun_path),
217 				xdg_runtime_dir, "/", display, NULL);
218 	} else {
219 		if (strlen(display) >= sizeof(addr->sun_path)) {
220 			wp_error("WAYLAND_DISPLAY='%s' is longer than %zu bytes (max socket path length), exiting",
221 					display, sizeof(addr->sun_path) - 1);
222 			return -1;
223 		}
224 		strcpy(addr->sun_path, display);
225 	}
226 	return 0;
227 }
228 
run_single_client_reconnector(int channelsock,int linkfd,struct connection_token conn_id)229 static int run_single_client_reconnector(
230 		int channelsock, int linkfd, struct connection_token conn_id)
231 {
232 	int retcode = EXIT_SUCCESS;
233 	while (!shutdown_flag) {
234 		struct pollfd pf[2];
235 		pf[0].fd = channelsock;
236 		pf[0].events = POLLIN;
237 		pf[0].revents = 0;
238 		pf[1].fd = linkfd;
239 		pf[1].events = 0;
240 		pf[1].revents = 0;
241 
242 		int r = poll(pf, 2, -1);
243 		if (r == -1 && errno == EINTR) {
244 			continue;
245 		} else if (r == -1) {
246 			retcode = EXIT_FAILURE;
247 			break;
248 		} else if (r == 0) {
249 			// Nothing to read
250 			continue;
251 		}
252 
253 		if (pf[1].revents & POLLHUP) {
254 			/* Hang up, main thread has closed its link */
255 			break;
256 		}
257 		if (!(pf[0].revents & POLLIN)) {
258 			continue;
259 		}
260 		int newclient = accept(channelsock, NULL, NULL);
261 		if (newclient == -1) {
262 			if (errno == EAGAIN || errno == EWOULDBLOCK) {
263 				// The wakeup may have been spurious
264 				continue;
265 			}
266 			wp_error("Connection failure: %s", strerror(errno));
267 			retcode = EXIT_FAILURE;
268 			break;
269 		}
270 		wp_debug("Reconnection to oneshot client");
271 
272 		struct connection_token new_conn;
273 		memset(&new_conn, 0, sizeof(new_conn));
274 		if (read(newclient, &new_conn.header,
275 				    sizeof(new_conn.header)) !=
276 				sizeof(new_conn.header)) {
277 			wp_error("Failed to get connection id header");
278 			goto done;
279 		}
280 		if (check_conn_header(new_conn.header, NULL, NULL, 0) < 0) {
281 			goto done;
282 		}
283 		if (read(newclient, &new_conn.key, sizeof(new_conn.key)) !=
284 				sizeof(new_conn.key)) {
285 			wp_error("Failed to get connection id key");
286 			goto done;
287 		}
288 		if (!key_match(new_conn.key, conn_id.key)) {
289 			wp_error("Connection attempt with unmatched key");
290 			goto done;
291 		}
292 		bool update = new_conn.header & CONN_RECONNECTABLE_BIT;
293 		if (!update) {
294 			wp_error("Connection token is missing update flag");
295 			goto done;
296 		}
297 		if (send_one_fd(linkfd, newclient) == -1) {
298 			wp_error("Failed to get connection id");
299 			retcode = EXIT_FAILURE;
300 			checked_close(newclient);
301 			break;
302 		}
303 	done:
304 		checked_close(newclient);
305 	}
306 	checked_close(channelsock);
307 	checked_close(linkfd);
308 	return retcode;
309 }
310 
run_single_client(int channelsock,pid_t * eol_pid,const struct main_config * config,int disp_fd)311 static int run_single_client(int channelsock, pid_t *eol_pid,
312 		const struct main_config *config, int disp_fd)
313 {
314 	/* To support reconnection attempts, this mode creates a child
315 	 * reconnection watcher process, linked via socketpair */
316 	int retcode = EXIT_SUCCESS;
317 	int chanclient = -1;
318 	struct connection_token conn_id;
319 	memset(&conn_id, 0, sizeof(conn_id));
320 	while (!shutdown_flag) {
321 		int status = -1;
322 		if (wait_for_pid_and_clean(eol_pid, &status, WNOHANG, NULL)) {
323 			eol_pid = 0; // < in case eol_pid is recycled
324 
325 			wp_debug("Child (ssh) died, exiting");
326 			// Copy the exit code
327 			retcode = WEXITSTATUS(status);
328 			break;
329 		}
330 
331 		struct pollfd cs;
332 		cs.fd = channelsock;
333 		cs.events = POLLIN;
334 		cs.revents = 0;
335 		int r = poll(&cs, 1, -1);
336 		if (r == -1) {
337 			if (errno == EINTR) {
338 				// If SIGCHLD, we will check the child.
339 				// If SIGINT, the loop ends
340 				continue;
341 			}
342 			retcode = EXIT_FAILURE;
343 			break;
344 		} else if (r == 0) {
345 			// Nothing to read
346 			continue;
347 		}
348 
349 		chanclient = accept(channelsock, NULL, NULL);
350 		if (chanclient == -1) {
351 			if (errno == EAGAIN || errno == EWOULDBLOCK) {
352 				// The wakeup may have been spurious
353 				continue;
354 			}
355 			wp_error("Connection failure: %s", strerror(errno));
356 			retcode = EXIT_FAILURE;
357 			break;
358 		}
359 
360 		char err_msg[512];
361 
362 		wp_debug("New connection to client");
363 		if (read(chanclient, &conn_id.header, sizeof(conn_id.header)) !=
364 				sizeof(conn_id.header)) {
365 			wp_error("Failed to get connection id header");
366 			goto fail_cc;
367 		}
368 
369 		if (check_conn_header(conn_id.header, config, err_msg,
370 				    sizeof(err_msg)) < 0) {
371 			wp_error("%s", err_msg);
372 			write_rejection_message(chanclient, err_msg);
373 			goto fail_cc;
374 		}
375 		if (read(chanclient, &conn_id.key, sizeof(conn_id.key)) !=
376 				sizeof(conn_id.key)) {
377 			wp_error("Failed to get connection id key");
378 			goto fail_cc;
379 		}
380 		break;
381 	fail_cc:
382 		retcode = EXIT_FAILURE;
383 		checked_close(chanclient);
384 		chanclient = -1;
385 		break;
386 	}
387 	if (retcode == EXIT_FAILURE || shutdown_flag || chanclient == -1) {
388 		checked_close(channelsock);
389 		checked_close(disp_fd);
390 		return retcode;
391 	}
392 	if (conn_id.header & CONN_UPDATE_BIT) {
393 		wp_error("Initial connection token had update flag set");
394 		checked_close(channelsock);
395 		checked_close(disp_fd);
396 		return retcode;
397 	}
398 
399 	/* Fork a reconnection handler, only if the connection is
400 	 * reconnectable/has a nonzero id */
401 	int linkfds[2] = {-1, -1};
402 	if (conn_id.header & CONN_RECONNECTABLE_BIT) {
403 		if (socketpair(AF_UNIX, SOCK_STREAM, 0, linkfds) == -1) {
404 			wp_error("Failed to create socketpair: %s",
405 					strerror(errno));
406 			checked_close(chanclient);
407 			return EXIT_FAILURE;
408 		}
409 
410 		pid_t reco_pid = fork();
411 		if (reco_pid == -1) {
412 			wp_error("Fork failure: %s", strerror(errno));
413 			checked_close(chanclient);
414 			return EXIT_FAILURE;
415 		} else if (reco_pid == 0) {
416 			if (linkfds[0] != -1) {
417 				checked_close(linkfds[0]);
418 			}
419 			checked_close(chanclient);
420 			checked_close(disp_fd);
421 			int rc = run_single_client_reconnector(
422 					channelsock, linkfds[1], conn_id);
423 			exit(rc);
424 		}
425 		checked_close(linkfds[1]);
426 	}
427 	checked_close(channelsock);
428 
429 	struct main_config mod_config = *config;
430 	apply_conn_header(conn_id.header, &mod_config);
431 	return main_interface_loop(
432 			chanclient, disp_fd, linkfds[0], &mod_config, true);
433 }
434 
send_new_connection_fd(struct conn_map * connmap,uint32_t key[static3],int new_fd)435 void send_new_connection_fd(
436 		struct conn_map *connmap, uint32_t key[static 3], int new_fd)
437 {
438 	for (int i = 0; i < connmap->count; i++) {
439 		if (key_match(connmap->data[i].token.key, key)) {
440 			if (send_one_fd(connmap->data[i].linkfd, new_fd) ==
441 					-1) {
442 				wp_error("Failed to send new connection fd to subprocess: %s",
443 						strerror(errno));
444 			}
445 			break;
446 		}
447 	}
448 }
449 
handle_new_client_connection(struct pollfd * other_fds,int n_other_fds,int chanclient,struct conn_map * connmap,const struct main_config * config,const struct sockaddr_un * disp_addr,const struct connection_token * conn_id)450 static void handle_new_client_connection(struct pollfd *other_fds,
451 		int n_other_fds, int chanclient, struct conn_map *connmap,
452 		const struct main_config *config,
453 		const struct sockaddr_un *disp_addr,
454 		const struct connection_token *conn_id)
455 {
456 	bool reconnectable = conn_id->header & CONN_RECONNECTABLE_BIT;
457 
458 	if (reconnectable && buf_ensure_size(connmap->count + 1,
459 					     sizeof(struct conn_addr),
460 					     &connmap->size,
461 					     (void **)&connmap->data) == -1) {
462 		wp_error("Failed to allocate space to track connection");
463 		goto fail_cc;
464 	}
465 	int linkfds[2] = {-1, -1};
466 	if (reconnectable) {
467 		if (socketpair(AF_UNIX, SOCK_STREAM, 0, linkfds) == -1) {
468 			wp_error("Failed to create socketpair: %s",
469 					strerror(errno));
470 			goto fail_cc;
471 		}
472 	}
473 	pid_t npid = fork();
474 	if (npid == 0) {
475 		// Run forked process, with the only shared
476 		// state being the new channel socket
477 		for (int i = 0; i < n_other_fds; i++) {
478 			if (other_fds[i].fd != chanclient) {
479 				checked_close(other_fds[i].fd);
480 			}
481 		}
482 		if (reconnectable) {
483 			checked_close(linkfds[0]);
484 		}
485 		for (int i = 0; i < connmap->count; i++) {
486 			checked_close(connmap->data[i].linkfd);
487 		}
488 
489 		int dfd = connect_to_socket(disp_addr);
490 		if (dfd == -1) {
491 			exit(EXIT_FAILURE);
492 		}
493 
494 		struct main_config mod_config = *config;
495 		apply_conn_header(conn_id->header, &mod_config);
496 		int rc = main_interface_loop(
497 				chanclient, dfd, linkfds[1], &mod_config, true);
498 		check_unclosed_fds();
499 		exit(rc);
500 	} else if (npid == -1) {
501 		wp_error("Fork failure: %s", strerror(errno));
502 		goto fail_ps;
503 	}
504 	// Remove connection from this process
505 
506 	if (reconnectable) {
507 		checked_close(linkfds[1]);
508 		connmap->data[connmap->count++] =
509 				(struct conn_addr){.linkfd = linkfds[0],
510 						.token = *conn_id,
511 						.pid = npid};
512 	}
513 
514 	return;
515 fail_ps:
516 	checked_close(linkfds[0]);
517 fail_cc:
518 	checked_close(chanclient);
519 	return;
520 }
521 #define NUM_INCOMPLETE_CONNECTIONS 63
522 
drop_incoming_connection(struct pollfd * fds,struct connection_token * tokens,uint8_t * bytes_read,int index,int incomplete)523 static void drop_incoming_connection(struct pollfd *fds,
524 		struct connection_token *tokens, uint8_t *bytes_read, int index,
525 		int incomplete)
526 {
527 	checked_close(fds[index].fd);
528 	if (index != incomplete - 1) {
529 		size_t shift = (size_t)(incomplete - 1 - index);
530 		memmove(fds + index, fds + index + 1,
531 				sizeof(struct pollfd) * shift);
532 		memmove(tokens + index, tokens + index + 1,
533 				sizeof(struct connection_token) * shift);
534 		memmove(bytes_read + index, bytes_read + index + 1,
535 				sizeof(uint8_t) * shift);
536 	}
537 	memset(&fds[incomplete - 1], 0, sizeof(struct pollfd));
538 	memset(&tokens[incomplete - 1], 0, sizeof(struct connection_token));
539 	bytes_read[incomplete - 1] = 0;
540 }
541 
run_multi_client(int channelsock,pid_t * eol_pid,const struct main_config * config,const struct sockaddr_un * disp_addr)542 static int run_multi_client(int channelsock, pid_t *eol_pid,
543 		const struct main_config *config,
544 		const struct sockaddr_un *disp_addr)
545 {
546 	struct conn_map connmap = {.data = NULL, .count = 0, .size = 0};
547 
548 	/* Keep track of the main socket, and all connections which have not
549 	 * yet fully provided their connection token. If we run out of space,
550 	 * the oldest incomplete connection gets dropped */
551 	struct pollfd fds[NUM_INCOMPLETE_CONNECTIONS + 1];
552 	struct connection_token tokens[NUM_INCOMPLETE_CONNECTIONS];
553 	uint8_t bytes_read[NUM_INCOMPLETE_CONNECTIONS];
554 	int incomplete = 0;
555 	memset(fds, 0, sizeof(fds));
556 	memset(tokens, 0, sizeof(tokens));
557 	memset(bytes_read, 0, sizeof(bytes_read));
558 	fds[0].fd = channelsock;
559 	fds[0].events = POLLIN;
560 	fds[0].revents = 0;
561 
562 	int retcode = EXIT_SUCCESS;
563 	while (!shutdown_flag) {
564 		int status = -1;
565 		if (wait_for_pid_and_clean(
566 				    eol_pid, &status, WNOHANG, &connmap)) {
567 			wp_debug("Child (ssh) died, exiting");
568 			// Copy the exit code
569 			retcode = WEXITSTATUS(status);
570 			break;
571 		}
572 
573 		int r = poll(fds, 1 + (nfds_t)incomplete, -1);
574 		if (r == -1) {
575 			if (errno == EINTR) {
576 				// If SIGCHLD, we will check the child.
577 				// If SIGINT, the loop ends
578 				continue;
579 			}
580 			retcode = EXIT_FAILURE;
581 			break;
582 		} else if (r == 0) {
583 			// Nothing to read
584 			continue;
585 		}
586 
587 		for (int i = 0; i < incomplete; i++) {
588 			if (!(fds[i + 1].revents & POLLIN)) {
589 				continue;
590 			}
591 			int cur_fd = fds[i + 1].fd;
592 			char *dest = ((char *)&tokens[i]) + bytes_read[i];
593 			ssize_t s = read(cur_fd, dest, 16 - bytes_read[i]);
594 			if (s == -1) {
595 				wp_error("Failed to read from connection: %s",
596 						strerror(errno));
597 				drop_incoming_connection(fds + 1, tokens,
598 						bytes_read, i, incomplete);
599 				incomplete--;
600 				continue;
601 			} else if (s == 0) {
602 				/* connection closed */
603 				wp_error("Connection closed early");
604 				drop_incoming_connection(fds + 1, tokens,
605 						bytes_read, i, incomplete);
606 				incomplete--;
607 				continue;
608 			}
609 			bytes_read[i] += (uint8_t)s;
610 			if (bytes_read[i] - (uint8_t)s < 4 &&
611 					bytes_read[i] >= 4) {
612 				char err_msg[512];
613 				/* Validate connection token header */
614 				if (check_conn_header(tokens[i].header, config,
615 						    err_msg,
616 						    sizeof(err_msg)) < 0) {
617 					wp_error("%s", err_msg);
618 					write_rejection_message(
619 							cur_fd, err_msg);
620 					drop_incoming_connection(fds + 1,
621 							tokens, bytes_read, i,
622 							incomplete);
623 					incomplete--;
624 					continue;
625 				}
626 			}
627 			if (bytes_read[i] < 16) {
628 				continue;
629 			}
630 			/* Validate connection token key */
631 			if (tokens[i].header & CONN_UPDATE_BIT) {
632 				send_new_connection_fd(&connmap, tokens[i].key,
633 						cur_fd);
634 				drop_incoming_connection(fds + 1, tokens,
635 						bytes_read, i, incomplete);
636 				incomplete--;
637 				continue;
638 			}
639 
640 			/* Failures here are logged, but should not
641 			 * affect this process' ability to e.g. handle
642 			 * reconnections. */
643 			handle_new_client_connection(fds, 1 + incomplete,
644 					cur_fd, &connmap, config, disp_addr,
645 					&tokens[i]);
646 			drop_incoming_connection(fds + 1, tokens, bytes_read, i,
647 					incomplete);
648 			incomplete--;
649 		}
650 
651 		/* Process new connections second, to give incomplete
652 		 * connections a chance to clear first */
653 		if (fds[0].revents & POLLIN) {
654 			int chanclient = accept(channelsock, NULL, NULL);
655 			if (chanclient == -1) {
656 				if (errno == EAGAIN || errno == EWOULDBLOCK) {
657 					// The wakeup may have been spurious
658 					continue;
659 				}
660 				// should errors like econnaborted exit?
661 				wp_error("Connection failure: %s",
662 						strerror(errno));
663 				retcode = EXIT_FAILURE;
664 				break;
665 			}
666 
667 			wp_debug("New connection to client");
668 			if (set_nonblocking(chanclient) == -1) {
669 				wp_error("Error making new connection nonblocking: %s",
670 						strerror(errno));
671 				checked_close(chanclient);
672 				continue;
673 			}
674 
675 			if (incomplete == NUM_INCOMPLETE_CONNECTIONS) {
676 				wp_error("Dropping oldest incomplete connection (out of %d)",
677 						NUM_INCOMPLETE_CONNECTIONS);
678 				drop_incoming_connection(fds + 1, tokens,
679 						bytes_read, 0, incomplete);
680 				incomplete--;
681 			}
682 			fds[1 + incomplete].fd = chanclient;
683 			fds[1 + incomplete].events = POLLIN;
684 			fds[1 + incomplete].revents = 0;
685 			memset(&tokens[incomplete], 0,
686 					sizeof(struct connection_token));
687 			bytes_read[incomplete] = 0;
688 			incomplete++;
689 		}
690 	}
691 	for (int i = 0; i < incomplete; i++) {
692 		checked_close(fds[i + 1].fd);
693 	}
694 
695 	for (int i = 0; i < connmap.count; i++) {
696 		checked_close(connmap.data[i].linkfd);
697 	}
698 	free(connmap.data);
699 	checked_close(channelsock);
700 	return retcode;
701 }
702 
run_client(const struct sockaddr_un * socket_addr,const struct main_config * config,bool oneshot,const char * wayland_socket,pid_t eol_pid,int channelsock)703 int run_client(const struct sockaddr_un *socket_addr,
704 		const struct main_config *config, bool oneshot,
705 		const char *wayland_socket, pid_t eol_pid, int channelsock)
706 {
707 	wp_debug("I'm a client listening on %s", socket_addr->sun_path);
708 	wp_debug("version: %s", WAYPIPE_VERSION);
709 
710 	/* Connect to Wayland display. We don't use the wayland-client
711 	 * function here, because its errors aren't immediately useful,
712 	 * and older Wayland versions have edge cases */
713 	int dispfd = -1;
714 	struct sockaddr_un disp_addr;
715 	memset(&disp_addr, 0, sizeof(disp_addr));
716 
717 	if (wayland_socket) {
718 		dispfd = get_inherited_socket(wayland_socket);
719 		if (dispfd == -1) {
720 			if (eol_pid) {
721 				waitpid(eol_pid, NULL, 0);
722 			}
723 			close(channelsock);
724 			unlink(socket_addr->sun_path);
725 			return EXIT_FAILURE;
726 		}
727 		/* This socket is inherited and meant to be closed by Waypipe */
728 		if (dispfd >= 0 && dispfd < 256) {
729 			inherited_fds[dispfd / 64] &= ~(1uLL << (dispfd % 64));
730 		}
731 	} else {
732 		if (get_display_path(&disp_addr) == -1) {
733 			if (eol_pid) {
734 				waitpid(eol_pid, NULL, 0);
735 			}
736 			close(channelsock);
737 			unlink(socket_addr->sun_path);
738 			return EXIT_FAILURE;
739 		}
740 	}
741 
742 	if (oneshot) {
743 		if (!wayland_socket) {
744 			dispfd = connect_to_socket(&disp_addr);
745 		}
746 	} else {
747 		int test_conn = connect_to_socket(&disp_addr);
748 		if (test_conn == -1) {
749 			if (eol_pid) {
750 				waitpid(eol_pid, NULL, 0);
751 			}
752 			close(channelsock);
753 			unlink(socket_addr->sun_path);
754 			return EXIT_FAILURE;
755 		}
756 		checked_close(test_conn);
757 	}
758 	wp_debug("A wayland compositor is available. Proceeding.");
759 
760 	/* These handlers close the channelsock and dispfd */
761 	int retcode;
762 	if (oneshot) {
763 		retcode = run_single_client(
764 				channelsock, &eol_pid, config, dispfd);
765 	} else {
766 		retcode = run_multi_client(
767 				channelsock, &eol_pid, config, &disp_addr);
768 	}
769 	unlink(socket_addr->sun_path);
770 	int cleanup_type = shutdown_flag ? WNOHANG : 0;
771 
772 	int status = -1;
773 	// Don't return until all child processes complete
774 	if (wait_for_pid_and_clean(&eol_pid, &status, cleanup_type, NULL)) {
775 		retcode = WEXITSTATUS(status);
776 	}
777 	return retcode;
778 }
779