]> git.proxmox.com Git - pmg-api.git/blobdiff - PMG/Config.pm
pmg config: fix avast scan executable path documentation
[pmg-api.git] / PMG / Config.pm
index 18b076aefd0e9c258db86315863a66f56bae4462..2392db8eaefeff84eba5351280cfeea4c4d472ff 100755 (executable)
@@ -15,7 +15,7 @@ my $defaultData = {
     propertyList => {
        type => { description => "Section type." },
        section => {
-           description => "Secion ID.",
+           description => "Section ID.",
            type => 'string', format => 'pve-configid',
        },
     },
@@ -92,17 +92,41 @@ sub properties {
            type => 'string',
            pattern => "http://.*",
        },
+       avast => {
+           description => "Use Avast Virus Scanner (/usr/bin/scan). You need to buy and install 'Avast Core Security' before you can enable this feature.",
+           type => 'boolean',
+           default => 0,
+       },
+       clamav => {
+           description => "Use ClamAV Virus Scanner. This is the default virus scanner and is enabled by default.",
+           type => 'boolean',
+           default => 1,
+       },
+       custom_check => {
+           description => "Use Custom Check Script. The script has to take the defined arguments and can return Virus findings or a Spamscore.",
+           type => 'boolean',
+           default => 0,
+       },
+       custom_check_path => {
+           description => "Absolute Path to the Custom Check Script",
+           type => 'string', pattern => '^/([^/\0]+\/)+[^/\0]+$',
+           default => '/usr/local/bin/pmg-custom-check',
+       },
     };
 }
 
 sub options {
     return {
        advfilter => { optional => 1 },
+       avast => { optional => 1 },
+       clamav => { optional => 1 },
        statlifetime => { optional => 1 },
        dailyreport => { optional => 1 },
        demo => { optional => 1 },
        email => { optional => 1 },
        http_proxy => { optional => 1 },
+       custom_check => { optional => 1 },
+       custom_check_path => { optional => 1 },
     };
 }
 
@@ -145,7 +169,7 @@ sub properties {
            type => 'string',
        },
        clamav_heuristic_score => {
-           description => "Score for ClamaAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
+           description => "Score for ClamAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
            type => 'integer',
            minimum => 0,
            maximum => 1000,
@@ -307,7 +331,7 @@ sub properties {
            default => 'database.clamav.net',
        },
        archiveblockencrypted => {
-           description => "Wether to block encrypted archives. Mark encrypted archives as viruses.",
+           description => "Whether to block encrypted archives. Mark encrypted archives as viruses.",
            type => 'boolean',
            default => 0,
        },
@@ -455,6 +479,13 @@ sub properties {
            description => "When set, all outgoing mails are deliverd to the specified smarthost.",
            type => 'string', format => 'address',
        },
+       smarthostport => {
+           description => "SMTP port number for smarthost.",
+           type => 'integer',
+           minimum => 1,
+           maximum => 65535,
+           default => 25,
+       },
        banner => {
            description => "ESMTP banner.",
            type => 'string',
@@ -573,6 +604,12 @@ sub properties {
            description => "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
            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).",
+           type => 'integer',
+           minimum => 0,
+           default => 1
+       },
     };
 }
 
@@ -581,6 +618,7 @@ sub options {
        int_port => { optional => 1 },
        ext_port => { optional => 1 },
        smarthost => { optional => 1 },
+       smarthostport => { optional => 1 },
        relay => { optional => 1 },
        relayport => { optional => 1 },
        relaynomx => { optional => 1 },
@@ -605,6 +643,7 @@ sub options {
        message_rate_limit => { optional => 1 },
        verifyreceivers => { optional => 1 },
        dnsbl_sites => { optional => 1 },
+       dnsbl_threshold => { optional => 1 },
     };
 }
 
@@ -649,16 +688,42 @@ sub pmg_verify_transport_domain {
     return $name;
 }
 
