2 * Copyright (c) 2013-2016 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]))
83 typedef struct Client Client
;
94 CLIENT_READONLY
= 1 << 0,
95 CLIENT_LOWPRIORITY
= 1 << 1,
107 struct winsize winsize
;
109 volatile sig_atomic_t running
;
111 const char *session_name
;
116 static Server server
= { .running
= true, .exit_status
= -1, .host
= "@localhost" };
117 static Client client
;
118 static struct termios orig_term
, cur_term
;
119 static bool has_term
, alternate_buffer
;
121 static struct sockaddr_un sockaddr
= {
122 .sun_family
= AF_UNIX
,
125 static bool set_socket_name(struct sockaddr_un
*sockaddr
, const char *name
);
126 static void die(const char *s
);
127 static void info(const char *str
, ...);
131 static inline size_t packet_header_size() {
132 return offsetof(Packet
, u
);
135 static size_t packet_size(Packet
*pkt
) {
136 return packet_header_size() + pkt
->len
;
139 static ssize_t
write_all(int fd
, const char *buf
, size_t len
) {
140 debug("write_all(%d)\n", len
);
143 ssize_t res
= write(fd
, buf
, len
);
145 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
157 static ssize_t
read_all(int fd
, char *buf
, size_t len
) {
158 debug("read_all(%d)\n", len
);
161 ssize_t res
= read(fd
, buf
, len
);
163 if (errno
== EWOULDBLOCK
)
165 if (errno
== EAGAIN
|| errno
== EINTR
)
177 static bool send_packet(int socket
, Packet
*pkt
) {
178 size_t size
= packet_size(pkt
);
179 if (size
> sizeof(*pkt
))
181 return write_all(socket
, (char *)pkt
, size
) == size
;
184 static bool recv_packet(int socket
, Packet
*pkt
) {
185 ssize_t len
= read_all(socket
, (char*)pkt
, packet_header_size());
186 if (len
<= 0 || len
!= packet_header_size())
188 if (pkt
->len
> sizeof(pkt
->u
.msg
)) {
193 len
= read_all(socket
, pkt
->u
.msg
, pkt
->len
);
194 if (len
<= 0 || len
!= pkt
->len
)
203 static void info(const char *str
, ...) {
207 fprintf(stderr
, "%s: %s: ", server
.name
, server
.session_name
);
208 vfprintf(stderr
, str
, ap
);
209 fprintf(stderr
, "\r\n");
215 static void die(const char *s
) {
220 static void usage(void) {
221 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-r] [-l] [-f] [-e detachkey] name command\n");
225 static bool xsnprintf(char *buf
, size_t size
, const char *fmt
, ...) {
230 int n
= vsnprintf(buf
, size
, fmt
, ap
);
235 errno
= ENAMETOOLONG
;
241 static int session_connect(const char *name
) {
244 if (!set_socket_name(&sockaddr
, name
) || (fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1)
246 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
247 if (connect(fd
, (struct sockaddr
*)&sockaddr
, socklen
) == -1) {
248 if (errno
== ECONNREFUSED
&& stat(sockaddr
.sun_path
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
))
249 unlink(sockaddr
.sun_path
);
256 static bool session_exists(const char *name
) {
257 int fd
= session_connect(name
);
263 static bool session_alive(const char *name
) {
265 return session_exists(name
) &&
266 stat(sockaddr
.sun_path
, &sb
) == 0 &&
267 S_ISSOCK(sb
.st_mode
) && (sb
.st_mode
& S_IXGRP
) == 0;
270 static bool create_socket_dir(struct sockaddr_un
*sockaddr
) {
271 sockaddr
->sun_path
[0] = '\0';
272 int socketfd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
276 size_t maxlen
= sizeof(sockaddr
->sun_path
);
277 uid_t uid
= getuid();
278 struct passwd
*pw
= getpwuid(uid
);
280 for (unsigned int i
= 0; i
< countof(socket_dirs
); i
++) {
282 struct Dir
*dir
= &socket_dirs
[i
];
285 dir
->path
= getenv(dir
->env
);
286 ishome
= !strcmp(dir
->env
, "HOME");
287 if (ishome
&& (!dir
->path
|| !dir
->path
[0]) && pw
)
288 dir
->path
= pw
->pw_dir
;
290 if (!dir
->path
|| !dir
->path
[0])
292 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s%s/", dir
->path
, ishome
? "." : "", server
.name
))
294 mode_t mask
= umask(0);
295 int r
= mkdir(sockaddr
->sun_path
, dir
->personal
? S_IRWXU
: S_IRWXU
|S_IRWXG
|S_IRWXO
|S_ISVTX
);
297 if (r
!= 0 && errno
!= EEXIST
)
299 if (lstat(sockaddr
->sun_path
, &sb
) != 0)
301 if (!S_ISDIR(sb
.st_mode
)) {
306 size_t dirlen
= strlen(sockaddr
->sun_path
);
307 if (!dir
->personal
) {
308 /* create subdirectory only accessible to user */
309 if (pw
&& !xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, "%s/", pw
->pw_name
))
311 if (!pw
&& !xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, "%d/", uid
))
313 if (mkdir(sockaddr
->sun_path
, S_IRWXU
) != 0 && errno
!= EEXIST
)
315 if (lstat(sockaddr
->sun_path
, &sb
) != 0)
317 if (!S_ISDIR(sb
.st_mode
)) {
321 dirlen
= strlen(sockaddr
->sun_path
);
324 if (sb
.st_uid
!= uid
|| sb
.st_mode
& (S_IRWXG
|S_IRWXO
)) {
329 if (!xsnprintf(sockaddr
->sun_path
+dirlen
, maxlen
-dirlen
, ".abduco-%d", getpid()))
332 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
->sun_path
) + 1;
333 if (bind(socketfd
, (struct sockaddr
*)sockaddr
, socklen
) == -1)
335 unlink(sockaddr
->sun_path
);
337 sockaddr
->sun_path
[dirlen
] = '\0';
345 static bool set_socket_name(struct sockaddr_un
*sockaddr
, const char *name
) {
346 size_t maxlen
= sizeof(sockaddr
->sun_path
);
347 if (name
[0] == '/') {
348 if (strlen(name
) >= maxlen
) {
349 errno
= ENAMETOOLONG
;
352 strncpy(sockaddr
->sun_path
, name
, maxlen
);
353 } else if (name
[0] == '.' && (name
[1] == '.' || name
[1] == '/')) {
354 char buf
[maxlen
], *cwd
= getcwd(buf
, sizeof buf
);
357 if (!xsnprintf(sockaddr
->sun_path
, maxlen
, "%s/%s", cwd
, name
))
360 if (!create_socket_dir(sockaddr
))
362 if (strlen(sockaddr
->sun_path
) + strlen(name
) + strlen(server
.host
) >= maxlen
) {
363 errno
= ENAMETOOLONG
;
366 strncat(sockaddr
->sun_path
, name
, maxlen
- strlen(sockaddr
->sun_path
) - 1);
367 strncat(sockaddr
->sun_path
, server
.host
, maxlen
- strlen(sockaddr
->sun_path
) - 1);
372 static bool create_session(const char *name
, char * const argv
[]) {
373 /* this uses the well known double fork strategy as described in section 1.7 of
375 * http://www.faqs.org/faqs/unix-faq/programmer/faq/
377 * pipes are used for synchronization and error reporting i.e. the child sets
378 * the close on exec flag before calling execvp(3) the parent blocks on a read(2)
379 * in case of failure the error message is written to the pipe, success is
380 * indicated by EOF on the pipe.
382 int client_pipe
[2], server_pipe
[2];
387 if (session_exists(name
)) {
392 if (pipe(client_pipe
) == -1)
394 if ((server
.socket
= server_create_socket(name
)) == -1)
397 switch ((pid
= fork())) {
398 case 0: /* child process */
400 close(client_pipe
[0]);
401 switch ((pid
= fork())) {
402 case 0: /* child process */
403 if (pipe(server_pipe
) == -1) {
404 snprintf(errormsg
, sizeof(errormsg
), "server-pipe: %s\n", strerror(errno
));
405 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
406 close(client_pipe
[1]);
410 sigemptyset(&sa
.sa_mask
);
411 sa
.sa_handler
= server_pty_died_handler
;
412 sigaction(SIGCHLD
, &sa
, NULL
);
413 switch (server
.pid
= forkpty(&server
.pty
, NULL
, has_term
? &server
.term
: NULL
, &server
.winsize
)) {
414 case 0: /* child = user application process */
415 close(server
.socket
);
416 close(server_pipe
[0]);
417 if (fcntl(client_pipe
[1], F_SETFD
, FD_CLOEXEC
) == 0 &&
418 fcntl(server_pipe
[1], F_SETFD
, FD_CLOEXEC
) == 0)
419 execvp(argv
[0], argv
);
420 snprintf(errormsg
, sizeof(errormsg
), "server-execvp: %s: %s\n",
421 argv
[0], strerror(errno
));
422 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
423 write_all(server_pipe
[1], errormsg
, strlen(errormsg
));
424 close(client_pipe
[1]);
425 close(server_pipe
[1]);
428 case -1: /* forkpty failed */
429 snprintf(errormsg
, sizeof(errormsg
), "server-forkpty: %s\n", strerror(errno
));
430 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
431 close(client_pipe
[1]);
432 close(server_pipe
[0]);
433 close(server_pipe
[1]);
436 default: /* parent = server process */
437 sa
.sa_handler
= server_sigterm_handler
;
438 sigaction(SIGTERM
, &sa
, NULL
);
439 sigaction(SIGINT
, &sa
, NULL
);
440 sa
.sa_handler
= server_sigusr1_handler
;
441 sigaction(SIGUSR1
, &sa
, NULL
);
442 sa
.sa_handler
= SIG_IGN
;
443 sigaction(SIGPIPE
, &sa
, NULL
);
444 sigaction(SIGHUP
, &sa
, NULL
);
447 int fd
= open("/dev/null", O_RDWR
);
455 close(client_pipe
[1]);
456 close(server_pipe
[1]);
457 if (read_all(server_pipe
[0], errormsg
, sizeof(errormsg
)) > 0)
459 close(server_pipe
[0]);
464 case -1: /* fork failed */
465 snprintf(errormsg
, sizeof(errormsg
), "server-fork: %s\n", strerror(errno
));
466 write_all(client_pipe
[1], errormsg
, strlen(errormsg
));
467 close(client_pipe
[1]);
470 default: /* parent = intermediate process */
471 close(client_pipe
[1]);
476 case -1: /* fork failed */
477 close(client_pipe
[0]);
478 close(client_pipe
[1]);
480 default: /* parent = client process */
481 close(client_pipe
[1]);
483 wait(&status
); /* wait for first fork */
484 ssize_t len
= read_all(client_pipe
[0], errormsg
, sizeof(errormsg
));
486 write_all(STDERR_FILENO
, errormsg
, len
);
487 unlink(sockaddr
.sun_path
);
490 close(client_pipe
[0]);
495 static bool attach_session(const char *name
, const bool terminate
) {
496 if (server
.socket
> 0)
497 close(server
.socket
);
498 if ((server
.socket
= session_connect(name
)) == -1)
500 if (server_set_socket_non_blocking(server
.socket
) == -1)
505 sigemptyset(&sa
.sa_mask
);
506 sa
.sa_handler
= client_sigwinch_handler
;
507 sigaction(SIGWINCH
, &sa
, NULL
);
508 sa
.sa_handler
= SIG_IGN
;
509 sigaction(SIGPIPE
, &sa
, NULL
);
511 client_setup_terminal();
512 int status
= client_mainloop();
513 client_restore_terminal();
516 } else if (status
== -EIO
) {
517 info("exited due to I/O errors");
519 info("session terminated with exit status %d", status
);
527 static int session_filter(const struct dirent
*d
) {
528 return strstr(d
->d_name
, server
.host
) != NULL
;
531 static int session_comparator(const struct dirent
**a
, const struct dirent
**b
) {
533 if (stat((*a
)->d_name
, &sa
) != 0)
535 if (stat((*b
)->d_name
, &sb
) != 0)
537 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
540 static int list_session(void) {
541 if (!create_socket_dir(&sockaddr
))
543 chdir(sockaddr
.sun_path
);
544 struct dirent
**namelist
;
545 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
548 printf("Active sessions (on host %s)\n", server
.host
+1);
550 struct stat sb
; char buf
[255];
551 if (stat(namelist
[n
]->d_name
, &sb
) == 0 && S_ISSOCK(sb
.st_mode
)) {
552 strftime(buf
, sizeof(buf
), "%a%t %F %T", localtime(&sb
.st_mtime
));
554 char *local
= strstr(namelist
[n
]->d_name
, server
.host
);
556 *local
= '\0'; /* truncate hostname if we are local */
557 if (!session_exists(namelist
[n
]->d_name
))
560 if (sb
.st_mode
& S_IXUSR
)
562 else if (sb
.st_mode
& S_IXGRP
)
564 printf("%c %s\t%s\n", status
, buf
, namelist
[n
]->d_name
);
572 int main(int argc
, char *argv
[]) {
575 char **cmd
= NULL
, action
= '\0';
577 char *default_cmd
[4] = { "/bin/sh", "-c", getenv("ABDUCO_CMD"), NULL
};
578 if (!default_cmd
[2]) {
579 default_cmd
[0] = ABDUCO_CMD
;
580 default_cmd
[1] = NULL
;
583 server
.name
= basename(argv
[0]);
584 gethostname(server
.host
+1, sizeof(server
.host
) - 1);
586 while ((opt
= getopt(argc
, argv
, "aAclne:frv")) != -1) {
597 if (optarg
[0] == '^' && optarg
[1])
598 optarg
[0] = CTRL(optarg
[1]);
599 KEY_DETACH
= optarg
[0];
605 client
.flags
|= CLIENT_READONLY
;
608 client
.flags
|= CLIENT_LOWPRIORITY
;
611 puts("abduco-"VERSION
" © 2013-2015 Marc André Tanner");
618 /* collect the session name if trailing args */
620 server
.session_name
= argv
[optind
];
622 /* if yet more trailing arguments, they must be the command */
623 if (optind
+ 1 < argc
)
624 cmd
= &argv
[optind
+ 1];
628 if (!action
&& !server
.session_name
)
629 exit(list_session());
630 if (!action
|| !server
.session_name
)
633 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
634 server
.term
= orig_term
;
638 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &server
.winsize
) == -1) {
639 server
.winsize
.ws_col
= 80;
640 server
.winsize
.ws_row
= 25;
643 server
.read_pty
= (action
== 'n');
650 if (session_alive(server
.session_name
)) {
651 info("session exists and has not yet terminated");
654 if (session_exists(server
.session_name
))
655 attach_session(server
.session_name
, false);
657 if (!create_session(server
.session_name
, cmd
))
658 die("create-session");
662 if (!attach_session(server
.session_name
, true))
663 die("attach-session");
666 if (session_alive(server
.session_name
)) {
667 if (!attach_session(server
.session_name
, true))
668 die("attach-session");
669 } else if (!attach_session(server
.session_name
, !force
)) {