In my earlier post, I explained how to retrieve a password stored in a Mac OS X keychain from within Mutt. Mutt can be compiled with IMAP support, allowing for up-to-date access to your email account, but sometimes you want local copies of your IMAP folders for offline browsing or for backups. OfflineIMAP provides exactly this functionality: it synchronizes local maildir format email folders with a remote IMAP host. As with mutt, you normally can put your passwords in the configuration file, ~/.offlineimaprc, but that leaves your password in clear text in the file. Thankfully, OfflineIMAP allows you to run python code in some of its fields, and it lets you specify a file containing python code for it to source methods from.

The python source file

First, we need to make a python function that will call the security command and grab only the relevant part of its output:

import re, commands
def get_keychain_pass(account=None, server=None):
    params = {
        'security': '/usr/bin/security',
        'command':  'find-internet-password',
        'account':  account,
        'server':   server
    }

    command = "%(security)s %(command)s -g -a %(account)s -s %(server)s" % params
    outtext = commands.getoutput(command)
    return re.match(r'password: "(.*)"', outtext).group(1)

I put this into ~/.mutt/offlineimap.py, although something like ~/.offlineimap.py makes sense as well. The params dict/hash makes it more legible and easy to change the command, and the command string gets run through commands.getoutput() to produce the command output. The re.match() line does the same thing as the perl regular expression matching that showed up in the muttrc file.

offlineimaprc

Next, OfflineIMAP’s RC file needs to know about this function. This can be done by adding a pythonfile directive to the [general] section:

[general]
    pythonfile = ~/.mutt/offlineimap.py

Finally, the get_keychain_pass() function itself needs to get called from the correct field:

[Repository gmailRemote]
    type = Gmail
    remoteuser = someuser@gmail.com
    remotepasseval = get_keychain_pass(account="someuser@gmail.com", server="imap.gmail.com")

Upon startup, OfflineIMAP will now run security and try to acquire the specified password from the keychain.

Conclusion

The above should work fine with OfflineIMAP and Mutt running at the same time. However, when I was still running Mac OS X 10.4, I ran into an annoying race condition involving security. When both programs executed security while my keychain was locked, both instances of security would open dialogs asking for my keychain password (I do not know if this problem still occurs in Mac OS X 10.5 or 10.6). My solution to this was to write a wrapper script that called security itself. It used a lockfile to let only one instance of security run at a time, and it also did the regular expression filtering for the password. If you are interested in using my script, getpassword can be downloaded here (It is written in Ruby, and it requires the ‘lockfile’ Ruby gem, so it may not be suitable for you as it currently exists).