http:// qmail.jms1.net / patches / domainkeys.shtml

qmail patch - domainkeys add-on

This add-on patch enables a qmail system with my combined patch to apply a domainkeys signatures to outgoing mail, as well as verify domainkeys signatures for incoming mail. This patch is an add-on for my combined patch, meant to be applied AFTER applying the combined patch but before compiling the code.

I no longer recommend this patch.

At the time I originally wrote this page (and the add-on patch it talks about), I was using the qmail-dk program because it was the only option out there for working with Domainkeys. Since then, Kyle Wheeler has written a script which applies both Domainkeys and DKIM signatures on messages as they leave the queue. I've always thought it would make more sense to add these signatures as the message is leaving the queue, so I immediately tried his scripts when I found them, and I'm still using them on my own server now.

He has also come up with a way to verify both Domainkeys and DKIM signatures as messages come into the queue (same link), which makes this add-on patch (and the qmail-dk program) pretty much useless. I haven't had time to figure out the verification scripts yet, but once I do, I plan to do away with this add-on patch entirely. The patch will no longer be available for download, the directions below will no longer be on my web site, and I will no longer offer any non-paid support for it.

If you are able to do so, I strongly recommend you download the scripts from Kyle's page, and use those instead of this patch. Because they are scripts, they should be able to work with ANY version of qmail, regardless of which patches you're using.

I'm leaving the remainder of this page here for now, but once I find the time to become familiar with, and document, Kyle's verification scripts, I plan to use this page to more fully document his scripts and how to set them up (since his own documentation consists of only a few paragraphs.)

domainkeys is a system developed by Yahoo! which enables mail servers to cryptographically sign the outbound messages they send, and allows the recipients of these signed messages to verify that they were sent by a server which is authorized to send mail for that domain.

This would help to prevent "spoofing", which is where a spammer would send an email which claims to have been sent by you (or by a user in your domain) but was not. Mail servers which are able to check these signatures would be able to reject messages with invalid (or in some cases, missing) signatures as forgeries.

The concept is simliar to SPF, but it's based on an actual cryptographic signature in the message headers rather than on the IP address of the machine which tries to send the message.


Pre-requisite Packages

If you are going to use the patch, you will need to download and install the libdomainkeys package, which contains the actual functions which apply and check the signatures. Their download package (as of version 0.64) does not include an installation routine, so I will show how to manually install the package after you compile it.

Note that this package requires that the openssl libraries and development header files already be installed on the machine. These are normally either included with, or available from, your OS distribution. Please make sure you have these packages installed before proceeding.

Start by downloading the source code from the SourceForge project page. Then we will use the following commands to build and "install" the package on your system. Note that these commands do not need to be run as root except where noted.

% tar xvzf libdomainkeys-0.68.tar.gz
...
% cd libdomainkeys-0.68
% make
You should see it compiling the software. You should NOT see any error messages at the end of the process.
% ./test
You should see it run a bunch of tests. You might see a failure on the very last test, "------TC30 '(pause)'". This appears to be related to a key which should be return a temporary failure, but is instead returning a hard failure for some reason. It appears to be harmless, the code works anyway.
% su
Password: Enter your root password. You should not see it as you type it.
# install -m 644 libdomainkeys.a /usr/local/lib/
# install -m 644 domainkeys.h dktrace.h /usr/local/include/
# install -m 755 dknewkey /usr/local/bin/
# exit

The Original Patch

This is NOT the patch file you should use, unless you know how to manually integrate it into whatever other patches you may be using on your own server. I'm including the link so that people can see where it came from, and look at the original documentation (such as it is.)

The original patch was written by Russell Nelson, somebody whose qmail experience makes me look like a beginner. He knows his stuff, but his instructions are rather sparse- they are embedded into the beginning on his patch file itself.

The way his directions are written, you have to specifically follow his instructions, line by line, and compile qmail within the libdomainkeys compile directory. I don't like the idea of having to combine what are essentially two separate packages like this, so I have modified his patch to use the libdomainkeys library from the normal system-wide locations where all of the other standard libraries are stored (and where we installed them above.)


