2 * Copyright (c) 2013-2015 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32 #include <sys/select.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
37 #include <sys/socket.h>
39 #if defined(__linux__) || defined(__CYGWIN__)
41 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
47 #if defined CTRL && defined _AIX
51 #define CTRL(k) ((k) & 0x1F)
57 # include "forkpty-aix.c"
59 # include "forkpty-sunos.c"
62 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
84 typedef struct Client Client
;
105 struct winsize winsize
;
107 volatile sig_atomic_t running
;
109 const char *session_name
;
114 static Server server
= { .running
= true, .exit_status
= -1, .host
= "@localhost" };
115 static Client client
;
116 static struct termios orig_term
, cur_term
;
117 static bool has_term
, alternate_buffer
;
119 static struct sockaddr_un sockaddr
= {
120 .sun_family
= AF_UNIX
,
123 static bool set_socket_name(struct sockaddr_un
*sockaddr
, const char *name
);
124 static void die(const char *s
);
125 static void info(const char *str
, ...);
129 static inline size_t packet_header_size() {
130 return offsetof(Packet
, u
);
133 static size_t packet_size(Packet
*pkt
) {
134 return packet_header_size() + pkt
->len
;
137 static ssize_t
write_all(int fd
, const char *buf
, size_t len
) {
138 debug("write_all(%d)\n", len
);
141 ssize_t res
= write(fd
, buf
, len
);
143 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
155 static ssize_t
read_all(int fd
, char *buf
, size_t len
) {
156 debug("read_all(%d)\n", len
);
159 ssize_t res
= read(fd
, buf
, len
);
161 if (errno
== EWOULDBLOCK
)
163 if (errno
== EAGAIN
|| errno
== EINTR
)
175 static bool send_packet(int socket
, Packet
*pkt
) {
176 size_t size
= packet_size(pkt
);
177 if (size
> sizeof(*pkt
))
179 return write_all(socket
, (char *)pkt
, size
) == size
;
182 static bool recv_packet(int socket
, Packet
*pkt
) {
183 ssize_t len
= read_all(socket
, (char*)pkt
, packet_header_size());
184 if (len
<= 0 || len
!= packet_header_size())
186 if (pkt
->len
> sizeof(pkt
->u
.msg
)) {
191 len
= read_all(socket
, pkt
->u
.msg
, pkt
->len
);
192 if (len
<= 0 || len
!= pkt
->len
)
201 static void info(const char *str
, ...) {
205 fprintf(stderr
, "%s: %s: ", server
.name
, server
.session_name
);
206 vfprintf(stderr
, str
, ap
);
207 fprintf(stderr
, "\r\n");
213 static void die(const char *s
) {
218 static void usage(void) {
219 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-r] [-f] [-e detachkey] name command\n");
223 static bool xsnprintf(char *buf
, size_t size
, const char *fmt
, ...) {
228 int n
= vsnprintf(buf
, size
, fmt
, ap
);
233 errno
= ENAMETOOLONG
;
239 static bool create_socket_dir(struct sockaddr_un
*sockaddr
) {
240 sockaddr
->sun_path
[0] = '\0';
241 uid_t uid
= getuid();
242 size_t maxlen
= sizeof(sockaddr
->sun_path
);
243 char *dirs
[] = { getenv("HOME"), getenv("TMPDIR"), "/tmp" };
244 int socketfd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
247 struct passwd
*pw
= getpwuid(uid
);
248 if ((!dirs
[0] || !dirs
[0][0]) && pw
)
249 dirs
[0] = pw
->pw_dir
;
251 for (unsigned int i
= 0; i
< countof(dirs
); i
++) {
254 bool ishome
= (i
== 0);
257 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s%s/", dir
, ishome
? "." : "", server
.name
))
259 mode_t mask
= umask(0);
260 int r
= mkdir(sockaddr
->sun_path
, ishome
? S_IRWXU
: S_IRWXU
|S_IRWXG
|S_IRWXO
|S_ISVTX
);
262 if (r
!= 0 && errno
!= EEXIST
)
264 if (lstat(sockaddr
->sun_path
, &sb
) != 0)
266 if (!S_ISDIR(sb
.st_mode
)) {
271 size_t dirlen
= strlen(sockaddr
->sun_path
);
273 if (pw
&& !xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, "%s/", pw
->pw_name
))
275 if (!pw
&& !xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, "%d/", uid
))
277 if (mkdir(sockaddr
->sun_path
, S_IRWXU
) != 0 && errno
!= EEXIST
)
279 if (lstat(sockaddr
->sun_path
, &sb
) != 0)
281 if (!S_ISDIR(sb
.st_mode
)) {
285 dirlen
= strlen(sockaddr
->sun_path
);
288 if (sb
.st_uid
!= uid
|| sb
.st_mode
& (S_IRWXG
|S_IRWXO
)) {
293 if (!xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, ".abduco-%d", getpid()))
296 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
->sun_path
) + 1;
297 if (bind(socketfd
, (struct sockaddr
*)sockaddr
, socklen
) == -1)
299 unlink(sockaddr
->sun_path
);
301 sockaddr
->sun_path
[dirlen
] = '\0';
309 static bool set_socket_name(struct sockaddr_un
*sockaddr
, const char *name
) {
310 size_t maxlen
= sizeof(sockaddr
->sun_path
);
311 if (name
[0] == '/') {
312 if (strlen(name
) >= maxlen
) {
313 errno
= ENAMETOOLONG
;
316 strncpy(sockaddr
->sun_path
, name
, maxlen
);
317 } else if (name
[0] == '.' && (name
[1] == '.' || name
[1] == '/')) {
318 char buf
[maxlen
], *cwd
= getcwd(buf
, sizeof buf
);
321 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s", cwd
, name
))
324 if (!create_socket_dir(sockaddr
))
326 if (strlen(sockaddr
->sun_path
) + strlen(name
) + strlen(server
.host
) >= maxlen
) {
327 errno
= ENAMETOOLONG
;
330 strncat(sockaddr
->sun_path
, name
, maxlen
- strlen(sockaddr
->sun_path
) - 1);
331 strncat(sockaddr
->sun_path
, server
.host
, maxlen
- strlen(sockaddr
->sun_path
) - 1);
336 static bool create_session(const char *name
, char * const argv
[]) {
337 /* this uses the well known double fork strategy as described in section 1.7 of
339 * http://www.faqs.org/faqs/unix-faq/programmer/faq/
341 * pipes are used for synchronization and error reporting i.e. the child sets
342 * the close on exec flag before calling execvp(3) the parent blocks on a read(2)
343 * in case of failure the error message is written to the pipe, success is
344 * indicated by EOF on the pipe.
346 int client_pipe
[2], server_pipe
[2];
351 if (pipe(client_pipe
) == -1)
353 if ((server
.socket
= server_create_socket(name
)) == -1)
356 switch ((pid
= fork())) {
357 case 0: /* child process */
359 close(client_pipe
[0]);
360 switch ((pid
= fork())) {
361 case 0: /* child process */
362 if (pipe(server_pipe
) == -1) {
363 snprintf(errormsg
, sizeof(errormsg
), "server-pipe: %s\n", strerror(errno
));
364 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
365 close(client_pipe
[1]);
369 sigemptyset(&sa
.sa_mask
);
370 sa
.sa_handler
= server_pty_died_handler
;
371 sigaction(SIGCHLD
, &sa
, NULL
);
372 switch (server
.pid
= forkpty(&server
.pty
, NULL
, has_term
? &server
.term
: NULL
, &server
.winsize
)) {
373 case 0: /* child = user application process */
374 close(server
.socket
);
375 close(server_pipe
[0]);
376 if (fcntl(client_pipe
[1], F_SETFD
, FD_CLOEXEC
) == 0 &&
377 fcntl(server_pipe
[1], F_SETFD
, FD_CLOEXEC
) == 0)
378 execvp(argv
[0], argv
);
379 snprintf(errormsg
, sizeof(errormsg
), "server-execvp: %s: %s\n",
380 argv
[0], strerror(errno
));
381 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
382 write_all(server_pipe
[1], errormsg
, strlen(errormsg
));
383 close(client_pipe
[1]);
384 close(server_pipe
[1]);
387 case -1: /* forkpty failed */
388 snprintf(errormsg
, sizeof(errormsg
), "server-forkpty: %s\n", strerror(errno
));
389 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
390 close(client_pipe
[1]);
391 close(server_pipe
[0]);
392 close(server_pipe
[1]);
395 default: /* parent = server process */
396 sa
.sa_handler
= server_sigterm_handler
;
397 sigaction(SIGTERM
, &sa
, NULL
);
398 sigaction(SIGINT
, &sa
, NULL
);
399 sa
.sa_handler
= server_sigusr1_handler
;
400 sigaction(SIGUSR1
, &sa
, NULL
);
401 sa
.sa_handler
= SIG_IGN
;
402 sigaction(SIGPIPE
, &sa
, NULL
);
403 sigaction(SIGHUP
, &sa
, NULL
);
406 int fd
= open("/dev/null", O_RDWR
);
414 close(client_pipe
[1]);
415 close(server_pipe
[1]);
416 if (read_all(server_pipe
[0], errormsg
, sizeof(errormsg
)) > 0)
418 close(server_pipe
[0]);
423 case -1: /* fork failed */
424 snprintf(errormsg
, sizeof(errormsg
), "server-fork: %s\n", strerror(errno
));
425 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
426 close(client_pipe
[1]);
429 default: /* parent = intermediate process */
430 close(client_pipe
[1]);
435 case -1: /* fork failed */
436 close(client_pipe
[0]);
437 close(client_pipe
[1]);
439 default: /* parent = client process */
440 close(client_pipe
[1]);
442 wait(&status
); /* wait for first fork */
443 ssize_t len
= read_all(client_pipe
[0], errormsg
, sizeof(errormsg
));
445 write_all(STDERR_FILENO
, errormsg
, len
);
446 unlink(sockaddr
.sun_path
);
449 close(client_pipe
[0]);
454 static bool attach_session(const char *name
, const bool terminate
) {
455 if (server
.socket
> 0)
456 close(server
.socket
);
457 if (!set_socket_name(&sockaddr
, name
) || (server
.socket
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1)
459 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
460 if (connect(server
.socket
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
462 if (server_set_socket_non_blocking(server
.socket
) == -1)
467 sigemptyset(&sa
.sa_mask
);
468 sa
.sa_handler
= client_sigwinch_handler
;
469 sigaction(SIGWINCH
, &sa
, NULL
);
470 sa
.sa_handler
= SIG_IGN
;
471 sigaction(SIGPIPE
, &sa
, NULL
);
473 client_setup_terminal();
474 int status
= client_mainloop();
475 client_restore_terminal();
478 } else if (status
== -EIO
) {
479 info("exited due to I/O errors");
481 info("session terminated with exit status %d", status
);
489 static bool session_exists(const char *name
) {
491 if (!set_socket_name(&sockaddr
, name
))
493 return stat(sockaddr
.sun_path
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
);
496 static bool session_alive(const char *name
) {
498 if (!set_socket_name(&sockaddr
, name
))
500 return stat(sockaddr
.sun_path
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
) &&
501 (sb
.st_mode
& S_IXGRP
) == 0;
504 static int session_filter(const struct dirent
*d
) {
505 return strstr(d
->d_name
, server
.host
) != NULL
;
508 static int session_comparator(const struct dirent
**a
, const struct dirent
**b
) {
510 if (stat((*a
)->d_name
, &sa
) != 0)
512 if (stat((*b
)->d_name
, &sb
) != 0)
514 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
517 static int list_session(void) {
518 if (!create_socket_dir(&sockaddr
))
520 chdir(sockaddr
.sun_path
);
521 struct dirent
**namelist
;
522 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
525 printf("Active sessions (on host %s)\n", server
.host
+1);
527 struct stat sb
; char buf
[255];
528 if (stat(namelist
[n
]->d_name
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
)) {
529 strftime(buf
, sizeof(buf
), "%a%t %F %T", localtime(&sb
.st_atime
));
531 if (sb
.st_mode
& S_IXUSR
)
533 else if (sb
.st_mode
& S_IXGRP
)
535 char *name
= strstr(namelist
[n
]->d_name
, server
.host
);
538 printf("%c %s\t%s\n", status
, buf
, namelist
[n
]->d_name
);
546 int main(int argc
, char *argv
[]) {
548 char **cmd
= NULL
, action
= '\0';
549 server
.name
= basename(argv
[0]);
550 gethostname(server
.host
+1, sizeof(server
.host
) - 1);
552 exit(list_session());
553 for (int arg
= 1; arg
< argc
; arg
++) {
554 if (argv
[arg
][0] != '-') {
555 if (!server
.session_name
) {
556 server
.session_name
= argv
[arg
];
563 if (server
.session_name
)
565 switch (argv
[arg
][1]) {
570 action
= argv
[arg
][1];
575 char *esc
= argv
[++arg
];
576 if (esc
[0] == '^' && esc
[1])
584 client
.readonly
= true;
587 puts("abduco-"VERSION
" © 2013-2015 Marc André Tanner");
595 cmd
= (char*[]){ getenv("ABDUCO_CMD"), NULL
};
600 if (!action
|| !server
.session_name
)
603 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
604 server
.term
= orig_term
;
608 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &server
.winsize
) == -1) {
609 server
.winsize
.ws_col
= 80;
610 server
.winsize
.ws_row
= 25;
613 server
.read_pty
= (action
== 'n');
620 if (session_alive(server
.session_name
)) {
621 info("session exists and has not yet terminated");
624 if (session_exists(server
.session_name
))
625 attach_session(server
.session_name
, false);
627 if (!create_session(server
.session_name
, cmd
))
628 die("create-session");
632 if (!attach_session(server
.session_name
, true))
633 die("attach-session");
636 if (session_alive(server
.session_name
) && !attach_session(server
.session_name
, true))
637 die("attach-session");
638 if (!attach_session(server
.session_name
, !force
)) {