Checking your mail when it's far away: offlineimap through jumphosts
Introduction
Though a loathed ubiquity, email can perversely forment greater concern in its absence than its reluctantly accepted presence. Or so I found out recently, when my ability to access my email servers was blocked due to a firewall.
I like to use a terminal for everything, and mail is no exception. To read email, I use offlineimap, which synchronises a local copy of my IMAP server for fast access. So when I found myself in this predicament, I wondered if a judicious application of SSH tunnels might ameliorate my worries.
SSH Tunnels
Like their analogue counterparts, SSH tunnels allow obstacles — in this case, firewalls — to be bypassed, as long as there is a good enough way to ‘tunnel’ around them. In my case, I had a laptop which was ostensibly behind a firewall; a ‘jumphost’, in the parlance: a middle-man to where I want to end up; and a mail server, accessible on the internet.
I wanted to use this ‘jumphost’ to talk to the internet for me, and relay all of my messages.
Scripts
The Easy Way
The easy way to do this would be to set up constantly forwarding port on each server along the trip, each ready to accept connections at any time. The problem with this is that:
- This would leave port open, possibly raising suspicion;
- I didn’t want my internet-accessible server with ports streaming straight to my IMAP server authentication page, and;
- That’s too easy
The Hard Way:
The solution I wanted would quickly establish a tunnel all of the way through, fetch/send all email, and then close the connection.
I have two computers:
l
, my jumphostj
, my sendmail server
exchange.iinet.net.au
is my IMAP server, where all of my emails to read wait for me.
Local Computer
To do this, I wrote the following in port_forward.sh
:
#!/bin/bash
nohup ssh -N -L 8000:localhost:4430 -L 8001:localhost:8001 l > /dev/null 2>&1 &
pid=$! # ssh PID
echo $pid
This makes local ports (8000, 8001) listen on server l
’s ports (4430, 8001) respectively. It also returns the process ID, so that we know how to stop the port forwarding when appropriate.
I have a script which runs my instance of syncmail:
#!/bin/bash
HOME=/Users/joel
MAIL=$HOME/imap_mail
PROCMAILD=$HOME/.procmail.d
#PATH=./:/Users/joel/perl5/bin:./:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/go/bin:/Users/joel/bin:/usr/local/go/bin:/usr/local/go/bin
CONFIG_FILE=${1:-$HOME/.offlineimaprc}
`/usr/local/bin/offlineimap -c "$CONFIG_FILE" 2>&1 | cat >> "$PROCMAILD"/log`
results=`find $MAIL/joelbuckley/INBOX/new -type f -newer "$PROCMAILD"/last_run -print0`
And the script that invokes it, syncmail_wrapper.sh
:
#!/bin/bash
CONFIG_FILE=${1:-$HOME/.offlineimaprc}
REMOTE_PID=$(ssh -n -f l '~/bin/port_forward_remote.sh')
LOCAL_PID=$(/Users/joel/bin/port_forward.sh)
sleep 2
~/bin/syncmail.sh "$CONFIG_FILE"
ssh l "kill -9 $REMOTE_PID"
kill $LOCAL_PID
And then sendmail equivalent, sendmail_mutt_wrapper.sh
:
#!/bin/bash
IN=$(cat -)
REMOTE_PID=$(ssh -n -f l '~/bin/port_forward_remote.sh')
LOCAL_PID=$(port_forward.sh)
sleep 2
echo "$IN" | /usr/local/bin/msmtp --read-envelope--from "$@"
ssh l "kill -9 $REMOTE_PID"
kill $LOCAL_PID
Remote Computer
#!/bin/bash
nohup ssh -N -g -L 4430:exchange.iinet.net.au:993 -L 8001:localhost:465 j > /dev/null 2>&1 &
pid=$! # ssh PID
rc=$? # ssh return code
[ $rc -eq 0 ] && echo $pid
Pulling It All Together
Now, whenever I would like to synchronise email, I run
$ syncmail_wrapper.sh ~/.offlineimaprc
.
My ~/.offlineimaprc
contains:
remotehost = localhost
remoteport = 8000
And, to send email (I use msmtp
), my ~/.msmtprc
looks like this:
host localhost
port 8001
My ~/.muttrc
file contains:
set sendmail = "~/bin/sendmail_mutt_wrapper.sh"