Download

Note that this patch requires the packages above in order to compile. Because I don't expect everybody to need the domainkeys functionality, I can't see forcing everybody to download and install this other library just to make it compile, especially if it's something they are not going to need. Having this external dependency makes it impossible to maintain one single combined patch which can be configured into everybody's needs at run time (which is how it has been up until now.)

Therefore, I am releasing my version of the domainkeys patch as an "add-on" for my combined patch files. The idea is that you should start with djb's original source code, apply my combined patch, and then apply this one afterwards.

File: qmail-1.03-domainkeys-jms1.7.patch
This file does work with the 7.05 patch. -jms1 2007-12-12
Size: 15,948 bytes
MD5: 695c4129040949d5ef1d0f1b7f658b3e
SHA-1: 6364db8c328f9fdab0315cb86c79e858cd9ab449
RIPEMD-160: 0c77a4fdbb0c91111e7c3b6a91341da481163f94
PGP Signature: qmail-1.03-domainkeys-jms1.7.patch.asc

Compiling with this patch

This is an example of what the process of downloading and installing this patch, with the appropriate combined patch, should look like. It's the same basic process as compiling qmail with just the combined patch, but we're also downloading and installing the domainkeys add-on patch.

% wget http://cr.yp.to/software/qmail-1.03.tar.gz
...
% wget http://qmail.jms1.net/patches/qmail-1.03-jms1.7.patch
...
% wget http://qmail.jms1.net/patches/qmail-1.03-domainkeys-jms1.7.patch
...
% tar xvzf qmail-1.03.tar.gz
...
% mv qmail-1.03 qmail-1.03-patched
% cd qmail-1.03-patched
% patch < ../qmail-1.03-jms1.7.patch <-- This applies the normal combined patch...
...
% patch < ../qmail-1.03-domainkeys-jms1.7.patch <-- and this adds the domainkeys stuff.
...
% make
...
% su
Password:
This next command must be run as root...
# make setup check
...
Finish configuring qmail as root. When you're done, this will make you not-root again...
# exit
%


Setting up the service(s)

To set up a daemontools SMTP service which will also perform the domainkeys function, you first need to select a physical location for the service directory. This may be something like /var/qmail/supervise/qmail-smtp, or /var/service/smtp, or some other location. It can technically be done anywhere on the system, as long as the filesystem (the disk partition) which contains the directory will have enough room to store the log files which will build up.

The ony requirement is that it NOT BE PHYSICALLY LOCATED IN THE "/service" DIRECTORY. If you try to set it up here, you will discover that daemontools will very quickly complain about not being able to start the service, and once you do get it running, if you ever decide to shut it down, you will run into problems.

For this example, we will use "/var/service/smtp" as the physical location of the service directory.

These commands should be run as root.
# mkdir -m 1755 /var/service/smtp
# cd /var/service/smtp
# wget http://qmail.jms1.net/scripts/run.smtp.sslserver
# mv run.smtp.sslserver run

Now edit the "run" script using your editor of choice. Set the options as needed for your service. The file itself contains documentation on the options you can set within the file.

# chmod 700 run
# mkdir -m 755 log
# cd log
# wget http://qmail.jms1.net/scripts/run.log
# mv run.log run
# chmod 700 run

The final step is to start the service running.

# ln -s /var/service/smtp /service/

Wait about ten seconds, and then make sure the service is running correctly.

# svstat /service/smtp
/service/smtp: up (pid 24620) 7 seconds

The number of seconds should be three or greater, and if you re-run the same command again, you should see the count going up rather than cycling back to zero. If the count never passes three, or if the service is not listed as "up" to start with, check the logs to see what's going on.

# tail log/main/current

Also note that this is the same procedure you would follow to set up any SMTP server, whether it supports domainkeys or not.


How the qmail-dk program works

