NAME Mail::DKIM::Iterator
Iterativ validation of DKIM records or DKIM signing of mails.
SYNOPSIS
# Verify all DKIM signature headers found within a mail
my $mailfile = $ARGV[0];
use Mail::DKIM::Iterator;
use Net::DNS;
my %dnscache;
my $res = Net::DNS::Resolver->new;
# Create a new Mail::DKIM::Iterator object and feed pieces of the mail
# into it until one gets the feedback that these are enough data to
# verify the signature.
# If no usable DKIM-Signature header was found enough means already at
# the end of the mail header, otherwise it usually needs the full mail
# (unless there is a length limit for the body in the signature).
open( my $fh,'<',$mailfile) or die $!;
my $dkim = Mail::DKIM::Iterator->new(dns => \%dnscache);
my $rv;
while (1) {
if (read($fh,$buf,8192)) {
$rv = $dkim->append($buf) and last;
} else {
$rv = $dkim->append('');
last;
}
}
# Once all signature headers are found and enough body is read so that
# the body hash is known we need the DKIM keys found in the DNS. The
# result we have so far are returned in a VerifyRecord for each
# DKIM-Signature in the mail header. If $r->status is not yet # defined
# we need to look up the TXT record and feed it into the
# Mail::DKIM::Iterator object using
# $dkim->verify({ dnsname => $dkim_record }).
# We do that until all names are resolved or we got error in resolving
# the name.
check_rv:
my $retry = 0;
my %dns;
for(@$rv) {
defined $_->status and next; # got already record
my $dnsname = $_->dnsname;
$retry++;
if (my $q = $res->query($dnsname,'TXT')) {
$dns{$dnsname} = [
map { $_->type eq 'TXT' ? ($_->txtdata) : () }
$q->answer
];
} else {
$dns{$dnsname} = undef;
}
}
if ($retry) {
$rv = $dkim->verify(\%dns);
goto check_rv;
}
# This final result consists of a VerifyRecord for each DKIM signature
# in the header, which provides access to the status. Status is one of
# of DKIM_SUCCESS, DKIM_PERMFAIL, DKIM_TEMPFAIL, DKIM_SOFTFAIL or
# DKIM_INVALID_HDR. In case of error $record->error contains a string
# representation of the error.
for(@$rv) {
my $status = $_->status;
my $name = $_->domain;
if (!defined $status) {
print STDERR "$mailfile: $name UNKNOWN\n";
} elsif ($status DKIM_SUCCESS) {
# fully validated
print STDERR "$mailfile: $name OK\n";
} elsif ($status == DKIM_PERMFAIL) {
# hard error
print STDERR "$mailfile: $name FAIL ".$_->error."\n";
} else {
# soft-fail, temp-fail, invalid-header
print STDERR "$mailfile: $name $status ".$_->error."\n";
}
}
# Create signature for a mail
my $mailfile = $ARGV[0];
use Mail::DKIM::Iterator;
my $dkim = Mail::DKIM::Iterator->new(sign => [{
c => 'relaxed/relaxed',
a => 'rsa-sha1',
d => 'example.com',
s => 'foobar',
':key' => ... PEM string for private key or Crypt::OpenSSL::RSA object
}]);
open(my $fh,'<',$mailfile) or die $!;
my $rv;
while (!$rv || grep { !defined $_->status } @$rv) {
if (read($fh, my $buf,8192)) {
$rv = $dkim->append($buf);
} else {
$rv = $dkim->append('');
last;
}
}
for(@$rv) {
my $status = $_->status;
my $name = $_->domain;
if (!defined $status) {
print STDERR "$mailfile: $name UNKNOWN\n";
} elsif (status != DKIM_SUCCESS) {
print STDERR "$mailfile: $name $status - ".$_->error."\n";
} else {
# show signature
print $_->signature;
}
}
DESCRIPTION
With this module one can validate DKIM Signatures in mails. The main difference to Mail::DKIM is that the validation can be done iterativ, that is the mail can be streamed into the object and if DNS lookups are necessary the results can be added to the DKIM object asynchronously. There are no blocking operation or waiting for input, everything is directly driven by input into this module.
This module implements only DKIM according to RFC 6376. It does not support the historic DomainKeys standard (RFC 4870).
The following methods are relevant. For details of their use see the examples in the SYNOPSIS.
- new(%args) -> $dkim
-
This will create a new object. The following arguments are supported
- dns => \%hash
-
A hash with the DNS name as key and the DKIM record for this name as value. This can be used as a common DNS cache shared over multiple instances of the class. If none is given only a local hash will be created inside the object.
- sign => \@dkim_sig
-
List of DKIM signatures which should be used for signing the mail (usually only a single one). These can be given as string or hash (see
parse_signature
below). These DKIM signatures are only used to collect the relevant information from the header and body of the mail, the actual signing is done in the SignRecord object (see below). - sign_and_verify => 0|1
-
Usually it either signs the mail (if
sign
is given) or validates signatures inside the mail. When this option is true it will validate existing signatures additionally to creating new signatures ifsign
is used.
- $dkim->append($buffer) -> $rv
-
This is used to append a new chunk from the mail. To signal the end of the mail
$buffer
should be''
.If more data are needed
$rv
will be undef. Otherwise$rv
will be a list of result records, one for each DKIM-Signature record in the mail and in the same order. If no such headers are found this list is empty. For details of the result records seeresult
. - $dkim->result([\%dns]) -> $rv
-
If
%dns
is given it will be used to update the internal mappings between DNS name and DKIM record (seenew
) which then will be used to update any record validations. The result will be returned, i.e. undef if still data are needed from the mail or$rv
with a list of records. Each of these records is either a VerifyRecord (in case of DKIM verification) or a SignRecord (in case of DKIM signing).Both VerifyRecord and SignRecord have the following methods:
- status - undef if no DKIM result is yet known for the record. Otherwise any of DKIM_SUCCESS, DKIM_INVALID_HDR, DKIM_TEMPFAIL, DKIM_SOFTFAIL, DKIM_PERMFAIL.
- error - an error description in case the status shows an error, i.e. with all status values except undef and DKIM_SUCCESS.
- sig - the DKIM signature as hash
- domain - the domain value from the DKIM signature
- dnsname - the dnsname value, i.e. based on domain and selector
A SignRecord has additionally the following methods:
If any of the result records contains a status of undef the user should do a DNS lookup for a TXT record for the name given in the record and feed it back into the object using
verify
.
Apart from these methods the following utility functions are provided
- parse_signature($dkim_sig,\$error) -> \%dkim_sig|undef
-
This parses the value from the DKIM-Signature field of mail and returns it as a hash. On any problems while interpreting the value undef will be returned and
$error
will be filled with a string representation of the problem. - parse_dkimkey($dkim_key,\$error) -> \%dkim_key|undef
-
This parses a DKIM key which is usually found as a TXT record in DNS and returns it as a hash. On any problems while interpreting the value undef will be returned and
$error
will be filled with a string representation of the problem. - sign($dkim_sig,$priv_key,$hdr,\$error) -> $signed_dkim_sig
-
This takes a DKIM signature
$dkim_sig
(as string or hash), an RSA private key$priv_key
(as PEM string or Crypt::OpenSSL::RSA object) and the header of the mail and computes the signature. The result$signed_dkim_sig
will be a signature string which can be put on top of the mail.On errors $error will be set and undef will returned.
SEE ALSO
AUTHOR
Steffen Ullrich <sullr[at]cpan[dot]org>
COPYRIGHT
Steffen Ullrich, 2015
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.