]> git.proxmox.com Git - pmg-api.git/blob - PMG/Config.pm
implement parser/writer and API for /etc/pmg/mynetworks
[pmg-api.git] / PMG / Config.pm
1 package PMG::Config::Base;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6
7 use PVE::Tools;
8 use PVE::JSONSchema qw(get_standard_option);
9 use PVE::SectionConfig;
10
11 use base qw(PVE::SectionConfig);
12
13 my $defaultData = {
14 propertyList => {
15 type => { description => "Section type." },
16 section => {
17 description => "Secion ID.",
18 type => 'string', format => 'pve-configid',
19 },
20 },
21 };
22
23 sub private {
24 return $defaultData;
25 }
26
27 sub format_section_header {
28 my ($class, $type, $sectionId) = @_;
29
30 die "internal error ($type ne $sectionId)" if $type ne $sectionId;
31
32 return "section: $type\n";
33 }
34
35
36 sub parse_section_header {
37 my ($class, $line) = @_;
38
39 if ($line =~ m/^section:\s*(\S+)\s*$/) {
40 my $section = $1;
41 my $errmsg = undef; # set if you want to skip whole section
42 eval { PVE::JSONSchema::pve_verify_configid($section); };
43 $errmsg = $@ if $@;
44 my $config = {}; # to return additional attributes
45 return ($section, $section, $errmsg, $config);
46 }
47 return undef;
48 }
49
50 package PMG::Config::Admin;
51
52 use strict;
53 use warnings;
54
55 use base qw(PMG::Config::Base);
56
57 sub type {
58 return 'admin';
59 }
60
61 sub properties {
62 return {
63 dailyreport => {
64 description => "Send daily reports.",
65 type => 'boolean',
66 default => 1,
67 },
68 demo => {
69 description => "Demo mode - do not start SMTP filter.",
70 type => 'boolean',
71 default => 0,
72 },
73 email => {
74 description => "Administrator E-Mail address.",
75 type => 'string', format => 'email',
76 default => 'admin@domain.tld',
77 },
78 proxyport => {
79 description => "HTTP proxy port.",
80 type => 'integer',
81 minimum => 1,
82 default => 8080,
83 },
84 proxyserver => {
85 description => "HTTP proxy server address.",
86 type => 'string',
87 },
88 proxyuser => {
89 description => "HTTP proxy user name.",
90 type => 'string',
91 },
92 proxypassword => {
93 description => "HTTP proxy password.",
94 type => 'string',
95 },
96 };
97 }
98
99 sub options {
100 return {
101 dailyreport => { optional => 1 },
102 demo => { optional => 1 },
103 email => { optional => 1 },
104 proxyport => { optional => 1 },
105 proxyserver => { optional => 1 },
106 proxyuser => { optional => 1 },
107 proxypassword => { optional => 1 },
108 };
109 }
110
111 package PMG::Config::Spam;
112
113 use strict;
114 use warnings;
115
116 use base qw(PMG::Config::Base);
117
118 sub type {
119 return 'spam';
120 }
121
122 sub properties {
123 return {
124 languages => {
125 description => "This option is used to specify which languages are considered OK for incoming mail.",
126 type => 'string',
127 pattern => '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
128 default => 'all',
129 },
130 use_bayes => {
131 description => "Whether to use the naive-Bayesian-style classifier.",
132 type => 'boolean',
133 default => 1,
134 },
135 use_awl => {
136 description => "Use the Auto-Whitelist plugin.",
137 type => 'boolean',
138 default => 1,
139 },
140 use_razor => {
141 description => "Whether to use Razor2, if it is available.",
142 type => 'boolean',
143 default => 1,
144 },
145 use_ocr => {
146 description => "Enable OCR to scan pictures.",
147 type => 'boolean',
148 default => 0,
149 },
150 wl_bounce_relays => {
151 description => "Whitelist legitimate bounce relays.",
152 type => 'string',
153 },
154 bounce_score => {
155 description => "Additional score for bounce mails.",
156 type => 'integer',
157 minimum => 0,
158 maximum => 1000,
159 default => 0,
160 },
161 rbl_checks => {
162 description => "Enable real time blacklists (RBL) checks.",
163 type => 'boolean',
164 default => 1,
165 },
166 maxspamsize => {
167 description => "Maximum size of spam messages in bytes.",
168 type => 'integer',
169 minimum => 64,
170 default => 200*1024,
171 },
172 };
173 }
174
175 sub options {
176 return {
177 use_awl => { optional => 1 },
178 use_razor => { optional => 1 },
179 use_ocr => { optional => 1 },
180 wl_bounce_relays => { optional => 1 },
181 languages => { optional => 1 },
182 use_bayes => { optional => 1 },
183 bounce_score => { optional => 1 },
184 rbl_checks => { optional => 1 },
185 maxspamsize => { optional => 1 },
186 };
187 }
188
189 package PMG::Config::ClamAV;
190
191 use strict;
192 use warnings;
193
194 use base qw(PMG::Config::Base);
195
196 sub type {
197 return 'clamav';
198 }
199
200 sub properties {
201 return {
202 dbmirror => {
203 description => "ClamAV database mirror server.",
204 type => 'string',
205 default => 'database.clamav.net',
206 },
207 archiveblockencrypted => {
208 description => "Wether to block encrypted archives. Mark encrypted archives as viruses.",
209 type => 'boolean',
210 default => 0,
211 },
212 archivemaxrec => {
213 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.",
214 type => 'integer',
215 minimum => 1,
216 default => 5,
217 },
218 archivemaxfiles => {
219 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.",
220 type => 'integer',
221 minimum => 0,
222 default => 1000,
223 },
224 archivemaxsize => {
225 description => "Files larger than this limit won't be scanned.",
226 type => 'integer',
227 minimum => 1000000,
228 default => 25000000,
229 },
230 maxscansize => {
231 description => "Sets the maximum amount of data to be scanned for each input file.",
232 type => 'integer',
233 minimum => 1000000,
234 default => 100000000,
235 },
236 maxcccount => {
237 description => "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
238 type => 'integer',
239 minimum => 0,
240 default => 0,
241 },
242 };
243 }
244
245 sub options {
246 return {
247 archiveblockencrypted => { optional => 1 },
248 archivemaxrec => { optional => 1 },
249 archivemaxfiles => { optional => 1 },
250 archivemaxsize => { optional => 1 },
251 maxscansize => { optional => 1 },
252 dbmirror => { optional => 1 },
253 maxcccount => { optional => 1 },
254 };
255 }
256
257 package PMG::Config::Mail;
258
259 use strict;
260 use warnings;
261
262 use PVE::ProcFSTools;
263
264 use base qw(PMG::Config::Base);
265
266 sub type {
267 return 'mail';
268 }
269
270 my $physicalmem = 0;
271 sub physical_memory {
272
273 return $physicalmem if $physicalmem;
274
275 my $info = PVE::ProcFSTools::read_meminfo();
276 my $total = int($info->{memtotal} / (1024*1024));
277
278 return $total;
279 }
280
281 sub get_max_filters {
282 # estimate optimal number of filter servers
283
284 my $max_servers = 5;
285 my $servermem = 120;
286 my $memory = physical_memory();
287 my $add_servers = int(($memory - 512)/$servermem);
288 $max_servers += $add_servers if $add_servers > 0;
289 $max_servers = 40 if $max_servers > 40;
290
291 return $max_servers - 2;
292 }
293
294 sub get_max_smtpd {
295 # estimate optimal number of smtpd daemons
296
297 my $max_servers = 25;
298 my $servermem = 20;
299 my $memory = physical_memory();
300 my $add_servers = int(($memory - 512)/$servermem);
301 $max_servers += $add_servers if $add_servers > 0;
302 $max_servers = 100 if $max_servers > 100;
303 return $max_servers;
304 }
305
306 sub get_max_policy {
307 # estimate optimal number of proxpolicy servers
308 my $max_servers = 2;
309 my $memory = physical_memory();
310 $max_servers = 5 if $memory >= 500;
311 return $max_servers;
312 }
313
314 sub properties {
315 return {
316 int_port => {
317 description => "SMTP port number for outgoing mail (trusted).",
318 type => 'integer',
319 minimum => 1,
320 maximum => 65535,
321 default => 25,
322 },
323 ext_port => {
324 description => "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
325 type => 'integer',
326 minimum => 1,
327 maximum => 65535,
328 default => 26,
329 },
330 relay => {
331 description => "The default mail delivery transport (incoming mails).",
332 type => 'string', format => 'address',
333 },
334 relayport => {
335 description => "SMTP port number for relay host.",
336 type => 'integer',
337 minimum => 1,
338 maximum => 65535,
339 default => 25,
340 },
341 relaynomx => {
342 description => "Disable MX lookups for default relay.",
343 type => 'boolean',
344 default => 0,
345 },
346 smarthost => {
347 description => "When set, all outgoing mails are deliverd to the specified smarthost.",
348 type => 'string', format => 'address',
349 },
350 banner => {
351 description => "ESMTP banner.",
352 type => 'string',
353 maxLength => 1024,
354 default => 'ESMTP Proxmox',
355 },
356 max_filters => {
357 description => "Maximum number of pmg-smtp-filter processes.",
358 type => 'integer',
359 minimum => 3,
360 maximum => 40,
361 default => get_max_filters(),
362 },
363 max_policy => {
364 description => "Maximum number of pmgpolicy processes.",
365 type => 'integer',
366 minimum => 2,
367 maximum => 10,
368 default => get_max_policy(),
369 },
370 max_smtpd_in => {
371 description => "Maximum number of SMTP daemon processes (in).",
372 type => 'integer',
373 minimum => 3,
374 maximum => 100,
375 default => get_max_smtpd(),
376 },
377 max_smtpd_out => {
378 description => "Maximum number of SMTP daemon processes (out).",
379 type => 'integer',
380 minimum => 3,
381 maximum => 100,
382 default => get_max_smtpd(),
383 },
384 conn_count_limit => {
385 description => "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
386 type => 'integer',
387 minimum => 0,
388 default => 50,
389 },
390 conn_rate_limit => {
391 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.",
392 type => 'integer',
393 minimum => 0,
394 default => 0,
395 },
396 message_rate_limit => {
397 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.",
398 type => 'integer',
399 minimum => 0,
400 default => 0,
401 },
402 hide_received => {
403 description => "Hide received header in outgoing mails.",
404 type => 'boolean',
405 default => 0,
406 },
407 maxsize => {
408 description => "Maximum email size. Larger mails are rejected.",
409 type => 'integer',
410 minimum => 1024,
411 default => 1024*1024*10,
412 },
413 dwarning => {
414 description => "SMTP delay warning time (in hours).",
415 type => 'integer',
416 minimum => 0,
417 default => 4,
418 },
419 use_rbl => {
420 description => "Use Realtime Blacklists.",
421 type => 'boolean',
422 default => 1,
423 },
424 tls => {
425 description => "Use TLS.",
426 type => 'boolean',
427 default => 0,
428 },
429 spf => {
430 description => "Use Sender Policy Framework.",
431 type => 'boolean',
432 default => 1,
433 },
434 greylist => {
435 description => "Use Greylisting.",
436 type => 'boolean',
437 default => 1,
438 },
439 helotests => {
440 description => "Use SMTP HELO tests.",
441 type => 'boolean',
442 default => 0,
443 },
444 rejectunknown => {
445 description => "Reject unknown clients.",
446 type => 'boolean',
447 default => 0,
448 },
449 rejectunknownsender => {
450 description => "Reject unknown senders.",
451 type => 'boolean',
452 default => 0,
453 },
454 verifyreceivers => {
455 description => "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
456 type => 'string',
457 enum => ['450', '550'],
458 },
459 dnsbl_sites => {
460 description => "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
461 type => 'string',
462 },
463 };
464 }
465
466 sub options {
467 return {
468 int_port => { optional => 1 },
469 ext_port => { optional => 1 },
470 smarthost => { optional => 1 },
471 relay => { optional => 1 },
472 relayport => { optional => 1 },
473 relaynomx => { optional => 1 },
474 dwarning => { optional => 1 },
475 max_smtpd_in => { optional => 1 },
476 max_smtpd_out => { optional => 1 },
477 greylist => { optional => 1 },
478 helotests => { optional => 1 },
479 use_rbl => { optional => 1 },
480 tls => { optional => 1 },
481 spf => { optional => 1 },
482 maxsize => { optional => 1 },
483 banner => { optional => 1 },
484 max_filters => { optional => 1 },
485 max_policy => { optional => 1 },
486 hide_received => { optional => 1 },
487 rejectunknown => { optional => 1 },
488 rejectunknownsender => { optional => 1 },
489 conn_count_limit => { optional => 1 },
490 conn_rate_limit => { optional => 1 },
491 message_rate_limit => { optional => 1 },
492 verifyreceivers => { optional => 1 },
493 dnsbl_sites => { optional => 1 },
494 };
495 }
496 package PMG::Config;
497
498 use strict;
499 use warnings;
500 use IO::File;
501 use Data::Dumper;
502 use Template;
503
504 use PVE::SafeSyslog;
505 use PVE::Tools qw($IPV4RE $IPV6RE);
506 use PVE::INotify;
507 use PVE::JSONSchema;
508
509 PMG::Config::Admin->register();
510 PMG::Config::Mail->register();
511 PMG::Config::Spam->register();
512 PMG::Config::ClamAV->register();
513
514 # initialize all plugins
515 PMG::Config::Base->init();
516
517 PVE::JSONSchema::register_format(
518 'transport-domain', \&pmg_verify_transport_domain);
519 sub pmg_verify_transport_domain {
520 my ($name, $noerr) = @_;
521
522 # like dns-name, but can contain leading dot
523 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
524
525 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
526 return undef if $noerr;
527 die "value does not look like a valid transport domain\n";
528 }
529 return $name;
530 }
531
532 sub new {
533 my ($type) = @_;
534
535 my $class = ref($type) || $type;
536
537 my $cfg = PVE::INotify::read_file("pmg.conf");
538
539 return bless $cfg, $class;
540 }
541
542 sub write {
543 my ($self) = @_;
544
545 PVE::INotify::write_file("pmg.conf", $self);
546 }
547
548 my $lockfile = "/var/lock/pmgconfig.lck";
549
550 sub lock_config {
551 my ($code, $errmsg) = @_;
552
553 my $p = PVE::Tools::lock_file($lockfile, undef, $code);
554 if (my $err = $@) {
555 $errmsg ? die "$errmsg: $err" : die $err;
556 }
557 }
558
559 # set section values
560 sub set {
561 my ($self, $section, $key, $value) = @_;
562
563 my $pdata = PMG::Config::Base->private();
564
565 my $plugin = $pdata->{plugins}->{$section};
566 die "no such section '$section'" if !$plugin;
567
568 if (defined($value)) {
569 my $tmp = PMG::Config::Base->check_value($section, $key, $value, $section, 0);
570 $self->{ids}->{$section} = { type => $section } if !defined($self->{ids}->{$section});
571 $self->{ids}->{$section}->{$key} = PMG::Config::Base->decode_value($section, $key, $tmp);
572 } else {
573 if (defined($self->{ids}->{$section})) {
574 delete $self->{ids}->{$section}->{$key};
575 }
576 }
577
578 return undef;
579 }
580
581 # get section value or default
582 sub get {
583 my ($self, $section, $key) = @_;
584
585 my $pdata = PMG::Config::Base->private();
586 my $pdesc = $pdata->{propertyList}->{$key};
587 die "no such property '$section/$key'\n"
588 if !(defined($pdesc) && defined($pdata->{options}->{$section}) &&
589 defined($pdata->{options}->{$section}->{$key}));
590
591 if (defined($self->{ids}->{$section}) &&
592 defined(my $value = $self->{ids}->{$section}->{$key})) {
593 return $value;
594 }
595
596 return $pdesc->{default};
597 }
598
599 # get a whole section with default value
600 sub get_section {
601 my ($self, $section) = @_;
602
603 my $pdata = PMG::Config::Base->private();
604 return undef if !defined($pdata->{options}->{$section});
605
606 my $res = {};
607
608 foreach my $key (keys %{$pdata->{options}->{$section}}) {
609
610 my $pdesc = $pdata->{propertyList}->{$key};
611
612 if (defined($self->{ids}->{$section}) &&
613 defined(my $value = $self->{ids}->{$section}->{$key})) {
614 $res->{$key} = $value;
615 next;
616 }
617 $res->{$key} = $pdesc->{default};
618 }
619
620 return $res;
621 }
622
623 # get a whole config with default values
624 sub get_config {
625 my ($self) = @_;
626
627 my $pdata = PMG::Config::Base->private();
628
629 my $res = {};
630
631 foreach my $type (keys %{$pdata->{plugins}}) {
632 my $plugin = $pdata->{plugins}->{$type};
633 $res->{$type} = $self->get_section($type);
634 }
635
636 return $res;
637 }
638
639 sub read_pmg_conf {
640 my ($filename, $fh) = @_;
641
642 local $/ = undef; # slurp mode
643
644 my $raw = <$fh> if defined($fh);
645
646 return PMG::Config::Base->parse_config($filename, $raw);
647 }
648
649 sub write_pmg_conf {
650 my ($filename, $fh, $cfg) = @_;
651
652 my $raw = PMG::Config::Base->write_config($filename, $cfg);
653
654 PVE::Tools::safe_print($filename, $fh, $raw);
655 }
656
657 PVE::INotify::register_file('pmg.conf', "/etc/pmg/pmg.conf",
658 \&read_pmg_conf,
659 \&write_pmg_conf,
660 undef, always_call_parser => 1);
661
662 # parsers/writers for other files
663
664 my $domainsfilename = "/etc/pmg/domains";
665
666 sub postmap_pmg_domains {
667 PMG::Utils::run_postmap($domainsfilename);
668 }
669
670 sub read_pmg_domains {
671 my ($filename, $fh) = @_;
672
673 my $domains = {};
674
675 my $comment = '';
676 if (defined($fh)) {
677 while (defined(my $line = <$fh>)) {
678 chomp $line;
679 next if $line =~ m/^\s*$/;
680 if ($line =~ m/^#(.*)\s*$/) {
681 $comment = $1;
682 next;
683 }
684 if ($line =~ m/^(\S+)\s.*$/) {
685 my $domain = $1;
686 $domains->{$domain} = {
687 domain => $domain, comment => $comment };
688 $comment = '';
689 } else {
690 warn "parse error in '$filename': $line\n";
691 $comment = '';
692 }
693 }
694 }
695
696 return $domains;
697 }
698
699 sub write_pmg_domains {
700 my ($filename, $fh, $domains) = @_;
701
702 foreach my $domain (sort keys %$domains) {
703 my $comment = $domains->{$domain}->{comment};
704 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
705 if defined($comment) && $comment !~ m/^\s*$/;
706
707 PVE::Tools::safe_print($filename, $fh, "$domain 1\n");
708 }
709 }
710
711 PVE::INotify::register_file('domains', $domainsfilename,
712 \&read_pmg_domains,
713 \&write_pmg_domains,
714 undef, always_call_parser => 1);
715
716 my $mynetworks_filename = "/etc/pmg/mynetworks";
717
718 sub postmap_pmg_mynetworks {
719 PMG::Utils::run_postmap($mynetworks_filename);
720 }
721
722 sub read_pmg_mynetworks {
723 my ($filename, $fh) = @_;
724
725 my $mynetworks = {};
726
727 my $comment = '';
728 if (defined($fh)) {
729 while (defined(my $line = <$fh>)) {
730 chomp $line;
731 next if $line =~ m/^\s*$/;
732 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
733 my ($network, $prefix_size, $comment) = ($1, $2, $3);
734 my $cidr = "$network/${prefix_size}";
735 $mynetworks->{$cidr} = {
736 cidr => $cidr,
737 network_address => $network,
738 prefix_size => $prefix_size,
739 comment => $comment // '',
740 };
741 } else {
742 warn "parse error in '$filename': $line\n";
743 }
744 }
745 }
746
747 return $mynetworks;
748 }
749
750 sub write_pmg_mynetworks {
751 my ($filename, $fh, $mynetworks) = @_;
752
753 foreach my $cidr (sort keys %$mynetworks) {
754 my $data = $mynetworks->{$cidr};
755 my $comment = $data->{comment} // '*';
756 PVE::Tools::safe_print($filename, $fh, "$cidr #$comment\n");
757 }
758 }
759
760 PVE::INotify::register_file('mynetworks', $mynetworks_filename,
761 \&read_pmg_mynetworks,
762 \&write_pmg_mynetworks,
763 undef, always_call_parser => 1);
764
765 my $transport_map_filename = "/etc/pmg/transport";
766
767 sub postmap_pmg_transport {
768 PMG::Utils::run_postmap($transport_map_filename);
769 }
770
771 sub read_transport_map {
772 my ($filename, $fh) = @_;
773
774 return [] if !defined($fh);
775
776 my $res = {};
777
778 my $comment = '';
779
780 while (defined(my $line = <$fh>)) {
781 chomp $line;
782 next if $line =~ m/^\s*$/;
783 if ($line =~ m/^#(.*)\s*$/) {
784 $comment = $1;
785 next;
786 }
787
788 my $parse_error = sub {
789 my ($err) = @_;
790 warn "parse error in '$filename': $line - $err";
791 $comment = '';
792 };
793
794 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
795 my ($domain, $host, $port) = ($1, $2, $3);
796
797 eval { pmg_verify_transport_domain($domain); };
798 if (my $err = $@) {
799 $parse_error->($err);
800 next;
801 }
802 my $use_mx = 1;
803 if ($host =~ m/^\[(.*)\]$/) {
804 $host = $1;
805 $use_mx = 0;
806 }
807
808 eval { PVE::JSONSchema::pve_verify_address($host); };
809 if (my $err = $@) {
810 $parse_error->($err);
811 next;
812 }
813
814 my $data = {
815 domain => $domain,
816 host => $host,
817 port => $port,
818 use_mx => $use_mx,
819 comment => $comment,
820 };
821 $res->{$domain} = $data;
822 $comment = '';
823 } else {
824 $parse_error->('wrong format');
825 }
826 }
827
828 return $res;
829 }
830
831 sub write_transport_map {
832 my ($filename, $fh, $tmap) = @_;
833
834 return if !$tmap;
835
836 foreach my $domain (sort keys %$tmap) {
837 my $data = $tmap->{$domain};
838
839 my $comment = $data->{comment};
840 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
841 if defined($comment) && $comment !~ m/^\s*$/;
842
843 my $use_mx = $data->{use_mx};
844 $use_mx = 0 if $data->{host} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
845
846 if ($use_mx) {
847 PVE::Tools::safe_print(
848 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
849 } else {
850 PVE::Tools::safe_print(
851 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
852 }
853 }
854 }
855
856 PVE::INotify::register_file('transport', $transport_map_filename,
857 \&read_transport_map,
858 \&write_transport_map,
859 undef, always_call_parser => 1);
860
861 # config file generation using templates
862
863 sub get_template_vars {
864 my ($self) = @_;
865
866 my $vars = { pmg => $self->get_config() };
867
868 my $nodename = PVE::INotify::nodename();
869 my $int_ip = PMG::Cluster::remote_node_ip($nodename);
870 my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip);
871 $vars->{ipconfig}->{int_ip} = $int_ip;
872 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
873
874 my $transportnets = [];
875
876 my $tmap = PVE::INotify::read_file('transport');
877 foreach my $domain (sort keys %$tmap) {
878 my $data = $tmap->{$domain};
879 my $host = $data->{host};
880 if ($host =~ m/^$IPV4RE$/) {
881 push @$transportnets, "$host/32";
882 } elsif ($host =~ m/^$IPV6RE$/) {
883 push @$transportnets, "[$host]/128";
884 }
885 }
886
887 $vars->{postfix}->{transportnets} = join(' ', @$transportnets);
888
889 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
890 push @$mynetworks, @$transportnets;
891 push @$mynetworks, $int_net_cidr;
892 push @$mynetworks, 'hash:/etc/pmg/mynetworks';
893
894 my $netlist = PVE::INotify::read_file('mynetworks');
895 # add default relay to mynetworks
896 if (my $relay = $self->get('mail', 'relay')) {
897 if ($relay =~ m/^$IPV4RE$/) {
898 push @$mynetworks, "$relay/32";
899 } elsif ($relay =~ m/^$IPV6RE$/) {
900 push @$mynetworks, "[$relay]/128";
901 } else {
902 # DNS name - do nothing ?
903 }
904 }
905
906 $vars->{postfix}->{mynetworks} = join(' ', @$mynetworks);
907
908 my $usepolicy = 0;
909 $usepolicy = 1 if $self->get('mail', 'greylist') ||
910 $self->get('mail', 'spf') || $self->get('mail', 'use_rbl');
911 $vars->{postfix}->{usepolicy} = $usepolicy;
912
913 my $resolv = PVE::INotify::read_file('resolvconf');
914 $vars->{dns}->{hostname} = $nodename;
915 $vars->{dns}->{domain} = $resolv->{search};
916
917 return $vars;
918 }
919
920 # rewrite file from template
921 # return true if file has changed
922 sub rewrite_config_file {
923 my ($self, $tmplname, $dstfn) = @_;
924
925 my $demo = $self->get('admin', 'demo');
926
927 my $srcfn = ($tmplname =~ m|^.?/|) ?
928 $tmplname : "/var/lib/pmg/templates/$tmplname";
929
930 if ($demo) {
931 my $demosrc = "$srcfn.demo";
932 $srcfn = $demosrc if -f $demosrc;
933 }
934
935 my ($perm, $uid, $gid);
936
937 my $srcfd = IO::File->new ($srcfn, "r")
938 || die "cant read template '$srcfn' - $!: ERROR";
939
940 if ($dstfn eq '/etc/fetchmailrc') {
941 (undef, undef, $uid, $gid) = getpwnam('fetchmail');
942 $perm = 0600;
943 } elsif ($dstfn eq '/etc/clamav/freshclam.conf') {
944 # needed if file contains a HTTPProxyPasswort
945
946 $uid = getpwnam('clamav');
947 $gid = getgrnam('adm');
948 $perm = 0600;
949 }
950
951 my $template = Template->new({});
952
953 my $vars = $self->get_template_vars();
954
955 my $output = '';
956
957 $template->process($srcfd, $vars, \$output) ||
958 die $template->error();
959
960 $srcfd->close();
961
962 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
963
964 return 0 if defined($old) && ($old eq $output); # no change
965
966 PVE::Tools::file_set_contents($dstfn, $output, $perm);
967
968 if (defined($uid) && defined($gid)) {
969 chown($uid, $gid, $dstfn);
970 }
971
972 return 1;
973 }
974
975 # rewrite spam configuration
976 sub rewrite_config_spam {
977 my ($self) = @_;
978
979 my $use_awl = $self->get('spam', 'use_awl');
980 my $use_bayes = $self->get('spam', 'use_bayes');
981 my $use_razor = $self->get('spam', 'use_razor');
982
983 my $changes = 0;
984
985 # delete AW and bayes databases if those features are disabled
986 if (!$use_awl) {
987 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
988 }
989
990 if (!$use_bayes) {
991 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
992 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
993 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
994 }
995
996 # make sure we have a custom.cf file (else cluster sync fails)
997 IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
998
999 $changes = 1 if $self->rewrite_config_file(
1000 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1001
1002 $changes = 1 if $self->rewrite_config_file(
1003 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1004
1005 $changes = 1 if $self->rewrite_config_file(
1006 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1007
1008 $changes = 1 if $self->rewrite_config_file(
1009 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1010
1011 if ($use_razor) {
1012 mkdir "/root/.razor";
1013
1014 $changes = 1 if $self->rewrite_config_file(
1015 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1016
1017 if (! -e '/root/.razor/identity') {
1018 eval {
1019 my $timeout = 30;
1020 PVE::Tools::run_command(['razor-admin', '-discover'], timeout => $timeout);
1021 PVE::Tools::run_command(['razor-admin', '-register'], timeout => $timeout);
1022 };
1023 my $err = $@;
1024 syslog('info', msgquote ("registering razor failed: $err")) if $err;
1025 }
1026 }
1027
1028 return $changes;
1029 }
1030
1031 # rewrite ClamAV configuration
1032 sub rewrite_config_clam {
1033 my ($self) = @_;
1034
1035 return $self->rewrite_config_file(
1036 'clamd.conf.in', '/etc/clamav/clamd.conf');
1037 }
1038
1039 sub rewrite_config_freshclam {
1040 my ($self) = @_;
1041
1042 return $self->rewrite_config_file(
1043 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1044 }
1045
1046 sub rewrite_config_postgres {
1047 my ($self) = @_;
1048
1049 my $pgconfdir = "/etc/postgresql/9.6/main";
1050
1051 my $changes = 0;
1052
1053 $changes = 1 if $self->rewrite_config_file(
1054 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1055
1056 $changes = 1 if $self->rewrite_config_file(
1057 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1058
1059 return $changes;
1060 }
1061
1062 # rewrite /root/.forward
1063 sub rewrite_dot_forward {
1064 my ($self) = @_;
1065
1066 my $dstfn = '/root/.forward';
1067
1068 my $email = $self->get('admin', 'email');
1069
1070 my $output = '';
1071 if ($email && $email =~ m/\s*(\S+)\s*/) {
1072 $output = "$1\n";
1073 } else {
1074 # empty .forward does not forward mails (see man local)
1075 }
1076
1077 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1078
1079 return 0 if defined($old) && ($old eq $output); # no change
1080
1081 PVE::Tools::file_set_contents($dstfn, $output);
1082
1083 return 1;
1084 }
1085
1086 # rewrite /etc/postfix/*
1087 sub rewrite_config_postfix {
1088 my ($self) = @_;
1089
1090 # make sure we have required files (else postfix start fails)
1091 postmap_pmg_domains();
1092 postmap_pmg_transport();
1093 postmap_pmg_mynetworks();
1094
1095 IO::File->new($transport_map_filename, 'a', 0644);
1096
1097 my $changes = 0;
1098
1099 if ($self->get('mail', 'tls')) {
1100 eval {
1101 PMG::Utils::gen_proxmox_tls_cert();
1102 };
1103 syslog ('info', msgquote ("generating certificate failed: $@")) if $@;
1104 }
1105
1106 $changes = 1 if $self->rewrite_config_file(
1107 'main.cf.in', '/etc/postfix/main.cf');
1108
1109 $changes = 1 if $self->rewrite_config_file(
1110 'master.cf.in', '/etc/postfix/master.cf');
1111
1112 #rewrite_config_transports ($class);
1113 #rewrite_config_whitelist ($class);
1114 #rewrite_config_tls_policy ($class);
1115
1116 # make sure aliases.db is up to date
1117 system('/usr/bin/newaliases');
1118
1119 return $changes;
1120 }
1121
1122 sub rewrite_config {
1123 my ($self, $restart_services) = @_;
1124
1125 if ($self->rewrite_config_postfix() && $restart_services) {
1126 PMG::Utils::service_cmd('postfix', 'restart');
1127 }
1128
1129 if ($self->rewrite_dot_forward() && $restart_services) {
1130 # no need to restart anything
1131 }
1132
1133 if ($self->rewrite_config_postgres() && $restart_services) {
1134 # do nothing (too many side effects)?
1135 # does not happen anyways, because config does not change.
1136 }
1137
1138 if ($self->rewrite_config_spam() && $restart_services) {
1139 PMG::Utils::service_cmd('pmg-smtp-filter', 'restart');
1140 }
1141
1142 if ($self->rewrite_config_clam() && $restart_services) {
1143 PMG::Utils::service_cmd('clamav-daemon', 'restart');
1144 }
1145
1146 if ($self->rewrite_config_freshclam() && $restart_services) {
1147 PMG::Utils::service_cmd('clamav-freshclam', 'restart');
1148 }
1149
1150 }
1151
1152 1;