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 int session_connect(const char *name
) {
242 if (!set_socket_name(&sockaddr
, name
) || (fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1)
244 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
245 if (connect(fd
, (struct sockaddr
*)&sockaddr
, socklen
) == -1) {
246 if (errno
== ECONNREFUSED
&& stat(sockaddr
.sun_path
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
))
247 unlink(sockaddr
.sun_path
);
254 static bool session_exists(const char *name
) {
255 int fd
= session_connect(name
);
261 static bool session_alive(const char *name
) {
263 return session_exists(name
) &&
264 stat(sockaddr
.sun_path
, &sb
) == 0 &&
265 S_ISSOCK(sb
.st_mode
) && (sb
.st_mode
& S_IXGRP
) == 0;
268 static bool create_socket_dir(struct sockaddr_un
*sockaddr
) {
269 sockaddr
->sun_path
[0] = '\0';
270 int socketfd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
274 size_t maxlen
= sizeof(sockaddr
->sun_path
);
275 uid_t uid
= getuid();
276 struct passwd
*pw
= getpwuid(uid
);
277 char *home
= getenv("HOME");
278 if ((!home
|| !home
[0]) && pw
)
283 bool personal
; /* whether it is a per user directory */
286 { getenv("TMPDIR"), false },
290 for (unsigned int i
= 0; i
< countof(dirs
); i
++) {
292 char *dir
= dirs
[i
].dir
;
293 bool ispersonal
= dirs
[i
].personal
;
296 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s%s/", dir
, dir
== home
? "." : "", server
.name
))
298 mode_t mask
= umask(0);
299 int r
= mkdir(sockaddr
->sun_path
, ispersonal
? 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
);
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
);
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 chdir(sockaddr
.sun_path
);
548 struct dirent
**namelist
;
549 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
552 printf("Active sessions (on host %s)\n", server
.host
+1);
554 struct stat sb
; char buf
[255];
555 if (stat(namelist
[n
]->d_name
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
)) {
556 strftime(buf
, sizeof(buf
), "%a%t %F %T", localtime(&sb
.st_atime
));
558 char *local
= strstr(namelist
[n
]->d_name
, server
.host
);
560 *local
= '\0'; /* truncate hostname if we are local */
561 if (!session_exists(namelist
[n
]->d_name
))
564 if (sb
.st_mode
& S_IXUSR
)
566 else if (sb
.st_mode
& S_IXGRP
)
568 printf("%c %s\t%s\n", status
, buf
, namelist
[n
]->d_name
);
576 int main(int argc
, char *argv
[]) {
579 char **cmd
= NULL
, action
= '\0';
580 server
.name
= basename(argv
[0]);
581 gethostname(server
.host
+1, sizeof(server
.host
) - 1);
583 exit(list_session());
585 while ((opt
= getopt(argc
, argv
, "aAcne:frv")) != -1) {
596 if (optarg
[0] == '^' && optarg
[1])
597 optarg
[0] = CTRL(optarg
[1]);
598 KEY_DETACH
= optarg
[0];
604 client
.readonly
= true;
607 puts("abduco-"VERSION
" © 2013-2015 Marc André Tanner");
614 /* collect the session name if trailing args */
616 server
.session_name
= argv
[optind
];
618 /* if yet more trailing arguments, they must be the command */
619 if (optind
+ 1 < argc
)
620 cmd
= &argv
[optind
+ 1];
623 cmd
= (char*[]){ getenv("ABDUCO_CMD"), NULL
};
628 if (!action
|| !server
.session_name
)
631 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
632 server
.term
= orig_term
;
636 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &server
.winsize
) == -1) {
637 server
.winsize
.ws_col
= 80;
638 server
.winsize
.ws_row
= 25;
641 server
.read_pty
= (action
== 'n');
648 if (session_alive(server
.session_name
)) {
649 info("session exists and has not yet terminated");
652 if (session_exists(server
.session_name
))
653 attach_session(server
.session_name
, false);
655 if (!create_session(server
.session_name
, cmd
))
656 die("create-session");
660 if (!attach_session(server
.session_name
, true))
661 die("attach-session");
664 if (session_alive(server
.session_name
)) {
665 if (!attach_session(server
.session_name
, true))
666 die("attach-session");
667 } else if (!attach_session(server
.session_name
, !force
)) {