]> git.proxmox.com Git - pmg-api.git/blobdiff - src/bin/pmgpolicy
complete changelog for 8.0.2
[pmg-api.git] / src / bin / pmgpolicy
index 59d28f7d909672e6e4de624307da46513e622c2c..f5335dea3870253b48c7e72460ef42c70c9aceb9 100755 (executable)
@@ -15,7 +15,7 @@ use Time::HiRes qw(gettimeofday);
 use Time::Zone;
 
 use PVE::INotify;
-use PVE::Tools;
+use PVE::Tools qw($IPV4RE $IPV6RE);
 use PVE::SafeSyslog;
 
 use PMG::Utils;
@@ -116,7 +116,7 @@ sub update_rbl_stats {
 
     my $sth = $dbh->prepare(
        'INSERT INTO LocalStat (Time, RBLCount, PregreetCount, CID, MTime) ' .
-       'VALUES (?, ?, ?, ?, EXTRACT(EPOCH FROM now())) ' .
+       'VALUES (?, ?, ?, ?, EXTRACT(EPOCH FROM now())::INTEGER) ' .
        'ON CONFLICT (Time, CID) DO UPDATE SET ' .
        'RBLCount = LocalStat.RBLCount + excluded.RBLCount, ' .
        'PregreetCount = LocalStat.PregreetCount + excluded.PregreetCount, ' .
@@ -129,7 +129,7 @@ sub update_rbl_stats {
 sub run_dequeue {
     my $self = shift;
 
-    $self->log(2, "starting policy database maintainance (greylist, rbl)");
+    $self->log(2, "starting policy database maintenance (greylist, rbl)");
 
     my $cinfo = PMG::ClusterConfig->new();
     my $lcid = $cinfo->{local}->{cid};
@@ -274,7 +274,7 @@ sub run_dequeue {
        $self->log(0, "greylist database update error: $err");
     }
 
-    $self->log(2, "end policy database maintainance ($rbltime ms, $ptime ms)");
+    $self->log(2, "end policy database maintenance ($rbltime ms, $ptime ms)");
 
     $dbh->disconnect() if $dbh;
 }
@@ -292,7 +292,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
            }
        }
     };
