Background
DKIM (http://www.dkim.org/) and DomainKeys (http://en.wikipedia.org/wiki/DomainKeys) are two distinct but similar technologies, which enables a public / private key pair, signatures in the email header, and a DNS record to check that an email has come from a particular server. They are each used by different mail servers as a spam check – Yahoo uses both, and Google / Gmail uses DKIM (the newer technology).
DKIMProxy (http://dkimproxy.sourceforge.net/) is a package that has made my life so much easier because it implements and sends both of these key types with one install.
It was a little painful for me to setup, and it took a fair while for me to work through all the various issues / configuration bits and pieces, so I have written this in the hope that things are made easier for others. The below is the process that I took to get this all up and running on our live production server, based upon trial-and-error, hack-and-slash on our staging server.
Note that I am distributing this information without warranty of any kind – I am not a dedicated sysadmin. The below instructions worked for us, but it isn’t our fault if your email world crumbles down
Our setup is Postfix on Ubuntu 8.04LTS (Hardy), and these instructions were gleaned from http://dkimproxy.sourceforge.net/, http://anothersysadmin.wordpress.com/2008/01/16/domainkeysdkim-with-postfix/ and http://dkimproxy.sourceforge.net/postfix-outbound-howto.html. These instructions also only cover outgoing mail – I edited the init script to only server for outgoing mail as well – so if you want to Authorize incoming mail you’re going to need to look elsewhere, although you may find the bits in here useful.
It is STRONGLY recommended that if you are to follow the below instructions, you do so in staging environment first, and test extensively using a @yahoo email address, and a @gmail address. This is also recommended by the creators of DKIMProxy as well, so just stage it first, ok?
After all, Rudder had a security blunder after a dodgy DKIM install. SO JUST STAGE IT FIRST, OK? Seriously, don’t mess with the absolute truth of this statement.
Testing the Authenticity – Email Headers
With each of the above, you can view the full headers to see what the mail receiver makes of your message. The below examples are our headers after installing everything – so it can act as a basis of comparison for you.
In Gmail, click on the menu arrow to the right of “Reply” in the message, and choose “Show original”. This will put out the full headers for the message. The below are the relevant parts of the message that we will be looking for in future testing:
Received-SPF: pass (google.com: best guess record for domain of accounts@pocketsmith.com designates 68.64.33.16 as permitted sender) client-ip=68.64.33.16;
Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of accounts@pocketsmith.com designates 68.64.33.16 as permitted sender) smtp.mail=accounts@pocketsmith.com; dkim=pass (test mode) header.i=@pocketsmith.com
Received: from mail.pocketsmith.com (localhost [127.0.0.1])
by mail.pocketsmith.com (Postfix) with ESMTP id 7FC639680E0
for <contactpocketsmith+appemails@gmail.com>; Sat, 4 Jul 2009 22:26:02 -0400 (EDT)
And then in Yahoo, click on the down arrow next to “Compact Header” and select “Full Header”. Again, the juicy bits we want to see at the end of this process are below:
Authentication-Results: mta186.mail.re3.yahoo.com from=pocketsmith.com; domainkeys=pass (ok); from=pocketsmith.com; dkim=pass (ok)
Received: from 68.64.33.16 (EHLO mail.pocketsmith.com) (68.64.33.16)
by mta186.mail.re3.yahoo.com with SMTP; Sat, 04 Jul 2009 19:25:17 -0700
Received: from mail.pocketsmith.com (localhost [127.0.0.1])
by mail.pocketsmith.com (Postfix) with ESMTP id 6C6299680DE
for <wigsgiw@yahoo.co.nz>; Sat, 4 Jul 2009 22:26:02 -0400 (EDT)
Another thing that you will see crop up in the headers is DomainKey-Signature and DKIM-Signature. You probably won’t have them yet, but the aim is to send these with each email and have them pass against the DNS record we’ll be setting up later.
DomainKey-Signature: a=rsa-sha1; c=nofws; d=pocketsmith.com; h=date:from
:reply-to:to:subject:mime-version:content-type:message-id; q=
dns; s=mail; b=QlTpx3...VA=
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=pocketsmith.com; h=date
:from:reply-to:to:subject:mime-version:content-type:message-id;
s=mail; bh=heQ/E1X...Sc=
Now lets quickly, for reference, check out how yahoo sees Google’s mail and vice-versa, just as a basis of comparison to make sure I ended up being correct in my setup I describe below.
Gmail >> Yahoo
Authentication-Results: mta208.mail.mud.yahoo.com from=gmail.com; domainkeys=pass (ok)
Received: from 209.85.210.179 (EHLO mail-yx0-f179.google.com) (209.85.210.179)
by mta208.mail.mud.yahoo.com with SMTP; Sat, 04 Jul 2009 20:06:16 -0700
Yahoo >> Gmail
Authentication-Results: mx.google.com; spf=neutral (google.com: 76.13.13.67 is neither permitted nor denied by best guess record for domain of wigsgiw@yahoo.co.nz) smtp.mail=wigsgiw@yahoo.co.nz; dkim=pass (test mode) header.i=@yahoo.co.nz
Received: from [76.13.13.25] by n4.bullet.mail.ac4.yahoo.com with NNFMP; 05 Jul 2009 03:13:09 -0000
So things look pretty good; we have the DKIM and DomainKeys; Google / Yahoo each only have one or the other. We can see that having mail.pocketsmith.com as the “Received: from” doesn’t matter; Google has mail-yx0-f179.google.com. I was initially concerned because the Domain in the DKIM-Signatuer etc above is pocketsmith.com – not mail.pocketsmith.com – but it appears this will not matter.
So go get familiar with these headers – you are likely to be looking at these extensively over the next wee while
Setting DKIM and DomainKeys Up with Postfix, using DKIMProxy
This is the exact process that I went through in getting this going on our production server. It may seem a little backwards at times, however, well, it worked for me.
First of all, lets get our required RSA keys generated, and the DNS records setup so they have time to propagate prior to testing.
Note that the below is strictly my choice for the location of these configuration files. This could well be wrong in terms of how things ’should’ be setup for configuration files / keys etc, but it makes sense to me. And that is all that matters really
Anyway, I’ll be using /etc/postfix/dkim as the main directory where I store the keys etc for DKIMProxy.
Generating the keys and DNS records
cd /etc/postfix/
sudo mkdir dkim
cd dkim
sudo openssl genrsa -out private.key 1024
sudo openssl rsa -in private.key -out public.key -pubout -outform PEM
cat public.key
Once you have that public key in hand, you need to create a DNS record with the key. We use Slicehost for our DNS (they kick ass), however just create the record in whatever you use – whether it is an external DNS or if you use Bind9.
You create a TXT record for the domain [selector]._domainkey.[domain.tld] – where selector is used later on in this process. I never quite got 100% into what this means… but bugger it. I chose ‘mail’ as our selector, as we send all mail from mail.pocketsmith.com and again, this just makes sense to me. This means I created the record for the following domain (note this is the complete domain, which I check for the record against):
mail._domainkey.pocketsmith.com
Then remove all the line-breaks in the public.key that we cat’d just before, so it is one long string. Then replace the “M5GfMA0…….fIDAQAB” bit below with it, and add this as the TXT record for the above domain (i.e. for us: mail._domainkey.pocketsmith.com).
k=rsa; t=y; p=M5GfMA0.......fIDAQAB
If you are doing this in Bind9, you’d just add something like:
[selector]._domainkey IN TXT "k=rsa; t=s; p=M5GfMA0.......fIDAQAB"
And these were the fields in Slicehost:
Type: TXT
Name: mail._domainkey
Data: k=rsa; t=s; p=M5GfMA0.......fIDAQAB
TTL seconds: 300
Note that if you can, set the TTL seconds to a low number (e.g. 300 as above) as this means that all servers will know to check back every 5 minutes – pretty important as you move from staging to production. Once you are happy, you can put this back up to 86400
Now that we have out keys we can:
Install core dependencies for Perl Libraries (Ubuntu Hardy)
I found this to be agony, however I’ll keep this short and sharp as I learnt all I needed to and nothing more.
First, we need to make sure that the openssl development libraries is installed – to do this you may also need to update your apt-get or aptitude package lists, so do that first.
sudo aptitude update
(or sudo apt-get update, depending on what you use)
sudo aptitude install libssl-dev
Then we can install all required perl modules. The first run of CPAN (A perl package manager) below will ask you to configure it – I had no problems using the defaults on all options)
Although all dependencies would be install if you jumped straight to Mail::DKIM, it is always nice to manually install the required dependencies so you can see them all.
sudo perl -MCPAN -e 'install Crypt::OpenSSL::RSA'
sudo perl -MCPAN -e 'install Digest::SHA'
sudo perl -MCPAN -e 'install Mail::Address'
sudo perl -MCPAN -e 'install MIME::Base64'
sudo perl -MCPAN -e 'install Net::DNS'
sudo perl -MCPAN -e 'install Net::Server'
sudo perl -MCPAN -e 'install Mail::DKIM'
With Perl dependencies taken care of, we can install dkimproxy.
Installing DKIMProxy
I use /usr/src for all our source files, and I chose to install dkimproxy in /usr/local/dkimproxy.
cd /usr/src
sudo wget http://downloads.sourceforge.net/dkimproxy/dkimproxy-1.1.tar.gz
sudo tar xfh dkimproxy-1.1.tar.gz
cd dkimproxy-1.1/
./configure --prefix=/usr/local/dkimproxy
sudo make install
Done! Now for configuration – first of all, lets get PostFix talking to our DKIMProxy.
sudo nano /etc/postfix/master.cf
And just under the smtp inet n – - – - smtpd line put:
submission inet n - y - - smtpd
-o smtpd_etrn_restrictions=reject
-o content_filter=dksign:[127.0.0.1]:10027
-o receive_override_options=no_address_mappings
-o smtpd_recipient_restrictions=permit_mynetworks,reject
(I left the rest of the commented-out stuff there… for prosperity)
And then right at the end of the same file:
#
# specify the location of the DKIM signing proxy
# Note: we allow "4" simultaneous deliveries here; high-volume sites may
# want a number higher than 4.
# Note: the smtp_discard_ehlo_keywords option requires Postfix 2.2 or
# better. Leave it off if your version does not support it.
#
dksign unix - - n - 4 smtp
-o smtp_send_xforward_command=yes
-o smtp_discard_ehlo_keywords=8bitmime,starttls
#
# service for accepting messages FROM the DKIM signing proxy
#
127.0.0.1:10028 inet n - n - 10 smtpd
-o content_filter=
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
-o smtpd_helo_restrictions=
-o smtpd_client_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
Then we need to add our users and groups that we are going to use. So back at the command line:
sudo groupadd -g 4321 dkim
sudo useradd -u 4321 -s /bin/false -d /dev/null -g dkim dkim
Then jump back into the dkim configuration / key folder:
cd /etc/postfix/dkim
And create a domain.key file, which tells DKIM to send out DKIM and DomainKeys, with our specific options. I called the file this… because I liked having all the files in /etc/postfix/dkim/ to have a .key suffix, which is kind of silly, I know.
Note this is different from the ’standard’ set up, as I was having great difficulty getting a consistent pass from both Yahoo and Gmail. Hence, is an amalgamation of a number of options – however it isn’t necessarily 100% (but it works for us).
pocketsmith.com domainkeys(a=rsa-sha1,c=nofws), dkim(a=rsa-sha256,c=relaxed)
Now create the init script – this is definitely very different from the ’standard’ example that comes with the DKIMProxy package, for the same reaasons as the above.
But hey, it works.
Note you should edit / update the bits within the ### BEGIN CONFIGURABLE BITS block to suit your setup, and especially the setup of the DNS entry you did above (i.e. the $DKIMPROXY_SELECTOR).
UPDATE: Brian helpfully put the below up on pastie without any of the HTML oddities that plagues this sort of thing – just grab the below code from: http://www.pastie.org/579385
#!/bin/sh
#
# Copyright (c) 2005-2007 Messiah College.
# Edited heavily by James Wigglesworth (PocketSmith) July 2009
# Note: These edits make this init file only good for OUTGOING DKIM signing
#
### BEGIN INIT INFO
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Description: Runs dkimproxy
### END INIT INFO
### BEGIN CONFIGURABLE BITS
DKIMPROXYDIR=/usr/local/dkimproxy
DKIMPROXYUSER=dkim
DKIMPROXYGROUP=dkim
DKIMPROXY_PRIVATE_KEY="/etc/postfix/dkim/private.key"
DKIMPROXY_SELECTOR="mail"
DKIMPROXY_SENDER_MAP="/etc/postfix/dkim/domain.key"
### END CONFIGURABLE BITS
HOSTNAME=`hostname -f`
DKIMPROXY_OUT_ARGS="
--conf_file=$DKIMPROXY_OUT_CFG"
DKIMPROXY_COMMON_ARGS="--user=$DKIMPROXYUSER --group=$DKIMPROXYGROUP --daemonize --keyfile=$DKIMPROXY_PRIVATE_KEY --selector=$DKIMPROXY_SELECTOR --sender_map=$DKIMPROXY_SENDER_MAP"
DKIMPROXY_OUT_BIN="$DKIMPROXYDIR/bin/dkimproxy.out"
PIDDIR=/var/run
DKIMPROXY_OUT_PID=$PIDDIR/dkimproxy_out.pid
case "$1" in
start)
echo -n "Starting outbound DKIM-proxy (dkimproxy.out)..."
# create directory for pid files if necessary
test -d $PIDDIR || mkdir -p $PIDDIR || exit 1
# start the daemon
$DKIMPROXY_OUT_BIN $DKIMPROXY_COMMON_ARGS --pidfile=$DKIMPROXY_OUT_PID 127.0.0.1:10027 127.0.0.1:10028
# echo "$DKIMPROXY_OUT_BIN $DKIMPROXY_COMMON_ARGS --pidfile=$DKIMPROXY_OUT_PID 127.0.0.1:10027 127.0.0.1:10028"
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
echo done.
else
echo failed.
exit $RETVAL
fi
;;
stop)
echo -n "Shutting down outbound DKIM-proxy (dkimproxy.out)..."
if [ -f $DKIMPROXY_OUT_PID ]; then
kill `cat $DKIMPROXY_OUT_PID` && rm -f $DKIMPROXY_OUT_PID
RETVAL=$?
[ $RETVAL -eq 0 ] && echo done. || echo failed.
exit $RETVAL
else
echo not running.
fi
;;
restart)
$0 stop && $0 start || exit $?
;;
status)
echo -n "dkimproxy.out..."
if [ -f $DKIMPROXY_OUT_PID ]; then
pid=`cat $DKIMPROXY_OUT_PID`
if ps -ef |grep -v grep |grep -q "$pid"; then
echo " running (pid=$pid)"
else
echo " stopped (pid=$pid not found)"
fi
else
echo " stopped"
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
Save this, and then we can get this to start on start up.
sudo chmod +x /etc/init.d/dkimproxy
I prefer to use sysv-rc-conf (http://sysv-rc-conf.sourceforge.net/) to manage startup events, so if you don’t have it, I highly recommend it:
sudo aptitude install sysv-rc-conf (or sudo apt-get install sysv-rc-conf)
sudo sysv-rc-conf --priority
Then I found the dkimproxy line and used the following values (I chose S21 as Postfix starts on S20… makes sense to me)
dkimproxy [K20 ] [S21 ] [S21 ] [S21 ] [S21 ] [K20 ] [ ] [ ]
Done!
Now we can start dkimproxy:
sudo /etc/init.d/dkimproxy start
Restart postfix:
sudo /etc/init.d/postfix restart
Then make sure that your mail is sending through port 587 – as PocketSmith is a rails app and we have our mail configuration in config/initializers/mail.rb, I just added in :port => ‘587′ instead of :port => ‘25′ and we were good to go.
All mail that is sent through port 587 will go through DKIMProxy, creating the DKIM and DomainKeys Signatures in the headers, or it will skip it if through port 25.
Some gotchas
You need to remove the test from the DNS entry when you are 100% happy that things are working correctly. I’m going to do this 24 hours from now, once I am 100% sure that the DKIM and DomainKeys records have completely, 100%, without-a-doubt propogated around the world.
The reason for this is that ONCE YOU REMOVE THIS TEST, YOU ARE EFFECTIVELY TELLING ALL SERVERS THAT MESSAGES WITHOUT A GOOD DKIM OR DOMAINKEYS IS SPAM.
I found with testing that the Yahoo Mail Authentication servers update at different times. I tested this by sending a flurry of emails from PocketSmith, all to the same account, and here are the results:
Authentication-Results: mta137.mail.re4.yahoo.com from=pocketsmith.com; domainkeys=pass (ok); from=pocketsmith.com; dkim=pass (ok)
Authentication-Results: mta172.mail.ac4.yahoo.com from=pocketsmith.com; domainkeys=fail (bad sig); from=pocketsmith.com; dkim=permerror (bad sig)
This is because I had the DNS record setup for a key from our testing server, while I butchered my way through getting this setup (and writing this article).
It appears that mta137 has obtained the correct record, but mta172 has not yet got the correct record – it is using the old one. Gmail however only uses one server for the Auth testing: mx.google.com. This means that once this one server has the correct DNS entry cached, all is well. Trust Yahoo to make things harder
So, I’m going to be patient, and wait before I remove that test call. And I’ll send through a lot more emails (around say, 10 – 15) from the application to my test yahoo address and check that all the mta’s are seeing the correct DKIM DNS record, in about 24 hours.
Remember – patience is key here – if you remove the test from the DNS record too quickly, you’re telling the world that you are 100% ready to go when you may not be!
And there you have it – you are one step closer to not being thrown in the spam folder :D
Update 6th July 2009 – I just sent through 15 test emails to the Yahoo test account, and all of them passed DomainKeys and DKIM authorisation. Success! Looks like Yahoo does like the caching of the DKIM DNS entry quite a lot, so give it 24 hours before you panic – particularly if you set a relatively high TTL on the DNS record.
Tags: development, email, tips








Wow this was helpful thanks for writing it up. I’m on slicehost as well. I initially tried setting it up with dkim-filter (or dkim-milter, could never figure out the difference). Got it running but postfix wouldn’t send emails with the headers. So I came across this and decided to try dkimproxy.
Tip to future people reading this, the sample /etc/init.d/dkimproxy above has tons of weird characters in it from being converted to html. Watch out for couple different kinds of quotes, ellipsis, and a strange dash character that should really be a double dash. Here is a plain text verion:
http://www.pastie.org/579385
If I send mail from the server manually with “telnet localhost 587″ it works now and puts in the headers. For some reason I still haven’t gotten my rails app to send emails with the dkim header tho.
I added this in environment.rb but no luck:
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
:domain => “feedmailpro.com”,
:port => 587
}
Will keep workin on it.
Anyway thanks for the writeup!
Brian Armstrong
Question, can you clarify why “mail._domainkey.pocketsmith.com” is the domain but you entered only “mail._domainkey” in the slicehost name field? Should there be a period after it like “mail._domainkey.”?
Thanks!
Brian
Ok well I ended up using dkim-filter for this, if anyone else wants more info on getting it working with multiple domains I posted some stuff here:
http://serverfault.com/questions/52830/dkim-sign-outgoing-mail-from-any-domain-with-postfix-and-ubuntu/52844
Hi Brian,
Pleased you found the article useful!
Thanks heaps for putting up that pastie plain-text version – I’ll edit the post and put this in it as well.
In terms of the mail configuration, the below is what we use:
ActionMailer::Base.smtp_settings = {
:address => “localhost”,
:port => “587″,
:domain => “mail.pocketsmith.com”
}
Perhaps forcing :address to be localhost could be it?
Our mail setup is in config/initializers/mail.rb, just for conveniences sake (feels nicer here than environment.rb).
I don’t believe there should be a period after it – a period (I believe) invokes that ‘this is the final field – don’t put the domain this relates to after it’. For example, when setting up the A records for pocketsmith.com, the record does need to have the dot – ‘pocketsmith.com.’ as the field – however when I’m doing a subdomain (which the domainkey thing pretty much is) I want pocketsmith.com to be appended after it – hence no dot. I’m pretty sure this is right – regardless it works so it is likely correct
Anyway, if you want to go back so you can do both DomainKeys and DKIM, try and throw in the :address => “localhost” in your mail settings and see if that helps along – if it does, I’ll update the post with this.
Thanks Brian!
I’ve tried various configurations and using one that is essentially identical to your (except it’s using my domain) i’m getting:
DKIM Signing – skipped
In /var/log/mail.log
Did you ever come across this and if so any pointers at to how to solve it…i feel like i’ve tried everything!
Hello,
nice article. I’m going to give it a shot on an rel box. Where do you get the k=, t=, and m= parameters?
@Jon – that is odd – are you sending the mail through port 587? Is the DKIM Proxy process running?
@Dave – these are pretty standard – k=rsa is the key type i believe, t=y means this is in test mode, and then p= is the content from public.key. Let me know if you need any more help
Sorry it didn’t work for me
it worked for me
I am definitely bookmarking this page and sharing it with my friends.
Your post is pure gold, man. Thank you so much for writing it up for us. I have a feeling this could have been just torturous, but with your instructions … it has taken me about 4 hours instead of 4 days!
Good luck to your pocketsmith application.
Kevin
@Kevin – thanks for the comment! Really pleased that it all worked for you. In fact, with a recent re-deployment I went through and followed this through again, and it worked again for me again too. A good sign.
Thanks also for the well-wishes, may your DKIM signed emails delivery be swift and may snailmailr.com go well!