+PVE::JSONSchema::register_format(
+    'transport-domain-or-email', \&pmg_verify_transport_domain_or_email);
+
+sub pmg_verify_transport_domain_or_email {
+    my ($name, $noerr) = @_;
+
+    my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
+
+    # email address
+    if ($name =~ m/^(?:[^\s\/\@]+\@)(${namere}\.)*${namere}$/) {
+       return $name;
+    }
+
+    # like dns-name, but can contain leading dot
+    if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
+          return undef if $noerr;
+          die "value does not look like a valid transport domain or email address\n";
+    }
+    return $name;
+}
+
 PVE::JSONSchema::register_format(
     'dnsbl-entry', \&pmg_verify_dnsbl_entry);
 
 sub pmg_verify_dnsbl_entry {
     my ($name, $noerr) = @_;
 
-    # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
+    # like dns-name, but can contain trailing filter and weight: 'domain=<FILTER>*<WEIGHT>'
+    # see http://www.postfix.org/postconf.5.html#postscreen_dnsbl_sites
+    # we don't implement the ';' separated numbers in pattern, because this
+    # breaks at PVE::JSONSchema::split_list
     my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
 
-    if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
+    my $dnsbloctet = qr/[0-9]+|\[(?:[0-9]+\.\.[0-9]+)\]/;
+    my $filterre = qr/=$dnsbloctet(:?\.$dnsbloctet){3}/;
+    if ($name !~ /^(${namere}\.)*${namere}(:?${filterre})?(?:\*\-?\d+)?$/) {
           return undef if $noerr;
           die "value '$name' does not look like a valid dnsbl entry\n";
     }
@@ -896,7 +961,92 @@ PVE::INotify::register_file('mynetworks', $mynetworks_filename,
                            \&write_pmg_mynetworks,
                            undef, always_call_parser => 1);
 
+PVE::JSONSchema::register_format(
+    'tls-policy', \&pmg_verify_tls_policy);
+
+# TODO: extend to parse attributes of the policy
+my $VALID_TLS_POLICY_RE = qr/none|may|encrypt|dane|dane-only|fingerprint|verify|secure/;
+sub pmg_verify_tls_policy {
+    my ($policy, $noerr) = @_;
+
+    if ($policy !~ /^$VALID_TLS_POLICY_RE\b/) {
+          return undef if $noerr;
+          die "value '$policy' does not look like a valid tls policy\n";
+    }
+    return $policy;
+}
+
+PVE::JSONSchema::register_format(
+    'tls-policy-strict', \&pmg_verify_tls_policy_strict);
+
+sub pmg_verify_tls_policy_strict {
+    my ($policy, $noerr) = @_;
+
+    if ($policy !~ /^$VALID_TLS_POLICY_RE$/) {
+       return undef if $noerr;
+       die "value '$policy' does not look like a valid tls policy\n";
+    }
+    return $policy;
+}
+
+sub read_tls_policy {
+    my ($filename, $fh) = @_;
+
+    return {} if !defined($fh);
+
+    my $tls_policy = {};
+
+    while (defined(my $line = <$fh>)) {
+       chomp $line;
+       next if $line =~ m/^\s*$/;
+       next if $line =~ m/^#(.*)\s*$/;
+
+       my $parse_error = sub {
+           my ($err) = @_;
+           die "parse error in '$filename': $line - $err";
+       };
+
+       if ($line =~ m/^(\S+)\s+(.+)\s*$/) {
+           my ($domain, $policy) = ($1, $2);
+
+           eval {
+               pmg_verify_transport_domain($domain);
+               pmg_verify_tls_policy($policy);
+           };
+           if (my $err = $@) {
+               $parse_error->($err);
+               next;
+           }
+
+           $tls_policy->{$domain} = {
+                   domain => $domain,
+                   policy => $policy,
+           };
+       } else {
+           $parse_error->('wrong format');
+       }
+    }
+
+    return $tls_policy;
+}
+
+sub write_tls_policy {
+    my ($filename, $fh, $tls_policy) = @_;
+
+    return if !$tls_policy;
+
+    foreach my $domain (sort keys %$tls_policy) {
+       my $entry = $tls_policy->{$domain};
+       PVE::Tools::safe_print(
+           $filename, $fh, "$entry->{domain} $entry->{policy}\n");
+    }
+}
+
 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
+PVE::INotify::register_file('tls_policy', $tls_policy_map_filename,
+                           \&read_tls_policy,
+                           \&write_tls_policy,
+                           undef, always_call_parser => 1);
 
 sub postmap_tls_policy {
     PMG::Utils::run_postmap($tls_policy_map_filename);
@@ -934,7 +1084,7 @@ sub read_transport_map {
        if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
            my ($domain, $host, $port) = ($1, $2, $3);
 
-           eval { pmg_verify_transport_domain($domain); };
+           eval { pmg_verify_transport_domain_or_email($domain); };
            if (my $err = $@) {
                $parse_error->($err);
                next;
@@ -1042,7 +1192,7 @@ sub get_template_vars {
     }
 
     my $netlist = PVE::INotify::read_file('mynetworks');
-    foreach my $cidr (keys %$netlist) {
+    foreach my $cidr (sort keys %$netlist) {
        if ($cidr =~ m/^($IPV6RE)\/(\d+)$/) {
            push @$mynetworks, "[$1]/$2";
        } else {
@@ -1071,6 +1221,8 @@ sub get_template_vars {
        $vars->{postfix}->{dnsbl_sites} = join(',', @dnsbl_sites);
     }
 
+    $vars->{postfix}->{dnsbl_threshold} = $self->get('mail', 'dnsbl_threshold');
+
     my $usepolicy = 0;
     $usepolicy = 1 if $self->get('mail', 'greylist') ||
        $self->get('mail', 'spf');
@@ -1398,9 +1550,14 @@ sub rewrite_config {
 
     $force_restart = {} if ! $force_restart;
 
+    my $log_restart = sub {
+       syslog ('info', "configuration change detected for '$_[0]', restarting");
+    };
+
     if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
        $force_restart->{postfix}) {
-       PMG::Utils::service_cmd('postfix', 'restart');
+       $log_restart->('postfix');
+       PMG::Utils::service_cmd('postfix', 'reload');
     }
 
     if ($self->rewrite_dot_forward() && $restart_services) {
@@ -1414,16 +1571,19 @@ sub rewrite_config {
 
     if (($self->rewrite_config_spam() && $restart_services) ||
        $force_restart->{spam}) {
+       $log_restart->('pmg-smtp-filter');
        PMG::Utils::service_cmd('pmg-smtp-filter', 'restart');
     }
 
     if (($self->rewrite_config_clam() && $restart_services) ||
        $force_restart->{clam}) {
+       $log_restart->('clamav-daemon');
        PMG::Utils::service_cmd('clamav-daemon', 'restart');
     }
 
     if (($self->rewrite_config_freshclam() && $restart_services) ||
        $force_restart->{freshclam}) {
+       $log_restart->('clamav-freshclam');
        PMG::Utils::service_cmd('clamav-freshclam', 'restart');
     }
 }