Search code examples
pythonwindowspipepipeline

Pipe ssh session into and out of python


The company I work for uses an archaic information system (Copyright 1991-2001). The system is a Centos machine running an ssh server. There's no access to the back-end or it's data in any way. All data needs to be retrieved through text reports, or input with manual keystrokes. Here's an example of the view you get when you login.

I'm trying to write a python script that will simulate keystrokes to run reports and do trivial tasks. I've already successfully done this with a .cmd file on windows that connects and simulates keystrokes. The problem is that there are some processes that have unpredictable branches (A message sometimes pops up and asks for some information or a key press to verify that you've seen a message). I can predict where a branch might occur, but can't detect if it actually has because my .cmd file is blind to output from the ssh session. (I'm working in windows by the way)

What I'm trying to do is use a python script that uses stdin and makes decisions based on what it sees, but I'm new to how piping works. Piping into my script works, but I'm unsure how to send keystrokes back to the ssh session from the python script. Here's an example of my test script:

import sys
import time

buff=''
try:
    while True:
        buff += sys.stdin.read(1)
        if buff[-5:] == 'press':
            print('found word "press"!')
            #Send a keystroke back to the ssh session here
            buff = ''
except KeyboardInterrupt:
    sys.stdout.flush
    pass

And here's how I call it:

ssh MyUsername@###.###.###.### | python -u pipe_test.py

While it's running, I can't see anything, but I've verified that I can send keystrokes through the terminal with my regular keyboard.

Any ideas on how to output keystrokes to the ssh session? Should I be doing some completely different, much simpler thing?

FYI: The data sent by the server to the terminal has ASCII escape characters flying all over the place. It's not a nice bash interface or anything like that. Also, I've installed a bunch of Unix command line tools so that I can, for example, ssh from windows.

tl;dr How do I pipe from an ssh session into python, and send keystrokes back to the ssh session from that same python script?


Solution

  • You definitely don't do this with pipes. A Unix pipe is a unidirectional inter-process communications mechanism. You can send data to it, or you can read data from it. But not both (through the same pipe).

    It is possible to use pairs of pipes to create co-processes. This is even supported directly in some Unix shells (such as Korn shell and Bash as of version 4 (https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html). However this mechanism is somewhat fragile and prone to deadlock. It works so long as the processes on both sides of this pair of pipes are rigorous in their handling of the pipes and the associated buffering (Actually it's possible for just one end to be rigorous, but even that's tricky). This is not likely to be the case for the programs that you're trying to run remotely.

    Someone suggested pexpect which is an excellent choice for controlling a locally spawned terminal or curses application. It's possible to manage a remote process with it, by spawning a local ssh client and controlling that.

    However, a better choice for accessing ssh protocols and APIs from Python would be Paramiko. This implements the ssh protocol such that you access the remote sshd process as an API rather than through a client (command line utility).

    An advantage of this approach is that you can programmatically manage port redirections, transfer and manage files (as you would with sftp) (including setting permissions and such), and you can execute programs and separately access their standard input, output, and error streams or their pseudo-terminals (pty) as well as fetch the exit codes of remote processes as distinct from the exit code of your local ssh client.

    There's even a package paramiko-expect adds an extension to Paramiko to enable more straightforward using of these remote pty objects. (Pexpect provides similar features on the pty controlling local processes). [Caveat: I haven't used Fotis Gimian's package yet; but I've used Paramiko fairly extensively and sometimes wished I had something like it].

    As you may have figured out from this answer, the complexity of programmatically dealing with an interactive terminal/text program under Unix (Linux or any of its variants) has to do with details about how that program is written.

    Some programs, such as shells, can be completely driven by line oriented input and output to their standard file descriptors (stdin, stdout, and stderr). Others must be controlled through their terminal interfaces (which is accomplished by launching them using a pseudo-terminal environment ... such as the one provided by sshd when it starts a normal interactive process, and those supplied by expect (and pexect and various other modules and utilities inspired by the old TCL/expect) and by your xterm or other terminal windowing programs under any modern OS (even including Cygwin or the "Bash for Windows" or WSL (Windows Support for Linux) under the latest versions of Microsoft Windows).

    In general your attempts will need to use one or another approach and pipes are only very crudely useful for the approach using the standard file descriptors. The decision of which approach to use will mostly be driven by the program you're trying to (remotely) control.