ZeroMx
About > News > Stigmergic antispam > Code

Code

This perl script works, it's something more than a proof-of-concept.
Instructions are included for using it with postfix.
A copy is also available from sourceforge.


#!/usr/bin/perl -w
# -------------------
# stixs - a blacklisting delegate policy agent for postfix
# see details in http://www.zeromx.net
# http://www.cammozzo.com
# a.cammozzo at gmail
#--------------------
#
# (c) Alberto Cammozzo Aug 2009
# release 0.5
# Licensed under the terms of the GPLv2
#
#--------------------
# How it works:
#
# checks if sender IP is already blacklisted querying bld
# (Blacklist daemon). In this case: Rejects.
# If mail addressed to one of a list of fake sender address
# listed in honeypot webpages arrives, blacklists sender IP
# and rejects.
# Reject timeout delegated to bld (see bld configuration).
#--------------------
#
# README
#
# Installation
# 1) install and configure bld (Black List Daemon) and postfix
#    apt-get install postfix bld
#    Unless you plan to use an ssh tunnel, they should be installed
#    on the same machine with bld listening on localhost.
#    Otherwise, edit bld access lists to allow updating blacklists
#    only from localhost.
# 2) place this file in /etc/postfix/stixs.pl
#
# Configure postfix as follows:
# 3) edit main.cf and insert:
#     smtpd_recipient_restrictions =
#         permit_mynetworks,
#         reject_unauth_destination,
#         check_policy_service unix:private/stixs,
#         ...
# 4) edit master.cf and add:
#      stixs   unix    -       n       n       -       -       spawn
#              user=nobody argv=/usr/bin/perl /etc/postfix/daemon.pl
# 5) edit /etc/postfix/fakeaddr is in postfix access (5) format:
#      fakeaddress1@domain.tld REJECT
#      fakeaddress2@domain.tld REJECT
#      fakeaddress3@domain.tld REJECT
#    these are the addresses that should be bublished in an indexed webpage
#    or extracted from the logs of frquently rejected addresses
#    Make sure the addresses are not rejected as unauth_destionation.
#    Perhaps you'll have to edit /etc/aliases to add them as "valid" destinations:
#      fakeaddress1: /dev/null
#      fakeaddress2: /dev/null
#      fakeaddress3: /dev/null
#   
# 6) rebuild fake addresses database and aliases
#      postmap /etc/postfix/fakeaddr
#      postalias /etc/aliases
#
# Configuration done. Now:
# 7) restart postfix
#      /etc/init.d postfix restart
# 8) watch the logs
#
# Troubleshooting hints:
# edit $debug=1
#
# bld normally binds to localhost, port 2905.
# If you configured differently, edit $blhost and $blport accordingly


# CONFIGURATION
# Set to 1 for extra debugging (in syslog)
my $debug =0;

# blacklist server options:
# run in localhost unless secured in ssl tunnel
my $blserver = "localhost";
my $blport = 2905; #default port for bld server

# database of fake addesses (mailtrap) in postfix format
my $database_name = "/etc/postfix/fakeaddr.db";

# syslog options
my $ident="stixs";
my $logopt="ndelay,pid";
my $facility="mail";

# PROGRAM
use strict;
use Sys::Syslog;
use IO::Socket;
use DB_File;
use Fcntl;

my %db_hash;


openlog $ident, $logopt, $facility;  

#
# Connects to Blacklist server
#
sub bl_connect {
    # create a socket, make it reusable
    my $black = new IO::Socket::INET(
        PeerAddr => $blserver,
        PeerPort => $blport,
        Proto => 'tcp'
        );
    syslog("ERR","Could not connect to bld server: $!") unless $black;
    die unless $black;
    return $black;
}

#
# Forces blacklisting of the offending IP on blacklist server
#
sub bl_force {
    my $ip=$_[0];
    my $rcpt=$_[1];

    my $reply;
    my $action="action = DUNNO\n\n"; #default action

    my $black=bl_connect()              or syslog ("ERR","could not connect to blacklist server");
    # bld server introduces himself...
    $reply=<$black> ;
    syswrite($black, "ipbl=$ip\r\n") and $debug and syslog("DEBUG","[$ip] forced to blacklist status on bld server");
    $reply=<$black>;
    chomp $reply;
    if ($reply =~ /421/) {
        $debug and syslog("NOTICE","[$ip] bld acknowledged: $reply");
        syslog("NOTICE","[$ip] has been blacklisted (recipient=$rcpt)");
    } else {
        syslog("ERR","[$ip] something went wrong. bld server said: $reply");
    }

    $action="action=REJECT you have been blacklisted\n\n";
    return ($action);
}

#
# Checks if a given IP is blacklisted on the bld server
#
sub bl_check {
    my $ip=$_[0];

    my $reply;
    my $action="action=DUNNO\n\n"; #default action

    my $black=bl_connect() or syslog ("ERR","could not connect to blacklist server");
    # bld introduces himself
    $reply=<$black>;
    # query bld
    syswrite($black, "ip?=$ip\r\n");
    $reply=<$black>;
    chomp $reply;
   
    if ($reply =~ /^421/) {
        syslog("NOTICE","[$ip] is already Blacklisted according to bld server");
        $action="action=REJECT you are blacklisted\n\n";
    } else {
        $debug and syslog("DEBUG","[$ip] is OK: $reply");
    }
    return $action;
}


#
# Checks if an email address (recipient) is listed with REJECTin the fake addresses database
#
sub is_fake_rcpt {
    my $rcpt = $_[0];
    my $ip = $_[1];
    my $reply;

    # prepare mail address for request
    my $send = lc($rcpt);
    $send =~ s/\+[^@]+//;
    chomp $send;
    $send .= chr(0);

    # query
    $reply = $db_hash{$send} ;
    $debug and syslog("DEBUG","database request for $rcpt gave $reply");
   
    if ($reply =~/^REJECT/) {
        syslog("NOTICE","$rcpt is a mailtrap address, I will blacklist sender ip [$ip]");
        return $rcpt;
    } else {
        return "";
    }
}

#
#   MAIN program
#

my $line="init";
my $ip="dont'know yet";
my $action = "action=DUNNO\n\n";
my $badaddress = "";
my $rcpt;
my $reply;
my $black;
my $notforced=1;

# open fake addresses database
tie (%db_hash, 'DB_File', $database_name, O_RDONLY, $DB_HASH)
    or syslog("ERR","Fatal: cannot open database $database_name: $!",)
    and die;

$debug and syslog ("DEBUG","$database_name opened successfully");

# main loop
# reads a list of attributes from input
#    attribute = value
#    attribute = value
# Checks only for two:
#    client_address=$ip
#    recipient=$rcpt
#
while ($line ne "") {
    $line = <>;
    chomp ($line);
    $debug and syslog("DEBUG","in: $line");
    # catch address of remote MTA and local recipient
    if ($line =~ /client_address=(\d+\.\d+\.\d+\.\d+)/ ){
        $ip = $1;
        chomp $ip;
        $debug and syslog("DEBUG","[$ip] is sender ip");
        # check if address is already blacklisted
        $action = bl_check($ip);
    }
    if ($line =~ /recipient=([[:print:]]+)/ ) {
        $rcpt =$1;
        # checks if address is fake
        $badaddress = is_fake_rcpt($rcpt,$ip);
    }
    # checsk if recipient is fake and snder has to be blacklisted
    # fake address -> force blacklisting (do this once)
    if ($badaddress and $ip and $notforced) {
        $action=bl_force($ip,$rcpt);
        $notforced =0; #do it only once
    }
}
# Returns
#   REJECT reason
#   DUNNO
print  $action;