summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@luxion>2007-12-27 14:36:45 +0000
committerdakkar <dakkar@luxion>2007-12-27 14:36:45 +0000
commitf67531bad2b0aaebfea7ec63922290f28e86d6ab (patch)
treec5650db6cef8af570767ccb00bfacdf422a49632
parent r3302@rfc-1918: dakkar | 2007-12-27 13:18:56 +0100 (diff)
downloadGraylister-f67531bad2b0aaebfea7ec63922290f28e86d6ab.tar.gz
Graylister-f67531bad2b0aaebfea7ec63922290f28e86d6ab.tar.bz2
Graylister-f67531bad2b0aaebfea7ec63922290f28e86d6ab.zip
r3304@rfc-1918: dakkar | 2007-12-27 13:19:58 +0100
prepraering fro branching
-rw-r--r--Makefile.PL12
-rw-r--r--lib/DAKKAR/Graylister.pm229
-rw-r--r--script/dakkar-graylister6
-rw-r--r--t/01-use.t16
-rw-r--r--t/02-input.t19
-rw-r--r--t/03-white.t19
-rw-r--r--t/04-gray.t18
-rw-r--r--t/05-gray-empty.t18
-rw-r--r--t/06-age.t34
9 files changed, 371 insertions, 0 deletions
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 time<?',
+ {},(time()-86400));
+ }
+}
+
+
+sub is_blacklisted {
+ my ($host)=@_;
+ my $look=Net::DNSBLLookup->new();
+ 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');
+