]> git.proxmox.com Git - pmg-api.git/blobdiff - src/bin/pmg-smtp-filter
smtp-filter: whitespace and indentation clean-ups
[pmg-api.git] / src / bin / pmg-smtp-filter
index a5bf246595d876a9825051067757a59d4949fc43..854d9bb4cb9e58ca2ad37a6241b0903c59da7dbb 100755 (executable)
@@ -4,11 +4,13 @@ use strict;
 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);
@@ -47,6 +49,10 @@ use PMG::Statistic;
 
 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;
@@ -64,15 +70,17 @@ my $prog_name = 'pmg-smtp-filter';
 
 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;
@@ -81,7 +89,6 @@ my $max_spare_servers = 0;
 my $max_requests = 1;
 
 if (!$opt_testmode) {
-
     my $pmg_cfg = PMG::Config->new();
 
     my $demo = $pmg_cfg->get('admin', 'demo');
@@ -91,6 +98,12 @@ if (!$opt_testmode) {
        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;
@@ -98,6 +111,9 @@ if (!$opt_testmode) {
     $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;
@@ -152,7 +168,7 @@ sub get_prox_vars {
     } 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},
@@ -166,7 +182,7 @@ sub get_prox_vars {
        '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;
@@ -203,11 +219,9 @@ sub get_prox_vars {
 
        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;
@@ -228,6 +242,7 @@ sub apply_rules {
     my %rule_targets;
     my %rule_actions;
     my %rule_marks;
+    my %rule_spaminfo;
     my $matching_rules = [];
 
     my $rulecache = $self->{rulecache};
@@ -263,9 +278,12 @@ sub apply_rules {
            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});
 
@@ -274,7 +292,6 @@ sub apply_rules {
            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;
@@ -317,32 +334,21 @@ sub apply_rules {
        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};
@@ -365,7 +371,7 @@ sub load_config {
     my $prop = $self->{server};
 
     if ($self->{ruledb}) {
-       $self->log (0, "reloading configuration $database");
+       $self->log ('info', "reloading configuration $database");
        $self->{ruledb}->close ();
     }
 
@@ -425,7 +431,7 @@ sub pre_loop_hook {
 
     $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 $@;
@@ -440,6 +446,7 @@ sub pre_loop_hook {
     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');
@@ -463,7 +470,7 @@ sub pre_loop_hook {
        # reloading server configuration
        if (defined $prop->{children}) {
            foreach my $pid (keys %{$prop->{children}}) {
-               kill (10, $pid); # SIGUSR1 childs
+               kill (10, $pid); # SIGUSR1 children
            }
        }
     }
@@ -505,12 +512,9 @@ sub run_dequeue {
 
     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
@@ -521,37 +525,29 @@ sub run_dequeue {
 
     # 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;
@@ -561,12 +557,9 @@ sub run_dequeue {
 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)) {
@@ -576,13 +569,11 @@ sub unpack_entity {
 
            $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}}));
@@ -591,13 +582,12 @@ sub unpack_entity {
                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);
     }
 
@@ -615,8 +605,7 @@ sub handle_smtp {
     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};
 
@@ -638,6 +627,14 @@ sub handle_smtp {
        $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');
@@ -664,7 +661,8 @@ sub handle_smtp {
 
        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});
 
@@ -804,10 +802,10 @@ sub handle_smtp {
        $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'); ";
@@ -889,7 +887,7 @@ sub process_request {
   $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;
@@ -921,11 +919,11 @@ sub pre_server_close_hook {
 
     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" };
@@ -952,10 +950,12 @@ if (!$opt_testmode) {
        $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) {