#!perl -w =head1 NAME domainkeys: validate a DomainKeys signature on an incoming mail =head1 SYNOPSIS domainkeys [reject 1] Performs a DomainKeys validation on the message. =head1 CONFIGURATION =head2 reject reject 1 Reject is a boolean that toggles message rejection on or off. Messages failing DomainKeys validation are rejected by default. =head2 reject_type reject_type [ temp | perm ] The default rejection type is permanent. =head2 warn_only A deprecated option that disables message rejection. See reject instead. =head1 COPYRIGHT Copyright (C) 2005-2006 John Peacock. Portions Copyright (C) 2004 Anthony D. Urso. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHORS Matt Simerson - 2012 John Peacock - 2005-2006 Anthony D. Urso. - 2004 =cut use strict; use warnings; use Qpsmtpd::Constants; sub init { my ($self, $qp, %args) = @_; foreach my $key ( %args ) { $self->{$key} = $args{$key}; } $self->{reject} = 1 if ! defined $self->{reject}; # default reject $self->{reject_type} = 'perm' if ! defined $self->{reject_type}; if ( $args{'warn_only'} ) { $self->log(LOGNOTICE, "warn_only is deprecated. Use reject instead"); $self->{'reject'} = 0; }; } sub register { my $self = shift; for my $m ( qw/ Mail::DomainKeys::Message Mail::DomainKeys::Policy / ) { eval "use $m"; if ( $@ ) { warn "skip: plugin disabled, could not load $m\n"; $self->log(LOGERROR, "skip: plugin disabled, is $m installed?"); return; }; }; $self->register_hook('data_post', 'data_post_handler'); }; sub data_post_handler { my ($self, $transaction) = @_; if ( ! $transaction->header->get('DomainKey-Signature') ) { $self->log(LOGINFO, "skip: unsigned"); return DECLINED; }; my $body = $self->assemble_body( $transaction ); my $message = load Mail::DomainKeys::Message( HeadString => $transaction->header->as_string, BodyReference => $body) or do { $self->log(LOGWARN, "skip: unable to load message"), return DECLINED; }; # no sender domain means no verification if ( ! $message->senderdomain ) { $self->log(LOGINFO, "skip: failed to parse sender domain"), return DECLINED; }; my $status = $self->get_message_status( $message ); if ( defined $status ) { $transaction->header->replace("DomainKey-Status", $status); $self->log(LOGINFO, "pass: $status"); return DECLINED; }; $self->log(LOGERROR, "fail: signature failed to verify"); return DECLINED if ! $self->{reject}; my $deny = $self->{reject_type} eq 'temp' ? DENYSOFT : DENY; return ($deny, "DomainKeys signature failed to verify"); } sub get_message_status { my ($self, $message) = @_; if ( $message->testing ) { return "testing"; # key testing, don't do anything else }; if ( $message->signed && $message->verify ) { return $message->signature->status; # verified: add good header }; # not signed or not verified my $policy = fetch Mail::DomainKeys::Policy( Protocol => 'dns', Domain => $message->senderdomain ); if ( ! $policy ) { return $message->signed ? "non-participant" : "no signature"; }; if ( $policy->testing ) { return "testing"; # Don't do anything else }; if ( $policy->signall ) { return undef; # policy requires all mail to be signed }; # $policy->signsome return "no signature"; # not signed and domain doesn't sign all }; sub assemble_body { my ($self, $transaction) = @_; $transaction->body_resetpos; $transaction->body_getline; # \r\n seperator is NOT part of the body my @body; while (my $line = $transaction->body_getline) { push @body, $line; } return \@body; };