From 674cf3d56349bed44380db2cb72529f797d7ee44 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Mon, 22 Sep 2014 17:59:28 -0400 Subject: move some things around --- termcast_client/__init__.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ termcast_client/pity.py | 94 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 termcast_client/__init__.py create mode 100644 termcast_client/pity.py (limited to 'termcast_client') diff --git a/termcast_client/__init__.py b/termcast_client/__init__.py new file mode 100644 index 0000000..64e6c2a --- /dev/null +++ b/termcast_client/__init__.py @@ -0,0 +1,97 @@ +import argparse +import json +import os +import pity +import shutil +import signal +import socket +import sys + +class Client(object): + def __init__(self, host, port, username, password): + self.host = host + self.port = port + self.username = username + self.password = password + + def run(self, argv): + sock = socket.socket() + sock.connect((self.host, self.port)) + sock.send(self._build_connection_string()) + self.winch_set = False + pity.spawn( + argv, + lambda fd: self._master_read(fd, sock), + handle_window_size=True + ) + + def _master_read(self, fd, sock): + if not self.winch_set: + prev_handler = signal.getsignal(signal.SIGWINCH) + signal.signal( + signal.SIGWINCH, + lambda signum, frame: self._winch( + sock, + lambda: prev_handler(signum, frame) + ) + ) + self.winch_set = True + + data = os.read(fd, 1024) + sock.send(data) + return data + + def _winch(self, sock, prev_handler): + prev_handler() + # XXX a bit racy - should try to avoid splitting existing escape + # sequences + sock.send(self._build_winsize_metadata_string()) + + def _build_connection_string(self): + auth = ( + b'hello ' + + self.username.encode('utf-8') + + b' ' + + self.password.encode('utf-8') + + b'\n' + ) + metadata = self._build_connection_metadata_string() + return auth + metadata + + def _build_connection_metadata_string(self): + # for now + return self._build_winsize_metadata_string() + + def _build_winsize_metadata_string(self): + size = shutil.get_terminal_size() + return self._build_metadata_string({ + "geometry": [ size.columns, size.lines ], + }) + + def _build_metadata_string(self, data): + return b'\033]499;' + json.dumps(data).encode('utf-8') + b'\007' + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--host', default="noway.ratry.ru") + parser.add_argument('--port', type=int, default=31337) + parser.add_argument('--username', default=os.getenv("USER")) + parser.add_argument('--password', default="asdf") + parser.add_argument( + 'command', + nargs=argparse.REMAINDER, + ) + + args = parser.parse_args(sys.argv[1:]) + + client = Client( + host=args.host, + port=args.port, + username=args.username, + password=args.password, + ) + + command = args.command + if len(args.command) < 1: + command = os.getenv("SHELL", default="/bin/sh") + client.run(command) diff --git a/termcast_client/pity.py b/termcast_client/pity.py new file mode 100644 index 0000000..26a9e49 --- /dev/null +++ b/termcast_client/pity.py @@ -0,0 +1,94 @@ +import fcntl +import os +import pty +import signal +import termios +import tty + +CHILD = pty.CHILD +STDIN_FILENO = pty.STDIN_FILENO +STDOUT_FILENO = pty.STDOUT_FILENO +STDERR_FILENO = pty.STDERR_FILENO + +def fork(handle_window_size=False): + # copied from pty.py, with modifications + master_fd, slave_fd = openpty() + slave_name = os.ttyname(slave_fd) + pid = os.fork() + if pid == CHILD: + # Establish a new session. + os.setsid() + os.close(master_fd) + + if handle_window_size: + clone_window_size_from(slave_name, STDIN_FILENO) + + # Slave becomes stdin/stdout/stderr of child. + os.dup2(slave_fd, STDIN_FILENO) + os.dup2(slave_fd, STDOUT_FILENO) + os.dup2(slave_fd, STDERR_FILENO) + if (slave_fd > STDERR_FILENO): + os.close (slave_fd) + + # Explicitly open the tty to make it become a controlling tty. + tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR) + os.close(tmp_fd) + else: + os.close(slave_fd) + + # Parent and child process. + return pid, master_fd, slave_name + + +def openpty(): + return pty.openpty() + +def spawn(argv, master_read=pty._read, stdin_read=pty._read, handle_window_size=False): + # copied from pty.py, with modifications + # note that it references a few private functions - would be nice to not + # do that, but you know + if type(argv) == type(''): + argv = (argv,) + pid, master_fd, slave_name = fork(handle_window_size) + if pid == CHILD: + os.execlp(argv[0], *argv) + try: + mode = tty.tcgetattr(STDIN_FILENO) + tty.setraw(STDIN_FILENO) + restore = 1 + except tty.error: # This is the same as termios.error + restore = 0 + + if handle_window_size: + signal.signal( + signal.SIGWINCH, + lambda signum, frame: _winch(slave_name, pid) + ) + + while True: + try: + pty._copy(master_fd, master_read, stdin_read) + except InterruptedError: + continue + except OSError: + if restore: + tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) + break + + os.close(master_fd) + return os.waitpid(pid, 0)[1] + +def clone_window_size_from(slave_name, from_fd): + slave_fd = os.open(slave_name, os.O_RDWR) + try: + fcntl.ioctl( + slave_fd, + termios.TIOCSWINSZ, + fcntl.ioctl(from_fd, termios.TIOCGWINSZ, " " * 1024) + ) + finally: + os.close(slave_fd) + +def _winch(slave_name, child_pid): + clone_window_size_from(slave_name, STDIN_FILENO) + os.kill(child_pid, signal.SIGWINCH) -- cgit v1.2.3-54-g00ecf