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.",
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. Useful if you run a Cluster and want users to connect to a specific host.",
232 type
=> 'string', format
=> 'address',
235 description
=> "Quarantine Port. Useful if you have a reverse proxy or port forwarding for the webinterface. Only used for the generated Spam report.",
242 description
=> "Quarantine Webinterface Protocol. Useful if you have a reverse proxy for the webinterface. Only used for the generated Spam report.",
244 enum
=> [qw(http https)],
248 description
=> "Text for 'From' header in daily spam report mails.",
256 mailfrom
=> { optional
=> 1 },
257 hostname
=> { optional
=> 1 },
258 lifetime
=> { optional
=> 1 },
259 authmode
=> { optional
=> 1 },
260 reportstyle
=> { optional
=> 1 },
261 viewimages
=> { optional
=> 1 },
262 allowhrefs
=> { optional
=> 1 },
263 port
=> { optional
=> 1 },
264 protocol
=> { optional
=> 1 },
268 package PMG
::Config
::VirusQuarantine
;
273 use base
qw(PMG::Config::Base);
285 lifetime
=> { optional
=> 1 },
286 viewimages
=> { optional
=> 1 },
287 allowhrefs
=> { optional
=> 1 },
291 package PMG
::Config
::ClamAV
;
296 use base
qw(PMG::Config::Base);
305 description
=> "ClamAV database mirror server.",
307 default => 'database.clamav.net',
309 archiveblockencrypted
=> {
310 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
315 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.",
321 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.",
327 description
=> "Files larger than this limit won't be scanned.",
333 description
=> "Sets the maximum amount of data to be scanned for each input file.",
336 default => 100000000,
339 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
345 description
=> "Enables support for Google Safe Browsing.",
354 archiveblockencrypted
=> { optional
=> 1 },
355 archivemaxrec
=> { optional
=> 1 },
356 archivemaxfiles
=> { optional
=> 1 },
357 archivemaxsize
=> { optional
=> 1 },
358 maxscansize
=> { optional
=> 1 },
359 dbmirror
=> { optional
=> 1 },
360 maxcccount
=> { optional
=> 1 },
361 safebrowsing
=> { optional
=> 1 },
365 package PMG
::Config
::Mail
;
370 use PVE
::ProcFSTools
;
372 use base
qw(PMG::Config::Base);
379 sub physical_memory
{
381 return $physicalmem if $physicalmem;
383 my $info = PVE
::ProcFSTools
::read_meminfo
();
384 my $total = int($info->{memtotal
} / (1024*1024));
389 sub get_max_filters
{
390 # estimate optimal number of filter servers
394 my $memory = physical_memory
();
395 my $add_servers = int(($memory - 512)/$servermem);
396 $max_servers += $add_servers if $add_servers > 0;
397 $max_servers = 40 if $max_servers > 40;
399 return $max_servers - 2;
403 # estimate optimal number of smtpd daemons
405 my $max_servers = 25;
407 my $memory = physical_memory
();
408 my $add_servers = int(($memory - 512)/$servermem);
409 $max_servers += $add_servers if $add_servers > 0;
410 $max_servers = 100 if $max_servers > 100;
415 # estimate optimal number of proxpolicy servers
417 my $memory = physical_memory
();
418 $max_servers = 5 if $memory >= 500;
425 description
=> "SMTP port number for outgoing mail (trusted).",
432 description
=> "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
439 description
=> "The default mail delivery transport (incoming mails).",
440 type
=> 'string', format
=> 'address',
443 description
=> "SMTP port number for relay host.",
450 description
=> "Disable MX lookups for default relay.",
455 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
456 type
=> 'string', format
=> 'address',
459 description
=> "ESMTP banner.",
462 default => 'ESMTP Proxmox',
465 description
=> "Maximum number of pmg-smtp-filter processes.",
469 default => get_max_filters
(),
472 description
=> "Maximum number of pmgpolicy processes.",
476 default => get_max_policy
(),
479 description
=> "Maximum number of SMTP daemon processes (in).",
483 default => get_max_smtpd
(),
486 description
=> "Maximum number of SMTP daemon processes (out).",
490 default => get_max_smtpd
(),
492 conn_count_limit
=> {
493 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
499 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.",
504 message_rate_limit
=> {
505 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.",
511 description
=> "Hide received header in outgoing mails.",
516 description
=> "Maximum email size. Larger mails are rejected.",
519 default => 1024*1024*10,
522 description
=> "SMTP delay warning time (in hours).",
528 description
=> "Enable TLS.",
533 description
=> "Enable TLS Logging.",
538 description
=> "Add TLS received header.",
543 description
=> "Use Sender Policy Framework.",
548 description
=> "Use Greylisting.",
553 description
=> "Use SMTP HELO tests.",
558 description
=> "Reject unknown clients.",
562 rejectunknownsender
=> {
563 description
=> "Reject unknown senders.",
568 description
=> "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
570 enum
=> ['450', '550'],
573 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
574 type
=> 'string', format
=> 'dnsbl-entry-list',
581 int_port
=> { optional
=> 1 },
582 ext_port
=> { optional
=> 1 },
583 smarthost
=> { optional
=> 1 },
584 relay
=> { optional
=> 1 },
585 relayport
=> { optional
=> 1 },
586 relaynomx
=> { optional
=> 1 },
587 dwarning
=> { optional
=> 1 },
588 max_smtpd_in
=> { optional
=> 1 },
589 max_smtpd_out
=> { optional
=> 1 },
590 greylist
=> { optional
=> 1 },
591 helotests
=> { optional
=> 1 },
592 tls
=> { optional
=> 1 },
593 tlslog
=> { optional
=> 1 },
594 tlsheader
=> { optional
=> 1 },
595 spf
=> { optional
=> 1 },
596 maxsize
=> { optional
=> 1 },
597 banner
=> { optional
=> 1 },
598 max_filters
=> { optional
=> 1 },
599 max_policy
=> { optional
=> 1 },
600 hide_received
=> { optional
=> 1 },
601 rejectunknown
=> { optional
=> 1 },
602 rejectunknownsender
=> { optional
=> 1 },
603 conn_count_limit
=> { optional
=> 1 },
604 conn_rate_limit
=> { optional
=> 1 },
605 message_rate_limit
=> { optional
=> 1 },
606 verifyreceivers
=> { optional
=> 1 },
607 dnsbl_sites
=> { optional
=> 1 },
620 use PVE
::Tools
qw($IPV4RE $IPV6RE);
626 PMG
::Config
::Admin-
>register();
627 PMG
::Config
::Mail-
>register();
628 PMG
::Config
::SpamQuarantine-
>register();
629 PMG
::Config
::VirusQuarantine-
>register();
630 PMG
::Config
::Spam-
>register();
631 PMG
::Config
::ClamAV-
>register();
633 # initialize all plugins
634 PMG
::Config
::Base-
>init();
636 PVE
::JSONSchema
::register_format
(
637 'transport-domain', \
&pmg_verify_transport_domain
);
639 sub pmg_verify_transport_domain
{
640 my ($name, $noerr) = @_;
642 # like dns-name, but can contain leading dot
643 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
645 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
646 return undef if $noerr;
647 die "value does not look like a valid transport domain\n";
652 PVE
::JSONSchema
::register_format
(
653 'transport-domain-or-email', \
&pmg_verify_transport_domain_or_email
);
655 sub pmg_verify_transport_domain_or_email
{
656 my ($name, $noerr) = @_;
658 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
661 if ($name =~ m/^(?:[^\s\/\
@]+\
@)(${namere
}\
.)*${namere
}$/) {
665 # like dns-name, but can contain leading dot
666 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
667 return undef if $noerr;
668 die "value does not look like a valid transport domain or email address\n";
673 PVE
::JSONSchema
::register_format
(
674 'dnsbl-entry', \
&pmg_verify_dnsbl_entry
);
676 sub pmg_verify_dnsbl_entry
{
677 my ($name, $noerr) = @_;
679 # like dns-name, but can contain trailing weight: 'domain*<WEIGHT>'
680 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
682 if ($name !~ /^(${namere}\.)*${namere}(\*\-?\d+)?$/) {
683 return undef if $noerr;
684 die "value '$name' does not look like a valid dnsbl entry\n";
692 my $class = ref($type) || $type;
694 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
696 return bless $cfg, $class;
702 PVE
::INotify
::write_file
("pmg.conf", $self);
705 my $lockfile = "/var/lock/pmgconfig.lck";
708 my ($code, $errmsg) = @_;
710 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
712 $errmsg ?
die "$errmsg: $err" : die $err;
718 my ($self, $section, $key, $value) = @_;
720 my $pdata = PMG
::Config
::Base-
>private();
722 my $plugin = $pdata->{plugins
}->{$section};
723 die "no such section '$section'" if !$plugin;
725 if (defined($value)) {
726 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
727 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
728 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
730 if (defined($self->{ids
}->{$section})) {
731 delete $self->{ids
}->{$section}->{$key};
738 # get section value or default
740 my ($self, $section, $key, $nodefault) = @_;
742 my $pdata = PMG
::Config
::Base-
>private();
743 my $pdesc = $pdata->{propertyList
}->{$key};
744 die "no such property '$section/$key'\n"
745 if !(defined($pdesc) && defined($pdata->{options
}->{$section}) &&
746 defined($pdata->{options
}->{$section}->{$key}));
748 if (defined($self->{ids
}->{$section}) &&
749 defined(my $value = $self->{ids
}->{$section}->{$key})) {
753 return undef if $nodefault;
755 return $pdesc->{default};
758 # get a whole section with default value
760 my ($self, $section) = @_;
762 my $pdata = PMG
::Config
::Base-
>private();
763 return undef if !defined($pdata->{options
}->{$section});
767 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
769 my $pdesc = $pdata->{propertyList
}->{$key};
771 if (defined($self->{ids
}->{$section}) &&
772 defined(my $value = $self->{ids
}->{$section}->{$key})) {
773 $res->{$key} = $value;
776 $res->{$key} = $pdesc->{default};
782 # get a whole config with default values
786 my $pdata = PMG
::Config
::Base-
>private();
790 foreach my $type (keys %{$pdata->{plugins
}}) {
791 my $plugin = $pdata->{plugins
}->{$type};
792 $res->{$type} = $self->get_section($type);
799 my ($filename, $fh) = @_;
801 local $/ = undef; # slurp mode
803 my $raw = <$fh> if defined($fh);
805 return PMG
::Config
::Base-
>parse_config($filename, $raw);
809 my ($filename, $fh, $cfg) = @_;
811 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
813 PVE
::Tools
::safe_print
($filename, $fh, $raw);
816 PVE
::INotify
::register_file
('pmg.conf', "/etc/pmg/pmg.conf",
819 undef, always_call_parser
=> 1);
821 # parsers/writers for other files
823 my $domainsfilename = "/etc/pmg/domains";
825 sub postmap_pmg_domains
{
826 PMG
::Utils
::run_postmap
($domainsfilename);
829 sub read_pmg_domains
{
830 my ($filename, $fh) = @_;
836 while (defined(my $line = <$fh>)) {
838 next if $line =~ m/^\s*$/;
839 if ($line =~ m/^#(.*)\s*$/) {
843 if ($line =~ m/^(\S+)\s.*$/) {
845 $domains->{$domain} = {
846 domain
=> $domain, comment
=> $comment };
849 warn "parse error in '$filename': $line\n";
858 sub write_pmg_domains
{
859 my ($filename, $fh, $domains) = @_;
861 foreach my $domain (sort keys %$domains) {
862 my $comment = $domains->{$domain}->{comment
};
863 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
864 if defined($comment) && $comment !~ m/^\s*$/;
866 PVE
::Tools
::safe_print
($filename, $fh, "$domain 1\n");
870 PVE
::INotify
::register_file
('domains', $domainsfilename,
873 undef, always_call_parser
=> 1);
875 my $mynetworks_filename = "/etc/pmg/mynetworks";
877 sub read_pmg_mynetworks
{
878 my ($filename, $fh) = @_;
884 while (defined(my $line = <$fh>)) {
886 next if $line =~ m/^\s*$/;
887 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
888 my ($network, $prefix_size, $comment) = ($1, $2, $3);
889 my $cidr = "$network/${prefix_size}";
890 $mynetworks->{$cidr} = {
892 network_address
=> $network,
893 prefix_size
=> $prefix_size,
894 comment
=> $comment // '',
897 warn "parse error in '$filename': $line\n";
905 sub write_pmg_mynetworks
{
906 my ($filename, $fh, $mynetworks) = @_;
908 foreach my $cidr (sort keys %$mynetworks) {
909 my $data = $mynetworks->{$cidr};
910 my $comment = $data->{comment
} // '*';
911 PVE
::Tools
::safe_print
($filename, $fh, "$cidr #$comment\n");
915 PVE
::INotify
::register_file
('mynetworks', $mynetworks_filename,
916 \
&read_pmg_mynetworks
,
917 \
&write_pmg_mynetworks
,
918 undef, always_call_parser
=> 1);
920 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
922 sub postmap_tls_policy
{
923 PMG
::Utils
::run_postmap
($tls_policy_map_filename);
926 my $transport_map_filename = "/etc/pmg/transport";
928 sub postmap_pmg_transport
{
929 PMG
::Utils
::run_postmap
($transport_map_filename);
932 sub read_transport_map
{
933 my ($filename, $fh) = @_;
935 return [] if !defined($fh);
941 while (defined(my $line = <$fh>)) {
943 next if $line =~ m/^\s*$/;
944 if ($line =~ m/^#(.*)\s*$/) {
949 my $parse_error = sub {
951 warn "parse error in '$filename': $line - $err";
955 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
956 my ($domain, $host, $port) = ($1, $2, $3);
958 eval { pmg_verify_transport_domain_or_email
($domain); };
960 $parse_error->($err);
964 if ($host =~ m/^\[(.*)\]$/) {
969 eval { PVE
::JSONSchema
::pve_verify_address
($host); };
971 $parse_error->($err);
982 $res->{$domain} = $data;
985 $parse_error->('wrong format');
992 sub write_transport_map
{
993 my ($filename, $fh, $tmap) = @_;
997 foreach my $domain (sort keys %$tmap) {
998 my $data = $tmap->{$domain};
1000 my $comment = $data->{comment
};
1001 PVE
::Tools
::safe_print
($filename, $fh, "#$comment\n")
1002 if defined($comment) && $comment !~ m/^\s*$/;
1004 my $use_mx = $data->{use_mx
};
1005 $use_mx = 0 if $data->{host
} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1008 PVE
::Tools
::safe_print
(
1009 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1011 PVE
::Tools
::safe_print
(
1012 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1017 PVE
::INotify
::register_file
('transport', $transport_map_filename,
1018 \
&read_transport_map
,
1019 \
&write_transport_map
,
1020 undef, always_call_parser
=> 1);
1022 # config file generation using templates
1024 sub get_template_vars
{
1027 my $vars = { pmg
=> $self->get_config() };
1029 my $nodename = PVE
::INotify
::nodename
();
1030 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
1031 $vars->{ipconfig
}->{int_ip
} = $int_ip;
1033 my $transportnets = [];
1035 if (my $tmap = PVE
::INotify
::read_file
('transport')) {
1036 foreach my $domain (sort keys %$tmap) {
1037 my $data = $tmap->{$domain};
1038 my $host = $data->{host
};
1039 if ($host =~ m/^$IPV4RE$/) {
1040 push @$transportnets, "$host/32";
1041 } elsif ($host =~ m/^$IPV6RE$/) {
1042 push @$transportnets, "[$host]/128";
1047 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
1049 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1051 if (my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip, 1)) {
1052 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1053 push @$mynetworks, "[$1]/$2";
1055 push @$mynetworks, $int_net_cidr;
1058 if ($int_ip =~ m/^$IPV6RE$/) {
1059 push @$mynetworks, "[$int_ip]/128";
1061 push @$mynetworks, "$int_ip/32";
1065 my $netlist = PVE
::INotify
::read_file
('mynetworks');
1066 foreach my $cidr (keys %$netlist) {
1067 if ($cidr =~ m/^($IPV6RE)\/(\d
+)$/) {
1068 push @$mynetworks, "[$1]/$2";
1070 push @$mynetworks, $cidr;
1074 push @$mynetworks, @$transportnets;
1076 # add default relay to mynetworks
1077 if (my $relay = $self->get('mail', 'relay')) {
1078 if ($relay =~ m/^$IPV4RE$/) {
1079 push @$mynetworks, "$relay/32";
1080 } elsif ($relay =~ m/^$IPV6RE$/) {
1081 push @$mynetworks, "[$relay]/128";
1083 # DNS name - do nothing ?
1087 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
1089 # normalize dnsbl_sites
1090 my @dnsbl_sites = PVE
::Tools
::split_list
($vars->{pmg
}->{mail
}->{dnsbl_sites
});
1091 if (scalar(@dnsbl_sites)) {
1092 $vars->{postfix
}->{dnsbl_sites
} = join(',', @dnsbl_sites);
1096 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1097 $self->get('mail', 'spf');
1098 $vars->{postfix
}->{usepolicy
} = $usepolicy;
1100 if ($int_ip =~ m/^$IPV6RE$/) {
1101 $vars->{postfix
}->{int_ip
} = "[$int_ip]";
1103 $vars->{postfix
}->{int_ip
} = $int_ip;
1106 my $resolv = PVE
::INotify
::read_file
('resolvconf');
1107 $vars->{dns
}->{hostname
} = $nodename;
1109 my $domain = $resolv->{search
} // 'localdomain';
1110 $vars->{dns
}->{domain
} = $domain;
1112 my $wlbr = "$nodename.$domain";
1113 foreach my $r (PVE
::Tools
::split_list
($vars->{pmg
}->{spam
}->{wl_bounce_relays
})) {
1116 $vars->{composed
}->{wl_bounce_relays
} = $wlbr;
1118 if (my $proxy = $vars->{pmg
}->{admin
}->{http_proxy
}) {
1120 my $uri = URI-
>new($proxy);
1121 my $host = $uri->host;
1122 my $port = $uri->port // 8080;
1124 my $data = { host
=> $host, port
=> $port };
1125 if (my $ui = $uri->userinfo) {
1126 my ($username, $pw) = split(/:/, $ui, 2);
1127 $data->{username
} = $username;
1128 $data->{password
} = $pw if defined($pw);
1130 $vars->{proxy
} = $data;
1133 warn "parse http_proxy failed - $@" if $@;
1139 # use one global TT cache
1140 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1142 my $template_toolkit;
1144 sub get_template_toolkit
{
1146 return $template_toolkit if $template_toolkit;
1148 $template_toolkit = Template-
>new({ INCLUDE_PATH
=> $tt_include_path });
1150 return $template_toolkit;
1153 # rewrite file from template
1154 # return true if file has changed
1155 sub rewrite_config_file
{
1156 my ($self, $tmplname, $dstfn) = @_;
1158 my $demo = $self->get('admin', 'demo');
1161 my $demosrc = "$tmplname.demo";
1162 $tmplname = $demosrc if -f
"/var/lib/pmg/templates/$demosrc";
1165 my ($perm, $uid, $gid);
1167 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1168 # needed if file contains a HTTPProxyPasswort
1170 $uid = getpwnam('clamav');
1171 $gid = getgrnam('adm');
1175 my $tt = get_template_toolkit
();
1177 my $vars = $self->get_template_vars();
1181 $tt->process($tmplname, $vars, \
$output) ||
1182 die $tt->error() . "\n";
1184 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1186 return 0 if defined($old) && ($old eq $output); # no change
1188 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
1190 if (defined($uid) && defined($gid)) {
1191 chown($uid, $gid, $dstfn);
1197 # rewrite spam configuration
1198 sub rewrite_config_spam
{
1201 my $use_awl = $self->get('spam', 'use_awl');
1202 my $use_bayes = $self->get('spam', 'use_bayes');
1203 my $use_razor = $self->get('spam', 'use_razor');
1207 # delete AW and bayes databases if those features are disabled
1209 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1213 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1214 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1215 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1218 # make sure we have a custom.cf file (else cluster sync fails)
1219 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1221 $changes = 1 if $self->rewrite_config_file(
1222 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1224 $changes = 1 if $self->rewrite_config_file(
1225 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1227 $changes = 1 if $self->rewrite_config_file(
1228 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1230 $changes = 1 if $self->rewrite_config_file(
1231 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1234 mkdir "/root/.razor";
1236 $changes = 1 if $self->rewrite_config_file(
1237 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1239 if (! -e
'/root/.razor/identity') {
1242 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
1243 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
1246 syslog
('info', "registering razor failed: $err") if $err;
1253 # rewrite ClamAV configuration
1254 sub rewrite_config_clam
{
1257 return $self->rewrite_config_file(
1258 'clamd.conf.in', '/etc/clamav/clamd.conf');
1261 sub rewrite_config_freshclam
{
1264 return $self->rewrite_config_file(
1265 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1268 sub rewrite_config_postgres
{
1271 my $pgconfdir = "/etc/postgresql/9.6/main";
1275 $changes = 1 if $self->rewrite_config_file(
1276 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1278 $changes = 1 if $self->rewrite_config_file(
1279 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1284 # rewrite /root/.forward
1285 sub rewrite_dot_forward
{
1288 my $dstfn = '/root/.forward';
1290 my $email = $self->get('admin', 'email');
1293 if ($email && $email =~ m/\s*(\S+)\s*/) {
1296 # empty .forward does not forward mails (see man local)
1299 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
1301 return 0 if defined($old) && ($old eq $output); # no change
1303 PVE
::Tools
::file_set_contents
($dstfn, $output);
1308 my $write_smtp_whitelist = sub {
1309 my ($filename, $data, $action) = @_;
1311 $action = 'OK' if !$action;
1313 my $old = PVE
::Tools
::file_get_contents
($filename, 1024*1024)
1317 foreach my $k (sort keys %$data) {
1318 $new .= "$k $action\n";
1321 return 0 if defined($old) && ($old eq $new); # no change
1323 PVE
::Tools
::file_set_contents
($filename, $new);
1325 PMG
::Utils
::run_postmap
($filename);
1330 sub rewrite_postfix_whitelist
{
1331 my ($rulecache) = @_;
1333 # see man page for regexp_table for postfix regex table format
1335 # we use a hash to avoid duplicate entries in regex tables
1338 my $clientlist = {};
1340 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1341 my $oclass = ref($obj);
1342 if ($oclass eq 'PMG::RuleDB::Receiver') {
1343 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1344 $tolist->{"/^$addr\$/"} = 1;
1345 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1346 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1347 $tolist->{"/^.+\@$addr\$/"} = 1;
1348 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1349 my $addr = $obj->{address
};
1351 $tolist->{"/^$addr\$/"} = 1;
1355 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1356 my $oclass = ref($obj);
1357 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1358 if ($oclass eq 'PMG::RuleDB::EMail') {
1359 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1360 $fromlist->{"/^$addr\$/"} = 1;
1361 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1362 my $addr = PMG
::Utils
::quote_regex
($obj->{address
});
1363 $fromlist->{"/^.+\@$addr\$/"} = 1;
1364 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1365 my $addr = $obj->{address
};
1367 $fromlist->{"/^$addr\$/"} = 1;
1368 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1369 $clientlist->{$obj->{address
}} = 1;
1370 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1371 $clientlist->{$obj->{address
}} = 1;
1375 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1376 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1377 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1378 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1381 # rewrite /etc/postfix/*
1382 sub rewrite_config_postfix
{
1383 my ($self, $rulecache) = @_;
1385 # make sure we have required files (else postfix start fails)
1386 IO
::File-
>new($transport_map_filename, 'a', 0644);
1390 if ($self->get('mail', 'tls')) {
1392 PMG
::Utils
::gen_proxmox_tls_cert
();
1394 syslog
('info', "generating certificate failed: $@") if $@;
1397 $changes = 1 if $self->rewrite_config_file(
1398 'main.cf.in', '/etc/postfix/main.cf');
1400 $changes = 1 if $self->rewrite_config_file(
1401 'master.cf.in', '/etc/postfix/master.cf');
1403 # make sure we have required files (else postfix start fails)
1404 # Note: postmap need a valid /etc/postfix/main.cf configuration
1405 postmap_pmg_domains
();
1406 postmap_pmg_transport
();
1407 postmap_tls_policy
();
1409 rewrite_postfix_whitelist
($rulecache) if $rulecache;
1411 # make sure aliases.db is up to date
1412 system('/usr/bin/newaliases');
1417 sub rewrite_config
{
1418 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1420 $force_restart = {} if ! $force_restart;
1422 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1423 $force_restart->{postfix
}) {
1424 PMG
::Utils
::service_cmd
('postfix', 'restart');
1427 if ($self->rewrite_dot_forward() && $restart_services) {
1428 # no need to restart anything
1431 if ($self->rewrite_config_postgres() && $restart_services) {
1432 # do nothing (too many side effects)?
1433 # does not happen anyways, because config does not change.
1436 if (($self->rewrite_config_spam() && $restart_services) ||
1437 $force_restart->{spam
}) {
1438 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1441 if (($self->rewrite_config_clam() && $restart_services) ||
1442 $force_restart->{clam
}) {
1443 PMG
::Utils
::service_cmd
('clamav-daemon', 'restart');
1446 if (($self->rewrite_config_freshclam() && $restart_services) ||
1447 $force_restart->{freshclam
}) {
1448 PMG
::Utils
::service_cmd
('clamav-freshclam', 'restart');