test repo for public-inbox, cleared periodically
 help / color / mirror / Atom feed
* greylist example
@ 2024-05-12 22:42 Eric Wong
  0 siblings, 0 replies; only message in thread
From: Eric Wong @ 2024-05-12 22:42 UTC (permalink / raw)
  To: test

#!/usr/bin/perl -w
use v5.12;
use autodie qw(open);
use DBI;
use Fcntl qw(LOCK_EX LOCK_UN);
use IO::Handle ();
use Sys::Syslog qw(:DEFAULT);

# sample Postfix greylister using SQLite
#
# in /etc/postfix/master.cf:
#
#    policy  unix  -       n       n       -       -       spawn
#      user=foo argv=/usr/bin/perl -w /path/to/greylist.perl
#
# in /etc/postfix/main.cf:
#
#    smtpd_recipient_restrictions =
#	...
#	reject_unauth_destination
#	check_policy_service unix:private/policy
#	...

# make sure user= in master.cf line can write to this
my $db_file = '/var/lib/greylist/greylist.sqlite3';
my $delay = 60;
my ($dbh, $lockfh, $dbg, %GREY_ADDR);
my $pi_config = '/home/pi/.public-inbox/config';
if (-r $pi_config) {
	open my $fh, '-|', qw(git config -z -l --includes -f), $pi_config;
	local $/ = "\0";
	my @l = grep /\.address\n/, <$fh>;
	chomp(@l);
	$GREY_ADDR{(split(/\n/, $_, 2))[1]} = undef for @l;
}

sub xflock ($$) {
	until (flock($_[0], $_[1])) { return unless $!{EINTR} }
	1;
}

# Demo SMTPD access policy routine. The result is an action just like
# it would be specified on the right-hand side of a Postfix access
# table.  Request attributes are available via the %attr hash.
sub smtpd_access_policy ($) {
	my ($attr) = @_;

	my $recipient = lc $attr->{recipient};
	return 'dunno' unless exists $GREY_ADDR{$recipient};

	state $get = $dbh->prepare(<<'');
SELECT expires FROM greystate WHERE key = ?

	state $upd = $dbh->prepare(<<'');
INSERT OR REPLACE INTO greystate (key,expires) VALUES (?,?)

	state $clean = $dbh->prepare(<<'');
DELETE FROM greystate WHERE expires < ?

	my $key = $attr->{client_address};
	$key =~ s/\.[0-9]+\z// or $key =~ s/:[0-9a-f]+\z//i;
	$key .= '|'.lc($attr->{sender});
	$get->bind_param(1, $key);

	xflock $lockfh, LOCK_EX;
	$dbh->begin_work;

	$get->execute;
	my ($ok_at) = $get->fetchrow_array;
	if (defined $ok_at) {
		$dbh->rollback;
		xflock $lockfh, LOCK_UN;

		time >= $ok_at ? 'dunno' : 'defer_if_permit still greylisted';
	} else {
		my $now = time;
		$upd->bind_param(1, $key);
		$upd->bind_param(2, $now + $delay);
		$clean->bind_param(1, $now - 86400 * 31);

		$clean->execute;
		$upd->execute;

		$dbh->commit;
		xflock $lockfh, LOCK_UN;

		syslog 'info', "greylisted $key";
		'defer_if_permit Greylisted';
	}
}

openlog 'greylist', 'pid', 'mail';
$| = 1;
open $dbg, '>>', "/tmp/dbg.$<.greylist";
my $pid = $$;
if ($dbg) {
	$dbg->autoflush;
	open STDERR, '>&', $dbg;
	print $dbg "$pid | $_\n" for (sort keys %GREY_ADDR);
}
open $lockfh, '>>', "$db_file.grey-flock";
$dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {
	AutoCommit => 1,
	RaiseError => 1,
	PrintError => 0,
	sqlite_use_immediate_transaction => 1,
	sqlite_see_if_its_a_number => 1,
}) or die "failed to open $db_file: $!";
$dbh->do('PRAGMA journal_mode = WAL');
$dbh->sqlite_busy_timeout(60_000); # slow disk :<
xflock $lockfh, LOCK_EX;
$dbh->do(<<'EOS');
CREATE TABLE IF NOT EXISTS greystate (
	key TEXT PRIMARY KEY NOT NULL,
	expires INTEGER NOT NULL,
	UNIQUE (key)
)
EOS
xflock $lockfh, LOCK_UN;

my ($k, $v, $attr, $action);
while (<STDIN>) {
	if (/([^=]+)=(.*)\n/) {
		$k = substr($1, 0, 512);
		$v = substr($2, 0, 512);
		$attr->{$k} = $v;
		print $dbg "$pid < $k=$v\n" if $dbg;
	} elsif ($_ eq "\n") {
		$action = smtpd_access_policy $attr;
		print $dbg "$pid > $action\n" if $dbg;
		print 'action='.$action."\n\n";
		%$attr = ();
	} else {
		chop;
		syslog 'error', 'unhandled garbage: %.100s', $_;
	}
}

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-05-12 22:42 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-05-12 22:42 greylist example Eric Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).