use DB_File;
use Fcntl;
use Sys::Syslog qw(:DEFAULT setlogsock);
# # Usage: greylist.pl [-v] # # Demo delegated Postfix SMTPD policy server. This server implements # greylisting. State is kept in a Berkeley DB database. Logging is # sent to syslogd. # # How it works: each time a Postfix SMTP server process is started # it connects to the policy service socket, and Postfix runs one # instance of this PERL script. By default, a Postfix SMTP server # process terminates after 100 seconds of idle time, or after serving # 100 clients. Thus, the cost of starting this PERL script is smoothed # out over time. # # To run this from /etc/postfix/master.cf: # # policy unix - n n - - spawn # user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl # # To use this from Postfix SMTPD, use in /etc/postfix/main.cf: # # smtpd_recipient_restrictions = # ... # reject_unauth_destination # check_policy_service unix:private/policy # ... # # NOTE: specify check_policy_service AFTER reject_unauth_destination # or else your system can become an open relay. # # To test this script by hand, execute: # # % perl greylist.pl # # Each query is a bunch of attributes. Order does not matter, and # the demo script uses only a few of all the attributes shown below: # # request=smtpd_access_policy # protocol_state=RCPT # protocol_name=SMTP # helo_name=some.domain.tld # queue_id=8045F2AB23 # sender=foo@bar.tld # recipient=bar@foo.tld # client_address=1.2.3.4 # client_name=another.domain.tld # instance=123.456.7 # sasl_method=plain # sasl_username=you # sasl_sender= # size=12345 # [empty line] # # The policy server script will answer in the same style, with an # attribute list followed by a empty line: # # action=dunno # [empty line] #
# # greylist status database and greylist time interval. DO NOT create the # greylist status database in a world-writable directory such as /tmp # or /var/tmp. DO NOT create the greylist database in a file system # that can run out of space. # # In case of database corruption, this script saves the database as # $database_name.time(), so that the mail system does not get stuck. #
$database_name="/var/mta/greylist.db";
$greylist_delay=60;
# # Auto-whitelist threshold. Specify 0 to disable, or the number of # successful "come backs" after which a client is no longer subject # to greylisting. #
$auto_whitelist_threshold = 10;
# # Syslogging options for verbose mode and for fatal errors. # NOTE: comment out the $syslog_socktype line if syslogging does not # work on your system. #
$syslog_socktype = 'unix'; # inet, unix, stream, console
$syslog_facility="mail";
$syslog_options="pid";
$syslog_priority="info";
# # 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($key, $time_stamp, $now, $count);
# Open the database on the fly.
open_database() unless $database_obj;
# Search the auto-whitelist. if ($auto_whitelist_threshold > 0) {
$count = read_database($attr{"client_address"}); if ($count > $auto_whitelist_threshold) {
return "dunno";
}
}
# Lookup the time stamp for this client/sender/recipient.
$key =
lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
$time_stamp = read_database($key);
$now = time();
# If this is a new request add this client/sender/recipient to the database. if ($time_stamp == 0) {
$time_stamp = $now;
update_database($key, $time_stamp);
}
# The result can be any action that is allowed in a Postfix access(5) map. # # To label mail, return ``PREPEND'' headername: headertext # # In case of success, return ``DUNNO'' instead of ``OK'' so that the # check_policy_service restriction can be followed by other restrictions. # # In case of failure, specify ``DEFER_IF_PERMIT optional text...'' # so that mail can still be blocked by other access restrictions. #
syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose; if ($now - $time_stamp > $greylist_delay) { # Update the auto-whitelist. if ($auto_whitelist_threshold > 0) {
update_database($attr{"client_address"}, $count + 1);
}
return "dunno";
} else {
return "defer_if_permit Service is unavailable";
}
}
# # You should not have to make changes below this point. #
sub LOCK_SH { 1 }; # Shared lock (used for reading).
sub LOCK_EX { 2 }; # Exclusive lock (used for writing).
sub LOCK_NB { 4 }; # Don't block (for testing).
sub LOCK_UN { 8 }; # Release lock.
# # Log an error and abort. #
sub fatal_exit {
my($first) = shift(@_);
syslog "err", "fatal: $first", @_;
exit 1;
}
# # Open hash database. #
sub open_database {
my($database_fd);
# Use tied database to make complex manipulations easier to express.
$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;
}
# # Read database. Use a shared lock to avoid reading the database # while it is being changed. XXX There should be a way to synchronize # our cache from the on-file database before looking up the key. #
sub read_database {
my($key) = @_;
my($value);
flock DATABASE_HANDLE, LOCK_SH ||
fatal_exit "Can't get shared lock on %s: $!", $database_name; # XXX Synchronize our cache from the on-disk copy before lookup.
$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;
}
# # Update database. Use an exclusive lock to avoid collisions with # other updaters, and to avoid surprises in database readers. XXX # There should be a way to synchronize our cache from the on-file # database before updating the database. #
sub update_database {
my($key, $value) = @_;
# # Signal 11 means that we have some kind of database corruption (yes # Berkeley DB should handle this better). Move the corrupted database # out of the way, and start with a new database. #
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";
}
$SIG{'SEGV'} = 'sigsegv_handler';
# # This process runs as a daemon, so it can't log to a terminal. Use # syslog so that people can actually see our messages. #
setlogsock $syslog_socktype;
openlog $0, $syslog_options, $syslog_facility;
# # We don't need getopt() for now. # while ($option = shift(@ARGV)) { if ($option eq "-v") {
$verbose = 1;
} else {
syslog $syslog_priority, "Invalid option: %s. Usage: %s [-v]",
$option, $0;
exit 1;
}
}
# # Unbuffer standard output. #
select((select(STDOUT), $| = 1)[0]);
# # Receive a bunch of attributes, evaluate the policy, send the result. # 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{$_};
}
}
fatal_exit "unrecognized request type: '%s'", $attr{request}
unless $attr{"request"} eq "smtpd_access_policy";
$action = smtpd_access_policy();
syslog $syslog_priority, "Action: %s", $action if $verbose;
print STDOUT "action=$action\n\n";
%attr = ();
} else {
chop;
syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.