File: whitelist

package info (click to toggle)
qpsmtpd 0.94-8
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 2,340 kB
  • sloc: perl: 17,176; sh: 543; makefile: 186; sql: 100
file content (232 lines) | stat: -rw-r--r-- 7,302 bytes parent folder | download | duplicates (4)
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!perl -w

=head1 NAME

whitelist - whitelist override for other qpsmtpd plugins


=head1 DESCRIPTION

The B<whitelist> plugin allows selected hosts or senders or recipients 
to be whitelisted as exceptions to later plugin processing. It is a more
conservative variant of Devin Carraway's 'whitelist' plugin.


=head1 CONFIGURATION

To enable the plugin, add it to the qpsmtpd/config/plugins file as usual.
It should precede any plugins you might wish to whitelist for.

Several configuration files are supported, corresponding to different
parts of the SMTP conversation:

=over 4

=item whitelisthosts

Any IP address (or start-anchored fragment thereof) listed in the
whitelisthosts file is exempted from any further validation during 
'connect', and can be selectively exempted at other stages by 
plugins testing for a 'whitelisthost' connection note.

Similarly, if the environment variable $WHITELISTCLIENT is set
(which can be done by tcpserver), the connection will be exempt from
further 'connect' validation, and the host can be selectively 
exempted by other plugins testing for a 'whitelistclient' connection 
note.

=item whitelisthelo

Any host that issues a HELO matching an entry in whitelisthelo will
be exempted from further validation at the 'helo' stage. Subsequent
plugins can test for a 'whitelisthelo' connection note. Note that 
this does not actually amount to an authentication in any meaningful 
sense.

=item whitelistsenders

If the envelope sender of a mail (that which is sent as the MAIL FROM)
matches an entry in whitelistsenders, or if the hostname component 
matches, the mail will be exempted from any further validation within
the 'mail' stage. Subsequent plugins can test for this exemption as a 
'whitelistsender' transaction note.

=item whitelistrcpt

If any recipient of a mail (that sent as the RCPT TO) matches an
entry from whitelistrcpt, or if the hostname component matches, no 
further validation will be required for this recipient. Subsequent 
plugins can test for this exemption using a 'whitelistrcpt' 
transaction note, which holds the count of whitelisted recipients.

=back

whitelist_soft also supports per-recipient whitelisting when using
the per_user_config plugin. To enable the per-recipient behaviour 
(delaying all whitelisting until the rcpt part of the smtp 
conversation, and using per-recipient whitelist configs, if 
available), pass a true 'per_recipient' argument in the
config/plugins invocation i.e.

  whitelist_soft per_recipient 1

By default global and per-recipient whitelists are merged; to turn off
the merge behaviour pass a false 'merge' argument in the config/plugins
invocation i.e.

  whitelist_soft per_recipient 1 merge 0


=head1 BUGS

Whitelist lookups are all O(n) linear scans of configuration files, even
though they're all associative lookups.  Something should be done about
this when CDB/DB/GDBM configs are supported.


=head1 AUTHOR

Based on the 'whitelist' plugin by Devin Carraway <qpsmtpd@devin.com>.

Modified by Gavin Carr <gavin@openfusion.com.au> to not inherit 
whitelisting across hooks, but use per-hook whitelist notes instead.
This is a more conservative approach e.g. whitelisting an IP will not
automatically allow relaying from that IP.

=cut

use strict;
use warnings;

use Qpsmtpd::Constants;

my $VERSION = 0.02;

# Default is to merge whitelists in per_recipient mode
my %MERGE = (merge => 1);

sub register {
    my ($self, $qp, %arg) = @_;

    $self->{_per_recipient} = 1 if $arg{per_recipient};
    $MERGE{merge} = $arg{merge} if defined $arg{merge};

    # Normal mode - whitelist per hook
    unless ($arg{per_recipient}) {
        $self->register_hook("connect", "check_host");
        $self->register_hook("helo",    "check_helo");
        $self->register_hook("ehlo",    "check_helo");
        $self->register_hook("mail",    "check_sender");
        $self->register_hook("rcpt",    "check_rcpt");
    }

    # Per recipient mode - defer all whitelisting to rcpt hook
    else {
        $self->register_hook("rcpt", "check_host");
        $self->register_hook("helo", "helo_helper");
        $self->register_hook("ehlo", "helo_helper");
        $self->register_hook("rcpt", "check_helo");
        $self->register_hook("rcpt", "check_sender");
        $self->register_hook("rcpt", "check_rcpt");
    }
}

sub check_host {
    my ($self, $transaction, $rcpt) = @_;
    my $ip = $self->qp->connection->remote_ip || return (DECLINED);

    # From tcpserver
    if (exists $ENV{WHITELISTCLIENT}) {
        $self->qp->connection->notes('whitelistclient', 1);
        $self->log(2, "pass, is whitelisted client");
        $self->adjust_karma(5);
        return OK;
    }

    my $config_arg = $self->{_per_recipient} ? {rcpt => $rcpt, %MERGE} : {};
    for my $h ($self->qp->config('whitelisthosts', $config_arg)) {
        if ($h eq $ip or $ip =~ /^\Q$h\E/) {
            $self->qp->connection->notes('whitelisthost', 1);
            $self->log(2, "pass, is a whitelisted host");
            $self->adjust_karma(5);
            return OK;
        }
    }
    $self->log(LOGDEBUG, "skip: $ip is not whitelisted");
    return DECLINED;
}

sub helo_helper {
    my ($self, $transaction, $helo) = @_;
    $self->{_whitelist_soft_helo} = $helo;
    return DECLINED;
}

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

    # If per_recipient will be rcpt hook, and helo actually rcpt
    my $config_arg = {};
    if ($self->{_per_recipient}) {
        $config_arg = {rcpt => $helo, %MERGE};
        $helo = $self->{_whitelist_soft_helo};
    }

    for my $h ($self->qp->config('whitelisthelo', $config_arg)) {
        if ($helo and lc $h eq lc $helo) {
            $self->qp->connection->notes('whitelisthelo', 1);
            $self->log(2, "helo host $helo in whitelisthelo");
            return OK;
        }
    }
    return DECLINED;
}

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

    # If per_recipient will be rcpt hook, and sender actually rcpt
    my $config_arg = {};
    if ($self->{_per_recipient}) {
        $config_arg = {rcpt => $sender, %MERGE};
        $sender = $transaction->sender;
    }

    return DECLINED if $sender->format eq '<>';
    my $addr = lc $sender->address or return DECLINED;
    my $host = lc $sender->host    or return DECLINED;

    for my $h ($self->qp->config('whitelistsenders', $config_arg)) {
        next unless $h;
        $h = lc $h;

        if ($addr eq $h or $host eq $h) {
            $transaction->notes('whitelistsender', 1);
            $self->log(2, "envelope sender $addr in whitelistsenders");
            return OK;
        }
    }
    return DECLINED;
}

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

    my $addr = lc $rcpt->address or return DECLINED;
    my $host = lc $rcpt->host    or return DECLINED;

    my $config_arg = $self->{_per_recipient} ? {rcpt => $rcpt, %MERGE} : {};
    for my $h ($self->qp->config('whitelistrcpt', $config_arg)) {
        next unless $h;
        $h = lc $h;

        if ($addr eq $h or $host eq $h) {
            my $note = $transaction->notes('whitelistrcpt');
            $transaction->notes('whitelistrcpt', ++$note);
            $self->log(2, "recipient $addr in whitelistrcpt");
            return OK;
        }
    }
    return DECLINED;
}