use DB_File;
use Fcntl;
use Sys::Syslog qw(:DEFAULT setlogsock);
$database_name="/Library/Server/Mail/Data/mta/greylist.db";
$whitelist_file="/Library/Server/Mail/Data/mta/whitelist";
$whitelist_db_name="/Library/Server/Mail/Data/mta/whitelist.db";
$greylist_delay=60;
$auto_whitelist_threshold = 10;
$syslog_socktype = 'unix'; $syslog_facility="mail";
$syslog_options="pid";
$syslog_priority="info";
sub add_whitelist {
my ($_host_name) = $attr{"host_name"};
if ($_host_name eq "") {
syslog $syslog_priority, "Warning: missing whitelist host name attribute";
return 0;
}
open_whitelist_db() unless $whitelist_db_obj;
$value = read_whitelist_db($attr{"host_name"});
if ($value == 0) {
syslog $syslog_priority, "adding host: %s to whitelist", $attr{"host_name"} if $verbose;
update_whitelist_db($attr{"host_name"}, 1);
open WHITELIST_FILE, ">> $whitelist_file" or
syslog $syslog_priority, "Error: unable to open whitelist file: %s", $whitelist_file;
print WHITELIST_FILE "$attr{\"host_name\"}\n";
close WHITELIST_FILE;
}
}
sub smtpd_access_policy {
my($key, $time_stamp, $now, $count);
open_database() unless $database_obj;
open_whitelist_db() unless $whitelist_db_obj;
$count = read_whitelist_db($attr{"client_name"});
if ($count > 0) {
syslog $syslog_priority, "host: %s is whitelisted", $attr{"client_name"} if $verbose;
return "dunno";
}
if ($auto_whitelist_threshold > 0) {
$count = read_database($attr{"client_address"});
if ($count > $auto_whitelist_threshold) {
return "dunno";
}
}
$key =
lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
$time_stamp = read_database($key);
$now = time();
if ($time_stamp == 0) {
$time_stamp = $now;
update_database($key, $time_stamp);
}
syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
if ($now - $time_stamp > $greylist_delay) {
if ($auto_whitelist_threshold > 0) {
update_database($attr{"client_address"}, $count + 1);
}
return "dunno";
} else {
syslog $syslog_priority, "Temporary message rejection to: <$attr{\"recipient\"}> from: <$attr{\"sender\"}> sent from: [$attr{\"client_address\"}] for: $greylist_delay seconds due to greylisting";
return "defer_if_permit Service is unavailable";
}
}
sub LOCK_SH { 1 }; sub LOCK_EX { 2 }; sub LOCK_NB { 4 }; sub LOCK_UN { 8 };
sub fatal_exit {
my($first) = shift(@_);
syslog "err", "fatal: $first", @_;
exit 1;
}
sub open_database {
my($database_fd);
$database_obj = tie(%db_hash, 'DB_File', $database_name,
O_CREAT|O_RDWR, 0644, $DB_BTREE) ||
fatal_exit "Cannot open database %s: $!", $database_name;
$database_fd = $database_obj->fd;
open DATABASE_HANDLE, "+<&=$database_fd" ||
fatal_exit "Cannot fdopen database %s: $!", $database_name;
syslog $syslog_priority, "open %s", $database_name if $verbose;
}
sub open_whitelist_db {
my($whitelist_db_fd);
$whitelist_db_obj = tie(%db_hash, 'DB_File', $whitelist_db_name,
O_CREAT|O_RDWR, 0644, $DB_BTREE) ||
fatal_exit "Cannot open database %s: $!", $whitelist_db_name;
$whitelist_db_fd = $whitelist_db_obj->fd;
open WHITELIST_DB_HANDLE, "+<&=$whitelist_db_fd" ||
fatal_exit "Cannot fdopen database %s: $!", $whitelist_db_name;
syslog $syslog_priority, "open %s", $whitelist_db_name if $verbose;
}
sub read_database {
my($key) = @_;
my($value);
flock DATABASE_HANDLE, LOCK_SH ||
fatal_exit "Can't get shared lock on %s: $!", $database_name;
$value = $db_hash{$key};
syslog $syslog_priority, "lookup %s: %s", $key, $value if $verbose;
flock DATABASE_HANDLE, LOCK_UN ||
fatal_exit "Can't unlock %s: $!", $database_name;
return $value;
}
sub read_whitelist_db {
my($key) = @_;
my($value);
flock WHITELIST_DB_HANDLE, LOCK_SH ||
fatal_exit "Can't get shared lock on %s: $!", $whitelist_db_name;
$value = $db_hash{$key};
syslog $syslog_priority, "whitelist lookup %s: %s", $key, $value if $verbose;
flock WHITELIST_DB_HANDLE, LOCK_UN ||
fatal_exit "Can't unlock %s: $!", $whitelist_db_name;
return $value;
}
sub update_database {
my($key, $value) = @_;
syslog $syslog_priority, "store %s: %s", $key, $value if $verbose;
flock DATABASE_HANDLE, LOCK_EX ||
fatal_exit "Can't exclusively lock %s: $!", $database_name;
$db_hash{$key} = $value;
$database_obj->sync() &&
fatal_exit "Can't update %s: $!", $database_name;
flock DATABASE_HANDLE, LOCK_UN ||
fatal_exit "Can't unlock %s: $!", $database_name;
}
sub update_whitelist_db {
my($key, $value) = @_;
syslog $syslog_priority, "store whitelist host %s: %s", $key, $value if $verbose;
flock WHITELIST_DB_HANDLE, LOCK_EX ||
fatal_exit "Can't exclusively lock %s: $!", $whitelist_db_name;
$db_hash{$key} = $value;
$whitelist_db_obj->sync() &&
fatal_exit "Can't update %s: $!", $whitelist_db_name;
flock WHITELIST_DB_HANDLE, LOCK_UN ||
fatal_exit "Can't unlock %s: $!", $whitelist_db_name;
}
sub sigsegv_handler {
my $backup = $database_name . "." . time();
rename $database_name, $backup ||
fatal_exit "Can't save %s as %s: $!", $database_name, $backup;
fatal_exit "Caught signal 11; the corrupted database is saved as $backup";
my $wl_backup = $whitelist_db_name . "." . time();
rename $whitelist_db_name, $wl_backup ||
fatal_exit "Can't save %s as %s: $!", $whitelist_db_name, $wl_backup;
fatal_exit "Caught signal 11; the corrupted database is saved as $wl_backup";
}
$SIG{'SEGV'} = 'sigsegv_handler';
setlogsock $syslog_socktype;
openlog $0, $syslog_options, $syslog_facility;
while ($option = shift(@ARGV)) {
if ($option eq "-v") {
$verbose = 1;
} else {
syslog $syslog_priority, "Invalid option: %s. Usage: %s [-v]",
$option, $0;
exit 1;
}
}
select((select(STDOUT), $| = 1)[0]);
while (<STDIN>) {
if (/([^=]+)=(.*)\n/) {
$attr{substr($1, 0, 512)} = substr($2, 0, 512);
} elsif ($_ eq "\n") {
if ($verbose) {
for (keys %attr) {
syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};
}
}
if ( $attr{"request"} eq "smtpd_access_policy" ) {
$action = smtpd_access_policy();
} elsif ( $attr{"request"} eq "whitelist" ) {
$action = add_whitelist();
} else {
fatal_exit "unrecognized request type: '%s'", $attr{request};
}
syslog $syslog_priority, "Action: %s", $action if $verbose;
print STDOUT "action=$action\n\n";
%attr = ();
} else {
chop;
syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;
}
}