1 package PMG
::Config
::Base
;
9 use PVE
::JSONSchema
qw(get_standard_option);
10 use PVE
::SectionConfig
;
12 use base
qw(PVE::SectionConfig);
16 type
=> { description
=> "Section type." },
18 description
=> "Secion ID.",
19 type
=> 'string', format
=> 'pve-configid',
28 sub format_section_header
{
29 my ($class, $type, $sectionId) = @_;
31 die "internal error ($type ne $sectionId)" if $type ne $sectionId;
33 return "section: $type\n";
37 sub parse_section_header
{
38 my ($class, $line) = @_;
40 if ($line =~ m/^section:\s*(\S+)\s*$/) {
42 my $errmsg = undef; # set if you want to skip whole section
43 eval { PVE
::JSONSchema
::pve_verify_configid
($section); };
45 my $config = {}; # to return additional attributes
46 return ($section, $section, $errmsg, $config);
51 package PMG
::Config
::Admin
;
56 use base
qw(PMG::Config::Base);
65 description
=> "Use advanced filters for statistic.",
70 description
=> "Send daily reports.",
75 description
=> "User Statistics Lifetime (days)",
81 description
=> "Demo mode - do not start SMTP filter.",
86 description
=> "Administrator E-Mail address.",
87 type
=> 'string', format
=> 'email',
88 default => 'admin@domain.tld',
91 description
=> "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
93 pattern
=> "http://.*",
100 advfilter
=> { optional
=> 1 },
101 statlifetime
=> { optional
=> 1 },
102 dailyreport
=> { optional
=> 1 },
103 demo
=> { optional
=> 1 },
104 email
=> { optional
=> 1 },
105 http_proxy
=> { optional
=> 1 },
109 package PMG
::Config
::Spam
;
114 use base
qw(PMG::Config::Base);
123 description
=> "This option is used to specify which languages are considered OK for incoming mail.",
125 pattern
=> '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
129 description
=> "Whether to use the naive-Bayesian-style classifier.",
134 description
=> "Use the Auto-Whitelist plugin.",
139 description
=> "Whether to use Razor2, if it is available.",
143 wl_bounce_relays
=> {
144 description
=> "Whitelist legitimate bounce relays.",
147 clamav_heuristic_score
=> {
148 description
=> "Score for ClamaAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
155 description
=> "Additional score for bounce mails.",
162 description
=> "Enable real time blacklists (RBL) checks.",
167 description
=> "Maximum size of spam messages in bytes.",
170 default => 1024*1024,
177 use_awl
=> { optional
=> 1 },
178 use_razor
=> { optional
=> 1 },
179 wl_bounce_relays
=> { optional
=> 1 },
180 languages
=> { optional
=> 1 },
181 use_bayes
=> { optional
=> 1 },
182 clamav_heuristic_score
=> { optional
=> 1 },
183 bounce_score
=> { optional
=> 1 },
184 rbl_checks
=> { optional
=> 1 },
185 maxspamsize
=> { optional
=> 1 },
189 package PMG
::Config
::SpamQuarantine
;
194 use base
qw(PMG::Config::Base);
203 description
=> "Quarantine life time (days)",
209 description
=> "Authentication mode to access the quarantine interface. Mode 'ticket' allows login using tickets sent with the daily spam report. Mode 'ldap' requires to login using an LDAP account. Finally, mode 'ldapticket' allows both ways.",
211 enum
=> [qw(ticket ldap ldapticket)],
215 description
=> "Spam report style.",
217 enum
=> [qw(none short verbose custom)],
218 default => 'verbose',
221 description
=> "Allow to view images.",
226 description
=> "Allow to view hyperlinks.",
231 description
=> "Quarantine Host. Usefule if you run a Cluster and want users to connect to a specific host.",
232 type
=> 'string', format
=> 'address',
235 description
=> "Text for 'From' header in daily spam report mails.",
243 mailfrom
=> { optional
=> 1 },
244 hostname
=> { optional
=> 1 },
245 lifetime
=> { optional
=> 1 },
246 authmode
=> { optional
=> 1 },
247 reportstyle
=> { optional
=> 1 },
248 viewimages
=> { optional
=> 1 },
249 allowhrefs
=> { optional
=> 1 },
253 package PMG
::Config
::VirusQuarantine
;
258 use base
qw(PMG::Config::Base);
270 lifetime
=> { optional
=> 1 },
271 viewimages
=> { optional
=> 1 },
272 allowhrefs
=> { optional
=> 1 },
276 package PMG
::Config
::ClamAV
;
281 use base
qw(PMG::Config::Base);
290 description
=> "ClamAV database mirror server.",
292 default => 'database.clamav.net',
294 archiveblockencrypted
=> {
295 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
300 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.",
306 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.",
312 description
=> "Files larger than this limit won't be scanned.",
318 description
=> "Sets the maximum amount of data to be scanned for each input file.",
321 default => 100000000,
324 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
330 description
=> "Enables support for Google Safe Browsing.",
339 archiveblockencrypted
=> { optional
=> 1 },
340 archivemaxrec
=> { optional
=> 1 },
341 archivemaxfiles
=> { optional
=> 1 },
342 archivemaxsize
=> { optional
=> 1 },
343 maxscansize
=> { optional
=> 1 },
344 dbmirror
=> { optional
=> 1 },
345 maxcccount
=> { optional
=> 1 },
346 safebrowsing
=> { optional
=> 1 },
350 package PMG
::Config
::Mail
;
355 use PVE
::ProcFSTools
;
357 use base
qw(PMG::Config::Base);
364 sub physical_memory
{
366 return $physicalmem if $physicalmem;
368 my $info = PVE
::ProcFSTools
::read_meminfo
();
369 my $total = int($info->{memtotal
} / (1024*1024));
374 sub get_max_filters
{
375 # estimate optimal number of filter servers
379 my $memory = physical_memory
();
380 my $add_servers = int(($memory - 512)/$servermem);
381 $max_servers += $add_servers if $add_servers > 0;
382 $max_servers = 40 if $max_servers > 40;
384 return $max_servers - 2;
388 # estimate optimal number of smtpd daemons
390 my $max_servers = 25;
392 my $memory = physical_memory
();
393 my $add_servers = int(($memory - 512)/$servermem);
394 $max_servers += $add_servers if $add_servers > 0;
395 $max_servers = 100 if $max_servers > 100;
400 # estimate optimal number of proxpolicy servers
402 my $memory = physical_memory
();
403 $max_servers = 5 if $memory >= 500;
410 description
=> "SMTP port number for outgoing mail (trusted).",
417 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
424 description
=> "The default mail delivery transport (incoming mails).",
425 type
=> 'string', format
=> 'address',
428 description
=> "SMTP port number for relay host.",
435 description
=> "Disable MX lookups for default relay.",
440 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
441 type
=> 'string', format
=> 'address',
444 description
=> "ESMTP banner.",
447 default => 'ESMTP Proxmox',
450 description
=> "Maximum number of pmg-smtp-filter processes.",
454 default => get_max_filters
(),
457 description
=> "Maximum number of pmgpolicy processes.",
461 default => get_max_policy
(),
464 description
=> "Maximum number of SMTP daemon processes (in).",
468 default => get_max_smtpd
(),
471 description
=> "Maximum number of SMTP daemon processes (out).",
475 default => get_max_smtpd
(),
477 conn_count_limit
=> {
478 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
484 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.",
489 message_rate_limit
=> {
490 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.",
496 description
=> "Hide received header in outgoing mails.",
501 description
=> "Maximum email size. Larger mails are rejected.",
504 default => 1024*1024*10,
507 description
=> "SMTP delay warning time (in hours).",
513 description
=> "Enable TLS.",
518 description
=> "Enable TLS Logging.",
523 description
=> "Add TLS received header.",
528 description
=> "Use Sender Policy Framework.",
533 description
=> "Use Greylisting.",
538 description
=> "Use SMTP HELO tests.",
543 description
=> "Reject unknown clients.",
547 rejectunknownsender
=> {
548 description
=> "Reject unknown senders.",
553 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
555 enum
=> ['450', '550'],
558 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
559 type
=> 'string', format
=> 'dnsbl-entry-list',
566 int_port
=> { optional
=> 1 },
567 ext_port
=> { optional
=> 1 },
568 smarthost
=> { optional
=> 1 },
569 relay
=> { optional
=> 1 },
570 relayport
=> { optional
=> 1 },
571 relaynomx
=> { optional
=> 1 },
572 dwarning
=> { optional
=> 1 },
573 max_smtpd_in
=> { optional
=> 1 },
574 max_smtpd_out
=> { optional
=> 1 },
575 greylist
=> { optional
=> 1 },
576 helotests
=> { optional
=> 1 },
577 tls
=> { optional
=> 1 },
578 tlslog
=> { optional
=> 1 },
579 tlsheader
=> { optional
=> 1 },
580 spf
=> { optional
=> 1 },
581 maxsize
=> { optional
=> 1 },
582 banner
=> { optional
=> 1 },
583 max_filters
=> { optional
=> 1 },
584 max_policy
=> { optional
=> 1 },
585 hide_received
=> { optional
=> 1 },
586 rejectunknown
=> { optional
=> 1 },
587 rejectunknownsender
=> { optional
=> 1 },
588 conn_count_limit
=> { optional
=> 1 },
589 conn_rate_limit
=> { optional
=> 1 },
590 message_rate_limit
=> { optional
=> 1 },
591 verifyreceivers
=> { optional
=> 1 },
592 dnsbl_sites
=> { optional
=> 1 },
605 use PVE
::Tools
qw($IPV4RE $IPV6RE);
611 PMG
::Config
::Admin-
>register();
612 PMG
::Config
::Mail-
>register();
613 PMG
::Config
::SpamQuarantine-
>register();
614 PMG
::Config
::VirusQuarantine-
>register();
615 PMG
::Config
::Spam-
>register();
616 PMG
::Config
::ClamAV-
>register();
618 # initialize all plugins
619 PMG
::Config
::Base-
>init();
621 PVE
::JSONSchema
::register_format
(
622 'transport-domain', \
&pmg_verify_transport_domain
);
624 sub pmg_verify_transport_domain
{
625 my ($name, $noerr) = @_;
627 # like dns-name, but can contain leading dot
628 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
630 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
631 return undef if $noerr;
632 die "value does not look like a valid transport domain\n";
637 PVE
::JSONSchema
::register_format
(
638 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
640 sub pmg_verify_dnsbl_entry
{
641 my ($name, $noerr) = @_;
643 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
644 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
646 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
647 return undef if $noerr;
648 die "value '$name' does not look like a valid dnsbl entry\n";
656 my $class = ref($type) || $type;
658 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
660 return bless $cfg, $class;
666 PVE
::INotify
::write_file
("pmg.conf", $self);
669 my $lockfile = "/var/lock/pmgconfig.lck";
672 my ($code, $errmsg) = @_;
674 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
676 $errmsg ?
die "$errmsg: $err" : die $err;
682 my ($self, $section, $key, $value) = @_;
684 my $pdata = PMG
::Config
::Base-
>private();
686 my $plugin = $pdata->{plugins
}->{$section};
687 die "no such section '$section'" if !$plugin;
689 if (defined($value)) {
690 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
691 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
692 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
694 if (defined($self->{ids
}->{$section})) {
695 delete $self->{ids
}->{$section}->{$key};
702 # get section value or default
704 my ($self, $section, $key, $nodefault) = @_;
706 my $pdata = PMG
::Config
::Base-
>private();
707 my $pdesc = $pdata->{propertyList
}->{$key};
708 die "no such property '$section/$key'\n"
709 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
710 defined($pdata->{options
}->{$section}->{$key}));
712 if (defined($self->{ids
}->{$section}) &&
713 defined(my $value = $self->{ids
}->{$section}->{$key})) {
717 return undef if $nodefault;
719 return $pdesc->{default};
722 # get a whole section with default value
724 my ($self, $section) = @_;
726 my $pdata = PMG
::Config
::Base-
>private();
727 return undef if !defined($pdata->{options
}->{$section});
731 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
733 my $pdesc = $pdata->{propertyList
}->{$key};
735 if (defined($self->{ids
}->{$section}) &&
736 defined(my $value = $self->{ids
}->{$section}->{$key})) {
737 $res->{$key} = $value;
740 $res->{$key} = $pdesc->{default};
746 # get a whole config with default values
750 my $pdata = PMG
::Config
::Base-
>private();
754 foreach my $type (keys %{$pdata->{plugins
}}) {
755 my $plugin = $pdata->{plugins
}->{$type};
756 $res->{$type} = $self->get_section($type);
763 my ($filename, $fh) = @_;
765 local $/ = undef; # slurp mode
767 my $raw = <$fh> if defined($fh);
769 return PMG
::Config
::Base-
>parse_config($filename, $raw);
773 my ($filename, $fh, $cfg) = @_;
775 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
777 PVE
::Tools
::safe_print
($filename, $fh, $raw);
780 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
783 undef, always_call_parser
=> 1);
785 # parsers/writers for other files
787 my $domainsfilename = "/etc/pmg/domains";
789 sub postmap_pmg_domains
{
790 PMG
::Utils
::run_postmap
($domainsfilename);
793 sub read_pmg_domains
{
794 my ($filename, $fh) = @_;
800 while (defined(my $line = <$fh>)) {
802 next if $line =~ m/^\s*$/;
803 if ($line =~ m/^#(.*)\s*$/) {
807 if ($line =~ m/^(\S+)\s.*$/) {
809 $domains->{$domain} = {
810 domain
=> $domain, comment
=> $comment };
813 warn "parse error in '$filename': $line\n";
822 sub write_pmg_domains
{
823 my ($filename, $fh, $domains) = @_;
825 foreach my $domain (sort keys %$domains) {
826 my $comment = $domains->{$domain}->{comment
};
827 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
828 if defined($comment) && $comment !~ m/^\s*$/;
830 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
834 PVE
::INotify
::register_file
('domains', $domainsfilename,
837 undef, always_call_parser
=> 1);
839 my $mynetworks_filename = "/etc/pmg/mynetworks";
841 sub read_pmg_mynetworks
{
842 my ($filename, $fh) = @_;
848 while (defined(my $line = <$fh>)) {
850 next if $line =~ m/^\s*$/;
851 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
852 my ($network, $prefix_size, $comment) = ($1, $2, $3);
853 my $cidr = "$network/${prefix_size}";
854 $mynetworks->{$cidr} = {
856 network_address
=> $network,
857 prefix_size
=> $prefix_size,
858 comment
=> $comment // '',
861 warn "parse error in '$filename': $line\n";
869 sub write_pmg_mynetworks
{
870 my ($filename, $fh, $mynetworks) = @_;
872 foreach my $cidr (sort keys %$mynetworks) {
873 my $data = $mynetworks->{$cidr};
874 my $comment = $data->{comment
} // '*';
875 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
879 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
880 \
&read_pmg_mynetworks
,
881 \
&write_pmg_mynetworks
,
882 undef, always_call_parser
=> 1);
884 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
886 sub postmap_tls_policy
{
887 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
890 my $transport_map_filename = "/etc/pmg/transport";
892 sub postmap_pmg_transport
{
893 PMG
::Utils
::run_postmap
($transport_map_filename);
896 sub read_transport_map
{
897 my ($filename, $fh) = @_;
899 return [] if !defined($fh);
905 while (defined(my $line = <$fh>)) {
907 next if $line =~ m/^\s*$/;
908 if ($line =~ m/^#(.*)\s*$/) {
913 my $parse_error = sub {
915 warn "parse error in '$filename': $line - $err";
919 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
920 my ($domain, $host, $port) = ($1, $2, $3);
922 eval { pmg_verify_transport_domain
($domain); };
924 $parse_error->($err);
928 if ($host =~ m/^\[(.*)\]$/) {
933 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
935 $parse_error->($err);
946 $res->{$domain} = $data;
949 $parse_error->('wrong format');
956 sub write_transport_map
{
957 my ($filename, $fh, $tmap) = @_;
961 foreach my $domain (sort keys %$tmap) {
962 my $data = $tmap->{$domain};
964 my $comment = $data->{comment
};
965 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
966 if defined($comment) && $comment !~ m/^\s*$/;
968 my $use_mx = $data->{use_mx
};
969 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
972 PVE
::Tools
::safe_print
(
973 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
975 PVE
::Tools
::safe_print
(
976 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
981 PVE
::INotify
::register_file
('transport', $transport_map_filename,
982 \
&read_transport_map
,
983 \
&write_transport_map
,
984 undef, always_call_parser
=> 1);
986 # config file generation using templates
988 sub get_template_vars
{
991 my $vars = { pmg
=> $self->get_config() };
993 my $nodename = PVE
::INotify
::nodename
();
994 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
995 my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip);
996 $vars->{ipconfig
}->{int_ip
} = $int_ip;
997 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
999 my $transportnets = [];
1001 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1002 foreach my $domain (sort keys %$tmap) {
1003 my $data = $tmap->{$domain};
1004 my $host = $data->{host
};
1005 if ($host =~ m/^$IPV4RE$/) {
1006 push @$transportnets, "$host/32";
1007 } elsif ($host =~ m/^$IPV6RE$/) {
1008 push @$transportnets, "[$host]/128";
1013 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1015 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1016 push @$mynetworks, $int_net_cidr;
1018 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1019 push @$mynetworks, keys %$netlist;
1021 push @$mynetworks, @$transportnets;
1023 # add default relay to mynetworks
1024 if (my $relay = $self->get('mail', 'relay')) {
1025 if ($relay =~ m/^$IPV4RE$/) {
1026 push @$mynetworks, "$relay/32";
1027 } elsif ($relay =~ m/^$IPV6RE$/) {
1028 push @$mynetworks, "[$relay]/128";
1030 # DNS name - do nothing ?
1034 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1036 # normalize dnsbl_sites
1037 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1038 if (scalar(@dnsbl_sites)) {
1039 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1043 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1044 $self->get('mail', 'spf');
1045 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1047 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1048 $vars->{dns
}->{hostname
} = $nodename;
1049 $vars->{dns
}->{domain
} = $resolv->{search
};
1051 my $wlbr = "$nodename.$resolv->{search}";
1052 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1055 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1057 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1059 my $uri = URI-
>new($proxy);
1060 my $host = $uri->host;
1061 my $port = $uri->port // 8080;
1063 my $data = { host
=> $host, port
=> $port };
1064 if (my $ui = $uri->userinfo) {
1065 my ($username, $pw) = split(/:/, $ui, 2);
1066 $data->{username
} = $username;
1067 $data->{password
} = $pw if defined($pw);
1069 $vars->{proxy
} = $data;
1072 warn "parse http_proxy failed - $@" if $@;
1078 # use one global TT cache
1079 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1081 my $template_toolkit;
1083 sub get_template_toolkit
{
1085 return $template_toolkit if $template_toolkit;
1087 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1089 return $template_toolkit;
1092 # rewrite file from template
1093 # return true if file has changed
1094 sub rewrite_config_file
{
1095 my ($self, $tmplname, $dstfn) = @_;
1097 my $demo = $self->get('admin', 'demo');
1100 my $demosrc = "$tmplname.demo";
1101 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1104 my ($perm, $uid, $gid);
1106 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1107 # needed if file contains a HTTPProxyPasswort
1109 $uid = getpwnam('clamav');
1110 $gid = getgrnam('adm');
1114 my $tt = get_template_toolkit
();
1116 my $vars = $self->get_template_vars();
1120 $tt->process($tmplname, $vars, \
$output) ||
1121 die $tt->error() . "\n";
1123 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1125 return 0 if defined($old) && ($old eq $output); # no change
1127 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1129 if (defined($uid) && defined($gid)) {
1130 chown($uid, $gid, $dstfn);
1136 # rewrite spam configuration
1137 sub rewrite_config_spam
{
1140 my $use_awl = $self->get('spam', 'use_awl');
1141 my $use_bayes = $self->get('spam', 'use_bayes');
1142 my $use_razor = $self->get('spam', 'use_razor');
1146 # delete AW and bayes databases if those features are disabled
1148 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1152 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1153 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1154 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1157 # make sure we have a custom.cf file (else cluster sync fails)
1158 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1160 $changes = 1 if $self->rewrite_config_file(
1161 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1163 $changes = 1 if $self->rewrite_config_file(
1164 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1166 $changes = 1 if $self->rewrite_config_file(
1167 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1169 $changes = 1 if $self->rewrite_config_file(
1170 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1173 mkdir "/root/.razor";
1175 $changes = 1 if $self->rewrite_config_file(
1176 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1178 if (! -e
'/root/.razor/identity') {
1181 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1182 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1185 syslog
('info', "registering razor failed: $err") if $err;
1192 # rewrite ClamAV configuration
1193 sub rewrite_config_clam
{
1196 return $self->rewrite_config_file(
1197 'clamd.conf.in', '/etc/clamav/clamd.conf');
1200 sub rewrite_config_freshclam
{
1203 return $self->rewrite_config_file(
1204 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1207 sub rewrite_config_postgres
{
1210 my $pgconfdir = "/etc/postgresql/9.6/main";
1214 $changes = 1 if $self->rewrite_config_file(
1215 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1217 $changes = 1 if $self->rewrite_config_file(
1218 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1223 # rewrite /root/.forward
1224 sub rewrite_dot_forward
{
1227 my $dstfn = '/root/.forward';
1229 my $email = $self->get('admin', 'email');
1232 if ($email && $email =~ m/\s*(\S+)\s*/) {
1235 # empty .forward does not forward mails (see man local)
1238 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1240 return 0 if defined($old) && ($old eq $output); # no change
1242 PVE
::Tools
::file_set_contents
($dstfn, $output);
1247 my $write_smtp_whitelist = sub {
1248 my ($filename, $data, $action) = @_;
1250 $action = 'OK' if !$action;
1252 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1256 foreach my $k (sort keys %$data) {
1257 $new .= "$k $action\n";
1260 return 0 if defined($old) && ($old eq $new); # no change
1262 PVE
::Tools
::file_set_contents
($filename, $new);
1264 PMG
::Utils
::run_postmap
($filename);
1269 sub rewrite_postfix_whitelist
{
1270 my ($rulecache) = @_;
1272 # see man page for regexp_table for postfix regex table format
1274 # we use a hash to avoid duplicate entries in regex tables
1277 my $clientlist = {};
1279 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1280 my $oclass = ref($obj);
1281 if ($oclass eq 'PMG::RuleDB::Receiver') {
1282 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1283 $tolist->{"/^$addr\$/"} = 1;
1284 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1285 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1286 $tolist->{"/^.+\@$addr\$/"} = 1;
1287 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1288 my $addr = $obj->{address
};
1290 $tolist->{"/^$addr\$/"} = 1;
1294 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1295 my $oclass = ref($obj);
1296 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1297 if ($oclass eq 'PMG::RuleDB::EMail') {
1298 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1299 $fromlist->{"/^$addr\$/"} = 1;
1300 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1301 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1302 $fromlist->{"/^.+\@$addr\$/"} = 1;
1303 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1304 my $addr = $obj->{address
};
1306 $fromlist->{"/^$addr\$/"} = 1;
1307 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1308 $clientlist->{$obj->{address
}} = 1;
1309 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1310 $clientlist->{$obj->{address
}} = 1;
1314 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1315 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1316 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1317 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1320 # rewrite /etc/postfix/*
1321 sub rewrite_config_postfix
{
1322 my ($self, $rulecache) = @_;
1324 # make sure we have required files (else postfix start fails)
1325 postmap_pmg_domains
();
1326 postmap_pmg_transport
();
1327 postmap_tls_policy
();
1329 IO
::File-
>new($transport_map_filename, 'a', 0644);
1333 if ($self->get('mail', 'tls')) {
1335 PMG
::Utils
::gen_proxmox_tls_cert
();
1337 syslog
('info', "generating certificate failed: $@") if $@;
1340 $changes = 1 if $self->rewrite_config_file(
1341 'main.cf.in', '/etc/postfix/main.cf');
1343 $changes = 1 if $self->rewrite_config_file(
1344 'master.cf.in', '/etc/postfix/master.cf');
1346 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1348 # make sure aliases.db is up to date
1349 system('/usr/bin/newaliases');
1354 sub rewrite_config
{
1355 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1357 $force_restart = {} if ! $force_restart;
1359 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1360 $force_restart->{postfix
}) {
1361 PMG
::Utils
::service_cmd
('postfix', 'restart');
1364 if ($self->rewrite_dot_forward() && $restart_services) {
1365 # no need to restart anything
1368 if ($self->rewrite_config_postgres() && $restart_services) {
1369 # do nothing (too many side effects)?
1370 # does not happen anyways, because config does not change.
1373 if (($self->rewrite_config_spam() && $restart_services) ||
1374 $force_restart->{spam
}) {
1375 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1378 if (($self->rewrite_config_clam() && $restart_services) ||
1379 $force_restart->{clam
}) {
1380 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1383 if (($self->rewrite_config_freshclam() && $restart_services) ||
1384 $force_restart->{freshclam
}) {
1385 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');