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.",
105 advfilter
=> { optional
=> 1 },
106 avast
=> { optional
=> 1 },
107 statlifetime
=> { optional
=> 1 },
108 dailyreport
=> { optional
=> 1 },
109 demo
=> { optional
=> 1 },
110 email
=> { optional
=> 1 },
111 http_proxy
=> { optional
=> 1 },
115 package PMG
::Config
::Spam
;
120 use base
qw(PMG::Config::Base);
129 description
=> "This option is used to specify which languages are considered OK for incoming mail.",
131 pattern
=> '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
135 description
=> "Whether to use the naive-Bayesian-style classifier.",
140 description
=> "Use the Auto-Whitelist plugin.",
145 description
=> "Whether to use Razor2, if it is available.",
149 wl_bounce_relays
=> {
150 description
=> "Whitelist legitimate bounce relays.",
153 clamav_heuristic_score
=> {
154 description
=> "Score for ClamaAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
161 description
=> "Additional score for bounce mails.",
168 description
=> "Enable real time blacklists (RBL) checks.",
173 description
=> "Maximum size of spam messages in bytes.",
183 use_awl
=> { optional
=> 1 },
184 use_razor
=> { optional
=> 1 },
185 wl_bounce_relays
=> { optional
=> 1 },
186 languages
=> { optional
=> 1 },
187 use_bayes
=> { optional
=> 1 },
188 clamav_heuristic_score
=> { optional
=> 1 },
189 bounce_score
=> { optional
=> 1 },
190 rbl_checks
=> { optional
=> 1 },
191 maxspamsize
=> { optional
=> 1 },
195 package PMG
::Config
::SpamQuarantine
;
200 use base
qw(PMG::Config::Base);
209 description
=> "Quarantine life time (days)",
215 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.",
217 enum
=> [qw(ticket ldap ldapticket)],
221 description
=> "Spam report style.",
223 enum
=> [qw(none short verbose custom)],
224 default => 'verbose',
227 description
=> "Allow to view images.",
232 description
=> "Allow to view hyperlinks.",
237 description
=> "Quarantine Host. Useful if you run a Cluster and want users to connect to a specific host.",
238 type
=> 'string', format
=> 'address',
241 description
=> "Quarantine Port. Useful if you have a reverse proxy or port forwarding for the webinterface. Only used for the generated Spam report.",
248 description
=> "Quarantine Webinterface Protocol. Useful if you have a reverse proxy for the webinterface. Only used for the generated Spam report.",
250 enum
=> [qw(http https)],
254 description
=> "Text for 'From' header in daily spam report mails.",
262 mailfrom
=> { optional
=> 1 },
263 hostname
=> { optional
=> 1 },
264 lifetime
=> { optional
=> 1 },
265 authmode
=> { optional
=> 1 },
266 reportstyle
=> { optional
=> 1 },
267 viewimages
=> { optional
=> 1 },
268 allowhrefs
=> { optional
=> 1 },
269 port
=> { optional
=> 1 },
270 protocol
=> { optional
=> 1 },
274 package PMG
::Config
::VirusQuarantine
;
279 use base
qw(PMG::Config::Base);
291 lifetime
=> { optional
=> 1 },
292 viewimages
=> { optional
=> 1 },
293 allowhrefs
=> { optional
=> 1 },
297 package PMG
::Config
::ClamAV
;
302 use base
qw(PMG::Config::Base);
311 description
=> "ClamAV database mirror server.",
313 default => 'database.clamav.net',
315 archiveblockencrypted
=> {
316 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
321 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.",
327 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.",
333 description
=> "Files larger than this limit won't be scanned.",
339 description
=> "Sets the maximum amount of data to be scanned for each input file.",
342 default => 100000000,
345 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
351 description
=> "Enables support for Google Safe Browsing.",
360 archiveblockencrypted
=> { optional
=> 1 },
361 archivemaxrec
=> { optional
=> 1 },
362 archivemaxfiles
=> { optional
=> 1 },
363 archivemaxsize
=> { optional
=> 1 },
364 maxscansize
=> { optional
=> 1 },
365 dbmirror
=> { optional
=> 1 },
366 maxcccount
=> { optional
=> 1 },
367 safebrowsing
=> { optional
=> 1 },
371 package PMG
::Config
::Mail
;
376 use PVE
::ProcFSTools
;
378 use base
qw(PMG::Config::Base);
385 sub physical_memory
{
387 return $physicalmem if $physicalmem;
389 my $info = PVE
::ProcFSTools
::read_meminfo
();
390 my $total = int($info->{memtotal
} / (1024*1024));
395 sub get_max_filters
{
396 # estimate optimal number of filter servers
400 my $memory = physical_memory
();
401 my $add_servers = int(($memory - 512)/$servermem);
402 $max_servers += $add_servers if $add_servers > 0;
403 $max_servers = 40 if $max_servers > 40;
405 return $max_servers - 2;
409 # estimate optimal number of smtpd daemons
411 my $max_servers = 25;
413 my $memory = physical_memory
();
414 my $add_servers = int(($memory - 512)/$servermem);
415 $max_servers += $add_servers if $add_servers > 0;
416 $max_servers = 100 if $max_servers > 100;
421 # estimate optimal number of proxpolicy servers
423 my $memory = physical_memory
();
424 $max_servers = 5 if $memory >= 500;
431 description
=> "SMTP port number for outgoing mail (trusted).",
438 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
445 description
=> "The default mail delivery transport (incoming mails).",
446 type
=> 'string', format
=> 'address',
449 description
=> "SMTP port number for relay host.",
456 description
=> "Disable MX lookups for default relay.",
461 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
462 type
=> 'string', format
=> 'address',
465 description
=> "ESMTP banner.",
468 default => 'ESMTP Proxmox',
471 description
=> "Maximum number of pmg-smtp-filter processes.",
475 default => get_max_filters
(),
478 description
=> "Maximum number of pmgpolicy processes.",
482 default => get_max_policy
(),
485 description
=> "Maximum number of SMTP daemon processes (in).",
489 default => get_max_smtpd
(),
492 description
=> "Maximum number of SMTP daemon processes (out).",
496 default => get_max_smtpd
(),
498 conn_count_limit
=> {
499 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
505 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.",
510 message_rate_limit
=> {
511 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.",
517 description
=> "Hide received header in outgoing mails.",
522 description
=> "Maximum email size. Larger mails are rejected.",
525 default => 1024*1024*10,
528 description
=> "SMTP delay warning time (in hours).",
534 description
=> "Enable TLS.",
539 description
=> "Enable TLS Logging.",
544 description
=> "Add TLS received header.",
549 description
=> "Use Sender Policy Framework.",
554 description
=> "Use Greylisting.",
559 description
=> "Use SMTP HELO tests.",
564 description
=> "Reject unknown clients.",
568 rejectunknownsender
=> {
569 description
=> "Reject unknown senders.",
574 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
576 enum
=> ['450', '550'],
579 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
580 type
=> 'string', format
=> 'dnsbl-entry-list',
587 int_port
=> { optional
=> 1 },
588 ext_port
=> { optional
=> 1 },
589 smarthost
=> { optional
=> 1 },
590 relay
=> { optional
=> 1 },
591 relayport
=> { optional
=> 1 },
592 relaynomx
=> { optional
=> 1 },
593 dwarning
=> { optional
=> 1 },
594 max_smtpd_in
=> { optional
=> 1 },
595 max_smtpd_out
=> { optional
=> 1 },
596 greylist
=> { optional
=> 1 },
597 helotests
=> { optional
=> 1 },
598 tls
=> { optional
=> 1 },
599 tlslog
=> { optional
=> 1 },
600 tlsheader
=> { optional
=> 1 },
601 spf
=> { optional
=> 1 },
602 maxsize
=> { optional
=> 1 },
603 banner
=> { optional
=> 1 },
604 max_filters
=> { optional
=> 1 },
605 max_policy
=> { optional
=> 1 },
606 hide_received
=> { optional
=> 1 },
607 rejectunknown
=> { optional
=> 1 },
608 rejectunknownsender
=> { optional
=> 1 },
609 conn_count_limit
=> { optional
=> 1 },
610 conn_rate_limit
=> { optional
=> 1 },
611 message_rate_limit
=> { optional
=> 1 },
612 verifyreceivers
=> { optional
=> 1 },
613 dnsbl_sites
=> { optional
=> 1 },
626 use PVE
::Tools
qw($IPV4RE $IPV6RE);
632 PMG
::Config
::Admin-
>register();
633 PMG
::Config
::Mail-
>register();
634 PMG
::Config
::SpamQuarantine-
>register();
635 PMG
::Config
::VirusQuarantine-
>register();
636 PMG
::Config
::Spam-
>register();
637 PMG
::Config
::ClamAV-
>register();
639 # initialize all plugins
640 PMG
::Config
::Base-
>init();
642 PVE
::JSONSchema
::register_format
(
643 'transport-domain', \
&pmg_verify_transport_domain
);
645 sub pmg_verify_transport_domain
{
646 my ($name, $noerr) = @_;
648 # like dns-name, but can contain leading dot
649 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
651 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
652 return undef if $noerr;
653 die "value does not look like a valid transport domain\n";
658 PVE
::JSONSchema
::register_format
(
659 'transport-domain-or-email', \
&pmg_verify_transport_domain_or_email
);
661 sub pmg_verify_transport_domain_or_email
{
662 my ($name, $noerr) = @_;
664 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
667 if ($name =~ m/^(?:[^\s\/\
@]+\
@)(${namere
}\
.)*${namere
}$/) {
671 # like dns-name, but can contain leading dot
672 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
673 return undef if $noerr;
674 die "value does not look like a valid transport domain or email address\n";
679 PVE
::JSONSchema
::register_format
(
680 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
682 sub pmg_verify_dnsbl_entry
{
683 my ($name, $noerr) = @_;
685 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
686 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
688 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
689 return undef if $noerr;
690 die "value '$name' does not look like a valid dnsbl entry\n";
698 my $class = ref($type) || $type;
700 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
702 return bless $cfg, $class;
708 PVE
::INotify
::write_file
("pmg.conf", $self);
711 my $lockfile = "/var/lock/pmgconfig.lck";
714 my ($code, $errmsg) = @_;
716 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
718 $errmsg ?
die "$errmsg: $err" : die $err;
724 my ($self, $section, $key, $value) = @_;
726 my $pdata = PMG
::Config
::Base-
>private();
728 my $plugin = $pdata->{plugins
}->{$section};
729 die "no such section '$section'" if !$plugin;
731 if (defined($value)) {
732 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
733 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
734 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
736 if (defined($self->{ids
}->{$section})) {
737 delete $self->{ids
}->{$section}->{$key};
744 # get section value or default
746 my ($self, $section, $key, $nodefault) = @_;
748 my $pdata = PMG
::Config
::Base-
>private();
749 my $pdesc = $pdata->{propertyList
}->{$key};
750 die "no such property '$section/$key'\n"
751 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
752 defined($pdata->{options
}->{$section}->{$key}));
754 if (defined($self->{ids
}->{$section}) &&
755 defined(my $value = $self->{ids
}->{$section}->{$key})) {
759 return undef if $nodefault;
761 return $pdesc->{default};
764 # get a whole section with default value
766 my ($self, $section) = @_;
768 my $pdata = PMG
::Config
::Base-
>private();
769 return undef if !defined($pdata->{options
}->{$section});
773 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
775 my $pdesc = $pdata->{propertyList
}->{$key};
777 if (defined($self->{ids
}->{$section}) &&
778 defined(my $value = $self->{ids
}->{$section}->{$key})) {
779 $res->{$key} = $value;
782 $res->{$key} = $pdesc->{default};
788 # get a whole config with default values
792 my $pdata = PMG
::Config
::Base-
>private();
796 foreach my $type (keys %{$pdata->{plugins
}}) {
797 my $plugin = $pdata->{plugins
}->{$type};
798 $res->{$type} = $self->get_section($type);
805 my ($filename, $fh) = @_;
807 local $/ = undef; # slurp mode
809 my $raw = <$fh> if defined($fh);
811 return PMG
::Config
::Base-
>parse_config($filename, $raw);
815 my ($filename, $fh, $cfg) = @_;
817 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
819 PVE
::Tools
::safe_print
($filename, $fh, $raw);
822 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
825 undef, always_call_parser
=> 1);
827 # parsers/writers for other files
829 my $domainsfilename = "/etc/pmg/domains";
831 sub postmap_pmg_domains
{
832 PMG
::Utils
::run_postmap
($domainsfilename);
835 sub read_pmg_domains
{
836 my ($filename, $fh) = @_;
842 while (defined(my $line = <$fh>)) {
844 next if $line =~ m/^\s*$/;
845 if ($line =~ m/^#(.*)\s*$/) {
849 if ($line =~ m/^(\S+)\s.*$/) {
851 $domains->{$domain} = {
852 domain
=> $domain, comment
=> $comment };
855 warn "parse error in '$filename': $line\n";
864 sub write_pmg_domains
{
865 my ($filename, $fh, $domains) = @_;
867 foreach my $domain (sort keys %$domains) {
868 my $comment = $domains->{$domain}->{comment
};
869 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
870 if defined($comment) && $comment !~ m/^\s*$/;
872 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
876 PVE
::INotify
::register_file
('domains', $domainsfilename,
879 undef, always_call_parser
=> 1);
881 my $mynetworks_filename = "/etc/pmg/mynetworks";
883 sub read_pmg_mynetworks
{
884 my ($filename, $fh) = @_;
890 while (defined(my $line = <$fh>)) {
892 next if $line =~ m/^\s*$/;
893 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
894 my ($network, $prefix_size, $comment) = ($1, $2, $3);
895 my $cidr = "$network/${prefix_size}";
896 $mynetworks->{$cidr} = {
898 network_address
=> $network,
899 prefix_size
=> $prefix_size,
900 comment
=> $comment // '',
903 warn "parse error in '$filename': $line\n";
911 sub write_pmg_mynetworks
{
912 my ($filename, $fh, $mynetworks) = @_;
914 foreach my $cidr (sort keys %$mynetworks) {
915 my $data = $mynetworks->{$cidr};
916 my $comment = $data->{comment
} // '*';
917 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
921 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
922 \
&read_pmg_mynetworks
,
923 \
&write_pmg_mynetworks
,
924 undef, always_call_parser
=> 1);
926 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
928 sub postmap_tls_policy
{
929 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
932 my $transport_map_filename = "/etc/pmg/transport";
934 sub postmap_pmg_transport
{
935 PMG
::Utils
::run_postmap
($transport_map_filename);
938 sub read_transport_map
{
939 my ($filename, $fh) = @_;
941 return [] if !defined($fh);
947 while (defined(my $line = <$fh>)) {
949 next if $line =~ m/^\s*$/;
950 if ($line =~ m/^#(.*)\s*$/) {
955 my $parse_error = sub {
957 warn "parse error in '$filename': $line - $err";
961 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
962 my ($domain, $host, $port) = ($1, $2, $3);
964 eval { pmg_verify_transport_domain_or_email
($domain); };
966 $parse_error->($err);
970 if ($host =~ m/^\[(.*)\]$/) {
975 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
977 $parse_error->($err);
988 $res->{$domain} = $data;
991 $parse_error->('wrong format');
998 sub write_transport_map
{
999 my ($filename, $fh, $tmap) = @_;
1003 foreach my $domain (sort keys %$tmap) {
1004 my $data = $tmap->{$domain};
1006 my $comment = $data->{comment
};
1007 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
1008 if defined($comment) && $comment !~ m/^\s*$/;
1010 my $use_mx = $data->{use_mx
};
1011 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1014 PVE
::Tools
::safe_print
(
1015 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1017 PVE
::Tools
::safe_print
(
1018 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1023 PVE
::INotify
::register_file
('transport', $transport_map_filename,
1024 \
&read_transport_map
,
1025 \
&write_transport_map
,
1026 undef, always_call_parser
=> 1);
1028 # config file generation using templates
1030 sub get_template_vars
{
1033 my $vars = { pmg
=> $self->get_config() };
1035 my $nodename = PVE
::INotify
::nodename
();
1036 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
1037 $vars->{ipconfig
}->{int_ip
} = $int_ip;
1039 my $transportnets = [];
1041 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1042 foreach my $domain (sort keys %$tmap) {
1043 my $data = $tmap->{$domain};
1044 my $host = $data->{host
};
1045 if ($host =~ m/^$IPV4RE$/) {
1046 push @$transportnets, "$host/32";
1047 } elsif ($host =~ m/^$IPV6RE$/) {
1048 push @$transportnets, "[$host]/128";
1053 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1055 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1057 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1058 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1059 push @$mynetworks, "[$1]/$2";
1061 push @$mynetworks, $int_net_cidr;
1064 if ($int_ip =~ m/^$IPV6RE$/) {
1065 push @$mynetworks, "[$int_ip]/128";
1067 push @$mynetworks, "$int_ip/32";
1071 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1072 foreach my $cidr (keys %$netlist) {
1073 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1074 push @$mynetworks, "[$1]/$2";
1076 push @$mynetworks, $cidr;
1080 push @$mynetworks, @$transportnets;
1082 # add default relay to mynetworks
1083 if (my $relay = $self->get('mail', 'relay')) {
1084 if ($relay =~ m/^$IPV4RE$/) {
1085 push @$mynetworks, "$relay/32";
1086 } elsif ($relay =~ m/^$IPV6RE$/) {
1087 push @$mynetworks, "[$relay]/128";
1089 # DNS name - do nothing ?
1093 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1095 # normalize dnsbl_sites
1096 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1097 if (scalar(@dnsbl_sites)) {
1098 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1102 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1103 $self->get('mail', 'spf');
1104 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1106 if ($int_ip =~ m/^$IPV6RE$/) {
1107 $vars->{postfix
}->{int_ip
} = "[$int_ip]";
1109 $vars->{postfix
}->{int_ip
} = $int_ip;
1112 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1113 $vars->{dns
}->{hostname
} = $nodename;
1115 my $domain = $resolv->{search
} // 'localdomain';
1116 $vars->{dns
}->{domain
} = $domain;
1118 my $wlbr = "$nodename.$domain";
1119 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1122 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1124 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1126 my $uri = URI-
>new($proxy);
1127 my $host = $uri->host;
1128 my $port = $uri->port // 8080;
1130 my $data = { host
=> $host, port
=> $port };
1131 if (my $ui = $uri->userinfo) {
1132 my ($username, $pw) = split(/:/, $ui, 2);
1133 $data->{username
} = $username;
1134 $data->{password
} = $pw if defined($pw);
1136 $vars->{proxy
} = $data;
1139 warn "parse http_proxy failed - $@" if $@;
1145 # use one global TT cache
1146 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1148 my $template_toolkit;
1150 sub get_template_toolkit
{
1152 return $template_toolkit if $template_toolkit;
1154 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1156 return $template_toolkit;
1159 # rewrite file from template
1160 # return true if file has changed
1161 sub rewrite_config_file
{
1162 my ($self, $tmplname, $dstfn) = @_;
1164 my $demo = $self->get('admin', 'demo');
1167 my $demosrc = "$tmplname.demo";
1168 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1171 my ($perm, $uid, $gid);
1173 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1174 # needed if file contains a HTTPProxyPasswort
1176 $uid = getpwnam('clamav');
1177 $gid = getgrnam('adm');
1181 my $tt = get_template_toolkit
();
1183 my $vars = $self->get_template_vars();
1187 $tt->process($tmplname, $vars, \
$output) ||
1188 die $tt->error() . "\n";
1190 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1192 return 0 if defined($old) && ($old eq $output); # no change
1194 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1196 if (defined($uid) && defined($gid)) {
1197 chown($uid, $gid, $dstfn);
1203 # rewrite spam configuration
1204 sub rewrite_config_spam
{
1207 my $use_awl = $self->get('spam', 'use_awl');
1208 my $use_bayes = $self->get('spam', 'use_bayes');
1209 my $use_razor = $self->get('spam', 'use_razor');
1213 # delete AW and bayes databases if those features are disabled
1215 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1219 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1220 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1221 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1224 # make sure we have a custom.cf file (else cluster sync fails)
1225 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1227 $changes = 1 if $self->rewrite_config_file(
1228 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1230 $changes = 1 if $self->rewrite_config_file(
1231 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1233 $changes = 1 if $self->rewrite_config_file(
1234 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1236 $changes = 1 if $self->rewrite_config_file(
1237 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1240 mkdir "/root/.razor";
1242 $changes = 1 if $self->rewrite_config_file(
1243 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1245 if (! -e
'/root/.razor/identity') {
1248 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1249 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1252 syslog
('info', "registering razor failed: $err") if $err;
1259 # rewrite ClamAV configuration
1260 sub rewrite_config_clam
{
1263 return $self->rewrite_config_file(
1264 'clamd.conf.in', '/etc/clamav/clamd.conf');
1267 sub rewrite_config_freshclam
{
1270 return $self->rewrite_config_file(
1271 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1274 sub rewrite_config_postgres
{
1277 my $pgconfdir = "/etc/postgresql/9.6/main";
1281 $changes = 1 if $self->rewrite_config_file(
1282 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1284 $changes = 1 if $self->rewrite_config_file(
1285 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1290 # rewrite /root/.forward
1291 sub rewrite_dot_forward
{
1294 my $dstfn = '/root/.forward';
1296 my $email = $self->get('admin', 'email');
1299 if ($email && $email =~ m/\s*(\S+)\s*/) {
1302 # empty .forward does not forward mails (see man local)
1305 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1307 return 0 if defined($old) && ($old eq $output); # no change
1309 PVE
::Tools
::file_set_contents
($dstfn, $output);
1314 my $write_smtp_whitelist = sub {
1315 my ($filename, $data, $action) = @_;
1317 $action = 'OK' if !$action;
1319 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1323 foreach my $k (sort keys %$data) {
1324 $new .= "$k $action\n";
1327 return 0 if defined($old) && ($old eq $new); # no change
1329 PVE
::Tools
::file_set_contents
($filename, $new);
1331 PMG
::Utils
::run_postmap
($filename);
1336 sub rewrite_postfix_whitelist
{
1337 my ($rulecache) = @_;
1339 # see man page for regexp_table for postfix regex table format
1341 # we use a hash to avoid duplicate entries in regex tables
1344 my $clientlist = {};
1346 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1347 my $oclass = ref($obj);
1348 if ($oclass eq 'PMG::RuleDB::Receiver') {
1349 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1350 $tolist->{"/^$addr\$/"} = 1;
1351 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1352 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1353 $tolist->{"/^.+\@$addr\$/"} = 1;
1354 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1355 my $addr = $obj->{address
};
1357 $tolist->{"/^$addr\$/"} = 1;
1361 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1362 my $oclass = ref($obj);
1363 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1364 if ($oclass eq 'PMG::RuleDB::EMail') {
1365 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1366 $fromlist->{"/^$addr\$/"} = 1;
1367 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1368 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1369 $fromlist->{"/^.+\@$addr\$/"} = 1;
1370 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1371 my $addr = $obj->{address
};
1373 $fromlist->{"/^$addr\$/"} = 1;
1374 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1375 $clientlist->{$obj->{address
}} = 1;
1376 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1377 $clientlist->{$obj->{address
}} = 1;
1381 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1382 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1383 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1384 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1387 # rewrite /etc/postfix/*
1388 sub rewrite_config_postfix
{
1389 my ($self, $rulecache) = @_;
1391 # make sure we have required files (else postfix start fails)
1392 IO
::File-
>new($transport_map_filename, 'a', 0644);
1396 if ($self->get('mail', 'tls')) {
1398 PMG
::Utils
::gen_proxmox_tls_cert
();
1400 syslog
('info', "generating certificate failed: $@") if $@;
1403 $changes = 1 if $self->rewrite_config_file(
1404 'main.cf.in', '/etc/postfix/main.cf');
1406 $changes = 1 if $self->rewrite_config_file(
1407 'master.cf.in', '/etc/postfix/master.cf');
1409 # make sure we have required files (else postfix start fails)
1410 # Note: postmap need a valid /etc/postfix/main.cf configuration
1411 postmap_pmg_domains
();
1412 postmap_pmg_transport
();
1413 postmap_tls_policy
();
1415 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1417 # make sure aliases.db is up to date
1418 system('/usr/bin/newaliases');
1423 sub rewrite_config
{
1424 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1426 $force_restart = {} if ! $force_restart;
1428 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1429 $force_restart->{postfix
}) {
1430 PMG
::Utils
::service_cmd
('postfix', 'restart');
1433 if ($self->rewrite_dot_forward() && $restart_services) {
1434 # no need to restart anything
1437 if ($self->rewrite_config_postgres() && $restart_services) {
1438 # do nothing (too many side effects)?
1439 # does not happen anyways, because config does not change.
1442 if (($self->rewrite_config_spam() && $restart_services) ||
1443 $force_restart->{spam
}) {
1444 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1447 if (($self->rewrite_config_clam() && $restart_services) ||
1448 $force_restart->{clam
}) {
1449 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1452 if (($self->rewrite_config_freshclam() && $restart_services) ||
1453 $force_restart->{freshclam
}) {
1454 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');