OAuth2 authentication for offline email clients

More than a year ago, in my first post on this blog, I described my head in the cloud, feet on the ground strategy for offline email access. At that time, my solution (based on offlineimap) required me to store my email password in my Gnome keyring. This is far from satisfactory because as I explained in my blog post on using encryption, I do not like to keep important passwords in the Gnome Keyring. For that, I use a KeePass password file in an encrypted file system. This means that I need three passwords to get to access my important passwords: first to login to the computer, second to mount my encrypted volume and third to open the password manager. On the other hand, I need only my login password to get to the less important passwords sitting in the Gnome Keyring. In my view, email passwords are among the most important ones, and it unfortunate that to use offlineimap, I had to store this critical stuff in the Gnome Keyring.

The solution is to use OAuth2. In the last year or so, offlineimap has acquired the capability to use OAuth2, and now I have completed my migration to this method. As part of this process, I sat down and read the official document on OAuth 2.0 Threat Model and Security Considerations. That made me uncomfortable with the suggested approach in offlineimap (and many other software as well) of storing the OAuth2 refresh token in plain text in the configuration file. It might be acceptable if the home partition is encrypted, but as I explained in my Using Encryption post, that is not how my laptop is set up. I therefore came up with the idea of storing the refresh token in the Gnome Keyring. Since it is possible to use arbitrary python code for almost all settings in the offlineimap configuration file, this is easy.

The comment in the default offlineimaprc configuration file is almost all that one needs to figure out how to do this:

# Here's how to register an OAuth2 client for Gmail, as of 10-2-2016:
#    - Go to the Google developer console
#         https://console.developers.google.com/project
#    - Create a new project
#    - In API & Auth, select Credentials
#    - Setup the OAuth Consent Screen
#    - Then add Credentials of type OAuth 2.0 Client ID
#    - Choose application type Other; type in a name for your client
#    - You now have a client ID and client secret
#oauth2_client_id = YOUR_CLIENT_ID
#oauth2_client_secret = YOUR_CLIENT_SECRET

# Specify the refresh token to use for the connection to the mail server.
# Here's an example of a way to get a refresh token:
#    - Clone this project: https://github.com/google/gmail-oauth2-tools
#    - Type the following command-line in a terminal and follow the instructions
#         python python/oauth2.py --generate_oauth2_token \
#            --client_id=YOUR_CLIENT_ID --client_secret=YOUR_CLIENT_SECRET
#    - Access token can be obtained using refresh token with command
#         python python/oauth2.py --user=YOUR_EMAIL --client_id=YOUR_CLIENT_ID
#            --client_secret=YOUR_CLIENT_SECRET --refresh_token=REFRESH_TOKEN
# If the type of the remote is IMAP, oauth2_request_url MUST be defined.
# For Gmail, the default URL is https://accounts.google.com/o/oauth2/token.
#oauth2_request_url = https://accounts.google.com/o/oauth2/token
#oauth2_refresh_token_eval = get_refresh_token("accountname")

I was earlier using Jason Graham’ Python code to read passwords from the Gnome Keyring and it was trivial to re-purpose the code to read the refresh token instead. The get_refresh_token function ended up being just one line of Python code.

To complete the migration, I needed a OAuth2 solution for sending mail using SMTP. I was using msmtp which does not currently support OAuth2. Then I found Christopher Corley’s send.py which was written for exactly this purpose. It turned out that send.py also stores the refresh token in the configuration file, but it needed a change of only one line to read it from the Gnome Keyring instead. I changed:

refresh_token = config.get(section, 'refresh_token')


refresh_token = get_refresh_token(config.get(section, 'accountname'))

Even when I was relying on msmtp, I was using Allen Li’s pymsmtp which has now become mir.msmtpq to queue messages and send them asynchronously. It was trivial to patch this to use send.py instead. As an aside, Python’s comprehensive set of libraries is truly amazing. In the old days, only hardened veterans would think of writing a replacement for something like sendmail or msmtp. But putting pymsmtp and send.py together, we have a functioning sendmail replacement in 350 lines of Python. More importantly, a reasonably competent programmer can modify this easily as required. True, all this works only if somebody else is running a SMTP server for you, but still, it is remarkable.

Finally, let me summarize the security goals that have been accomplished by migrating to OAuth2. If my laptop is stolen while it was switched off (possibly hibernated), the thief cannot read my old emails because they are in an encrypted partition. To read or send new mails, the attacker would need to crack my login password that protects the Gnome Keyring. There is a good chance that in the time that it takes to crack my login password, I would be able to login to my Gmail account and revoke the authorization of the email client and then the refresh token is worthless. I have even more time to change my Gmail password since the attacker needs to crack multiple lines of defence before gaining access to that. In the earlier setup, the thief was just one password away from accessing my Gmail password and inflicting significant damage quite quickly. Even if the laptop is stolen while it is running, the thief does not get access to the email password (my password manager, KeePass, is rarely open and even if it is, it locks itself within a minute or so of inactivity). Yes, the thief would be able to send and receive mails until I get around to logging in and revoking the authorization of those refresh tokens. Either way, there is a big improvement in security. Of course, some threat models still remain.


9 thoughts on “OAuth2 authentication for offline email clients”

  1. Can you please share how you patched msmtp or mir.msmtpq to use send.py? (sorry for the duplicate comment, I reposted so I can be notified via e-mail when replies are made to this comment).


    1. You will find a line

      MSMTP_PATH = 'msmtp'


      MSMTP = 'msmtp'

      All that you need to do is to change ‘msmtp’ to ‘send.py’ assuming that ‘send.py’ is in your path.


      1. Many thanks for your quick reply, sir. My main concern was that send.py has an interface that isn’t compatible with that of sendmail and I wanted to configure my Emacs to send mail using send.py.

        I ended up having to change two things in “send.py” to support that:

        I changed “argparse” line for “–readfrommsg” to:

        parser.add_argument(‘-t’,’–readfrommsg’, action=’store_true’, help=’Read the mail to determine the sender and recievers’)

        to add support for the “-t” flag in “sendmail” which is synonymous as far as I can tell.

        I also added the line:

        parser.add_argument(‘-oi’, action=’store_true’, help=’Not supported’)

        to silently ignore the “-oi” option recognized by sendmail. This is not needed because “send.py” reads its stdin till EOF and doesn’t stop at a “.” like the default behavior of sendmail. I needed to “eat” this option because Emacs passes it by default to whatever program I use as a sendmail replacement.


      2. I am sorry, I forgot to mention that I do not call send.py directly. I use a shell script that “eats” all arguments and simply runs

        send.py –readfrommsg

        That is why I did not need to patch send.py


      3. I should also mention that I couldn’t get msmtp (or mir.msmtpq) to work for me. I have configured Emacs to use my patched “send.py” as a sendmail replacement and it seems to work fine now.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s