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 if ($type eq 'ldap') {
31 $sectionId =~ s/^ldap_//;
32 return "$type: $sectionId\n";
34 return "section: $type\n";
39 sub parse_section_header
{
40 my ($class, $line) = @_;
42 if ($line =~ m/^(ldap|section):\s*(\S+)\s*$/) {
43 my ($raw_type, $raw_id) = (lc($1), $2);
44 my $type = $raw_type eq 'section' ?
$raw_id : $raw_type;
45 my $section_id = "${raw_type}_${raw_id}";
46 my $errmsg = undef; # set if you want to skip whole section
47 eval { PVE
::JSONSchema
::pve_verify_configid
($raw_id); };
49 my $config = {}; # to return additional attributes
50 return ($type, $section_id, $errmsg, $config);
55 package PMG
::Config
::Admin
;
60 use base
qw(PMG::Config::Base);
69 description
=> "Send daily reports.",
74 description
=> "Demo mode - do not start SMTP filter.",
79 description
=> "Administrator E-Mail address.",
80 type
=> 'string', format
=> 'email',
81 default => 'admin@domain.tld',
84 description
=> "HTTP proxy port.",
90 description
=> "HTTP proxy server address.",
94 description
=> "HTTP proxy user name.",
98 description
=> "HTTP proxy password.",
106 dailyreport
=> { optional
=> 1 },
107 demo
=> { optional
=> 1 },
108 proxyport
=> { optional
=> 1 },
109 proxyserver
=> { optional
=> 1 },
110 proxyuser
=> { optional
=> 1 },
111 proxypassword
=> { optional
=> 1 },
115 package PMG
::Config
::Spam
;
120 use base
qw(PMG::Config::Base);
129 description
=> "This option is used to specify which languages are considered OK for incoming mail.",
131 pattern
=> '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
135 description
=> "Whether to use the naive-Bayesian-style classifier.",
140 description
=> "Use the Auto-Whitelist plugin.",
145 description
=> "Whether to use Razor2, if it is available.",
150 description
=> "Enable OCR to scan pictures.",
154 wl_bounce_relays
=> {
155 description
=> "Whitelist legitimate bounce relays.",
159 description
=> "Additional score for bounce mails.",
166 description
=> "Enable real time blacklists (RBL) checks.",
171 description
=> "Maximum size of spam messages in bytes.",
181 use_awl
=> { optional
=> 1 },
182 use_razor
=> { optional
=> 1 },
183 use_ocr
=> { optional
=> 1 },
184 wl_bounce_relays
=> { optional
=> 1 },
185 languages
=> { optional
=> 1 },
186 use_bayes
=> { optional
=> 1 },
187 bounce_score
=> { optional
=> 1 },
188 rbl_checks
=> { optional
=> 1 },
189 maxspamsize
=> { optional
=> 1 },
193 package PMG
::Config
::ClamAV
;
198 use base
qw(PMG::Config::Base);
207 description
=> "ClamAV database mirror server.",
209 default => 'database.clamav.net',
211 archiveblockencrypted
=> {
212 description
=> "Wether to block encrypted archives. Mark encrypted archives as viruses.",
217 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.",
223 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.",
229 description
=> "Files larger than this limit won't be scanned.",
235 description
=> "Sets the maximum amount of data to be scanned for each input file.",
238 default => 100000000,
241 description
=> "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
251 archiveblockencrypted
=> { optional
=> 1 },
252 archivemaxrec
=> { optional
=> 1 },
253 archivemaxfiles
=> { optional
=> 1 },
254 archivemaxsize
=> { optional
=> 1 },
255 maxscansize
=> { optional
=> 1 },
256 dbmirror
=> { optional
=> 1 },
257 maxcccount
=> { optional
=> 1 },
261 package PMG
::Config
::LDAP
;
266 use base
qw(PMG::Config::Base);
275 description
=> "LDAP protocol mode ('ldap' or 'ldaps').",
277 enum
=> ['ldap', 'ldaps'],
285 mode
=> { optional
=> 1 },
289 package PMG
::Config
::Mail
;
294 use PVE
::ProcFSTools
;
296 use base
qw(PMG::Config::Base);
303 sub physical_memory
{
305 return $physicalmem if $physicalmem;
307 my $info = PVE
::ProcFSTools
::read_meminfo
();
308 my $total = int($info->{memtotal
} / (1024*1024));
313 sub get_max_filters
{
314 # estimate optimal number of filter servers
318 my $memory = physical_memory
();
319 my $add_servers = int(($memory - 512)/$servermem);
320 $max_servers += $add_servers if $add_servers > 0;
321 $max_servers = 40 if $max_servers > 40;
323 return $max_servers - 2;
327 # estimate optimal number of smtpd daemons
329 my $max_servers = 25;
331 my $memory = physical_memory
();
332 my $add_servers = int(($memory - 512)/$servermem);
333 $max_servers += $add_servers if $add_servers > 0;
334 $max_servers = 100 if $max_servers > 100;
342 description
=> "The default mail delivery transport (incoming mails).",
346 description
=> "SMTP port number for relay host.",
353 description
=> "Disable MX lookups for default relay.",
358 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
362 description
=> "ESMTP banner.",
365 default => 'ESMTP Proxmox',
368 description
=> "Maximum number of filter processes.",
372 default => get_max_filters
(),
375 description
=> "Maximum number of SMTP daemon processes (in).",
379 default => get_max_smtpd
(),
382 description
=> "Maximum number of SMTP daemon processes (out).",
386 default => get_max_smtpd
(),
388 conn_count_limit
=> {
389 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
395 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.",
400 message_rate_limit
=> {
401 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.",
407 description
=> "Hide received header in outgoing mails.",
412 description
=> "Maximum email size. Larger mails are rejected.",
415 default => 1024*1024*10,
418 description
=> "SMTP delay warning time (in hours).",
424 description
=> "Use Realtime Blacklists.",
429 description
=> "Use TLS.",
434 description
=> "Use Sender Policy Framework.",
439 description
=> "Use Greylisting.",
444 description
=> "Use SMTP HELO tests.",
449 description
=> "Reject unknown clients.",
453 rejectunknownsender
=> {
454 description
=> "Reject unknown senders.",
459 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).",
466 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
474 relay
=> { optional
=> 1 },
475 relayport
=> { optional
=> 1 },
476 relaynomx
=> { optional
=> 1 },
477 dwarning
=> { optional
=> 1 },
478 max_smtpd_in
=> { optional
=> 1 },
479 max_smtpd_out
=> { optional
=> 1 },
480 greylist
=> { optional
=> 1 },
481 helotests
=> { optional
=> 1 },
482 use_rbl
=> { optional
=> 1 },
483 tls
=> { optional
=> 1 },
484 spf
=> { optional
=> 1 },
485 maxsize
=> { optional
=> 1 },
486 banner
=> { optional
=> 1 },
487 max_filters
=> { optional
=> 1 },
488 hide_received
=> { optional
=> 1 },
489 rejectunknown
=> { optional
=> 1 },
490 rejectunknownsender
=> { optional
=> 1 },
491 conn_count_limit
=> { optional
=> 1 },
492 conn_rate_limit
=> { optional
=> 1 },
493 message_rate_limit
=> { optional
=> 1 },
494 verifyreceivers
=> { optional
=> 1 },
495 dnsbl_sites
=> { optional
=> 1 },
512 PMG
::Config
::Admin-
>register();
513 PMG
::Config
::Mail-
>register();
514 PMG
::Config
::Spam-
>register();
515 PMG
::Config
::LDAP-
>register();
516 PMG
::Config
::ClamAV-
>register();
518 # initialize all plugins
519 PMG
::Config
::Base-
>init();
525 my $class = ref($type) || $type;
527 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
529 return bless $cfg, $class;
535 PVE
::INotify
::write_file
("pmg.conf", $self);
538 my $lockfile = "/var/lock/pmgconfig.lck";
541 my ($code, $errmsg) = @_;
543 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
545 $errmsg ?
die "$errmsg: $err" : die $err;
550 # this does not work for ldap entries
552 my ($self, $section, $key, $value) = @_;
554 my $pdata = PMG
::Config
::Base-
>private();
556 die "internal error" if $section eq 'ldap';
558 my $plugin = $pdata->{plugins
}->{$section};
559 die "no such section '$section'" if !$plugin;
561 my $configid = "section_$section";
562 if (defined($value)) {
563 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
564 print Dumper
($self->{ids
});
565 $self->{ids
}->{$configid} = { type
=> $section } if !defined($self->{ids
}->{$configid});
566 $self->{ids
}->{$configid}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
568 if (defined($self->{ids
}->{$configid})) {
569 delete $self->{ids
}->{$configid}->{$key};
576 # get section value or default
577 # this does not work for ldap entries
579 my ($self, $section, $key) = @_;
581 my $pdata = PMG
::Config
::Base-
>private();
582 return undef if !defined($pdata->{options
}->{$section});
583 return undef if !defined($pdata->{options
}->{$section}->{$key});
584 my $pdesc = $pdata->{propertyList
}->{$key};
585 return undef if !defined($pdesc);
587 my $configid = "section_$section";
588 if (defined($self->{ids
}->{$configid}) &&
589 defined(my $value = $self->{ids
}->{$configid}->{$key})) {
593 return $pdesc->{default};
596 # get a whole section with default value
597 # this does not work for ldap entries
599 my ($self, $section) = @_;
601 my $pdata = PMG
::Config
::Base-
>private();
602 return undef if !defined($pdata->{options
}->{$section});
606 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
608 my $pdesc = $pdata->{propertyList
}->{$key};
610 my $configid = "section_$section";
611 if (defined($self->{ids
}->{$configid}) &&
612 defined(my $value = $self->{ids
}->{$configid}->{$key})) {
613 $res->{$key} = $value;
616 $res->{$key} = $pdesc->{default};
622 # get a whole config with default values
623 # this does not work for ldap entries
627 my $pdata = PMG
::Config
::Base-
>private();
631 foreach my $type (keys %{$pdata->{plugins
}}) {
632 next if $type eq 'ldap';
633 my $plugin = $pdata->{plugins
}->{$type};
634 $res->{$type} = $self->get_section($type);
641 my ($filename, $fh) = @_;
643 local $/ = undef; # slurp mode
647 return PMG
::Config
::Base-
>parse_config($filename, $raw);
651 my ($filename, $fh, $cfg) = @_;
653 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
655 PVE
::Tools
::safe_print
($filename, $fh, $raw);
658 PVE
::INotify
::register_file
('pmg.conf', "/etc/proxmox/pmg.conf",
662 # parsers/writers for other files
664 my $domainsfilename = "/etc/proxmox/domains";
666 sub read_pmg_domains
{
667 my ($filename, $fh) = @_;
672 while (defined(my $line = <$fh>)) {
673 if ($line =~ m/^\s*(\S+)\s*$/) {
675 push @$domains, $domain;
683 sub write_pmg_domains
{
684 my ($filename, $fh, $domain) = @_;
686 foreach my $domain (sort @$domain) {
687 PVE
::Tools
::safe_print
($filename, $fh, "$domain\n");
691 PVE
::INotify
::register_file
('domains', $domainsfilename,
694 undef, always_call_parser
=> 1);
696 my $transport_map_filename = "/etc/postfix/transport";
698 sub read_transport_map
{
699 my ($filename, $fh) = @_;
701 return [] if !defined($fh);
705 while (defined(my $line = <$fh>)) {
707 next if $line =~ m/^\s*$/;
708 next if $line =~ m/^\s*\#/;
710 if ($line =~ m/^(\S+)\s+smtp:([^\s:]+):(\d+)\s*$/) {
716 if ($host =~ m/^\[(.*)\]$/) {
721 my $key = "$host:$port";
723 $res->{$key}->{nomx
} = $nomx;
724 $res->{$key}->{host
} = $host;
725 $res->{$key}->{port
} = $port;
726 $res->{$key}->{transport
} = $key;
728 push @{$res->{$key}->{domains
}}, $domain;
734 foreach my $t (sort keys %$res) {
735 push @$ta, $res->{$t};
741 sub write_ransport_map
{
742 my ($filename, $fh, $tmap) = @_;
746 foreach my $t (sort { $a->{transport
} cmp $b->{transport
} } @$tmap) {
747 my $domains = $t->{domains
};
749 foreach my $d (sort @$domains) {
751 PVE
::Tools
::safe_print
($filename, $fh, "$d smtp:[$t->{host}]:$t->{port}\n");
753 PVE
::Tools
::safe_print
($filename, $fh, "$d smtp:$t->{host}:$t->{port}\n");
759 PVE
::INotify
::register_file
('transport', $transport_map_filename,
760 \
&read_transport_map
,
761 \
&write_ransport_map
,
762 undef, always_call_parser
=> 1);
764 # config file generation using templates
766 sub rewrite_config_file
{
767 my ($self, $tmplname, $dstfn) = @_;
769 my $demo = $self->get('admin', 'demo');
771 my $srcfn = ($tmplname =~ m
|^.?
/|) ?
772 $tmplname : "/var/lib/pmg/templates/$tmplname";
775 my $demosrc = "$srcfn.demo";
776 $srcfn = $demosrc if -f
$demosrc;
779 my $srcfd = IO
::File-
>new ($srcfn, "r")
780 || die "cant read template '$srcfn' - $!: ERROR";
781 my $dstfd = PMG
::AtomicFile-
>open ($dstfn, "w")
782 || die "cant open config file '$dstfn' - $!: ERROR";
784 if ($dstfn eq '/etc/fetchmailrc') {
785 my ($login, $pass, $uid, $gid) = getpwnam('fetchmail');
787 chown($uid, $gid, ${*$dstfd}{'io_atomicfile_temp'});
789 chmod (0600, ${*$dstfd}{'io_atomicfile_temp'});
790 } elsif ($dstfn eq '/etc/clamav/freshclam.conf') {
791 # needed if file contains a HTTPProxyPasswort
793 my $uid = getpwnam('clamav');
794 my $gid = getgrnam('adm');
797 chown ($uid, $gid, ${*$dstfd}{'io_atomicfile_temp'});
799 chmod (0600, ${*$dstfd}{'io_atomicfile_temp'});
802 my $template = Template-
>new({});
804 my $vars = { pmg
=> $self->get_config() };
806 my $nodename = PVE
::INotify
::nodename
();
807 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
808 my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip);
810 $vars->{ipconfig
}->{int_ip
} = $int_ip;
811 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
812 $vars->{ipconfig
}->{int_port
} = 26;
813 $vars->{ipconfig
}->{ext_port
} = 25;
815 my $transportnets = []; # fixme
816 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
818 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
819 push @$mynetworks, @$transportnets;
820 push @$mynetworks, $int_net_cidr;
822 # add default relay to mynetworks
823 if (my $relay = $self->get('mail', 'relay')) {
824 if (Net
::IP
::ip_is_ipv4
($relay)) {
825 push @$mynetworks, "$relay/32";
826 } elsif (Net
::IP
::ip_is_ipv6
($relay)) {
827 push @$mynetworks, "[$relay]/128";
829 warn "unable to detect IP version of relay '$relay'";
833 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
836 $usepolicy = 1 if $self->get('mail', 'greylist') ||
837 $self->get('mail', 'spf') || $self->get('mail', 'use_rbl');
838 $vars->{postfix
}->{usepolicy
} = $usepolicy;
840 my $resolv = PVE
::INotify
::read_file
('resolvconf');
841 $vars->{dns
}->{hostname
} = $nodename;
842 $vars->{dns
}->{domain
} = $resolv->{search
};
844 $template->process($srcfd, $vars, $dstfd) ||
845 die $template->error();
851 sub rewrite_config_script
{
852 my ($self, $tmplname, $dstfn) = @_;
854 $self->rewrite_config_file($tmplname, $dstfn);
855 system("chmod +x $dstfn");
858 # rewrite spam configuration
859 sub rewrite_config_spam
{
862 my $use_awl = $self->get('spam', 'use_awl');
863 my $use_bayes = $self->get('spam', 'use_bayes');
864 my $use_razor = $self->get('spam', 'use_razor');
866 # delete AW and bayes databases if those features are disabled
867 unlink '/root/.spamassassin/auto-whitelist' if !$use_awl;
869 unlink '/root/.spamassassin/bayes_journal';
870 unlink '/root/.spamassassin/bayes_seen';
871 unlink '/root/.spamassassin/bayes_toks';
874 # make sure we have a custom.cf file (else cluster sync fails)
875 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
877 $self->rewrite_config_file('local.cf.in', '/etc/mail/spamassassin/local.cf');
878 $self->rewrite_config_file('init.pre.in', '/etc/mail/spamassassin/init.pre');
879 $self->rewrite_config_file('v310.pre.in', '/etc/mail/spamassassin/v310.pre');
880 $self->rewrite_config_file('v320.pre.in', '/etc/mail/spamassassin/v320.pre');
883 mkdir "/root/.razor";
884 $self->rewrite_config_file('razor-agent.conf.in', '/root/.razor/razor-agent.conf');
885 if (! -e
'/root/.razor/identity') {
888 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
889 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
892 syslog
('info', msgquote
("registering razor failed: $err")) if $err;
897 # rewrite ClamAV configuration
898 sub rewrite_config_clam
{
901 $self->rewrite_config_file('clamd.conf.in', '/etc/clamav/clamd.conf');
902 $self->rewrite_config_file('freshclam.conf.in', '/etc/clamav/freshclam.conf');
905 sub rewrite_config_postgres
{
908 my $pgconfdir = "/etc/postgresql/9.6/main";
910 $self->rewrite_config_file('pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
911 $self->rewrite_config_file('postgresql.conf.in', "$pgconfdir/postgresql.conf");
914 # rewrite /root/.forward
915 sub rewrite_dot_forward
{
918 my $fname = '/root/.forward';
920 my $email = $self->get('administration', 'email');
921 open(TMP
, ">$fname");
922 if ($email && $email =~ m/\s*(\S+)\s*/) {
925 # empty .forward does not forward mails (see man local)
930 # rewrite /etc/postfix/*
931 sub rewrite_config_postfix
{
934 # make sure we have required files (else postfix start fails)
935 IO
::File-
>new($domainsfilename, 'a', 0644);
936 IO
::File-
>new($transport_map_filename, 'a', 0644);
938 if ($self->get('mail', 'tls')) {
940 my $resolv = PVE
::INotify
::read_file
('resolvconf');
941 my $domain = $resolv->{search
};
943 my $company = $domain; # what else ?
944 my $cn = "*.$domain";
945 PMG
::Utils
::gen_proxmox_tls_cert
(0, $company, $cn);
947 syslog
('info', msgquote
("generating certificate failed: $@")) if $@;
950 $self->rewrite_config_file('main.cf.in', '/etc/postfix/main.cf');
951 $self->rewrite_config_file('master.cf.in', '/etc/postfix/master.cf');
952 #rewrite_config_transports ($class);
953 #rewrite_config_whitelist ($class);
954 #rewrite_config_tls_policy ($class);
956 # make sure aliases.db is up to date
957 system('/usr/bin/newaliases');
963 $self->rewrite_config_postfix();
964 $self->rewrite_dot_forward();
965 $self->rewrite_config_postgres();
966 $self->rewrite_config_spam();
967 $self->rewrite_config_clam();