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.
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.
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.
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.)
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.
This file does work with the 7.05 patch. -jms1 2007-12-12
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://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.
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...
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.
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:
If DKSIGN exists, the message will be signed.
If DKSIGN does not exist but DKVERIFY does, any signature on the incoming message will be verified.
Otherwise (if neither variable exists) it will examine the sender's email address. If it has a key matching the domain name, it will sign the message, otherwise it will try to verify any signature it sees.
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:
|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.
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;
This is the signature itself. Now look for this header:
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.
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:
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:
'_domainkey.domain.xyz:t=y; o=~; email@example.com
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:
t=y: Tells the remote servers that you are testing the domainkeys implementation for this domain. This should stay until you are 100% sure that you are happy with the implementation.
Note that removing this cannot "undo" the effects of a "t=y" tag in a key record. This value is normally used if you have multiple keys for your domain, and want to have all of them be considered "test" keys.
o=~: Tells the remote servers that your domain does not require that every message sent from the domain must be signed.
If you decide that you do want all of your domain's outbound mail to be signed, and are okay with the fact that this may cause other servers to drop or reject mail which is not signed, you can use o=- instead.
firstname.lastname@example.org This is a human-readable tag so that if another server has a problem with your key, they will know the correct email address with which to contact you. The software ignores this, if present.
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
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...
email@example.com, is Yahoo!'s testing server. When you send a message to this address, it will send you back a message telling you whether or not the domainkeys signature was valid.
firstname.lastname@example.org is a free service from the sendmail people. It's very similar to the Yahoo! address, but it also shows you the results of an SPF check as well.