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://.*",
96 description
=> "Use Avast Virus Scanner (/bin/scan). You need to buy and install 'Avast Core Security' before you can enable this feature.",
101 description
=> "Use ClamAV Virus Scanner. This is the default virus scanner and is enabled by default.",
110 advfilter
=> { optional
=> 1 },
111 avast
=> { optional
=> 1 },
112 clamav
=> { optional
=> 1 },
113 statlifetime
=> { optional
=> 1 },
114 dailyreport
=> { optional
=> 1 },
115 demo
=> { optional
=> 1 },
116 email
=> { optional
=> 1 },
117 http_proxy
=> { optional
=> 1 },
121 package PMG
::Config
::Spam
;
126 use base
qw(PMG::Config::Base);
135 description
=> "This option is used to specify which languages are considered OK for incoming mail.",
137 pattern
=> '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
141 description
=> "Whether to use the naive-Bayesian-style classifier.",
146 description
=> "Use the Auto-Whitelist plugin.",
151 description
=> "Whether to use Razor2, if it is available.",
155 wl_bounce_relays
=> {
156 description
=> "Whitelist legitimate bounce relays.",
159 clamav_heuristic_score
=> {
160 description
=> "Score for ClamaAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
167 description
=> "Additional score for bounce mails.",
174 description
=> "Enable real time blacklists (RBL) checks.",
179 description
=> "Maximum size of spam messages in bytes.",
189 use_awl
=> { optional
=> 1 },
190 use_razor
=> { optional
=> 1 },
191 wl_bounce_relays
=> { optional
=> 1 },
192 languages
=> { optional
=> 1 },
193 use_bayes
=> { optional
=> 1 },
194 clamav_heuristic_score
=> { optional
=> 1 },
195 bounce_score
=> { optional
=> 1 },
196 rbl_checks
=> { optional
=> 1 },
197 maxspamsize
=> { optional
=> 1 },
201 package PMG
::Config
::SpamQuarantine
;
206 use base
qw(PMG::Config::Base);
215 description
=> "Quarantine life time (days)",
221 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.",
223 enum
=> [qw(ticket ldap ldapticket)],
227 description
=> "Spam report style.",
229 enum
=> [qw(none short verbose custom)],
230 default => 'verbose',
233 description
=> "Allow to view images.",
238 description
=> "Allow to view hyperlinks.",
243 description
=> "Quarantine Host. Useful if you run a Cluster and want users to connect to a specific host.",
244 type
=> 'string', format
=> 'address',
247 description
=> "Quarantine Port. Useful if you have a reverse proxy or port forwarding for the webinterface. Only used for the generated Spam report.",
254 description
=> "Quarantine Webinterface Protocol. Useful if you have a reverse proxy for the webinterface. Only used for the generated Spam report.",
256 enum
=> [qw(http https)],
260 description
=> "Text for 'From' header in daily spam report mails.",
268 mailfrom
=> { optional
=> 1 },
269 hostname
=> { optional
=> 1 },
270 lifetime
=> { optional
=> 1 },
271 authmode
=> { optional
=> 1 },
272 reportstyle
=> { optional
=> 1 },
273 viewimages
=> { optional
=> 1 },
274 allowhrefs
=> { optional
=> 1 },
275 port
=> { optional
=> 1 },
276 protocol
=> { optional
=> 1 },
280 package PMG
::Config
::VirusQuarantine
;
285 use base
qw(PMG::Config::Base);
297 lifetime
=> { optional
=> 1 },
298 viewimages
=> { optional
=> 1 },
299 allowhrefs
=> { optional
=> 1 },
303 package PMG
::Config
::ClamAV
;
308 use base
qw(PMG::Config::Base);
317 description
=> "ClamAV database mirror server.",
319 default => 'database.clamav.net',
321 archiveblockencrypted
=> {
322 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
327 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.",
333 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.",
339 description
=> "Files larger than this limit won't be scanned.",
345 description
=> "Sets the maximum amount of data to be scanned for each input file.",
348 default => 100000000,
351 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
357 description
=> "Enables support for Google Safe Browsing.",
366 archiveblockencrypted
=> { optional
=> 1 },
367 archivemaxrec
=> { optional
=> 1 },
368 archivemaxfiles
=> { optional
=> 1 },
369 archivemaxsize
=> { optional
=> 1 },
370 maxscansize
=> { optional
=> 1 },
371 dbmirror
=> { optional
=> 1 },
372 maxcccount
=> { optional
=> 1 },
373 safebrowsing
=> { optional
=> 1 },
377 package PMG
::Config
::Mail
;
382 use PVE
::ProcFSTools
;
384 use base
qw(PMG::Config::Base);
391 sub physical_memory
{
393 return $physicalmem if $physicalmem;
395 my $info = PVE
::ProcFSTools
::read_meminfo
();
396 my $total = int($info->{memtotal
} / (1024*1024));
401 sub get_max_filters
{
402 # estimate optimal number of filter servers
406 my $memory = physical_memory
();
407 my $add_servers = int(($memory - 512)/$servermem);
408 $max_servers += $add_servers if $add_servers > 0;
409 $max_servers = 40 if $max_servers > 40;
411 return $max_servers - 2;
415 # estimate optimal number of smtpd daemons
417 my $max_servers = 25;
419 my $memory = physical_memory
();
420 my $add_servers = int(($memory - 512)/$servermem);
421 $max_servers += $add_servers if $add_servers > 0;
422 $max_servers = 100 if $max_servers > 100;
427 # estimate optimal number of proxpolicy servers
429 my $memory = physical_memory
();
430 $max_servers = 5 if $memory >= 500;
437 description
=> "SMTP port number for outgoing mail (trusted).",
444 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
451 description
=> "The default mail delivery transport (incoming mails).",
452 type
=> 'string', format
=> 'address',
455 description
=> "SMTP port number for relay host.",
462 description
=> "Disable MX lookups for default relay.",
467 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
468 type
=> 'string', format
=> 'address',
471 description
=> "SMTP port number for smarthost.",
478 description
=> "ESMTP banner.",
481 default => 'ESMTP Proxmox',
484 description
=> "Maximum number of pmg-smtp-filter processes.",
488 default => get_max_filters
(),
491 description
=> "Maximum number of pmgpolicy processes.",
495 default => get_max_policy
(),
498 description
=> "Maximum number of SMTP daemon processes (in).",
502 default => get_max_smtpd
(),
505 description
=> "Maximum number of SMTP daemon processes (out).",
509 default => get_max_smtpd
(),
511 conn_count_limit
=> {
512 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
518 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.",
523 message_rate_limit
=> {
524 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.",
530 description
=> "Hide received header in outgoing mails.",
535 description
=> "Maximum email size. Larger mails are rejected.",
538 default => 1024*1024*10,
541 description
=> "SMTP delay warning time (in hours).",
547 description
=> "Enable TLS.",
552 description
=> "Enable TLS Logging.",
557 description
=> "Add TLS received header.",
562 description
=> "Use Sender Policy Framework.",
567 description
=> "Use Greylisting.",
572 description
=> "Use SMTP HELO tests.",
577 description
=> "Reject unknown clients.",
581 rejectunknownsender
=> {
582 description
=> "Reject unknown senders.",
587 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
589 enum
=> ['450', '550'],
592 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
593 type
=> 'string', format
=> 'dnsbl-entry-list',
596 description
=> "The inclusive lower bound for blocking a remote SMTP client, based on its combined DNSBL score (see postscreen_dnsbl_threshold parameter).",
606 int_port
=> { optional
=> 1 },
607 ext_port
=> { optional
=> 1 },
608 smarthost
=> { optional
=> 1 },
609 smarthostport
=> { optional
=> 1 },
610 relay
=> { optional
=> 1 },
611 relayport
=> { optional
=> 1 },
612 relaynomx
=> { optional
=> 1 },
613 dwarning
=> { optional
=> 1 },
614 max_smtpd_in
=> { optional
=> 1 },
615 max_smtpd_out
=> { optional
=> 1 },
616 greylist
=> { optional
=> 1 },
617 helotests
=> { optional
=> 1 },
618 tls
=> { optional
=> 1 },
619 tlslog
=> { optional
=> 1 },
620 tlsheader
=> { optional
=> 1 },
621 spf
=> { optional
=> 1 },
622 maxsize
=> { optional
=> 1 },
623 banner
=> { optional
=> 1 },
624 max_filters
=> { optional
=> 1 },
625 max_policy
=> { optional
=> 1 },
626 hide_received
=> { optional
=> 1 },
627 rejectunknown
=> { optional
=> 1 },
628 rejectunknownsender
=> { optional
=> 1 },
629 conn_count_limit
=> { optional
=> 1 },
630 conn_rate_limit
=> { optional
=> 1 },
631 message_rate_limit
=> { optional
=> 1 },
632 verifyreceivers
=> { optional
=> 1 },
633 dnsbl_sites
=> { optional
=> 1 },
634 dnsbl_threshold
=> { optional
=> 1 },
647 use PVE
::Tools
qw($IPV4RE $IPV6RE);
653 PMG
::Config
::Admin-
>register();
654 PMG
::Config
::Mail-
>register();
655 PMG
::Config
::SpamQuarantine-
>register();
656 PMG
::Config
::VirusQuarantine-
>register();
657 PMG
::Config
::Spam-
>register();
658 PMG
::Config
::ClamAV-
>register();
660 # initialize all plugins
661 PMG
::Config
::Base-
>init();
663 PVE
::JSONSchema
::register_format
(
664 'transport-domain', \
&pmg_verify_transport_domain
);
666 sub pmg_verify_transport_domain
{
667 my ($name, $noerr) = @_;
669 # like dns-name, but can contain leading dot
670 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
672 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
673 return undef if $noerr;
674 die "value does not look like a valid transport domain\n";
679 PVE
::JSONSchema
::register_format
(
680 'transport-domain-or-email', \
&pmg_verify_transport_domain_or_email
);
682 sub pmg_verify_transport_domain_or_email
{
683 my ($name, $noerr) = @_;
685 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
688 if ($name =~ m/^(?:[^\s\/\
@]+\
@)(${namere
}\
.)*${namere
}$/) {
692 # like dns-name, but can contain leading dot
693 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
694 return undef if $noerr;
695 die "value does not look like a valid transport domain or email address\n";
700 PVE
::JSONSchema
::register_format
(
701 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
703 sub pmg_verify_dnsbl_entry
{
704 my ($name, $noerr) = @_;
706 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
707 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
709 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
710 return undef if $noerr;
711 die "value '$name' does not look like a valid dnsbl entry\n";
719 my $class = ref($type) || $type;
721 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
723 return bless $cfg, $class;
729 PVE
::INotify
::write_file
("pmg.conf", $self);
732 my $lockfile = "/var/lock/pmgconfig.lck";
735 my ($code, $errmsg) = @_;
737 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
739 $errmsg ?
die "$errmsg: $err" : die $err;
745 my ($self, $section, $key, $value) = @_;
747 my $pdata = PMG
::Config
::Base-
>private();
749 my $plugin = $pdata->{plugins
}->{$section};
750 die "no such section '$section'" if !$plugin;
752 if (defined($value)) {
753 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
754 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
755 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
757 if (defined($self->{ids
}->{$section})) {
758 delete $self->{ids
}->{$section}->{$key};
765 # get section value or default
767 my ($self, $section, $key, $nodefault) = @_;
769 my $pdata = PMG
::Config
::Base-
>private();
770 my $pdesc = $pdata->{propertyList
}->{$key};
771 die "no such property '$section/$key'\n"
772 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
773 defined($pdata->{options
}->{$section}->{$key}));
775 if (defined($self->{ids
}->{$section}) &&
776 defined(my $value = $self->{ids
}->{$section}->{$key})) {
780 return undef if $nodefault;
782 return $pdesc->{default};
785 # get a whole section with default value
787 my ($self, $section) = @_;
789 my $pdata = PMG
::Config
::Base-
>private();
790 return undef if !defined($pdata->{options
}->{$section});
794 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
796 my $pdesc = $pdata->{propertyList
}->{$key};
798 if (defined($self->{ids
}->{$section}) &&
799 defined(my $value = $self->{ids
}->{$section}->{$key})) {
800 $res->{$key} = $value;
803 $res->{$key} = $pdesc->{default};
809 # get a whole config with default values
813 my $pdata = PMG
::Config
::Base-
>private();
817 foreach my $type (keys %{$pdata->{plugins
}}) {
818 my $plugin = $pdata->{plugins
}->{$type};
819 $res->{$type} = $self->get_section($type);
826 my ($filename, $fh) = @_;
828 local $/ = undef; # slurp mode
830 my $raw = <$fh> if defined($fh);
832 return PMG
::Config
::Base-
>parse_config($filename, $raw);
836 my ($filename, $fh, $cfg) = @_;
838 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
840 PVE
::Tools
::safe_print
($filename, $fh, $raw);
843 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
846 undef, always_call_parser
=> 1);
848 # parsers/writers for other files
850 my $domainsfilename = "/etc/pmg/domains";
852 sub postmap_pmg_domains
{
853 PMG
::Utils
::run_postmap
($domainsfilename);
856 sub read_pmg_domains
{
857 my ($filename, $fh) = @_;
863 while (defined(my $line = <$fh>)) {
865 next if $line =~ m/^\s*$/;
866 if ($line =~ m/^#(.*)\s*$/) {
870 if ($line =~ m/^(\S+)\s.*$/) {
872 $domains->{$domain} = {
873 domain
=> $domain, comment
=> $comment };
876 warn "parse error in '$filename': $line\n";
885 sub write_pmg_domains
{
886 my ($filename, $fh, $domains) = @_;
888 foreach my $domain (sort keys %$domains) {
889 my $comment = $domains->{$domain}->{comment
};
890 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
891 if defined($comment) && $comment !~ m/^\s*$/;
893 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
897 PVE
::INotify
::register_file
('domains', $domainsfilename,
900 undef, always_call_parser
=> 1);
902 my $mynetworks_filename = "/etc/pmg/mynetworks";
904 sub read_pmg_mynetworks
{
905 my ($filename, $fh) = @_;
911 while (defined(my $line = <$fh>)) {
913 next if $line =~ m/^\s*$/;
914 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
915 my ($network, $prefix_size, $comment) = ($1, $2, $3);
916 my $cidr = "$network/${prefix_size}";
917 $mynetworks->{$cidr} = {
919 network_address
=> $network,
920 prefix_size
=> $prefix_size,
921 comment
=> $comment // '',
924 warn "parse error in '$filename': $line\n";
932 sub write_pmg_mynetworks
{
933 my ($filename, $fh, $mynetworks) = @_;
935 foreach my $cidr (sort keys %$mynetworks) {
936 my $data = $mynetworks->{$cidr};
937 my $comment = $data->{comment
} // '*';
938 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
942 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
943 \
&read_pmg_mynetworks
,
944 \
&write_pmg_mynetworks
,
945 undef, always_call_parser
=> 1);
947 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
949 sub postmap_tls_policy
{
950 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
953 my $transport_map_filename = "/etc/pmg/transport";
955 sub postmap_pmg_transport
{
956 PMG
::Utils
::run_postmap
($transport_map_filename);
959 sub read_transport_map
{
960 my ($filename, $fh) = @_;
962 return [] if !defined($fh);
968 while (defined(my $line = <$fh>)) {
970 next if $line =~ m/^\s*$/;
971 if ($line =~ m/^#(.*)\s*$/) {
976 my $parse_error = sub {
978 warn "parse error in '$filename': $line - $err";
982 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
983 my ($domain, $host, $port) = ($1, $2, $3);
985 eval { pmg_verify_transport_domain_or_email
($domain); };
987 $parse_error->($err);
991 if ($host =~ m/^\[(.*)\]$/) {
996 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
998 $parse_error->($err);
1007 comment
=> $comment,
1009 $res->{$domain} = $data;
1012 $parse_error->('wrong format');
1019 sub write_transport_map
{
1020 my ($filename, $fh, $tmap) = @_;
1024 foreach my $domain (sort keys %$tmap) {
1025 my $data = $tmap->{$domain};
1027 my $comment = $data->{comment
};
1028 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
1029 if defined($comment) && $comment !~ m/^\s*$/;
1031 my $use_mx = $data->{use_mx
};
1032 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1035 PVE
::Tools
::safe_print
(
1036 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1038 PVE
::Tools
::safe_print
(
1039 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1044 PVE
::INotify
::register_file
('transport', $transport_map_filename,
1045 \
&read_transport_map
,
1046 \
&write_transport_map
,
1047 undef, always_call_parser
=> 1);
1049 # config file generation using templates
1051 sub get_template_vars
{
1054 my $vars = { pmg
=> $self->get_config() };
1056 my $nodename = PVE
::INotify
::nodename
();
1057 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
1058 $vars->{ipconfig
}->{int_ip
} = $int_ip;
1060 my $transportnets = [];
1062 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1063 foreach my $domain (sort keys %$tmap) {
1064 my $data = $tmap->{$domain};
1065 my $host = $data->{host
};
1066 if ($host =~ m/^$IPV4RE$/) {
1067 push @$transportnets, "$host/32";
1068 } elsif ($host =~ m/^$IPV6RE$/) {
1069 push @$transportnets, "[$host]/128";
1074 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1076 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1078 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1079 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1080 push @$mynetworks, "[$1]/$2";
1082 push @$mynetworks, $int_net_cidr;
1085 if ($int_ip =~ m/^$IPV6RE$/) {
1086 push @$mynetworks, "[$int_ip]/128";
1088 push @$mynetworks, "$int_ip/32";
1092 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1093 foreach my $cidr (keys %$netlist) {
1094 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1095 push @$mynetworks, "[$1]/$2";
1097 push @$mynetworks, $cidr;
1101 push @$mynetworks, @$transportnets;
1103 # add default relay to mynetworks
1104 if (my $relay = $self->get('mail', 'relay')) {
1105 if ($relay =~ m/^$IPV4RE$/) {
1106 push @$mynetworks, "$relay/32";
1107 } elsif ($relay =~ m/^$IPV6RE$/) {
1108 push @$mynetworks, "[$relay]/128";
1110 # DNS name - do nothing ?
1114 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1116 # normalize dnsbl_sites
1117 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1118 if (scalar(@dnsbl_sites)) {
1119 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1122 $vars->{postfix
}->{dnsbl_threshold
} = $self->get('mail', 'dnsbl_threshold');
1125 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1126 $self->get('mail', 'spf');
1127 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1129 if ($int_ip =~ m/^$IPV6RE$/) {
1130 $vars->{postfix
}->{int_ip
} = "[$int_ip]";
1132 $vars->{postfix
}->{int_ip
} = $int_ip;
1135 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1136 $vars->{dns
}->{hostname
} = $nodename;
1138 my $domain = $resolv->{search
} // 'localdomain';
1139 $vars->{dns
}->{domain
} = $domain;
1141 my $wlbr = "$nodename.$domain";
1142 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1145 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1147 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1149 my $uri = URI-
>new($proxy);
1150 my $host = $uri->host;
1151 my $port = $uri->port // 8080;
1153 my $data = { host
=> $host, port
=> $port };
1154 if (my $ui = $uri->userinfo) {
1155 my ($username, $pw) = split(/:/, $ui, 2);
1156 $data->{username
} = $username;
1157 $data->{password
} = $pw if defined($pw);
1159 $vars->{proxy
} = $data;
1162 warn "parse http_proxy failed - $@" if $@;
1168 # use one global TT cache
1169 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1171 my $template_toolkit;
1173 sub get_template_toolkit
{
1175 return $template_toolkit if $template_toolkit;
1177 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1179 return $template_toolkit;
1182 # rewrite file from template
1183 # return true if file has changed
1184 sub rewrite_config_file
{
1185 my ($self, $tmplname, $dstfn) = @_;
1187 my $demo = $self->get('admin', 'demo');
1190 my $demosrc = "$tmplname.demo";
1191 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1194 my ($perm, $uid, $gid);
1196 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1197 # needed if file contains a HTTPProxyPasswort
1199 $uid = getpwnam('clamav');
1200 $gid = getgrnam('adm');
1204 my $tt = get_template_toolkit
();
1206 my $vars = $self->get_template_vars();
1210 $tt->process($tmplname, $vars, \
$output) ||
1211 die $tt->error() . "\n";
1213 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1215 return 0 if defined($old) && ($old eq $output); # no change
1217 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1219 if (defined($uid) && defined($gid)) {
1220 chown($uid, $gid, $dstfn);
1226 # rewrite spam configuration
1227 sub rewrite_config_spam
{
1230 my $use_awl = $self->get('spam', 'use_awl');
1231 my $use_bayes = $self->get('spam', 'use_bayes');
1232 my $use_razor = $self->get('spam', 'use_razor');
1236 # delete AW and bayes databases if those features are disabled
1238 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1242 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1243 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1244 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1247 # make sure we have a custom.cf file (else cluster sync fails)
1248 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1250 $changes = 1 if $self->rewrite_config_file(
1251 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1253 $changes = 1 if $self->rewrite_config_file(
1254 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1256 $changes = 1 if $self->rewrite_config_file(
1257 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1259 $changes = 1 if $self->rewrite_config_file(
1260 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1263 mkdir "/root/.razor";
1265 $changes = 1 if $self->rewrite_config_file(
1266 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1268 if (! -e
'/root/.razor/identity') {
1271 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1272 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1275 syslog
('info', "registering razor failed: $err") if $err;
1282 # rewrite ClamAV configuration
1283 sub rewrite_config_clam
{
1286 return $self->rewrite_config_file(
1287 'clamd.conf.in', '/etc/clamav/clamd.conf');
1290 sub rewrite_config_freshclam
{
1293 return $self->rewrite_config_file(
1294 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1297 sub rewrite_config_postgres
{
1300 my $pgconfdir = "/etc/postgresql/9.6/main";
1304 $changes = 1 if $self->rewrite_config_file(
1305 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1307 $changes = 1 if $self->rewrite_config_file(
1308 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1313 # rewrite /root/.forward
1314 sub rewrite_dot_forward
{
1317 my $dstfn = '/root/.forward';
1319 my $email = $self->get('admin', 'email');
1322 if ($email && $email =~ m/\s*(\S+)\s*/) {
1325 # empty .forward does not forward mails (see man local)
1328 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1330 return 0 if defined($old) && ($old eq $output); # no change
1332 PVE
::Tools
::file_set_contents
($dstfn, $output);
1337 my $write_smtp_whitelist = sub {
1338 my ($filename, $data, $action) = @_;
1340 $action = 'OK' if !$action;
1342 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1346 foreach my $k (sort keys %$data) {
1347 $new .= "$k $action\n";
1350 return 0 if defined($old) && ($old eq $new); # no change
1352 PVE
::Tools
::file_set_contents
($filename, $new);
1354 PMG
::Utils
::run_postmap
($filename);
1359 sub rewrite_postfix_whitelist
{
1360 my ($rulecache) = @_;
1362 # see man page for regexp_table for postfix regex table format
1364 # we use a hash to avoid duplicate entries in regex tables
1367 my $clientlist = {};
1369 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1370 my $oclass = ref($obj);
1371 if ($oclass eq 'PMG::RuleDB::Receiver') {
1372 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1373 $tolist->{"/^$addr\$/"} = 1;
1374 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1375 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1376 $tolist->{"/^.+\@$addr\$/"} = 1;
1377 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1378 my $addr = $obj->{address
};
1380 $tolist->{"/^$addr\$/"} = 1;
1384 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1385 my $oclass = ref($obj);
1386 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1387 if ($oclass eq 'PMG::RuleDB::EMail') {
1388 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1389 $fromlist->{"/^$addr\$/"} = 1;
1390 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1391 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1392 $fromlist->{"/^.+\@$addr\$/"} = 1;
1393 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1394 my $addr = $obj->{address
};
1396 $fromlist->{"/^$addr\$/"} = 1;
1397 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1398 $clientlist->{$obj->{address
}} = 1;
1399 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1400 $clientlist->{$obj->{address
}} = 1;
1404 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1405 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1406 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1407 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1410 # rewrite /etc/postfix/*
1411 sub rewrite_config_postfix
{
1412 my ($self, $rulecache) = @_;
1414 # make sure we have required files (else postfix start fails)
1415 IO
::File-
>new($transport_map_filename, 'a', 0644);
1419 if ($self->get('mail', 'tls')) {
1421 PMG
::Utils
::gen_proxmox_tls_cert
();
1423 syslog
('info', "generating certificate failed: $@") if $@;
1426 $changes = 1 if $self->rewrite_config_file(
1427 'main.cf.in', '/etc/postfix/main.cf');
1429 $changes = 1 if $self->rewrite_config_file(
1430 'master.cf.in', '/etc/postfix/master.cf');
1432 # make sure we have required files (else postfix start fails)
1433 # Note: postmap need a valid /etc/postfix/main.cf configuration
1434 postmap_pmg_domains
();
1435 postmap_pmg_transport
();
1436 postmap_tls_policy
();
1438 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1440 # make sure aliases.db is up to date
1441 system('/usr/bin/newaliases');
1446 sub rewrite_config
{
1447 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1449 $force_restart = {} if ! $force_restart;
1451 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1452 $force_restart->{postfix
}) {
1453 PMG
::Utils
::service_cmd
('postfix', 'restart');
1456 if ($self->rewrite_dot_forward() && $restart_services) {
1457 # no need to restart anything
1460 if ($self->rewrite_config_postgres() && $restart_services) {
1461 # do nothing (too many side effects)?
1462 # does not happen anyways, because config does not change.
1465 if (($self->rewrite_config_spam() && $restart_services) ||
1466 $force_restart->{spam
}) {
1467 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1470 if (($self->rewrite_config_clam() && $restart_services) ||
1471 $force_restart->{clam
}) {
1472 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1475 if (($self->rewrite_config_freshclam() && $restart_services) ||
1476 $force_restart->{freshclam
}) {
1477 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');