So today I had to write a quick script on Darwin(i.e. MacOS X) that was to run a command line app and interact with it. Basically what would happen is that the command line would run and it would prompt the user to enter a password. At first the viscious hack involved writing a dead simple C extension to python that simluated a key press (i.e as if a human being really hit the key on a keyboard) -- I would've liked to just use python, but the ioctl module was not available for the platform. The python extension wrapper is ugly, so I'll just show you a command line version of what I did.


#include <sys/ioctl.h>

int main(int argc, char* argv[])
{
        char *cmd, *nl = "\n";
        int i, fd = 0; // stdin

        if (argc > 1) {
                cmd = argv[1];

                for (i= 0; cmd[i]; i ++) {
                        ioctl(fd, TIOCSTI, cmd + i);
                }

                ioctl(fd, TIOCSTI, nl);
        }

        return 0;
}

Then things got weird when the password prompt showed up arbitrary number of times depending on the argument used for the command line app. So I needed a way to read in from the terminal and only type the password when it was explicitly asked for. At first I just used popen to get access to the stdin, stdout, stderr to see if I can read the prompt in, but it seemed like the command line was writing directly to the terminal... =( So I essentially had to find a way to somehow create a terminal-like environment, run the command line in that and intercept the output it's piping to the terminal. After some asking around and googling I noticed that the pty module had its own fork function that forked a child process inside a pseudo-terminal which it returned the file descriptor of! So the hack came out as such:

import sys
from pty import fork
from fcntl import fcntl, F_SETFD, FD_CLOEXEC, F_GETFD
from os import execvp, close, read, fdopen, pipe, write, waitpid

__MAX_BUF_SIZE = 1024

def my_func(cmd, prompt, type_this, ack=None):
    """
    @type cmd: sequence
    @type prompt: string
    @type type_this: string
    @type ack: string
    """
    
    (r_fd, w_fd) = pipe() # synch pipe
    old_fd = fcntl(w_fd, F_GETFD)
    fcntl(w_fd, F_SETFD, old_fd | FD_CLOEXEC)

    (pid, fd) = fork()

    if pid == 0:
        # child process
        execvp(cmd[0], cmd)
        # ---------------------------------------------------------------------
    else:
        close(w_fd)

        # wait for the child process to run
        if not fdopen(r_fd).read():
            buf = ""
            n = len(prompt)

            while 1:
                s = read(fd, n)
                
                if s == prompt:
                    write(fd, type_this)
                elif ack != None and s == ack:
                    # an ack for the password (i.e. "\r\n" is typically echoed)
                    pass
                elif s:
                    if not buf:
                        n = __MAX_BUF_SIZE # raise amount of read we do

                    buf = "%s%s"%(buf, s)
                else:
                    break
                
         # wait for child process to finish running
         waitpid(pid, 0)     
         close(fd)
     
         return buf

if __name__ == "__main__":
    print my_func("/usr/bin/cmd arg1 arg2".split(" "), 
    "Password: ", 
    "myeasilycrackablepassword\n",
    "\r\n")
    

There you have it. Just thought I'd blog about it, since I couldn't find anything immediately on the net... So if you want to interact with terminal apps by reading its output and typing in some input, this should give you a good start. Hope that helps!


1 comment(s) | link to this entry | edit this entry

Want some more? Dig in to the archive for past entries.