Hi all! I have managed to run a perl script (called from .qmail or control/defaultdelivery) which performs SPF and DKIM checks on incoming messages. It is work in progress and needs some tuning as white-listing IP-nets is hardcoded (when sending from Intranet) and $dkim object usage, behavior on soft/hard-errors is hardcoded too, etc. Still it is working well on our ~150 mailboxes server so far. This is temporary solution for me until real qmail-src implementation is (probably) added for the SPF and/or DKIM RFCs. Feel free to enhance it and provide feedback.
[attached: abusecheck.pl] -- Zdravko Stoychev System Software and Support MPS Ltd. [EMAIL PROTECTED] +359-2-491-1827 (ext.271) Not answered? http://6lyokavitza.org/mail PGP Public Key: http://keyserver.kjsl.com:11371/pks/lookup?op=get&search=0x2C64101C Key Fingerprint: EC7E CA74 952D F343 2AAD 0F34 857D E27D 2C64 101C This e-mail is intended only for the addressee(s) and may contain privileged and confidential information. It should not be disse- minated, distributed, or copied. If you have received this e-mail message by mistake, please inform the sender, and delete it from your system.
#!/usr/bin/perl
#
# If you are using qmail as your MTA stick this file in the
# /var/qmail/bin/abusecheck.pl and chmod +x it, and then for
# each .qmail file which needs SPF/DKIM processing just add
#
# |/var/qmail/bin/abusecheck.pl
#
# to the top of the .qmail file -- normally before the Maildir
# line.
#
# By default, this rejects nothing, but adds the Received-SPF
# or X-SPF-Guess header (for SPF), or Authentication-Results
# header (for DKIM). Rejecting is pretty useless as you know
# that the sender is forged!
#
# Philip Gladstone - original SPF implementation
# Zdravko Stoychev - added DKIM implementation
#
use strict;
use Sys::Hostname;
use Mail::SPF::Query;
use Mail::DKIM::Verifier;
my $sender = $ENV{SENDER};
my $src;
my $helo;
my $ourhost = hostname;
my $softfail = 1; # 0 = Don't delay ERROR messages,
# 1 = Delay SPF/DKIM msgs temporary
my $hardfail = 1; # 0 = Don't reject DENY messages,
# 1 = Reject SPF/DKIM FAIL messages
if (exists $ENV{RELAYCLIENT}) {
# This is a well-known allowed relaying client.
# Signify that delivery is to continue
exit 0;
}
my @msg;
my $dkim = Mail::DKIM::Verifier->new();
while (<>) {
push @msg, $_;
if (/^Received-SPF/i || /^X-SPF-Guess/i || /^Authentication-Results/i) {
# I tought to check that it is my record but since it appears
# before the first received, it probably is mine.
# Signify that delivery is to continue
exit 0;
}
# remove local line terminators
chomp;
s/\015$//;
# use SMTP line terminators
$dkim->PRINT("$_\015\012");
if (!$sender && /^Return-path: <(.*)>/i) {
$sender = $1;
}
if (!$src && /^Received:\s*from\s+(\S+)\s.*\(\[([.\d]+)\]\)/i) {
my $from = $1;
$src = $2;
($helo) = /\(HELO (\S+)\)/;
$helo ||= $from;
last;
}
}
if (!$src || $src =~ /^10\.0\./ || $src =~ /^192\.168\.0\./) {
# This is probably a local delivery or sending from Intranet
# Signify that delivery is to continue
exit 0;
}
# OK, perform SPF check...
my $spfheader = "Received-SPF"; # No spaces please!
my $query = Mail::SPF::Query->new(ip => $src, sender => $sender, helo => $helo,
guess => 1, trusted => 1, myhostname => $ourhost);
my ($spfresult, $spferror, $spfcomment) = $query->result();
if ($spfresult eq "fail") {
# domain is forged!!!
if ($hardfail) {
print "SPF failed: $spferror";
# Fail the delivery
exit 100;
}
} elsif ($spfresult eq "error") {
# DNS temporary error
if ($softfail) {
print $spferror;
# Delay the delivery
exit 111;
}
}
$spfresult ||= 'unknown';
my $spfoutput = "$spfresult";
if ($spfcomment ne "") {
$spfoutput = "$spfoutput ($spfcomment)";
}
# NEXT, perform DKIM check...
while (<>) {
push @msg, $_;
# remove local line terminators
chomp;
s/\015$//;
# use SMTP line terminators
$dkim->PRINT("$_\015\012");
}
$dkim->CLOSE;
# what is the result of the verify?
my $dkimheader = "Authentication-Results"; # No spaces please!
my $dkimresult = $dkim->result;
my $dkimpolicy = $dkim->fetch_author_policy;
my $dkimcomment = $dkim->result_detail;
if ($dkimresult eq "fail") {
# domain is forged!!!
if (! $dkimpolicy->testing && $hardfail) {
print "DKIM failed: $dkimcomment";
# Fail the delivery
exit 100;
}
} elsif ($dkimresult eq "invalid") {
# SIG temporary error
if ($softfail) {
print "DKIM signature: $dkimcomment";
# Delay the delivery
exit 111;
}
}
$dkimresult ||= 'unknown';
my $dkimwhy = $dkimpolicy->apply($dkim);
my $dkimoutput = "$ourhost; mail.from=$sender; dkim=$dkimcomment;
dkim.policy=$dkimwhy";
if ($dkimpolicy->testing) {
$dkimoutput = "$dkimoutput (testing)";
}
# Done
($ENV{QMAILSUSER}, $ENV{QMAILSHOST}) = $sender =~ /(.*)@([EMAIL PROTECTED])/;
# Instead using LOCAL you might want to use RECIPIENT bellow,
# depending of your qmail configuration
open (MAIL, "|-") || exec 'qmail-inject', $ENV{LOCAL};
# output checks results
print MAIL "$dkimheader: $dkimoutput\n";
print MAIL "$spfheader: $spfoutput\n";
# output the message
print MAIL @msg;
# just in case
#while (<>) { print MAIL; }
close(MAIL) || exit 0;
# Delivery OK, but don't continue in .qmail file
exit 99;
smime.p7s
Description: S/MIME Cryptographic Signature
