1 package PMG
::Config
::Base
;
9 use PVE
::JSONSchema
qw(get_standard_option);
10 use PVE
::SectionConfig
;
12 use base
qw(PVE::SectionConfig);
16 type
=> { description
=> "Section type." },
18 description
=> "Secion ID.",
19 type
=> 'string', format
=> 'pve-configid',
28 sub format_section_header
{
29 my ($class, $type, $sectionId) = @_;
31 die "internal error ($type ne $sectionId)" if $type ne $sectionId;
33 return "section: $type\n";
37 sub parse_section_header
{
38 my ($class, $line) = @_;
40 if ($line =~ m/^section:\s*(\S+)\s*$/) {
42 my $errmsg = undef; # set if you want to skip whole section
43 eval { PVE
::JSONSchema
::pve_verify_configid
($section); };
45 my $config = {}; # to return additional attributes
46 return ($section, $section, $errmsg, $config);
51 package PMG
::Config
::Admin
;
56 use base
qw(PMG::Config::Base);
65 description
=> "Use advanced filters for statistic.",
70 description
=> "Send daily reports.",
75 description
=> "User Statistics Lifetime (days)",
81 description
=> "Demo mode - do not start SMTP filter.",
86 description
=> "Administrator E-Mail address.",
87 type
=> 'string', format
=> 'email',
88 default => 'admin@domain.tld',
91 description
=> "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
93 pattern
=> "http://.*",
100 advfilter
=> { optional
=> 1 },
101 statlifetime
=> { optional
=> 1 },
102 dailyreport
=> { optional
=> 1 },
103 demo
=> { optional
=> 1 },
104 email
=> { optional
=> 1 },
105 http_proxy
=> { optional
=> 1 },
109 package PMG
::Config
::Spam
;
114 use base
qw(PMG::Config::Base);
123 description
=> "This option is used to specify which languages are considered OK for incoming mail.",
125 pattern
=> '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
129 description
=> "Whether to use the naive-Bayesian-style classifier.",
134 description
=> "Use the Auto-Whitelist plugin.",
139 description
=> "Whether to use Razor2, if it is available.",
143 wl_bounce_relays
=> {
144 description
=> "Whitelist legitimate bounce relays.",
147 clamav_heuristic_score
=> {
148 description
=> "Score for ClamaAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
155 description
=> "Additional score for bounce mails.",
162 description
=> "Enable real time blacklists (RBL) checks.",
167 description
=> "Maximum size of spam messages in bytes.",
177 use_awl
=> { optional
=> 1 },
178 use_razor
=> { optional
=> 1 },
179 wl_bounce_relays
=> { optional
=> 1 },
180 languages
=> { optional
=> 1 },
181 use_bayes
=> { optional
=> 1 },
182 clamav_heuristic_score
=> { optional
=> 1 },
183 bounce_score
=> { optional
=> 1 },
184 rbl_checks
=> { optional
=> 1 },
185 maxspamsize
=> { optional
=> 1 },
189 package PMG
::Config
::SpamQuarantine
;
194 use base
qw(PMG::Config::Base);
203 description
=> "Quarantine life time (days)",
209 description
=> "Authentication mode to access the quarantine interface. Mode 'ticket' allows login using tickets sent with the daily spam report. Mode 'ldap' requires to login using an LDAP account. Finally, mode 'ldapticket' allows both ways.",
211 enum
=> [qw(ticket ldap ldapticket)],
215 description
=> "Spam report style.",
217 enum
=> [qw(none short verbose custom)],
218 default => 'verbose',
221 description
=> "Allow to view images.",
226 description
=> "Allow to view hyperlinks.",
231 description
=> "Quarantine Host. Usefule if you run a Cluster and want users to connect to a specific host.",
232 type
=> 'string', format
=> 'address',
235 description
=> "Text for 'From' header in daily spam report mails.",
243 mailfrom
=> { optional
=> 1 },
244 hostname
=> { optional
=> 1 },
245 lifetime
=> { optional
=> 1 },
246 authmode
=> { optional
=> 1 },
247 reportstyle
=> { optional
=> 1 },
248 viewimages
=> { optional
=> 1 },
249 allowhrefs
=> { optional
=> 1 },
253 package PMG
::Config
::VirusQuarantine
;
258 use base
qw(PMG::Config::Base);
270 lifetime
=> { optional
=> 1 },
271 viewimages
=> { optional
=> 1 },
272 allowhrefs
=> { optional
=> 1 },
276 package PMG
::Config
::ClamAV
;
281 use base
qw(PMG::Config::Base);
290 description
=> "ClamAV database mirror server.",
292 default => 'database.clamav.net',
294 archiveblockencrypted
=> {
295 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
300 description
=> "Nested archives are scanned recursively, e.g. if a ZIP archive contains a TAR file, all files within it will also be scanned. This options specifies how deeply the process should be continued. Warning: setting this limit too high may result in severe damage to the system.",
306 description
=> "Number of files to be scanned within an archive, a document, or any other kind of container. Warning: disabling this limit or setting it too high may result in severe damage to the system.",
312 description
=> "Files larger than this limit won't be scanned.",
318 description
=> "Sets the maximum amount of data to be scanned for each input file.",
321 default => 100000000,
324 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
330 description
=> "Enables support for Google Safe Browsing.",
339 archiveblockencrypted
=> { optional
=> 1 },
340 archivemaxrec
=> { optional
=> 1 },
341 archivemaxfiles
=> { optional
=> 1 },
342 archivemaxsize
=> { optional
=> 1 },
343 maxscansize
=> { optional
=> 1 },
344 dbmirror
=> { optional
=> 1 },
345 maxcccount
=> { optional
=> 1 },
346 safebrowsing
=> { optional
=> 1 },
350 package PMG
::Config
::Mail
;
355 use PVE
::ProcFSTools
;
357 use base
qw(PMG::Config::Base);
364 sub physical_memory
{
366 return $physicalmem if $physicalmem;
368 my $info = PVE
::ProcFSTools
::read_meminfo
();
369 my $total = int($info->{memtotal
} / (1024*1024));
374 sub get_max_filters
{
375 # estimate optimal number of filter servers
379 my $memory = physical_memory
();
380 my $add_servers = int(($memory - 512)/$servermem);
381 $max_servers += $add_servers if $add_servers > 0;
382 $max_servers = 40 if $max_servers > 40;
384 return $max_servers - 2;
388 # estimate optimal number of smtpd daemons
390 my $max_servers = 25;
392 my $memory = physical_memory
();
393 my $add_servers = int(($memory - 512)/$servermem);
394 $max_servers += $add_servers if $add_servers > 0;
395 $max_servers = 100 if $max_servers > 100;
400 # estimate optimal number of proxpolicy servers
402 my $memory = physical_memory
();
403 $max_servers = 5 if $memory >= 500;
410 description
=> "SMTP port number for outgoing mail (trusted).",
417 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
424 description
=> "The default mail delivery transport (incoming mails).",
425 type
=> 'string', format
=> 'address',
428 description
=> "SMTP port number for relay host.",
435 description
=> "Disable MX lookups for default relay.",
440 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
441 type
=> 'string', format
=> 'address',
444 description
=> "ESMTP banner.",
447 default => 'ESMTP Proxmox',
450 description
=> "Maximum number of pmg-smtp-filter processes.",
454 default => get_max_filters
(),
457 description
=> "Maximum number of pmgpolicy processes.",
461 default => get_max_policy
(),
464 description
=> "Maximum number of SMTP daemon processes (in).",
468 default => get_max_smtpd
(),
471 description
=> "Maximum number of SMTP daemon processes (out).",
475 default => get_max_smtpd
(),
477 conn_count_limit
=> {
478 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
484 description
=> "The maximal number of connection attempts any client is allowed to make to this service per minute. To disable this feature, specify a limit of 0.",
489 message_rate_limit
=> {
490 description
=> "The maximal number of message delivery requests that any client is allowed to make to this service per minute.To disable this feature, specify a limit of 0.",
496 description
=> "Hide received header in outgoing mails.",
501 description
=> "Maximum email size. Larger mails are rejected.",
504 default => 1024*1024*10,
507 description
=> "SMTP delay warning time (in hours).",
513 description
=> "Enable TLS.",
518 description
=> "Enable TLS Logging.",
523 description
=> "Add TLS received header.",
528 description
=> "Use Sender Policy Framework.",
533 description
=> "Use Greylisting.",
538 description
=> "Use SMTP HELO tests.",
543 description
=> "Reject unknown clients.",
547 rejectunknownsender
=> {
548 description
=> "Reject unknown senders.",
553 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
555 enum
=> ['450', '550'],
558 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
559 type
=> 'string', format
=> 'dnsbl-entry-list',
566 int_port
=> { optional
=> 1 },
567 ext_port
=> { optional
=> 1 },
568 smarthost
=> { optional
=> 1 },
569 relay
=> { optional
=> 1 },
570 relayport
=> { optional
=> 1 },
571 relaynomx
=> { optional
=> 1 },
572 dwarning
=> { optional
=> 1 },
573 max_smtpd_in
=> { optional
=> 1 },
574 max_smtpd_out
=> { optional
=> 1 },
575 greylist
=> { optional
=> 1 },
576 helotests
=> { optional
=> 1 },
577 tls
=> { optional
=> 1 },
578 tlslog
=> { optional
=> 1 },
579 tlsheader
=> { optional
=> 1 },
580 spf
=> { optional
=> 1 },
581 maxsize
=> { optional
=> 1 },
582 banner
=> { optional
=> 1 },
583 max_filters
=> { optional
=> 1 },
584 max_policy
=> { optional
=> 1 },
585 hide_received
=> { optional
=> 1 },
586 rejectunknown
=> { optional
=> 1 },
587 rejectunknownsender
=> { optional
=> 1 },
588 conn_count_limit
=> { optional
=> 1 },
589 conn_rate_limit
=> { optional
=> 1 },
590 message_rate_limit
=> { optional
=> 1 },
591 verifyreceivers
=> { optional
=> 1 },
592 dnsbl_sites
=> { optional
=> 1 },
605 use PVE
::Tools
qw($IPV4RE $IPV6RE);
611 PMG
::Config
::Admin-
>register();
612 PMG
::Config
::Mail-
>register();
613 PMG
::Config
::SpamQuarantine-
>register();
614 PMG
::Config
::VirusQuarantine-
>register();
615 PMG
::Config
::Spam-
>register();
616 PMG
::Config
::ClamAV-
>register();
618 # initialize all plugins
619 PMG
::Config
::Base-
>init();
621 PVE
::JSONSchema
::register_format
(
622 'transport-domain', \
&pmg_verify_transport_domain
);
624 sub pmg_verify_transport_domain
{
625 my ($name, $noerr) = @_;
627 # like dns-name, but can contain leading dot
628 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
630 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
631 return undef if $noerr;
632 die "value does not look like a valid transport domain\n";
637 PVE
::JSONSchema
::register_format
(
638 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
640 sub pmg_verify_dnsbl_entry
{
641 my ($name, $noerr) = @_;
643 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
644 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
646 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
647 return undef if $noerr;
648 die "value '$name' does not look like a valid dnsbl entry\n";
656 my $class = ref($type) || $type;
658 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
660 return bless $cfg, $class;
666 PVE
::INotify
::write_file
("pmg.conf", $self);
669 my $lockfile = "/var/lock/pmgconfig.lck";
672 my ($code, $errmsg) = @_;
674 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
676 $errmsg ?
die "$errmsg: $err" : die $err;
682 my ($self, $section, $key, $value) = @_;
684 my $pdata = PMG
::Config
::Base-
>private();
686 my $plugin = $pdata->{plugins
}->{$section};
687 die "no such section '$section'" if !$plugin;
689 if (defined($value)) {
690 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
691 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
692 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
694 if (defined($self->{ids
}->{$section})) {
695 delete $self->{ids
}->{$section}->{$key};
702 # get section value or default
704 my ($self, $section, $key, $nodefault) = @_;
706 my $pdata = PMG
::Config
::Base-
>private();
707 my $pdesc = $pdata->{propertyList
}->{$key};
708 die "no such property '$section/$key'\n"
709 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
710 defined($pdata->{options
}->{$section}->{$key}));
712 if (defined($self->{ids
}->{$section}) &&
713 defined(my $value = $self->{ids
}->{$section}->{$key})) {
717 return undef if $nodefault;
719 return $pdesc->{default};
722 # get a whole section with default value
724 my ($self, $section) = @_;
726 my $pdata = PMG
::Config
::Base-
>private();
727 return undef if !defined($pdata->{options
}->{$section});
731 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
733 my $pdesc = $pdata->{propertyList
}->{$key};
735 if (defined($self->{ids
}->{$section}) &&
736 defined(my $value = $self->{ids
}->{$section}->{$key})) {
737 $res->{$key} = $value;
740 $res->{$key} = $pdesc->{default};
746 # get a whole config with default values
750 my $pdata = PMG
::Config
::Base-
>private();
754 foreach my $type (keys %{$pdata->{plugins
}}) {
755 my $plugin = $pdata->{plugins
}->{$type};
756 $res->{$type} = $self->get_section($type);
763 my ($filename, $fh) = @_;
765 local $/ = undef; # slurp mode
767 my $raw = <$fh> if defined($fh);
769 return PMG
::Config
::Base-
>parse_config($filename, $raw);
773 my ($filename, $fh, $cfg) = @_;
775 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
777 PVE
::Tools
::safe_print
($filename, $fh, $raw);
780 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
783 undef, always_call_parser
=> 1);
785 # parsers/writers for other files
787 my $domainsfilename = "/etc/pmg/domains";
789 sub postmap_pmg_domains
{
790 PMG
::Utils
::run_postmap
($domainsfilename);
793 sub read_pmg_domains
{
794 my ($filename, $fh) = @_;
800 while (defined(my $line = <$fh>)) {
802 next if $line =~ m/^\s*$/;
803 if ($line =~ m/^#(.*)\s*$/) {
807 if ($line =~ m/^(\S+)\s.*$/) {
809 $domains->{$domain} = {
810 domain
=> $domain, comment
=> $comment };
813 warn "parse error in '$filename': $line\n";
822 sub write_pmg_domains
{
823 my ($filename, $fh, $domains) = @_;
825 foreach my $domain (sort keys %$domains) {
826 my $comment = $domains->{$domain}->{comment
};
827 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
828 if defined($comment) && $comment !~ m/^\s*$/;
830 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
834 PVE
::INotify
::register_file
('domains', $domainsfilename,
837 undef, always_call_parser
=> 1);
839 my $mynetworks_filename = "/etc/pmg/mynetworks";
841 sub read_pmg_mynetworks
{
842 my ($filename, $fh) = @_;
848 while (defined(my $line = <$fh>)) {
850 next if $line =~ m/^\s*$/;
851 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
852 my ($network, $prefix_size, $comment) = ($1, $2, $3);
853 my $cidr = "$network/${prefix_size}";
854 $mynetworks->{$cidr} = {
856 network_address
=> $network,
857 prefix_size
=> $prefix_size,
858 comment
=> $comment // '',
861 warn "parse error in '$filename': $line\n";
869 sub write_pmg_mynetworks
{
870 my ($filename, $fh, $mynetworks) = @_;
872 foreach my $cidr (sort keys %$mynetworks) {
873 my $data = $mynetworks->{$cidr};
874 my $comment = $data->{comment
} // '*';
875 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
879 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
880 \
&read_pmg_mynetworks
,
881 \
&write_pmg_mynetworks
,
882 undef, always_call_parser
=> 1);
884 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
886 sub postmap_tls_policy
{
887 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
890 my $transport_map_filename = "/etc/pmg/transport";
892 sub postmap_pmg_transport
{
893 PMG
::Utils
::run_postmap
($transport_map_filename);
896 sub read_transport_map
{
897 my ($filename, $fh) = @_;
899 return [] if !defined($fh);
905 while (defined(my $line = <$fh>)) {
907 next if $line =~ m/^\s*$/;
908 if ($line =~ m/^#(.*)\s*$/) {
913 my $parse_error = sub {
915 warn "parse error in '$filename': $line - $err";
919 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
920 my ($domain, $host, $port) = ($1, $2, $3);
922 eval { pmg_verify_transport_domain
($domain); };
924 $parse_error->($err);
928 if ($host =~ m/^\[(.*)\]$/) {
933 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
935 $parse_error->($err);
946 $res->{$domain} = $data;
949 $parse_error->('wrong format');
956 sub write_transport_map
{
957 my ($filename, $fh, $tmap) = @_;
961 foreach my $domain (sort keys %$tmap) {
962 my $data = $tmap->{$domain};
964 my $comment = $data->{comment
};
965 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
966 if defined($comment) && $comment !~ m/^\s*$/;
968 my $use_mx = $data->{use_mx
};
969 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
972 PVE
::Tools
::safe_print
(
973 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
975 PVE
::Tools
::safe_print
(
976 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
981 PVE
::INotify
::register_file
('transport', $transport_map_filename,
982 \
&read_transport_map
,
983 \
&write_transport_map
,
984 undef, always_call_parser
=> 1);
986 # config file generation using templates
988 sub get_template_vars
{
991 my $vars = { pmg
=> $self->get_config() };
993 my $nodename = PVE
::INotify
::nodename
();
994 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
995 my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip);
996 $vars->{ipconfig
}->{int_ip
} = $int_ip;
997 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
999 my $transportnets = [];
1001 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1002 foreach my $domain (sort keys %$tmap) {
1003 my $data = $tmap->{$domain};
1004 my $host = $data->{host
};
1005 if ($host =~ m/^$IPV4RE$/) {
1006 push @$transportnets, "$host/32";
1007 } elsif ($host =~ m/^$IPV6RE$/) {
1008 push @$transportnets, "[$host]/128";
1013 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1015 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1016 push @$mynetworks, $int_net_cidr;
1018 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1019 push @$mynetworks, keys %$netlist;
1021 push @$mynetworks, @$transportnets;
1023 # add default relay to mynetworks
1024 if (my $relay = $self->get('mail', 'relay')) {
1025 if ($relay =~ m/^$IPV4RE$/) {
1026 push @$mynetworks, "$relay/32";
1027 } elsif ($relay =~ m/^$IPV6RE$/) {
1028 push @$mynetworks, "[$relay]/128";
1030 # DNS name - do nothing ?
1034 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1036 # normalize dnsbl_sites
1037 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1038 if (scalar(@dnsbl_sites)) {
1039 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1043 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1044 $self->get('mail', 'spf');
1045 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1047 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1048 $vars->{dns
}->{hostname
} = $nodename;
1050 my $domain = $resolv->{search
} // 'localdomain';
1051 $vars->{dns
}->{domain
} = $domain;
1053 my $wlbr = "$nodename.$domain";
1054 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1057 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1059 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1061 my $uri = URI-
>new($proxy);
1062 my $host = $uri->host;
1063 my $port = $uri->port // 8080;
1065 my $data = { host
=> $host, port
=> $port };
1066 if (my $ui = $uri->userinfo) {
1067 my ($username, $pw) = split(/:/, $ui, 2);
1068 $data->{username
} = $username;
1069 $data->{password
} = $pw if defined($pw);
1071 $vars->{proxy
} = $data;
1074 warn "parse http_proxy failed - $@" if $@;
1080 # use one global TT cache
1081 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1083 my $template_toolkit;
1085 sub get_template_toolkit
{
1087 return $template_toolkit if $template_toolkit;
1089 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1091 return $template_toolkit;
1094 # rewrite file from template
1095 # return true if file has changed
1096 sub rewrite_config_file
{
1097 my ($self, $tmplname, $dstfn) = @_;
1099 my $demo = $self->get('admin', 'demo');
1102 my $demosrc = "$tmplname.demo";
1103 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1106 my ($perm, $uid, $gid);
1108 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1109 # needed if file contains a HTTPProxyPasswort
1111 $uid = getpwnam('clamav');
1112 $gid = getgrnam('adm');
1116 my $tt = get_template_toolkit
();
1118 my $vars = $self->get_template_vars();
1122 $tt->process($tmplname, $vars, \
$output) ||
1123 die $tt->error() . "\n";
1125 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1127 return 0 if defined($old) && ($old eq $output); # no change
1129 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1131 if (defined($uid) && defined($gid)) {
1132 chown($uid, $gid, $dstfn);
1138 # rewrite spam configuration
1139 sub rewrite_config_spam
{
1142 my $use_awl = $self->get('spam', 'use_awl');
1143 my $use_bayes = $self->get('spam', 'use_bayes');
1144 my $use_razor = $self->get('spam', 'use_razor');
1148 # delete AW and bayes databases if those features are disabled
1150 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1154 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1155 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1156 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1159 # make sure we have a custom.cf file (else cluster sync fails)
1160 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1162 $changes = 1 if $self->rewrite_config_file(
1163 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1165 $changes = 1 if $self->rewrite_config_file(
1166 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1168 $changes = 1 if $self->rewrite_config_file(
1169 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1171 $changes = 1 if $self->rewrite_config_file(
1172 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1175 mkdir "/root/.razor";
1177 $changes = 1 if $self->rewrite_config_file(
1178 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1180 if (! -e
'/root/.razor/identity') {
1183 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1184 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1187 syslog
('info', "registering razor failed: $err") if $err;
1194 # rewrite ClamAV configuration
1195 sub rewrite_config_clam
{
1198 return $self->rewrite_config_file(
1199 'clamd.conf.in', '/etc/clamav/clamd.conf');
1202 sub rewrite_config_freshclam
{
1205 return $self->rewrite_config_file(
1206 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1209 sub rewrite_config_postgres
{
1212 my $pgconfdir = "/etc/postgresql/9.6/main";
1216 $changes = 1 if $self->rewrite_config_file(
1217 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1219 $changes = 1 if $self->rewrite_config_file(
1220 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1225 # rewrite /root/.forward
1226 sub rewrite_dot_forward
{
1229 my $dstfn = '/root/.forward';
1231 my $email = $self->get('admin', 'email');
1234 if ($email && $email =~ m/\s*(\S+)\s*/) {
1237 # empty .forward does not forward mails (see man local)
1240 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1242 return 0 if defined($old) && ($old eq $output); # no change
1244 PVE
::Tools
::file_set_contents
($dstfn, $output);
1249 my $write_smtp_whitelist = sub {
1250 my ($filename, $data, $action) = @_;
1252 $action = 'OK' if !$action;
1254 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1258 foreach my $k (sort keys %$data) {
1259 $new .= "$k $action\n";
1262 return 0 if defined($old) && ($old eq $new); # no change
1264 PVE
::Tools
::file_set_contents
($filename, $new);
1266 PMG
::Utils
::run_postmap
($filename);
1271 sub rewrite_postfix_whitelist
{
1272 my ($rulecache) = @_;
1274 # see man page for regexp_table for postfix regex table format
1276 # we use a hash to avoid duplicate entries in regex tables
1279 my $clientlist = {};
1281 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1282 my $oclass = ref($obj);
1283 if ($oclass eq 'PMG::RuleDB::Receiver') {
1284 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1285 $tolist->{"/^$addr\$/"} = 1;
1286 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1287 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1288 $tolist->{"/^.+\@$addr\$/"} = 1;
1289 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1290 my $addr = $obj->{address
};
1292 $tolist->{"/^$addr\$/"} = 1;
1296 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1297 my $oclass = ref($obj);
1298 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1299 if ($oclass eq 'PMG::RuleDB::EMail') {
1300 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1301 $fromlist->{"/^$addr\$/"} = 1;
1302 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1303 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1304 $fromlist->{"/^.+\@$addr\$/"} = 1;
1305 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1306 my $addr = $obj->{address
};
1308 $fromlist->{"/^$addr\$/"} = 1;
1309 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1310 $clientlist->{$obj->{address
}} = 1;
1311 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1312 $clientlist->{$obj->{address
}} = 1;
1316 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1317 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1318 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1319 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1322 # rewrite /etc/postfix/*
1323 sub rewrite_config_postfix
{
1324 my ($self, $rulecache) = @_;
1326 # make sure we have required files (else postfix start fails)
1327 IO
::File-
>new($transport_map_filename, 'a', 0644);
1331 if ($self->get('mail', 'tls')) {
1333 PMG
::Utils
::gen_proxmox_tls_cert
();
1335 syslog
('info', "generating certificate failed: $@") if $@;
1338 $changes = 1 if $self->rewrite_config_file(
1339 'main.cf.in', '/etc/postfix/main.cf');
1341 $changes = 1 if $self->rewrite_config_file(
1342 'master.cf.in', '/etc/postfix/master.cf');
1344 # make sure we have required files (else postfix start fails)
1345 # Note: postmap need a valid /etc/postfix/main.cf configuration
1346 postmap_pmg_domains
();
1347 postmap_pmg_transport
();
1348 postmap_tls_policy
();
1350 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1352 # make sure aliases.db is up to date
1353 system('/usr/bin/newaliases');
1358 sub rewrite_config
{
1359 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1361 $force_restart = {} if ! $force_restart;
1363 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1364 $force_restart->{postfix
}) {
1365 PMG
::Utils
::service_cmd
('postfix', 'restart');
1368 if ($self->rewrite_dot_forward() && $restart_services) {
1369 # no need to restart anything
1372 if ($self->rewrite_config_postgres() && $restart_services) {
1373 # do nothing (too many side effects)?
1374 # does not happen anyways, because config does not change.
1377 if (($self->rewrite_config_spam() && $restart_services) ||
1378 $force_restart->{spam
}) {
1379 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1382 if (($self->rewrite_config_clam() && $restart_services) ||
1383 $force_restart->{clam
}) {
1384 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1387 if (($self->rewrite_config_freshclam() && $restart_services) ||
1388 $force_restart->{freshclam
}) {
1389 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');