diff options
-rw-r--r-- | README | 37 | ||||
-rwxr-xr-x | bin/spamcat | 2 | ||||
-rw-r--r-- | config/spamcat.conf | 1 | ||||
-rw-r--r-- | lib/SpamCat.pm | 38 | ||||
-rwxr-xr-x | t/bin.t | 4 | ||||
-rw-r--r-- | t/conf.t | 3 | ||||
-rwxr-xr-x | t/delivert | 25 | ||||
-rw-r--r-- | t/fixtures/bar.expected | 15 | ||||
-rw-r--r-- | t/fixtures/sample.conf | 2 | ||||
-rw-r--r-- | t/lib.t | 50 |
10 files changed, 78 insertions, 99 deletions
@@ -76,8 +76,9 @@ total messages from this sender, and there are 19 remaining). * INSTALLATION 1) See INSTALL to install the library and spamcat executable. 2) Once the spamcat executable is installed you'll need to add it to - your procmail, sieve, or some other similar device that takes an - email on standard input and expects local delivery. + your procmail, sieve, or some other similar device that writes + email to standard input and receives the transformed email on + standard output. 3) You will also need to create a config file for spamcat (see config/spamcat.conf for an example) somewhere that can be read by spamcat. @@ -89,35 +90,35 @@ total messages from this sender, and there are 19 remaining). *** main.cf Postfix needs to know that the spam domain should be handled locally. -#+BEGIN_COMMENT -virtual_mailbox_domains = spamcat.domain somewhere.else -#+END_COMMENT +#+begin_example + Virtual_mailbox_domains = spamcat.domain somewhere.else +#+end_example *** virtual aliases table Make sure postfix can route the spam domain to the user who should receive it. -#+BEGIN_COMMENT -@spamcat.domain hidden-address@somewhere.else -#+END_COMMENT +#+begin_example + @spamcat.domain hidden-address@somewhere.else +#+end_example ** Dovecot/Sieve The sieve file for the user which receives mail for the spam domain should pipe the email into spamcat, which will handle final delivery for the spam domain. -#+BEGIN_COMMENT -require ["fileinto", "envelope", "vnd.dovecot.pipe"]; +#+begin_example + require ["fileinto", "envelope", "vnd.dovecot.filter"]; + + if address :contains "to" "@spamcat.domain" { + filter "spamcat" ["-c", "/path/to/spamcat.conf"]; + } +#+end_example -if address :contains "to" "@spamcat.domain" { - pipe :try "spamcat" ["-c", "/path/to/spamcat.conf"]; - fileinto "Junk"; -} -#+END_COMMENT ** DNS The MX record for your spam domain should point to the MTA that hosts spamcat. -#+BEGIN_COMMENT -spamcat.domain MX 10 yourmta.domain. -#+END_COMMENT +#+begin_example + spamcat.domain MX 10 yourmta.domain. +#+end_example diff --git a/bin/spamcat b/bin/spamcat index 9af410d..dbe9ea7 100755 --- a/bin/spamcat +++ b/bin/spamcat @@ -108,5 +108,5 @@ if ($#ARGV >= 0) { pod2usage(1); } } else { - $sch->deliver; + $sch->filter; } diff --git a/config/spamcat.conf b/config/spamcat.conf index 1f1313d..5ef87cd 100644 --- a/config/spamcat.conf +++ b/config/spamcat.conf @@ -1,4 +1,3 @@ DBPATH=/var/vmail/spork.org/bjc/spamcat.sqlite3 DEFAULT_COUNT=20 -DELIVER=/usr/local/libexec/dovecot/deliver DOMAINS=bjc.spork.org
\ No newline at end of file diff --git a/lib/SpamCat.pm b/lib/SpamCat.pm index 4807149..cbae3fd 100644 --- a/lib/SpamCat.pm +++ b/lib/SpamCat.pm @@ -23,7 +23,7 @@ sub new { bless \%conf, $class; } -sub deliver { +sub filter { my ($self) = @_; local $/; @@ -44,26 +44,22 @@ sub deliver { } } - if (defined $count) { - return if $count == 0; - - if ($count > 0) { - my $count_str = '[' . ($self->{default_count} - $count + 1) . '/' . $self->{default_count} . ']'; - my $new_subject = $email->header('Subject'); - if ($new_subject) { - $new_subject .= ' - ' . $count_str; - } else { - $new_subject = $count_str; - } - $email->header_set('Subject' => $new_subject); - $email->header_set('X-SpamCat-Remaining' => $count); - } + # + # Negative counts indicate unlimited delivery. + # + if (defined $count && $count >= 0) { + my $count_str = '[' . ($self->{default_count} - $count + 1) . '/' . $self->{default_count} . ']'; + my $new_subject = $email->header('Subject'); + if ($new_subject) { + $new_subject .= ' - ' . $count_str; + } else { + $new_subject = $count_str; + } + $email->header_set('Subject' => $new_subject); + $email->header_set('X-SpamCat-Remaining' => $count); } - my $deliverfh = IO::File->new("| " . $self->{deliver}) || - die "Couldn't open pipe to " . $self->{deliver} . ": $!\n"; - print $deliverfh $email->as_string; - $deliverfh->close; + print $email->as_string; } sub parse_to { @@ -164,8 +160,10 @@ sub decrement_count_t { if (!defined $count) { $count = $self->{default_count}; $q = 'INSERT INTO emails (count, sender) VALUES (?, ?)'; + } elsif ($count <= 0) { + return $count; } else { - $count = $count <= 0 ? $count : $count - 1; + $count--; $q = "UPDATE emails SET count = ?, modified = CURRENT_TIMESTAMP WHERE sender = ?"; } @@ -22,7 +22,6 @@ BEGIN { die "Couldn't create $tmpdir/spamcat.conf: $!\n"; print $fh "DBPATH = $tmpdir/spamcat.sqlite3\n"; print $fh "DEFAULT_COUNT = 20\n"; - print $fh "DELIVER = t/delivert $tmpdir\n"; print $fh "DOMAINS = spamcat.example.com\n"; } @@ -39,8 +38,7 @@ my @dumpconfig = `$spamcat -c $conffile dumpconfig`; my %got = parse_configdump(@dumpconfig); my %expected = (DBPATH => '/tmp/spamcat.sqlite3', DEFAULT_COUNT => 10, - DELIVER => 't/delivert', - DOMAINS => "spamcat.example.com, spamcat2.example.com, spamcat3"); + DOMAINS => "spamcat.example.com, spamcat2.example.com, spamcat3"); is_deeply(\%got, \%expected); # Unknown senders have the default number of mails left. @@ -1,6 +1,6 @@ # -*- mode: cperl -*- -use Test::More tests => 7; +use Test::More tests => 6; use strict; use warnings; @@ -13,6 +13,5 @@ ok(%conf); is($conf{dbpath}, '/tmp/spamcat.sqlite3'); is($conf{default_count}, 10); -is($conf{deliver}, 't/delivert'); is_deeply($conf{domains}, ['spamcat.example.com', 'spamcat2.example.com', 'spamcat3']); diff --git a/t/delivert b/t/delivert deleted file mode 100755 index 794e0a1..0000000 --- a/t/delivert +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env perl - -use Email::Simple; -use IO::File; - -use strict; -use warnings; - -die usage() unless $#ARGV == 0; -my $path = shift; - -local $/; -my $email = Email::Simple->new(<>); - -my $msgid = $email->header('Message-ID'); -$msgid =~ s/<(.*)@.*>/$1/; - -my $fh = IO::File->new(">$path/$msgid") || - die "Couldn't write to $path/$msgid: $!\n"; -print $fh $email->as_string; -$fh->close; - -sub usage { - "Usage: $0 path\n"; -} diff --git a/t/fixtures/bar.expected b/t/fixtures/bar.expected new file mode 100644 index 0000000..e554c65 --- /dev/null +++ b/t/fixtures/bar.expected @@ -0,0 +1,15 @@ +Return-Path: <test@mta.example.com>
+Delivered-To: spamcat@example.com
+Received: from mta.example.com
+ by mta.example.com (Dovecot) with LMTP id 9DDcI25oblTvgAEAQc1eRg
+ for <spamcat@example.com>; Thu, 20 Nov 2014 17:17:18 -0500
+Received: by mta.example.com (Postfix, from userid 1001)
+ id 844956F1EE; Thu, 20 Nov 2014 17:17:18 -0500 (EST)
+To: bar@spamcat.example.com
+Subject: test - [21/20]
+Message-Id: <bar@mta.example.com>
+Date: Thu, 20 Nov 2014 17:17:18 -0500 (EST)
+From: sender@example.com (Spamcat Sender)
+X-SpamCat-Remaining: 0
+
+Sample email.
diff --git a/t/fixtures/sample.conf b/t/fixtures/sample.conf index e0d644b..ba3054d 100644 --- a/t/fixtures/sample.conf +++ b/t/fixtures/sample.conf @@ -2,5 +2,5 @@ DBPATH=/tmp/spamcat.sqlite3 # So should most whitespace and inline comments. DEFAULT_COUNT =10 # Make sure key=val doesn't work in comments -DELIVER= t/delivert # by saying DEFAULT_COUNT=8 + # by saying DEFAULT_COUNT=8 DOMAINS = spamcat.example.com, spamcat2.example.com spamcat3 @@ -1,6 +1,6 @@ # -*- Mode: cperl -*- -use Test::More tests => 46; +use Test::More tests => 40; use strict; use warnings; @@ -10,8 +10,7 @@ BEGIN { $tmpdir = "/tmp/spamcat.t.$$"; %conf = (dbpath => "$tmpdir/spamcat.sqlite3", default_count => 20, - deliver => "t/delivert $tmpdir", - domains => ['spamcat.example.com', 'spamcat2.example.com']); + domains => ['spamcat.example.com', 'spamcat2.example.com']); system("rm -rf $tmpdir") == 0 or die "Couldn't remove $tmpdir: $!\n"; @@ -58,13 +57,13 @@ is($addrs[0], 'baz@pham.com'); is($addrs[0], 'baz@pham.com'); is($addrs[1], 'one@two.com'); -ok(SpamCat->can('deliver'), 'Has delivery method'); -test_file('foo', 1); -test_file('foo2', 1); -test_file('multiple', 1); -test_file('wrongdomain', 1); -test_file('nosubj', 1); -test_file('bar', 0); +ok(SpamCat->can('filter'), 'Has filter method'); +test_file('foo'); +test_file('foo2'); +test_file('multiple'); +test_file('wrongdomain'); +test_file('nosubj'); +test_file('bar'); $sch->set_count('always-allowed', -1); test_file('always-allowed', 1); @@ -85,31 +84,26 @@ is($rows[4]->{sender}, 'nosubj'); is($rows[4]->{count}, 20); sub test_file { - my ($filen, $should_exist) = @_; + my ($filen) = @_; my $input = IO::File->new("<t/fixtures/$filen") || die "Couldn't open $filen: $!\n"; my $inputfd = fileno($input); open STDIN, ">&$inputfd" || die "Couldn't open $inputfd: $!\n"; + open STDOUT, ">$tmpdir/$filen" || die "Couldn't open $tmpdir/$filen: $!\n"; - $sch->deliver(); + $sch->filter(); - if ($should_exist) { - ok(-f "$tmpdir/$filen") || diag("$tmpdir/$filen doesn't exist."); + local $/; + my $fh = IO::File->new("<$tmpdir/$filen") || + die "Couldn't open $tmpdir/$filen for reading: $!\n"; + my $got = <$fh>; + $fh->close; - local $/; - my $fh = IO::File->new("<$tmpdir/$filen") || - die "Couldn't open $tmpdir/$filen for reading: $!\n"; - my $got = <$fh>; - $fh->close; + $fh = IO::File->new("<t/fixtures/$filen.expected") || + die "Couldn't open t/fixtures/$filen.expected for reading: $!\n"; + my $expected = <$fh>; + $fh->close; - $fh = IO::File->new("<t/fixtures/$filen.expected") || - die "Couldn't open t/fixtures/$filen.expected for reading: $!\n"; - my $expected = <$fh>; - $fh->close; - - is($got, $expected) || diag("Test for $filen output failed."); - } else { - ok(! -f "$tmpdir/$filen") || diag("$tmpdir/$filen exists."); - } + is($got, $expected) || diag("Test for $filen output failed."); } |