xref: /openbsd-src/usr.bin/tmux/client.c (revision ab0a70a7530153ec4f250b9a77919504fee94ad1)
1*ab0a70a7Snicm /* $OpenBSD: client.c,v 1.163 2024/08/26 07:30:46 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
498ca8272Snicm  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5311827fbSnicm  *
6311827fbSnicm  * Permission to use, copy, modify, and distribute this software for any
7311827fbSnicm  * purpose with or without fee is hereby granted, provided that the above
8311827fbSnicm  * copyright notice and this permission notice appear in all copies.
9311827fbSnicm  *
10311827fbSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11311827fbSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12311827fbSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311827fbSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14311827fbSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15311827fbSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16311827fbSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17311827fbSnicm  */
18311827fbSnicm 
19311827fbSnicm #include <sys/types.h>
20311827fbSnicm #include <sys/socket.h>
2196a20458Snicm #include <sys/uio.h>
22311827fbSnicm #include <sys/un.h>
23311827fbSnicm #include <sys/wait.h>
24311827fbSnicm 
25311827fbSnicm #include <errno.h>
267724d0b0Snicm #include <event.h>
276b59e893Snicm #include <fcntl.h>
285b8ac713Snicm #include <imsg.h>
2926529d6fSnicm #include <signal.h>
30311827fbSnicm #include <stdlib.h>
31311827fbSnicm #include <string.h>
32311827fbSnicm #include <unistd.h>
33311827fbSnicm 
34311827fbSnicm #include "tmux.h"
35311827fbSnicm 
36413e5e52Snicm static struct tmuxproc	*client_proc;
37413e5e52Snicm static struct tmuxpeer	*client_peer;
38b3e4eeb8Snicm static uint64_t		 client_flags;
39b18d2679Snicm static int		 client_suspended;
40413e5e52Snicm static enum {
4198a5f147Snicm 	CLIENT_EXIT_NONE,
4298a5f147Snicm 	CLIENT_EXIT_DETACHED,
4398a5f147Snicm 	CLIENT_EXIT_DETACHED_HUP,
4498a5f147Snicm 	CLIENT_EXIT_LOST_TTY,
4598a5f147Snicm 	CLIENT_EXIT_TERMINATED,
4698a5f147Snicm 	CLIENT_EXIT_LOST_SERVER,
4798a5f147Snicm 	CLIENT_EXIT_EXITED,
4898a5f147Snicm 	CLIENT_EXIT_SERVER_EXITED,
49e09158d2Snicm 	CLIENT_EXIT_MESSAGE_PROVIDED
5098a5f147Snicm } client_exitreason = CLIENT_EXIT_NONE;
51c920a131Snicm static int		 client_exitflag;
52413e5e52Snicm static int		 client_exitval;
53413e5e52Snicm static enum msgtype	 client_exittype;
54413e5e52Snicm static const char	*client_exitsession;
55e09158d2Snicm static char		*client_exitmessage;
56b15f33ecSnicm static const char	*client_execshell;
57b15f33ecSnicm static const char	*client_execcmd;
58413e5e52Snicm static int		 client_attached;
59f4bc7c7aSnicm static struct client_files client_files = RB_INITIALIZER(&client_files);
60311827fbSnicm 
61413e5e52Snicm static __dead void	 client_exec(const char *,const char *);
62413e5e52Snicm static int		 client_get_lock(char *);
63d337e2efSnicm static int		 client_connect(struct event_base *, const char *,
64d337e2efSnicm 			     uint64_t);
65c05282f8Snicm static void		 client_send_identify(const char *, const char *,
66c05282f8Snicm 			     char **, u_int, const char *, int);
67413e5e52Snicm static void		 client_signal(int);
68413e5e52Snicm static void		 client_dispatch(struct imsg *, void *);
69413e5e52Snicm static void		 client_dispatch_attached(struct imsg *);
70667452e4Snicm static void		 client_dispatch_wait(struct imsg *);
71413e5e52Snicm static const char	*client_exit_message(void);
72a2f71d82Snicm 
73549634dfSnicm /*
74549634dfSnicm  * Get server create lock. If already held then server start is happening in
755c705c36Snicm  * another client, so block until the lock is released and return -2 to
765c705c36Snicm  * retry. Return -1 on failure to continue and start the server anyway.
77549634dfSnicm  */
78413e5e52Snicm static int
79549634dfSnicm client_get_lock(char *lockfile)
80549634dfSnicm {
81549634dfSnicm 	int lockfd;
82549634dfSnicm 
83a5035d44Snicm 	log_debug("lock file is %s", lockfile);
84549634dfSnicm 
855c705c36Snicm 	if ((lockfd = open(lockfile, O_WRONLY|O_CREAT, 0600)) == -1) {
865c705c36Snicm 		log_debug("open failed: %s", strerror(errno));
875c705c36Snicm 		return (-1);
885c705c36Snicm 	}
895c705c36Snicm 
90a5035d44Snicm 	if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
91a5035d44Snicm 		log_debug("flock failed: %s", strerror(errno));
92a5035d44Snicm 		if (errno != EAGAIN)
93a5035d44Snicm 			return (lockfd);
94a5035d44Snicm 		while (flock(lockfd, LOCK_EX) == -1 && errno == EINTR)
95549634dfSnicm 			/* nothing */;
96549634dfSnicm 		close(lockfd);
975c705c36Snicm 		return (-2);
98549634dfSnicm 	}
99a5035d44Snicm 	log_debug("flock succeeded");
100549634dfSnicm 
101549634dfSnicm 	return (lockfd);
102549634dfSnicm }
103549634dfSnicm 
10465439d22Snicm /* Connect client to server. */
105413e5e52Snicm static int
106d337e2efSnicm client_connect(struct event_base *base, const char *path, uint64_t flags)
107311827fbSnicm {
108311827fbSnicm 	struct sockaddr_un	sa;
109311827fbSnicm 	size_t			size;
110a5035d44Snicm 	int			fd, lockfd = -1, locked = 0;
111a5035d44Snicm 	char		       *lockfile = NULL;
112311827fbSnicm 
113311827fbSnicm 	memset(&sa, 0, sizeof sa);
114311827fbSnicm 	sa.sun_family = AF_UNIX;
115311827fbSnicm 	size = strlcpy(sa.sun_path, path, sizeof sa.sun_path);
116311827fbSnicm 	if (size >= sizeof sa.sun_path) {
117311827fbSnicm 		errno = ENAMETOOLONG;
11865439d22Snicm 		return (-1);
119311827fbSnicm 	}
120a5035d44Snicm 	log_debug("socket is %s", path);
121311827fbSnicm 
122549634dfSnicm retry:
12386cee480Snicm 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
1240e6594eaSnicm 		return (-1);
125311827fbSnicm 
126a5035d44Snicm 	log_debug("trying connect");
1270e6594eaSnicm 	if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
128a5035d44Snicm 		log_debug("connect failed: %s", strerror(errno));
129549634dfSnicm 		if (errno != ECONNREFUSED && errno != ENOENT)
130549634dfSnicm 			goto failed;
131962d9ddaSnicm 		if (flags & CLIENT_NOSTARTSERVER)
132962d9ddaSnicm 			goto failed;
133b6d7e0c7Snicm 		if (~flags & CLIENT_STARTSERVER)
13465439d22Snicm 			goto failed;
135549634dfSnicm 		close(fd);
136549634dfSnicm 
137a5035d44Snicm 		if (!locked) {
138549634dfSnicm 			xasprintf(&lockfile, "%s.lock", path);
1395c705c36Snicm 			if ((lockfd = client_get_lock(lockfile)) < 0) {
1405c705c36Snicm 				log_debug("didn't get lock (%d)", lockfd);
1415c705c36Snicm 
142c38c88fdSnicm 				free(lockfile);
1435c705c36Snicm 				lockfile = NULL;
1445c705c36Snicm 
1455c705c36Snicm 				if (lockfd == -2)
146549634dfSnicm 					goto retry;
147c38c88fdSnicm 			}
1485c705c36Snicm 			log_debug("got lock (%d)", lockfd);
149a5035d44Snicm 
150a5035d44Snicm 			/*
151a5035d44Snicm 			 * Always retry at least once, even if we got the lock,
152a5035d44Snicm 			 * because another client could have taken the lock,
153a5035d44Snicm 			 * started the server and released the lock between our
154a5035d44Snicm 			 * connect() and flock().
155a5035d44Snicm 			 */
156a5035d44Snicm 			locked = 1;
157a5035d44Snicm 			goto retry;
158a5035d44Snicm 		}
159a5035d44Snicm 
1605c705c36Snicm 		if (lockfd >= 0 && unlink(path) != 0 && errno != ENOENT) {
161c38c88fdSnicm 			free(lockfile);
162c38c88fdSnicm 			close(lockfd);
163549634dfSnicm 			return (-1);
164c38c88fdSnicm 		}
165b6d7e0c7Snicm 		fd = server_start(client_proc, flags, base, lockfd, lockfile);
166a5035d44Snicm 	}
1676eb3685bSnicm 
1685c705c36Snicm 	if (locked && lockfd >= 0) {
1697d053cf9Snicm 		free(lockfile);
170549634dfSnicm 		close(lockfd);
171311827fbSnicm 	}
172a0ee4254Snicm 	setblocking(fd, 0);
17365439d22Snicm 	return (fd);
174311827fbSnicm 
17565439d22Snicm failed:
1766eb3685bSnicm 	if (locked) {
1776eb3685bSnicm 		free(lockfile);
1786eb3685bSnicm 		close(lockfd);
1796eb3685bSnicm 	}
18065439d22Snicm 	close(fd);
18165439d22Snicm 	return (-1);
18265439d22Snicm }
18365439d22Snicm 
18498a5f147Snicm /* Get exit string from reason number. */
18598a5f147Snicm const char *
18698a5f147Snicm client_exit_message(void)
18798a5f147Snicm {
188e7126be6Snicm 	static char msg[256];
189e7126be6Snicm 
19098a5f147Snicm 	switch (client_exitreason) {
19198a5f147Snicm 	case CLIENT_EXIT_NONE:
19298a5f147Snicm 		break;
19398a5f147Snicm 	case CLIENT_EXIT_DETACHED:
194e7126be6Snicm 		if (client_exitsession != NULL) {
195e7126be6Snicm 			xsnprintf(msg, sizeof msg, "detached "
196e7126be6Snicm 			    "(from session %s)", client_exitsession);
197e7126be6Snicm 			return (msg);
198e7126be6Snicm 		}
19998a5f147Snicm 		return ("detached");
20098a5f147Snicm 	case CLIENT_EXIT_DETACHED_HUP:
201e7126be6Snicm 		if (client_exitsession != NULL) {
202e7126be6Snicm 			xsnprintf(msg, sizeof msg, "detached and SIGHUP "
203e7126be6Snicm 			    "(from session %s)", client_exitsession);
204e7126be6Snicm 			return (msg);
205e7126be6Snicm 		}
20698a5f147Snicm 		return ("detached and SIGHUP");
20798a5f147Snicm 	case CLIENT_EXIT_LOST_TTY:
20898a5f147Snicm 		return ("lost tty");
20998a5f147Snicm 	case CLIENT_EXIT_TERMINATED:
21098a5f147Snicm 		return ("terminated");
21198a5f147Snicm 	case CLIENT_EXIT_LOST_SERVER:
212e11f6b2aSnicm 		return ("server exited unexpectedly");
21398a5f147Snicm 	case CLIENT_EXIT_EXITED:
21498a5f147Snicm 		return ("exited");
21598a5f147Snicm 	case CLIENT_EXIT_SERVER_EXITED:
21698a5f147Snicm 		return ("server exited");
217e09158d2Snicm 	case CLIENT_EXIT_MESSAGE_PROVIDED:
218e09158d2Snicm 		return (client_exitmessage);
21998a5f147Snicm 	}
22098a5f147Snicm 	return ("unknown reason");
22198a5f147Snicm }
22298a5f147Snicm 
223c920a131Snicm /* Exit if all streams flushed. */
224c920a131Snicm static void
225c920a131Snicm client_exit(void)
226c920a131Snicm {
227ab26eabfSnicm 	if (!file_write_left(&client_files))
228c920a131Snicm 		proc_exit(client_proc);
229c920a131Snicm }
230c920a131Snicm 
23165439d22Snicm /* Client main loop. */
23265439d22Snicm int
233d337e2efSnicm client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
234d337e2efSnicm     int feat)
23565439d22Snicm {
236a6afde38Snicm 	struct cmd_parse_result	*pr;
237f4bc7c7aSnicm 	struct msg_command	*data;
238b6d7e0c7Snicm 	int			 fd, i;
239c05282f8Snicm 	const char		*ttynam, *termname, *cwd;
240a4c8f3f7Snicm 	pid_t			 ppid;
24165439d22Snicm 	enum msgtype		 msg;
24290896992Snicm 	struct termios		 tio, saved_tio;
243b3e4eeb8Snicm 	size_t			 size, linesize = 0;
244b3e4eeb8Snicm 	ssize_t			 linelen;
245c05282f8Snicm 	char			*line = NULL, **caps = NULL, *cause;
246c05282f8Snicm 	u_int			 ncaps = 0;
247d8b32369Snicm 	struct args_value	*values;
24865439d22Snicm 
24965439d22Snicm 	/* Set up the initial command. */
250667452e4Snicm 	if (shell_command != NULL) {
25165439d22Snicm 		msg = MSG_SHELL;
252b9704b83Snicm 		flags |= CLIENT_STARTSERVER;
25365439d22Snicm 	} else if (argc == 0) {
25465439d22Snicm 		msg = MSG_COMMAND;
255b6d7e0c7Snicm 		flags |= CLIENT_STARTSERVER;
25665439d22Snicm 	} else {
25765439d22Snicm 		msg = MSG_COMMAND;
25865439d22Snicm 
25965439d22Snicm 		/*
260d8b32369Snicm 		 * It's annoying parsing the command string twice (in client
261d8b32369Snicm 		 * and later in server) but it is necessary to get the start
262d8b32369Snicm 		 * server flag.
26365439d22Snicm 		 */
264d8b32369Snicm 		values = args_from_vector(argc, argv);
265d8b32369Snicm 		pr = cmd_parse_from_arguments(values, argc, NULL);
266a6afde38Snicm 		if (pr->status == CMD_PARSE_SUCCESS) {
26790d7ba38Snicm 			if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
268b6d7e0c7Snicm 				flags |= CLIENT_STARTSERVER;
269a6afde38Snicm 			cmd_list_free(pr->cmdlist);
270a6afde38Snicm 		} else
271a6afde38Snicm 			free(pr->error);
272d8b32369Snicm 		args_free_values(values, argc);
273d8b32369Snicm 		free(values);
274a3834af8Snicm 	}
27565439d22Snicm 
2760e6594eaSnicm 	/* Create client process structure (starts logging). */
277c37a9299Snicm 	client_proc = proc_start("client");
278c37a9299Snicm 	proc_set_signals(client_proc, client_signal);
2790e6594eaSnicm 
280b3e4eeb8Snicm 	/* Save the flags. */
281b3e4eeb8Snicm 	client_flags = flags;
282d337e2efSnicm 	log_debug("flags are %#llx", (unsigned long long)client_flags);
283b3e4eeb8Snicm 
284a5035d44Snicm 	/* Initialize the client socket and start the server. */
285b6d7e0c7Snicm 	fd = client_connect(base, socket_path, client_flags);
28665439d22Snicm 	if (fd == -1) {
2873ccbc5e0Snicm 		if (errno == ECONNREFUSED) {
2883ccbc5e0Snicm 			fprintf(stderr, "no server running on %s\n",
2893ccbc5e0Snicm 			    socket_path);
2903ccbc5e0Snicm 		} else {
2913ccbc5e0Snicm 			fprintf(stderr, "error connecting to %s (%s)\n",
2923ccbc5e0Snicm 			    socket_path, strerror(errno));
2933ccbc5e0Snicm 		}
29465439d22Snicm 		return (1);
29565439d22Snicm 	}
296667452e4Snicm 	client_peer = proc_add_peer(client_proc, fd, client_dispatch, NULL);
2975b8ac713Snicm 
298e258a268Snicm 	/* Save these before pledge(). */
299ca2738caSnicm 	if ((cwd = find_cwd()) == NULL && (cwd = find_home()) == NULL)
3003c571915Snicm 		cwd = "/";
301e258a268Snicm 	if ((ttynam = ttyname(STDIN_FILENO)) == NULL)
302e258a268Snicm 		ttynam = "";
303c05282f8Snicm 	if ((termname = getenv("TERM")) == NULL)
304c05282f8Snicm 		termname = "";
305e258a268Snicm 
306e258a268Snicm 	/*
307e258a268Snicm 	 * Drop privileges for client. "proc exec" is needed for -c and for
308e258a268Snicm 	 * locking (which uses system(3)).
309e258a268Snicm 	 *
310e258a268Snicm 	 * "tty" is needed to restore termios(4) and also for some reason -CC
311e258a268Snicm 	 * does not work properly without it (input is not recognised).
312e258a268Snicm 	 *
313e258a268Snicm 	 * "sendfd" is dropped later in client_dispatch_wait().
314e258a268Snicm 	 */
315f4bc7c7aSnicm 	if (pledge(
316f4bc7c7aSnicm 	    "stdio rpath wpath cpath unix sendfd proc exec tty",
317f4bc7c7aSnicm 	    NULL) != 0)
318e258a268Snicm 		fatal("pledge failed");
319e258a268Snicm 
320c05282f8Snicm 	/* Load terminfo entry if any. */
321c05282f8Snicm 	if (isatty(STDIN_FILENO) &&
322c05282f8Snicm 	    *termname != '\0' &&
323c05282f8Snicm 	    tty_term_read_list(termname, STDIN_FILENO, &caps, &ncaps,
324c05282f8Snicm 	    &cause) != 0) {
325c05282f8Snicm 		fprintf(stderr, "%s\n", cause);
326c05282f8Snicm 		free(cause);
327c05282f8Snicm 		return (1);
328c05282f8Snicm 	}
329c05282f8Snicm 
330e258a268Snicm 	/* Free stuff that is not used in the client. */
331b6ec3d9fSnicm 	if (ptm_fd != -1)
332b6ec3d9fSnicm 		close(ptm_fd);
333d89252e5Snicm 	options_free(global_options);
334d89252e5Snicm 	options_free(global_s_options);
335d89252e5Snicm 	options_free(global_w_options);
336fb46cb3dSnicm 	environ_free(global_environ);
33765439d22Snicm 
338f4bc7c7aSnicm 	/* Set up control mode. */
3396b014674Snicm 	if (client_flags & CLIENT_CONTROLCONTROL) {
34008cc55a3Snicm 		if (tcgetattr(STDIN_FILENO, &saved_tio) != 0) {
34108cc55a3Snicm 			fprintf(stderr, "tcgetattr failed: %s\n",
34208cc55a3Snicm 			    strerror(errno));
34308cc55a3Snicm 			return (1);
34408cc55a3Snicm 		}
34590896992Snicm 		cfmakeraw(&tio);
34690896992Snicm 		tio.c_iflag = ICRNL|IXANY;
34790896992Snicm 		tio.c_oflag = OPOST|ONLCR;
34890896992Snicm 		tio.c_lflag = NOKERNINFO;
34990896992Snicm 		tio.c_cflag = CREAD|CS8|HUPCL;
35090896992Snicm 		tio.c_cc[VMIN] = 1;
35190896992Snicm 		tio.c_cc[VTIME] = 0;
35290896992Snicm 		cfsetispeed(&tio, cfgetispeed(&saved_tio));
35390896992Snicm 		cfsetospeed(&tio, cfgetospeed(&saved_tio));
35490896992Snicm 		tcsetattr(STDIN_FILENO, TCSANOW, &tio);
35590896992Snicm 	}
356c66f8665Snicm 
357e1803d63Snicm 	/* Send identify messages. */
358c05282f8Snicm 	client_send_identify(ttynam, termname, caps, ncaps, cwd, feat);
359c05282f8Snicm 	tty_term_free_list(caps, ncaps);
3608ab000fcSnicm 	proc_flush_peer(client_peer);
361311827fbSnicm 
36265439d22Snicm 	/* Send first command. */
36365439d22Snicm 	if (msg == MSG_COMMAND) {
364340463dcSnicm 		/* How big is the command? */
365340463dcSnicm 		size = 0;
366340463dcSnicm 		for (i = 0; i < argc; i++)
367340463dcSnicm 			size += strlen(argv[i]) + 1;
368fa2f6809Snicm 		if (size > MAX_IMSGSIZE - (sizeof *data)) {
369fa2f6809Snicm 			fprintf(stderr, "command too long\n");
370fa2f6809Snicm 			return (1);
371fa2f6809Snicm 		}
372340463dcSnicm 		data = xmalloc((sizeof *data) + size);
373340463dcSnicm 
37465439d22Snicm 		/* Prepare command for server. */
375340463dcSnicm 		data->argc = argc;
376340463dcSnicm 		if (cmd_pack_argv(argc, argv, (char *)(data + 1), size) != 0) {
37736fa3172Snicm 			fprintf(stderr, "command too long\n");
378340463dcSnicm 			free(data);
37965439d22Snicm 			return (1);
380311827fbSnicm 		}
381340463dcSnicm 		size += sizeof *data;
382311827fbSnicm 
383340463dcSnicm 		/* Send the command. */
3845b8ac713Snicm 		if (proc_send(client_peer, msg, -1, data, size) != 0) {
385340463dcSnicm 			fprintf(stderr, "failed to send command\n");
386340463dcSnicm 			free(data);
387340463dcSnicm 			return (1);
388340463dcSnicm 		}
389340463dcSnicm 		free(data);
39065439d22Snicm 	} else if (msg == MSG_SHELL)
3915b8ac713Snicm 		proc_send(client_peer, msg, -1, NULL, 0);
39265439d22Snicm 
3935b8ac713Snicm 	/* Start main loop. */
3945b8ac713Snicm 	proc_loop(client_proc, NULL);
39565439d22Snicm 
396b15f33ecSnicm 	/* Run command if user requested exec, instead of exiting. */
397b15f33ecSnicm 	if (client_exittype == MSG_EXEC) {
398b15f33ecSnicm 		if (client_flags & CLIENT_CONTROLCONTROL)
399b15f33ecSnicm 			tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
400b15f33ecSnicm 		client_exec(client_execshell, client_execcmd);
401b15f33ecSnicm 	}
402b15f33ecSnicm 
403a34cf9c8Snicm 	/* Restore streams to blocking. */
404a34cf9c8Snicm 	setblocking(STDIN_FILENO, 1);
405a34cf9c8Snicm 	setblocking(STDOUT_FILENO, 1);
406a34cf9c8Snicm 	setblocking(STDERR_FILENO, 1);
407a34cf9c8Snicm 
40865439d22Snicm 	/* Print the exit message, if any, and exit. */
409a4c8f3f7Snicm 	if (client_attached) {
4106b014674Snicm 		if (client_exitreason != CLIENT_EXIT_NONE)
41198a5f147Snicm 			printf("[%s]\n", client_exit_message());
412a4c8f3f7Snicm 
413a4c8f3f7Snicm 		ppid = getppid();
414a4c8f3f7Snicm 		if (client_exittype == MSG_DETACHKILL && ppid > 1)
415a4c8f3f7Snicm 			kill(ppid, SIGHUP);
416a34cf9c8Snicm 	} else if (client_flags & CLIENT_CONTROL) {
41784f8cb99Snicm 		if (client_exitreason != CLIENT_EXIT_NONE)
41884f8cb99Snicm 			printf("%%exit %s\n", client_exit_message());
41984f8cb99Snicm 		else
42084f8cb99Snicm 			printf("%%exit\n");
421b3e4eeb8Snicm 		fflush(stdout);
422b3e4eeb8Snicm 		if (client_flags & CLIENT_CONTROL_WAITEXIT) {
423b3e4eeb8Snicm 			setvbuf(stdin, NULL, _IOLBF, 0);
424b3e4eeb8Snicm 			for (;;) {
425b3e4eeb8Snicm 				linelen = getline(&line, &linesize, stdin);
426b3e4eeb8Snicm 				if (linelen <= 1)
427b3e4eeb8Snicm 					break;
428b3e4eeb8Snicm 			}
429b3e4eeb8Snicm 			free(line);
430b3e4eeb8Snicm 		}
431a34cf9c8Snicm 		if (client_flags & CLIENT_CONTROLCONTROL) {
43284f8cb99Snicm 			printf("\033\\");
433b3e4eeb8Snicm 			fflush(stdout);
43490896992Snicm 			tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
435a34cf9c8Snicm 		}
43639b22c05Snicm 	} else if (client_exitreason != CLIENT_EXIT_NONE)
4370e6594eaSnicm 		fprintf(stderr, "%s\n", client_exit_message());
43865439d22Snicm 	return (client_exitval);
43965439d22Snicm }
44065439d22Snicm 
441e1803d63Snicm /* Send identify messages to server. */
442413e5e52Snicm static void
443c05282f8Snicm client_send_identify(const char *ttynam, const char *termname, char **caps,
444c05282f8Snicm     u_int ncaps, const char *cwd, int feat)
4455344c1aaSnicm {
446e1803d63Snicm 	char	**ss;
447135343a5Snicm 	size_t	  sslen;
448*ab0a70a7Snicm 	int	  fd;
449*ab0a70a7Snicm 	uint64_t  flags = client_flags;
450fb92d41aSnicm 	pid_t	  pid;
451c05282f8Snicm 	u_int	  i;
4525344c1aaSnicm 
453*ab0a70a7Snicm 	proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &flags, sizeof flags);
454d337e2efSnicm 	proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &client_flags,
455d337e2efSnicm 	    sizeof client_flags);
4565344c1aaSnicm 
457c05282f8Snicm 	proc_send(client_peer, MSG_IDENTIFY_TERM, -1, termname,
458c05282f8Snicm 	    strlen(termname) + 1);
4595a160f88Snicm 	proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat);
4605344c1aaSnicm 
4613dd392ddSnicm 	proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam,
4623dd392ddSnicm 	    strlen(ttynam) + 1);
4635b8ac713Snicm 	proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1);
4645344c1aaSnicm 
465c05282f8Snicm 	for (i = 0; i < ncaps; i++) {
466c05282f8Snicm 		proc_send(client_peer, MSG_IDENTIFY_TERMINFO, -1,
467c05282f8Snicm 		    caps[i], strlen(caps[i]) + 1);
468c05282f8Snicm 	}
469c05282f8Snicm 
4703448d637Snicm 	if ((fd = dup(STDIN_FILENO)) == -1)
4713448d637Snicm 		fatal("dup failed");
4725b8ac713Snicm 	proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0);
47354e3eca9Snicm 	if ((fd = dup(STDOUT_FILENO)) == -1)
47454e3eca9Snicm 		fatal("dup failed");
47554e3eca9Snicm 	proc_send(client_peer, MSG_IDENTIFY_STDOUT, fd, NULL, 0);
476e1803d63Snicm 
477fb92d41aSnicm 	pid = getpid();
4785b8ac713Snicm 	proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid);
479fb92d41aSnicm 
480135343a5Snicm 	for (ss = environ; *ss != NULL; ss++) {
481135343a5Snicm 		sslen = strlen(*ss) + 1;
4823dd392ddSnicm 		if (sslen > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
4833dd392ddSnicm 			continue;
4845b8ac713Snicm 		proc_send(client_peer, MSG_IDENTIFY_ENVIRON, -1, *ss, sslen);
485135343a5Snicm 	}
4860ab4a763Snicm 
4875b8ac713Snicm 	proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0);
4887724d0b0Snicm }
4897724d0b0Snicm 
4906b014674Snicm /* Run command in shell; used for -c. */
491413e5e52Snicm static __dead void
492e192934cSnicm client_exec(const char *shell, const char *shellcmd)
4936b014674Snicm {
4946b014674Snicm 	char	*argv0;
4956b014674Snicm 
496e192934cSnicm 	log_debug("shell %s, command %s", shell, shellcmd);
497611df774Snicm 	argv0 = shell_argv0(shell, !!(client_flags & CLIENT_LOGIN));
4986b014674Snicm 	setenv("SHELL", shell, 1);
4996b014674Snicm 
50089b52a5bSnicm 	proc_clear_signals(client_proc, 1);
50189b52a5bSnicm 
5026b014674Snicm 	setblocking(STDIN_FILENO, 1);
5036b014674Snicm 	setblocking(STDOUT_FILENO, 1);
5046b014674Snicm 	setblocking(STDERR_FILENO, 1);
5056b014674Snicm 	closefrom(STDERR_FILENO + 1);
5066b014674Snicm 
507e192934cSnicm 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
5086b014674Snicm 	fatal("execl failed");
5096b014674Snicm }
5106b014674Snicm 
5115b8ac713Snicm /* Callback to handle signals in the client. */
512413e5e52Snicm static void
5135b8ac713Snicm client_signal(int sig)
51465439d22Snicm {
5155b8ac713Snicm 	struct sigaction sigact;
5165b8ac713Snicm 	int		 status;
517bc5de943Snicm 	pid_t		 pid;
5185b8ac713Snicm 
519b18d2679Snicm 	log_debug("%s: %s", __func__, strsignal(sig));
520bc5de943Snicm 	if (sig == SIGCHLD) {
521bc5de943Snicm 		for (;;) {
522bc5de943Snicm 			pid = waitpid(WAIT_ANY, &status, WNOHANG);
523bc5de943Snicm 			if (pid == 0)
524bc5de943Snicm 				break;
525bc5de943Snicm 			if (pid == -1) {
526bc5de943Snicm 				if (errno == ECHILD)
527bc5de943Snicm 					break;
528bc5de943Snicm 				log_debug("waitpid failed: %s",
529bc5de943Snicm 				    strerror(errno));
530bc5de943Snicm 			}
531bc5de943Snicm 		}
532bc5de943Snicm 	} else if (!client_attached) {
53322a1d4f1Snicm 		if (sig == SIGTERM || sig == SIGHUP)
5345b8ac713Snicm 			proc_exit(client_proc);
5355b8ac713Snicm 	} else {
5365b8ac713Snicm 		switch (sig) {
5375b8ac713Snicm 		case SIGHUP:
5385b8ac713Snicm 			client_exitreason = CLIENT_EXIT_LOST_TTY;
5395b8ac713Snicm 			client_exitval = 1;
5405b8ac713Snicm 			proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
5415b8ac713Snicm 			break;
5425b8ac713Snicm 		case SIGTERM:
543b18d2679Snicm 			if (!client_suspended)
5445b8ac713Snicm 				client_exitreason = CLIENT_EXIT_TERMINATED;
5455b8ac713Snicm 			client_exitval = 1;
5465b8ac713Snicm 			proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
5475b8ac713Snicm 			break;
5485b8ac713Snicm 		case SIGWINCH:
5495b8ac713Snicm 			proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
5505b8ac713Snicm 			break;
5515b8ac713Snicm 		case SIGCONT:
5525b8ac713Snicm 			memset(&sigact, 0, sizeof sigact);
5535b8ac713Snicm 			sigemptyset(&sigact.sa_mask);
5545b8ac713Snicm 			sigact.sa_flags = SA_RESTART;
5555b8ac713Snicm 			sigact.sa_handler = SIG_IGN;
5565b8ac713Snicm 			if (sigaction(SIGTSTP, &sigact, NULL) != 0)
5575b8ac713Snicm 				fatal("sigaction failed");
5585b8ac713Snicm 			proc_send(client_peer, MSG_WAKEUP, -1, NULL, 0);
559b18d2679Snicm 			client_suspended = 0;
5605b8ac713Snicm 			break;
5615b8ac713Snicm 		}
5625b8ac713Snicm 	}
5635b8ac713Snicm }
5645b8ac713Snicm 
5650c0c2584Snicm /* Callback for file write error or close. */
5660c0c2584Snicm static void
5670c0c2584Snicm client_file_check_cb(__unused struct client *c, __unused const char *path,
5680c0c2584Snicm     __unused int error, __unused int closed, __unused struct evbuffer *buffer,
5690c0c2584Snicm     __unused void *data)
5700c0c2584Snicm {
5710c0c2584Snicm 	if (client_exitflag)
5720c0c2584Snicm 		client_exit();
5730c0c2584Snicm }
5740c0c2584Snicm 
5755b8ac713Snicm /* Callback for client read events. */
576413e5e52Snicm static void
577667452e4Snicm client_dispatch(struct imsg *imsg, __unused void *arg)
5785b8ac713Snicm {
5795b8ac713Snicm 	if (imsg == NULL) {
5806e7200d4Snicm 		if (!client_exitflag) {
5815b8ac713Snicm 			client_exitreason = CLIENT_EXIT_LOST_SERVER;
5825b8ac713Snicm 			client_exitval = 1;
5836e7200d4Snicm 		}
5840e6594eaSnicm 		proc_exit(client_proc);
5850e6594eaSnicm 		return;
5860e6594eaSnicm 	}
5870e6594eaSnicm 
5880e6594eaSnicm 	if (client_attached)
5895b8ac713Snicm 		client_dispatch_attached(imsg);
5905b8ac713Snicm 	else
591667452e4Snicm 		client_dispatch_wait(imsg);
5925b8ac713Snicm }
5935b8ac713Snicm 
594e09158d2Snicm /* Process an exit message. */
595e09158d2Snicm static void
596e09158d2Snicm client_dispatch_exit_message(char *data, size_t datalen)
597e09158d2Snicm {
598e09158d2Snicm 	int	retval;
599e09158d2Snicm 
600e09158d2Snicm 	if (datalen < sizeof retval && datalen != 0)
601e09158d2Snicm 		fatalx("bad MSG_EXIT size");
602e09158d2Snicm 
603e09158d2Snicm 	if (datalen >= sizeof retval) {
604e09158d2Snicm 		memcpy(&retval, data, sizeof retval);
605e09158d2Snicm 		client_exitval = retval;
606e09158d2Snicm 	}
607e09158d2Snicm 
608e09158d2Snicm 	if (datalen > sizeof retval) {
609e09158d2Snicm 		datalen -= sizeof retval;
610e09158d2Snicm 		data += sizeof retval;
611e09158d2Snicm 
612e09158d2Snicm 		client_exitmessage = xmalloc(datalen);
613e09158d2Snicm 		memcpy(client_exitmessage, data, datalen);
614e09158d2Snicm 		client_exitmessage[datalen - 1] = '\0';
615e09158d2Snicm 
616e09158d2Snicm 		client_exitreason = CLIENT_EXIT_MESSAGE_PROVIDED;
617e09158d2Snicm 	}
618e09158d2Snicm }
619e09158d2Snicm 
6205b8ac713Snicm /* Dispatch imsgs when in wait state (before MSG_READY). */
621413e5e52Snicm static void
622667452e4Snicm client_dispatch_wait(struct imsg *imsg)
6235b8ac713Snicm {
6247f1a01f4Snicm 	char		*data;
6255b8ac713Snicm 	ssize_t		 datalen;
626e258a268Snicm 	static int	 pledge_applied;
627e258a268Snicm 
628e258a268Snicm 	/*
629e258a268Snicm 	 * "sendfd" is no longer required once all of the identify messages
630e258a268Snicm 	 * have been sent. We know the server won't send us anything until that
631e258a268Snicm 	 * point (because we don't ask it to), so we can drop "sendfd" once we
632e258a268Snicm 	 * get the first message from the server.
633e258a268Snicm 	 */
634e258a268Snicm 	if (!pledge_applied) {
635f4bc7c7aSnicm 		if (pledge(
636f4bc7c7aSnicm 		    "stdio rpath wpath cpath unix proc exec tty",
637f4bc7c7aSnicm 		    NULL) != 0)
638e258a268Snicm 			fatal("pledge failed");
639e258a268Snicm 		pledge_applied = 1;
640f4bc7c7aSnicm 	}
64165439d22Snicm 
6425b8ac713Snicm 	data = imsg->data;
6435b8ac713Snicm 	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
6447f1a01f4Snicm 
6455b8ac713Snicm 	switch (imsg->hdr.type) {
64665439d22Snicm 	case MSG_EXIT:
64765439d22Snicm 	case MSG_SHUTDOWN:
648e09158d2Snicm 		client_dispatch_exit_message(data, datalen);
649c920a131Snicm 		client_exitflag = 1;
650c920a131Snicm 		client_exit();
6515b8ac713Snicm 		break;
65265439d22Snicm 	case MSG_READY:
65365439d22Snicm 		if (datalen != 0)
65465439d22Snicm 			fatalx("bad MSG_READY size");
65565439d22Snicm 
65665439d22Snicm 		client_attached = 1;
6575b8ac713Snicm 		proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
65865439d22Snicm 		break;
65965439d22Snicm 	case MSG_VERSION:
66065439d22Snicm 		if (datalen != 0)
66165439d22Snicm 			fatalx("bad MSG_VERSION size");
66265439d22Snicm 
663c66f8665Snicm 		fprintf(stderr, "protocol version mismatch "
664e5f4d2afSnicm 		    "(client %d, server %u)\n", PROTOCOL_VERSION,
6655913d2d6Snicm 		    imsg->hdr.peerid & 0xff);
66665439d22Snicm 		client_exitval = 1;
6675b8ac713Snicm 		proc_exit(client_proc);
6685b8ac713Snicm 		break;
669b3e4eeb8Snicm 	case MSG_FLAGS:
670b3e4eeb8Snicm 		if (datalen != sizeof client_flags)
671b3e4eeb8Snicm 			fatalx("bad MSG_FLAGS string");
672b3e4eeb8Snicm 
673b3e4eeb8Snicm 		memcpy(&client_flags, data, sizeof client_flags);
674d337e2efSnicm 		log_debug("new flags are %#llx",
675d337e2efSnicm 		    (unsigned long long)client_flags);
676b3e4eeb8Snicm 		break;
67765439d22Snicm 	case MSG_SHELL:
678197d2e58Snicm 		if (datalen == 0 || data[datalen - 1] != '\0')
6797f1a01f4Snicm 			fatalx("bad MSG_SHELL string");
68065439d22Snicm 
681667452e4Snicm 		client_exec(data, shell_command);
68265439d22Snicm 		/* NOTREACHED */
68390896992Snicm 	case MSG_DETACH:
684e7126be6Snicm 	case MSG_DETACHKILL:
6855b8ac713Snicm 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
68690896992Snicm 		break;
68790896992Snicm 	case MSG_EXITED:
6885b8ac713Snicm 		proc_exit(client_proc);
6895b8ac713Snicm 		break;
690f4bc7c7aSnicm 	case MSG_READ_OPEN:
6910c0c2584Snicm 		file_read_open(&client_files, client_peer, imsg, 1,
6920c0c2584Snicm 		    !(client_flags & CLIENT_CONTROL), client_file_check_cb,
6930c0c2584Snicm 		    NULL);
694f4bc7c7aSnicm 		break;
69583e2ab13Snicm 	case MSG_READ_CANCEL:
69683e2ab13Snicm 		file_read_cancel(&client_files, imsg);
69783e2ab13Snicm 		break;
698f4bc7c7aSnicm 	case MSG_WRITE_OPEN:
6990c0c2584Snicm 		file_write_open(&client_files, client_peer, imsg, 1,
7000c0c2584Snicm 		    !(client_flags & CLIENT_CONTROL), client_file_check_cb,
7010c0c2584Snicm 		    NULL);
702f4bc7c7aSnicm 		break;
703f4bc7c7aSnicm 	case MSG_WRITE:
7040c0c2584Snicm 		file_write_data(&client_files, imsg);
705f4bc7c7aSnicm 		break;
706f4bc7c7aSnicm 	case MSG_WRITE_CLOSE:
7070c0c2584Snicm 		file_write_close(&client_files, imsg);
708f4bc7c7aSnicm 		break;
709b0727cb7Snicm 	case MSG_OLDSTDERR:
710b0727cb7Snicm 	case MSG_OLDSTDIN:
711b0727cb7Snicm 	case MSG_OLDSTDOUT:
712b0727cb7Snicm 		fprintf(stderr, "server version is too old for client\n");
713b0727cb7Snicm 		proc_exit(client_proc);
714b0727cb7Snicm 		break;
71565439d22Snicm 	}
71665439d22Snicm }
71765439d22Snicm 
71865439d22Snicm /* Dispatch imsgs in attached state (after MSG_READY). */
719413e5e52Snicm static void
7205b8ac713Snicm client_dispatch_attached(struct imsg *imsg)
7215b522837Snicm {
7227724d0b0Snicm 	struct sigaction	 sigact;
7237f1a01f4Snicm 	char			*data;
7245b8ac713Snicm 	ssize_t			 datalen;
725fd234c13Snicm 
7265b8ac713Snicm 	data = imsg->data;
7275b8ac713Snicm 	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
7287f1a01f4Snicm 
7295b8ac713Snicm 	switch (imsg->hdr.type) {
730b3e4eeb8Snicm 	case MSG_FLAGS:
731b3e4eeb8Snicm 		if (datalen != sizeof client_flags)
732b3e4eeb8Snicm 			fatalx("bad MSG_FLAGS string");
733b3e4eeb8Snicm 
734b3e4eeb8Snicm 		memcpy(&client_flags, data, sizeof client_flags);
735d337e2efSnicm 		log_debug("new flags are %#llx",
736d337e2efSnicm 		    (unsigned long long)client_flags);
737b3e4eeb8Snicm 		break;
7385b522837Snicm 	case MSG_DETACH:
739e7126be6Snicm 	case MSG_DETACHKILL:
740e7126be6Snicm 		if (datalen == 0 || data[datalen - 1] != '\0')
741e7126be6Snicm 			fatalx("bad MSG_DETACH string");
7425b522837Snicm 
743e7126be6Snicm 		client_exitsession = xstrdup(data);
7445b8ac713Snicm 		client_exittype = imsg->hdr.type;
7455b8ac713Snicm 		if (imsg->hdr.type == MSG_DETACHKILL)
74698a5f147Snicm 			client_exitreason = CLIENT_EXIT_DETACHED_HUP;
747a4c8f3f7Snicm 		else
74898a5f147Snicm 			client_exitreason = CLIENT_EXIT_DETACHED;
7495b8ac713Snicm 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
7505b522837Snicm 		break;
751b15f33ecSnicm 	case MSG_EXEC:
752b15f33ecSnicm 		if (datalen == 0 || data[datalen - 1] != '\0' ||
753b15f33ecSnicm 		    strlen(data) + 1 == (size_t)datalen)
754b15f33ecSnicm 			fatalx("bad MSG_EXEC string");
755b15f33ecSnicm 		client_execcmd = xstrdup(data);
756b15f33ecSnicm 		client_execshell = xstrdup(data + strlen(data) + 1);
757b15f33ecSnicm 
758b15f33ecSnicm 		client_exittype = imsg->hdr.type;
759b15f33ecSnicm 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
760b15f33ecSnicm 		break;
7615b522837Snicm 	case MSG_EXIT:
762e09158d2Snicm 		client_dispatch_exit_message(data, datalen);
763e09158d2Snicm 		if (client_exitreason == CLIENT_EXIT_NONE)
76498a5f147Snicm 			client_exitreason = CLIENT_EXIT_EXITED;
765e09158d2Snicm 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
7665b522837Snicm 		break;
7675b522837Snicm 	case MSG_EXITED:
768fd234c13Snicm 		if (datalen != 0)
7695b522837Snicm 			fatalx("bad MSG_EXITED size");
7705b522837Snicm 
7715b8ac713Snicm 		proc_exit(client_proc);
7725b8ac713Snicm 		break;
7735b522837Snicm 	case MSG_SHUTDOWN:
774fd234c13Snicm 		if (datalen != 0)
7755b522837Snicm 			fatalx("bad MSG_SHUTDOWN size");
7765b522837Snicm 
7775b8ac713Snicm 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
77898a5f147Snicm 		client_exitreason = CLIENT_EXIT_SERVER_EXITED;
7794ae4f420Snicm 		client_exitval = 1;
7805b522837Snicm 		break;
7815b522837Snicm 	case MSG_SUSPEND:
782fd234c13Snicm 		if (datalen != 0)
7835b522837Snicm 			fatalx("bad MSG_SUSPEND size");
7845b522837Snicm 
7857724d0b0Snicm 		memset(&sigact, 0, sizeof sigact);
7867724d0b0Snicm 		sigemptyset(&sigact.sa_mask);
7877724d0b0Snicm 		sigact.sa_flags = SA_RESTART;
7887724d0b0Snicm 		sigact.sa_handler = SIG_DFL;
7897724d0b0Snicm 		if (sigaction(SIGTSTP, &sigact, NULL) != 0)
7907724d0b0Snicm 			fatal("sigaction failed");
791b18d2679Snicm 		client_suspended = 1;
7927724d0b0Snicm 		kill(getpid(), SIGTSTP);
7935b522837Snicm 		break;
7941c5425bdSnicm 	case MSG_LOCK:
795197d2e58Snicm 		if (datalen == 0 || data[datalen - 1] != '\0')
7967f1a01f4Snicm 			fatalx("bad MSG_LOCK string");
7971c5425bdSnicm 
7987f1a01f4Snicm 		system(data);
7995b8ac713Snicm 		proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
8001c5425bdSnicm 		break;
8015b522837Snicm 	}
8025b522837Snicm }
803