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