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];
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
);
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 sigaction(SIGTERM
, &sa
, NULL
);
255 sigaction(SIGINT
, &sa
, NULL
);
256 sa
.sa_handler
= SIG_IGN
;
257 sigaction(SIGPIPE
, &sa
, NULL
);
258 sigaction(SIGHUP
, &sa
, NULL
);
261 int fd
= open("/dev/null", O_RDWR
);
277 case -1: /* fork failed */
279 default: /* parent */
282 wait(&status
); /* wait for first fork */
283 if ((status
= read_all(pipefds
[0], errormsg
, sizeof(errormsg
))) > 0) {
284 write_all(STDERR_FILENO
, errormsg
, status
);
285 unlink(sockaddr
.sun_path
);
292 unlink(sockaddr
.sun_path
);
296 static bool attach_session(const char *name
) {
297 if (server
.socket
> 0)
298 close(server
.socket
);
299 if ((server
.socket
= create_socket(name
)) == -1)
301 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
302 if (connect(server
.socket
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
304 if (server_set_socket_non_blocking(server
.socket
) == -1)
309 sigemptyset(&sa
.sa_mask
);
310 sa
.sa_handler
= client_sigwinch_handler
;
311 sigaction(SIGWINCH
, &sa
, NULL
);
312 sa
.sa_handler
= SIG_IGN
;
313 sigaction(SIGPIPE
, &sa
, NULL
);
314 atexit(client_restore_terminal
);
316 cur_term
= orig_term
;
317 cur_term
.c_iflag
&= ~(IGNBRK
|BRKINT
|PARMRK
|ISTRIP
|INLCR
|IGNCR
|ICRNL
|IXON
|IXOFF
);
318 cur_term
.c_oflag
&= ~(OPOST
);
319 cur_term
.c_lflag
&= ~(ECHO
|ECHONL
|ICANON
|ISIG
|IEXTEN
);
320 cur_term
.c_cflag
&= ~(CSIZE
|PARENB
);
321 cur_term
.c_cflag
|= CS8
;
322 cur_term
.c_cc
[VLNEXT
] = _POSIX_VDISABLE
;
323 cur_term
.c_cc
[VMIN
] = 1;
324 cur_term
.c_cc
[VTIME
] = 0;
325 tcsetattr(0, TCSADRAIN
, &cur_term
);
327 client_clear_screen();
328 switch (client_mainloop()) {
333 info("exited due to I/O errors: %s", strerror(errno
));
340 static int list_session() {
341 if (create_socket_dir() == -1)
343 chdir(sockaddr
.sun_path
);
344 DIR *d
= opendir(sockaddr
.sun_path
);
347 puts("Active sessions");
349 while ((e
= readdir(d
))) {
350 if (e
->d_name
[0] != '.') {
351 struct stat sb
; char buf
[255];
352 if (stat(e
->d_name
, &sb
) == 0) {
353 strftime(buf
, sizeof(buf
), "%a%t %d.%m.%Y %T", localtime(&sb
.st_atime
));
354 printf(" %s\t%s\n", buf
, e
->d_name
);
362 int main(int argc
, char *argv
[]) {
363 char *session
= NULL
, **cmd
= NULL
, action
= '\0';
364 server
.name
= basename(argv
[0]);
366 exit(list_session());
367 for (int arg
= 1; arg
< argc
; arg
++) {
368 if (argv
[arg
][0] != '-') {
379 switch (argv
[arg
][1]) {
384 action
= argv
[arg
][1];
389 char *esc
= argv
[++arg
];
390 if (esc
[0] == '^' && esc
[1])
395 puts("abduco-"VERSION
" © 2013-2014 Marc André Tanner");
402 if (!action
|| !session
|| (action
!= 'a' && !cmd
))
405 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
406 server
.term
= orig_term
;
414 if (!create_session(session
, cmd
))
415 die("create-session");
420 if (!attach_session(session
)) {
425 die("attach-session");