2 * Copyright (c) 2013-2018 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.
33 #include <sys/select.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
38 #include <sys/socket.h>
40 #if defined(__linux__) || defined(__CYGWIN__)
42 #elif defined(__FreeBSD__) || defined(__DragonFly__)
44 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
48 #if defined CTRL && defined _AIX
52 #define CTRL(k) ((k) & 0x1F)
58 # include "forkpty-aix.c"
60 # include "forkpty-sunos.c"
63 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
78 char msg
[4096 - 2*sizeof(uint32_t)];
87 typedef struct Client Client
;
98 CLIENT_READONLY
= 1 << 0,
99 CLIENT_LOWPRIORITY
= 1 << 1,
111 struct winsize winsize
;
113 volatile sig_atomic_t running
;
115 const char *session_name
;
120 static Server server
= { .running
= true, .exit_status
= -1, .host
= "@localhost" };
121 static Client client
;
122 static struct termios orig_term
, cur_term
;
123 static bool has_term
, alternate_buffer
, quiet
, passthrough
;
125 static struct sockaddr_un sockaddr
= {
126 .sun_family
= AF_UNIX
,
129 static bool set_socket_name(struct sockaddr_un
*sockaddr
, const char *name
);
130 static void die(const char *s
);
131 static void info(const char *str
, ...);
135 static inline size_t packet_header_size() {
136 return offsetof(Packet
, u
);
139 static size_t packet_size(Packet
*pkt
) {
140 return packet_header_size() + pkt
->len
;
143 static ssize_t
write_all(int fd
, const char *buf
, size_t len
) {
144 debug("write_all(%d)\n", len
);
147 ssize_t res
= write(fd
, buf
, len
);
149 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
161 static ssize_t
read_all(int fd
, char *buf
, size_t len
) {
162 debug("read_all(%d)\n", len
);
165 ssize_t res
= read(fd
, buf
, len
);
167 if (errno
== EWOULDBLOCK
)
169 if (errno
== EAGAIN
|| errno
== EINTR
)
181 static bool send_packet(int socket
, Packet
*pkt
) {
182 size_t size
= packet_size(pkt
);
183 if (size
> sizeof(*pkt
))
185 return write_all(socket
, (char *)pkt
, size
) == size
;
188 static bool recv_packet(int socket
, Packet
*pkt
) {
189 ssize_t len
= read_all(socket
, (char*)pkt
, packet_header_size());
190 if (len
<= 0 || len
!= packet_header_size())
192 if (pkt
->len
> sizeof(pkt
->u
.msg
)) {
197 len
= read_all(socket
, pkt
->u
.msg
, pkt
->len
);
198 if (len
<= 0 || len
!= pkt
->len
)
207 static void info(const char *str
, ...) {
211 fprintf(stderr
, "%s: %s: ", server
.name
, server
.session_name
);
212 vfprintf(stderr
, str
, ap
);
213 fprintf(stderr
, "\r\n");
219 static void die(const char *s
) {
224 static void usage(void) {
225 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-p] [-r] [-q] [-l] [-f] [-e detachkey] name command\n");
229 static bool xsnprintf(char *buf
, size_t size
, const char *fmt
, ...) {
234 int n
= vsnprintf(buf
, size
, fmt
, ap
);
239 errno
= ENAMETOOLONG
;
245 static int session_connect(const char *name
) {
248 if (!set_socket_name(&sockaddr
, name
) || (fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1)
250 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
251 if (connect(fd
, (struct sockaddr
*)&sockaddr
, socklen
) == -1) {
252 if (errno
== ECONNREFUSED
&& stat(sockaddr
.sun_path
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
))
253 unlink(sockaddr
.sun_path
);
260 static bool session_exists(const char *name
) {
261 int fd
= session_connect(name
);
267 static bool session_alive(const char *name
) {
269 return session_exists(name
) &&
270 stat(sockaddr
.sun_path
, &sb
) == 0 &&
271 S_ISSOCK(sb
.st_mode
) && (sb
.st_mode
& S_IXGRP
) == 0;
274 static bool create_socket_dir(struct sockaddr_un
*sockaddr
) {
275 sockaddr
->sun_path
[0] = '\0';
276 int socketfd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
280 size_t maxlen
= sizeof(sockaddr
->sun_path
);
281 uid_t uid
= getuid();
282 struct passwd
*pw
= getpwuid(uid
);
284 for (unsigned int i
= 0; i
< countof(socket_dirs
); i
++) {
286 struct Dir
*dir
= &socket_dirs
[i
];
289 dir
->path
= getenv(dir
->env
);
290 ishome
= !strcmp(dir
->env
, "HOME");
291 if (ishome
&& (!dir
->path
|| !dir
->path
[0]) && pw
)
292 dir
->path
= pw
->pw_dir
;
294 if (!dir
->path
|| !dir
->path
[0])
296 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s%s/", dir
->path
, ishome
? "." : "", server
.name
))
298 mode_t mask
= umask(0);
299 int r
= mkdir(sockaddr
->sun_path
, dir
->personal
? S_IRWXU
: S_IRWXU
|S_IRWXG
|S_IRWXO
|S_ISVTX
);
301 if (r
!= 0 && errno
!= EEXIST
)
303 if (lstat(sockaddr
->sun_path
, &sb
) != 0)
305 if (!S_ISDIR(sb
.st_mode
)) {
310 size_t dirlen
= strlen(sockaddr
->sun_path
);
311 if (!dir
->personal
) {
312 /* create subdirectory only accessible to user */
313 if (pw
&& !xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, "%s/", pw
->pw_name
))
315 if (!pw
&& !xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, "%d/", uid
))
317 if (mkdir(sockaddr
->sun_path
, S_IRWXU
) != 0 && errno
!= EEXIST
)
319 if (lstat(sockaddr
->sun_path
, &sb
) != 0)
321 if (!S_ISDIR(sb
.st_mode
)) {
325 dirlen
= strlen(sockaddr
->sun_path
);
328 if (sb
.st_uid
!= uid
|| sb
.st_mode
& (S_IRWXG
|S_IRWXO
)) {
333 if (!xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, ".abduco-%d", getpid()))
336 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
->sun_path
) + 1;
337 if (bind(socketfd
, (struct sockaddr
*)sockaddr
, socklen
) == -1)
339 unlink(sockaddr
->sun_path
);
341 sockaddr
->sun_path
[dirlen
] = '\0';
349 static bool set_socket_name(struct sockaddr_un
*sockaddr
, const char *name
) {
350 size_t maxlen
= sizeof(sockaddr
->sun_path
);
351 if (name
[0] == '/') {
352 if (strlen(name
) >= maxlen
) {
353 errno
= ENAMETOOLONG
;
356 strncpy(sockaddr
->sun_path
, name
, maxlen
);
357 } else if (name
[0] == '.' && (name
[1] == '.' || name
[1] == '/')) {
358 char buf
[maxlen
], *cwd
= getcwd(buf
, sizeof buf
);
361 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s", cwd
, name
))
364 if (!create_socket_dir(sockaddr
))
366 if (strlen(sockaddr
->sun_path
) + strlen(name
) + strlen(server
.host
) >= maxlen
) {
367 errno
= ENAMETOOLONG
;
370 strncat(sockaddr
->sun_path
, name
, maxlen
- strlen(sockaddr
->sun_path
) - 1);
371 strncat(sockaddr
->sun_path
, server
.host
, maxlen
- strlen(sockaddr
->sun_path
) - 1);
376 static bool create_session(const char *name
, char * const argv
[]) {
377 /* this uses the well known double fork strategy as described in section 1.7 of
379 * http://www.faqs.org/faqs/unix-faq/programmer/faq/
381 * pipes are used for synchronization and error reporting i.e. the child sets
382 * the close on exec flag before calling execvp(3) the parent blocks on a read(2)
383 * in case of failure the error message is written to the pipe, success is
384 * indicated by EOF on the pipe.
386 int client_pipe
[2], server_pipe
[2];
391 if (session_exists(name
)) {
396 if (pipe(client_pipe
) == -1)
398 if ((server
.socket
= server_create_socket(name
)) == -1)
401 switch ((pid
= fork())) {
402 case 0: /* child process */
404 close(client_pipe
[0]);
405 switch ((pid
= fork())) {
406 case 0: /* child process */
407 if (pipe(server_pipe
) == -1) {
408 snprintf(errormsg
, sizeof(errormsg
), "server-pipe: %s\n", strerror(errno
));
409 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
410 close(client_pipe
[1]);
414 sigemptyset(&sa
.sa_mask
);
415 sa
.sa_handler
= server_pty_died_handler
;
416 sigaction(SIGCHLD
, &sa
, NULL
);
417 switch (server
.pid
= forkpty(&server
.pty
, NULL
, has_term
? &server
.term
: NULL
, &server
.winsize
)) {
418 case 0: /* child = user application process */
419 close(server
.socket
);
420 close(server_pipe
[0]);
421 if (fcntl(client_pipe
[1], F_SETFD
, FD_CLOEXEC
) == 0 &&
422 fcntl(server_pipe
[1], F_SETFD
, FD_CLOEXEC
) == 0)
423 execvp(argv
[0], argv
);
424 snprintf(errormsg
, sizeof(errormsg
), "server-execvp: %s: %s\n",
425 argv
[0], strerror(errno
));
426 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
427 write_all(server_pipe
[1], errormsg
, strlen(errormsg
));
428 close(client_pipe
[1]);
429 close(server_pipe
[1]);
432 case -1: /* forkpty failed */
433 snprintf(errormsg
, sizeof(errormsg
), "server-forkpty: %s\n", strerror(errno
));
434 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
435 close(client_pipe
[1]);
436 close(server_pipe
[0]);
437 close(server_pipe
[1]);
440 default: /* parent = server process */
441 sa
.sa_handler
= server_sigterm_handler
;
442 sigaction(SIGTERM
, &sa
, NULL
);
443 sigaction(SIGINT
, &sa
, NULL
);
444 sa
.sa_handler
= server_sigusr1_handler
;
445 sigaction(SIGUSR1
, &sa
, NULL
);
446 sa
.sa_handler
= SIG_IGN
;
447 sigaction(SIGPIPE
, &sa
, NULL
);
448 sigaction(SIGHUP
, &sa
, NULL
);
451 int fd
= open("/dev/null", O_RDWR
);
453 dup2(fd
, STDIN_FILENO
);
454 dup2(fd
, STDOUT_FILENO
);
455 dup2(fd
, STDERR_FILENO
);
459 close(client_pipe
[1]);
460 close(server_pipe
[1]);
461 if (read_all(server_pipe
[0], errormsg
, sizeof(errormsg
)) > 0)
463 close(server_pipe
[0]);
468 case -1: /* fork failed */
469 snprintf(errormsg
, sizeof(errormsg
), "server-fork: %s\n", strerror(errno
));
470 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
471 close(client_pipe
[1]);
474 default: /* parent = intermediate process */
475 close(client_pipe
[1]);
480 case -1: /* fork failed */
481 close(client_pipe
[0]);
482 close(client_pipe
[1]);
484 default: /* parent = client process */
485 close(client_pipe
[1]);
487 wait(&status
); /* wait for first fork */
488 ssize_t len
= read_all(client_pipe
[0], errormsg
, sizeof(errormsg
));
490 write_all(STDERR_FILENO
, errormsg
, len
);
491 unlink(sockaddr
.sun_path
);
494 close(client_pipe
[0]);
499 static bool attach_session(const char *name
, const bool terminate
) {
500 if (server
.socket
> 0)
501 close(server
.socket
);
502 if ((server
.socket
= session_connect(name
)) == -1)
504 if (server_set_socket_non_blocking(server
.socket
) == -1)
509 sigemptyset(&sa
.sa_mask
);
510 sa
.sa_handler
= client_sigwinch_handler
;
511 sigaction(SIGWINCH
, &sa
, NULL
);
512 sa
.sa_handler
= SIG_IGN
;
513 sigaction(SIGPIPE
, &sa
, NULL
);
515 client_setup_terminal();
516 int status
= client_mainloop();
517 client_restore_terminal();
520 } else if (status
== -EIO
) {
521 info("exited due to I/O errors");
523 info("session terminated with exit status %d", status
);
531 static int session_filter(const struct dirent
*d
) {
532 return strstr(d
->d_name
, server
.host
) != NULL
;
535 static int session_comparator(const struct dirent
**a
, const struct dirent
**b
) {
537 if (stat((*a
)->d_name
, &sa
) != 0)
539 if (stat((*b
)->d_name
, &sb
) != 0)
541 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
544 static int list_session(void) {
545 if (!create_socket_dir(&sockaddr
))
547 if (chdir(sockaddr
.sun_path
) == -1)
549 struct dirent
**namelist
;
550 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
553 printf("Active sessions (on host %s)\n", server
.host
+1);
555 struct stat sb
; char buf
[255];
556 if (stat(namelist
[n
]->d_name
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
)) {
557 strftime(buf
, sizeof(buf
), "%a%t %F %T", localtime(&sb
.st_mtime
));
559 char *local
= strstr(namelist
[n
]->d_name
, server
.host
);
561 *local
= '\0'; /* truncate hostname if we are local */
562 if (!session_exists(namelist
[n
]->d_name
))
565 if (sb
.st_mode
& S_IXUSR
)
567 else if (sb
.st_mode
& S_IXGRP
)
569 printf("%c %s\t%s\n", status
, buf
, namelist
[n
]->d_name
);
577 int main(int argc
, char *argv
[]) {
580 char **cmd
= NULL
, action
= '\0';
582 char *default_cmd
[4] = { "/bin/sh", "-c", getenv("ABDUCO_CMD"), NULL
};
583 if (!default_cmd
[2]) {
584 default_cmd
[0] = ABDUCO_CMD
;
585 default_cmd
[1] = NULL
;
588 server
.name
= basename(argv
[0]);
589 gethostname(server
.host
+1, sizeof(server
.host
) - 1);
591 passthrough
= !isatty(STDIN_FILENO
);
593 while ((opt
= getopt(argc
, argv
, "aAclne:fpqrv")) != -1) {
604 if (optarg
[0] == '^' && optarg
[1])
605 optarg
[0] = CTRL(optarg
[1]);
606 KEY_DETACH
= optarg
[0];
618 client
.flags
|= CLIENT_READONLY
;
621 client
.flags
|= CLIENT_LOWPRIORITY
;
624 puts("abduco-"VERSION
" © 2013-2018 Marc André Tanner");
635 client
.flags
|= CLIENT_LOWPRIORITY
;
638 /* collect the session name if trailing args */
640 server
.session_name
= argv
[optind
];
642 /* if yet more trailing arguments, they must be the command */
643 if (optind
+ 1 < argc
)
644 cmd
= &argv
[optind
+ 1];
648 if (!action
&& !server
.session_name
)
649 exit(list_session());
650 if (!action
|| !server
.session_name
)
653 if (!passthrough
&& tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
654 server
.term
= orig_term
;
658 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &server
.winsize
) == -1) {
659 server
.winsize
.ws_col
= 80;
660 server
.winsize
.ws_row
= 25;
663 server
.read_pty
= (action
== 'n');
670 if (session_alive(server
.session_name
)) {
671 info("session exists and has not yet terminated");
674 if (session_exists(server
.session_name
))
675 attach_session(server
.session_name
, false);
677 if (!create_session(server
.session_name
, cmd
))
678 die("create-session");
682 if (!attach_session(server
.session_name
, true))
683 die("attach-session");
686 if (session_alive(server
.session_name
)) {
687 if (!attach_session(server
.session_name
, true))
688 die("attach-session");
689 } else if (!attach_session(server
.session_name
, !force
)) {