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. Useful if you run a Cluster and want users to connect to a specific host.",
232 type
=> 'string', format
=> 'address',
235 description
=> "Quarantine Port. Useful if you have a reverse proxy or port forwarding for the webinterface. Only used for the generated Spam report.",
242 description
=> "Quarantine Webinterface Protocol. Useful if you have a reverse proxy for the webinterface. Only used for the generated Spam report.",
244 enum
=> [qw(http https)],
248 description
=> "Text for 'From' header in daily spam report mails.",
256 mailfrom
=> { optional
=> 1 },
257 hostname
=> { optional
=> 1 },
258 lifetime
=> { optional
=> 1 },
259 authmode
=> { optional
=> 1 },
260 reportstyle
=> { optional
=> 1 },
261 viewimages
=> { optional
=> 1 },
262 allowhrefs
=> { optional
=> 1 },
263 port
=> { optional
=> 1 },
264 protocol
=> { optional
=> 1 },
268 package PMG
::Config
::VirusQuarantine
;
273 use base
qw(PMG::Config::Base);
285 lifetime
=> { optional
=> 1 },
286 viewimages
=> { optional
=> 1 },
287 allowhrefs
=> { optional
=> 1 },
291 package PMG
::Config
::ClamAV
;
296 use base
qw(PMG::Config::Base);
305 description
=> "ClamAV database mirror server.",
307 default => 'database.clamav.net',
309 archiveblockencrypted
=> {
310 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
315 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.",
321 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.",
327 description
=> "Files larger than this limit won't be scanned.",
333 description
=> "Sets the maximum amount of data to be scanned for each input file.",
336 default => 100000000,
339 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
345 description
=> "Enables support for Google Safe Browsing.",
354 archiveblockencrypted
=> { optional
=> 1 },
355 archivemaxrec
=> { optional
=> 1 },
356 archivemaxfiles
=> { optional
=> 1 },
357 archivemaxsize
=> { optional
=> 1 },
358 maxscansize
=> { optional
=> 1 },
359 dbmirror
=> { optional
=> 1 },
360 maxcccount
=> { optional
=> 1 },
361 safebrowsing
=> { optional
=> 1 },
365 package PMG
::Config
::Mail
;
370 use PVE
::ProcFSTools
;
372 use base
qw(PMG::Config::Base);
379 sub physical_memory
{
381 return $physicalmem if $physicalmem;
383 my $info = PVE
::ProcFSTools
::read_meminfo
();
384 my $total = int($info->{memtotal
} / (1024*1024));
389 sub get_max_filters
{
390 # estimate optimal number of filter servers
394 my $memory = physical_memory
();
395 my $add_servers = int(($memory - 512)/$servermem);
396 $max_servers += $add_servers if $add_servers > 0;
397 $max_servers = 40 if $max_servers > 40;
399 return $max_servers - 2;
403 # estimate optimal number of smtpd daemons
405 my $max_servers = 25;
407 my $memory = physical_memory
();
408 my $add_servers = int(($memory - 512)/$servermem);
409 $max_servers += $add_servers if $add_servers > 0;
410 $max_servers = 100 if $max_servers > 100;
415 # estimate optimal number of proxpolicy servers
417 my $memory = physical_memory
();
418 $max_servers = 5 if $memory >= 500;
425 description
=> "SMTP port number for outgoing mail (trusted).",
432 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
439 description
=> "The default mail delivery transport (incoming mails).",
440 type
=> 'string', format
=> 'address',
443 description
=> "SMTP port number for relay host.",
450 description
=> "Disable MX lookups for default relay.",
455 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
456 type
=> 'string', format
=> 'address',
459 description
=> "ESMTP banner.",
462 default => 'ESMTP Proxmox',
465 description
=> "Maximum number of pmg-smtp-filter processes.",
469 default => get_max_filters
(),
472 description
=> "Maximum number of pmgpolicy processes.",
476 default => get_max_policy
(),
479 description
=> "Maximum number of SMTP daemon processes (in).",
483 default => get_max_smtpd
(),
486 description
=> "Maximum number of SMTP daemon processes (out).",
490 default => get_max_smtpd
(),
492 conn_count_limit
=> {
493 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
499 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.",
504 message_rate_limit
=> {
505 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.",
511 description
=> "Hide received header in outgoing mails.",
516 description
=> "Maximum email size. Larger mails are rejected.",
519 default => 1024*1024*10,
522 description
=> "SMTP delay warning time (in hours).",
528 description
=> "Enable TLS.",
533 description
=> "Enable TLS Logging.",
538 description
=> "Add TLS received header.",
543 description
=> "Use Sender Policy Framework.",
548 description
=> "Use Greylisting.",
553 description
=> "Use SMTP HELO tests.",
558 description
=> "Reject unknown clients.",
562 rejectunknownsender
=> {
563 description
=> "Reject unknown senders.",
568 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
570 enum
=> ['450', '550'],
573 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
574 type
=> 'string', format
=> 'dnsbl-entry-list',
581 int_port
=> { optional
=> 1 },
582 ext_port
=> { optional
=> 1 },
583 smarthost
=> { optional
=> 1 },
584 relay
=> { optional
=> 1 },
585 relayport
=> { optional
=> 1 },
586 relaynomx
=> { optional
=> 1 },
587 dwarning
=> { optional
=> 1 },
588 max_smtpd_in
=> { optional
=> 1 },
589 max_smtpd_out
=> { optional
=> 1 },
590 greylist
=> { optional
=> 1 },
591 helotests
=> { optional
=> 1 },
592 tls
=> { optional
=> 1 },
593 tlslog
=> { optional
=> 1 },
594 tlsheader
=> { optional
=> 1 },
595 spf
=> { optional
=> 1 },
596 maxsize
=> { optional
=> 1 },
597 banner
=> { optional
=> 1 },
598 max_filters
=> { optional
=> 1 },
599 max_policy
=> { optional
=> 1 },
600 hide_received
=> { optional
=> 1 },
601 rejectunknown
=> { optional
=> 1 },
602 rejectunknownsender
=> { optional
=> 1 },
603 conn_count_limit
=> { optional
=> 1 },
604 conn_rate_limit
=> { optional
=> 1 },
605 message_rate_limit
=> { optional
=> 1 },
606 verifyreceivers
=> { optional
=> 1 },
607 dnsbl_sites
=> { optional
=> 1 },
620 use PVE
::Tools
qw($IPV4RE $IPV6RE);
626 PMG
::Config
::Admin-
>register();
627 PMG
::Config
::Mail-
>register();
628 PMG
::Config
::SpamQuarantine-
>register();
629 PMG
::Config
::VirusQuarantine-
>register();
630 PMG
::Config
::Spam-
>register();
631 PMG
::Config
::ClamAV-
>register();
633 # initialize all plugins
634 PMG
::Config
::Base-
>init();
636 PVE
::JSONSchema
::register_format
(
637 'transport-domain', \
&pmg_verify_transport_domain
);
639 sub pmg_verify_transport_domain
{
640 my ($name, $noerr) = @_;
642 # like dns-name, but can contain leading dot
643 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
645 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
646 return undef if $noerr;
647 die "value does not look like a valid transport domain\n";
652 PVE
::JSONSchema
::register_format
(
653 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
655 sub pmg_verify_dnsbl_entry
{
656 my ($name, $noerr) = @_;
658 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
659 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
661 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
662 return undef if $noerr;
663 die "value '$name' does not look like a valid dnsbl entry\n";
671 my $class = ref($type) || $type;
673 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
675 return bless $cfg, $class;
681 PVE
::INotify
::write_file
("pmg.conf", $self);
684 my $lockfile = "/var/lock/pmgconfig.lck";
687 my ($code, $errmsg) = @_;
689 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
691 $errmsg ?
die "$errmsg: $err" : die $err;
697 my ($self, $section, $key, $value) = @_;
699 my $pdata = PMG
::Config
::Base-
>private();
701 my $plugin = $pdata->{plugins
}->{$section};
702 die "no such section '$section'" if !$plugin;
704 if (defined($value)) {
705 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
706 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
707 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
709 if (defined($self->{ids
}->{$section})) {
710 delete $self->{ids
}->{$section}->{$key};
717 # get section value or default
719 my ($self, $section, $key, $nodefault) = @_;
721 my $pdata = PMG
::Config
::Base-
>private();
722 my $pdesc = $pdata->{propertyList
}->{$key};
723 die "no such property '$section/$key'\n"
724 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
725 defined($pdata->{options
}->{$section}->{$key}));
727 if (defined($self->{ids
}->{$section}) &&
728 defined(my $value = $self->{ids
}->{$section}->{$key})) {
732 return undef if $nodefault;
734 return $pdesc->{default};
737 # get a whole section with default value
739 my ($self, $section) = @_;
741 my $pdata = PMG
::Config
::Base-
>private();
742 return undef if !defined($pdata->{options
}->{$section});
746 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
748 my $pdesc = $pdata->{propertyList
}->{$key};
750 if (defined($self->{ids
}->{$section}) &&
751 defined(my $value = $self->{ids
}->{$section}->{$key})) {
752 $res->{$key} = $value;
755 $res->{$key} = $pdesc->{default};
761 # get a whole config with default values
765 my $pdata = PMG
::Config
::Base-
>private();
769 foreach my $type (keys %{$pdata->{plugins
}}) {
770 my $plugin = $pdata->{plugins
}->{$type};
771 $res->{$type} = $self->get_section($type);
778 my ($filename, $fh) = @_;
780 local $/ = undef; # slurp mode
782 my $raw = <$fh> if defined($fh);
784 return PMG
::Config
::Base-
>parse_config($filename, $raw);
788 my ($filename, $fh, $cfg) = @_;
790 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
792 PVE
::Tools
::safe_print
($filename, $fh, $raw);
795 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
798 undef, always_call_parser
=> 1);
800 # parsers/writers for other files
802 my $domainsfilename = "/etc/pmg/domains";
804 sub postmap_pmg_domains
{
805 PMG
::Utils
::run_postmap
($domainsfilename);
808 sub read_pmg_domains
{
809 my ($filename, $fh) = @_;
815 while (defined(my $line = <$fh>)) {
817 next if $line =~ m/^\s*$/;
818 if ($line =~ m/^#(.*)\s*$/) {
822 if ($line =~ m/^(\S+)\s.*$/) {
824 $domains->{$domain} = {
825 domain
=> $domain, comment
=> $comment };
828 warn "parse error in '$filename': $line\n";
837 sub write_pmg_domains
{
838 my ($filename, $fh, $domains) = @_;
840 foreach my $domain (sort keys %$domains) {
841 my $comment = $domains->{$domain}->{comment
};
842 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
843 if defined($comment) && $comment !~ m/^\s*$/;
845 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
849 PVE
::INotify
::register_file
('domains', $domainsfilename,
852 undef, always_call_parser
=> 1);
854 my $mynetworks_filename = "/etc/pmg/mynetworks";
856 sub read_pmg_mynetworks
{
857 my ($filename, $fh) = @_;
863 while (defined(my $line = <$fh>)) {
865 next if $line =~ m/^\s*$/;
866 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
867 my ($network, $prefix_size, $comment) = ($1, $2, $3);
868 my $cidr = "$network/${prefix_size}";
869 $mynetworks->{$cidr} = {
871 network_address
=> $network,
872 prefix_size
=> $prefix_size,
873 comment
=> $comment // '',
876 warn "parse error in '$filename': $line\n";
884 sub write_pmg_mynetworks
{
885 my ($filename, $fh, $mynetworks) = @_;
887 foreach my $cidr (sort keys %$mynetworks) {
888 my $data = $mynetworks->{$cidr};
889 my $comment = $data->{comment
} // '*';
890 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
894 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
895 \
&read_pmg_mynetworks
,
896 \
&write_pmg_mynetworks
,
897 undef, always_call_parser
=> 1);
899 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
901 sub postmap_tls_policy
{
902 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
905 my $transport_map_filename = "/etc/pmg/transport";
907 sub postmap_pmg_transport
{
908 PMG
::Utils
::run_postmap
($transport_map_filename);
911 sub read_transport_map
{
912 my ($filename, $fh) = @_;
914 return [] if !defined($fh);
920 while (defined(my $line = <$fh>)) {
922 next if $line =~ m/^\s*$/;
923 if ($line =~ m/^#(.*)\s*$/) {
928 my $parse_error = sub {
930 warn "parse error in '$filename': $line - $err";
934 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
935 my ($domain, $host, $port) = ($1, $2, $3);
937 eval { pmg_verify_transport_domain
($domain); };
939 $parse_error->($err);
943 if ($host =~ m/^\[(.*)\]$/) {
948 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
950 $parse_error->($err);
961 $res->{$domain} = $data;
964 $parse_error->('wrong format');
971 sub write_transport_map
{
972 my ($filename, $fh, $tmap) = @_;
976 foreach my $domain (sort keys %$tmap) {
977 my $data = $tmap->{$domain};
979 my $comment = $data->{comment
};
980 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
981 if defined($comment) && $comment !~ m/^\s*$/;
983 my $use_mx = $data->{use_mx
};
984 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
987 PVE
::Tools
::safe_print
(
988 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
990 PVE
::Tools
::safe_print
(
991 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
996 PVE
::INotify
::register_file
('transport', $transport_map_filename,
997 \
&read_transport_map
,
998 \
&write_transport_map
,
999 undef, always_call_parser
=> 1);
1001 # config file generation using templates
1003 sub get_template_vars
{
1006 my $vars = { pmg
=> $self->get_config() };
1008 my $nodename = PVE
::INotify
::nodename
();
1009 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
1010 $vars->{ipconfig
}->{int_ip
} = $int_ip;
1012 my $transportnets = [];
1014 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1015 foreach my $domain (sort keys %$tmap) {
1016 my $data = $tmap->{$domain};
1017 my $host = $data->{host
};
1018 if ($host =~ m/^$IPV4RE$/) {
1019 push @$transportnets, "$host/32";
1020 } elsif ($host =~ m/^$IPV6RE$/) {
1021 push @$transportnets, "[$host]/128";
1026 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1028 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1030 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1031 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1032 push @$mynetworks, "[$1]/$2";
1034 push @$mynetworks, $int_net_cidr;
1037 if ($int_ip =~ m/^$IPV6RE$/) {
1038 push @$mynetworks, "[$int_ip]/128";
1040 push @$mynetworks, "$int_ip/32";
1044 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1045 foreach my $cidr (keys %$netlist) {
1046 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1047 push @$mynetworks, "[$1]/$2";
1049 push @$mynetworks, $cidr;
1053 push @$mynetworks, @$transportnets;
1055 # add default relay to mynetworks
1056 if (my $relay = $self->get('mail', 'relay')) {
1057 if ($relay =~ m/^$IPV4RE$/) {
1058 push @$mynetworks, "$relay/32";
1059 } elsif ($relay =~ m/^$IPV6RE$/) {
1060 push @$mynetworks, "[$relay]/128";
1062 # DNS name - do nothing ?
1066 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1068 # normalize dnsbl_sites
1069 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1070 if (scalar(@dnsbl_sites)) {
1071 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1075 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1076 $self->get('mail', 'spf');
1077 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1079 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1080 $vars->{dns
}->{hostname
} = $nodename;
1082 my $domain = $resolv->{search
} // 'localdomain';
1083 $vars->{dns
}->{domain
} = $domain;
1085 my $wlbr = "$nodename.$domain";
1086 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1089 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1091 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1093 my $uri = URI-
>new($proxy);
1094 my $host = $uri->host;
1095 my $port = $uri->port // 8080;
1097 my $data = { host
=> $host, port
=> $port };
1098 if (my $ui = $uri->userinfo) {
1099 my ($username, $pw) = split(/:/, $ui, 2);
1100 $data->{username
} = $username;
1101 $data->{password
} = $pw if defined($pw);
1103 $vars->{proxy
} = $data;
1106 warn "parse http_proxy failed - $@" if $@;
1112 # use one global TT cache
1113 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1115 my $template_toolkit;
1117 sub get_template_toolkit
{
1119 return $template_toolkit if $template_toolkit;
1121 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1123 return $template_toolkit;
1126 # rewrite file from template
1127 # return true if file has changed
1128 sub rewrite_config_file
{
1129 my ($self, $tmplname, $dstfn) = @_;
1131 my $demo = $self->get('admin', 'demo');
1134 my $demosrc = "$tmplname.demo";
1135 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1138 my ($perm, $uid, $gid);
1140 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1141 # needed if file contains a HTTPProxyPasswort
1143 $uid = getpwnam('clamav');
1144 $gid = getgrnam('adm');
1148 my $tt = get_template_toolkit
();
1150 my $vars = $self->get_template_vars();
1154 $tt->process($tmplname, $vars, \
$output) ||
1155 die $tt->error() . "\n";
1157 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1159 return 0 if defined($old) && ($old eq $output); # no change
1161 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1163 if (defined($uid) && defined($gid)) {
1164 chown($uid, $gid, $dstfn);
1170 # rewrite spam configuration
1171 sub rewrite_config_spam
{
1174 my $use_awl = $self->get('spam', 'use_awl');
1175 my $use_bayes = $self->get('spam', 'use_bayes');
1176 my $use_razor = $self->get('spam', 'use_razor');
1180 # delete AW and bayes databases if those features are disabled
1182 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1186 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1187 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1188 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1191 # make sure we have a custom.cf file (else cluster sync fails)
1192 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1194 $changes = 1 if $self->rewrite_config_file(
1195 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1197 $changes = 1 if $self->rewrite_config_file(
1198 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1200 $changes = 1 if $self->rewrite_config_file(
1201 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1203 $changes = 1 if $self->rewrite_config_file(
1204 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1207 mkdir "/root/.razor";
1209 $changes = 1 if $self->rewrite_config_file(
1210 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1212 if (! -e
'/root/.razor/identity') {
1215 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1216 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1219 syslog
('info', "registering razor failed: $err") if $err;
1226 # rewrite ClamAV configuration
1227 sub rewrite_config_clam
{
1230 return $self->rewrite_config_file(
1231 'clamd.conf.in', '/etc/clamav/clamd.conf');
1234 sub rewrite_config_freshclam
{
1237 return $self->rewrite_config_file(
1238 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1241 sub rewrite_config_postgres
{
1244 my $pgconfdir = "/etc/postgresql/9.6/main";
1248 $changes = 1 if $self->rewrite_config_file(
1249 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1251 $changes = 1 if $self->rewrite_config_file(
1252 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1257 # rewrite /root/.forward
1258 sub rewrite_dot_forward
{
1261 my $dstfn = '/root/.forward';
1263 my $email = $self->get('admin', 'email');
1266 if ($email && $email =~ m/\s*(\S+)\s*/) {
1269 # empty .forward does not forward mails (see man local)
1272 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1274 return 0 if defined($old) && ($old eq $output); # no change
1276 PVE
::Tools
::file_set_contents
($dstfn, $output);
1281 my $write_smtp_whitelist = sub {
1282 my ($filename, $data, $action) = @_;
1284 $action = 'OK' if !$action;
1286 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1290 foreach my $k (sort keys %$data) {
1291 $new .= "$k $action\n";
1294 return 0 if defined($old) && ($old eq $new); # no change
1296 PVE
::Tools
::file_set_contents
($filename, $new);
1298 PMG
::Utils
::run_postmap
($filename);
1303 sub rewrite_postfix_whitelist
{
1304 my ($rulecache) = @_;
1306 # see man page for regexp_table for postfix regex table format
1308 # we use a hash to avoid duplicate entries in regex tables
1311 my $clientlist = {};
1313 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1314 my $oclass = ref($obj);
1315 if ($oclass eq 'PMG::RuleDB::Receiver') {
1316 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1317 $tolist->{"/^$addr\$/"} = 1;
1318 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1319 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1320 $tolist->{"/^.+\@$addr\$/"} = 1;
1321 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1322 my $addr = $obj->{address
};
1324 $tolist->{"/^$addr\$/"} = 1;
1328 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1329 my $oclass = ref($obj);
1330 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1331 if ($oclass eq 'PMG::RuleDB::EMail') {
1332 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1333 $fromlist->{"/^$addr\$/"} = 1;
1334 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1335 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1336 $fromlist->{"/^.+\@$addr\$/"} = 1;
1337 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1338 my $addr = $obj->{address
};
1340 $fromlist->{"/^$addr\$/"} = 1;
1341 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1342 $clientlist->{$obj->{address
}} = 1;
1343 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1344 $clientlist->{$obj->{address
}} = 1;
1348 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1349 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1350 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1351 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1354 # rewrite /etc/postfix/*
1355 sub rewrite_config_postfix
{
1356 my ($self, $rulecache) = @_;
1358 # make sure we have required files (else postfix start fails)
1359 IO
::File-
>new($transport_map_filename, 'a', 0644);
1363 if ($self->get('mail', 'tls')) {
1365 PMG
::Utils
::gen_proxmox_tls_cert
();
1367 syslog
('info', "generating certificate failed: $@") if $@;
1370 $changes = 1 if $self->rewrite_config_file(
1371 'main.cf.in', '/etc/postfix/main.cf');
1373 $changes = 1 if $self->rewrite_config_file(
1374 'master.cf.in', '/etc/postfix/master.cf');
1376 # make sure we have required files (else postfix start fails)
1377 # Note: postmap need a valid /etc/postfix/main.cf configuration
1378 postmap_pmg_domains
();
1379 postmap_pmg_transport
();
1380 postmap_tls_policy
();
1382 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1384 # make sure aliases.db is up to date
1385 system('/usr/bin/newaliases');
1390 sub rewrite_config
{
1391 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1393 $force_restart = {} if ! $force_restart;
1395 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1396 $force_restart->{postfix
}) {
1397 PMG
::Utils
::service_cmd
('postfix', 'restart');
1400 if ($self->rewrite_dot_forward() && $restart_services) {
1401 # no need to restart anything
1404 if ($self->rewrite_config_postgres() && $restart_services) {
1405 # do nothing (too many side effects)?
1406 # does not happen anyways, because config does not change.
1409 if (($self->rewrite_config_spam() && $restart_services) ||
1410 $force_restart->{spam
}) {
1411 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1414 if (($self->rewrite_config_clam() && $restart_services) ||
1415 $force_restart->{clam
}) {
1416 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1419 if (($self->rewrite_config_freshclam() && $restart_services) ||
1420 $force_restart->{freshclam
}) {
1421 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');