One of `ayrton`'s features is the remote execution of code and programs via `ssh`. For this I
initially used `paramiko`, which is a complete reimplementation of the `ssh` protocol
in pure Python. It manages to connect, authenticate and create channels and port
forwardings with any recent `ssh` server, and is quite easy:

```python
import paramiko

c= paramiko.SSHClient ()
c.connect (...)

# get_pty=True so we emulate a tty and programs like vi and mc work
i, o, e= c.execute_command (command, get_pty=True)
```

So far so good, but the interface is those 3 objects, `i`, `o` and `e`, that
represent the remote command's `stdin`, `stdout` and `stderr`. If one wants to fully
implement a client, one needs to copy everything from the local process' standard
streams to those.

For this, the most brute force approach is to create a thread for each pair of
streams[^1]:

```python
class CopyThread (Thread):
    def __init__ (self, src, dst):
        super ().__init__ ()
        self.src= src
        self.dst= dst

    def run (self):
        while True:
            data= self.src.read (1024)
            if len (data)==0:
                break
            else:
                self.dst.write (data)

        self.close ()

    def close (self):
        self.src.close ()
        self.dst.close ()
```

This for some reason does not work out of the bat. When I implemented it in `ayrton`,
what I got was that I didn't get anything from `stdout` or `stderr` until the remote
code was finished. I tiptoed a little around the problem, but at the end I took cue
from one of
[`paramiko`'s examples](https://github.com/paramiko/paramiko/blob/master/demos/interactive.py)
and implemented a single copy loop with `select()`:

```python
class InteractiveThread (Thread):
    def __init__ (self, pairs):
        super ().__init__ ()
        self.pairs= pairs
        self.copy_to= dict (pairs)
        self.finished= os.pipe ()

    def run (self):
        while True:
            wait_for= list (self.copy_to.keys ())
            wait_for.append (self.finished[0])
            r, w, e= select (wait_for, [], [])

            if self.finished[0] in r:
                self.self.finished[0].close ()
                break

            for i in r:
                o= self.copy_to[i]
                data= i.read (1024)
                if len (data)==0:
                    # do not try to read any more from this file
                    del self.copy_to[i]
                else:
                    o.write (data)

        self.close ()


    def close (self):
        for k, v in self.pairs:
            for f in (k, v):
                 f.close ()

        self.finished[1].close ()


t= InteractiveThread (( (0, i), (o, 1), (e, 2) ))
t.start ()
[...]
t.close ()
```

The extra pipe, `finished`, is there to make sure we don't wait forever for `stdin`
to finish.

This completely solves the problem of handling the streams, but that's not the only
problem. The next step is to handle the fact that when we do some input via `stdin`,
we see it twice. This is because both the local and the remote terminals are echoing
what we type, so we just need to disable the local echoing. In fact, `ssh` does
[quite more than that](http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/sshtty.c?rev=1.14&content-type=text/x-cvsweb-markup):

```python
class InteractiveThread (Thread):
    def __init__ (self, pairs):
        super ().__init__ ()

        [...]

        self.orig_terminfo= tcgetattr (pairs[0][0])
        # input, output, control, local, speeds, special chars
        iflag, oflag, cflag, lflag, ispeed, ospeed, cc= self.orig_terminfo

        # turn on:
        # Ignore framing errors and parity errors
        iflag|= IGNPAR
        # turn off:
        # Strip off eighth bit
        # Translate NL to CR on input
        # Ignore carriage return on input
        # XON/XOFF flow control on output
        # (XSI) Typing any character will restart stopped output. NOTE: not needed?
        # XON/XOFF flow control on input
        iflag&= ~( ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF )

        # turn off:
        # When any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal
        # canonical mode
        # Echo input characters (finally)
        # NOTE: why these three? they only work with ICANON and we're disabling it
        # If ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word
        # If ICANON is also set, the KILL character erases the current line
        # If ICANON is also set, echo the NL character even if ECHO is not set
        # implementation-defined input processing
        lflag&= ~( ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL | IEXTEN )

        # turn off:
        # implementation-defined output processing
        oflag&= ~OPOST

        # NOTE: whatever
        # Minimum number of characters for noncanonical read
        cc[VMIN]= 1
        # Timeout in deciseconds for noncanonical read
        cc[VTIME]= 0

        tcsetattr(self.pairs[0][0], TCSADRAIN, [ iflag, oflag, cflag, lflag,
                                                 ispeed, ospeed, cc ])


    def close (self):
        # reset term settings
        tcsetattr (self.pairs[0][0], TCSADRAIN, self.orig_terminfo)

        [...]
```

I won't pretend I understand all of that. Checking the file's history, I'm
tempted to bet that neither the `openssh` developers do. I would even bet that it
was taken from a `telnet` or `rsh` implementation or something. This is the kind
of things I meant when I wrote
[my previous post](http://www.grulic.org.ar/~mdione/glob/posts/can-I-haz-libfoo/)
about implementing these complex pieces of software as a library with a public API
and a shallow frontend in the form of a program. At least the guys from `openssh`
say that
[they're going in that direction](http://lists.mindrot.org/pipermail/openssh-unix-dev/2015-December/034580.html).
That's wonderful news.

Almost there. The last stone in the way is the terminal emulation. As is,
`SSHClient.execute_command()` tells the other end that we're running in a 80x25
`VT100` terminal. Unluckily the API does not allow us to set it by ourselves, but
`SSHClient.execute_command()` is
[a very simple method](https://github.com/paramiko/paramiko/blob/master/paramiko/client.py#L382)
that we can rewrite:

```python
channel= c.get_transport ().open_session ()
term= shutil.get_terminal_size ()
channel.get_pty (os.environ['TERM'], term.columns, term.lines)
```

Reacting to `SIGWINCH` and changing the terminal's size is left as an exercise for
the reader :)

[^1]: In fact this might seem slightly wasteful, as data has to be read into user space
      and then pushed down back to the kernel. The problem is that `os.sendfile()` only
      works if `src` is a kernel object that supports `mmap()`, which sockets don't, and
      even when `splice()` is available in a
      [3dr party module](https://pypi.python.org/pypi/butter/), one of the parameters must
      be a pipe. There is at least one
      [huge thread](https://marc.info/?t=137891946200004&r=7&w=4) spread over 4 or 5 kernel
      mailing lists discussing widening the applicability of `splice()`, but to be honest,
      I hadn't finished reading it.

