use warnings;
use Carp;
+use Encode qw(encode);
use Getopt::Long;
use Time::HiRes qw (usleep gettimeofday tv_interval);
use POSIX qw(:sys_wait_h errno_h signal_h);
use MIME::Parser;
+use MIME::WordDecoder qw(mime_to_perl_string);
use File::Path;
use Net::Server::PreFork;
use Net::Server::SIG qw(register_sig check_sigs);
use base qw(Net::Server::PreFork);
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+
my $opt_commandline = [$0, @ARGV];
my $opt_max_dequeue = 1;
my $opt_dequeue_time = 30;
initlog($prog_name, 'mail');
-if (!GetOptions ('testmode=s' => \$opt_testmode,
- 'pidfile=s' => \$opt_pidfile,
- 'untrusted' => \$opt_untrusted,
- 'database=s' => \$opt_database)) {
+if (!GetOptions(
+ 'testmode=s' => \$opt_testmode,
+ 'pidfile=s' => \$opt_pidfile,
+ 'untrusted' => \$opt_untrusted,
+ 'database=s' => \$opt_database
+)) {
die "usage error\n";
exit (-1);
}
-$opt_pidfile = "/var/run/${prog_name}.pid" if !$opt_pidfile;
+$opt_pidfile = "/run/${prog_name}.pid" if !$opt_pidfile;
my $max_servers = 1;
my $min_servers = 1;
my $max_requests = 1;
if (!$opt_testmode) {
-
my $pmg_cfg = PMG::Config->new();
my $demo = $pmg_cfg->get('admin', 'demo');
exit (0);
}
+ my $memory = PMG::Config::Mail::physical_memory();
+ if ($memory < 3840) {
+ warn "total memory below 4 GiB, consider setting 'max_filters' manually to avoid OOM-kills\n"
+ if !defined($pmg_cfg->get('mail', 'max_filters', 1));
+ }
+
$max_servers = $pmg_cfg->get('mail', 'max_filters') + 2;
$min_servers = 2;
$min_spare_servers = 1;
$max_requests = 20;
}
+print "using pre-fork workers with min=$min_servers, max=$max_servers, min_spare=$min_spare_servers"
+ .", max_spare=$max_spare_servers, max_requests=$max_requests\n";
+
$opt_max_dequeue = 0 if $opt_testmode;
my $daemonize = 1;
} if !$spaminfo;
my $vars = {
- 'SUBJECT' => $entity->head->get ('subject', 0) || 'No Subject',
+ 'SUBJECT' => PMG::Utils::decode_rfc1522($entity->head->get ('subject', 0) || 'No Subject'),
'RULE' => $rule->{name},
'RULE_INFO' => $msginfo->{rule_info},
'SENDER' => $msginfo->{sender},
'DOMAIN' => $msginfo->{domain},
'FQDN' => $msginfo->{fqdn},
'MSGID' => $queue->{msgid},
- 'VERSION' => PMG::pmgcfg::package() . "/" . PMG::pmgcfg::version() . "/" . PMG::pmgcfg::repoid(),
+ 'VERSION' => PMG::pmgcfg::package() . "/" . PMG::pmgcfg::release() . "/" . PMG::pmgcfg::repoid(),
};
$vars->{__spaminfo} = $spaminfo;
foreach my $s (@$sscores) {
if ($opt_testmode) {
- $sa_text .= sprintf ("%-22s %6s %s\n", $s->{rule},
- 1, $s->{desc} || '-');
+ $sa_text .= sprintf ("%-22s %6s %s\n", $s->{rule}, 1, $s->{desc} || '-');
} else {
- $sa_text .= sprintf ("%-22s %6s %s\n", $s->{rule},
- $s->{score}, $s->{desc} || '-');
+ $sa_text .= sprintf ("%-22s %6s %s\n", $s->{rule}, $s->{score}, $s->{desc} || '-');
}
}
$vars->{'SPAM_INFO'} = $sa_text;
my %rule_targets;
my %rule_actions;
my %rule_marks;
+ my %rule_spaminfo;
my $matching_rules = [];
my $rulecache = $self->{rulecache};
next;
}
- $rule_marks{$rule->{id}} =
+ my ($marks, $spaminfo) =
$rulecache->what_match ($rule->{id}, $queue, $entity, $msginfo, $dbh);
+ $rule_marks{$rule->{id}} = $marks;
+ $rule_spaminfo{$rule->{id}} = $spaminfo;
+
$rule_actions{$rule->{id}} = $rulecache->get_actions ($rule->{id});
my $fin = $rulecache->final ($rule->{id});
next if $final->{$target};
next if !defined ($rule_marks{$rule->{id}});
next if !defined ($rule_marks{$rule->{id}}->{$target});
- next if !defined ($rule_marks{$rule->{id}}->{$target}->{marks});
next if !$rulecache->to_match ($rule->{id}, $target, $ldap);
$final->{$target} = $fin;
my $targets = $rule_targets{$rule->{id}};
next if !$targets;
- my $spaminfo;
- foreach my $t (@$targets) {
- if ($rule_marks{$rule->{id}}->{$t} && $rule_marks{$rule->{id}}->{$t}->{spaminfo}) {
- $spaminfo = $rule_marks{$rule->{id}}->{$t}->{spaminfo};
- # we assume spam info is the same for all matching targets
- last;
- }
- }
-
- my $vars = $self->get_prox_vars ($queue, $entity, $msginfo, $rule,
- $rule_targets{$rule->{id}}, $spaminfo);
+ my $vars = $self->get_prox_vars (
+ $queue, $entity, $msginfo, $rule, $rule_targets{$rule->{id}}, $rule_spaminfo{$rule->{id}});
-
- my @sorted_actions =
- sort {$a->priority <=> $b->priority} @{$rule_actions{$rule->{id}}};
+ my @sorted_actions = sort {$a->priority <=> $b->priority} @{$rule_actions{$rule->{id}}};
foreach my $action (@sorted_actions) {
- $action->execute ($queue, $self->{ruledb}, $mod_group,
- $rule_targets{$rule->{id}},
- $msginfo, $vars, $rule_marks{$rule->{id}}->{marks}, $ldap);
+ $action->execute(
+ $queue, $self->{ruledb}, $mod_group, $rule_targets{$rule->{id}}, $msginfo, $vars,
+ $rule_marks{$rule->{id}}, $ldap
+ );
last if $action->final;
}
}
- # we deliver all mail not matched by any rule
- # (default action = accept)
+ # we deliver all mail not matched by any rule (default action = accept)
my $unmatched;
foreach my $target (@{$msginfo->{targets}}) {
next if $final->{$target};
my $prop = $self->{server};
if ($self->{ruledb}) {
- $self->log (0, "reloading configuration $database");
+ $self->log ('info', "reloading configuration $database");
$self->{ruledb}->close ();
}
$prop->{log_level} = 3;
- $self->log (0, "Filter daemon (re)started (max. $max_servers processes)");
+ $self->log ('info', "Filter daemon (re)started (max. $max_servers processes)");
eval { PMG::MailQueue::cleanup_active(); };
$self->log (0, "Cleanup failures: $@") if $@;
my ($backup_umask) = umask;
my $pmg_cfg = PMG::Config->new();
+ $pmg_cfg->write_smtp_filter_config();
# Note: you need to restart the daemon when you change 'rbl_checks'
my $rbl_checks = $pmg_cfg->get('spam', 'rbl_checks');
# reloading server configuration
if (defined $prop->{children}) {
foreach my $pid (keys %{$prop->{children}}) {
- kill (10, $pid); # SIGUSR1 childs
+ kill (10, $pid); # SIGUSR1 children
}
}
}
my $err;
- # do database maintainance here
+ # do database maintenance here, this is called every 30 secends
- # this is called every 30 secends
- eval {
- PMG::Utils::update_node_status_rrd();
- };
+ eval { PMG::Utils::update_node_status_rrd() };
if ($err = $@) {
$self->log(0, "ERROR: $err");
# continue
# return if tdiff less than 2 minutes
return if $tdiff < 2*60;
-
$last_dequeue_time = $ctime;
- $self->log (2, "starting database maintainance");
+ $self->log (2, "starting database maintenance");
my ($csec, $usec) = gettimeofday ();
my $cinfo = PVE::INotify::read_file("cluster.conf");
- my $dbh;
-
- eval {
- $dbh = PMG::DBTools::open_ruledb($database);
- };
+ my $dbh = eval { PMG::DBTools::open_ruledb($database) };
if ($err = $@) {
$self->log (0, "ERROR: $err");
return;
}
- eval {
- PMG::Statistic::update_stats($dbh, $cinfo);
- };
+ eval { PMG::Statistic::update_stats($dbh, $cinfo) };
$err = $@;
- my ($csec_end, $usec_end) = gettimeofday ();
- my $ptime = int (($csec_end-$csec)*1000 + ($usec_end - $usec)/1000);
-
if ($err) {
$self->log (0, $err);
} else {
- $self->log (2, "end database maintainance ($ptime ms)");
+ my ($csec_end, $usec_end) = gettimeofday ();
+ my $ptime = int (($csec_end - $csec) * 1000 + ($usec_end - $usec) / 1000);
+ $self->log (2, "end database maintenance ($ptime ms)");
}
$dbh->disconnect() if $dbh;
sub unpack_entity {
my ($self, $unpack, $entity, $msginfo, $queue) = @_;
- my $magic;
- my $path;
-
- if (($magic = $entity->{PMX_magic_ct}) &&
- ($path = $entity->{PMX_decoded_path})) {
+ my ($magic, $path) = $entity->@{'PMX_magic_ct', 'PMX_decoded_path'};
+ if ($magic && $path) {
my $filename = basename ($path);
if (PMG::Unpack::is_archive ($magic)) {
$unpack->{mime} = {};
- eval {
- $unpack->unpack_archive ($path, $magic);
- };
-
+ eval { $unpack->unpack_archive ($path, $magic) };
$self->log (3, "$queue->{logid}: unpack failed - $@") if $@;
$entity->{PMX_content_types} = $unpack->{mime};
+ $entity->{PMX_filenames} = $unpack->{filenames};
if ($opt_testmode) {
my $types = join (", ", sort keys (%{$entity->{PMX_content_types}}));
print $fh "Types:$filename: $types\n" if $types;
}
- my $elapsed = int(tv_interval ($start) * 1000);
-
+ my $elapsed = int(tv_interval($start) * 1000);
$self->log (3, "$queue->{logid}: unpack archive '$filename' done ($elapsed ms)");
}
}
- foreach my $part ($entity->parts) {
+ for my $part ($entity->parts) {
$self->unpack_entity ($unpack, $part, $msginfo, $queue);
}
my $cinfo = $self->{cinfo};
my $lcid = $cinfo->{local}->{cid};
- $msginfo->{test_fh} = PMG::AtomicFile->new("testresult.out", "w")
- if $opt_testmode;
+ $msginfo->{test_fh} = PMG::AtomicFile->new("testresult.out", "w") if $opt_testmode;
$msginfo->{trusted} = $self->{trusted};
$msginfo->{sender} = $smtp->{from};
$msginfo->{xforward} = $smtp->{xforward};
$msginfo->{targets} = $smtp->{to};
+ $msginfo->{param} = $smtp->{param};
+
+ my $dkim_sign = $msginfo->{trusted} && $pmg_cfg->get('admin', 'dkim_sign');
+ if ($dkim_sign) {
+ $msginfo->{dkim}->{sign} = $dkim_sign;
+ $msginfo->{dkim}->{sign_all} = $pmg_cfg->get('admin', 'dkim_sign_all_mail');
+ $msginfo->{dkim}->{selector} = $pmg_cfg->get('admin', 'dkim_selector');
+ }
$msginfo->{hostname} = PVE::INotify::nodename();
my $resolv = PVE::INotify::read_file('resolvconf');
my $maxfiles = $pmg_cfg->get('clamav', 'archivemaxfiles');
- my $entity = $queue->parse_mail($maxfiles);
+ my ($entity, $max_aid) = $queue->parse_mail($maxfiles);
+ $msginfo->{max_aid} = $max_aid;
$self->log (3, "$queue->{logid}: new mail message-id=%s", $queue->{msgid});
$insert_cmds .= ($queue->{sa_score} || 0) . ',';
$insert_cmds .= $dbh->quote($queue->{vinfo}) . ',';
$insert_cmds .= $time_total . ',';
- $insert_cmds .= $dbh->quote($msginfo->{sender}) . ');';
+ $insert_cmds .= $dbh->quote(encode('UTF-8', $msginfo->{sender})) . ');';
foreach my $r (@{$msginfo->{targets}}) {
- my $tmp = $dbh->quote($r);
+ my $tmp = $dbh->quote(encode('UTF-8',$r));
my $blocked = $queue->{status}->{$r} eq 'blocked' ? 1 : 0;
$insert_cmds .= "INSERT INTO CReceivers (CStatistic_CID, CStatistic_RID, Receiver, Blocked) " .
"VALUES ($lcid, currval ('cstatistic_id_seq'), $tmp, '$blocked'); ";
$self->done (1) if $err;
}
-# test sig_hup with: for ((;;)) ;do kill -HUP `cat /var/run/${prog_name}.pid`; done;
+# test sig_hup with: for ((;;)) ;do kill -HUP `cat /run/${prog_name}.pid`; done;
# wrapper to avoid multiple calls to sig_hup
sub sig_hup {
my $self = shift;
if (defined $prop->{children}) {
foreach my $pid (keys %{$prop->{children}}) {
- kill (1, $pid); # HUP childs
+ kill (1, $pid); # HUP children
}
}
- # nicely shutdown childs (give them max 30 seconds to shut down)
+ # nicely shutdown children (give them max 30 seconds to shut down)
my $previous_alarm = alarm(30);
eval {
local $SIG{ALRM} = sub { die "Timed Out!\n" };
$server->run();
} else {
- my $sender ='sender@proxtest.com';
- my $targets = ['target1@proxtest.com',
- 'target2@proxtest.com',
- 'target3@proxtest.com'];
+ my $sender ='sender@pmg.example';
+ my $targets = [
+ 'target1@pmg.example',
+ 'target2@pmg.example',
+ 'target3@pmg.example',
+ ];
my $smtp;
while (!$smtp) {