tested and working Authentication-Results
changed the method of saving results. Instead of appending to/from a header, plugins save results to a connection note. Qpsmtpd::SMTP.pm has a new method that inserts the Authentication-Results header The smtp-auth information has been removed from the Received header Authentication-Results providing plugins have been updated to store results in connection note
This commit is contained in:
parent
4ae16219bd
commit
4d489ea6ef
@ -7,7 +7,7 @@ use Qpsmtpd::Constants;
|
|||||||
|
|
||||||
#use DashProfiler;
|
#use DashProfiler;
|
||||||
|
|
||||||
$VERSION = "0.92";
|
$VERSION = "0.93";
|
||||||
|
|
||||||
my $git;
|
my $git;
|
||||||
|
|
||||||
|
@ -272,31 +272,14 @@ sub store_deferred_reject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub store_auth_results {
|
sub store_auth_results {
|
||||||
my ($self, $value) = @_;
|
my ($self, $result) = @_;
|
||||||
|
my $auths = $self->qp->connection->notes('authentication_results') or do {
|
||||||
my @headers = $self->transaction->header->get('Authentication-Results');
|
$self->qp->connection->notes('authentication_results', $result);
|
||||||
chomp @headers;
|
return;
|
||||||
my @deleteme;
|
|
||||||
for ( my $i = 0; $i < scalar @headers; $i++ ) {
|
|
||||||
my @values = split /;/, $headers[$i];
|
|
||||||
if ( $self->config->('me') ne $values[0] ) { # some other MTA
|
|
||||||
# we generally want to remove Authentication-Results headers added by other
|
|
||||||
# MTAs (so our downstream can trust the A-R header we insert), but we also
|
|
||||||
# don't want to invalidate DKIM signatures.
|
|
||||||
# TODO: parse the DKIM signature(s) to see if A-R header is signed
|
|
||||||
if ( ! $self->transaction->header->get('DKIM-Signature') ) {
|
|
||||||
$self->log(LOGINFO, "deleted auth-results from $_");
|
|
||||||
push @deleteme, $i;
|
|
||||||
};
|
|
||||||
next;
|
|
||||||
};
|
|
||||||
push @values, $value;
|
|
||||||
$self->log(LOGINFO, "appended to auth-results: $value");
|
|
||||||
$self->transaction->header->replace('Authentication->Results', join('; ', @values ), $i);
|
|
||||||
}
|
|
||||||
foreach ( @deleteme ) {
|
|
||||||
$self->transaction->header->delete('Authentication-Results', $_);
|
|
||||||
};
|
};
|
||||||
|
my $ar = join('; ', $auths, $result);
|
||||||
|
$self->log(LOGDEBUG, "auth-results: $ar");
|
||||||
|
$self->qp->connection->notes('authentication_results', $ar );
|
||||||
};
|
};
|
||||||
|
|
||||||
sub init_resolver {
|
sub init_resolver {
|
||||||
|
@ -766,43 +766,6 @@ sub data_respond {
|
|||||||
|
|
||||||
$self->log(LOGDEBUG, "max_size: $max_size / size: $size");
|
$self->log(LOGDEBUG, "max_size: $max_size / size: $size");
|
||||||
|
|
||||||
my $smtp = $self->connection->hello eq "ehlo" ? "ESMTP" : "SMTP";
|
|
||||||
my $esmtp = substr($smtp, 0, 1) eq "E";
|
|
||||||
my $authheader = '';
|
|
||||||
my $sslheader = '';
|
|
||||||
my $auth_result = 'none';
|
|
||||||
|
|
||||||
if (defined $self->connection->notes('tls_enabled')
|
|
||||||
and $self->connection->notes('tls_enabled'))
|
|
||||||
{
|
|
||||||
$smtp .= "S" if $esmtp; # RFC3848
|
|
||||||
$sslheader = "("
|
|
||||||
. $self->connection->notes('tls_socket')->get_cipher()
|
|
||||||
. " encrypted) ";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defined $self->{_auth} ) {
|
|
||||||
my $mech = $self->{_auth_mechanism};
|
|
||||||
my $user = $self->{_auth_user};
|
|
||||||
$auth_result = "auth=";
|
|
||||||
if ( $self->{_auth} == OK) {
|
|
||||||
$smtp .= "A" if $esmtp; # RFC3848
|
|
||||||
$authheader = "(smtp-auth username $user, mechanism $mech)\n";
|
|
||||||
$auth_result .= 'pass';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$auth_result .= 'fail';
|
|
||||||
};
|
|
||||||
$auth_result .= " ($mech) smtp.auth=$user";
|
|
||||||
}
|
|
||||||
|
|
||||||
$header->add('Received',
|
|
||||||
$self->received_line($smtp, $authheader, $sslheader), 0);
|
|
||||||
|
|
||||||
# RFC 5451: used in AUTH, DKIM, DOMAINKEYS, SENDERID, SPF
|
|
||||||
$header->add('Authentication-Results',
|
|
||||||
join('; ', $self->config('me'), $auth_result ) );
|
|
||||||
|
|
||||||
# if we get here without seeing a terminator, the connection is
|
# if we get here without seeing a terminator, the connection is
|
||||||
# probably dead.
|
# probably dead.
|
||||||
unless ($complete) {
|
unless ($complete) {
|
||||||
@ -823,8 +786,75 @@ sub data_respond {
|
|||||||
$self->run_hooks("data_post");
|
$self->run_hooks("data_post");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub authentication_results {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my @auth_list = $self->config('me');
|
||||||
|
# $self->clean_authentication_results();
|
||||||
|
|
||||||
|
if ( ! defined $self->{_auth} ) {
|
||||||
|
push @auth_list, 'auth=none';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $mechanism = "(" . $self->{_auth_mechanism} . ")";
|
||||||
|
my $user = "smtp.auth=" . $self->{_auth_user};
|
||||||
|
if ( $self->{_auth} == OK) {
|
||||||
|
push @auth_list, "auth=pass $mechanism $user";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push @auth_list, "auth=fail $mechanism $user";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# RFC 5451: used in AUTH, DKIM, DOMAINKEYS, SENDERID, SPF
|
||||||
|
if ( $self->connection->notes('authentication_results') ) {
|
||||||
|
push @auth_list, $self->connection->notes('authentication_results');
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->log(LOGDEBUG, "adding auth results header" );
|
||||||
|
$self->transaction->header->add('Authentication-Results', join('; ', @auth_list) );
|
||||||
|
};
|
||||||
|
|
||||||
|
sub clean_authentication_results {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
# On messages received from the internet, we may want to remove
|
||||||
|
# the Authentication-Results headers added by other MTAs, so our downstream
|
||||||
|
# can trust the new A-R header we insert.
|
||||||
|
# We do not want to invalidate DKIM signatures.
|
||||||
|
# TODO: parse the DKIM signature(s) to see if A-R header is signed
|
||||||
|
return if $self->transaction->header->get('DKIM-Signature');
|
||||||
|
|
||||||
|
my @headers = $self->transaction->header->get('Authentication-Results');
|
||||||
|
for ( my $i = 0; $i < scalar @headers; $i++ ) {
|
||||||
|
$self->transaction->header->delete('Authentication-Results', $i);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
sub received_line {
|
sub received_line {
|
||||||
my ($self, $smtp, $authheader, $sslheader) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $smtp = $self->connection->hello eq "ehlo" ? "ESMTP" : "SMTP";
|
||||||
|
my $esmtp = substr($smtp, 0, 1) eq "E";
|
||||||
|
my $authheader = '';
|
||||||
|
my $sslheader = '';
|
||||||
|
|
||||||
|
if (defined $self->connection->notes('tls_enabled')
|
||||||
|
and $self->connection->notes('tls_enabled'))
|
||||||
|
{
|
||||||
|
$smtp .= "S" if $esmtp; # RFC3848
|
||||||
|
$sslheader = "("
|
||||||
|
. $self->connection->notes('tls_socket')->get_cipher()
|
||||||
|
. " encrypted) ";
|
||||||
|
}
|
||||||
|
if (defined $self->{_auth} && $self->{_auth} == OK) {
|
||||||
|
my $mech = $self->{_auth_mechanism};
|
||||||
|
my $user = $self->{_auth_user};
|
||||||
|
$smtp .= "A" if $esmtp; # RFC3848
|
||||||
|
$authheader = "(smtp-auth username $user, mechanism $mech)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $header_str;
|
||||||
my ($rc, @received) =
|
my ($rc, @received) =
|
||||||
$self->run_hooks("received_line", $smtp, $authheader, $sslheader);
|
$self->run_hooks("received_line", $smtp, $authheader, $sslheader);
|
||||||
if ($rc == YIELD) {
|
if ($rc == YIELD) {
|
||||||
@ -834,7 +864,7 @@ sub received_line {
|
|||||||
return join("\n", @received);
|
return join("\n", @received);
|
||||||
}
|
}
|
||||||
else { # assume $rc == DECLINED
|
else { # assume $rc == DECLINED
|
||||||
return
|
$header_str =
|
||||||
"from "
|
"from "
|
||||||
. $self->connection->remote_info
|
. $self->connection->remote_info
|
||||||
. " (HELO "
|
. " (HELO "
|
||||||
@ -847,6 +877,7 @@ sub received_line {
|
|||||||
. ") with $sslheader$smtp; "
|
. ") with $sslheader$smtp; "
|
||||||
. (strftime('%a, %d %b %Y %H:%M:%S %z', localtime));
|
. (strftime('%a, %d %b %Y %H:%M:%S %z', localtime));
|
||||||
}
|
}
|
||||||
|
$self->transaction->header->add('Received', $header_str, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub data_post_respond {
|
sub data_post_respond {
|
||||||
@ -881,6 +912,8 @@ sub data_post_respond {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
$self->authentication_results();
|
||||||
|
$self->received_line();
|
||||||
$self->queue($self->transaction);
|
$self->queue($self->transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,11 @@ sub validate_it {
|
|||||||
my $result = $dkim->result;
|
my $result = $dkim->result;
|
||||||
my $mess = $self->get_details($dkim);
|
my $mess = $self->get_details($dkim);
|
||||||
|
|
||||||
$self->store_auth_results("dkim=" .$dkim->result_detail . " header.i=@".$dkim->signature->domain);
|
my $auth_str = "dkim=" .$dkim->result_detail;
|
||||||
|
if ( $dkim->signature && $dkim->signature->domain ) {
|
||||||
|
$auth_str .= " header.i=@" . $dkim->signature->domain;
|
||||||
|
};
|
||||||
|
$self->store_auth_results( $auth_str );
|
||||||
#$self->add_header($mess);
|
#$self->add_header($mess);
|
||||||
|
|
||||||
foreach my $t (qw/ pass fail invalid temperror none /) {
|
foreach my $t (qw/ pass fail invalid temperror none /) {
|
||||||
|
@ -43,7 +43,9 @@ the same terms as Perl itself.
|
|||||||
|
|
||||||
=head1 AUTHORS
|
=head1 AUTHORS
|
||||||
|
|
||||||
Matt Simerson - 2012
|
Matt Simerson - 2013 - safe results to Authentication-Results header
|
||||||
|
instead of DomainKey-Status
|
||||||
|
Matt Simerson - 2012 - refactored, added tests, safe loading
|
||||||
John Peacock - 2005-2006
|
John Peacock - 2005-2006
|
||||||
Anthony D. Urso. - 2004
|
Anthony D. Urso. - 2004
|
||||||
|
|
||||||
@ -113,7 +115,8 @@ sub data_post_handler {
|
|||||||
my $status = $self->get_message_status($message);
|
my $status = $self->get_message_status($message);
|
||||||
|
|
||||||
if (defined $status) {
|
if (defined $status) {
|
||||||
$transaction->header->add("DomainKey-Status", $status, 0);
|
#$transaction->header->add("DomainKey-Status", $status, 0);
|
||||||
|
$self->store_auth_results('domainkey=' . $status);
|
||||||
$self->log(LOGINFO, "pass, $status");
|
$self->log(LOGINFO, "pass, $status");
|
||||||
return DECLINED;
|
return DECLINED;
|
||||||
}
|
}
|
||||||
|
@ -119,8 +119,6 @@ RCODE of 3, commonly known as NXDOMAIN, or an RCODE of 0 (NOERROR)
|
|||||||
in a reply containing no answers, was returned. This prevented
|
in a reply containing no answers, was returned. This prevented
|
||||||
completion of the evaluation.
|
completion of the evaluation.
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
=head1 AUTHOR
|
=head1 AUTHOR
|
||||||
|
|
||||||
2013 - Matt Simerson
|
2013 - Matt Simerson
|
||||||
@ -146,7 +144,6 @@ sub register {
|
|||||||
$self->init_resolver() or return;
|
$self->init_resolver() or return;
|
||||||
|
|
||||||
$self->register_hook('connect', 'connect_handler');
|
$self->register_hook('connect', 'connect_handler');
|
||||||
$self->register_hook('data_post', 'data_post_handler');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub connect_handler {
|
sub connect_handler {
|
||||||
@ -166,13 +163,6 @@ sub connect_handler {
|
|||||||
return DECLINED;
|
return DECLINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub data_post_handler {
|
|
||||||
my ($self, $transaction) = @_;
|
|
||||||
my $match = $self->connection->notes('fcrdns_match') || 'error';
|
|
||||||
$self->store_auth_results("iprev=$match");
|
|
||||||
return (DECLINED);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub invalid_localhost {
|
sub invalid_localhost {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
return 1 if lc $self->qp->connection->remote_host ne 'localhost';
|
return 1 if lc $self->qp->connection->remote_host ne 'localhost';
|
||||||
@ -216,20 +206,20 @@ sub has_reverse_dns {
|
|||||||
my $query = $res->query($ip, 'PTR') or do {
|
my $query = $res->query($ip, 'PTR') or do {
|
||||||
if ($res->errorstring eq 'NXDOMAIN') {
|
if ($res->errorstring eq 'NXDOMAIN') {
|
||||||
$self->adjust_karma(-1);
|
$self->adjust_karma(-1);
|
||||||
$self->connection->notes('fcrdns_match', 'permerror');
|
$self->store_auth_results("iprev=permerror");
|
||||||
$self->log(LOGINFO, "fail, no rDNS: " . $res->errorstring);
|
$self->log(LOGINFO, "fail, no rDNS: " . $res->errorstring);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( $res->errorstring eq 'SERVFAIL' ) {
|
if ( $res->errorstring eq 'SERVFAIL' ) {
|
||||||
$self->log(LOGINFO, "fail, error getting rDNS: " . $res->errorstring);
|
$self->log(LOGINFO, "fail, error getting rDNS: " . $res->errorstring);
|
||||||
$self->connection->notes('fcrdns_match', 'temperror');
|
$self->store_auth_results("iprev=temperror");
|
||||||
}
|
}
|
||||||
elsif ( $res->errorstring eq 'NOERROR' ) {
|
elsif ( $res->errorstring eq 'NOERROR' ) {
|
||||||
$self->log(LOGINFO, "fail, no PTR (NOERROR)" );
|
$self->log(LOGINFO, "fail, no PTR (NOERROR)" );
|
||||||
$self->connection->notes('fcrdns_match', 'permerror');
|
$self->store_auth_results("iprev=permerror");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$self->connection->notes('fcrdns_match', 'fail');
|
$self->store_auth_results("iprev=fail");
|
||||||
$self->log(LOGINFO, "fail, error getting rDNS: " . $res->errorstring);
|
$self->log(LOGINFO, "fail, error getting rDNS: " . $res->errorstring);
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@ -246,7 +236,7 @@ sub has_reverse_dns {
|
|||||||
if (!$hits) {
|
if (!$hits) {
|
||||||
$self->adjust_karma(-1);
|
$self->adjust_karma(-1);
|
||||||
$self->log(LOGINFO, "fail, no PTR records");
|
$self->log(LOGINFO, "fail, no PTR records");
|
||||||
$self->connection->notes('fcrdns_match', 'permerror');
|
$self->store_auth_results("iprev=permerror");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,11 +254,11 @@ sub has_forward_dns {
|
|||||||
$host .= '.' if '.' ne substr($host, -1, 1); # fully qualify name
|
$host .= '.' if '.' ne substr($host, -1, 1); # fully qualify name
|
||||||
my $query = $res->query($host) or do {
|
my $query = $res->query($host) or do {
|
||||||
if ($res->errorstring eq 'NXDOMAIN') {
|
if ($res->errorstring eq 'NXDOMAIN') {
|
||||||
$self->connection->notes('fcrdns_match', 'permerror');
|
$self->store_auth_results("iprev=permerror");
|
||||||
$self->log(LOGDEBUG, "host $host does not exist");
|
$self->log(LOGDEBUG, "host $host does not exist");
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
$self->connection->notes('fcrdns_match', 'fail');
|
$self->store_auth_results("iprev=fail");
|
||||||
$self->log(LOGDEBUG, "query for $host failed (",
|
$self->log(LOGDEBUG, "query for $host failed (",
|
||||||
$res->errorstring, ")");
|
$res->errorstring, ")");
|
||||||
next;
|
next;
|
||||||
@ -281,13 +271,13 @@ sub has_forward_dns {
|
|||||||
$self->check_ip_match($rr->address) and return 1;
|
$self->check_ip_match($rr->address) and return 1;
|
||||||
}
|
}
|
||||||
if ($hits) {
|
if ($hits) {
|
||||||
$self->connection->notes('fcrdns_match', 'fail');
|
$self->store_auth_results("iprev=fail");
|
||||||
$self->log(LOGDEBUG, "PTR host has forward DNS") if $hits;
|
$self->log(LOGDEBUG, "PTR host has forward DNS") if $hits;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$self->adjust_karma(-1);
|
$self->adjust_karma(-1);
|
||||||
$self->connection->notes('fcrdns_match', 'fail');
|
$self->store_auth_results("iprev=fail");
|
||||||
$self->log(LOGINFO, "fail, no PTR hosts have forward DNS");
|
$self->log(LOGINFO, "fail, no PTR hosts have forward DNS");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -298,7 +288,7 @@ sub check_ip_match {
|
|||||||
|
|
||||||
if ($ip eq $self->qp->connection->remote_ip) {
|
if ($ip eq $self->qp->connection->remote_ip) {
|
||||||
$self->log(LOGDEBUG, "forward ip match");
|
$self->log(LOGDEBUG, "forward ip match");
|
||||||
$self->connection->notes('fcrdns_match', 'pass');
|
$self->store_auth_results("iprev=pass");
|
||||||
$self->adjust_karma(1);
|
$self->adjust_karma(1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -310,7 +300,7 @@ sub check_ip_match {
|
|||||||
|
|
||||||
if ($dns_net eq $rem_net) {
|
if ($dns_net eq $rem_net) {
|
||||||
$self->log(LOGNOTICE, "forward network match");
|
$self->log(LOGNOTICE, "forward network match");
|
||||||
$self->connection->notes('fcrdns_match', 'pass');
|
$self->store_auth_results("iprev=pass");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user