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;
309 description
=> "The default mail delivery transport (incoming mails).",
313 description
=> "SMTP port number for relay host.",
320 description
=> "Disable MX lookups for default relay.",
325 description
=> "When set, all outgoing mails are deliverd to the specified smarthost.",
329 description
=> "ESMTP banner.",
332 default => 'ESMTP Proxmox',
335 description
=> "Maximum number of filter processes.",
339 default => get_max_filters
(),
342 description
=> "Maximum number of SMTP daemon processes (in).",
346 default => get_max_smtpd
(),
349 description
=> "Maximum number of SMTP daemon processes (out).",
353 default => get_max_smtpd
(),
355 conn_count_limit
=> {
356 description
=> "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
362 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.",
367 message_rate_limit
=> {
368 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.",
374 description
=> "Hide received header in outgoing mails.",
379 description
=> "Maximum email size. Larger mails are rejected.",
382 default => 1024*1024*10,
385 description
=> "SMTP delay warning time (in hours).",
391 description
=> "Use Realtime Blacklists.",
396 description
=> "Use TLS.",
401 description
=> "Use Sender Policy Framework.",
406 description
=> "Use Greylisting.",
411 description
=> "Use SMTP HELO tests.",
416 description
=> "Reject unknown clients.",
420 rejectunknownsender
=> {
421 description
=> "Reject unknown senders.",
426 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).",
433 description
=> "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
441 relay
=> { optional
=> 1 },
442 relayport
=> { optional
=> 1 },
443 relaynomx
=> { optional
=> 1 },
444 dwarning
=> { optional
=> 1 },
445 max_smtpd_in
=> { optional
=> 1 },
446 max_smtpd_out
=> { optional
=> 1 },
447 greylist
=> { optional
=> 1 },
448 helotests
=> { optional
=> 1 },
449 use_rbl
=> { optional
=> 1 },
450 tls
=> { optional
=> 1 },
451 spf
=> { optional
=> 1 },
452 maxsize
=> { optional
=> 1 },
453 banner
=> { optional
=> 1 },
454 max_filters
=> { optional
=> 1 },
455 hide_received
=> { optional
=> 1 },
456 rejectunknown
=> { optional
=> 1 },
457 rejectunknownsender
=> { optional
=> 1 },
458 conn_count_limit
=> { optional
=> 1 },
459 conn_rate_limit
=> { optional
=> 1 },
460 message_rate_limit
=> { optional
=> 1 },
461 verifyreceivers
=> { optional
=> 1 },
462 dnsbl_sites
=> { optional
=> 1 },
477 PMG
::Config
::Admin-
>register();
478 PMG
::Config
::Mail-
>register();
479 PMG
::Config
::Spam-
>register();
480 PMG
::Config
::ClamAV-
>register();
482 # initialize all plugins
483 PMG
::Config
::Base-
>init();
489 my $class = ref($type) || $type;
491 my $cfg = PVE
::INotify
::read_file
("pmg.conf");
493 return bless $cfg, $class;
499 PVE
::INotify
::write_file
("pmg.conf", $self);
502 my $lockfile = "/var/lock/pmgconfig.lck";
505 my ($code, $errmsg) = @_;
507 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
509 $errmsg ?
die "$errmsg: $err" : die $err;
515 my ($self, $section, $key, $value) = @_;
517 my $pdata = PMG
::Config
::Base-
>private();
519 my $plugin = $pdata->{plugins
}->{$section};
520 die "no such section '$section'" if !$plugin;
522 if (defined($value)) {
523 my $tmp = PMG
::Config
::Base-
>check_value($section, $key, $value, $section, 0);
524 $self->{ids
}->{$section} = { type
=> $section } if !defined($self->{ids
}->{$section});
525 $self->{ids
}->{$section}->{$key} = PMG
::Config
::Base-
>decode_value($section, $key, $tmp);
527 if (defined($self->{ids
}->{$section})) {
528 delete $self->{ids
}->{$section}->{$key};
535 # get section value or default
537 my ($self, $section, $key) = @_;
539 my $pdata = PMG
::Config
::Base-
>private();
540 return undef if !defined($pdata->{options
}->{$section});
541 return undef if !defined($pdata->{options
}->{$section}->{$key});
542 my $pdesc = $pdata->{propertyList
}->{$key};
543 return undef if !defined($pdesc);
545 if (defined($self->{ids
}->{$section}) &&
546 defined(my $value = $self->{ids
}->{$section}->{$key})) {
550 return $pdesc->{default};
553 # get a whole section with default value
555 my ($self, $section) = @_;
557 my $pdata = PMG
::Config
::Base-
>private();
558 return undef if !defined($pdata->{options
}->{$section});
562 foreach my $key (keys %{$pdata->{options
}->{$section}}) {
564 my $pdesc = $pdata->{propertyList
}->{$key};
566 if (defined($self->{ids
}->{$section}) &&
567 defined(my $value = $self->{ids
}->{$section}->{$key})) {
568 $res->{$key} = $value;
571 $res->{$key} = $pdesc->{default};
577 # get a whole config with default values
581 my $pdata = PMG
::Config
::Base-
>private();
585 foreach my $type (keys %{$pdata->{plugins
}}) {
586 my $plugin = $pdata->{plugins
}->{$type};
587 $res->{$type} = $self->get_section($type);
594 my ($filename, $fh) = @_;
596 local $/ = undef; # slurp mode
600 return PMG
::Config
::Base-
>parse_config($filename, $raw);
604 my ($filename, $fh, $cfg) = @_;
606 my $raw = PMG
::Config
::Base-
>write_config($filename, $cfg);
608 PVE
::Tools
::safe_print
($filename, $fh, $raw);
611 PVE
::INotify
::register_file
('pmg.conf', "/etc/proxmox/pmg.conf",
615 # parsers/writers for other files
617 my $domainsfilename = "/etc/proxmox/domains";
619 sub read_pmg_domains
{
620 my ($filename, $fh) = @_;
625 while (defined(my $line = <$fh>)) {
626 if ($line =~ m/^\s*(\S+)\s*$/) {
628 push @$domains, $domain;
636 sub write_pmg_domains
{
637 my ($filename, $fh, $domain) = @_;
639 foreach my $domain (sort @$domain) {
640 PVE
::Tools
::safe_print
($filename, $fh, "$domain\n");
644 PVE
::INotify
::register_file
('domains', $domainsfilename,
647 undef, always_call_parser
=> 1);
649 my $transport_map_filename = "/etc/postfix/transport";
651 sub read_transport_map
{
652 my ($filename, $fh) = @_;
654 return [] if !defined($fh);
658 while (defined(my $line = <$fh>)) {
660 next if $line =~ m/^\s*$/;
661 next if $line =~ m/^\s*\#/;
663 if ($line =~ m/^(\S+)\s+smtp:([^\s:]+):(\d+)\s*$/) {
669 if ($host =~ m/^\[(.*)\]$/) {
674 my $key = "$host:$port";
676 $res->{$key}->{nomx
} = $nomx;
677 $res->{$key}->{host
} = $host;
678 $res->{$key}->{port
} = $port;
679 $res->{$key}->{transport
} = $key;
681 push @{$res->{$key}->{domains
}}, $domain;
687 foreach my $t (sort keys %$res) {
688 push @$ta, $res->{$t};
694 sub write_ransport_map
{
695 my ($filename, $fh, $tmap) = @_;
699 foreach my $t (sort { $a->{transport
} cmp $b->{transport
} } @$tmap) {
700 my $domains = $t->{domains
};
702 foreach my $d (sort @$domains) {
704 PVE
::Tools
::safe_print
($filename, $fh, "$d smtp:[$t->{host}]:$t->{port}\n");
706 PVE
::Tools
::safe_print
($filename, $fh, "$d smtp:$t->{host}:$t->{port}\n");
712 PVE
::INotify
::register_file
('transport', $transport_map_filename,
713 \
&read_transport_map
,
714 \
&write_ransport_map
,
715 undef, always_call_parser
=> 1);
717 # config file generation using templates
719 sub get_template_vars
{
722 my $vars = { pmg
=> $self->get_config() };
724 my $nodename = PVE
::INotify
::nodename
();
725 my $int_ip = PMG
::Cluster
::remote_node_ip
($nodename);
726 my $int_net_cidr = PMG
::Utils
::find_local_network_for_ip
($int_ip);
728 $vars->{ipconfig
}->{int_ip
} = $int_ip;
729 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
730 $vars->{ipconfig
}->{int_port
} = 26;
731 $vars->{ipconfig
}->{ext_port
} = 25;
733 my $transportnets = []; # fixme
734 $vars->{postfix
}->{transportnets
} = join(' ', @$transportnets);
736 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
737 push @$mynetworks, @$transportnets;
738 push @$mynetworks, $int_net_cidr;
740 # add default relay to mynetworks
741 if (my $relay = $self->get('mail', 'relay')) {
742 if (Net
::IP
::ip_is_ipv4
($relay)) {
743 push @$mynetworks, "$relay/32";
744 } elsif (Net
::IP
::ip_is_ipv6
($relay)) {
745 push @$mynetworks, "[$relay]/128";
747 warn "unable to detect IP version of relay '$relay'";
751 $vars->{postfix
}->{mynetworks
} = join(' ', @$mynetworks);
754 $usepolicy = 1 if $self->get('mail', 'greylist') ||
755 $self->get('mail', 'spf') || $self->get('mail', 'use_rbl');
756 $vars->{postfix
}->{usepolicy
} = $usepolicy;
758 my $resolv = PVE
::INotify
::read_file
('resolvconf');
759 $vars->{dns
}->{hostname
} = $nodename;
760 $vars->{dns
}->{domain
} = $resolv->{search
};
765 # rewrite file from template
766 # return true if file has changed
767 sub rewrite_config_file
{
768 my ($self, $tmplname, $dstfn) = @_;
770 my $demo = $self->get('admin', 'demo');
772 my $srcfn = ($tmplname =~ m
|^.?
/|) ?
773 $tmplname : "/var/lib/pmg/templates/$tmplname";
776 my $demosrc = "$srcfn.demo";
777 $srcfn = $demosrc if -f
$demosrc;
780 my ($perm, $uid, $gid);
782 my $srcfd = IO
::File-
>new ($srcfn, "r")
783 || die "cant read template '$srcfn' - $!: ERROR";
785 if ($dstfn eq '/etc/fetchmailrc') {
786 (undef, undef, $uid, $gid) = getpwnam('fetchmail');
788 } elsif ($dstfn eq '/etc/clamav/freshclam.conf') {
789 # needed if file contains a HTTPProxyPasswort
791 $uid = getpwnam('clamav');
792 $gid = getgrnam('adm');
796 my $template = Template-
>new({});
798 my $vars = $self->get_template_vars();
802 $template->process($srcfd, $vars, \
$output) ||
803 die $template->error();
807 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
809 return 0 if defined($old) && ($old eq $output); # no change
811 PVE
::Tools
::file_set_contents
($dstfn, $output, $perm);
813 if (defined($uid) && defined($gid)) {
814 chown($uid, $gid, $dstfn);
820 # rewrite spam configuration
821 sub rewrite_config_spam
{
824 my $use_awl = $self->get('spam', 'use_awl');
825 my $use_bayes = $self->get('spam', 'use_bayes');
826 my $use_razor = $self->get('spam', 'use_razor');
830 # delete AW and bayes databases if those features are disabled
832 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
836 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
837 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
838 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
841 # make sure we have a custom.cf file (else cluster sync fails)
842 IO
::File-
>new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
844 $changes = 1 if $self->rewrite_config_file(
845 'local.cf.in', '/etc/mail/spamassassin/local.cf');
847 $changes = 1 if $self->rewrite_config_file(
848 'init.pre.in', '/etc/mail/spamassassin/init.pre');
850 $changes = 1 if $self->rewrite_config_file(
851 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
853 $changes = 1 if $self->rewrite_config_file(
854 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
857 mkdir "/root/.razor";
859 $changes = 1 if $self->rewrite_config_file(
860 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
862 if (! -e
'/root/.razor/identity') {
865 PVE
::Tools
::run_command
(['razor-admin', '-discover'], timeout
=> $timeout);
866 PVE
::Tools
::run_command
(['razor-admin', '-register'], timeout
=> $timeout);
869 syslog
('info', msgquote
("registering razor failed: $err")) if $err;
876 # rewrite ClamAV configuration
877 sub rewrite_config_clam
{
880 return $self->rewrite_config_file(
881 'clamd.conf.in', '/etc/clamav/clamd.conf');
884 sub rewrite_config_freshclam
{
887 return $self->rewrite_config_file(
888 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
891 sub rewrite_config_postgres
{
894 my $pgconfdir = "/etc/postgresql/9.6/main";
898 $changes = 1 if $self->rewrite_config_file(
899 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
901 $changes = 1 if $self->rewrite_config_file(
902 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
907 # rewrite /root/.forward
908 sub rewrite_dot_forward
{
911 my $dstfn = '/root/.forward';
913 my $email = $self->get('administration', 'email');
916 if ($email && $email =~ m/\s*(\S+)\s*/) {
919 # empty .forward does not forward mails (see man local)
922 my $old = PVE
::Tools
::file_get_contents
($dstfn, 128*1024) if -f
$dstfn;
924 return 0 if defined($old) && ($old eq $output); # no change
926 PVE
::Tools
::file_set_contents
($dstfn, $output);
931 # rewrite /etc/postfix/*
932 sub rewrite_config_postfix
{
935 # make sure we have required files (else postfix start fails)
936 IO
::File-
>new($domainsfilename, 'a', 0644);
937 IO
::File-
>new($transport_map_filename, 'a', 0644);
941 if ($self->get('mail', 'tls')) {
943 my $resolv = PVE
::INotify
::read_file
('resolvconf');
944 my $domain = $resolv->{search
};
946 my $company = $domain; # what else ?
947 my $cn = "*.$domain";
948 PMG
::Utils
::gen_proxmox_tls_cert
(0, $company, $cn);
950 syslog
('info', msgquote
("generating certificate failed: $@")) if $@;
953 $changes = 1 if $self->rewrite_config_file(
954 'main.cf.in', '/etc/postfix/main.cf');
956 $changes = 1 if $self->rewrite_config_file(
957 'master.cf.in', '/etc/postfix/master.cf');
959 #rewrite_config_transports ($class);
960 #rewrite_config_whitelist ($class);
961 #rewrite_config_tls_policy ($class);
963 # make sure aliases.db is up to date
964 system('/usr/bin/newaliases');
970 my ($self, $restart_services) = @_;
972 if ($self->rewrite_config_postfix() && $restart_services) {
973 PMG
::Utils
::service_cmd
('postfix', 'restart');
976 if ($self->rewrite_dot_forward() && $restart_services) {
977 # no need to restart anything
980 if ($self->rewrite_config_postgres() && $restart_services) {
981 # do nothing (too many side effects)?
982 # does not happen anyways, because config does not change.
985 if ($self->rewrite_config_spam() && $restart_services) {
986 PMG
::Utils
::service_cmd
('pmg-smtp-filter', 'restart');
989 if ($self->rewrite_config_clam() && $restart_services) {
990 PMG
::Utils
::service_cmd
('clamd', 'restart');
993 if ($self->rewrite_config_freshclam() && $restart_services) {
994 PMG
::Utils
::service_cmd
('freshclam', 'restart');