File: sender_permitted_from

package info (click to toggle)
qpsmtpd 0.84-9
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 1,376 kB
  • sloc: perl: 8,012; sh: 382; makefile: 61
file content (147 lines) | stat: -rw-r--r-- 4,955 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

=head1 NAME

SPF - plugin to implement Sender Permitted From

=head1 SYNOPSIS

Prevents email sender address spoofing by checking the SPF policy of the purported senders domain.

=head1 DESCRIPTION

Sender Policy Framework (SPF) is an e-mail validation system designed to prevent spam by addressing source address spoofing. SPF allows administrators to specify which hosts are allowed to send e-mail from a given domain by creating a specific SPF record in the public DNS. Mail exchangers then use the DNS to check that mail from a given domain is being sent by a host sanctioned by that domain's administrators. -- http://en.wikipedia.org/wiki/Sender_Policy_Framework

=head1 CONFIGURATION

In config/plugins, add arguments to the sender_permitted_from line.

  sender_permitted_from spf_deny 1

=head2 spf_deny

Setting spf_deny to 0 will prevent emails from being rejected, even if they fail SPF checks. sfp_deny 1 is the default, and a reasonable setting. It temporarily defers connections (4xx) that have soft SFP failures and only rejects (5xx) messages when the sending domains policy suggests it. Settings spf_deny to 2 is more aggressive and will cause soft failures to be rejected permanently.

See also http://spf.pobox.com/

=head1 AUTHOR

Matt Simerson <msimerson@cpan.org>

=head1 ACKNOWLEDGEMENTS

whomever wrote the original SPF plugin, upon which I based this.

=cut

use strict;
use Mail::SPF 2.000;
use Data::Dumper;

sub register {
    my ($self, $qp, @args) = @_;
    %{$self->{_args}} = @args;
}

sub hook_mail {
    my ($self, $transaction, $sender, %param) = @_;

    my $format    = $sender->format;
    my $host      = lc $sender->host;
    my $user      = $sender->user;
    my $client_ip = $self->qp->connection->remote_ip;
    my $from      = $sender->user . '@' . $host;
    my $helo      = $self->qp->connection->hello_host;

    return (DECLINED, "SPF - null sender")
      unless ($format ne "<>" && $host && $user);

    # If we are receving from a relay permitted host, then we are probably
    # not the delivery system, and so we shouldn't check
    return (DECLINED, "SPF - relaying permitted")
      if $self->qp->connection->relay_client();

    my @relay_clients      = $self->qp->config("relayclients");
    my $more_relay_clients = $self->qp->config("morerelayclients", "map");
    my %relay_clients      = map { $_ => 1 } @relay_clients;
    while ($client_ip) {
        return (DECLINED, "SPF - relaying permitted")
          if exists $relay_clients{$client_ip};
        return (DECLINED, "SPF - relaying permitted")
          if exists $more_relay_clients->{$client_ip};
        if ( $client_ip =~ /:/ ) {
          $client_ip =~s /[0-9a-f]+:*$//; # strip off another segment
        }
        else {
          $client_ip =~ s/\d+\.?$//;      # strip off another 8 bits
        }
    }

    my $scope = $from ? 'mfrom' : 'helo';
    $client_ip = $self->qp->connection->remote_ip;
    my %req_params = (
        versions => [1, 2],           # optional
        scope => $scope,
        ip_address => $client_ip,
                     );

    if ($scope =~ /mfrom|pra/) {
        $req_params{identity} = $from;
        $req_params{helo_identity} = $helo if $helo;
    }
    elsif ($scope eq 'helo') {
        $req_params{identity}      = $helo;
        $req_params{helo_identity} = $helo;
    }

    my $spf_server = Mail::SPF::Server->new();
    my $request    = Mail::SPF::Request->new(%req_params);
    my $result     = $spf_server->process($request);

    $transaction->notes('spfquery', $result);

    return (OK) if $result->code eq 'pass';    # this test passed
    return (DECLINED, "SPF - $result->code");
}

sub hook_rcpt {
    my ($self, $transaction, $rcpt, %param) = @_;

    # special addresses don't get SPF-tested.
    return DECLINED
      if $rcpt
          and $rcpt->user
          and $rcpt->user =~ /^(?:postmaster|abuse|mailer-daemon|root)$/i;

    my $result = $transaction->notes('spfquery') or return DECLINED;
    my $code   = $result->code;
    my $why    = $result->local_explanation;
    my $deny   = $self->{_args}{spf_deny};

    return (DECLINED, "SPF - $code: $why")   if $code eq "pass";
    return (DECLINED, "SPF - $code, $why")   if !$deny;
    return (DENYSOFT, "SPF - $code: $why")   if $code eq "error";
    return (DENY,     "SPF - forgery: $why") if $code eq 'fail';

    if ($code eq "softfail") {
        return (DENY, "SPF probable forgery: $why") if $deny > 1;
        return (DENYSOFT, "SPF probable forgery: $why");
    }

    $self->log(LOGDEBUG, "result for $rcpt->address was $code: $why");

    return (DECLINED, "SPF - $code, $why");
}

sub hook_data_post {
    my ($self, $transaction) = @_;

    my $result = $transaction->notes('spfquery') or return DECLINED;

    $self->log(LOGDEBUG, "result was $result->code");

    $transaction->header->add('Received-SPF' => $result->received_spf_header,
                              0);

    return DECLINED;
}