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!


back to the list of latest entries


Wow. You really are a geek. Gosh... *sniff* you make a noona so proud! Btw, thanks for the birthday wishes. You should email me and tell me what country you're in these days or tell me when you'll be visiting NYC! (920)

Cindy - 2/10/2006 7:33:42 PM [ 12.75.133.14 ]


Name
Email
Homepage
Comment
Remember my information