use DB_File;
use Fcntl;
use Sys::Syslog qw(:DEFAULT setlogsock);
$database_name="/Library/Server/Mail/Data/gldb/greylist.db";
$whitelist_host_file="/Library/Server/Mail/Data/gldb/whitelist_host";
$whitelist_domain_file="/Library/Server/Mail/Data/gldb/whitelist_domain";
$whitelist_db_name="/Library/Server/Mail/Data/gldb/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 host", $attr{"host_name"} if $verbose;
update_whitelist_db($attr{"host_name"}, 1);
open WHITELIST_FILE, ">> $whitelist_host_file" or
syslog $syslog_priority, "Error: unable to open whitelist host file: %s", $whitelist_host_file;
print WHITELIST_FILE "$attr{\"host_name\"}\n";
close WHITELIST_FILE;
}
}
sub add_whitelist_domain {
my ($_domain_name) = $attr{"domain_name"};
if ($_domain_name eq "") {
syslog $syslog_priority, "Warning: missing whitelist domain name attribute";
return 0;
}
open_whitelist_db() unless $whitelist_db_obj;
$value = read_whitelist_db($attr{"domain_name"});
if ($value == 0) {
syslog $syslog_priority, "adding domain: %s to whitelist doman", $attr{"domain_name"} if $verbose;
update_whitelist_db($attr{"domain_name"}, 1);
open WHITELIST_FILE, ">> $whitelist_domain_file" or
syslog $syslog_priority, "Error: unable to open whitelist domain file: %s", $whitelist_domain_file;
print WHITELIST_FILE "$attr{\"domain_name\"}\n";
close WHITELIST_FILE;
}
}
sub smtpd_access_policy {
my($key, $time_stamp, $now, $count, $domain);
open_database() unless $database_obj;
open_whitelist_db() unless $whitelist_db_obj;
$domain = get_domain_name($attr{"client_name"});
$count = read_whitelist_db($domain);
if ($count > 0) {
syslog $syslog_priority, "domain: %s is whitelisted", $domain if $verbose;
return "dunno";
}
$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);
if ( !$database_obj ) {
my $db_backup = $database_name . "." . time();
syslog $syslog_priority, "Warning: open failed for: %s : backing up to: %s",
$database_name, $db_backup;
rename $database_name, $db_backup ||
fatal_exit "Can't save %s as %s: $!", $database_name, $db_backup;
$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);
if ( !$whitelist_db_obj ) {
my $db_backup = $whitelist_db_name . "." . time();
syslog $syslog_priority, "Warning: open failed for: %s : backing up to: %s",
$whitelist_db_name, $db_backup;
rename $whitelist_db_name, $db_backup ||
fatal_exit "Can't save %s as %s: $!", $whitelist_db_name, $db_backup;
$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 get_domain_name {
my($in_host_name) = @_;
my($value);
my($count) = 0;
@tokens = split(/\./, $in_host_name);
$count = $#tokens;
$value=$tokens[$count-1] . "." . $tokens[$count];
return $value;
}
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();
} elsif ( $attr{"request"} eq "whitelist_domain" ) {
$action = add_whitelist_domain();
} 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", $_;
}
}