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