File: hosts_allow

package info (click to toggle)
qpsmtpd 0.94-4
  • links: PTS
  • area: main
  • in suites: bullseye, buster
  • size: 2,284 kB
  • sloc: perl: 17,176; sh: 543; makefile: 186; sql: 100
file content (131 lines) | stat: -rw-r--r-- 3,775 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
#!perl -w

=head1 NAME

hosts_allow - decide if a host is allowed to connect

=head1 DESCRIPTION

The B<hosts_allow> module decides before the SMTP-Greeting if a host is
allowed to connect. It checks for too many (running) connections from one
host (see -m/--max-from-ip options in qpsmtpd-forkserver) and the config
file I<hosts_allow>.

The plugin takes no config/plugin arguments.

This plugin only works with the forkserver and prefork deployment models. It
does not work with the tcpserver deployment model. See SEE ALSO below.

=head1 CONFIG

The I<hosts_allow> config file contains lines with two or three items. The
first is an IP address or a network/mask pair. The second is a (valid) return
code from Qpsmtpd::Constants. The last is a comment which will be returned to
the connecting client if the return code is DENY or DENYSOFT (and of course
DENY_DISCONNECT and DENYSOFT_DISCONNECT).

Example:

  192.168.3.4    DECLINED
  192.168.3.0/24 DENY Sorry, known spam only source

This would exclude 192.168.3.4 from the DENY of 192.168.3.0/24.

=head1 SEE ALSO

To get similar functionality for the tcpserver deployment model, use
tcpserver's -x feature. Create a tcp.smtp file with entries like this:

  70.65.227.235:deny
  183.7.90.207:deny
  :allow

compile the tcp.smtp file like this:

  /usr/local/bin/tcprules tcp.smtp.cdb tcp.smtp.tmp < tcp.smtp

and add the file to the chain of arguments to tcpserver in your run file.

See also: http://cr.yp.to/ucspi-tcp.html

=cut

use strict;
use warnings;

use Qpsmtpd::Constants;
use Socket;

sub hook_pre_connection {
    my ($self, $transaction, %args) = @_;

    # remote_ip    => inet_ntoa($iaddr),
    # remote_port  => $port,
    # local_ip     => inet_ntoa($laddr),
    # local_port   => $lport,
    # max_conn_ip  => $MAXCONNIP,
    # child_addrs  => [values %childstatus],

    my $remote = $args{remote_ip};
    my $max    = $args{max_conn_ip};
    my $karma  = $self->connection->notes('karma_history');

    if ($max) {
        my $num_conn = 1;                    # seed with current value
        my $raddr    = inet_aton($remote);
        foreach my $rip (@{$args{child_addrs}}) {
            ++$num_conn if (defined $rip && $rip eq $raddr);
        }
        $max = $self->karma_bump($karma, $max) if defined $karma;
        if ($num_conn > $max) {
            my $err_mess = "too many connections from $remote";
            $self->log(LOGINFO, "fail: $err_mess ($num_conn > $max)");
            return (DENYSOFT, "$err_mess, try again later");
        }
    }

    my @r = $self->in_hosts_allow($remote);
    return @r if scalar @r;

    $self->log(LOGDEBUG, "pass");
    return (DECLINED);
}

sub in_hosts_allow {
    my $self   = shift;
    my $remote = shift;

    foreach ($self->qp->config('hosts_allow')) {
        s/^\s*//;    # trim leading whitespace
        my ($ipmask, $const, $message) = split /\s+/, $_, 3;
        next unless defined $const;

        my ($net, $mask) = split /\//, $ipmask, 2;
        $mask = 32 if !defined $mask;
        $mask = pack "B32", "1" x ($mask) . "0" x (32 - $mask);
        if (join('.', unpack('C4', inet_aton($remote) & $mask)) eq $net) {
            $const = Qpsmtpd::Constants::return_code($const) || DECLINED;
            if ($const =~ /deny/i) {
                $self->log(LOGINFO, "fail, $message");
            }
            $self->log(LOGDEBUG, "pass, $const, $message");
            return ($const, $message);
        }
    }

    return;
}

sub karma_bump {
    my ($self, $karma, $max) = @_;

    if ($karma > 5) {
        $self->log(LOGDEBUG, "connect limit +3 for positive karma");
        return $max + 3;
    }
    if ($karma <= 0) {
        $self->log(LOGINFO, "connect limit 1, karma $karma");
        return 1;
    }
    return $max;
}