@@ -318,10 +318,14 @@ sub load_config {
     my $pmg_cfg = PMG::Config->new ();
     $self->{use_spf} = $pmg_cfg->get('mail', 'spf');
     $self->{use_greylist} = $pmg_cfg->get('mail', 'greylist');
+    $self->{use_greylist6} = $pmg_cfg->get('mail', 'greylist6');
+    $self->{greylistmask4} = $pmg_cfg->get('mail', 'greylistmask4');
+    $self->{greylistmask6} = $pmg_cfg->get('mail', 'greylistmask6');
 
     if ($opt_testmode) {
        $self->{use_spf} = 1;
        $self->{use_greylist} = 1;
+       $self->{use_greylist6} = 1;
     }
 
     my $nodename = PVE::INotify::nodename();
@@ -377,7 +381,8 @@ sub child_init_hook {
        $self->{dns_resolver} = Net::DNS::Resolver->new(%dnsargs);
 
        $self->{spf_server} = Mail::SPF::Server->new(
-           hostname => $self->{fqdn}, dns_resolver => $self->{dns_resolver});
+           hostname => $self->{fqdn}, dns_resolver => $self->{dns_resolver},
+           default_authority_explanation => 'Rejected by SPF: %{C} is not a designated mailserver for %{S} (context %{_scope}, on %{R})');
     };
     if (my $err = $@) {
        $self->log(0, $err);
@@ -407,7 +412,7 @@ sub get_spf_result {
     my $auth_expl;
 
     # we only use helo tests when we have no sender,
-    # helo is sometimes empty, so we cant use SPF helo tests
+    # helo is sometimes empty, so we can't use SPF helo tests
     # in that case - strange
     if ($helo && !$sender) {
        my $query;
@@ -484,7 +489,7 @@ sub is_backup_mx {
        $self->{cache}->{$dkey}->{status} = 1;
        my @mxa = grep { $_->type eq 'MX' } $mx->answer;
        my @mxl = sort { $a->preference <=> $b->preference } @mxa;
-       # shift @mxl; # optionaly skip primary MX ?
+       # shift @mxl; # optionally skip primary MX ?
        foreach my $rr (@mxl) {
            my $a = $resolver->send ($rr->exchange, 'A');
            if ($a) {
@@ -529,7 +534,7 @@ sub greylist_value {
     if (defined ($user) && defined ($domain)) {
        # see http://cr.yp.to/proto/verp.txt
        $user =~ s/\+.*//; # strip extensions  (mailing-list VERP)
-       $user =~ s/\b\d+\b/#/g; #replace nubmers in VERP address
+       $user =~ s/\b\d+\b/#/g; #replace numbers in VERP address
        $sender = "$user\@$domain";
     }
 
@@ -550,8 +555,17 @@ sub greylist_value {
        return 'dunno';
     }
 
-    my ($net, $host) = $ip =~ m/(\d+\.\d+\.\d+)\.(\d+)/; # IPv4 for now
-    return 'dunno' if !defined($net);
+    my $masklen;
+    my $do_greylist = 0;
+    if ($ip =~ m/$IPV4RE/) {
+       $masklen = $self->{greylistmask4};
+       $do_greylist = $self->{use_greylist};
+    } elsif ($ip =~ m/$IPV6RE/) {
+       $masklen = $self->{greylistmask6};
+       $do_greylist = $self->{use_greylist6};
+    } else {
+       return 'dunno';
+    }
 
     my $spf_header;
 
@@ -604,9 +618,10 @@ sub greylist_value {
                    # check if there is already a record in the GL database
                    my $sth = $dbh->prepare(
                        "SELECT * FROM CGreylist " .
-                       "where IPNet = ? AND Sender = ? AND Receiver = ?");
+                       "WHERE IPNet::cidr = network(set_masklen(?, ?)) AND ".
+                       "Sender = ? AND Receiver = ?");
 
-                   $sth->execute($net, $sender, $rcpt);
+                   $sth->execute($ip, $masklen, $sender, $rcpt);
                    my $ref = $sth->fetchrow_hashref();
                    $sth->finish();
 
@@ -615,9 +630,8 @@ sub greylist_value {
                    # table later. We set 'blocked' to 100000 to identify those entries.
 
                    if (!defined($ref->{rctime})) {
-
-                       $dbh->do($PMG::DBTools::cgreylist_merge_sql, undef,
-                                $net, $host, $sender, $rcpt, $instance,
+                       $dbh->do(PMG::DBTools::cgreylist_merge_sql(1), undef,
+                                $ip, $masklen, $sender, $rcpt, $instance,
                                 $ctime, $ctime + 10, 0, 100000, 0, $ctime, $self->{lcid});
                    }
 
@@ -643,13 +657,13 @@ sub greylist_value {
        $self->{cache}->{$instance}->{spf_header_added} = 1;
     }
 
-    return $res if !$self->{use_greylist};
+    return $res if !$do_greylist;
 
     my $defer_res = "defer_if_permit Service is unavailable (try later)";
 
     eval {
 
-       # we dont use alarm here, because it does not work with DBI
+       # we don't use alarm here, because it does not work with DBI
 
        $dbh->begin_work;
 
@@ -658,9 +672,10 @@ sub greylist_value {
 
        my $sth = $dbh->prepare(
            "SELECT * FROM CGreylist " .
-           "where IPNet = ? AND Sender = ? AND Receiver = ?");
+           "WHERE IPNet::cidr = network(set_masklen(?, ?)) AND ".
+           "Sender = ? AND Receiver = ?");
 
-       $sth->execute($net, $sender, $rcpt);
+       $sth->execute($ip, $masklen, $sender, $rcpt);
 
        my $ref = $sth->fetchrow_hashref();
 
@@ -668,9 +683,11 @@ sub greylist_value {
 
        if (!defined($ref->{rctime})) {
 
-           $dbh->do($PMG::DBTools::cgreylist_merge_sql, undef,
-                    $net, $host, $sender, $rcpt, $instance,
-                    $ctime, $ctime + $greylist_lifetime, 0, 1, 0, $ctime, $self->{lcid});
+           $dbh->do(
+               PMG::DBTools::cgreylist_merge_sql(1), undef, $ip, $masklen,
+               $sender, $rcpt, $instance, $ctime, $ctime + $greylist_lifetime,
+               0, 1, 0, $ctime, $self->{lcid}
+           );
 
            $res = $defer_res;
            $self->log(3, "defer greylisted mail");
@@ -681,28 +698,37 @@ sub greylist_value {
                # defer (resent within greylist_delay window)
                $res = $defer_res;
                $self->log(3, "defer greylisted mail");
-               $dbh->do("UPDATE CGreylist " .
-                        "SET  Blocked = Blocked + 1, Host = ?, MTime = ? " .
-                        "WHERE IPNet = ? AND Sender = ? AND Receiver = ?", undef,
-                        $host, $ctime, $net, $sender, $rcpt);
+               $dbh->do(
+                   "UPDATE CGreylist " .
+                   "SET Blocked = Blocked + 1, MTime = ? " .
+                   "WHERE IPNet::cidr = network(set_masklen(?, ?)) ".
+                   " AND Sender = ? AND Receiver = ?", undef,
+                   $ctime, $ip, $masklen, $sender, $rcpt
+               );
            } else {
                if ($ctime < $ref->{extime}) {
                    # accept (not expired)
                    my $lifetime = $sender eq "" ? 0 : $greylist_awlifetime;
                    my $delay = $ref->{passed} ? "" : "Delay = $age, ";
-                   $dbh->do("UPDATE CGreylist " .
-                            "SET  Passed =  Passed + 1, $delay Host = ?, ExTime = ?, MTime = ? " .
-                            "WHERE IPNet =  ? AND Sender = ? AND Receiver = ?", undef,
-                            $host, $ctime + $lifetime, $ctime, $net, $sender, $rcpt);
+                   $dbh->do(
+                       "UPDATE CGreylist " .
+                       "SET Passed = Passed + 1, $delay ExTime = ?, MTime = ? " .
+                       "WHERE IPNet::cidr = network(set_masklen(?, ?)) ".
+                       " AND Sender = ? AND Receiver = ?", undef,
+                       $ctime + $lifetime, $ctime, $ip, $masklen, $sender, $rcpt
+                   );
                } else {
                    # defer (record is expired)
                    $res = $defer_res;
-                   $dbh->do("UPDATE CGreylist " .
-                            "SET  Host = ?, RCTime = ?, ExTime = ?, MTime = ?, Instance = ?, " .
-                            "Blocked = 1, Passed = 0 " .
-                            "WHERE IPNet =  ? AND Sender = ? AND Receiver = ?", undef,
-                            $host, $ctime, $ctime + $greylist_lifetime, $ctime, $instance,
-                            $net, $sender, $rcpt);
+                   $dbh->do(
+                       "UPDATE CGreylist " .
+                       "SET RCTime = ?, ExTime = ?, MTime = ?, Instance = ?, " .
+                       "Blocked = 1, Passed = 0 " .
+                       "WHERE IPNet::cidr = network(set_masklen(?, ?)) ".
+                       " AND Sender = ? AND Receiver = ?", undef,
+                       $ctime, $ctime + $greylist_lifetime, $ctime, $instance,
+                       $ip, $masklen, $sender, $rcpt
+                   );
                }
            }
        }
@@ -833,11 +859,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" };
@@ -870,7 +896,7 @@ sub setup_fork_signal_mask {
 # subroutine to start up a specified number of children.
 # We need to block signals until handlers are set up correctly.
 # Else its possible that HUP occurs after fork, which triggers
-# singal TERM at childs and calls server_close() instead of
+# signal TERM at children and calls server_close() instead of
 # simply exit the child.
 # Note: on server startup signals are setup to trigger
 # asynchronously for a short period of time (in PreForkSimple]::loop,