At Handle we talk to a lot of email servers, mostly via IMAP. Something most engineers don’t realize is how crazy IMAP has become, in large part due to fragmentation and providers who add functionality on top of the protocol. Talking to Yahoo? No IMAP IDLE. Talking to Gmail? You may or may not be blessed with an “All Mail” folder. It might also be localized to the user’s language (e.g. “Alle Nachrichten” for German users). There’s also COMPRESS, MODESEQ, Gmail MSGID & THRID – the list goes on. How does a programmer begin to learn the nuances?
Open source clients like Thunderbird or
Zimbra are a good place to start. But sometimes it’s faster to watch an exchange than
trace code. What about a TCP dump? Sometimes the server only responds to SSL and it’s not possible to sniff the
handshake. Or even if you record a session, the IMAP server might have compressed parts of it. Annoyances such as
these are what prompted me to write an IMAP proxy, making it easy to inspect any IMAP conversation.
To run the script you’ll need node.js and coffee-script (via npm). To
start the script, just run coffee imap_proxy.coffee. Then configure your email client to connect to your machine
on port 3737. Disable SSL. Save your settings and watch the conversation unfold!
#!/usr/bin/env coffeetls = require("tls")net = require("net")Socket = require("net").Socketstate = {}IMAP_SERVER = "imap.gmail.com"GREEN_TEXT_CODE = '\x1b[0;32m'RED_TEXT_CODE = '\x1b[0;31m'clientListener = (connectionToClient) -># This callback is run when the server gets a connection from a client.isConnected = trueconsole.log"Connected to mail client"connectionToClient.on"data",(data) ->console.log"#{RED_TEXT_CODE} Mail client: ",data.toString()connectionToImapServer.write(data)connectionToClient.on"end",->isConnected = falseconsole.log"Disconnected from #{IMAP_SERVER}"# Now that we have a client on the line, make a connection to the IMAP server.state.conn = newSocket()connectionToImapServer = tls.connect(socket: state.conn,# Skip TLS certificate verification. Don't use this script on sensitive data!rejectUnauthorized: false,->console.log"Client connected"state.conn = connectionToImapServer)connectionToImapServer.on"data",(data) ->console.log"#{GREEN_TEXT_CODE}#{IMAP_SERVER}: ",data.toString()returnif!isConnected# Do something sneaky during the CAPABILITY exchange. Remove "DEFLATE" from# the list (if present) so this proxy doesn't have to decompress messages.ifdata.toString().match(/CAPABILITY.*COMPRESS=DEFLATE/)minusCompress = data.toString().replace"COMPRESS=DEFLATE ",""console.log"Proxy substitution: ",minusCompressconnectionToClient.write(minusCompress)elseconnectionToClient.write(data)state.conn.connect993,IMAP_SERVERserver = net.createServer(clientListener)server.listen3737,->console.log"IMAP proxy is listening on port 3737"
I hope this helps on your next IMAP adventure. And if you’re passionate about IMAP, Handle is hiring!