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 $vars->{ipconfig
}->{int_ip
} = $int_ip;
997 my $transportnets = [];
999 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1000 foreach my $domain (sort keys %$tmap) {
1001 my $data = $tmap->{$domain};
1002 my $host = $data->{host
};
1003 if ($host =~ m/^$IPV4RE$/) {
1004 push @$transportnets, "$host/32";
1005 } elsif ($host =~ m/^$IPV6RE$/) {
1006 push @$transportnets, "[$host]/128";
1011 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1013 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1015 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1016 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1017 push @$mynetworks, "[$1]/$2";
1019 push @$mynetworks, $int_net_cidr;
1022 if ($int_ip =~ m/^$IPV6RE$/) {
1023 push @$mynetworks, "[$int_ip]/128";
1025 push @$mynetworks, "$int_ip/32";
1029 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1030 foreach my $cidr (keys %$netlist) {
1031 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1032 push @$mynetworks, "[$1]/$2";
1034 push @$mynetworks, $cidr;
1038 push @$mynetworks, @$transportnets;
1040 # add default relay to mynetworks
1041 if (my $relay = $self->get('mail', 'relay')) {
1042 if ($relay =~ m/^$IPV4RE$/) {
1043 push @$mynetworks, "$relay/32";
1044 } elsif ($relay =~ m/^$IPV6RE$/) {
1045 push @$mynetworks, "[$relay]/128";
1047 # DNS name - do nothing ?
1051 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1053 # normalize dnsbl_sites
1054 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1055 if (scalar(@dnsbl_sites)) {
1056 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1060 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1061 $self->get('mail', 'spf');
1062 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1064 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1065 $vars->{dns
}->{hostname
} = $nodename;
1067 my $domain = $resolv->{search
} // 'localdomain';
1068 $vars->{dns
}->{domain
} = $domain;
1070 my $wlbr = "$nodename.$domain";
1071 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1074 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1076 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1078 my $uri = URI-
>new($proxy);
1079 my $host = $uri->host;
1080 my $port = $uri->port // 8080;
1082 my $data = { host
=> $host, port
=> $port };
1083 if (my $ui = $uri->userinfo) {
1084 my ($username, $pw) = split(/:/, $ui, 2);
1085 $data->{username
} = $username;
1086 $data->{password
} = $pw if defined($pw);
1088 $vars->{proxy
} = $data;
1091 warn "parse http_proxy failed - $@" if $@;
1097 # use one global TT cache
1098 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1100 my $template_toolkit;
1102 sub get_template_toolkit
{
1104 return $template_toolkit if $template_toolkit;
1106 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1108 return $template_toolkit;
1111 # rewrite file from template
1112 # return true if file has changed
1113 sub rewrite_config_file
{
1114 my ($self, $tmplname, $dstfn) = @_;
1116 my $demo = $self->get('admin', 'demo');
1119 my $demosrc = "$tmplname.demo";
1120 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1123 my ($perm, $uid, $gid);
1125 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1126 # needed if file contains a HTTPProxyPasswort
1128 $uid = getpwnam('clamav');
1129 $gid = getgrnam('adm');
1133 my $tt = get_template_toolkit
();
1135 my $vars = $self->get_template_vars();
1139 $tt->process($tmplname, $vars, \
$output) ||
1140 die $tt->error() . "\n";
1142 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1144 return 0 if defined($old) && ($old eq $output); # no change
1146 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1148 if (defined($uid) && defined($gid)) {
1149 chown($uid, $gid, $dstfn);
1155 # rewrite spam configuration
1156 sub rewrite_config_spam
{
1159 my $use_awl = $self->get('spam', 'use_awl');
1160 my $use_bayes = $self->get('spam', 'use_bayes');
1161 my $use_razor = $self->get('spam', 'use_razor');
1165 # delete AW and bayes databases if those features are disabled
1167 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1171 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1172 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1173 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1176 # make sure we have a custom.cf file (else cluster sync fails)
1177 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1179 $changes = 1 if $self->rewrite_config_file(
1180 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1182 $changes = 1 if $self->rewrite_config_file(
1183 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1185 $changes = 1 if $self->rewrite_config_file(
1186 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1188 $changes = 1 if $self->rewrite_config_file(
1189 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1192 mkdir "/root/.razor";
1194 $changes = 1 if $self->rewrite_config_file(
1195 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1197 if (! -e
'/root/.razor/identity') {
1200 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1201 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1204 syslog
('info', "registering razor failed: $err") if $err;
1211 # rewrite ClamAV configuration
1212 sub rewrite_config_clam
{
1215 return $self->rewrite_config_file(
1216 'clamd.conf.in', '/etc/clamav/clamd.conf');
1219 sub rewrite_config_freshclam
{
1222 return $self->rewrite_config_file(
1223 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1226 sub rewrite_config_postgres
{
1229 my $pgconfdir = "/etc/postgresql/9.6/main";
1233 $changes = 1 if $self->rewrite_config_file(
1234 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1236 $changes = 1 if $self->rewrite_config_file(
1237 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1242 # rewrite /root/.forward
1243 sub rewrite_dot_forward
{
1246 my $dstfn = '/root/.forward';
1248 my $email = $self->get('admin', 'email');
1251 if ($email && $email =~ m/\s*(\S+)\s*/) {
1254 # empty .forward does not forward mails (see man local)
1257 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1259 return 0 if defined($old) && ($old eq $output); # no change
1261 PVE
::Tools
::file_set_contents
($dstfn, $output);
1266 my $write_smtp_whitelist = sub {
1267 my ($filename, $data, $action) = @_;
1269 $action = 'OK' if !$action;
1271 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1275 foreach my $k (sort keys %$data) {
1276 $new .= "$k $action\n";
1279 return 0 if defined($old) && ($old eq $new); # no change
1281 PVE
::Tools
::file_set_contents
($filename, $new);
1283 PMG
::Utils
::run_postmap
($filename);
1288 sub rewrite_postfix_whitelist
{
1289 my ($rulecache) = @_;
1291 # see man page for regexp_table for postfix regex table format
1293 # we use a hash to avoid duplicate entries in regex tables
1296 my $clientlist = {};
1298 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1299 my $oclass = ref($obj);
1300 if ($oclass eq 'PMG::RuleDB::Receiver') {
1301 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1302 $tolist->{"/^$addr\$/"} = 1;
1303 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1304 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1305 $tolist->{"/^.+\@$addr\$/"} = 1;
1306 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1307 my $addr = $obj->{address
};
1309 $tolist->{"/^$addr\$/"} = 1;
1313 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1314 my $oclass = ref($obj);
1315 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1316 if ($oclass eq 'PMG::RuleDB::EMail') {
1317 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1318 $fromlist->{"/^$addr\$/"} = 1;
1319 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1320 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1321 $fromlist->{"/^.+\@$addr\$/"} = 1;
1322 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1323 my $addr = $obj->{address
};
1325 $fromlist->{"/^$addr\$/"} = 1;
1326 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1327 $clientlist->{$obj->{address
}} = 1;
1328 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1329 $clientlist->{$obj->{address
}} = 1;
1333 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1334 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1335 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1336 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1339 # rewrite /etc/postfix/*
1340 sub rewrite_config_postfix
{
1341 my ($self, $rulecache) = @_;
1343 # make sure we have required files (else postfix start fails)
1344 IO
::File-
>new($transport_map_filename, 'a', 0644);
1348 if ($self->get('mail', 'tls')) {
1350 PMG
::Utils
::gen_proxmox_tls_cert
();
1352 syslog
('info', "generating certificate failed: $@") if $@;
1355 $changes = 1 if $self->rewrite_config_file(
1356 'main.cf.in', '/etc/postfix/main.cf');
1358 $changes = 1 if $self->rewrite_config_file(
1359 'master.cf.in', '/etc/postfix/master.cf');
1361 # make sure we have required files (else postfix start fails)
1362 # Note: postmap need a valid /etc/postfix/main.cf configuration
1363 postmap_pmg_domains
();
1364 postmap_pmg_transport
();
1365 postmap_tls_policy
();
1367 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1369 # make sure aliases.db is up to date
1370 system('/usr/bin/newaliases');
1375 sub rewrite_config
{
1376 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1378 $force_restart = {} if ! $force_restart;
1380 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1381 $force_restart->{postfix
}) {
1382 PMG
::Utils
::service_cmd
('postfix', 'restart');
1385 if ($self->rewrite_dot_forward() && $restart_services) {
1386 # no need to restart anything
1389 if ($self->rewrite_config_postgres() && $restart_services) {
1390 # do nothing (too many side effects)?
1391 # does not happen anyways, because config does not change.
1394 if (($self->rewrite_config_spam() && $restart_services) ||
1395 $force_restart->{spam
}) {
1396 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1399 if (($self->rewrite_config_clam() && $restart_services) ||
1400 $force_restart->{clam
}) {
1401 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1404 if (($self->rewrite_config_freshclam() && $restart_services) ||
1405 $force_restart->{freshclam
}) {
1406 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');