=head1 NAME 

hosts_allow - decide if a host is allowed to send mail 

=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 arguments.

=head1 CONFIG

The config file contains lines with two or three items. The first is either
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.

=cut

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};

    if ($args{max_conn_ip}) {
        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);
        }
        if ($num_conn > $args{max_conn_ip}) {
            $self->log(LOGINFO,
                       "Too many connections from $remote: "
                      . "$num_conn > " . $args{max_conn_ip} 
                      . "Denying connection.");
            return (DENYSOFT, "Sorry, too many connections from $remote, "
                             ."try again later");
        }
    }
 
    foreach ($self->qp->config("hosts_allow")) {
        s/^\s*//;
        my ($ipmask, $const, $message) = split /\s+/, $_, 3;
        next unless defined $const;

        my ($net,$mask) = split '/', $ipmask, 2;
        if (!defined $mask) {
            $mask = 32;
        }
        $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;
            return($const, $message);
        }
    }

    return (DECLINED);
}

# vim: sw=4 ts=4 expandtab syn=perl