]> git.proxmox.com Git - pmg-api.git/blobdiff - src/PMG/Config.pm
config: adjust max_filters calculation to reflect current memory usage
[pmg-api.git] / src / PMG / Config.pm
old mode 100755 (executable)
new mode 100644 (file)
index 0d9c65e..061396e
@@ -8,6 +8,7 @@ use Data::Dumper;
 use PVE::Tools;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::SectionConfig;
+use PVE::Network;
 
 use base qw(PVE::SectionConfig);
 
@@ -62,9 +63,16 @@ sub type {
 sub properties {
     return {
        advfilter => {
-           description => "Use advanced filters for statistic.",
+           description => "Enable advanced filters for statistic.",
+           verbose_description => <<EODESC,
+Enable advanced filters for statistic.
+
+If this is enabled, the receiver statistic are limited to active ones
+(receivers which also sent out mail in the 90 days before), and the contact
+statistic will not contain these active receivers.
+EODESC
            type => 'boolean',
-           default => 1,
+           default => 0,
        },
        dailyreport => {
            description => "Send daily reports.",
@@ -169,12 +177,12 @@ sub properties {
        use_bayes => {
            description => "Whether to use the naive-Bayesian-style classifier.",
            type => 'boolean',
-           default => 1,
+           default => 0,
        },
        use_awl => {
            description => "Use the Auto-Whitelist plugin.",
            type => 'boolean',
-           default => 1,
+           default => 0,
        },
        use_razor => {
            description => "Whether to use Razor2, if it is available.",
@@ -186,7 +194,7 @@ sub properties {
            type => 'string',
        },
        clamav_heuristic_score => {
-           description => "Score for ClamAV heuristics (Encrypted Archives/Documents, Google Safe Browsing database, PhishingScanURLs, ...).",
+           description => "Score for ClamAV heuristics (Encrypted Archives/Documents, PhishingScanURLs, ...).",
            type => 'integer',
            minimum => 0,
            maximum => 1000,
@@ -210,6 +218,11 @@ sub properties {
            minimum => 64,
            default => 256*1024,
        },
+       extract_text => {
+           description => "Extract text from attachments (doc, pdf, rtf, images) and scan for spam.",
+           type => 'boolean',
+           default => 0,
+       },
     };
 }
 
@@ -224,6 +237,7 @@ sub options {
        bounce_score => { optional => 1 },
        rbl_checks => { optional => 1 },
        maxspamsize => { optional => 1 },
+       extract_text => { optional => 1 },
     };
 }
 
@@ -289,6 +303,11 @@ sub properties {
            description => "Text for 'From' header in daily spam report mails.",
            type => 'string',
        },
+       quarantinelink => {
+           description => "Enables user self-service for Quarantine Links. Caution: this is accessible without authentication",
+           type => 'boolean',
+           default => 0,
+       },
     };
 }
 
@@ -303,6 +322,7 @@ sub options {
        allowhrefs => { optional => 1 },
        port => { optional => 1 },
        protocol => { optional => 1 },
+       quarantinelink => { optional => 1 },
     };
 }
 
@@ -382,8 +402,14 @@ sub properties {
            minimum => 0,
            default => 0,
        },
+       # FIXME: remove for PMG 8.0 - https://blog.clamav.net/2021/04/are-you-still-attempting-to-download.html
        safebrowsing => {
-           description => "Enables support for Google Safe Browsing.",
+           description => "Enables support for Google Safe Browsing. (deprecated option, will be ignored)",
+           type => 'boolean',
+           default => 0
+       },
+       scriptedupdates => {
+           description => "Enables ScriptedUpdates (incremental download of signatures)",
            type => 'boolean',
            default => 1
        },
@@ -399,7 +425,8 @@ sub options {
        maxscansize  => { optional => 1 },
        dbmirror => { optional => 1 },
        maxcccount => { optional => 1 },
-       safebrowsing => { optional => 1 },
+       safebrowsing => { optional => 1 }, # FIXME: remove for PMG 8.0
+       scriptedupdates => { optional => 1},
     };
 }
 