The qmail-dk program works in one of two modes- either it verifies the signature on a message, or it adds a signature to a message. The mode is selected by the presence or absence of two environment variables, called DKSIGN and DKVERIFY. The decision is made as follows:

In order to sign a message, qmail-dk needs a private key. These keys are usually stored in a default directory, and the DKSIGN variable (if set) can specify a different directory. The default location is /etc/domainkeys/%/default, where "%" is replaced with the sender's domain name.

This is actually a very elegant way of handling things. The patch author's example shows the DKVERIFY variable being set in /etc/tcp/smtp.cdb for "the world at large", and a DKSIGN variable being set (with no DKVERIFY being set) for any lines where RELAYCLIENT="" is also set.

This works for situations where all of the machines sending outbound email have "trusted" IP addresses. However, if a connection gains its permission to relay by virtue of the AUTH command, any DKVERIFY variable which may have already existed is still there.

My AUTH_SET patch adds the ability to make changes to the environment when a successful AUTH command is given. This allows the smtp.cdb file to have a DKVERIFY variable, which causes messages to be verified instead of signed, but also to add a DKSIGN value when the user does a successful AUTH command, so that their outbound message can be signed.

Of course if you want to always sign messages from certain IP addresses, you should add ,DKSIGN="/etc/domainkeys/%/default" to the appropriate line(s) in your tcpserver access control file- even if you store your keys in the default directory (as shown here.)

When a message is being verified, the contents of the DKVERIFY variable configure how qmail-dk reacts to the various test result conditions which may occur when verifying a message. Each possible test result is represented by a letter. If the lowercase version of that letter is found in the DKVERIFY value, that result will be treated as a soft error (i.e. the server will return an error code to the client saying "try this message again later".) If the UPPERCASE version is found, that result will be treated as a hard error (i.e. the server tells the client to stop trying.)

The letters correspond to the result codes as follows:

Letter Code Description
A OK The message contained a signature which correctly matched the contents of the message.
B BADSIG The message contained a signature which DID NOT correctly match the contents of the message. The signature may be forged, or the content may have been changed after the original server applied the signature.
C NOSIG The message did not contain a DomainKey-Signature header, or contained one which was missing a required field, or had a signature header without a "From:" header.
D NOKEY The public key needed to verify the signature does not exist (i.e. the authoritative DNS server for the domain says that the TXT record which should contain the key does not exist.)
E BADKEY The public key which was found in DNS is not usable.
F CANTVRFY The plublic key needed to verify the signature cannot be found, because the DNS server which should have the key is not responding, or returned a temporary error condition. The domainkeys specification says that the server SHOULD treat this as a soft error, telling the client to try their delivery again at some point in the future.
G SYNTAX The message is not in the proper format. This could be an improperly formatted email address, a duplicate "From:" header in the message, or any number of things which "confuse" the program.
H NORESOURCE Out of memory. The domainkeys specification says that the server SHOULD treat this as a soft error, telling the client to try their delivery again at some point in the future.
I ARGS Arguments are not usable.
I know, this is not the best description in the world, but that's all it says in the documentation. It looks to me like it may have to do with the DomainKeys-Signature header claiming to use an encryption method which the server does not support.
J REVOKED The key which was used to generate the signature has been revoked.
K INTERNAL Internal error within the qmail-dk program.

The value recommended by Russell Nelson, the original author of the patch, is "DEGIJKfh", meaning that the D, E, G, I, J, and K result codes are "hard errors", while the F and H result codes are "soft errors".

Also note that the order of the letters within the value is not important. "GIJfhDEK" and "DEfGhIJK" have the same effect. Use whatever makes sense to you.


Testing verification of incoming mail

The easiest way to test that incoming mail is being properly verified is to send yourself an email from a system which applies domainkeys signatures to their outgoing messages. Both Yahoo! and Google's gmail services do this, or you may be able to ask for somebody with a working domainkeys config on the qmailrocks mailing list to send you a test message.

