1 package PMG
::Config
::Base
;
8 use PVE
::JSONSchema
qw(get_standard_option);
9 use PVE
::SectionConfig
;
11 use base
qw(PVE::SectionConfig);
15 type
=> { description
=> "Section type." },
17 description
=> "Secion ID.",
18 type
=> 'string', format
=> 'pve-configid',
27 sub format_section_header
{
28 my ($class, $type, $sectionId) = @_;
30 die "internal error ($type ne $sectionId)" if $type ne $sectionId;
32 return "section: $type\n";
36 sub parse_section_header
{
37 my ($class, $line) = @_;
39 if ($line =~ m/^section:\s*(\S+)\s*$/) {
41 my $errmsg = undef; # set if you want to skip whole section
42 eval { PVE
::JSONSchema
::pve_verify_configid
($section); };
44 my $config = {}; # to return additional attributes
45 return ($section, $section, $errmsg, $config);
50 package PMG
::Config
::Admin
;
55 use base
qw(PMG::Config::Base);
64 description
=> "Send daily reports.",
69 description
=> "Demo mode - do not start SMTP filter.",
74 description
=> "Administrator E-Mail address.",
75 type
=> 'string', format
=> 'email',
76 default => 'admin@domain.tld',
79 description
=> "HTTP proxy port.",
85 description
=> "HTTP proxy server address.",
89 description
=> "HTTP proxy user name.",
93 description
=> "HTTP proxy password.",
101 dailyreport
=> { optional
=> 1 },
102 demo
=> { optional
=> 1 },
103 proxyport
=> { optional
=> 1 },
104 proxyserver
=> { optional
=> 1 },
105 proxyuser
=> { optional
=> 1 },
106 proxypassword
=> { optional
=> 1 },
110 package PMG
::Config
::Spam
;
115 use base
qw(PMG::Config::Base);
124 description
=> "This option is used to specify which languages are considered OK for incoming mail.",
126 pattern
=> '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
130 description
=> "Whether to use the naive-Bayesian-style classifier.",
135 description
=> "Use the Auto-Whitelist plugin.",
140 description
=> "Whether to use Razor2, if it is available.",
145 description
=> "Enable OCR to scan pictures.",
149 wl_bounce_relays
=> {
150 description
=> "Whitelist legitimate bounce relays.",
154 description
=> "Additional score for bounce mails.",
161 description
=> "Enable real time blacklists (RBL) checks.",
166 description
=> "Maximum size of spam messages in bytes.",
176 use_awl
=> { optional
=> 1 },
177 use_razor
=> { optional
=> 1 },
178 use_ocr
=> { optional
=> 1 },
179 wl_bounce_relays
=> { optional
=> 1 },
180 languages
=> { optional
=> 1 },
181 use_bayes
=> { optional
=> 1 },
182 bounce_score
=> { optional
=> 1 },
183 rbl_checks
=> { optional
=> 1 },
184 maxspamsize
=> { optional
=> 1 },
188 package PMG
::Config
::ClamAV
;
193 use base
qw(PMG::Config::Base);
202 description
=> "ClamAV database mirror server.",
204 default => 'database.clamav.net',
206 archiveblockencrypted
=> {
207 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
212 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.",
218 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.",
224 description
=> "Files larger than this limit won't be scanned.",
230 description
=> "Sets the maximum amount of data to be scanned for each input file.",
233 default => 100000000,
236 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
246 archiveblockencrypted
=> { optional
=> 1 },
247 archivemaxrec
=> { optional
=> 1 },
248 archivemaxfiles
=> { optional
=> 1 },
249 archivemaxsize
=> { optional
=> 1 },
250 maxscansize
=> { optional
=> 1 },
251 dbmirror
=> { optional
=> 1 },
252 maxcccount
=> { optional
=> 1 },
256 package PMG
::Config
::Mail
;
261 use PVE
::ProcFSTools
;
263 use base
qw(PMG::Config::Base);
270 sub physical_memory
{
272 return $physicalmem if $physicalmem;
274 my $info = PVE
::ProcFSTools
::read_meminfo
();
275 my $total = int($info->{memtotal
} / (1024*1024));
280 sub get_max_filters
{
281 # estimate optimal number of filter servers
285 my $memory = physical_memory
();
286 my $add_servers = int(($memory - 512)/$servermem);
287 $max_servers += $add_servers if $add_servers > 0;
288 $max_servers = 40 if $max_servers > 40;
290 return $max_servers - 2;
294 # estimate optimal number of smtpd daemons
296 my $max_servers = 25;
298 my $memory = physical_memory
();
299 my $add_servers = int(($memory - 512)/$servermem);
300 $max_servers += $add_servers if $add_servers > 0;
301 $max_servers = 100 if $max_servers > 100;
306 # estimate optimal number of proxpolicy servers
308 my $memory = physical_memory
();
309 $max_servers = 5 if $memory >= 500;
316 description
=> "The default mail delivery transport (incoming mails).",
320 description
=> "SMTP port number for relay host.",
327 description
=> "Disable MX lookups for default relay.",
332 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
336 description
=> "ESMTP banner.",
339 default => 'ESMTP Proxmox',
342 description
=> "Maximum number of pmg-smtp-filter processes.",
346 default => get_max_filters
(),
349 description
=> "Maximum number of pmgpolicy processes.",
353 default => get_max_policy
(),
356 description
=> "Maximum number of SMTP daemon processes (in).",
360 default => get_max_smtpd
(),
363 description
=> "Maximum number of SMTP daemon processes (out).",
367 default => get_max_smtpd
(),
369 conn_count_limit
=> {
370 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
376 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.",
381 message_rate_limit
=> {
382 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.",
388 description
=> "Hide received header in outgoing mails.",
393 description
=> "Maximum email size. Larger mails are rejected.",
396 default => 1024*1024*10,
399 description
=> "SMTP delay warning time (in hours).",
405 description
=> "Use Realtime Blacklists.",
410 description
=> "Use TLS.",
415 description
=> "Use Sender Policy Framework.",
420 description
=> "Use Greylisting.",
425 description
=> "Use SMTP HELO tests.",
430 description
=> "Reject unknown clients.",
434 rejectunknownsender
=> {
435 description
=> "Reject unknown senders.",
440 description
=> "Enable receiver verification. The value (if greater than 0) spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address (450 or 550).",
447 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
455 relay
=> { optional
=> 1 },
456 relayport
=> { optional
=> 1 },
457 relaynomx
=> { optional
=> 1 },
458 dwarning
=> { optional
=> 1 },
459 max_smtpd_in
=> { optional
=> 1 },
460 max_smtpd_out
=> { optional
=> 1 },
461 greylist
=> { optional
=> 1 },
462 helotests
=> { optional
=> 1 },
463 use_rbl
=> { optional
=> 1 },
464 tls
=> { optional
=> 1 },
465 spf
=> { optional
=> 1 },
466 maxsize
=> { optional
=> 1 },
467 banner
=> { optional
=> 1 },
468 max_filters
=> { optional
=> 1 },
469 max_policy
=> { optional
=> 1 },
470 hide_received
=> { optional
=> 1 },
471 rejectunknown
=> { optional
=> 1 },
472 rejectunknownsender
=> { optional
=> 1 },
473 conn_count_limit
=> { optional
=> 1 },
474 conn_rate_limit
=> { optional
=> 1 },
475 message_rate_limit
=> { optional
=> 1 },
476 verifyreceivers
=> { optional
=> 1 },
477 dnsbl_sites
=> { optional
=> 1 },
492 PMG
::Config
::Admin-
>register();
493 PMG
::Config
::Mail-
>register();
494 PMG
::Config
::Spam-
>register();
495 PMG
::Config
::ClamAV-
>register();
497 # initialize all plugins
498 PMG
::Config
::Base-
>init();
504 my $class = ref($type) || $type;
506 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
508 return bless $cfg, $class;
514 PVE
::INotify
::write_file
("pmg.conf", $self);
517 my $lockfile = "/var/lock/pmgconfig.lck";
520 my ($code, $errmsg) = @_;
522 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
524 $errmsg ?
die "$errmsg: $err" : die $err;
530 my ($self, $section, $key, $value) = @_;
532 my $pdata = PMG
::Config
::Base-
>private();
534 my $plugin = $pdata->{plugins
}->{$section};
535 die "no such section '$section'" if !$plugin;
537 if (defined($value)) {
538 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
539 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
540 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
542 if (defined($self->{ids
}->{$section})) {
543 delete $self->{ids
}->{$section}->{$key};
550 # get section value or default
552 my ($self, $section, $key) = @_;
554 my $pdata = PMG
::Config
::Base-
>private();
555 return undef if !defined($pdata->{options
}->{$section});
556 return undef if !defined($pdata->{options
}->{$section}->{$key});
557 my $pdesc = $pdata->{propertyList
}->{$key};
558 return undef if !defined($pdesc);
560 if (defined($self->{ids
}->{$section}) &&
561 defined(my $value = $self->{ids
}->{$section}->{$key})) {
565 return $pdesc->{default};
568 # get a whole section with default value
570 my ($self, $section) = @_;
572 my $pdata = PMG
::Config
::Base-
>private();
573 return undef if !defined($pdata->{options
}->{$section});
577 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
579 my $pdesc = $pdata->{propertyList
}->{$key};
581 if (defined($self->{ids
}->{$section}) &&
582 defined(my $value = $self->{ids
}->{$section}->{$key})) {
583 $res->{$key} = $value;
586 $res->{$key} = $pdesc->{default};
592 # get a whole config with default values
596 my $pdata = PMG
::Config
::Base-
>private();
600 foreach my $type (keys %{$pdata->{plugins
}}) {
601 my $plugin = $pdata->{plugins
}->{$type};
602 $res->{$type} = $self->get_section($type);
609 my ($filename, $fh) = @_;
611 local $/ = undef; # slurp mode
615 return PMG
::Config
::Base-
>parse_config($filename, $raw);
619 my ($filename, $fh, $cfg) = @_;
621 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
623 PVE
::Tools
::safe_print
($filename, $fh, $raw);
626 PVE
::INotify
::register_file
('pmg.conf', "/etc/proxmox/pmg.conf",
630 # parsers/writers for other files
632 my $domainsfilename = "/etc/proxmox/domains";
634 sub read_pmg_domains
{
635 my ($filename, $fh) = @_;
640 while (defined(my $line = <$fh>)) {
641 if ($line =~ m/^\s*(\S+)\s*$/) {
643 push @$domains, $domain;
651 sub write_pmg_domains
{
652 my ($filename, $fh, $domain) = @_;
654 foreach my $domain (sort @$domain) {
655 PVE
::Tools
::safe_print
($filename, $fh, "$domain\n");
659 PVE
::INotify
::register_file
('domains', $domainsfilename,
662 undef, always_call_parser
=> 1);
664 my $transport_map_filename = "/etc/postfix/transport";
666 sub read_transport_map
{
667 my ($filename, $fh) = @_;
669 return [] if !defined($fh);
673 while (defined(my $line = <$fh>)) {
675 next if $line =~ m/^\s*$/;
676 next if $line =~ m/^\s*\#/;
678 if ($line =~ m/^(\S+)\s+smtp:([^\s:]+):(\d+)\s*$/) {
684 if ($host =~ m/^\[(.*)\]$/) {
689 my $key = "$host:$port";
691 $res->{$key}->{nomx
} = $nomx;
692 $res->{$key}->{host
} = $host;
693 $res->{$key}->{port
} = $port;
694 $res->{$key}->{transport
} = $key;
696 push @{$res->{$key}->{domains
}}, $domain;
702 foreach my $t (sort keys %$res) {
703 push @$ta, $res->{$t};
709 sub write_ransport_map
{
710 my ($filename, $fh, $tmap) = @_;
714 foreach my $t (sort { $a->{transport
} cmp $b->{transport
} } @$tmap) {
715 my $domains = $t->{domains
};
717 foreach my $d (sort @$domains) {
719 PVE
::Tools
::safe_print
($filename, $fh, "$d smtp:[$t->{host}]:$t->{port}\n");
721 PVE
::Tools
::safe_print
($filename, $fh, "$d smtp:$t->{host}:$t->{port}\n");
727 PVE
::INotify
::register_file
('transport', $transport_map_filename,
728 \
&read_transport_map
,
729 \
&write_ransport_map
,
730 undef, always_call_parser
=> 1);
732 # config file generation using templates
734 sub get_template_vars
{
737 my $vars = { pmg
=> $self->get_config() };
739 my $nodename = PVE
::INotify
::nodename
();
740 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
741 my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip);
743 $vars->{ipconfig
}->{int_ip
} = $int_ip;
744 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
745 $vars->{ipconfig
}->{int_port
} = 26;
746 $vars->{ipconfig
}->{ext_port
} = 25;
748 my $transportnets = []; # fixme
749 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
751 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
752 push @$mynetworks, @$transportnets;
753 push @$mynetworks, $int_net_cidr;
755 # add default relay to mynetworks
756 if (my $relay = $self->get('mail', 'relay')) {
757 if (Net
::IP
::ip_is_ipv4
($relay)) {
758 push @$mynetworks, "$relay/32";
759 } elsif (Net
::IP
::ip_is_ipv6
($relay)) {
760 push @$mynetworks, "[$relay]/128";
762 warn "unable to detect IP version of relay '$relay'";
766 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
769 $usepolicy = 1 if $self->get('mail', 'greylist') ||
770 $self->get('mail', 'spf') || $self->get('mail', 'use_rbl');
771 $vars->{postfix
}->{usepolicy
} = $usepolicy;
773 my $resolv = PVE
::INotify
::read_file
('resolvconf');
774 $vars->{dns
}->{hostname
} = $nodename;
775 $vars->{dns
}->{domain
} = $resolv->{search
};
780 # rewrite file from template
781 # return true if file has changed
782 sub rewrite_config_file
{
783 my ($self, $tmplname, $dstfn) = @_;
785 my $demo = $self->get('admin', 'demo');
787 my $srcfn = ($tmplname =~ m
|^.?
/|) ?
788 $tmplname : "/var/lib/pmg/templates/$tmplname";
791 my $demosrc = "$srcfn.demo";
792 $srcfn = $demosrc if -f
$demosrc;
795 my ($perm, $uid, $gid);
797 my $srcfd = IO
::File-
>new ($srcfn, "r")
798 || die "cant read template '$srcfn' - $!: ERROR";
800 if ($dstfn eq '/etc/fetchmailrc') {
801 (undef, undef, $uid, $gid) = getpwnam('fetchmail');
803 } elsif ($dstfn eq '/etc/clamav/freshclam.conf') {
804 # needed if file contains a HTTPProxyPasswort
806 $uid = getpwnam('clamav');
807 $gid = getgrnam('adm');
811 my $template = Template-
>new({});
813 my $vars = $self->get_template_vars();
817 $template->process($srcfd, $vars, \
$output) ||
818 die $template->error();
822 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
824 return 0 if defined($old) && ($old eq $output); # no change
826 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
828 if (defined($uid) && defined($gid)) {
829 chown($uid, $gid, $dstfn);
835 # rewrite spam configuration
836 sub rewrite_config_spam
{
839 my $use_awl = $self->get('spam', 'use_awl');
840 my $use_bayes = $self->get('spam', 'use_bayes');
841 my $use_razor = $self->get('spam', 'use_razor');
845 # delete AW and bayes databases if those features are disabled
847 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
851 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
852 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
853 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
856 # make sure we have a custom.cf file (else cluster sync fails)
857 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
859 $changes = 1 if $self->rewrite_config_file(
860 'local.cf.in', '/etc/mail/spamassassin/local.cf');
862 $changes = 1 if $self->rewrite_config_file(
863 'init.pre.in', '/etc/mail/spamassassin/init.pre');
865 $changes = 1 if $self->rewrite_config_file(
866 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
868 $changes = 1 if $self->rewrite_config_file(
869 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
872 mkdir "/root/.razor";
874 $changes = 1 if $self->rewrite_config_file(
875 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
877 if (! -e
'/root/.razor/identity') {
880 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
881 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
884 syslog
('info', msgquote
("registering razor failed: $err")) if $err;
891 # rewrite ClamAV configuration
892 sub rewrite_config_clam
{
895 return $self->rewrite_config_file(
896 'clamd.conf.in', '/etc/clamav/clamd.conf');
899 sub rewrite_config_freshclam
{
902 return $self->rewrite_config_file(
903 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
906 sub rewrite_config_postgres
{
909 my $pgconfdir = "/etc/postgresql/9.6/main";
913 $changes = 1 if $self->rewrite_config_file(
914 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
916 $changes = 1 if $self->rewrite_config_file(
917 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
922 # rewrite /root/.forward
923 sub rewrite_dot_forward
{
926 my $dstfn = '/root/.forward';
928 my $email = $self->get('administration', 'email');
931 if ($email && $email =~ m/\s*(\S+)\s*/) {
934 # empty .forward does not forward mails (see man local)
937 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
939 return 0 if defined($old) && ($old eq $output); # no change
941 PVE
::Tools
::file_set_contents
($dstfn, $output);
946 # rewrite /etc/postfix/*
947 sub rewrite_config_postfix
{
950 # make sure we have required files (else postfix start fails)
951 IO
::File-
>new($domainsfilename, 'a', 0644);
952 IO
::File-
>new($transport_map_filename, 'a', 0644);
956 if ($self->get('mail', 'tls')) {
958 my $resolv = PVE
::INotify
::read_file
('resolvconf');
959 my $domain = $resolv->{search
};
961 my $company = $domain; # what else ?
962 my $cn = "*.$domain";
963 PMG
::Utils
::gen_proxmox_tls_cert
(0, $company, $cn);
965 syslog
('info', msgquote
("generating certificate failed: $@")) if $@;
968 $changes = 1 if $self->rewrite_config_file(
969 'main.cf.in', '/etc/postfix/main.cf');
971 $changes = 1 if $self->rewrite_config_file(
972 'master.cf.in', '/etc/postfix/master.cf');
974 #rewrite_config_transports ($class);
975 #rewrite_config_whitelist ($class);
976 #rewrite_config_tls_policy ($class);
978 # make sure aliases.db is up to date
979 system('/usr/bin/newaliases');
985 my ($self, $restart_services) = @_;
987 if ($self->rewrite_config_postfix() && $restart_services) {
988 PMG
::Utils
::service_cmd
('postfix', 'restart');
991 if ($self->rewrite_dot_forward() && $restart_services) {
992 # no need to restart anything
995 if ($self->rewrite_config_postgres() && $restart_services) {
996 # do nothing (too many side effects)?
997 # does not happen anyways, because config does not change.
1000 if ($self->rewrite_config_spam() && $restart_services) {
1001 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
1004 if ($self->rewrite_config_clam() && $restart_services) {
1005 PMG
::Utils
::service_cmd
('clamd', 'restart');
1008 if ($self->rewrite_config_freshclam() && $restart_services) {
1009 PMG
::Utils
::service_cmd
('freshclam', 'restart');