From f67531bad2b0aaebfea7ec63922290f28e86d6ab Mon Sep 17 00:00:00 2001 From: dakkar Date: Thu, 27 Dec 2007 14:36:45 +0000 Subject: r3304@rfc-1918: dakkar | 2007-12-27 13:19:58 +0100 prepraering fro branching --- Makefile.PL | 12 +++ lib/DAKKAR/Graylister.pm | 229 +++++++++++++++++++++++++++++++++++++++++++++++ script/dakkar-graylister | 6 ++ t/01-use.t | 16 ++++ t/02-input.t | 19 ++++ t/03-white.t | 19 ++++ t/04-gray.t | 18 ++++ t/05-gray-empty.t | 18 ++++ t/06-age.t | 34 +++++++ 9 files changed, 371 insertions(+) create mode 100644 Makefile.PL create mode 100644 lib/DAKKAR/Graylister.pm create mode 100644 script/dakkar-graylister create mode 100644 t/01-use.t create mode 100644 t/02-input.t create mode 100644 t/03-white.t create mode 100644 t/04-gray.t create mode 100644 t/05-gray-empty.t create mode 100644 t/06-age.t diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..4d7c913 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,12 @@ +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'DAKKAR::Graylister', + AUTHOR => 'dakkar@thenautilus.net', + EXE_FILES=>['script/dakkar-graylister'], + PREREQ_PM=>{ + DBI => 0, + DBD::SQLite => 0, + Net::DNSBLLookup => 0, + }, +); diff --git a/lib/DAKKAR/Graylister.pm b/lib/DAKKAR/Graylister.pm new file mode 100644 index 0000000..9ee9be0 --- /dev/null +++ b/lib/DAKKAR/Graylister.pm @@ -0,0 +1,229 @@ +package DAKKAR::Graylister; +use strict; +use warnings; +use Net::DNSBLLookup; +use DBI; + +{ + %Net::DNSBLLookup::dns_servers=( + 'dnsbl.sorbs.net' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_PROXY_HTTP, + '127.0.0.3' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_PROXY_SOCKS, + '127.0.0.4' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_PROXY_MISC, + '127.0.0.5' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_RELAY, + '127.0.0.6' => Net::DNSBLLookup::DNSBLLOOKUP_SPAMHOUSE, + '127.0.0.7' => Net::DNSBLLookup::DNSBLLOOKUP_FORMMAIL, + '127.0.0.8' => Net::DNSBLLookup::DNSBLLOOKUP_CONFIRMED_SPAM, + '127.0.0.9' => Net::DNSBLLookup::DNSBLLOOKUP_HIJACKED, + '127.0.0.10' => Net::DNSBLLookup::DNSBLLOOKUP_DYNAMIC_IP, + }, + 'dnsbl.njabl.org' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_RELAY, + '127.0.0.3' => Net::DNSBLLookup::DNSBLLOOKUP_DYNAMIC_IP, + '127.0.0.4' => Net::DNSBLLookup::DNSBLLOOKUP_SPAMHOUSE, + '127.0.0.5' => Net::DNSBLLookup::DNSBLLOOKUP_MULTI_OPEN_RELAY, + '127.0.0.8' => Net::DNSBLLookup::DNSBLLOOKUP_FORMMAIL, + '127.0.0.9' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_PROXY, + }, + 'bl.spamcop.net' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_UNKNOWN, + }, + 'unconfirmed.dsbl.org' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_UNKNOWN, + }, + 'list.dsbl.org' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_UNKNOWN, + }, + 'sbl.spamhaus.org' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_SPAMHOUSE, + }, + 'pbl.spamhaus.org' => { + '127.0.0.10' => Net::DNSBLLookup::DNSBLLOOKUP_DYNAMIC_IP, + '127.0.0.11' => Net::DNSBLLookup::DNSBLLOOKUP_DYNAMIC_IP, + }, + 'cbl.abuseat.org' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_PROXY, + }, + 'psbl.surriel.com' => { + '127.0.0.2' => Net::DNSBLLookup::DNSBLLOOKUP_OPEN_PROXY, + }, + ); + + use Net::DNSBLLookup::Result; + {package Net::DNSBLLookup::Result; + our %result_type; + no warnings 'redefine'; + sub breakdown { + my ($self)=@_; + my ($total_spam, $total_proxy, $total_unknown, $total_dyn) = (0,0,0); + return unless exists $self->{results}; + while (my ($dnsbl, $v) = each %{$self->{results}}) { + my ($is_spam, $is_proxy, $is_unknown, $is_dyn) = (0,0,0,0); + for my $retval (@$v) { + my $result_type = $result_type{$retval}; + if ($result_type == DNSBLLOOKUP_RESULT_OPEN_PROXY) { + $is_proxy = 1; + } elsif ($result_type == DNSBLLOOKUP_RESULT_SPAM) { + $is_spam = 1; + } elsif ($result_type == DNSBLLOOKUP_RESULT_UNKNOWN) { + $is_unknown = 1; + } elsif ($result_type == DNSBLLOOKUP_RESULT_DYNAMIC_IP) { + $is_dyn = 1; + } + } + $total_proxy += $is_proxy; + $total_spam += $is_spam; + $total_dyn += $is_dyn; + unless ($is_proxy || $is_spam || $is_dyn) { + $total_unknown += $is_unknown; + } + } + return ($total_proxy, $total_spam, $total_dyn, $total_unknown); + } +} +} + +my $DBNAME; + +sub get_from_env { + my $from=$ENV{SMTPMAILFROM}; + die "No from??\n" unless defined $from; + my $to=$ENV{SMTPRCPTTO}; + die "No to??\n" unless defined $to; + my $host=$ENV{TCPREMOTEIP}; + die "No ip??\n" unless defined $host; + $DBNAME=$ENV{DAKGL_DBNAME} + or die "No dbname??\n"; + return _cleanup_data($host,$from,$to); +} + +sub _cleanup_data { + my ($host,$from,$to)=@_; + # EZMLM changes the "from" at each attempt... + $from=~s{-return-\d+-}{-return-\$-}; + return ($host,$from,$to); +} + +sub check { + my ($host,$from,$to)=@_; + + remove_old_attempts(); + if (is_whitelisted($host)) { + accept_message();return; + } + if (is_second_attempt($host,$from,$to)) { + if (is_blacklisted($host)) { + cleanup_attempt($host,$from,$to); + accept_message();return; + } + else { + add_to_whitelist($host); + accept_message();return; + } + } + record_first_attempt($host,$from,$to); + reject_temporarily($from); + return; +} + +{my $dbh; + + sub _init_dbh { + return if defined $dbh; + $dbh=DBI->connect("dbi:SQLite:dbname=$DBNAME",'','', + {RaiseError=>1,PrintError=>0}); + my $ver=$dbh->selectall_arrayref(q{SELECT count(*) FROM SQLite_Master WHERE Type = 'table' AND name = 'version'}); + if ((!defined $ver) or (!@$ver) or ($ver->[0]->[0]==0)) { + _prepare_db(); + } + } + + {my @sts=( + q{create table version(version integer)}, + q{insert into version(version) values (1)}, + q{create table whitelist(host varchar)}, + q{create table attempts(host varchar,smtpfrom varchar,smtprcpt varchar,time integer)}, + ); + sub _prepare_db { + for my $s (@sts) { + $dbh->do($s); + } + } + } + + sub add_to_whitelist { + my ($host)=@_; + + _init_dbh(); + my $ret=$dbh->do('INSERT INTO whitelist(host) VALUES (?)', + {},$host); + } + + sub is_whitelisted { + my ($host)=@_; + + _init_dbh(); + my $ret=$dbh->selectall_arrayref('SELECT * FROM whitelist WHERE host=?', + {Slice=>{}},$host); + return @$ret>0; + } + + sub record_first_attempt { + my ($host,$from,$to)=@_; + _init_dbh(); + my $ret=$dbh->do('INSERT INTO attempts(host,smtpfrom,smtprcpt,time) VALUES (?,?,?,?)', + {},$host,$from,$to,time()); + } + + sub is_second_attempt { + my ($host,$from,$to)=@_; + _init_dbh(); + my $ret=$dbh->selectall_arrayref('SELECT * FROM attempts WHERE host=? AND smtpfrom=? AND smtprcpt=?', + {Slice=>{}},$host,$from,$to); + return @$ret>0; + } + + sub cleanup_attempt { + my ($host,$from,$to)=@_; + _init_dbh(); + if ($from) { + my $ret=$dbh->do('UPDATE attempts SET time=? WHERE host=? AND smtpfrom=? AND smtprcpt=?', + {},time(),$host,$from,$to); + } + else { + # prbobaly a bounce + my $ret=$dbh->do('DELETE FROM attempts WHERE host=? AND smtpfrom=? AND smtprcpt=?', + {},$host,$from,$to); + } + } + + sub remove_old_attempts { + _init_dbh(); + my $ret=$dbh->do('DELETE FROM attempts WHERE timenew(); + my $ret=$look->lookup($host); + my ($proxy, $spam, $dyn, $unknown) = $ret->breakdown; + return ($proxy+$spam+$dyn+$unknown > 0); +} + +sub accept_message {} + +{my $message='DAKKAR-Graylister temproray reject, try again later'; + sub reject_temporarily { + my ($from)=@_; + + if ($from eq '' or $from=~/^postmaster@/) { + print "LD451 $message\n"; + } + else { + print "E451 $message\n"; + } + } +} diff --git a/script/dakkar-graylister b/script/dakkar-graylister new file mode 100644 index 0000000..07dc7db --- /dev/null +++ b/script/dakkar-graylister @@ -0,0 +1,6 @@ +#!/usr/bin/perl +use DAKKAR::Graylister; + +exit 0 unless (defined $ENV{GRAYLISTING}) and (!defined $ENV{RELAYCLIENT}); + +DAKKAR::Graylister::check(DAKKAR::Graylister::get_from_env()); diff --git a/t/01-use.t b/t/01-use.t new file mode 100644 index 0000000..f151e18 --- /dev/null +++ b/t/01-use.t @@ -0,0 +1,16 @@ +# -*- mode: cperl -*- +use Test::More tests=>3; + +BEGIN { + use_ok('DAKKAR::Graylister') + or die; +} + +my ($host,$from,$to)=qw(10.1.0.0 gino@qui.it pino@li.com); +is_deeply([DAKKAR::Graylister::_cleanup_data($host,$from,$to)], + [$host,$from,$to], + 'no cleanup'); +$from='gino-return-10-@qui.it'; +is_deeply([DAKKAR::Graylister::_cleanup_data($host,$from,$to)], + [$host,'gino-return-$-@qui.it',,$to], + 'ezmlm cleanup'); diff --git a/t/02-input.t b/t/02-input.t new file mode 100644 index 0000000..3013798 --- /dev/null +++ b/t/02-input.t @@ -0,0 +1,19 @@ +# -*- mode: cperl -*- +use Test::More tests=>2; +use File::Temp 'tempdir'; +use DAKKAR::Graylister; + +my $dbdir=tempdir(CLEANUP=>1); + +local $ENV{SMTPMAILFROM}='gino'; +local $ENV{SMTPRCPTTO}='pino'; +local $ENV{TCPREMOTEIP}='10.1.2.3'; +local $ENV{DAKGL_DBNAME}="$dbdir/gray1.db"; +is_deeply([DAKKAR::Graylister::get_from_env()], + ['10.1.2.3','gino','pino'], + 'from env'); +$ENV{SMTPMAILFROM}='gino-return-12-@pino.it'; +is_deeply([DAKKAR::Graylister::get_from_env()], + ['10.1.2.3','gino-return-$-@pino.it','pino'], + 'from env, ezmlm'); + diff --git a/t/03-white.t b/t/03-white.t new file mode 100644 index 0000000..b2ac66c --- /dev/null +++ b/t/03-white.t @@ -0,0 +1,19 @@ +# -*- mode: cperl -*- +use Test::More tests=>3; +use File::Temp 'tempdir'; +use DAKKAR::Graylister; + +my $dbdir=tempdir(CLEANUP=>1); + +local $ENV{SMTPMAILFROM}='gino'; +local $ENV{SMTPRCPTTO}='pino'; +local $ENV{TCPREMOTEIP}='10.1.2.3'; +local $ENV{DAKGL_DBNAME}="$dbdir/gray2.db"; +my ($host,$from,$to)=DAKKAR::Graylister::get_from_env(); + +ok(!DAKKAR::Graylister::is_whitelisted($host),'db touch'); + +ok(-f "$dbdir/gray2.db",'db file created'); + +DAKKAR::Graylister::add_to_whitelist($host); +ok(DAKKAR::Graylister::is_whitelisted($host),'whitelist'); diff --git a/t/04-gray.t b/t/04-gray.t new file mode 100644 index 0000000..893c1d1 --- /dev/null +++ b/t/04-gray.t @@ -0,0 +1,18 @@ +# -*- mode: cperl -*- +use Test::More tests=>3; +use File::Temp 'tempdir'; +use DAKKAR::Graylister; + +my $dbdir=tempdir(CLEANUP=>1); + +local $ENV{SMTPMAILFROM}='gino'; +local $ENV{SMTPRCPTTO}='pino'; +local $ENV{TCPREMOTEIP}='10.1.2.3'; +local $ENV{DAKGL_DBNAME}="$dbdir/gray3.db"; +my ($host,$from,$to)=DAKKAR::Graylister::get_from_env(); + +ok(!DAKKAR::Graylister::is_second_attempt($host,$from,$to),'first attempt'); +DAKKAR::Graylister::record_first_attempt($host,$from,$to); +ok(DAKKAR::Graylister::is_second_attempt($host,$from,$to),'second attempt'); +DAKKAR::Graylister::cleanup_attempt($host,$from,$to); +ok(DAKKAR::Graylister::is_second_attempt($host,$from,$to),'updated attempt'); diff --git a/t/05-gray-empty.t b/t/05-gray-empty.t new file mode 100644 index 0000000..2fe3b0d --- /dev/null +++ b/t/05-gray-empty.t @@ -0,0 +1,18 @@ +# -*- mode: cperl -*- +use Test::More tests=>3; +use File::Temp 'tempdir'; +use DAKKAR::Graylister; + +my $dbdir=tempdir(CLEANUP=>1); + +local $ENV{SMTPMAILFROM}=''; +local $ENV{SMTPRCPTTO}='pino'; +local $ENV{TCPREMOTEIP}='10.1.2.3'; +local $ENV{DAKGL_DBNAME}="$dbdir/gray4.db"; +my ($host,$from,$to)=DAKKAR::Graylister::get_from_env(); + +ok(!DAKKAR::Graylister::is_second_attempt($host,$from,$to),'first attempt'); +DAKKAR::Graylister::record_first_attempt($host,$from,$to); +ok(DAKKAR::Graylister::is_second_attempt($host,$from,$to),'second attempt'); +DAKKAR::Graylister::cleanup_attempt($host,$from,$to); +ok(!DAKKAR::Graylister::is_second_attempt($host,$from,$to),'cleaned attempt'); diff --git a/t/06-age.t b/t/06-age.t new file mode 100644 index 0000000..0d47c5e --- /dev/null +++ b/t/06-age.t @@ -0,0 +1,34 @@ +# -*- mode: cperl -*- +use Test::More tests=>3; +use File::Temp 'tempdir'; + +my $basetime; + +BEGIN { +*CORE::GLOBAL::time=sub{$basetime}; +} + +use DAKKAR::Graylister; + +no warnings 'redefine'; + +my $dbdir=tempdir(CLEANUP=>1); + +local $ENV{SMTPMAILFROM}='gino'; +local $ENV{SMTPRCPTTO}='pino'; +local $ENV{TCPREMOTEIP}='10.1.2.3'; +local $ENV{DAKGL_DBNAME}="$dbdir/gray5.db"; +my ($host,$from,$to)=DAKKAR::Graylister::get_from_env(); + +$basetime=10; + +ok(!DAKKAR::Graylister::is_second_attempt($host,$from,$to),'first attempt'); +DAKKAR::Graylister::record_first_attempt($host,$from,$to); +ok(DAKKAR::Graylister::is_second_attempt($host,$from,$to),'second attempt'); + +$basetime=90000; + +DAKKAR::Graylister::remove_old_attempts(); + +ok(!DAKKAR::Graylister::is_second_attempt($host,$from,$to),'aged attempt'); + -- cgit v1.2.3