ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


Using Qpsmtpd
Pages: 1, 2, 3, 4

For our purposes, the only interesting codes are DENY or DENY_DISCONNECT. The other reasons this might be called would be for DENYSOFT and DENYSOFT_DISCONNECT. Returning DECLINED gives other deny hooks the chance to be called.



Finally, it returns DECLINED if the DENY came from this plugin to avoid ending up in a feedback loop.

    my $ip = $self->connection->remote_ip;
    my $now = time;

    my $record = $self->{dbm}->{$ip};
    # Is this IP in the DB?
    if (!$record) {
        $self->{dbm}->{$ip} = pack("NN", 1, $now);
        return DECLINED;
    }
    
    my ($count, $tlast) = unpack("NN", $record);

    # Denied within the last 8 hours?
    if ($tlast < ($now - 28800)) {
        # Not denied in last 8 hours so just reset count.
        $self->{dbm}->{$ip} = pack("NN", 1, $now);
        return DECLINED;
    }
    
    # Now just update the count
    $self->{dbm}->{$ip} = pack("NN", $count+1, $now);
    
    return DECLINED;
}

The rest of this hook is quite simple--it stores some details in a DBM file about when it last saw a DENY from the IP address. If the IP hasn't been denied in the last 8 hours, it resets the counter.

With all that information stored, it's time to do something with it:

sub hook_connect {
    my ($self, $transaction) = @_;
    
    my $ip = $self->connection->remote_ip;
    my $record = $self->{dbm}->{$ip} || return DECLINED;
    my ($count, $tlast) = unpack("NN", $record);
    
    # Ignore and delete entry if not denied in last 12 hours
    if ($tlast < (time - 43200)) {
        delete $self->{dbm}->{$ip};
        return DECLINED;
    }
    
    if ($count >= $self->{deny_threshold}) {
        return DENYSOFT, "You are a repeat offender. Go away";
    }
    
    return DECLINED;
}

First, the code extracts the details from the DBM regarding that IP address. Then it makes sure that the IP address has been denied within the last 12 hours. (Otherwise, it's a stale entry in the DB and needs deleting.) Finally, if this IP has been denied too regularly recently, it issues a DENYSOFT. At connect time a DENYSOFT will disconnect the client with a 450 error, meaning that even if this is a false accusation, it will come back later to try to deliver the email. If this is legitimate email on a legitimate SMTP server, then the email will eventually get delivered--but spammers probably will not try again.

That's it. To add the plugin, edit your config/plugins file and put a line like this somewhere near the top. (The order of plugins determines when they run, and you want this one to run early.)

# deny repeat offenders for 12 hours after 5 bad attempts
deny_repeat_offenders ip_deny_db.dbm 5

Remember to restart qpsmtpd for this change to take effect.

Conclusions

Qpsmtpd is a wonderful, flexible mail server that is fantastic for trying out experiments in filtering mail. As classic examples of this experimentation philosophy, Qpsmtpd was the first SMTPD to have SPF support, the first to have URIBL support, and the first to have early talker checks shipped in core. All of these plugins were very simple to implement, taking up only a few lines of Perl, just like the example you see above.

Extending your mail server with Qpsmtpd is both easy and fun. To learn more, look through the plugins in the distribution, and ask questions on the mailing list. With Qpsmtpd, anything is possible, and your incoming mail will become usable once again.

Matt Sergeant is a leading figure in the mod_perl community, having contributed many open source modules and much documentation work.


Return to the Sysadmin DevCenter



Sponsored by: