Search code examples
pythonopensslsubprocesstty

Interact with `/dev/tty` opened by subprocess in python


I have a python script which uses subprocess.Popen to launch a target program: a webserver. I want to interact with the target, so I assign stdin, stdout and stderr to pipes, and I can now read and write to these.

Now, the webserver links the OpenSSL library for TLS processing. As part of TLS processing, it opens a certificate and prompts for a password. Generally, on typing in the password and hitting enter, the webserver starts serving. Now, as you might have guessed, I want to enter this password from the python script. Usually, what I would need to do is write to the server's stdin. However, there is a problem. When OpenSSL prompts for and reads the password, it does not use stdin/stdout. It opens /dev/tty and uses that instead. As a result, I have to type in the password and enter manually.

The following figure demonstrates the situation.

Program setup

Below, you can see the a snippet of the output of lsof for the server:

memcached 25279 USER    0r  FIFO   0,13      0t0  7771739 pipe
memcached 25279 USER    1w  FIFO   0,13      0t0  7771740 pipe
memcached 25279 USER    2w  FIFO   0,13      0t0  7771740 pipe
memcached 25279 USER    3r   REG    8,1     3414  3414276 ....key.pem
memcached 25279 USER    4r   CHR    5,0      0t0       13 /dev/tty
memcached 25279 USER    5w   CHR    5,0      0t0       13 /dev/tty

Is there a way to intercept the call to /dev/tty for the server, so that I can write to it directly from the script?


Solution

  • The standard way to achieve what you are describing it by using a pty - a pseudo terminal. A pty looks exactly like a normal tty, with the exception of it being controlled by software instead of an actual terminal or a terminal emulator.

    In your case you could easily use the pty module in the python standard library, that provides a few utilities for using pty. Specifically, look at the pty.spawn() function:

    Spawn a process, and connect its controlling terminal with the current process’s standard io. This is often used to baffle programs which insist on reading from the controlling terminal. It is expected that the process spawned behind the pty will eventually terminate, and when it does spawn will return.

    Example usage:

    import subprocess, sys
    
    openssl_cmd = ['openssl', 'foo', 'bar']
    p = subprocess.Popen([sys.executable, '-c', 'import pty, sys; pty.spawn(sys.argv[1:])', *openssl_cmd])
    p.stdin.write('password\n')
    print(p.wait())
    

    In this example, instead of running openssl directly, we run it using spawn, which means that whatever we write into the process's stdin will be read by openssl from its /dev/tty.