propertyList => {
type => { description => "Section type." },
section => {
- description => "Secion ID.",
+ description => "Section ID.",
type => 'string', format => 'pve-configid',
},
},
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 },
};
}
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,
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,
},
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',
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
+ },
};
}
int_port => { optional => 1 },
ext_port => { optional => 1 },
smarthost => { optional => 1 },
+ smarthostport => { optional => 1 },
relay => { optional => 1 },
relayport => { optional => 1 },
relaynomx => { optional => 1 },
message_rate_limit => { optional => 1 },
verifyreceivers => { optional => 1 },
dnsbl_sites => { optional => 1 },
+ dnsbl_threshold => { optional => 1 },
};
}
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";
}
\&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);
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;
}
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 {
$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');
$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) {
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');
}
}