2 * Copyright (c) 2013-2014 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/ioctl.h>
34 #include <sys/types.h>
36 #include <sys/socket.h>
38 #if defined(__linux__) || defined(__CYGWIN__)
40 #elif defined(__FreeBSD__)
42 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
46 #if defined CTRL && defined _AIX
50 #define CTRL(k) ((k) & 0x1F)
56 # include "forkpty-aix.c"
58 #define CLIENT_TIMEOUT 100
59 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
69 /* packet sent from client to server */
74 char msg
[sizeof(struct winsize
)];
80 /* packet sent from server to all clients */
96 typedef struct Client Client
;
98 ServerPacketState output
; /* display output as received from server */
99 ClientPacketState input
; /* input as sent to the server */
107 time_t last_activity
;
116 ServerPacket pty_output
;
117 ClientPacketState pty_input
;
118 ClientPacket queue
[10];
119 unsigned int queue_count
;
120 unsigned int queue_insert
;
121 unsigned int queue_remove
;
126 volatile sig_atomic_t running
;
130 static Server server
= { .running
= true, .exit_status
= -1 };
131 static struct termios orig_term
, cur_term
;
134 static struct sockaddr_un sockaddr
= {
135 .sun_family
= AF_UNIX
,
138 static int create_socket(const char *name
);
139 static void die(const char *s
);
140 static void info(const char *str
, ...);
142 static bool is_client_packet_complete(ClientPacketState
*pkt
) {
143 return pkt
->off
== sizeof pkt
->pkt
;
146 static bool is_server_packet_complete(ServerPacketState
*pkt
) {
147 return pkt
->pkt
&& pkt
->off
== pkt
->pkt
->len
;
150 static bool is_server_packet_nonempty(ServerPacketState
*pkt
) {
151 return pkt
->pkt
&& pkt
->pkt
->len
> 0;
158 static void info(const char *str
, ...) {
161 fprintf(stdout
, "\e[999H\r\n");
163 fprintf(stdout
, "%s: ", server
.name
);
164 vfprintf(stdout
, str
, ap
);
165 fprintf(stdout
, "\r\n");
171 static void die(const char *s
) {
176 static void usage() {
177 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-e detachkey] name command\n");
181 static int create_socket_dir() {
182 size_t maxlen
= sizeof(sockaddr
.sun_path
);
183 char *dir
= getenv("HOME");
186 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/", dir
, server
.name
);
187 if (len
< 0 || (size_t)len
>= maxlen
)
189 if (mkdir(sockaddr
.sun_path
, 0750) == -1 && errno
!= EEXIST
)
194 static int create_socket(const char *name
) {
195 size_t maxlen
= sizeof(sockaddr
.sun_path
);
196 if (name
[0] == '.' || name
[0] == '/') {
197 strncpy(sockaddr
.sun_path
, name
, maxlen
);
198 if (sockaddr
.sun_path
[maxlen
-1])
201 int len
= create_socket_dir(), rem
= strlen(name
);
202 if (len
== -1 || maxlen
- len
- rem
<= 0)
204 strncat(sockaddr
.sun_path
, name
, maxlen
- len
- 1);
206 return socket(AF_LOCAL
, SOCK_STREAM
, 0);
209 static bool create_session(const char *name
, char * const argv
[]) {
211 if (pipe(pipefds
) == -1)
213 if ((server
.socket
= create_socket(name
)) == -1)
215 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
216 mode_t mode
= S_IRUSR
|S_IWUSR
;
217 fchmod(server
.socket
, mode
);
218 if (bind(server
.socket
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
220 if (listen(server
.socket
, 5) == -1)
222 if (fchmod(server
.socket
, mode
) == -1 && chmod(sockaddr
.sun_path
, mode
) == -1)
229 switch ((pid
= fork())) {
230 case 0: /* child process */
233 switch ((pid
= fork())) {
234 case 0: /* child process */
236 sigemptyset(&sa
.sa_mask
);
237 sa
.sa_handler
= server_pty_died_handler
;
238 sigaction(SIGCHLD
, &sa
, NULL
);
239 switch (server
.pid
= forkpty(&server
.pty
, NULL
, has_term
? &server
.term
: NULL
, NULL
)) {
240 case 0: /* child process */
241 fcntl(pipefds
[1], F_SETFD
, FD_CLOEXEC
);
242 close(server
.socket
);
243 execvp(argv
[0], argv
);
244 snprintf(errormsg
, sizeof(errormsg
), "server-execvp: %s\n", strerror(errno
));
245 write_all(pipefds
[1], errormsg
, strlen(errormsg
));
250 die("server-forkpty");
253 /* SIGTTIN, SIGTTU */
254 sa
.sa_handler
= server_sigterm_handler
;
255 sigaction(SIGTERM
, &sa
, NULL
);
256 sigaction(SIGINT
, &sa
, NULL
);
257 sa
.sa_handler
= SIG_IGN
;
258 sigaction(SIGPIPE
, &sa
, NULL
);
259 sigaction(SIGHUP
, &sa
, NULL
);
262 int fd
= open("/dev/null", O_RDWR
);
278 case -1: /* fork failed */
280 default: /* parent */
283 wait(&status
); /* wait for first fork */
284 if ((status
= read_all(pipefds
[0], errormsg
, sizeof(errormsg
))) > 0) {
285 write_all(STDERR_FILENO
, errormsg
, status
);
286 unlink(sockaddr
.sun_path
);
293 unlink(sockaddr
.sun_path
);
297 static bool attach_session(const char *name
) {
298 if (server
.socket
> 0)
299 close(server
.socket
);
300 if ((server
.socket
= create_socket(name
)) == -1)
302 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
303 if (connect(server
.socket
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
305 if (server_set_socket_non_blocking(server
.socket
) == -1)
310 sigemptyset(&sa
.sa_mask
);
311 sa
.sa_handler
= client_sigwinch_handler
;
312 sigaction(SIGWINCH
, &sa
, NULL
);
313 sa
.sa_handler
= SIG_IGN
;
314 sigaction(SIGPIPE
, &sa
, NULL
);
315 atexit(client_restore_terminal
);
317 cur_term
= orig_term
;
318 cur_term
.c_iflag
&= ~(IGNBRK
|BRKINT
|PARMRK
|ISTRIP
|INLCR
|IGNCR
|ICRNL
|IXON
|IXOFF
);
319 cur_term
.c_oflag
&= ~(OPOST
);
320 cur_term
.c_lflag
&= ~(ECHO
|ECHONL
|ICANON
|ISIG
|IEXTEN
);
321 cur_term
.c_cflag
&= ~(CSIZE
|PARENB
);
322 cur_term
.c_cflag
|= CS8
;
323 cur_term
.c_cc
[VLNEXT
] = _POSIX_VDISABLE
;
324 cur_term
.c_cc
[VMIN
] = 1;
325 cur_term
.c_cc
[VTIME
] = 0;
326 tcsetattr(0, TCSADRAIN
, &cur_term
);
328 client_clear_screen();
329 switch (client_mainloop()) {
334 info("exited due to I/O errors: %s", strerror(errno
));
341 static int session_filter(const struct dirent
*d
) {
342 return d
->d_name
[0] != '.';
345 static int session_comparator(const struct dirent
**a
, const struct dirent
**b
) {
347 if (stat((*a
)->d_name
, &sa
) != 0)
349 if (stat((*b
)->d_name
, &sb
) != 0)
351 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
354 static int list_session() {
355 if (create_socket_dir() == -1)
357 chdir(sockaddr
.sun_path
);
358 struct dirent
**namelist
;
359 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
362 puts("Active sessions");
364 struct stat sb
; char buf
[255];
365 if (stat(namelist
[n
]->d_name
, &sb
) == 0) {
366 strftime(buf
, sizeof(buf
), "%a%t %d.%m.%Y %T", localtime(&sb
.st_atime
));
367 printf(" %s\t%s\n", buf
, namelist
[n
]->d_name
);
375 int main(int argc
, char *argv
[]) {
376 char *session
= NULL
, **cmd
= NULL
, action
= '\0';
377 server
.name
= basename(argv
[0]);
379 exit(list_session());
380 for (int arg
= 1; arg
< argc
; arg
++) {
381 if (argv
[arg
][0] != '-') {
392 switch (argv
[arg
][1]) {
397 action
= argv
[arg
][1];
402 char *esc
= argv
[++arg
];
403 if (esc
[0] == '^' && esc
[1])
408 puts("abduco-"VERSION
" © 2013-2014 Marc André Tanner");
415 if (!action
|| !session
|| (action
!= 'a' && !cmd
))
418 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
419 server
.term
= orig_term
;
427 if (!create_session(session
, cmd
))
428 die("create-session");
433 if (!attach_session(session
)) {
438 die("attach-session");