X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPMG%2FConfig.pm;h=061396e4d6da1025c7ca4c5bfde2968f29a5f79b;hb=b0f049b6674ea574fc3352859a71c54d40603a50;hp=cd69c9cdbfff5eb8160b4d0b3591dcec910082c8;hpb=aa7c37450afa660153768b4bd21b74ca60d7f38f;p=pmg-api.git diff --git a/src/PMG/Config.pm b/src/PMG/Config.pm old mode 100755 new mode 100644 index cd69c9c..061396e --- a/src/PMG/Config.pm +++ b/src/PMG/Config.pm @@ -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 => < '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,15 +402,16 @@ 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 => 1 + default => 0 }, scriptedupdates => { description => "Enables ScriptedUpdates (incremental download of signatures)", type => 'boolean', - default => 0 + default => 1 }, }; } @@ -404,7 +425,7 @@ 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}, }; } @@ -438,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; @@ -505,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, @@ -573,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, @@ -629,31 +660,35 @@ sub properties { 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 @@ -668,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 + }, }; } @@ -708,6 +748,7 @@ sub options { dnsbl_threshold => { optional => 1 }, before_queue_filtering => { optional => 1 }, ndr_on_block => { optional => 1 }, + smtputf8 => { optional => 1 }, }; } @@ -907,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); } @@ -1003,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, @@ -1095,7 +1136,7 @@ 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*$/) { @@ -1144,6 +1185,61 @@ 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 { @@ -1270,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"; @@ -1287,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"; + $transportnets->{"$host/32"} = 1; + $mynetworks->{"$host/32"} = 1; } elsif ($host =~ m/^(?:ipv6:)?($IPV6RE)$/i) { - push @$transportnets, "[$1]/128"; + $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}); @@ -1356,7 +1463,10 @@ sub get_template_vars { $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; @@ -1390,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' ]; @@ -1432,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); @@ -1485,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"; @@ -1551,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); @@ -1566,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); @@ -1661,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; @@ -1675,6 +1791,7 @@ my $pmg_service_params = { mail => { hide_received => 1, ndr_on_block => 1, + smtputf8 => 1, }, admin => { dkim_selector => 1,