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
=> "ESMTP banner.",
474 default => 'ESMTP Proxmox',
477 description
=> "Maximum number of pmg-smtp-filter processes.",
481 default => get_max_filters
(),
484 description
=> "Maximum number of pmgpolicy processes.",
488 default => get_max_policy
(),
491 description
=> "Maximum number of SMTP daemon processes (in).",
495 default => get_max_smtpd
(),
498 description
=> "Maximum number of SMTP daemon processes (out).",
502 default => get_max_smtpd
(),
504 conn_count_limit
=> {
505 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
511 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.",
516 message_rate_limit
=> {
517 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.",
523 description
=> "Hide received header in outgoing mails.",
528 description
=> "Maximum email size. Larger mails are rejected.",
531 default => 1024*1024*10,
534 description
=> "SMTP delay warning time (in hours).",
540 description
=> "Enable TLS.",
545 description
=> "Enable TLS Logging.",
550 description
=> "Add TLS received header.",
555 description
=> "Use Sender Policy Framework.",
560 description
=> "Use Greylisting.",
565 description
=> "Use SMTP HELO tests.",
570 description
=> "Reject unknown clients.",
574 rejectunknownsender
=> {
575 description
=> "Reject unknown senders.",
580 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
582 enum
=> ['450', '550'],
585 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
586 type
=> 'string', format
=> 'dnsbl-entry-list',
589 description
=> "The inclusive lower bound for blocking a remote SMTP client, based on its combined DNSBL score (see postscreen_dnsbl_threshold parameter).",
599 int_port
=> { optional
=> 1 },
600 ext_port
=> { optional
=> 1 },
601 smarthost
=> { optional
=> 1 },
602 relay
=> { optional
=> 1 },
603 relayport
=> { optional
=> 1 },
604 relaynomx
=> { optional
=> 1 },
605 dwarning
=> { optional
=> 1 },
606 max_smtpd_in
=> { optional
=> 1 },
607 max_smtpd_out
=> { optional
=> 1 },
608 greylist
=> { optional
=> 1 },
609 helotests
=> { optional
=> 1 },
610 tls
=> { optional
=> 1 },
611 tlslog
=> { optional
=> 1 },
612 tlsheader
=> { optional
=> 1 },
613 spf
=> { optional
=> 1 },
614 maxsize
=> { optional
=> 1 },
615 banner
=> { optional
=> 1 },
616 max_filters
=> { optional
=> 1 },
617 max_policy
=> { optional
=> 1 },
618 hide_received
=> { optional
=> 1 },
619 rejectunknown
=> { optional
=> 1 },
620 rejectunknownsender
=> { optional
=> 1 },
621 conn_count_limit
=> { optional
=> 1 },
622 conn_rate_limit
=> { optional
=> 1 },
623 message_rate_limit
=> { optional
=> 1 },
624 verifyreceivers
=> { optional
=> 1 },
625 dnsbl_sites
=> { optional
=> 1 },
626 dnsbl_threshold
=> { optional
=> 1 },
639 use PVE
::Tools
qw($IPV4RE $IPV6RE);
645 PMG
::Config
::Admin-
>register();
646 PMG
::Config
::Mail-
>register();
647 PMG
::Config
::SpamQuarantine-
>register();
648 PMG
::Config
::VirusQuarantine-
>register();
649 PMG
::Config
::Spam-
>register();
650 PMG
::Config
::ClamAV-
>register();
652 # initialize all plugins
653 PMG
::Config
::Base-
>init();
655 PVE
::JSONSchema
::register_format
(
656 'transport-domain', \
&pmg_verify_transport_domain
);
658 sub pmg_verify_transport_domain
{
659 my ($name, $noerr) = @_;
661 # like dns-name, but can contain leading dot
662 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
664 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
665 return undef if $noerr;
666 die "value does not look like a valid transport domain\n";
671 PVE
::JSONSchema
::register_format
(
672 'transport-domain-or-email', \
&pmg_verify_transport_domain_or_email
);
674 sub pmg_verify_transport_domain_or_email
{
675 my ($name, $noerr) = @_;
677 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
680 if ($name =~ m/^(?:[^\s\/\
@]+\
@)(${namere
}\
.)*${namere
}$/) {
684 # like dns-name, but can contain leading dot
685 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
686 return undef if $noerr;
687 die "value does not look like a valid transport domain or email address\n";
692 PVE
::JSONSchema
::register_format
(
693 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
695 sub pmg_verify_dnsbl_entry
{
696 my ($name, $noerr) = @_;
698 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
699 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
701 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
702 return undef if $noerr;
703 die "value '$name' does not look like a valid dnsbl entry\n";
711 my $class = ref($type) || $type;
713 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
715 return bless $cfg, $class;
721 PVE
::INotify
::write_file
("pmg.conf", $self);
724 my $lockfile = "/var/lock/pmgconfig.lck";
727 my ($code, $errmsg) = @_;
729 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
731 $errmsg ?
die "$errmsg: $err" : die $err;
737 my ($self, $section, $key, $value) = @_;
739 my $pdata = PMG
::Config
::Base-
>private();
741 my $plugin = $pdata->{plugins
}->{$section};
742 die "no such section '$section'" if !$plugin;
744 if (defined($value)) {
745 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
746 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
747 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
749 if (defined($self->{ids
}->{$section})) {
750 delete $self->{ids
}->{$section}->{$key};
757 # get section value or default
759 my ($self, $section, $key, $nodefault) = @_;
761 my $pdata = PMG
::Config
::Base-
>private();
762 my $pdesc = $pdata->{propertyList
}->{$key};
763 die "no such property '$section/$key'\n"
764 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
765 defined($pdata->{options
}->{$section}->{$key}));
767 if (defined($self->{ids
}->{$section}) &&
768 defined(my $value = $self->{ids
}->{$section}->{$key})) {
772 return undef if $nodefault;
774 return $pdesc->{default};
777 # get a whole section with default value
779 my ($self, $section) = @_;
781 my $pdata = PMG
::Config
::Base-
>private();
782 return undef if !defined($pdata->{options
}->{$section});
786 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
788 my $pdesc = $pdata->{propertyList
}->{$key};
790 if (defined($self->{ids
}->{$section}) &&
791 defined(my $value = $self->{ids
}->{$section}->{$key})) {
792 $res->{$key} = $value;
795 $res->{$key} = $pdesc->{default};
801 # get a whole config with default values
805 my $pdata = PMG
::Config
::Base-
>private();
809 foreach my $type (keys %{$pdata->{plugins
}}) {
810 my $plugin = $pdata->{plugins
}->{$type};
811 $res->{$type} = $self->get_section($type);
818 my ($filename, $fh) = @_;
820 local $/ = undef; # slurp mode
822 my $raw = <$fh> if defined($fh);
824 return PMG
::Config
::Base-
>parse_config($filename, $raw);
828 my ($filename, $fh, $cfg) = @_;
830 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
832 PVE
::Tools
::safe_print
($filename, $fh, $raw);
835 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
838 undef, always_call_parser
=> 1);
840 # parsers/writers for other files
842 my $domainsfilename = "/etc/pmg/domains";
844 sub postmap_pmg_domains
{
845 PMG
::Utils
::run_postmap
($domainsfilename);
848 sub read_pmg_domains
{
849 my ($filename, $fh) = @_;
855 while (defined(my $line = <$fh>)) {
857 next if $line =~ m/^\s*$/;
858 if ($line =~ m/^#(.*)\s*$/) {
862 if ($line =~ m/^(\S+)\s.*$/) {
864 $domains->{$domain} = {
865 domain
=> $domain, comment
=> $comment };
868 warn "parse error in '$filename': $line\n";
877 sub write_pmg_domains
{
878 my ($filename, $fh, $domains) = @_;
880 foreach my $domain (sort keys %$domains) {
881 my $comment = $domains->{$domain}->{comment
};
882 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
883 if defined($comment) && $comment !~ m/^\s*$/;
885 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
889 PVE
::INotify
::register_file
('domains', $domainsfilename,
892 undef, always_call_parser
=> 1);
894 my $mynetworks_filename = "/etc/pmg/mynetworks";
896 sub read_pmg_mynetworks
{
897 my ($filename, $fh) = @_;
903 while (defined(my $line = <$fh>)) {
905 next if $line =~ m/^\s*$/;
906 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
907 my ($network, $prefix_size, $comment) = ($1, $2, $3);
908 my $cidr = "$network/${prefix_size}";
909 $mynetworks->{$cidr} = {
911 network_address
=> $network,
912 prefix_size
=> $prefix_size,
913 comment
=> $comment // '',
916 warn "parse error in '$filename': $line\n";
924 sub write_pmg_mynetworks
{
925 my ($filename, $fh, $mynetworks) = @_;
927 foreach my $cidr (sort keys %$mynetworks) {
928 my $data = $mynetworks->{$cidr};
929 my $comment = $data->{comment
} // '*';
930 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
934 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
935 \
&read_pmg_mynetworks
,
936 \
&write_pmg_mynetworks
,
937 undef, always_call_parser
=> 1);
939 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
941 sub postmap_tls_policy
{
942 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
945 my $transport_map_filename = "/etc/pmg/transport";
947 sub postmap_pmg_transport
{
948 PMG
::Utils
::run_postmap
($transport_map_filename);
951 sub read_transport_map
{
952 my ($filename, $fh) = @_;
954 return [] if !defined($fh);
960 while (defined(my $line = <$fh>)) {
962 next if $line =~ m/^\s*$/;
963 if ($line =~ m/^#(.*)\s*$/) {
968 my $parse_error = sub {
970 warn "parse error in '$filename': $line - $err";
974 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
975 my ($domain, $host, $port) = ($1, $2, $3);
977 eval { pmg_verify_transport_domain_or_email
($domain); };
979 $parse_error->($err);
983 if ($host =~ m/^\[(.*)\]$/) {
988 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
990 $parse_error->($err);
1001 $res->{$domain} = $data;
1004 $parse_error->('wrong format');
1011 sub write_transport_map
{
1012 my ($filename, $fh, $tmap) = @_;
1016 foreach my $domain (sort keys %$tmap) {
1017 my $data = $tmap->{$domain};
1019 my $comment = $data->{comment
};
1020 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
1021 if defined($comment) && $comment !~ m/^\s*$/;
1023 my $use_mx = $data->{use_mx
};
1024 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1027 PVE
::Tools
::safe_print
(
1028 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1030 PVE
::Tools
::safe_print
(
1031 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1036 PVE
::INotify
::register_file
('transport', $transport_map_filename,
1037 \
&read_transport_map
,
1038 \
&write_transport_map
,
1039 undef, always_call_parser
=> 1);
1041 # config file generation using templates
1043 sub get_template_vars
{
1046 my $vars = { pmg
=> $self->get_config() };
1048 my $nodename = PVE
::INotify
::nodename
();
1049 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
1050 $vars->{ipconfig
}->{int_ip
} = $int_ip;
1052 my $transportnets = [];
1054 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1055 foreach my $domain (sort keys %$tmap) {
1056 my $data = $tmap->{$domain};
1057 my $host = $data->{host
};
1058 if ($host =~ m/^$IPV4RE$/) {
1059 push @$transportnets, "$host/32";
1060 } elsif ($host =~ m/^$IPV6RE$/) {
1061 push @$transportnets, "[$host]/128";
1066 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1068 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1070 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1071 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1072 push @$mynetworks, "[$1]/$2";
1074 push @$mynetworks, $int_net_cidr;
1077 if ($int_ip =~ m/^$IPV6RE$/) {
1078 push @$mynetworks, "[$int_ip]/128";
1080 push @$mynetworks, "$int_ip/32";
1084 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1085 foreach my $cidr (keys %$netlist) {
1086 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1087 push @$mynetworks, "[$1]/$2";
1089 push @$mynetworks, $cidr;
1093 push @$mynetworks, @$transportnets;
1095 # add default relay to mynetworks
1096 if (my $relay = $self->get('mail', 'relay')) {
1097 if ($relay =~ m/^$IPV4RE$/) {
1098 push @$mynetworks, "$relay/32";
1099 } elsif ($relay =~ m/^$IPV6RE$/) {
1100 push @$mynetworks, "[$relay]/128";
1102 # DNS name - do nothing ?
1106 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1108 # normalize dnsbl_sites
1109 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1110 if (scalar(@dnsbl_sites)) {
1111 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1114 $vars->{postfix
}->{dnsbl_threshold
} = $self->get('mail', 'dnsbl_threshold');
1117 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1118 $self->get('mail', 'spf');
1119 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1121 if ($int_ip =~ m/^$IPV6RE$/) {
1122 $vars->{postfix
}->{int_ip
} = "[$int_ip]";
1124 $vars->{postfix
}->{int_ip
} = $int_ip;
1127 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1128 $vars->{dns
}->{hostname
} = $nodename;
1130 my $domain = $resolv->{search
} // 'localdomain';
1131 $vars->{dns
}->{domain
} = $domain;
1133 my $wlbr = "$nodename.$domain";
1134 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1137 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1139 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1141 my $uri = URI-
>new($proxy);
1142 my $host = $uri->host;
1143 my $port = $uri->port // 8080;
1145 my $data = { host
=> $host, port
=> $port };
1146 if (my $ui = $uri->userinfo) {
1147 my ($username, $pw) = split(/:/, $ui, 2);
1148 $data->{username
} = $username;
1149 $data->{password
} = $pw if defined($pw);
1151 $vars->{proxy
} = $data;
1154 warn "parse http_proxy failed - $@" if $@;
1160 # use one global TT cache
1161 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1163 my $template_toolkit;
1165 sub get_template_toolkit
{
1167 return $template_toolkit if $template_toolkit;
1169 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1171 return $template_toolkit;
1174 # rewrite file from template
1175 # return true if file has changed
1176 sub rewrite_config_file
{
1177 my ($self, $tmplname, $dstfn) = @_;
1179 my $demo = $self->get('admin', 'demo');
1182 my $demosrc = "$tmplname.demo";
1183 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1186 my ($perm, $uid, $gid);
1188 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1189 # needed if file contains a HTTPProxyPasswort
1191 $uid = getpwnam('clamav');
1192 $gid = getgrnam('adm');
1196 my $tt = get_template_toolkit
();
1198 my $vars = $self->get_template_vars();
1202 $tt->process($tmplname, $vars, \
$output) ||
1203 die $tt->error() . "\n";
1205 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1207 return 0 if defined($old) && ($old eq $output); # no change
1209 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1211 if (defined($uid) && defined($gid)) {
1212 chown($uid, $gid, $dstfn);
1218 # rewrite spam configuration
1219 sub rewrite_config_spam
{
1222 my $use_awl = $self->get('spam', 'use_awl');
1223 my $use_bayes = $self->get('spam', 'use_bayes');
1224 my $use_razor = $self->get('spam', 'use_razor');
1228 # delete AW and bayes databases if those features are disabled
1230 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1234 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1235 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1236 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1239 # make sure we have a custom.cf file (else cluster sync fails)
1240 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1242 $changes = 1 if $self->rewrite_config_file(
1243 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1245 $changes = 1 if $self->rewrite_config_file(
1246 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1248 $changes = 1 if $self->rewrite_config_file(
1249 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1251 $changes = 1 if $self->rewrite_config_file(
1252 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1255 mkdir "/root/.razor";
1257 $changes = 1 if $self->rewrite_config_file(
1258 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1260 if (! -e
'/root/.razor/identity') {
1263 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1264 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1267 syslog
('info', "registering razor failed: $err") if $err;
1274 # rewrite ClamAV configuration
1275 sub rewrite_config_clam
{
1278 return $self->rewrite_config_file(
1279 'clamd.conf.in', '/etc/clamav/clamd.conf');
1282 sub rewrite_config_freshclam
{
1285 return $self->rewrite_config_file(
1286 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1289 sub rewrite_config_postgres
{
1292 my $pgconfdir = "/etc/postgresql/9.6/main";
1296 $changes = 1 if $self->rewrite_config_file(
1297 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1299 $changes = 1 if $self->rewrite_config_file(
1300 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1305 # rewrite /root/.forward
1306 sub rewrite_dot_forward
{
1309 my $dstfn = '/root/.forward';
1311 my $email = $self->get('admin', 'email');
1314 if ($email && $email =~ m/\s*(\S+)\s*/) {
1317 # empty .forward does not forward mails (see man local)
1320 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1322 return 0 if defined($old) && ($old eq $output); # no change
1324 PVE
::Tools
::file_set_contents
($dstfn, $output);
1329 my $write_smtp_whitelist = sub {
1330 my ($filename, $data, $action) = @_;
1332 $action = 'OK' if !$action;
1334 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1338 foreach my $k (sort keys %$data) {
1339 $new .= "$k $action\n";
1342 return 0 if defined($old) && ($old eq $new); # no change
1344 PVE
::Tools
::file_set_contents
($filename, $new);
1346 PMG
::Utils
::run_postmap
($filename);
1351 sub rewrite_postfix_whitelist
{
1352 my ($rulecache) = @_;
1354 # see man page for regexp_table for postfix regex table format
1356 # we use a hash to avoid duplicate entries in regex tables
1359 my $clientlist = {};
1361 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1362 my $oclass = ref($obj);
1363 if ($oclass eq 'PMG::RuleDB::Receiver') {
1364 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1365 $tolist->{"/^$addr\$/"} = 1;
1366 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1367 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1368 $tolist->{"/^.+\@$addr\$/"} = 1;
1369 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1370 my $addr = $obj->{address
};
1372 $tolist->{"/^$addr\$/"} = 1;
1376 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1377 my $oclass = ref($obj);
1378 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1379 if ($oclass eq 'PMG::RuleDB::EMail') {
1380 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1381 $fromlist->{"/^$addr\$/"} = 1;
1382 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1383 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1384 $fromlist->{"/^.+\@$addr\$/"} = 1;
1385 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1386 my $addr = $obj->{address
};
1388 $fromlist->{"/^$addr\$/"} = 1;
1389 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1390 $clientlist->{$obj->{address
}} = 1;
1391 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1392 $clientlist->{$obj->{address
}} = 1;
1396 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1397 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1398 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1399 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1402 # rewrite /etc/postfix/*
1403 sub rewrite_config_postfix
{
1404 my ($self, $rulecache) = @_;
1406 # make sure we have required files (else postfix start fails)
1407 IO
::File-
>new($transport_map_filename, 'a', 0644);
1411 if ($self->get('mail', 'tls')) {
1413 PMG
::Utils
::gen_proxmox_tls_cert
();
1415 syslog
('info', "generating certificate failed: $@") if $@;
1418 $changes = 1 if $self->rewrite_config_file(
1419 'main.cf.in', '/etc/postfix/main.cf');
1421 $changes = 1 if $self->rewrite_config_file(
1422 'master.cf.in', '/etc/postfix/master.cf');
1424 # make sure we have required files (else postfix start fails)
1425 # Note: postmap need a valid /etc/postfix/main.cf configuration
1426 postmap_pmg_domains
();
1427 postmap_pmg_transport
();
1428 postmap_tls_policy
();
1430 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1432 # make sure aliases.db is up to date
1433 system('/usr/bin/newaliases');
1438 sub rewrite_config
{
1439 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1441 $force_restart = {} if ! $force_restart;
1443 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1444 $force_restart->{postfix
}) {
1445 PMG
::Utils
::service_cmd
('postfix', 'restart');
1448 if ($self->rewrite_dot_forward() && $restart_services) {
1449 # no need to restart anything
1452 if ($self->rewrite_config_postgres() && $restart_services) {
1453 # do nothing (too many side effects)?
1454 # does not happen anyways, because config does not change.
1457 if (($self->rewrite_config_spam() && $restart_services) ||
1458 $force_restart->{spam
}) {
1459 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1462 if (($self->rewrite_config_clam() && $restart_services) ||
1463 $force_restart->{clam
}) {
1464 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1467 if (($self->rewrite_config_freshclam() && $restart_services) ||
1468 $force_restart->{freshclam
}) {
1469 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');