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',
593 int_port
=> { optional
=> 1 },
594 ext_port
=> { optional
=> 1 },
595 smarthost
=> { optional
=> 1 },
596 relay
=> { optional
=> 1 },
597 relayport
=> { optional
=> 1 },
598 relaynomx
=> { optional
=> 1 },
599 dwarning
=> { optional
=> 1 },
600 max_smtpd_in
=> { optional
=> 1 },
601 max_smtpd_out
=> { optional
=> 1 },
602 greylist
=> { optional
=> 1 },
603 helotests
=> { optional
=> 1 },
604 tls
=> { optional
=> 1 },
605 tlslog
=> { optional
=> 1 },
606 tlsheader
=> { optional
=> 1 },
607 spf
=> { optional
=> 1 },
608 maxsize
=> { optional
=> 1 },
609 banner
=> { optional
=> 1 },
610 max_filters
=> { optional
=> 1 },
611 max_policy
=> { optional
=> 1 },
612 hide_received
=> { optional
=> 1 },
613 rejectunknown
=> { optional
=> 1 },
614 rejectunknownsender
=> { optional
=> 1 },
615 conn_count_limit
=> { optional
=> 1 },
616 conn_rate_limit
=> { optional
=> 1 },
617 message_rate_limit
=> { optional
=> 1 },
618 verifyreceivers
=> { optional
=> 1 },
619 dnsbl_sites
=> { optional
=> 1 },
632 use PVE
::Tools
qw($IPV4RE $IPV6RE);
638 PMG
::Config
::Admin-
>register();
639 PMG
::Config
::Mail-
>register();
640 PMG
::Config
::SpamQuarantine-
>register();
641 PMG
::Config
::VirusQuarantine-
>register();
642 PMG
::Config
::Spam-
>register();
643 PMG
::Config
::ClamAV-
>register();
645 # initialize all plugins
646 PMG
::Config
::Base-
>init();
648 PVE
::JSONSchema
::register_format
(
649 'transport-domain', \
&pmg_verify_transport_domain
);
651 sub pmg_verify_transport_domain
{
652 my ($name, $noerr) = @_;
654 # like dns-name, but can contain leading dot
655 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
657 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
658 return undef if $noerr;
659 die "value does not look like a valid transport domain\n";
664 PVE
::JSONSchema
::register_format
(
665 'transport-domain-or-email', \
&pmg_verify_transport_domain_or_email
);
667 sub pmg_verify_transport_domain_or_email
{
668 my ($name, $noerr) = @_;
670 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
673 if ($name =~ m/^(?:[^\s\/\
@]+\
@)(${namere
}\
.)*${namere
}$/) {
677 # like dns-name, but can contain leading dot
678 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
679 return undef if $noerr;
680 die "value does not look like a valid transport domain or email address\n";
685 PVE
::JSONSchema
::register_format
(
686 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
688 sub pmg_verify_dnsbl_entry
{
689 my ($name, $noerr) = @_;
691 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
692 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
694 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
695 return undef if $noerr;
696 die "value '$name' does not look like a valid dnsbl entry\n";
704 my $class = ref($type) || $type;
706 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
708 return bless $cfg, $class;
714 PVE
::INotify
::write_file
("pmg.conf", $self);
717 my $lockfile = "/var/lock/pmgconfig.lck";
720 my ($code, $errmsg) = @_;
722 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
724 $errmsg ?
die "$errmsg: $err" : die $err;
730 my ($self, $section, $key, $value) = @_;
732 my $pdata = PMG
::Config
::Base-
>private();
734 my $plugin = $pdata->{plugins
}->{$section};
735 die "no such section '$section'" if !$plugin;
737 if (defined($value)) {
738 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
739 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
740 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
742 if (defined($self->{ids
}->{$section})) {
743 delete $self->{ids
}->{$section}->{$key};
750 # get section value or default
752 my ($self, $section, $key, $nodefault) = @_;
754 my $pdata = PMG
::Config
::Base-
>private();
755 my $pdesc = $pdata->{propertyList
}->{$key};
756 die "no such property '$section/$key'\n"
757 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
758 defined($pdata->{options
}->{$section}->{$key}));
760 if (defined($self->{ids
}->{$section}) &&
761 defined(my $value = $self->{ids
}->{$section}->{$key})) {
765 return undef if $nodefault;
767 return $pdesc->{default};
770 # get a whole section with default value
772 my ($self, $section) = @_;
774 my $pdata = PMG
::Config
::Base-
>private();
775 return undef if !defined($pdata->{options
}->{$section});
779 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
781 my $pdesc = $pdata->{propertyList
}->{$key};
783 if (defined($self->{ids
}->{$section}) &&
784 defined(my $value = $self->{ids
}->{$section}->{$key})) {
785 $res->{$key} = $value;
788 $res->{$key} = $pdesc->{default};
794 # get a whole config with default values
798 my $pdata = PMG
::Config
::Base-
>private();
802 foreach my $type (keys %{$pdata->{plugins
}}) {
803 my $plugin = $pdata->{plugins
}->{$type};
804 $res->{$type} = $self->get_section($type);
811 my ($filename, $fh) = @_;
813 local $/ = undef; # slurp mode
815 my $raw = <$fh> if defined($fh);
817 return PMG
::Config
::Base-
>parse_config($filename, $raw);
821 my ($filename, $fh, $cfg) = @_;
823 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
825 PVE
::Tools
::safe_print
($filename, $fh, $raw);
828 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
831 undef, always_call_parser
=> 1);
833 # parsers/writers for other files
835 my $domainsfilename = "/etc/pmg/domains";
837 sub postmap_pmg_domains
{
838 PMG
::Utils
::run_postmap
($domainsfilename);
841 sub read_pmg_domains
{
842 my ($filename, $fh) = @_;
848 while (defined(my $line = <$fh>)) {
850 next if $line =~ m/^\s*$/;
851 if ($line =~ m/^#(.*)\s*$/) {
855 if ($line =~ m/^(\S+)\s.*$/) {
857 $domains->{$domain} = {
858 domain
=> $domain, comment
=> $comment };
861 warn "parse error in '$filename': $line\n";
870 sub write_pmg_domains
{
871 my ($filename, $fh, $domains) = @_;
873 foreach my $domain (sort keys %$domains) {
874 my $comment = $domains->{$domain}->{comment
};
875 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
876 if defined($comment) && $comment !~ m/^\s*$/;
878 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
882 PVE
::INotify
::register_file
('domains', $domainsfilename,
885 undef, always_call_parser
=> 1);
887 my $mynetworks_filename = "/etc/pmg/mynetworks";
889 sub read_pmg_mynetworks
{
890 my ($filename, $fh) = @_;
896 while (defined(my $line = <$fh>)) {
898 next if $line =~ m/^\s*$/;
899 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
900 my ($network, $prefix_size, $comment) = ($1, $2, $3);
901 my $cidr = "$network/${prefix_size}";
902 $mynetworks->{$cidr} = {
904 network_address
=> $network,
905 prefix_size
=> $prefix_size,
906 comment
=> $comment // '',
909 warn "parse error in '$filename': $line\n";
917 sub write_pmg_mynetworks
{
918 my ($filename, $fh, $mynetworks) = @_;
920 foreach my $cidr (sort keys %$mynetworks) {
921 my $data = $mynetworks->{$cidr};
922 my $comment = $data->{comment
} // '*';
923 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
927 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
928 \
&read_pmg_mynetworks
,
929 \
&write_pmg_mynetworks
,
930 undef, always_call_parser
=> 1);
932 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
934 sub postmap_tls_policy
{
935 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
938 my $transport_map_filename = "/etc/pmg/transport";
940 sub postmap_pmg_transport
{
941 PMG
::Utils
::run_postmap
($transport_map_filename);
944 sub read_transport_map
{
945 my ($filename, $fh) = @_;
947 return [] if !defined($fh);
953 while (defined(my $line = <$fh>)) {
955 next if $line =~ m/^\s*$/;
956 if ($line =~ m/^#(.*)\s*$/) {
961 my $parse_error = sub {
963 warn "parse error in '$filename': $line - $err";
967 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
968 my ($domain, $host, $port) = ($1, $2, $3);
970 eval { pmg_verify_transport_domain_or_email
($domain); };
972 $parse_error->($err);
976 if ($host =~ m/^\[(.*)\]$/) {
981 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
983 $parse_error->($err);
994 $res->{$domain} = $data;
997 $parse_error->('wrong format');
1004 sub write_transport_map
{
1005 my ($filename, $fh, $tmap) = @_;
1009 foreach my $domain (sort keys %$tmap) {
1010 my $data = $tmap->{$domain};
1012 my $comment = $data->{comment
};
1013 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
1014 if defined($comment) && $comment !~ m/^\s*$/;
1016 my $use_mx = $data->{use_mx
};
1017 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1020 PVE
::Tools
::safe_print
(
1021 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1023 PVE
::Tools
::safe_print
(
1024 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1029 PVE
::INotify
::register_file
('transport', $transport_map_filename,
1030 \
&read_transport_map
,
1031 \
&write_transport_map
,
1032 undef, always_call_parser
=> 1);
1034 # config file generation using templates
1036 sub get_template_vars
{
1039 my $vars = { pmg
=> $self->get_config() };
1041 my $nodename = PVE
::INotify
::nodename
();
1042 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
1043 $vars->{ipconfig
}->{int_ip
} = $int_ip;
1045 my $transportnets = [];
1047 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1048 foreach my $domain (sort keys %$tmap) {
1049 my $data = $tmap->{$domain};
1050 my $host = $data->{host
};
1051 if ($host =~ m/^$IPV4RE$/) {
1052 push @$transportnets, "$host/32";
1053 } elsif ($host =~ m/^$IPV6RE$/) {
1054 push @$transportnets, "[$host]/128";
1059 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1061 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1063 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1064 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1065 push @$mynetworks, "[$1]/$2";
1067 push @$mynetworks, $int_net_cidr;
1070 if ($int_ip =~ m/^$IPV6RE$/) {
1071 push @$mynetworks, "[$int_ip]/128";
1073 push @$mynetworks, "$int_ip/32";
1077 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1078 foreach my $cidr (keys %$netlist) {
1079 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1080 push @$mynetworks, "[$1]/$2";
1082 push @$mynetworks, $cidr;
1086 push @$mynetworks, @$transportnets;
1088 # add default relay to mynetworks
1089 if (my $relay = $self->get('mail', 'relay')) {
1090 if ($relay =~ m/^$IPV4RE$/) {
1091 push @$mynetworks, "$relay/32";
1092 } elsif ($relay =~ m/^$IPV6RE$/) {
1093 push @$mynetworks, "[$relay]/128";
1095 # DNS name - do nothing ?
1099 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1101 # normalize dnsbl_sites
1102 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1103 if (scalar(@dnsbl_sites)) {
1104 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1108 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1109 $self->get('mail', 'spf');
1110 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1112 if ($int_ip =~ m/^$IPV6RE$/) {
1113 $vars->{postfix
}->{int_ip
} = "[$int_ip]";
1115 $vars->{postfix
}->{int_ip
} = $int_ip;
1118 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1119 $vars->{dns
}->{hostname
} = $nodename;
1121 my $domain = $resolv->{search
} // 'localdomain';
1122 $vars->{dns
}->{domain
} = $domain;
1124 my $wlbr = "$nodename.$domain";
1125 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1128 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1130 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1132 my $uri = URI-
>new($proxy);
1133 my $host = $uri->host;
1134 my $port = $uri->port // 8080;
1136 my $data = { host
=> $host, port
=> $port };
1137 if (my $ui = $uri->userinfo) {
1138 my ($username, $pw) = split(/:/, $ui, 2);
1139 $data->{username
} = $username;
1140 $data->{password
} = $pw if defined($pw);
1142 $vars->{proxy
} = $data;
1145 warn "parse http_proxy failed - $@" if $@;
1151 # use one global TT cache
1152 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1154 my $template_toolkit;
1156 sub get_template_toolkit
{
1158 return $template_toolkit if $template_toolkit;
1160 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1162 return $template_toolkit;
1165 # rewrite file from template
1166 # return true if file has changed
1167 sub rewrite_config_file
{
1168 my ($self, $tmplname, $dstfn) = @_;
1170 my $demo = $self->get('admin', 'demo');
1173 my $demosrc = "$tmplname.demo";
1174 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1177 my ($perm, $uid, $gid);
1179 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1180 # needed if file contains a HTTPProxyPasswort
1182 $uid = getpwnam('clamav');
1183 $gid = getgrnam('adm');
1187 my $tt = get_template_toolkit
();
1189 my $vars = $self->get_template_vars();
1193 $tt->process($tmplname, $vars, \
$output) ||
1194 die $tt->error() . "\n";
1196 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1198 return 0 if defined($old) && ($old eq $output); # no change
1200 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1202 if (defined($uid) && defined($gid)) {
1203 chown($uid, $gid, $dstfn);
1209 # rewrite spam configuration
1210 sub rewrite_config_spam
{
1213 my $use_awl = $self->get('spam', 'use_awl');
1214 my $use_bayes = $self->get('spam', 'use_bayes');
1215 my $use_razor = $self->get('spam', 'use_razor');
1219 # delete AW and bayes databases if those features are disabled
1221 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1225 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1226 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1227 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1230 # make sure we have a custom.cf file (else cluster sync fails)
1231 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1233 $changes = 1 if $self->rewrite_config_file(
1234 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1236 $changes = 1 if $self->rewrite_config_file(
1237 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1239 $changes = 1 if $self->rewrite_config_file(
1240 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1242 $changes = 1 if $self->rewrite_config_file(
1243 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1246 mkdir "/root/.razor";
1248 $changes = 1 if $self->rewrite_config_file(
1249 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1251 if (! -e
'/root/.razor/identity') {
1254 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1255 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1258 syslog
('info', "registering razor failed: $err") if $err;
1265 # rewrite ClamAV configuration
1266 sub rewrite_config_clam
{
1269 return $self->rewrite_config_file(
1270 'clamd.conf.in', '/etc/clamav/clamd.conf');
1273 sub rewrite_config_freshclam
{
1276 return $self->rewrite_config_file(
1277 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1280 sub rewrite_config_postgres
{
1283 my $pgconfdir = "/etc/postgresql/9.6/main";
1287 $changes = 1 if $self->rewrite_config_file(
1288 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1290 $changes = 1 if $self->rewrite_config_file(
1291 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1296 # rewrite /root/.forward
1297 sub rewrite_dot_forward
{
1300 my $dstfn = '/root/.forward';
1302 my $email = $self->get('admin', 'email');
1305 if ($email && $email =~ m/\s*(\S+)\s*/) {
1308 # empty .forward does not forward mails (see man local)
1311 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1313 return 0 if defined($old) && ($old eq $output); # no change
1315 PVE
::Tools
::file_set_contents
($dstfn, $output);
1320 my $write_smtp_whitelist = sub {
1321 my ($filename, $data, $action) = @_;
1323 $action = 'OK' if !$action;
1325 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1329 foreach my $k (sort keys %$data) {
1330 $new .= "$k $action\n";
1333 return 0 if defined($old) && ($old eq $new); # no change
1335 PVE
::Tools
::file_set_contents
($filename, $new);
1337 PMG
::Utils
::run_postmap
($filename);
1342 sub rewrite_postfix_whitelist
{
1343 my ($rulecache) = @_;
1345 # see man page for regexp_table for postfix regex table format
1347 # we use a hash to avoid duplicate entries in regex tables
1350 my $clientlist = {};
1352 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1353 my $oclass = ref($obj);
1354 if ($oclass eq 'PMG::RuleDB::Receiver') {
1355 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1356 $tolist->{"/^$addr\$/"} = 1;
1357 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1358 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1359 $tolist->{"/^.+\@$addr\$/"} = 1;
1360 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1361 my $addr = $obj->{address
};
1363 $tolist->{"/^$addr\$/"} = 1;
1367 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1368 my $oclass = ref($obj);
1369 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1370 if ($oclass eq 'PMG::RuleDB::EMail') {
1371 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1372 $fromlist->{"/^$addr\$/"} = 1;
1373 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1374 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1375 $fromlist->{"/^.+\@$addr\$/"} = 1;
1376 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1377 my $addr = $obj->{address
};
1379 $fromlist->{"/^$addr\$/"} = 1;
1380 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1381 $clientlist->{$obj->{address
}} = 1;
1382 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1383 $clientlist->{$obj->{address
}} = 1;
1387 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1388 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1389 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1390 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1393 # rewrite /etc/postfix/*
1394 sub rewrite_config_postfix
{
1395 my ($self, $rulecache) = @_;
1397 # make sure we have required files (else postfix start fails)
1398 IO
::File-
>new($transport_map_filename, 'a', 0644);
1402 if ($self->get('mail', 'tls')) {
1404 PMG
::Utils
::gen_proxmox_tls_cert
();
1406 syslog
('info', "generating certificate failed: $@") if $@;
1409 $changes = 1 if $self->rewrite_config_file(
1410 'main.cf.in', '/etc/postfix/main.cf');
1412 $changes = 1 if $self->rewrite_config_file(
1413 'master.cf.in', '/etc/postfix/master.cf');
1415 # make sure we have required files (else postfix start fails)
1416 # Note: postmap need a valid /etc/postfix/main.cf configuration
1417 postmap_pmg_domains
();
1418 postmap_pmg_transport
();
1419 postmap_tls_policy
();
1421 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1423 # make sure aliases.db is up to date
1424 system('/usr/bin/newaliases');
1429 sub rewrite_config
{
1430 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1432 $force_restart = {} if ! $force_restart;
1434 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1435 $force_restart->{postfix
}) {
1436 PMG
::Utils
::service_cmd
('postfix', 'restart');
1439 if ($self->rewrite_dot_forward() && $restart_services) {
1440 # no need to restart anything
1443 if ($self->rewrite_config_postgres() && $restart_services) {
1444 # do nothing (too many side effects)?
1445 # does not happen anyways, because config does not change.
1448 if (($self->rewrite_config_spam() && $restart_services) ||
1449 $force_restart->{spam
}) {
1450 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1453 if (($self->rewrite_config_clam() && $restart_services) ||
1454 $force_restart->{clam
}) {
1455 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1458 if (($self->rewrite_config_freshclam() && $restart_services) ||
1459 $force_restart->{freshclam
}) {
1460 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');