xref: /netbsd-src/external/bsd/tmux/dist/client.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
199e242abSchristos /* $OpenBSD$ */
2698d5317Sjmmv 
3698d5317Sjmmv /*
4f26e8bc9Schristos  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5698d5317Sjmmv  *
6698d5317Sjmmv  * Permission to use, copy, modify, and distribute this software for any
7698d5317Sjmmv  * purpose with or without fee is hereby granted, provided that the above
8698d5317Sjmmv  * copyright notice and this permission notice appear in all copies.
9698d5317Sjmmv  *
10698d5317Sjmmv  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11698d5317Sjmmv  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12698d5317Sjmmv  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13698d5317Sjmmv  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14698d5317Sjmmv  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15698d5317Sjmmv  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16698d5317Sjmmv  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17698d5317Sjmmv  */
18698d5317Sjmmv 
19698d5317Sjmmv #include <sys/types.h>
20698d5317Sjmmv #include <sys/socket.h>
21e271dbb8Schristos #include <sys/uio.h>
22698d5317Sjmmv #include <sys/un.h>
23698d5317Sjmmv #include <sys/wait.h>
24c7e17de0Schristos #include <sys/file.h>
25698d5317Sjmmv 
26698d5317Sjmmv #include <errno.h>
27698d5317Sjmmv #include <fcntl.h>
2899e242abSchristos #include <signal.h>
29698d5317Sjmmv #include <stdlib.h>
30698d5317Sjmmv #include <string.h>
31698d5317Sjmmv #include <unistd.h>
32698d5317Sjmmv 
33698d5317Sjmmv #include "tmux.h"
34698d5317Sjmmv 
35e9a2d6faSchristos static struct tmuxproc	*client_proc;
36e9a2d6faSchristos static struct tmuxpeer	*client_peer;
37e271dbb8Schristos static uint64_t		 client_flags;
38e271dbb8Schristos static int		 client_suspended;
39e9a2d6faSchristos static enum {
40928fc495Schristos 	CLIENT_EXIT_NONE,
41928fc495Schristos 	CLIENT_EXIT_DETACHED,
42928fc495Schristos 	CLIENT_EXIT_DETACHED_HUP,
43928fc495Schristos 	CLIENT_EXIT_LOST_TTY,
44928fc495Schristos 	CLIENT_EXIT_TERMINATED,
45928fc495Schristos 	CLIENT_EXIT_LOST_SERVER,
46928fc495Schristos 	CLIENT_EXIT_EXITED,
47928fc495Schristos 	CLIENT_EXIT_SERVER_EXITED,
48e271dbb8Schristos 	CLIENT_EXIT_MESSAGE_PROVIDED
49928fc495Schristos } client_exitreason = CLIENT_EXIT_NONE;
5068e6ba84Schristos static int		 client_exitflag;
51e9a2d6faSchristos static int		 client_exitval;
52e9a2d6faSchristos static enum msgtype	 client_exittype;
53e9a2d6faSchristos static const char	*client_exitsession;
54e271dbb8Schristos static char		*client_exitmessage;
55e9a2d6faSchristos static const char	*client_execshell;
56e9a2d6faSchristos static const char	*client_execcmd;
57e9a2d6faSchristos static int		 client_attached;
5868e6ba84Schristos static struct client_files client_files = RB_INITIALIZER(&client_files);
59698d5317Sjmmv 
60e9a2d6faSchristos static __dead void	 client_exec(const char *,const char *);
61e9a2d6faSchristos static int		 client_get_lock(char *);
62e271dbb8Schristos static int		 client_connect(struct event_base *, const char *,
63e271dbb8Schristos 			     uint64_t);
64e271dbb8Schristos static void		 client_send_identify(const char *, const char *,
65e271dbb8Schristos 			     char **, u_int, const char *, int);
66e9a2d6faSchristos static void		 client_signal(int);
67e9a2d6faSchristos static void		 client_dispatch(struct imsg *, void *);
68e9a2d6faSchristos static void		 client_dispatch_attached(struct imsg *);
69fe99a117Schristos static void		 client_dispatch_wait(struct imsg *);
70e9a2d6faSchristos static const char	*client_exit_message(void);
71928fc495Schristos 
72928fc495Schristos /*
73928fc495Schristos  * Get server create lock. If already held then server start is happening in
74f26e8bc9Schristos  * another client, so block until the lock is released and return -2 to
75f26e8bc9Schristos  * retry. Return -1 on failure to continue and start the server anyway.
76928fc495Schristos  */
77e9a2d6faSchristos static int
78928fc495Schristos client_get_lock(char *lockfile)
79928fc495Schristos {
80928fc495Schristos 	int lockfd;
81928fc495Schristos 
8299e242abSchristos 	log_debug("lock file is %s", lockfile);
83928fc495Schristos 
84f26e8bc9Schristos 	if ((lockfd = open(lockfile, O_WRONLY|O_CREAT, 0600)) == -1) {
85f26e8bc9Schristos 		log_debug("open failed: %s", strerror(errno));
86f26e8bc9Schristos 		return (-1);
87f26e8bc9Schristos 	}
88f26e8bc9Schristos 
8999e242abSchristos 	if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
9099e242abSchristos 		log_debug("flock failed: %s", strerror(errno));
9199e242abSchristos 		if (errno != EAGAIN)
9299e242abSchristos 			return (lockfd);
9399e242abSchristos 		while (flock(lockfd, LOCK_EX) == -1 && errno == EINTR)
94928fc495Schristos 			/* nothing */;
95928fc495Schristos 		close(lockfd);
96f26e8bc9Schristos 		return (-2);
97928fc495Schristos 	}
9899e242abSchristos 	log_debug("flock succeeded");
99928fc495Schristos 
100928fc495Schristos 	return (lockfd);
101928fc495Schristos }
102698d5317Sjmmv 
103698d5317Sjmmv /* Connect client to server. */
104e9a2d6faSchristos static int
105e271dbb8Schristos client_connect(struct event_base *base, const char *path, uint64_t flags)
106698d5317Sjmmv {
107698d5317Sjmmv 	struct sockaddr_un	sa;
108698d5317Sjmmv 	size_t			size;
10999e242abSchristos 	int			fd, lockfd = -1, locked = 0;
11099e242abSchristos 	char		       *lockfile = NULL;
111698d5317Sjmmv 
112698d5317Sjmmv 	memset(&sa, 0, sizeof sa);
113698d5317Sjmmv 	sa.sun_family = AF_UNIX;
114698d5317Sjmmv 	size = strlcpy(sa.sun_path, path, sizeof sa.sun_path);
115698d5317Sjmmv 	if (size >= sizeof sa.sun_path) {
116698d5317Sjmmv 		errno = ENAMETOOLONG;
117698d5317Sjmmv 		return (-1);
118698d5317Sjmmv 	}
11999e242abSchristos 	log_debug("socket is %s", path);
120698d5317Sjmmv 
121928fc495Schristos retry:
122698d5317Sjmmv 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
123f26e8bc9Schristos 		return (-1);
124698d5317Sjmmv 
12599e242abSchristos 	log_debug("trying connect");
126f26e8bc9Schristos 	if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
12799e242abSchristos 		log_debug("connect failed: %s", strerror(errno));
128928fc495Schristos 		if (errno != ECONNREFUSED && errno != ENOENT)
129928fc495Schristos 			goto failed;
130e271dbb8Schristos 		if (flags & CLIENT_NOSTARTSERVER)
131e271dbb8Schristos 			goto failed;
132e271dbb8Schristos 		if (~flags & CLIENT_STARTSERVER)
133698d5317Sjmmv 			goto failed;
134928fc495Schristos 		close(fd);
135928fc495Schristos 
13699e242abSchristos 		if (!locked) {
137928fc495Schristos 			xasprintf(&lockfile, "%s.lock", path);
138f26e8bc9Schristos 			if ((lockfd = client_get_lock(lockfile)) < 0) {
139f26e8bc9Schristos 				log_debug("didn't get lock (%d)", lockfd);
140f26e8bc9Schristos 
141928fc495Schristos 				free(lockfile);
142f26e8bc9Schristos 				lockfile = NULL;
143f26e8bc9Schristos 
144f26e8bc9Schristos 				if (lockfd == -2)
145928fc495Schristos 					goto retry;
146698d5317Sjmmv 			}
147f26e8bc9Schristos 			log_debug("got lock (%d)", lockfd);
14899e242abSchristos 
14999e242abSchristos 			/*
15099e242abSchristos 			 * Always retry at least once, even if we got the lock,
15199e242abSchristos 			 * because another client could have taken the lock,
15299e242abSchristos 			 * started the server and released the lock between our
15399e242abSchristos 			 * connect() and flock().
15499e242abSchristos 			 */
15599e242abSchristos 			locked = 1;
15699e242abSchristos 			goto retry;
15799e242abSchristos 		}
15899e242abSchristos 
159f26e8bc9Schristos 		if (lockfd >= 0 && unlink(path) != 0 && errno != ENOENT) {
160928fc495Schristos 			free(lockfile);
161928fc495Schristos 			close(lockfd);
162928fc495Schristos 			return (-1);
163928fc495Schristos 		}
164e271dbb8Schristos 		fd = server_start(client_proc, flags, base, lockfd, lockfile);
16599e242abSchristos 	}
16699e242abSchristos 
167f26e8bc9Schristos 	if (locked && lockfd >= 0) {
168928fc495Schristos 		free(lockfile);
169928fc495Schristos 		close(lockfd);
170698d5317Sjmmv 	}
171d530c4d0Sjmmv 	setblocking(fd, 0);
172698d5317Sjmmv 	return (fd);
173698d5317Sjmmv 
174698d5317Sjmmv failed:
17599e242abSchristos 	if (locked) {
17699e242abSchristos 		free(lockfile);
17799e242abSchristos 		close(lockfd);
17899e242abSchristos 	}
179698d5317Sjmmv 	close(fd);
180698d5317Sjmmv 	return (-1);
181698d5317Sjmmv }
182698d5317Sjmmv 
183928fc495Schristos /* Get exit string from reason number. */
184928fc495Schristos const char *
185928fc495Schristos client_exit_message(void)
186928fc495Schristos {
187928fc495Schristos 	static char msg[256];
188928fc495Schristos 
189928fc495Schristos 	switch (client_exitreason) {
190928fc495Schristos 	case CLIENT_EXIT_NONE:
191928fc495Schristos 		break;
192928fc495Schristos 	case CLIENT_EXIT_DETACHED:
193928fc495Schristos 		if (client_exitsession != NULL) {
194928fc495Schristos 			xsnprintf(msg, sizeof msg, "detached "
195928fc495Schristos 			    "(from session %s)", client_exitsession);
196928fc495Schristos 			return (msg);
197928fc495Schristos 		}
198928fc495Schristos 		return ("detached");
199928fc495Schristos 	case CLIENT_EXIT_DETACHED_HUP:
200928fc495Schristos 		if (client_exitsession != NULL) {
201928fc495Schristos 			xsnprintf(msg, sizeof msg, "detached and SIGHUP "
202928fc495Schristos 			    "(from session %s)", client_exitsession);
203928fc495Schristos 			return (msg);
204928fc495Schristos 		}
205928fc495Schristos 		return ("detached and SIGHUP");
206928fc495Schristos 	case CLIENT_EXIT_LOST_TTY:
207928fc495Schristos 		return ("lost tty");
208928fc495Schristos 	case CLIENT_EXIT_TERMINATED:
209928fc495Schristos 		return ("terminated");
210928fc495Schristos 	case CLIENT_EXIT_LOST_SERVER:
21130744affSchristos 		return ("server exited unexpectedly");
212928fc495Schristos 	case CLIENT_EXIT_EXITED:
213928fc495Schristos 		return ("exited");
214928fc495Schristos 	case CLIENT_EXIT_SERVER_EXITED:
215928fc495Schristos 		return ("server exited");
216e271dbb8Schristos 	case CLIENT_EXIT_MESSAGE_PROVIDED:
217e271dbb8Schristos 		return (client_exitmessage);
218928fc495Schristos 	}
219928fc495Schristos 	return ("unknown reason");
220928fc495Schristos }
221928fc495Schristos 
22268e6ba84Schristos /* Exit if all streams flushed. */
22368e6ba84Schristos static void
22468e6ba84Schristos client_exit(void)
22568e6ba84Schristos {
226e271dbb8Schristos 	if (!file_write_left(&client_files))
22768e6ba84Schristos 		proc_exit(client_proc);
22868e6ba84Schristos }
22968e6ba84Schristos 
230698d5317Sjmmv /* Client main loop. */
231698d5317Sjmmv int
232e271dbb8Schristos client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
233e271dbb8Schristos     int feat)
234698d5317Sjmmv {
23530744affSchristos 	struct cmd_parse_result	*pr;
23668e6ba84Schristos 	struct msg_command	*data;
237e271dbb8Schristos 	int			 fd, i;
238e271dbb8Schristos 	const char		*ttynam, *termname, *cwd;
239d530c4d0Sjmmv 	pid_t			 ppid;
240698d5317Sjmmv 	enum msgtype		 msg;
241928fc495Schristos 	struct termios		 tio, saved_tio;
242e271dbb8Schristos 	size_t			 size, linesize = 0;
243e271dbb8Schristos 	ssize_t			 linelen;
244e271dbb8Schristos 	char			*line = NULL, **caps = NULL, *cause;
245e271dbb8Schristos 	u_int			 ncaps = 0;
24646548964Swiz 	struct args_value	*values;
247698d5317Sjmmv 
248698d5317Sjmmv 	/* Set up the initial command. */
249fe99a117Schristos 	if (shell_command != NULL) {
250698d5317Sjmmv 		msg = MSG_SHELL;
251e271dbb8Schristos 		flags |= CLIENT_STARTSERVER;
252698d5317Sjmmv 	} else if (argc == 0) {
253698d5317Sjmmv 		msg = MSG_COMMAND;
254e271dbb8Schristos 		flags |= CLIENT_STARTSERVER;
255698d5317Sjmmv 	} else {
256698d5317Sjmmv 		msg = MSG_COMMAND;
257698d5317Sjmmv 
258698d5317Sjmmv 		/*
25946548964Swiz 		 * It's annoying parsing the command string twice (in client
26046548964Swiz 		 * and later in server) but it is necessary to get the start
26146548964Swiz 		 * server flag.
262698d5317Sjmmv 		 */
26346548964Swiz 		values = args_from_vector(argc, argv);
26446548964Swiz 		pr = cmd_parse_from_arguments(values, argc, NULL);
26530744affSchristos 		if (pr->status == CMD_PARSE_SUCCESS) {
266e271dbb8Schristos 			if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
267e271dbb8Schristos 				flags |= CLIENT_STARTSERVER;
26830744affSchristos 			cmd_list_free(pr->cmdlist);
26930744affSchristos 		} else
27030744affSchristos 			free(pr->error);
27146548964Swiz 		args_free_values(values, argc);
27246548964Swiz 		free(values);
273e9a2d6faSchristos 	}
274698d5317Sjmmv 
275f26e8bc9Schristos 	/* Create client process structure (starts logging). */
276fe99a117Schristos 	client_proc = proc_start("client");
277fe99a117Schristos 	proc_set_signals(client_proc, client_signal);
27899e242abSchristos 
279e271dbb8Schristos 	/* Save the flags. */
280e271dbb8Schristos 	client_flags = flags;
281e271dbb8Schristos 	log_debug("flags are %#llx", (unsigned long long)client_flags);
282e271dbb8Schristos 
28399e242abSchristos 	/* Initialize the client socket and start the server. */
284f844e94eSwiz #ifdef HAVE_SYSTEMD
285f844e94eSwiz 	if (systemd_activated()) {
286f844e94eSwiz 		/* socket-based activation, do not even try to be a client. */
287f844e94eSwiz 		fd = server_start(client_proc, flags, base, 0, NULL);
288f844e94eSwiz 	} else
289f844e94eSwiz #endif
290e271dbb8Schristos 	fd = client_connect(base, socket_path, client_flags);
29199e242abSchristos 	if (fd == -1) {
29299e242abSchristos 		if (errno == ECONNREFUSED) {
29399e242abSchristos 			fprintf(stderr, "no server running on %s\n",
29499e242abSchristos 			    socket_path);
29599e242abSchristos 		} else {
29699e242abSchristos 			fprintf(stderr, "error connecting to %s (%s)\n",
29799e242abSchristos 			    socket_path, strerror(errno));
29899e242abSchristos 		}
299698d5317Sjmmv 		return (1);
300698d5317Sjmmv 	}
301fe99a117Schristos 	client_peer = proc_add_peer(client_proc, fd, client_dispatch, NULL);
302698d5317Sjmmv 
30399e242abSchristos 	/* Save these before pledge(). */
3040a274e86Schristos 	if ((cwd = find_cwd()) == NULL && (cwd = find_home()) == NULL)
305f26e8bc9Schristos 		cwd = "/";
30699e242abSchristos 	if ((ttynam = ttyname(STDIN_FILENO)) == NULL)
30799e242abSchristos 		ttynam = "";
308e271dbb8Schristos 	if ((termname = getenv("TERM")) == NULL)
309e271dbb8Schristos 		termname = "";
31099e242abSchristos 
31199e242abSchristos 	/*
31299e242abSchristos 	 * Drop privileges for client. "proc exec" is needed for -c and for
31399e242abSchristos 	 * locking (which uses system(3)).
31499e242abSchristos 	 *
31599e242abSchristos 	 * "tty" is needed to restore termios(4) and also for some reason -CC
31699e242abSchristos 	 * does not work properly without it (input is not recognised).
31799e242abSchristos 	 *
31899e242abSchristos 	 * "sendfd" is dropped later in client_dispatch_wait().
31999e242abSchristos 	 */
32068e6ba84Schristos 	if (pledge(
32168e6ba84Schristos 	    "stdio rpath wpath cpath unix sendfd proc exec tty",
32268e6ba84Schristos 	    NULL) != 0)
32399e242abSchristos 		fatal("pledge failed");
32499e242abSchristos 
325e271dbb8Schristos 	/* Load terminfo entry if any. */
326e271dbb8Schristos 	if (isatty(STDIN_FILENO) &&
327e271dbb8Schristos 	    *termname != '\0' &&
328e271dbb8Schristos 	    tty_term_read_list(termname, STDIN_FILENO, &caps, &ncaps,
329e271dbb8Schristos 	    &cause) != 0) {
330e271dbb8Schristos 		fprintf(stderr, "%s\n", cause);
331e271dbb8Schristos 		free(cause);
332e271dbb8Schristos 		return (1);
333e271dbb8Schristos 	}
334e271dbb8Schristos 
33599e242abSchristos 	/* Free stuff that is not used in the client. */
336e9a2d6faSchristos 	if (ptm_fd != -1)
337e9a2d6faSchristos 		close(ptm_fd);
338f26e8bc9Schristos 	options_free(global_options);
339f26e8bc9Schristos 	options_free(global_s_options);
340f26e8bc9Schristos 	options_free(global_w_options);
341f26e8bc9Schristos 	environ_free(global_environ);
342698d5317Sjmmv 
34368e6ba84Schristos 	/* Set up control mode. */
34499e242abSchristos 	if (client_flags & CLIENT_CONTROLCONTROL) {
345e9a2d6faSchristos 		if (tcgetattr(STDIN_FILENO, &saved_tio) != 0) {
346e9a2d6faSchristos 			fprintf(stderr, "tcgetattr failed: %s\n",
347e9a2d6faSchristos 			    strerror(errno));
348e9a2d6faSchristos 			return (1);
349e9a2d6faSchristos 		}
350928fc495Schristos 		cfmakeraw(&tio);
351928fc495Schristos 		tio.c_iflag = ICRNL|IXANY;
352928fc495Schristos 		tio.c_oflag = OPOST|ONLCR;
353928fc495Schristos #ifdef NOKERNINFO
354928fc495Schristos 		tio.c_lflag = NOKERNINFO;
355928fc495Schristos #endif
356928fc495Schristos 		tio.c_cflag = CREAD|CS8|HUPCL;
357928fc495Schristos 		tio.c_cc[VMIN] = 1;
358928fc495Schristos 		tio.c_cc[VTIME] = 0;
359928fc495Schristos 		cfsetispeed(&tio, cfgetispeed(&saved_tio));
360928fc495Schristos 		cfsetospeed(&tio, cfgetospeed(&saved_tio));
361928fc495Schristos 		tcsetattr(STDIN_FILENO, TCSANOW, &tio);
362928fc495Schristos 	}
363928fc495Schristos 
364928fc495Schristos 	/* Send identify messages. */
365e271dbb8Schristos 	client_send_identify(ttynam, termname, caps, ncaps, cwd, feat);
366e271dbb8Schristos 	tty_term_free_list(caps, ncaps);
36746548964Swiz 	proc_flush_peer(client_peer);
368698d5317Sjmmv 
369698d5317Sjmmv 	/* Send first command. */
370698d5317Sjmmv 	if (msg == MSG_COMMAND) {
371928fc495Schristos 		/* How big is the command? */
372928fc495Schristos 		size = 0;
373928fc495Schristos 		for (i = 0; i < argc; i++)
374928fc495Schristos 			size += strlen(argv[i]) + 1;
375c7e17de0Schristos 		if (size > MAX_IMSGSIZE - (sizeof *data)) {
376c7e17de0Schristos 			fprintf(stderr, "command too long\n");
377c7e17de0Schristos 			return (1);
378c7e17de0Schristos 		}
379928fc495Schristos 		data = xmalloc((sizeof *data) + size);
380698d5317Sjmmv 
381698d5317Sjmmv 		/* Prepare command for server. */
382928fc495Schristos 		data->argc = argc;
383928fc495Schristos 		if (cmd_pack_argv(argc, argv, (char *)(data + 1), size) != 0) {
384928fc495Schristos 			fprintf(stderr, "command too long\n");
385928fc495Schristos 			free(data);
386698d5317Sjmmv 			return (1);
387698d5317Sjmmv 		}
388928fc495Schristos 		size += sizeof *data;
389698d5317Sjmmv 
390928fc495Schristos 		/* Send the command. */
391f26e8bc9Schristos 		if (proc_send(client_peer, msg, -1, data, size) != 0) {
392928fc495Schristos 			fprintf(stderr, "failed to send command\n");
393928fc495Schristos 			free(data);
394928fc495Schristos 			return (1);
395928fc495Schristos 		}
396928fc495Schristos 		free(data);
397698d5317Sjmmv 	} else if (msg == MSG_SHELL)
398f26e8bc9Schristos 		proc_send(client_peer, msg, -1, NULL, 0);
399698d5317Sjmmv 
400f26e8bc9Schristos 	/* Start main loop. */
401f26e8bc9Schristos 	proc_loop(client_proc, NULL);
402698d5317Sjmmv 
403e9a2d6faSchristos 	/* Run command if user requested exec, instead of exiting. */
404e9a2d6faSchristos 	if (client_exittype == MSG_EXEC) {
405e9a2d6faSchristos 		if (client_flags & CLIENT_CONTROLCONTROL)
406e9a2d6faSchristos 			tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
407e9a2d6faSchristos 		client_exec(client_execshell, client_execcmd);
408e9a2d6faSchristos 	}
409e9a2d6faSchristos 
410e271dbb8Schristos 	/* Restore streams to blocking. */
411e271dbb8Schristos 	setblocking(STDIN_FILENO, 1);
412e271dbb8Schristos 	setblocking(STDOUT_FILENO, 1);
413e271dbb8Schristos 	setblocking(STDERR_FILENO, 1);
414e271dbb8Schristos 
415698d5317Sjmmv 	/* Print the exit message, if any, and exit. */
416d530c4d0Sjmmv 	if (client_attached) {
41799e242abSchristos 		if (client_exitreason != CLIENT_EXIT_NONE)
418928fc495Schristos 			printf("[%s]\n", client_exit_message());
419d530c4d0Sjmmv 
420d530c4d0Sjmmv 		ppid = getppid();
421d530c4d0Sjmmv 		if (client_exittype == MSG_DETACHKILL && ppid > 1)
422d530c4d0Sjmmv 			kill(ppid, SIGHUP);
423e271dbb8Schristos 	} else if (client_flags & CLIENT_CONTROL) {
424928fc495Schristos 		if (client_exitreason != CLIENT_EXIT_NONE)
425928fc495Schristos 			printf("%%exit %s\n", client_exit_message());
426928fc495Schristos 		else
427928fc495Schristos 			printf("%%exit\n");
428e271dbb8Schristos 		fflush(stdout);
429e271dbb8Schristos 		if (client_flags & CLIENT_CONTROL_WAITEXIT) {
430e271dbb8Schristos 			setvbuf(stdin, NULL, _IOLBF, 0);
431e271dbb8Schristos 			for (;;) {
432e271dbb8Schristos 				linelen = getline(&line, &linesize, stdin);
433e271dbb8Schristos 				if (linelen <= 1)
434e271dbb8Schristos 					break;
435e271dbb8Schristos 			}
436e271dbb8Schristos 			free(line);
437e271dbb8Schristos 		}
438e271dbb8Schristos 		if (client_flags & CLIENT_CONTROLCONTROL) {
439928fc495Schristos 			printf("\033\\");
440e271dbb8Schristos 			fflush(stdout);
441928fc495Schristos 			tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
442e271dbb8Schristos 		}
443f26e8bc9Schristos 	} else if (client_exitreason != CLIENT_EXIT_NONE)
444f26e8bc9Schristos 		fprintf(stderr, "%s\n", client_exit_message());
445698d5317Sjmmv 	return (client_exitval);
446698d5317Sjmmv }
447698d5317Sjmmv 
448928fc495Schristos /* Send identify messages to server. */
449e9a2d6faSchristos static void
450e271dbb8Schristos client_send_identify(const char *ttynam, const char *termname, char **caps,
451e271dbb8Schristos     u_int ncaps, const char *cwd, int feat)
452698d5317Sjmmv {
453928fc495Schristos 	char	**ss;
45499e242abSchristos 	size_t	  sslen;
455*890b6d91Swiz 	int	  fd;
456*890b6d91Swiz 	uint64_t  flags = client_flags;
45799e242abSchristos 	pid_t	  pid;
458e271dbb8Schristos 	u_int	  i;
459698d5317Sjmmv 
460*890b6d91Swiz 	proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &flags, sizeof flags);
461e271dbb8Schristos 	proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &client_flags,
462e271dbb8Schristos 	    sizeof client_flags);
463698d5317Sjmmv 
464e271dbb8Schristos 	proc_send(client_peer, MSG_IDENTIFY_TERM, -1, termname,
465e271dbb8Schristos 	    strlen(termname) + 1);
466e271dbb8Schristos 	proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat);
467698d5317Sjmmv 
468f26e8bc9Schristos 	proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam,
469f26e8bc9Schristos 	    strlen(ttynam) + 1);
470f26e8bc9Schristos 	proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1);
471698d5317Sjmmv 
472e271dbb8Schristos 	for (i = 0; i < ncaps; i++) {
473e271dbb8Schristos 		proc_send(client_peer, MSG_IDENTIFY_TERMINFO, -1,
474e271dbb8Schristos 		    caps[i], strlen(caps[i]) + 1);
475e271dbb8Schristos 	}
476e271dbb8Schristos 
477698d5317Sjmmv 	if ((fd = dup(STDIN_FILENO)) == -1)
478698d5317Sjmmv 		fatal("dup failed");
479f26e8bc9Schristos 	proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0);
480e271dbb8Schristos 	if ((fd = dup(STDOUT_FILENO)) == -1)
481e271dbb8Schristos 		fatal("dup failed");
482e271dbb8Schristos 	proc_send(client_peer, MSG_IDENTIFY_STDOUT, fd, NULL, 0);
483698d5317Sjmmv 
48499e242abSchristos 	pid = getpid();
485f26e8bc9Schristos 	proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid);
48699e242abSchristos 
48799e242abSchristos 	for (ss = environ; *ss != NULL; ss++) {
48899e242abSchristos 		sslen = strlen(*ss) + 1;
489f26e8bc9Schristos 		if (sslen > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
490f26e8bc9Schristos 			continue;
491f26e8bc9Schristos 		proc_send(client_peer, MSG_IDENTIFY_ENVIRON, -1, *ss, sslen);
49299e242abSchristos 	}
493698d5317Sjmmv 
494f26e8bc9Schristos 	proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0);
495698d5317Sjmmv }
496698d5317Sjmmv 
49799e242abSchristos /* Run command in shell; used for -c. */
498e9a2d6faSchristos static __dead void
499f26e8bc9Schristos client_exec(const char *shell, const char *shellcmd)
50099e242abSchristos {
50199e242abSchristos 	char	*argv0;
50299e242abSchristos 
503f26e8bc9Schristos 	log_debug("shell %s, command %s", shell, shellcmd);
504*890b6d91Swiz 	argv0 = shell_argv0(shell, !!(client_flags & CLIENT_LOGIN));
50599e242abSchristos 	setenv("SHELL", shell, 1);
50699e242abSchristos 
507fe99a117Schristos 	proc_clear_signals(client_proc, 1);
508fe99a117Schristos 
50999e242abSchristos 	setblocking(STDIN_FILENO, 1);
51099e242abSchristos 	setblocking(STDOUT_FILENO, 1);
51199e242abSchristos 	setblocking(STDERR_FILENO, 1);
51299e242abSchristos 	closefrom(STDERR_FILENO + 1);
51399e242abSchristos 
514f26e8bc9Schristos 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
51599e242abSchristos 	fatal("execl failed");
51699e242abSchristos }
51799e242abSchristos 
518f26e8bc9Schristos /* Callback to handle signals in the client. */
519e9a2d6faSchristos static void
520f26e8bc9Schristos client_signal(int sig)
521698d5317Sjmmv {
522f26e8bc9Schristos 	struct sigaction sigact;
523f26e8bc9Schristos 	int		 status;
524f844e94eSwiz 	pid_t		 pid;
525f26e8bc9Schristos 
526e271dbb8Schristos 	log_debug("%s: %s", __func__, strsignal(sig));
527f844e94eSwiz 	if (sig == SIGCHLD) {
528f844e94eSwiz 		for (;;) {
529f844e94eSwiz 			pid = waitpid(WAIT_ANY, &status, WNOHANG);
530f844e94eSwiz 			if (pid == 0)
531f844e94eSwiz 				break;
532f844e94eSwiz 			if (pid == -1) {
533f844e94eSwiz 				if (errno == ECHILD)
534f844e94eSwiz 					break;
535f844e94eSwiz 				log_debug("waitpid failed: %s",
536f844e94eSwiz 				    strerror(errno));
537f844e94eSwiz 			}
538f844e94eSwiz 		}
539f844e94eSwiz 	} else if (!client_attached) {
54046548964Swiz 		if (sig == SIGTERM || sig == SIGHUP)
541f26e8bc9Schristos 			proc_exit(client_proc);
542f26e8bc9Schristos 	} else {
543f26e8bc9Schristos 		switch (sig) {
544f26e8bc9Schristos 		case SIGHUP:
545f26e8bc9Schristos 			client_exitreason = CLIENT_EXIT_LOST_TTY;
546f26e8bc9Schristos 			client_exitval = 1;
547f26e8bc9Schristos 			proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
548f26e8bc9Schristos 			break;
549f26e8bc9Schristos 		case SIGTERM:
550e271dbb8Schristos 			if (!client_suspended)
551f26e8bc9Schristos 				client_exitreason = CLIENT_EXIT_TERMINATED;
552f26e8bc9Schristos 			client_exitval = 1;
553f26e8bc9Schristos 			proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
554f26e8bc9Schristos 			break;
555f26e8bc9Schristos 		case SIGWINCH:
556f26e8bc9Schristos 			proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
557f26e8bc9Schristos 			break;
558f26e8bc9Schristos 		case SIGCONT:
559f26e8bc9Schristos 			memset(&sigact, 0, sizeof sigact);
560f26e8bc9Schristos 			sigemptyset(&sigact.sa_mask);
561f26e8bc9Schristos 			sigact.sa_flags = SA_RESTART;
562f26e8bc9Schristos 			sigact.sa_handler = SIG_IGN;
563f26e8bc9Schristos 			if (sigaction(SIGTSTP, &sigact, NULL) != 0)
564f26e8bc9Schristos 				fatal("sigaction failed");
565f26e8bc9Schristos 			proc_send(client_peer, MSG_WAKEUP, -1, NULL, 0);
566e271dbb8Schristos 			client_suspended = 0;
567f26e8bc9Schristos 			break;
568f26e8bc9Schristos 		}
569f26e8bc9Schristos 	}
570f26e8bc9Schristos }
571f26e8bc9Schristos 
572e271dbb8Schristos /* Callback for file write error or close. */
573e271dbb8Schristos static void
574e271dbb8Schristos client_file_check_cb(__unused struct client *c, __unused const char *path,
575e271dbb8Schristos     __unused int error, __unused int closed, __unused struct evbuffer *buffer,
576e271dbb8Schristos     __unused void *data)
577e271dbb8Schristos {
578e271dbb8Schristos 	if (client_exitflag)
579e271dbb8Schristos 		client_exit();
580e271dbb8Schristos }
581e271dbb8Schristos 
582f26e8bc9Schristos /* Callback for client read events. */
583e9a2d6faSchristos static void
584fe99a117Schristos client_dispatch(struct imsg *imsg, __unused void *arg)
585f26e8bc9Schristos {
586f26e8bc9Schristos 	if (imsg == NULL) {
587e271dbb8Schristos 		if (!client_exitflag) {
588f26e8bc9Schristos 			client_exitreason = CLIENT_EXIT_LOST_SERVER;
589f26e8bc9Schristos 			client_exitval = 1;
590e271dbb8Schristos 		}
591f26e8bc9Schristos 		proc_exit(client_proc);
592f26e8bc9Schristos 		return;
593f26e8bc9Schristos 	}
594f26e8bc9Schristos 
595f26e8bc9Schristos 	if (client_attached)
596f26e8bc9Schristos 		client_dispatch_attached(imsg);
597f26e8bc9Schristos 	else
598fe99a117Schristos 		client_dispatch_wait(imsg);
599f26e8bc9Schristos }
600f26e8bc9Schristos 
601e271dbb8Schristos /* Process an exit message. */
602e271dbb8Schristos static void
603e271dbb8Schristos client_dispatch_exit_message(char *data, size_t datalen)
604e271dbb8Schristos {
605e271dbb8Schristos 	int	retval;
606e271dbb8Schristos 
607e271dbb8Schristos 	if (datalen < sizeof retval && datalen != 0)
608e271dbb8Schristos 		fatalx("bad MSG_EXIT size");
609e271dbb8Schristos 
610e271dbb8Schristos 	if (datalen >= sizeof retval) {
611e271dbb8Schristos 		memcpy(&retval, data, sizeof retval);
612e271dbb8Schristos 		client_exitval = retval;
613e271dbb8Schristos 	}
614e271dbb8Schristos 
615e271dbb8Schristos 	if (datalen > sizeof retval) {
616e271dbb8Schristos 		datalen -= sizeof retval;
617e271dbb8Schristos 		data += sizeof retval;
618e271dbb8Schristos 
619e271dbb8Schristos 		client_exitmessage = xmalloc(datalen);
620e271dbb8Schristos 		memcpy(client_exitmessage, data, datalen);
621e271dbb8Schristos 		client_exitmessage[datalen - 1] = '\0';
622e271dbb8Schristos 
623e271dbb8Schristos 		client_exitreason = CLIENT_EXIT_MESSAGE_PROVIDED;
624e271dbb8Schristos 	}
625e271dbb8Schristos }
626e271dbb8Schristos 
627f26e8bc9Schristos /* Dispatch imsgs when in wait state (before MSG_READY). */
628e9a2d6faSchristos static void
629fe99a117Schristos client_dispatch_wait(struct imsg *imsg)
630f26e8bc9Schristos {
631928fc495Schristos 	char		*data;
632f26e8bc9Schristos 	ssize_t		 datalen;
63399e242abSchristos 	static int	 pledge_applied;
63499e242abSchristos 
63599e242abSchristos 	/*
63699e242abSchristos 	 * "sendfd" is no longer required once all of the identify messages
63799e242abSchristos 	 * have been sent. We know the server won't send us anything until that
63899e242abSchristos 	 * point (because we don't ask it to), so we can drop "sendfd" once we
63999e242abSchristos 	 * get the first message from the server.
64099e242abSchristos 	 */
64199e242abSchristos 	if (!pledge_applied) {
64268e6ba84Schristos 		if (pledge(
64368e6ba84Schristos 		    "stdio rpath wpath cpath unix proc exec tty",
64468e6ba84Schristos 		    NULL) != 0)
64599e242abSchristos 			fatal("pledge failed");
64699e242abSchristos 		pledge_applied = 1;
64768e6ba84Schristos 	}
648698d5317Sjmmv 
649f26e8bc9Schristos 	data = imsg->data;
650f26e8bc9Schristos 	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
651928fc495Schristos 
652f26e8bc9Schristos 	switch (imsg->hdr.type) {
653698d5317Sjmmv 	case MSG_EXIT:
654698d5317Sjmmv 	case MSG_SHUTDOWN:
655e271dbb8Schristos 		client_dispatch_exit_message(data, datalen);
65668e6ba84Schristos 		client_exitflag = 1;
65768e6ba84Schristos 		client_exit();
658f26e8bc9Schristos 		break;
659698d5317Sjmmv 	case MSG_READY:
660698d5317Sjmmv 		if (datalen != 0)
661698d5317Sjmmv 			fatalx("bad MSG_READY size");
662698d5317Sjmmv 
663698d5317Sjmmv 		client_attached = 1;
664f26e8bc9Schristos 		proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
665928fc495Schristos 		break;
666698d5317Sjmmv 	case MSG_VERSION:
667698d5317Sjmmv 		if (datalen != 0)
668698d5317Sjmmv 			fatalx("bad MSG_VERSION size");
669698d5317Sjmmv 
670928fc495Schristos 		fprintf(stderr, "protocol version mismatch "
67199e242abSchristos 		    "(client %d, server %u)\n", PROTOCOL_VERSION,
672f26e8bc9Schristos 		    imsg->hdr.peerid & 0xff);
673698d5317Sjmmv 		client_exitval = 1;
674f26e8bc9Schristos 		proc_exit(client_proc);
675f26e8bc9Schristos 		break;
676e271dbb8Schristos 	case MSG_FLAGS:
677e271dbb8Schristos 		if (datalen != sizeof client_flags)
678e271dbb8Schristos 			fatalx("bad MSG_FLAGS string");
679e271dbb8Schristos 
680e271dbb8Schristos 		memcpy(&client_flags, data, sizeof client_flags);
681e271dbb8Schristos 		log_debug("new flags are %#llx",
682e271dbb8Schristos 		    (unsigned long long)client_flags);
683e271dbb8Schristos 		break;
684698d5317Sjmmv 	case MSG_SHELL:
685928fc495Schristos 		if (datalen == 0 || data[datalen - 1] != '\0')
686928fc495Schristos 			fatalx("bad MSG_SHELL string");
687698d5317Sjmmv 
688fe99a117Schristos 		client_exec(data, shell_command);
689698d5317Sjmmv 		/* NOTREACHED */
690928fc495Schristos 	case MSG_DETACH:
691928fc495Schristos 	case MSG_DETACHKILL:
692f26e8bc9Schristos 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
693928fc495Schristos 		break;
694928fc495Schristos 	case MSG_EXITED:
695f26e8bc9Schristos 		proc_exit(client_proc);
696f26e8bc9Schristos 		break;
69768e6ba84Schristos 	case MSG_READ_OPEN:
698e271dbb8Schristos 		file_read_open(&client_files, client_peer, imsg, 1,
699e271dbb8Schristos 		    !(client_flags & CLIENT_CONTROL), client_file_check_cb,
700e271dbb8Schristos 		    NULL);
70168e6ba84Schristos 		break;
702f844e94eSwiz 	case MSG_READ_CANCEL:
703f844e94eSwiz 		file_read_cancel(&client_files, imsg);
704f844e94eSwiz 		break;
70568e6ba84Schristos 	case MSG_WRITE_OPEN:
706e271dbb8Schristos 		file_write_open(&client_files, client_peer, imsg, 1,
707e271dbb8Schristos 		    !(client_flags & CLIENT_CONTROL), client_file_check_cb,
708e271dbb8Schristos 		    NULL);
70968e6ba84Schristos 		break;
71068e6ba84Schristos 	case MSG_WRITE:
711e271dbb8Schristos 		file_write_data(&client_files, imsg);
71268e6ba84Schristos 		break;
71368e6ba84Schristos 	case MSG_WRITE_CLOSE:
714e271dbb8Schristos 		file_write_close(&client_files, imsg);
71568e6ba84Schristos 		break;
71668e6ba84Schristos 	case MSG_OLDSTDERR:
71768e6ba84Schristos 	case MSG_OLDSTDIN:
71868e6ba84Schristos 	case MSG_OLDSTDOUT:
71968e6ba84Schristos 		fprintf(stderr, "server version is too old for client\n");
72068e6ba84Schristos 		proc_exit(client_proc);
72168e6ba84Schristos 		break;
722698d5317Sjmmv 	}
723698d5317Sjmmv }
724698d5317Sjmmv 
725698d5317Sjmmv /* Dispatch imsgs in attached state (after MSG_READY). */
726e9a2d6faSchristos static void
727f26e8bc9Schristos client_dispatch_attached(struct imsg *imsg)
728698d5317Sjmmv {
729698d5317Sjmmv 	struct sigaction	 sigact;
730928fc495Schristos 	char			*data;
731f26e8bc9Schristos 	ssize_t			 datalen;
732698d5317Sjmmv 
733f26e8bc9Schristos 	data = imsg->data;
734f26e8bc9Schristos 	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
735928fc495Schristos 
736f26e8bc9Schristos 	switch (imsg->hdr.type) {
737e271dbb8Schristos 	case MSG_FLAGS:
738e271dbb8Schristos 		if (datalen != sizeof client_flags)
739e271dbb8Schristos 			fatalx("bad MSG_FLAGS string");
740e271dbb8Schristos 
741e271dbb8Schristos 		memcpy(&client_flags, data, sizeof client_flags);
742e271dbb8Schristos 		log_debug("new flags are %#llx",
743e271dbb8Schristos 		    (unsigned long long)client_flags);
744e271dbb8Schristos 		break;
745698d5317Sjmmv 	case MSG_DETACH:
746928fc495Schristos 	case MSG_DETACHKILL:
747928fc495Schristos 		if (datalen == 0 || data[datalen - 1] != '\0')
748928fc495Schristos 			fatalx("bad MSG_DETACH string");
749698d5317Sjmmv 
750928fc495Schristos 		client_exitsession = xstrdup(data);
751f26e8bc9Schristos 		client_exittype = imsg->hdr.type;
752f26e8bc9Schristos 		if (imsg->hdr.type == MSG_DETACHKILL)
753928fc495Schristos 			client_exitreason = CLIENT_EXIT_DETACHED_HUP;
754d530c4d0Sjmmv 		else
755928fc495Schristos 			client_exitreason = CLIENT_EXIT_DETACHED;
756f26e8bc9Schristos 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
757698d5317Sjmmv 		break;
758e9a2d6faSchristos 	case MSG_EXEC:
759e9a2d6faSchristos 		if (datalen == 0 || data[datalen - 1] != '\0' ||
760e9a2d6faSchristos 		    strlen(data) + 1 == (size_t)datalen)
761e9a2d6faSchristos 			fatalx("bad MSG_EXEC string");
762e9a2d6faSchristos 		client_execcmd = xstrdup(data);
763e9a2d6faSchristos 		client_execshell = xstrdup(data + strlen(data) + 1);
764e9a2d6faSchristos 
765e9a2d6faSchristos 		client_exittype = imsg->hdr.type;
766e9a2d6faSchristos 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
767e9a2d6faSchristos 		break;
768698d5317Sjmmv 	case MSG_EXIT:
769e271dbb8Schristos 		client_dispatch_exit_message(data, datalen);
770e271dbb8Schristos 		if (client_exitreason == CLIENT_EXIT_NONE)
771928fc495Schristos 			client_exitreason = CLIENT_EXIT_EXITED;
772e271dbb8Schristos 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
773698d5317Sjmmv 		break;
774698d5317Sjmmv 	case MSG_EXITED:
775698d5317Sjmmv 		if (datalen != 0)
776698d5317Sjmmv 			fatalx("bad MSG_EXITED size");
777698d5317Sjmmv 
778f26e8bc9Schristos 		proc_exit(client_proc);
779f26e8bc9Schristos 		break;
780698d5317Sjmmv 	case MSG_SHUTDOWN:
781698d5317Sjmmv 		if (datalen != 0)
782698d5317Sjmmv 			fatalx("bad MSG_SHUTDOWN size");
783698d5317Sjmmv 
784f26e8bc9Schristos 		proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
785928fc495Schristos 		client_exitreason = CLIENT_EXIT_SERVER_EXITED;
786698d5317Sjmmv 		client_exitval = 1;
787698d5317Sjmmv 		break;
788698d5317Sjmmv 	case MSG_SUSPEND:
789698d5317Sjmmv 		if (datalen != 0)
790698d5317Sjmmv 			fatalx("bad MSG_SUSPEND size");
791698d5317Sjmmv 
792698d5317Sjmmv 		memset(&sigact, 0, sizeof sigact);
793698d5317Sjmmv 		sigemptyset(&sigact.sa_mask);
794698d5317Sjmmv 		sigact.sa_flags = SA_RESTART;
795698d5317Sjmmv 		sigact.sa_handler = SIG_DFL;
796698d5317Sjmmv 		if (sigaction(SIGTSTP, &sigact, NULL) != 0)
797698d5317Sjmmv 			fatal("sigaction failed");
798e271dbb8Schristos 		client_suspended = 1;
799698d5317Sjmmv 		kill(getpid(), SIGTSTP);
800698d5317Sjmmv 		break;
801698d5317Sjmmv 	case MSG_LOCK:
802928fc495Schristos 		if (datalen == 0 || data[datalen - 1] != '\0')
803928fc495Schristos 			fatalx("bad MSG_LOCK string");
804698d5317Sjmmv 
805928fc495Schristos 		system(data);
806f26e8bc9Schristos 		proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
807698d5317Sjmmv 		break;
808698d5317Sjmmv 	}
809698d5317Sjmmv }
810