1 |
|
|
/* $OpenBSD: client.c,v 1.123 2017/07/14 18:49:07 nicm Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 |
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 |
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/file.h> |
21 |
|
|
#include <sys/socket.h> |
22 |
|
|
#include <sys/stat.h> |
23 |
|
|
#include <sys/un.h> |
24 |
|
|
#include <sys/wait.h> |
25 |
|
|
|
26 |
|
|
#include <errno.h> |
27 |
|
|
#include <event.h> |
28 |
|
|
#include <fcntl.h> |
29 |
|
|
#include <imsg.h> |
30 |
|
|
#include <signal.h> |
31 |
|
|
#include <stdlib.h> |
32 |
|
|
#include <string.h> |
33 |
|
|
#include <unistd.h> |
34 |
|
|
|
35 |
|
|
#include "tmux.h" |
36 |
|
|
|
37 |
|
|
static struct tmuxproc *client_proc; |
38 |
|
|
static struct tmuxpeer *client_peer; |
39 |
|
|
static int client_flags; |
40 |
|
|
static struct event client_stdin; |
41 |
|
|
static enum { |
42 |
|
|
CLIENT_EXIT_NONE, |
43 |
|
|
CLIENT_EXIT_DETACHED, |
44 |
|
|
CLIENT_EXIT_DETACHED_HUP, |
45 |
|
|
CLIENT_EXIT_LOST_TTY, |
46 |
|
|
CLIENT_EXIT_TERMINATED, |
47 |
|
|
CLIENT_EXIT_LOST_SERVER, |
48 |
|
|
CLIENT_EXIT_EXITED, |
49 |
|
|
CLIENT_EXIT_SERVER_EXITED, |
50 |
|
|
} client_exitreason = CLIENT_EXIT_NONE; |
51 |
|
|
static int client_exitval; |
52 |
|
|
static enum msgtype client_exittype; |
53 |
|
|
static const char *client_exitsession; |
54 |
|
|
static const char *client_execshell; |
55 |
|
|
static const char *client_execcmd; |
56 |
|
|
static int client_attached; |
57 |
|
|
|
58 |
|
|
static __dead void client_exec(const char *,const char *); |
59 |
|
|
static int client_get_lock(char *); |
60 |
|
|
static int client_connect(struct event_base *, const char *, int); |
61 |
|
|
static void client_send_identify(const char *, const char *); |
62 |
|
|
static void client_stdin_callback(int, short, void *); |
63 |
|
|
static void client_write(int, const char *, size_t); |
64 |
|
|
static void client_signal(int); |
65 |
|
|
static void client_dispatch(struct imsg *, void *); |
66 |
|
|
static void client_dispatch_attached(struct imsg *); |
67 |
|
|
static void client_dispatch_wait(struct imsg *); |
68 |
|
|
static const char *client_exit_message(void); |
69 |
|
|
|
70 |
|
|
/* |
71 |
|
|
* Get server create lock. If already held then server start is happening in |
72 |
|
|
* another client, so block until the lock is released and return -2 to |
73 |
|
|
* retry. Return -1 on failure to continue and start the server anyway. |
74 |
|
|
*/ |
75 |
|
|
static int |
76 |
|
|
client_get_lock(char *lockfile) |
77 |
|
|
{ |
78 |
|
|
int lockfd; |
79 |
|
|
|
80 |
|
|
log_debug("lock file is %s", lockfile); |
81 |
|
|
|
82 |
|
|
if ((lockfd = open(lockfile, O_WRONLY|O_CREAT, 0600)) == -1) { |
83 |
|
|
log_debug("open failed: %s", strerror(errno)); |
84 |
|
|
return (-1); |
85 |
|
|
} |
86 |
|
|
|
87 |
|
|
if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) { |
88 |
|
|
log_debug("flock failed: %s", strerror(errno)); |
89 |
|
|
if (errno != EAGAIN) |
90 |
|
|
return (lockfd); |
91 |
|
|
while (flock(lockfd, LOCK_EX) == -1 && errno == EINTR) |
92 |
|
|
/* nothing */; |
93 |
|
|
close(lockfd); |
94 |
|
|
return (-2); |
95 |
|
|
} |
96 |
|
|
log_debug("flock succeeded"); |
97 |
|
|
|
98 |
|
|
return (lockfd); |
99 |
|
|
} |
100 |
|
|
|
101 |
|
|
/* Connect client to server. */ |
102 |
|
|
static int |
103 |
|
|
client_connect(struct event_base *base, const char *path, int start_server) |
104 |
|
|
{ |
105 |
|
|
struct sockaddr_un sa; |
106 |
|
|
size_t size; |
107 |
|
|
int fd, lockfd = -1, locked = 0; |
108 |
|
|
char *lockfile = NULL; |
109 |
|
|
|
110 |
|
|
memset(&sa, 0, sizeof sa); |
111 |
|
|
sa.sun_family = AF_UNIX; |
112 |
|
|
size = strlcpy(sa.sun_path, path, sizeof sa.sun_path); |
113 |
|
|
if (size >= sizeof sa.sun_path) { |
114 |
|
|
errno = ENAMETOOLONG; |
115 |
|
|
return (-1); |
116 |
|
|
} |
117 |
|
|
log_debug("socket is %s", path); |
118 |
|
|
|
119 |
|
|
retry: |
120 |
|
|
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) |
121 |
|
|
return (-1); |
122 |
|
|
|
123 |
|
|
log_debug("trying connect"); |
124 |
|
|
if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == -1) { |
125 |
|
|
log_debug("connect failed: %s", strerror(errno)); |
126 |
|
|
if (errno != ECONNREFUSED && errno != ENOENT) |
127 |
|
|
goto failed; |
128 |
|
|
if (!start_server) |
129 |
|
|
goto failed; |
130 |
|
|
close(fd); |
131 |
|
|
|
132 |
|
|
if (!locked) { |
133 |
|
|
xasprintf(&lockfile, "%s.lock", path); |
134 |
|
|
if ((lockfd = client_get_lock(lockfile)) < 0) { |
135 |
|
|
log_debug("didn't get lock (%d)", lockfd); |
136 |
|
|
|
137 |
|
|
free(lockfile); |
138 |
|
|
lockfile = NULL; |
139 |
|
|
|
140 |
|
|
if (lockfd == -2) |
141 |
|
|
goto retry; |
142 |
|
|
} |
143 |
|
|
log_debug("got lock (%d)", lockfd); |
144 |
|
|
|
145 |
|
|
/* |
146 |
|
|
* Always retry at least once, even if we got the lock, |
147 |
|
|
* because another client could have taken the lock, |
148 |
|
|
* started the server and released the lock between our |
149 |
|
|
* connect() and flock(). |
150 |
|
|
*/ |
151 |
|
|
locked = 1; |
152 |
|
|
goto retry; |
153 |
|
|
} |
154 |
|
|
|
155 |
|
|
if (lockfd >= 0 && unlink(path) != 0 && errno != ENOENT) { |
156 |
|
|
free(lockfile); |
157 |
|
|
close(lockfd); |
158 |
|
|
return (-1); |
159 |
|
|
} |
160 |
|
|
fd = server_start(client_proc, base, lockfd, lockfile); |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
if (locked && lockfd >= 0) { |
164 |
|
|
free(lockfile); |
165 |
|
|
close(lockfd); |
166 |
|
|
} |
167 |
|
|
setblocking(fd, 0); |
168 |
|
|
return (fd); |
169 |
|
|
|
170 |
|
|
failed: |
171 |
|
|
if (locked) { |
172 |
|
|
free(lockfile); |
173 |
|
|
close(lockfd); |
174 |
|
|
} |
175 |
|
|
close(fd); |
176 |
|
|
return (-1); |
177 |
|
|
} |
178 |
|
|
|
179 |
|
|
/* Get exit string from reason number. */ |
180 |
|
|
const char * |
181 |
|
|
client_exit_message(void) |
182 |
|
|
{ |
183 |
|
|
static char msg[256]; |
184 |
|
|
|
185 |
|
|
switch (client_exitreason) { |
186 |
|
|
case CLIENT_EXIT_NONE: |
187 |
|
|
break; |
188 |
|
|
case CLIENT_EXIT_DETACHED: |
189 |
|
|
if (client_exitsession != NULL) { |
190 |
|
|
xsnprintf(msg, sizeof msg, "detached " |
191 |
|
|
"(from session %s)", client_exitsession); |
192 |
|
|
return (msg); |
193 |
|
|
} |
194 |
|
|
return ("detached"); |
195 |
|
|
case CLIENT_EXIT_DETACHED_HUP: |
196 |
|
|
if (client_exitsession != NULL) { |
197 |
|
|
xsnprintf(msg, sizeof msg, "detached and SIGHUP " |
198 |
|
|
"(from session %s)", client_exitsession); |
199 |
|
|
return (msg); |
200 |
|
|
} |
201 |
|
|
return ("detached and SIGHUP"); |
202 |
|
|
case CLIENT_EXIT_LOST_TTY: |
203 |
|
|
return ("lost tty"); |
204 |
|
|
case CLIENT_EXIT_TERMINATED: |
205 |
|
|
return ("terminated"); |
206 |
|
|
case CLIENT_EXIT_LOST_SERVER: |
207 |
|
|
return ("lost server"); |
208 |
|
|
case CLIENT_EXIT_EXITED: |
209 |
|
|
return ("exited"); |
210 |
|
|
case CLIENT_EXIT_SERVER_EXITED: |
211 |
|
|
return ("server exited"); |
212 |
|
|
} |
213 |
|
|
return ("unknown reason"); |
214 |
|
|
} |
215 |
|
|
|
216 |
|
|
/* Client main loop. */ |
217 |
|
|
int |
218 |
|
|
client_main(struct event_base *base, int argc, char **argv, int flags) |
219 |
|
|
{ |
220 |
|
|
struct cmd *cmd; |
221 |
|
|
struct cmd_list *cmdlist; |
222 |
|
|
struct msg_command_data *data; |
223 |
|
|
int cmdflags, fd, i; |
224 |
|
|
const char *ttynam, *cwd; |
225 |
|
|
pid_t ppid; |
226 |
|
|
enum msgtype msg; |
227 |
|
|
char *cause, path[PATH_MAX]; |
228 |
|
|
struct termios tio, saved_tio; |
229 |
|
|
size_t size; |
230 |
|
|
|
231 |
|
|
/* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ |
232 |
|
|
signal(SIGCHLD, SIG_IGN); |
233 |
|
|
|
234 |
|
|
/* Save the flags. */ |
235 |
|
|
client_flags = flags; |
236 |
|
|
|
237 |
|
|
/* Set up the initial command. */ |
238 |
|
|
cmdflags = 0; |
239 |
|
|
if (shell_command != NULL) { |
240 |
|
|
msg = MSG_SHELL; |
241 |
|
|
cmdflags = CMD_STARTSERVER; |
242 |
|
|
} else if (argc == 0) { |
243 |
|
|
msg = MSG_COMMAND; |
244 |
|
|
cmdflags = CMD_STARTSERVER; |
245 |
|
|
} else { |
246 |
|
|
msg = MSG_COMMAND; |
247 |
|
|
|
248 |
|
|
/* |
249 |
|
|
* It sucks parsing the command string twice (in client and |
250 |
|
|
* later in server) but it is necessary to get the start server |
251 |
|
|
* flag. |
252 |
|
|
*/ |
253 |
|
|
cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause); |
254 |
|
|
if (cmdlist != NULL) { |
255 |
|
|
TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { |
256 |
|
|
if (cmd->entry->flags & CMD_STARTSERVER) |
257 |
|
|
cmdflags |= CMD_STARTSERVER; |
258 |
|
|
} |
259 |
|
|
cmd_list_free(cmdlist); |
260 |
|
|
} |
261 |
|
|
} |
262 |
|
|
|
263 |
|
|
/* Create client process structure (starts logging). */ |
264 |
|
|
client_proc = proc_start("client"); |
265 |
|
|
proc_set_signals(client_proc, client_signal); |
266 |
|
|
|
267 |
|
|
/* Initialize the client socket and start the server. */ |
268 |
|
|
fd = client_connect(base, socket_path, cmdflags & CMD_STARTSERVER); |
269 |
|
|
if (fd == -1) { |
270 |
|
|
if (errno == ECONNREFUSED) { |
271 |
|
|
fprintf(stderr, "no server running on %s\n", |
272 |
|
|
socket_path); |
273 |
|
|
} else { |
274 |
|
|
fprintf(stderr, "error connecting to %s (%s)\n", |
275 |
|
|
socket_path, strerror(errno)); |
276 |
|
|
} |
277 |
|
|
return (1); |
278 |
|
|
} |
279 |
|
|
client_peer = proc_add_peer(client_proc, fd, client_dispatch, NULL); |
280 |
|
|
|
281 |
|
|
/* Save these before pledge(). */ |
282 |
|
|
if ((cwd = getcwd(path, sizeof path)) == NULL) { |
283 |
|
|
if ((cwd = find_home()) == NULL) |
284 |
|
|
cwd = "/"; |
285 |
|
|
} |
286 |
|
|
if ((ttynam = ttyname(STDIN_FILENO)) == NULL) |
287 |
|
|
ttynam = ""; |
288 |
|
|
|
289 |
|
|
/* |
290 |
|
|
* Drop privileges for client. "proc exec" is needed for -c and for |
291 |
|
|
* locking (which uses system(3)). |
292 |
|
|
* |
293 |
|
|
* "tty" is needed to restore termios(4) and also for some reason -CC |
294 |
|
|
* does not work properly without it (input is not recognised). |
295 |
|
|
* |
296 |
|
|
* "sendfd" is dropped later in client_dispatch_wait(). |
297 |
|
|
*/ |
298 |
|
|
if (pledge("stdio unix sendfd proc exec tty flock rpath cpath wpath", NULL) != 0) |
299 |
|
|
fatal("pledge failed"); |
300 |
|
|
|
301 |
|
|
/* Free stuff that is not used in the client. */ |
302 |
|
|
if (ptm_fd != -1) |
303 |
|
|
close(ptm_fd); |
304 |
|
|
options_free(global_options); |
305 |
|
|
options_free(global_s_options); |
306 |
|
|
options_free(global_w_options); |
307 |
|
|
environ_free(global_environ); |
308 |
|
|
|
309 |
|
|
/* Create stdin handler. */ |
310 |
|
|
setblocking(STDIN_FILENO, 0); |
311 |
|
|
event_set(&client_stdin, STDIN_FILENO, EV_READ|EV_PERSIST, |
312 |
|
|
client_stdin_callback, NULL); |
313 |
|
|
if (client_flags & CLIENT_CONTROLCONTROL) { |
314 |
|
|
if (tcgetattr(STDIN_FILENO, &saved_tio) != 0) { |
315 |
|
|
fprintf(stderr, "tcgetattr failed: %s\n", |
316 |
|
|
strerror(errno)); |
317 |
|
|
return (1); |
318 |
|
|
} |
319 |
|
|
cfmakeraw(&tio); |
320 |
|
|
tio.c_iflag = ICRNL|IXANY; |
321 |
|
|
tio.c_oflag = OPOST|ONLCR; |
322 |
|
|
tio.c_lflag = NOKERNINFO; |
323 |
|
|
tio.c_cflag = CREAD|CS8|HUPCL; |
324 |
|
|
tio.c_cc[VMIN] = 1; |
325 |
|
|
tio.c_cc[VTIME] = 0; |
326 |
|
|
cfsetispeed(&tio, cfgetispeed(&saved_tio)); |
327 |
|
|
cfsetospeed(&tio, cfgetospeed(&saved_tio)); |
328 |
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &tio); |
329 |
|
|
} |
330 |
|
|
|
331 |
|
|
/* Send identify messages. */ |
332 |
|
|
client_send_identify(ttynam, cwd); |
333 |
|
|
|
334 |
|
|
/* Send first command. */ |
335 |
|
|
if (msg == MSG_COMMAND) { |
336 |
|
|
/* How big is the command? */ |
337 |
|
|
size = 0; |
338 |
|
|
for (i = 0; i < argc; i++) |
339 |
|
|
size += strlen(argv[i]) + 1; |
340 |
|
|
data = xmalloc((sizeof *data) + size); |
341 |
|
|
|
342 |
|
|
/* Prepare command for server. */ |
343 |
|
|
data->argc = argc; |
344 |
|
|
if (cmd_pack_argv(argc, argv, (char *)(data + 1), size) != 0) { |
345 |
|
|
fprintf(stderr, "command too long\n"); |
346 |
|
|
free(data); |
347 |
|
|
return (1); |
348 |
|
|
} |
349 |
|
|
size += sizeof *data; |
350 |
|
|
|
351 |
|
|
/* Send the command. */ |
352 |
|
|
if (proc_send(client_peer, msg, -1, data, size) != 0) { |
353 |
|
|
fprintf(stderr, "failed to send command\n"); |
354 |
|
|
free(data); |
355 |
|
|
return (1); |
356 |
|
|
} |
357 |
|
|
free(data); |
358 |
|
|
} else if (msg == MSG_SHELL) |
359 |
|
|
proc_send(client_peer, msg, -1, NULL, 0); |
360 |
|
|
|
361 |
|
|
/* Start main loop. */ |
362 |
|
|
proc_loop(client_proc, NULL); |
363 |
|
|
|
364 |
|
|
/* Run command if user requested exec, instead of exiting. */ |
365 |
|
|
if (client_exittype == MSG_EXEC) { |
366 |
|
|
if (client_flags & CLIENT_CONTROLCONTROL) |
367 |
|
|
tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); |
368 |
|
|
client_exec(client_execshell, client_execcmd); |
369 |
|
|
} |
370 |
|
|
|
371 |
|
|
/* Print the exit message, if any, and exit. */ |
372 |
|
|
if (client_attached) { |
373 |
|
|
if (client_exitreason != CLIENT_EXIT_NONE) |
374 |
|
|
printf("[%s]\n", client_exit_message()); |
375 |
|
|
|
376 |
|
|
ppid = getppid(); |
377 |
|
|
if (client_exittype == MSG_DETACHKILL && ppid > 1) |
378 |
|
|
kill(ppid, SIGHUP); |
379 |
|
|
} else if (client_flags & CLIENT_CONTROLCONTROL) { |
380 |
|
|
if (client_exitreason != CLIENT_EXIT_NONE) |
381 |
|
|
printf("%%exit %s\n", client_exit_message()); |
382 |
|
|
else |
383 |
|
|
printf("%%exit\n"); |
384 |
|
|
printf("\033\\"); |
385 |
|
|
tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); |
386 |
|
|
} else if (client_exitreason != CLIENT_EXIT_NONE) |
387 |
|
|
fprintf(stderr, "%s\n", client_exit_message()); |
388 |
|
|
setblocking(STDIN_FILENO, 1); |
389 |
|
|
return (client_exitval); |
390 |
|
|
} |
391 |
|
|
|
392 |
|
|
/* Send identify messages to server. */ |
393 |
|
|
static void |
394 |
|
|
client_send_identify(const char *ttynam, const char *cwd) |
395 |
|
|
{ |
396 |
|
|
const char *s; |
397 |
|
|
char **ss; |
398 |
|
|
size_t sslen; |
399 |
|
|
int fd, flags = client_flags; |
400 |
|
|
pid_t pid; |
401 |
|
|
|
402 |
|
|
proc_send(client_peer, MSG_IDENTIFY_FLAGS, -1, &flags, sizeof flags); |
403 |
|
|
|
404 |
|
|
if ((s = getenv("TERM")) == NULL) |
405 |
|
|
s = ""; |
406 |
|
|
proc_send(client_peer, MSG_IDENTIFY_TERM, -1, s, strlen(s) + 1); |
407 |
|
|
|
408 |
|
|
proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam, |
409 |
|
|
strlen(ttynam) + 1); |
410 |
|
|
proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1); |
411 |
|
|
|
412 |
|
|
if ((fd = dup(STDIN_FILENO)) == -1) |
413 |
|
|
fatal("dup failed"); |
414 |
|
|
proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0); |
415 |
|
|
|
416 |
|
|
pid = getpid(); |
417 |
|
|
proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid); |
418 |
|
|
|
419 |
|
|
for (ss = environ; *ss != NULL; ss++) { |
420 |
|
|
sslen = strlen(*ss) + 1; |
421 |
|
|
if (sslen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) |
422 |
|
|
continue; |
423 |
|
|
proc_send(client_peer, MSG_IDENTIFY_ENVIRON, -1, *ss, sslen); |
424 |
|
|
} |
425 |
|
|
|
426 |
|
|
proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0); |
427 |
|
|
} |
428 |
|
|
|
429 |
|
|
/* Callback for client stdin read events. */ |
430 |
|
|
static void |
431 |
|
|
client_stdin_callback(__unused int fd, __unused short events, |
432 |
|
|
__unused void *arg) |
433 |
|
|
{ |
434 |
|
|
struct msg_stdin_data data; |
435 |
|
|
|
436 |
|
|
data.size = read(STDIN_FILENO, data.data, sizeof data.data); |
437 |
|
|
if (data.size < 0 && (errno == EINTR || errno == EAGAIN)) |
438 |
|
|
return; |
439 |
|
|
|
440 |
|
|
proc_send(client_peer, MSG_STDIN, -1, &data, sizeof data); |
441 |
|
|
if (data.size <= 0) |
442 |
|
|
event_del(&client_stdin); |
443 |
|
|
} |
444 |
|
|
|
445 |
|
|
/* Force write to file descriptor. */ |
446 |
|
|
static void |
447 |
|
|
client_write(int fd, const char *data, size_t size) |
448 |
|
|
{ |
449 |
|
|
ssize_t used; |
450 |
|
|
|
451 |
|
|
while (size != 0) { |
452 |
|
|
used = write(fd, data, size); |
453 |
|
|
if (used == -1) { |
454 |
|
|
if (errno == EINTR || errno == EAGAIN) |
455 |
|
|
continue; |
456 |
|
|
break; |
457 |
|
|
} |
458 |
|
|
data += used; |
459 |
|
|
size -= used; |
460 |
|
|
} |
461 |
|
|
} |
462 |
|
|
|
463 |
|
|
/* Run command in shell; used for -c. */ |
464 |
|
|
static __dead void |
465 |
|
|
client_exec(const char *shell, const char *shellcmd) |
466 |
|
|
{ |
467 |
|
|
const char *name, *ptr; |
468 |
|
|
char *argv0; |
469 |
|
|
|
470 |
|
|
log_debug("shell %s, command %s", shell, shellcmd); |
471 |
|
|
|
472 |
|
|
ptr = strrchr(shell, '/'); |
473 |
|
|
if (ptr != NULL && *(ptr + 1) != '\0') |
474 |
|
|
name = ptr + 1; |
475 |
|
|
else |
476 |
|
|
name = shell; |
477 |
|
|
if (client_flags & CLIENT_LOGIN) |
478 |
|
|
xasprintf(&argv0, "-%s", name); |
479 |
|
|
else |
480 |
|
|
xasprintf(&argv0, "%s", name); |
481 |
|
|
setenv("SHELL", shell, 1); |
482 |
|
|
|
483 |
|
|
proc_clear_signals(client_proc, 1); |
484 |
|
|
|
485 |
|
|
setblocking(STDIN_FILENO, 1); |
486 |
|
|
setblocking(STDOUT_FILENO, 1); |
487 |
|
|
setblocking(STDERR_FILENO, 1); |
488 |
|
|
closefrom(STDERR_FILENO + 1); |
489 |
|
|
|
490 |
|
|
execl(shell, argv0, "-c", shellcmd, (char *) NULL); |
491 |
|
|
fatal("execl failed"); |
492 |
|
|
} |
493 |
|
|
|
494 |
|
|
/* Callback to handle signals in the client. */ |
495 |
|
|
static void |
496 |
|
|
client_signal(int sig) |
497 |
|
|
{ |
498 |
|
|
struct sigaction sigact; |
499 |
|
|
int status; |
500 |
|
|
|
501 |
|
|
if (sig == SIGCHLD) |
502 |
|
|
waitpid(WAIT_ANY, &status, WNOHANG); |
503 |
|
|
else if (!client_attached) { |
504 |
|
|
if (sig == SIGTERM) |
505 |
|
|
proc_exit(client_proc); |
506 |
|
|
} else { |
507 |
|
|
switch (sig) { |
508 |
|
|
case SIGHUP: |
509 |
|
|
client_exitreason = CLIENT_EXIT_LOST_TTY; |
510 |
|
|
client_exitval = 1; |
511 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
512 |
|
|
break; |
513 |
|
|
case SIGTERM: |
514 |
|
|
client_exitreason = CLIENT_EXIT_TERMINATED; |
515 |
|
|
client_exitval = 1; |
516 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
517 |
|
|
break; |
518 |
|
|
case SIGWINCH: |
519 |
|
|
proc_send(client_peer, MSG_RESIZE, -1, NULL, 0); |
520 |
|
|
break; |
521 |
|
|
case SIGCONT: |
522 |
|
|
memset(&sigact, 0, sizeof sigact); |
523 |
|
|
sigemptyset(&sigact.sa_mask); |
524 |
|
|
sigact.sa_flags = SA_RESTART; |
525 |
|
|
sigact.sa_handler = SIG_IGN; |
526 |
|
|
if (sigaction(SIGTSTP, &sigact, NULL) != 0) |
527 |
|
|
fatal("sigaction failed"); |
528 |
|
|
proc_send(client_peer, MSG_WAKEUP, -1, NULL, 0); |
529 |
|
|
break; |
530 |
|
|
} |
531 |
|
|
} |
532 |
|
|
} |
533 |
|
|
|
534 |
|
|
/* Callback for client read events. */ |
535 |
|
|
static void |
536 |
|
|
client_dispatch(struct imsg *imsg, __unused void *arg) |
537 |
|
|
{ |
538 |
|
|
if (imsg == NULL) { |
539 |
|
|
client_exitreason = CLIENT_EXIT_LOST_SERVER; |
540 |
|
|
client_exitval = 1; |
541 |
|
|
proc_exit(client_proc); |
542 |
|
|
return; |
543 |
|
|
} |
544 |
|
|
|
545 |
|
|
if (client_attached) |
546 |
|
|
client_dispatch_attached(imsg); |
547 |
|
|
else |
548 |
|
|
client_dispatch_wait(imsg); |
549 |
|
|
} |
550 |
|
|
|
551 |
|
|
/* Dispatch imsgs when in wait state (before MSG_READY). */ |
552 |
|
|
static void |
553 |
|
|
client_dispatch_wait(struct imsg *imsg) |
554 |
|
|
{ |
555 |
|
|
char *data; |
556 |
|
|
ssize_t datalen; |
557 |
|
|
struct msg_stdout_data stdoutdata; |
558 |
|
|
struct msg_stderr_data stderrdata; |
559 |
|
|
int retval; |
560 |
|
|
static int pledge_applied; |
561 |
|
|
|
562 |
|
|
/* |
563 |
|
|
* "sendfd" is no longer required once all of the identify messages |
564 |
|
|
* have been sent. We know the server won't send us anything until that |
565 |
|
|
* point (because we don't ask it to), so we can drop "sendfd" once we |
566 |
|
|
* get the first message from the server. |
567 |
|
|
*/ |
568 |
|
|
if (!pledge_applied) { |
569 |
|
|
if (pledge("stdio unix proc exec tty flock rpath cpath wpath", NULL) != 0) |
570 |
|
|
fatal("pledge failed"); |
571 |
|
|
pledge_applied = 1; |
572 |
|
|
}; |
573 |
|
|
|
574 |
|
|
data = imsg->data; |
575 |
|
|
datalen = imsg->hdr.len - IMSG_HEADER_SIZE; |
576 |
|
|
|
577 |
|
|
switch (imsg->hdr.type) { |
578 |
|
|
case MSG_EXIT: |
579 |
|
|
case MSG_SHUTDOWN: |
580 |
|
|
if (datalen != sizeof retval && datalen != 0) |
581 |
|
|
fatalx("bad MSG_EXIT size"); |
582 |
|
|
if (datalen == sizeof retval) { |
583 |
|
|
memcpy(&retval, data, sizeof retval); |
584 |
|
|
client_exitval = retval; |
585 |
|
|
} |
586 |
|
|
proc_exit(client_proc); |
587 |
|
|
break; |
588 |
|
|
case MSG_READY: |
589 |
|
|
if (datalen != 0) |
590 |
|
|
fatalx("bad MSG_READY size"); |
591 |
|
|
|
592 |
|
|
event_del(&client_stdin); |
593 |
|
|
client_attached = 1; |
594 |
|
|
proc_send(client_peer, MSG_RESIZE, -1, NULL, 0); |
595 |
|
|
break; |
596 |
|
|
case MSG_STDIN: |
597 |
|
|
if (datalen != 0) |
598 |
|
|
fatalx("bad MSG_STDIN size"); |
599 |
|
|
|
600 |
|
|
event_add(&client_stdin, NULL); |
601 |
|
|
break; |
602 |
|
|
case MSG_STDOUT: |
603 |
|
|
if (datalen != sizeof stdoutdata) |
604 |
|
|
fatalx("bad MSG_STDOUT size"); |
605 |
|
|
memcpy(&stdoutdata, data, sizeof stdoutdata); |
606 |
|
|
|
607 |
|
|
client_write(STDOUT_FILENO, stdoutdata.data, |
608 |
|
|
stdoutdata.size); |
609 |
|
|
break; |
610 |
|
|
case MSG_STDERR: |
611 |
|
|
if (datalen != sizeof stderrdata) |
612 |
|
|
fatalx("bad MSG_STDERR size"); |
613 |
|
|
memcpy(&stderrdata, data, sizeof stderrdata); |
614 |
|
|
|
615 |
|
|
client_write(STDERR_FILENO, stderrdata.data, |
616 |
|
|
stderrdata.size); |
617 |
|
|
break; |
618 |
|
|
case MSG_VERSION: |
619 |
|
|
if (datalen != 0) |
620 |
|
|
fatalx("bad MSG_VERSION size"); |
621 |
|
|
|
622 |
|
|
fprintf(stderr, "protocol version mismatch " |
623 |
|
|
"(client %d, server %u)\n", PROTOCOL_VERSION, |
624 |
|
|
imsg->hdr.peerid & 0xff); |
625 |
|
|
client_exitval = 1; |
626 |
|
|
proc_exit(client_proc); |
627 |
|
|
break; |
628 |
|
|
case MSG_SHELL: |
629 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
630 |
|
|
fatalx("bad MSG_SHELL string"); |
631 |
|
|
|
632 |
|
|
client_exec(data, shell_command); |
633 |
|
|
/* NOTREACHED */ |
634 |
|
|
case MSG_DETACH: |
635 |
|
|
case MSG_DETACHKILL: |
636 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
637 |
|
|
break; |
638 |
|
|
case MSG_EXITED: |
639 |
|
|
proc_exit(client_proc); |
640 |
|
|
break; |
641 |
|
|
} |
642 |
|
|
} |
643 |
|
|
|
644 |
|
|
/* Dispatch imsgs in attached state (after MSG_READY). */ |
645 |
|
|
static void |
646 |
|
|
client_dispatch_attached(struct imsg *imsg) |
647 |
|
|
{ |
648 |
|
|
struct sigaction sigact; |
649 |
|
|
char *data; |
650 |
|
|
ssize_t datalen; |
651 |
|
|
|
652 |
|
|
data = imsg->data; |
653 |
|
|
datalen = imsg->hdr.len - IMSG_HEADER_SIZE; |
654 |
|
|
|
655 |
|
|
switch (imsg->hdr.type) { |
656 |
|
|
case MSG_DETACH: |
657 |
|
|
case MSG_DETACHKILL: |
658 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
659 |
|
|
fatalx("bad MSG_DETACH string"); |
660 |
|
|
|
661 |
|
|
client_exitsession = xstrdup(data); |
662 |
|
|
client_exittype = imsg->hdr.type; |
663 |
|
|
if (imsg->hdr.type == MSG_DETACHKILL) |
664 |
|
|
client_exitreason = CLIENT_EXIT_DETACHED_HUP; |
665 |
|
|
else |
666 |
|
|
client_exitreason = CLIENT_EXIT_DETACHED; |
667 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
668 |
|
|
break; |
669 |
|
|
case MSG_EXEC: |
670 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0' || |
671 |
|
|
strlen(data) + 1 == (size_t)datalen) |
672 |
|
|
fatalx("bad MSG_EXEC string"); |
673 |
|
|
client_execcmd = xstrdup(data); |
674 |
|
|
client_execshell = xstrdup(data + strlen(data) + 1); |
675 |
|
|
|
676 |
|
|
client_exittype = imsg->hdr.type; |
677 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
678 |
|
|
break; |
679 |
|
|
case MSG_EXIT: |
680 |
|
|
if (datalen != 0 && datalen != sizeof (int)) |
681 |
|
|
fatalx("bad MSG_EXIT size"); |
682 |
|
|
|
683 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
684 |
|
|
client_exitreason = CLIENT_EXIT_EXITED; |
685 |
|
|
break; |
686 |
|
|
case MSG_EXITED: |
687 |
|
|
if (datalen != 0) |
688 |
|
|
fatalx("bad MSG_EXITED size"); |
689 |
|
|
|
690 |
|
|
proc_exit(client_proc); |
691 |
|
|
break; |
692 |
|
|
case MSG_SHUTDOWN: |
693 |
|
|
if (datalen != 0) |
694 |
|
|
fatalx("bad MSG_SHUTDOWN size"); |
695 |
|
|
|
696 |
|
|
proc_send(client_peer, MSG_EXITING, -1, NULL, 0); |
697 |
|
|
client_exitreason = CLIENT_EXIT_SERVER_EXITED; |
698 |
|
|
client_exitval = 1; |
699 |
|
|
break; |
700 |
|
|
case MSG_SUSPEND: |
701 |
|
|
if (datalen != 0) |
702 |
|
|
fatalx("bad MSG_SUSPEND size"); |
703 |
|
|
|
704 |
|
|
memset(&sigact, 0, sizeof sigact); |
705 |
|
|
sigemptyset(&sigact.sa_mask); |
706 |
|
|
sigact.sa_flags = SA_RESTART; |
707 |
|
|
sigact.sa_handler = SIG_DFL; |
708 |
|
|
if (sigaction(SIGTSTP, &sigact, NULL) != 0) |
709 |
|
|
fatal("sigaction failed"); |
710 |
|
|
kill(getpid(), SIGTSTP); |
711 |
|
|
break; |
712 |
|
|
case MSG_LOCK: |
713 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
714 |
|
|
fatalx("bad MSG_LOCK string"); |
715 |
|
|
|
716 |
|
|
system(data); |
717 |
|
|
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0); |
718 |
|
|
break; |
719 |
|
|
} |
720 |
|
|
} |