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.",
148 description
=> "Additional score for bounce mails.",
155 description
=> "Enable real time blacklists (RBL) checks.",
160 description
=> "Maximum size of spam messages in bytes.",
170 use_awl
=> { optional
=> 1 },
171 use_razor
=> { optional
=> 1 },
172 wl_bounce_relays
=> { optional
=> 1 },
173 languages
=> { optional
=> 1 },
174 use_bayes
=> { optional
=> 1 },
175 bounce_score
=> { optional
=> 1 },
176 rbl_checks
=> { optional
=> 1 },
177 maxspamsize
=> { optional
=> 1 },
181 package PMG
::Config
::SpamQuarantine
;
186 use base
qw(PMG::Config::Base);
195 description
=> "Quarantine life time (days)",
201 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.",
203 enum
=> [qw(ticket ldap ldapticket)],
207 description
=> "Spam report style.",
209 enum
=> [qw(none short verbose custom)],
210 default => 'verbose',
213 description
=> "Allow to view images.",
218 description
=> "Allow to view hyperlinks.",
223 description
=> "Quarantine Host. Usefule if you run a Cluster and want users to connect to a specific host.",
224 type
=> 'string', format
=> 'address',
227 description
=> "Text for 'From' header in daily spam report mails.",
235 mailfrom
=> { optional
=> 1 },
236 hostname
=> { optional
=> 1 },
237 lifetime
=> { optional
=> 1 },
238 authmode
=> { optional
=> 1 },
239 reportstyle
=> { optional
=> 1 },
240 viewimages
=> { optional
=> 1 },
241 allowhrefs
=> { optional
=> 1 },
245 package PMG
::Config
::VirusQuarantine
;
250 use base
qw(PMG::Config::Base);
262 lifetime
=> { optional
=> 1 },
263 viewimages
=> { optional
=> 1 },
264 allowhrefs
=> { optional
=> 1 },
268 package PMG
::Config
::ClamAV
;
273 use base
qw(PMG::Config::Base);
282 description
=> "ClamAV database mirror server.",
284 default => 'database.clamav.net',
286 archiveblockencrypted
=> {
287 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
292 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.",
298 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.",
304 description
=> "Files larger than this limit won't be scanned.",
310 description
=> "Sets the maximum amount of data to be scanned for each input file.",
313 default => 100000000,
316 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
322 description
=> "Enables support for Google Safe Browsing.",
331 archiveblockencrypted
=> { optional
=> 1 },
332 archivemaxrec
=> { optional
=> 1 },
333 archivemaxfiles
=> { optional
=> 1 },
334 archivemaxsize
=> { optional
=> 1 },
335 maxscansize
=> { optional
=> 1 },
336 dbmirror
=> { optional
=> 1 },
337 maxcccount
=> { optional
=> 1 },
338 safebrowsing
=> { optional
=> 1 },
342 package PMG
::Config
::Mail
;
347 use PVE
::ProcFSTools
;
349 use base
qw(PMG::Config::Base);
356 sub physical_memory
{
358 return $physicalmem if $physicalmem;
360 my $info = PVE
::ProcFSTools
::read_meminfo
();
361 my $total = int($info->{memtotal
} / (1024*1024));
366 sub get_max_filters
{
367 # estimate optimal number of filter servers
371 my $memory = physical_memory
();
372 my $add_servers = int(($memory - 512)/$servermem);
373 $max_servers += $add_servers if $add_servers > 0;
374 $max_servers = 40 if $max_servers > 40;
376 return $max_servers - 2;
380 # estimate optimal number of smtpd daemons
382 my $max_servers = 25;
384 my $memory = physical_memory
();
385 my $add_servers = int(($memory - 512)/$servermem);
386 $max_servers += $add_servers if $add_servers > 0;
387 $max_servers = 100 if $max_servers > 100;
392 # estimate optimal number of proxpolicy servers
394 my $memory = physical_memory
();
395 $max_servers = 5 if $memory >= 500;
402 description
=> "SMTP port number for outgoing mail (trusted).",
409 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
416 description
=> "The default mail delivery transport (incoming mails).",
417 type
=> 'string', format
=> 'address',
420 description
=> "SMTP port number for relay host.",
427 description
=> "Disable MX lookups for default relay.",
432 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
433 type
=> 'string', format
=> 'address',
436 description
=> "ESMTP banner.",
439 default => 'ESMTP Proxmox',
442 description
=> "Maximum number of pmg-smtp-filter processes.",
446 default => get_max_filters
(),
449 description
=> "Maximum number of pmgpolicy processes.",
453 default => get_max_policy
(),
456 description
=> "Maximum number of SMTP daemon processes (in).",
460 default => get_max_smtpd
(),
463 description
=> "Maximum number of SMTP daemon processes (out).",
467 default => get_max_smtpd
(),
469 conn_count_limit
=> {
470 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
476 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.",
481 message_rate_limit
=> {
482 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.",
488 description
=> "Hide received header in outgoing mails.",
493 description
=> "Maximum email size. Larger mails are rejected.",
496 default => 1024*1024*10,
499 description
=> "SMTP delay warning time (in hours).",
505 description
=> "Enable TLS.",
510 description
=> "Enable TLS Logging.",
515 description
=> "Add TLS received header.",
520 description
=> "Use Sender Policy Framework.",
525 description
=> "Use Greylisting.",
530 description
=> "Use SMTP HELO tests.",
535 description
=> "Reject unknown clients.",
539 rejectunknownsender
=> {
540 description
=> "Reject unknown senders.",
545 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
547 enum
=> ['450', '550'],
550 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
551 type
=> 'string', format
=> 'dnsbl-entry-list',
558 int_port
=> { optional
=> 1 },
559 ext_port
=> { optional
=> 1 },
560 smarthost
=> { optional
=> 1 },
561 relay
=> { optional
=> 1 },
562 relayport
=> { optional
=> 1 },
563 relaynomx
=> { optional
=> 1 },
564 dwarning
=> { optional
=> 1 },
565 max_smtpd_in
=> { optional
=> 1 },
566 max_smtpd_out
=> { optional
=> 1 },
567 greylist
=> { optional
=> 1 },
568 helotests
=> { optional
=> 1 },
569 tls
=> { optional
=> 1 },
570 tlslog
=> { optional
=> 1 },
571 tlsheader
=> { optional
=> 1 },
572 spf
=> { optional
=> 1 },
573 maxsize
=> { optional
=> 1 },
574 banner
=> { optional
=> 1 },
575 max_filters
=> { optional
=> 1 },
576 max_policy
=> { optional
=> 1 },
577 hide_received
=> { optional
=> 1 },
578 rejectunknown
=> { optional
=> 1 },
579 rejectunknownsender
=> { optional
=> 1 },
580 conn_count_limit
=> { optional
=> 1 },
581 conn_rate_limit
=> { optional
=> 1 },
582 message_rate_limit
=> { optional
=> 1 },
583 verifyreceivers
=> { optional
=> 1 },
584 dnsbl_sites
=> { optional
=> 1 },
597 use PVE
::Tools
qw($IPV4RE $IPV6RE);
603 PMG
::Config
::Admin-
>register();
604 PMG
::Config
::Mail-
>register();
605 PMG
::Config
::SpamQuarantine-
>register();
606 PMG
::Config
::VirusQuarantine-
>register();
607 PMG
::Config
::Spam-
>register();
608 PMG
::Config
::ClamAV-
>register();
610 # initialize all plugins
611 PMG
::Config
::Base-
>init();
613 PVE
::JSONSchema
::register_format
(
614 'transport-domain', \
&pmg_verify_transport_domain
);
616 sub pmg_verify_transport_domain
{
617 my ($name, $noerr) = @_;
619 # like dns-name, but can contain leading dot
620 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
622 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
623 return undef if $noerr;
624 die "value does not look like a valid transport domain\n";
629 PVE
::JSONSchema
::register_format
(
630 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
632 sub pmg_verify_dnsbl_entry
{
633 my ($name, $noerr) = @_;
635 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
636 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
638 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
639 return undef if $noerr;
640 die "value '$name' does not look like a valid dnsbl entry\n";
648 my $class = ref($type) || $type;
650 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
652 return bless $cfg, $class;
658 PVE
::INotify
::write_file
("pmg.conf", $self);
661 my $lockfile = "/var/lock/pmgconfig.lck";
664 my ($code, $errmsg) = @_;
666 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
668 $errmsg ?
die "$errmsg: $err" : die $err;
674 my ($self, $section, $key, $value) = @_;
676 my $pdata = PMG
::Config
::Base-
>private();
678 my $plugin = $pdata->{plugins
}->{$section};
679 die "no such section '$section'" if !$plugin;
681 if (defined($value)) {
682 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
683 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
684 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
686 if (defined($self->{ids
}->{$section})) {
687 delete $self->{ids
}->{$section}->{$key};
694 # get section value or default
696 my ($self, $section, $key, $nodefault) = @_;
698 my $pdata = PMG
::Config
::Base-
>private();
699 my $pdesc = $pdata->{propertyList
}->{$key};
700 die "no such property '$section/$key'\n"
701 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
702 defined($pdata->{options
}->{$section}->{$key}));
704 if (defined($self->{ids
}->{$section}) &&
705 defined(my $value = $self->{ids
}->{$section}->{$key})) {
709 return undef if $nodefault;
711 return $pdesc->{default};
714 # get a whole section with default value
716 my ($self, $section) = @_;
718 my $pdata = PMG
::Config
::Base-
>private();
719 return undef if !defined($pdata->{options
}->{$section});
723 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
725 my $pdesc = $pdata->{propertyList
}->{$key};
727 if (defined($self->{ids
}->{$section}) &&
728 defined(my $value = $self->{ids
}->{$section}->{$key})) {
729 $res->{$key} = $value;
732 $res->{$key} = $pdesc->{default};
738 # get a whole config with default values
742 my $pdata = PMG
::Config
::Base-
>private();
746 foreach my $type (keys %{$pdata->{plugins
}}) {
747 my $plugin = $pdata->{plugins
}->{$type};
748 $res->{$type} = $self->get_section($type);
755 my ($filename, $fh) = @_;
757 local $/ = undef; # slurp mode
759 my $raw = <$fh> if defined($fh);
761 return PMG
::Config
::Base-
>parse_config($filename, $raw);
765 my ($filename, $fh, $cfg) = @_;
767 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
769 PVE
::Tools
::safe_print
($filename, $fh, $raw);
772 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
775 undef, always_call_parser
=> 1);
777 # parsers/writers for other files
779 my $domainsfilename = "/etc/pmg/domains";
781 sub postmap_pmg_domains
{
782 PMG
::Utils
::run_postmap
($domainsfilename);
785 sub read_pmg_domains
{
786 my ($filename, $fh) = @_;
792 while (defined(my $line = <$fh>)) {
794 next if $line =~ m/^\s*$/;
795 if ($line =~ m/^#(.*)\s*$/) {
799 if ($line =~ m/^(\S+)\s.*$/) {
801 $domains->{$domain} = {
802 domain
=> $domain, comment
=> $comment };
805 warn "parse error in '$filename': $line\n";
814 sub write_pmg_domains
{
815 my ($filename, $fh, $domains) = @_;
817 foreach my $domain (sort keys %$domains) {
818 my $comment = $domains->{$domain}->{comment
};
819 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
820 if defined($comment) && $comment !~ m/^\s*$/;
822 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
826 PVE
::INotify
::register_file
('domains', $domainsfilename,
829 undef, always_call_parser
=> 1);
831 my $mynetworks_filename = "/etc/pmg/mynetworks";
833 sub postmap_pmg_mynetworks
{
834 PMG
::Utils
::run_postmap
($mynetworks_filename);
837 sub read_pmg_mynetworks
{
838 my ($filename, $fh) = @_;
844 while (defined(my $line = <$fh>)) {
846 next if $line =~ m/^\s*$/;
847 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
848 my ($network, $prefix_size, $comment) = ($1, $2, $3);
849 my $cidr = "$network/${prefix_size}";
850 $mynetworks->{$cidr} = {
852 network_address
=> $network,
853 prefix_size
=> $prefix_size,
854 comment
=> $comment // '',
857 warn "parse error in '$filename': $line\n";
865 sub write_pmg_mynetworks
{
866 my ($filename, $fh, $mynetworks) = @_;
868 foreach my $cidr (sort keys %$mynetworks) {
869 my $data = $mynetworks->{$cidr};
870 my $comment = $data->{comment
} // '*';
871 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
875 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
876 \
&read_pmg_mynetworks
,
877 \
&write_pmg_mynetworks
,
878 undef, always_call_parser
=> 1);
880 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
882 sub postmap_tls_policy
{
883 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
886 my $transport_map_filename = "/etc/pmg/transport";
888 sub postmap_pmg_transport
{
889 PMG
::Utils
::run_postmap
($transport_map_filename);
892 sub read_transport_map
{
893 my ($filename, $fh) = @_;
895 return [] if !defined($fh);
901 while (defined(my $line = <$fh>)) {
903 next if $line =~ m/^\s*$/;
904 if ($line =~ m/^#(.*)\s*$/) {
909 my $parse_error = sub {
911 warn "parse error in '$filename': $line - $err";
915 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
916 my ($domain, $host, $port) = ($1, $2, $3);
918 eval { pmg_verify_transport_domain
($domain); };
920 $parse_error->($err);
924 if ($host =~ m/^\[(.*)\]$/) {
929 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
931 $parse_error->($err);
942 $res->{$domain} = $data;
945 $parse_error->('wrong format');
952 sub write_transport_map
{
953 my ($filename, $fh, $tmap) = @_;
957 foreach my $domain (sort keys %$tmap) {
958 my $data = $tmap->{$domain};
960 my $comment = $data->{comment
};
961 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
962 if defined($comment) && $comment !~ m/^\s*$/;
964 my $use_mx = $data->{use_mx
};
965 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
968 PVE
::Tools
::safe_print
(
969 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
971 PVE
::Tools
::safe_print
(
972 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
977 PVE
::INotify
::register_file
('transport', $transport_map_filename,
978 \
&read_transport_map
,
979 \
&write_transport_map
,
980 undef, always_call_parser
=> 1);
982 # config file generation using templates
984 sub get_template_vars
{
987 my $vars = { pmg
=> $self->get_config() };
989 my $nodename = PVE
::INotify
::nodename
();
990 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
991 my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip);
992 $vars->{ipconfig
}->{int_ip
} = $int_ip;
993 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
995 my $transportnets = [];
997 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
998 foreach my $domain (sort keys %$tmap) {
999 my $data = $tmap->{$domain};
1000 my $host = $data->{host
};
1001 if ($host =~ m/^$IPV4RE$/) {
1002 push @$transportnets, "$host/32";
1003 } elsif ($host =~ m/^$IPV6RE$/) {
1004 push @$transportnets, "[$host]/128";
1009 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1011 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1012 push @$mynetworks, @$transportnets;
1013 push @$mynetworks, $int_net_cidr;
1014 push @$mynetworks, 'hash:/etc/pmg/mynetworks';
1016 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1017 # add default relay to mynetworks
1018 if (my $relay = $self->get('mail', 'relay')) {
1019 if ($relay =~ m/^$IPV4RE$/) {
1020 push @$mynetworks, "$relay/32";
1021 } elsif ($relay =~ m/^$IPV6RE$/) {
1022 push @$mynetworks, "[$relay]/128";
1024 # DNS name - do nothing ?
1028 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1030 # normalize dnsbl_sites
1031 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1032 if (scalar(@dnsbl_sites)) {
1033 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1037 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1038 $self->get('mail', 'spf');
1039 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1041 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1042 $vars->{dns
}->{hostname
} = $nodename;
1043 $vars->{dns
}->{domain
} = $resolv->{search
};
1045 my $wlbr = "$nodename.$resolv->{search}";
1046 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1049 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1051 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1053 my $uri = URI-
>new($proxy);
1054 my $host = $uri->host;
1055 my $port = $uri->port // 8080;
1057 my $data = { host
=> $host, port
=> $port };
1058 if (my $ui = $uri->userinfo) {
1059 my ($username, $pw) = split(/:/, $ui, 2);
1060 $data->{username
} = $username;
1061 $data->{password
} = $pw if defined($pw);
1063 $vars->{proxy
} = $data;
1066 warn "parse http_proxy failed - $@" if $@;
1072 # use one global TT cache
1073 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1075 my $template_toolkit;
1077 sub get_template_toolkit
{
1079 return $template_toolkit if $template_toolkit;
1081 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1083 return $template_toolkit;
1086 # rewrite file from template
1087 # return true if file has changed
1088 sub rewrite_config_file
{
1089 my ($self, $tmplname, $dstfn) = @_;
1091 my $demo = $self->get('admin', 'demo');
1094 my $demosrc = "$tmplname.demo";
1095 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1098 my ($perm, $uid, $gid);
1100 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1101 # needed if file contains a HTTPProxyPasswort
1103 $uid = getpwnam('clamav');
1104 $gid = getgrnam('adm');
1108 my $tt = get_template_toolkit
();
1110 my $vars = $self->get_template_vars();
1114 $tt->process($tmplname, $vars, \
$output) ||
1115 die $tt->error() . "\n";
1117 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1119 return 0 if defined($old) && ($old eq $output); # no change
1121 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1123 if (defined($uid) && defined($gid)) {
1124 chown($uid, $gid, $dstfn);
1130 # rewrite spam configuration
1131 sub rewrite_config_spam
{
1134 my $use_awl = $self->get('spam', 'use_awl');
1135 my $use_bayes = $self->get('spam', 'use_bayes');
1136 my $use_razor = $self->get('spam', 'use_razor');
1140 # delete AW and bayes databases if those features are disabled
1142 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1146 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1147 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1148 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1151 # make sure we have a custom.cf file (else cluster sync fails)
1152 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1154 $changes = 1 if $self->rewrite_config_file(
1155 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1157 $changes = 1 if $self->rewrite_config_file(
1158 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1160 $changes = 1 if $self->rewrite_config_file(
1161 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1163 $changes = 1 if $self->rewrite_config_file(
1164 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1167 mkdir "/root/.razor";
1169 $changes = 1 if $self->rewrite_config_file(
1170 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1172 if (! -e
'/root/.razor/identity') {
1175 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1176 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1179 syslog
('info', "registering razor failed: $err") if $err;
1186 # rewrite ClamAV configuration
1187 sub rewrite_config_clam
{
1190 return $self->rewrite_config_file(
1191 'clamd.conf.in', '/etc/clamav/clamd.conf');
1194 sub rewrite_config_freshclam
{
1197 return $self->rewrite_config_file(
1198 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1201 sub rewrite_config_postgres
{
1204 my $pgconfdir = "/etc/postgresql/9.6/main";
1208 $changes = 1 if $self->rewrite_config_file(
1209 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1211 $changes = 1 if $self->rewrite_config_file(
1212 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1217 # rewrite /root/.forward
1218 sub rewrite_dot_forward
{
1221 my $dstfn = '/root/.forward';
1223 my $email = $self->get('admin', 'email');
1226 if ($email && $email =~ m/\s*(\S+)\s*/) {
1229 # empty .forward does not forward mails (see man local)
1232 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1234 return 0 if defined($old) && ($old eq $output); # no change
1236 PVE
::Tools
::file_set_contents
($dstfn, $output);
1241 my $write_smtp_whitelist = sub {
1242 my ($filename, $data, $action) = @_;
1244 $action = 'OK' if !$action;
1246 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1250 foreach my $k (sort keys %$data) {
1251 $new .= "$k $action\n";
1254 return 0 if defined($old) && ($old eq $new); # no change
1256 PVE
::Tools
::file_set_contents
($filename, $new);
1258 PMG
::Utils
::run_postmap
($filename);
1263 sub rewrite_postfix_whitelist
{
1264 my ($rulecache) = @_;
1266 # see man page for regexp_table for postfix regex table format
1268 # we use a hash to avoid duplicate entries in regex tables
1271 my $clientlist = {};
1273 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1274 my $oclass = ref($obj);
1275 if ($oclass eq 'PMG::RuleDB::Receiver') {
1276 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1277 $tolist->{"/^$addr\$/"} = 1;
1278 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1279 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1280 $tolist->{"/^.+\@$addr\$/"} = 1;
1281 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1282 my $addr = $obj->{address
};
1284 $tolist->{"/^$addr\$/"} = 1;
1288 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1289 my $oclass = ref($obj);
1290 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1291 if ($oclass eq 'PMG::RuleDB::EMail') {
1292 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1293 $fromlist->{"/^$addr\$/"} = 1;
1294 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1295 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1296 $fromlist->{"/^.+\@$addr\$/"} = 1;
1297 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1298 my $addr = $obj->{address
};
1300 $fromlist->{"/^$addr\$/"} = 1;
1301 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1302 $clientlist->{$obj->{address
}} = 1;
1303 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1304 $clientlist->{$obj->{address
}} = 1;
1308 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1309 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1310 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1311 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1314 # rewrite /etc/postfix/*
1315 sub rewrite_config_postfix
{
1316 my ($self, $rulecache) = @_;
1318 # make sure we have required files (else postfix start fails)
1319 postmap_pmg_domains
();
1320 postmap_pmg_transport
();
1321 postmap_pmg_mynetworks
();
1322 postmap_tls_policy
();
1324 IO
::File-
>new($transport_map_filename, 'a', 0644);
1328 if ($self->get('mail', 'tls')) {
1330 PMG
::Utils
::gen_proxmox_tls_cert
();
1332 syslog
('info', "generating certificate failed: $@") if $@;
1335 $changes = 1 if $self->rewrite_config_file(
1336 'main.cf.in', '/etc/postfix/main.cf');
1338 $changes = 1 if $self->rewrite_config_file(
1339 'master.cf.in', '/etc/postfix/master.cf');
1341 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1343 # make sure aliases.db is up to date
1344 system('/usr/bin/newaliases');
1349 sub rewrite_config
{
1350 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1352 $force_restart = {} if ! $force_restart;
1354 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1355 $force_restart->{postfix
}) {
1356 PMG
::Utils
::service_cmd
('postfix', 'restart');
1359 if ($self->rewrite_dot_forward() && $restart_services) {
1360 # no need to restart anything
1363 if ($self->rewrite_config_postgres() && $restart_services) {
1364 # do nothing (too many side effects)?
1365 # does not happen anyways, because config does not change.
1368 if (($self->rewrite_config_spam() && $restart_services) ||
1369 $force_restart->{spam
}) {
1370 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1373 if (($self->rewrite_config_clam() && $restart_services) ||
1374 $force_restart->{clam
}) {
1375 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1378 if (($self->rewrite_config_freshclam() && $restart_services) ||
1379 $force_restart->{freshclam
}) {
1380 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');