When the message arrives in your inbox, examine the headers. You should see the following two headers in the message:

DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
  s=default; d=senders.domain.name;
  b=aXYiTtndWoFrXEERsnQqcvGJlQ1awpO99jez... ;

This is the signature itself. Now look for this header:

DomainKey-Status: good

The DomainKey-Status header tells you that a verification check was done. The result (in this case "good") tells you that the signature did match the original message, as it existed when it left the server which signed it.

I don't know yet what kinds of problems people may have with this process- the only problem I ran into when testing this was forgetting to export the DKVERIFY environment variable, and that's fixed with the updated "run" script. I guess if you have problems with this, join the qmailrocks mailing list and ask about it there. I would rather you ask on the list than to me directly, that way others who may be having the same problem will be able to benefit from the answer.


Generating keys

If you wish to go beyond just verifying incoming signatures, and actually start applying domainkeys signatures to your outbound email, you will need to generate a key pair and store it on the server where the qmail-dk program can find it. This example shows how, using "domain.xyz" as the domain name.

These commands should be run as root.
# mkdir -p /etc/domainkeys/domain.xyz
# cd /etc/domainkeys/domain.xyz
# dknewkey default 1024 > default.pub

After creating or changing keys, you should run these two commands to ensure that the files have the correct ownership and permissions. Note that the group ID listed in the chown command should be the same as the group under which qmail-smtpd runs.

# chown -R root:nofiles /etc/domainkeys
# chmod -R g=u-w,o= /etc/domainkeys

This creates two files. "default" is the private key, and "default.pub" is the public key, formatted as a DNS record. If the "dknewkey" script sees that you have djbdns installed on your system, this will be formatted as a tinydns-data line. Otherwise, it writes out an "IN TXT" line as needed by BIND. This example will continue on the assumption that you are using tinydns.

Make sure to safeguard this "default" file. If an attacker were able to get this file, they would be able to forge your domain's signature to any message they choose to send, and there would be no way to reliably tell whether a given signature really came from your server or from their server.

You should also keep a backup of the "default" file in a secure location. If your server has a hardware problem and you lose the files entirely, you will have to generate a new key pair AND any messages which were signed with the old key will no longer be able to be verified.

The next step is to edit the "default.pub" file. Since different people have different editors, I will not show an actual sequence of commands- you are expected to know how to work your editor of choice.

The file itself consists of one line of text. At the beginning of the line you will see the following:

'default._domainkey.example.com:k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCVC...

Change the domain name portion of the name from "example.com" to your own domain name, add "t=y; " to the beginning of the string, and add a second line which advertises your domain's domainkeys policy:

'default._domainkey.domain.xyz:t=y; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCVC...
'_domainkey.domain.xyz:t=y; o=~; r=postmaster@domain.xyz

The "t=y; " value that you are adding to the first line (the actual key record) is a flag which tells other servers that you are TESTING this key. You should leave this here until you are 100% sure that your server is properly signing outbound messages.

This tag may affect how other servers handle messages with invalid signatures based on the key- they may report them to you as errors, rather than dropping or refusing them.

The second line (the domain policy record) tells other servers how you prefer them to handle messages which have bad signatures claiming to be from your domain. It consists of the following items, all of which are optional:

This list should not be considered as "the final word" on what your DNS records should contain. The information here is fairly accurate, but if there are any questions you should consult the current domainkeys specification, which should be available through Yahoo's domainkeys page.

The last step is to actually add the new record to your DNS data. This example assumes that your server is the authoritative nameserver for your domain.

# cat default.pub >> /service/tinydns/root/data
# cd /service/tinydns/root
# make


Testing outbound signatures

Once you have installed your private key file and added your public key to your DNS data, you should test the server and make sure that your outbound message are having the proper signatures added to them. This is actually a lot easier than it sounds.

All you need to do is send a message through the server you're testing, to an automated testing service. There are several of them, listed on the domainkeys sourceforge page.

A few examples are...