@@ -432,8 +459,17 @@ sub get_max_filters {
 
     my $max_servers = 5;
     my $servermem = 120;
+    my $base;
     my $memory = physical_memory();
-    my $add_servers = int(($memory - 512)/$servermem);
+    if ($memory < 3840) {
+       warn "low amount of system memory installed, recommended is 4+ GB\n"
+           ."to prevent OOM kills, it is better to set max_filters manually\n";
+       $base = $memory > 1536 ? 1024 : 512;
+    } else {
+       $base = 2816;
+       $servermem = 150;
+    }
+    my $add_servers = int(($memory - $base)/$servermem);
     $max_servers += $add_servers if $add_servers > 0;
     $max_servers = 40 if  $max_servers > 40;
 
@@ -499,11 +535,12 @@ sub properties {
            default => 0,
        },
        smarthost => {
-           description => "When set, all outgoing mails are deliverd to the specified smarthost.",
+           description => "When set, all outgoing mails are deliverd to the specified smarthost."
+               ." (postfix option `default_transport`)",
            type => 'string', format => 'address',
        },
        smarthostport => {
-           description => "SMTP port number for smarthost.",
+           description => "SMTP port number for smarthost. (postfix option `default_transport`)",
            type => 'integer',
            minimum => 1,
            maximum => 65535,
@@ -567,13 +604,13 @@ sub properties {
            default => 0,
        },
        maxsize => {
-           description => "Maximum email size. Larger mails are rejected.",
+           description => "Maximum email size. Larger mails are rejected. (postfix option `message_size_limit`)",
            type => 'integer',
            minimum => 1024,
            default => 1024*1024*10,
        },
        dwarning => {
-           description => "SMTP delay warning time (in hours).",
+           description => "SMTP delay warning time (in hours). (postfix option `delay_warning_time`)",
            type => 'integer',
            minimum => 0,
            default => 4,
@@ -599,36 +636,59 @@ sub properties {
            default => 1,
        },
        greylist => {
-           description => "Use Greylisting.",
+           description => "Use Greylisting for IPv4.",
            type => 'boolean',
            default => 1,
        },
+       greylistmask4 => {
+           description => "Netmask to apply for greylisting IPv4 hosts",
+           type => 'integer',
+           minimum => 0,
+           maximum => 32,
+           default => 24,
+       },
+       greylist6 => {
+           description => "Use Greylisting for IPv6.",
+           type => 'boolean',
+           default => 0,
+       },
+       greylistmask6 => {
+           description => "Netmask to apply for greylisting IPv6 hosts",
+           type => 'integer',
+           minimum => 0,
+           maximum => 128,
+           default => 64,
+       },
        helotests => {
-           description => "Use SMTP HELO tests.",
+           description => "Use SMTP HELO tests. (postfix option `smtpd_helo_restrictions`)",
            type => 'boolean',
            default => 0,
        },
        rejectunknown => {
-           description => "Reject unknown clients.",
+           description => "Reject unknown clients. (postfix option `reject_unknown_client_hostname`)",
            type => 'boolean',
            default => 0,
        },
        rejectunknownsender => {
-           description => "Reject unknown senders.",
+           description => "Reject unknown senders. (postfix option `reject_unknown_sender_domain`)",
            type => 'boolean',
            default => 0,
        },
        verifyreceivers => {
-           description => "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
+           description => "Enable receiver verification. The value spefifies the numerical reply"
+               ." code when the Postfix SMTP server rejects a recipient address."
+               ." (postfix options `reject_unknown_recipient_domain`, `reject_unverified_recipient`,"
+               ." and `unverified_recipient_reject_code`)",
            type => 'string',
            enum => ['450', '550'],
        },
        dnsbl_sites => {
-           description => "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
+           description => "Optional list of DNS white/blacklist domains (postfix option `postscreen_dnsbl_sites`).",
            type => 'string', format => 'dnsbl-entry-list',
        },
        dnsbl_threshold => {
-           description => "The inclusive lower bound for blocking a remote SMTP client, based on its combined DNSBL score (see postscreen_dnsbl_threshold parameter).",
+           description => "The inclusive lower bound for blocking a remote SMTP client, based on"
+               ." its combined DNSBL score (postfix option `postscreen_dnsbl_threshold`).",
            type => 'integer',
            minimum => 0,
            default => 1
@@ -643,6 +703,11 @@ sub properties {
            type => 'boolean',
            default => 0
        },
+       smtputf8 => {
+           description => "Enable SMTPUTF8 support in Postfix and detection for locally generated mail (postfix option `smtputf8_enable`)",
+           type => 'boolean',
+           default => 1
+       },
     };
 }
 
@@ -660,6 +725,9 @@ sub options {
        max_smtpd_in => { optional => 1 },
        max_smtpd_out => { optional => 1 },
        greylist => { optional => 1 },
+       greylistmask4 => { optional => 1 },
+       greylist6 => { optional => 1 },
+       greylistmask6 => { optional => 1 },
        helotests => { optional => 1 },
        tls => { optional => 1 },
        tlslog => { optional => 1 },
@@ -680,6 +748,7 @@ sub options {
        dnsbl_threshold => { optional => 1 },
        before_queue_filtering => { optional => 1 },
        ndr_on_block => { optional => 1 },
+       smtputf8 => { optional => 1 },
     };
 }
 
@@ -879,9 +948,8 @@ sub get_config {
 sub read_pmg_conf {
     my ($filename, $fh) = @_;
 
-    local $/ = undef; # slurp mode
-
-    my $raw = <$fh> if defined($fh);
+    my $raw;
+    $raw = do { local $/ = undef; <$fh> } if defined($fh);
 
     return  PMG::Config::Base->parse_config($filename, $raw);
 }
@@ -975,6 +1043,7 @@ sub read_pmg_mynetworks {
            if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
                my ($network, $prefix_size, $comment) = ($1, $2, $3);
                my $cidr = "$network/${prefix_size}";
+               # FIXME: Drop unused `network_address` and `prefix_size` with PMG 8.0
                $mynetworks->{$cidr} = {
                    cidr => $cidr,
                    network_address => $network,
@@ -1033,6 +1102,26 @@ sub pmg_verify_tls_policy_strict {
     return $policy;
 }
 
+PVE::JSONSchema::register_format(
+    'transport-domain-or-nexthop', \&pmg_verify_transport_domain_or_nexthop);
+
+sub pmg_verify_transport_domain_or_nexthop {
+    my ($name, $noerr) = @_;
+
+    if (pmg_verify_transport_domain($name, 1)) {
+       return $name;
+    } elsif ($name =~ m/^(\S+)(?::\d+)?$/) {
+       my $nexthop = $1;
+       if ($nexthop =~ m/^\[(.*)\]$/) {
+           $nexthop = $1;
+       }
+       return $name if pmg_verify_transport_address($nexthop, 1);
+    } else {
+          return undef if $noerr;
+          die "value does not look like a valid domain or next-hop\n";
+    }
+}
+
 sub read_tls_policy {
     my ($filename, $fh) = @_;
 
@@ -1047,14 +1136,14 @@ sub read_tls_policy {
 
        my $parse_error = sub {
            my ($err) = @_;
-           die "parse error in '$filename': $line - $err";
+           warn "parse error in '$filename': $line - $err\n";
        };
 
        if ($line =~ m/^(\S+)\s+(.+)\s*$/) {
-           my ($domain, $policy) = ($1, $2);
+           my ($destination, $policy) = ($1, $2);
 
            eval {
-               pmg_verify_transport_domain($domain);
+               pmg_verify_transport_domain_or_nexthop($destination);
                pmg_verify_tls_policy($policy);
            };
            if (my $err = $@) {
@@ -1062,8 +1151,8 @@ sub read_tls_policy {
                next;
            }
 
-           $tls_policy->{$domain} = {
-                   domain => $domain,
+           $tls_policy->{$destination} = {
+                   destination => $destination,
                    policy => $policy,
            };
        } else {
@@ -1079,10 +1168,10 @@ sub write_tls_policy {
 
     return if !$tls_policy;
 
-    foreach my $domain (sort keys %$tls_policy) {
-       my $entry = $tls_policy->{$domain};
+    foreach my $destination (sort keys %$tls_policy) {
+       my $entry = $tls_policy->{$destination};
        PVE::Tools::safe_print(
-           $filename, $fh, "$entry->{domain} $entry->{policy}\n");
+           $filename, $fh, "$entry->{destination} $entry->{policy}\n");
     }
 }
 
@@ -1096,12 +1185,83 @@ sub postmap_tls_policy {
     PMG::Utils::run_postmap($tls_policy_map_filename);
 }
 
+sub read_tls_inbound_domains {
+    my ($filename, $fh) = @_;
+
+    return {} if !defined($fh);
+
+    my $domains = {};
+
+    while (defined(my $line = <$fh>)) {
+       chomp $line;
+       next if $line =~ m/^\s*$/;
+       next if $line =~ m/^#(.*)\s*$/;
+
+       my $parse_error = sub {
+           my ($err) = @_;
+           warn "parse error in '$filename': $line - $err\n";
+       };
+
+       if ($line =~ m/^(\S+) reject_plaintext_session$/) {
+           my $domain = $1;
+
+           eval { pmg_verify_transport_domain($domain) };
+           if (my $err = $@) {
+               $parse_error->($err);
+               next;
+           }
+
+           $domains->{$domain} = 1;
+       } else {
+           $parse_error->('wrong format');
+       }
+    }
+
+    return $domains;
+}
+
+sub write_tls_inbound_domains {
+    my ($filename, $fh, $domains) = @_;
+
+    return if !$domains;
+
+    foreach my $domain (sort keys %$domains) {
+       PVE::Tools::safe_print($filename, $fh, "$domain reject_plaintext_session\n");
+    }
+}
+
+my $tls_inbound_domains_map_filename = "/etc/pmg/tls_inbound_domains";
+PVE::INotify::register_file('tls_inbound_domains', $tls_inbound_domains_map_filename,
+                           \&read_tls_inbound_domains,
+                           \&write_tls_inbound_domains,
+                           undef, always_call_parser => 1);
+
+sub postmap_tls_inbound_domains {
+    PMG::Utils::run_postmap($tls_inbound_domains_map_filename);
+}
+
 my $transport_map_filename = "/etc/pmg/transport";
 
 sub postmap_pmg_transport {
     PMG::Utils::run_postmap($transport_map_filename);
 }
 
+PVE::JSONSchema::register_format(
+    'transport-address', \&pmg_verify_transport_address);
+
+sub pmg_verify_transport_address {
+    my ($name, $noerr) = @_;
+
+    if ($name =~ m/^ipv6:($IPV6RE)$/i) {
+       return $name;
+    } elsif (PVE::JSONSchema::pve_verify_address($name, 1)) {
+       return $name;
+    } else {
+       return undef if $noerr;
+       die "value does not look like a valid address\n";
+    }
+}
+
 sub read_transport_map {
     my ($filename, $fh) = @_;
 
@@ -1138,9 +1298,9 @@ sub read_transport_map {
                $host = $1;
                $use_mx = 0;
            }
-               $use_mx = 0 if ($protocol eq "lmtp");
+           $use_mx = 0 if ($protocol eq "lmtp");
 
-           eval { PVE::JSONSchema::pve_verify_address($host); };
+           eval { pmg_verify_transport_address($host); };
            if (my $err = $@) {
                $parse_error->($err);
                next;
@@ -1176,23 +1336,16 @@ sub write_transport_map {
        PVE::Tools::safe_print($filename, $fh, "#$comment\n")
            if defined($comment) && $comment !~ m/^\s*$/;
 
-       my $use_mx = $data->{use_mx};
-       $use_mx = 0 if $data->{host} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
-
-       my $is_lmtp = 0;
-       $is_lmtp = 1 if $data->{protocol} eq "lmtp";
+       my $bracket_host = !$data->{use_mx};
 
-       if ($is_lmtp) {
-               $data->{protocol} = "lmtp:inet";
+       if ($data->{protocol} eq 'lmtp') {
+           $bracket_host = 0;
+           $data->{protocol} .= ":inet";
        }
+       $bracket_host = 1 if $data->{host} =~ m/^(?:$IPV4RE|(?:ipv6:)?$IPV6RE)$/i;
+       my $host = $bracket_host ? "[$data->{host}]" : $data->{host};
 
-       if ($use_mx or $is_lmtp) {
-           PVE::Tools::safe_print(
-               $filename, $fh, "$data->{domain} $data->{protocol}:$data->{host}:$data->{port}\n");
-       } else {
-           PVE::Tools::safe_print(
-               $filename, $fh, "$data->{domain} $data->{protocol}:[$data->{host}]:$data->{port}\n");
-       }
+       PVE::Tools::safe_print($filename, $fh, "$data->{domain} $data->{protocol}:$host:$data->{port}\n");
     }
 }
 
@@ -1213,6 +1366,8 @@ sub get_host_dns_info {
     my $resolv = PVE::INotify::read_file('resolvconf');
 
     my $domain = $resolv->{search} // 'localdomain';
+    # postfix will not parse a hostname with trailing '.'
+    $domain =~ s/^(.*)\.$/$1/;
     $dnsinfo->{domain} = $domain;
 
     $dnsinfo->{fqdn} = "$nodename.$domain";
@@ -1230,61 +1385,70 @@ sub get_template_vars {
     my $int_ip = PMG::Cluster::remote_node_ip($dnsinfo->{hostname});
     $vars->{ipconfig}->{int_ip} = $int_ip;
 
-    my $transportnets = [];
+    my $transportnets = {};
+    my $mynetworks = {
+       '127.0.0.0/8' => 1,
+       '[::1]/128', => 1,
+    };
 
     if (my $tmap = PVE::INotify::read_file('transport')) {
-       foreach my $domain (sort keys %$tmap) {
+       foreach my $domain (keys %$tmap) {
            my $data = $tmap->{$domain};
            my $host = $data->{host};
            if ($host =~ m/^$IPV4RE$/) {
-               push @$transportnets, "$host/32";
-           } elsif ($host =~ m/^$IPV6RE$/) {
-               push @$transportnets, "[$host]/128";
+               $transportnets->{"$host/32"} = 1;
+               $mynetworks->{"$host/32"} = 1;
+           } elsif ($host =~ m/^(?:ipv6:)?($IPV6RE)$/i) {
+               $transportnets->{"[$1]/128"} = 1;
+               $mynetworks->{"[$1]/128"} = 1;
            }
        }
     }
 
-    $vars->{postfix}->{transportnets} = join(' ', @$transportnets);
-
-    my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
+    $vars->{postfix}->{transportnets} = join(' ', sort keys %$transportnets);
 
-    if (my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip, 1)) {
-       if ($int_net_cidr =~ m/^($IPV6RE)\/(\d+)$/) {
-           push @$mynetworks, "[$1]/$2";
-       } else {
-           push @$mynetworks, $int_net_cidr;
-       }
-    } else {
-       if ($int_ip =~ m/^$IPV6RE$/) {
-           push @$mynetworks, "[$int_ip]/128";
+    if (defined($int_ip)) { # we cannot really do anything and the loopback nets are already added
+       if (my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip, 1)) {
+           if ($int_net_cidr =~ m/^($IPV6RE)\/(\d+)$/) {
+               $mynetworks->{"[$1]/$2"} = 1;
+           } else {
+               $mynetworks->{$int_net_cidr} = 1;
+           }
        } else {
-           push @$mynetworks, "$int_ip/32";
+           if ($int_ip =~ m/^$IPV6RE$/) {
+               $mynetworks->{"[$int_ip]/128"} = 1;
+           } else {
+               $mynetworks->{"$int_ip/32"} = 1;
+           }
        }
     }
 
     my $netlist = PVE::INotify::read_file('mynetworks');
-    foreach my $cidr (sort keys %$netlist) {
-       if ($cidr =~ m/^($IPV6RE)\/(\d+)$/) {
-           push @$mynetworks, "[$1]/$2";
+    foreach my $cidr (keys %$netlist) {
+       my $ip = PVE::Network::IP_from_cidr($cidr);
+
+       if (!$ip) {
+           warn "failed to parse mynetworks entry '$cidr', ignoring\n";
+       } elsif ($ip->version() == 4) {
+           $mynetworks->{$ip->prefix()} = 1;
        } else {
-           push @$mynetworks, $cidr;
+           my $address = '[' . $ip->short() . ']/' . $ip->prefixlen();
+           $mynetworks->{$address} = 1;
        }
     }
 
-    push @$mynetworks, @$transportnets;
-
     # add default relay to mynetworks
     if (my $relay = $self->get('mail', 'relay')) {
        if ($relay =~ m/^$IPV4RE$/) {
-           push @$mynetworks, "$relay/32";
+           $mynetworks->{"$relay/32"} = 1;
        } elsif ($relay =~ m/^$IPV6RE$/) {
-           push @$mynetworks, "[$relay]/128";
+           $mynetworks->{"[$relay]/128"} = 1;
        } else {
            # DNS name - do nothing ?
        }
     }
 
-    $vars->{postfix}->{mynetworks} = join(' ', @$mynetworks);
+    $vars->{postfix}->{mynetworks} = join(' ', sort keys %$mynetworks);
 
     # normalize dnsbl_sites
     my @dnsbl_sites = PVE::Tools::split_list($vars->{pmg}->{mail}->{dnsbl_sites});
@@ -1296,10 +1460,13 @@ sub get_template_vars {
 
     my $usepolicy = 0;
     $usepolicy = 1 if $self->get('mail', 'greylist') ||
-       $self->get('mail', 'spf');
+       $self->get('mail', 'greylist6') || $self->get('mail', 'spf');
     $vars->{postfix}->{usepolicy} = $usepolicy;
 
-    if ($int_ip =~ m/^$IPV6RE$/) {
+    if (!defined($int_ip)) {
+       warn "could not get node IP, falling back to loopback '127.0.0.1'\n";
+       $vars->{postfix}->{int_ip} = '127.0.0.1';
+    } elsif ($int_ip =~ m/^$IPV6RE$/) {
         $vars->{postfix}->{int_ip} = "[$int_ip]";
     } else {
         $vars->{postfix}->{int_ip} = $int_ip;
@@ -1333,6 +1500,15 @@ sub get_template_vars {
     return $vars;
 }
 
+# reads the $filename and checks if it's equal as the $cmp string passed
+my sub file_content_equals_str {
+    my ($filename, $cmp) = @_;
+
+    return if !-f $filename;
+    my $current = PVE::Tools::file_get_contents($filename, 128*1024);
+    return defined($current) && $current eq $cmp; # no change
+}
+
 # use one global TT cache
 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
 
@@ -1375,12 +1551,9 @@ sub rewrite_config_file {
 
     my $output = '';
 
-    $tt->process($tmplname, $vars, \$output) ||
-       die $tt->error() . "\n";
-
-    my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
+    $tt->process($tmplname, $vars, \$output) || die $tt->error() . "\n";
 
-    return 0 if defined($old) && ($old eq $output); # no change
+    return 0 if file_content_equals_str($dstfn, $output); # no change -> nothing to do
 
     PVE::Tools::file_set_contents($dstfn, $output, $perm);
 
@@ -1428,6 +1601,12 @@ sub rewrite_config_spam {
     $changes = 1 if $self->rewrite_config_file(
        'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
 
+    $changes = 1 if $self->rewrite_config_file(
+       'v342.pre.in', '/etc/mail/spamassassin/v342.pre');
+
+    $changes = 1 if $self->rewrite_config_file(
+       'v400.pre.in', '/etc/mail/spamassassin/v400.pre');
+
     if ($use_razor) {
        mkdir "/root/.razor";
 
@@ -1494,10 +1673,7 @@ sub rewrite_dot_forward {
     } else {
        # empty .forward does not forward mails (see man local)
     }
-
-    my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
-
-    return 0 if defined($old) && ($old eq $output); # no change
+    return 0 if file_content_equals_str($dstfn, $output); # no change -> nothing to do
 
     PVE::Tools::file_set_contents($dstfn, $output);
 
@@ -1509,15 +1685,11 @@ my $write_smtp_whitelist = sub {
 
     $action = 'OK' if !$action;
 
-    my $old = PVE::Tools::file_get_contents($filename, 1024*1024)
-       if -f $filename;
-
     my $new = '';
     foreach my $k (sort keys %$data) {
        $new .= "$k $action\n";
     }
-
-    return 0 if defined($old) && ($old eq $new); # no change
+    return 0 if file_content_equals_str($filename, $new); # no change -> nothing to do
 
     PVE::Tools::file_set_contents($filename, $new);
 
@@ -1604,6 +1776,7 @@ sub rewrite_config_postfix {
     postmap_pmg_domains();
     postmap_pmg_transport();
     postmap_tls_policy();
+    postmap_tls_inbound_domains();
 
     rewrite_postfix_whitelist($rulecache) if $rulecache;
 
@@ -1618,6 +1791,7 @@ my $pmg_service_params = {
     mail => {
        hide_received => 1,
        ndr_on_block => 1,
+       smtputf8 => 1,
     },
     admin => {
        dkim_selector => 1,