]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/Config.pm
adapt clamav.conf.in to new upstream version
[pmg-api.git] / src / PMG / Config.pm
1 package PMG::Config::Base;
2
3 use strict;
4 use warnings;
5 use URI;
6 use Data::Dumper;
7
8 use PVE::Tools;
9 use PVE::JSONSchema qw(get_standard_option);
10 use PVE::SectionConfig;
11
12 use base qw(PVE::SectionConfig);
13
14 my $defaultData = {
15 propertyList => {
16 type => { description => "Section type." },
17 section => {
18 description => "Section ID.",
19 type => 'string', format => 'pve-configid',
20 },
21 },
22 };
23
24 sub private {
25 return $defaultData;
26 }
27
28 sub format_section_header {
29 my ($class, $type, $sectionId) = @_;
30
31 die "internal error ($type ne $sectionId)" if $type ne $sectionId;
32
33 return "section: $type\n";
34 }
35
36
37 sub parse_section_header {
38 my ($class, $line) = @_;
39
40 if ($line =~ m/^section:\s*(\S+)\s*$/) {
41 my $section = $1;
42 my $errmsg = undef; # set if you want to skip whole section
43 eval { PVE::JSONSchema::pve_verify_configid($section); };
44 $errmsg = $@ if $@;
45 my $config = {}; # to return additional attributes
46 return ($section, $section, $errmsg, $config);
47 }
48 return undef;
49 }
50
51 package PMG::Config::Admin;
52
53 use strict;
54 use warnings;
55
56 use base qw(PMG::Config::Base);
57
58 sub type {
59 return 'admin';
60 }
61
62 sub properties {
63 return {
64 advfilter => {
65 description => "Use advanced filters for statistic.",
66 type => 'boolean',
67 default => 1,
68 },
69 dailyreport => {
70 description => "Send daily reports.",
71 type => 'boolean',
72 default => 1,
73 },
74 statlifetime => {
75 description => "User Statistics Lifetime (days)",
76 type => 'integer',
77 default => 7,
78 minimum => 1,
79 },
80 demo => {
81 description => "Demo mode - do not start SMTP filter.",
82 type => 'boolean',
83 default => 0,
84 },
85 email => {
86 description => "Administrator E-Mail address.",
87 type => 'string', format => 'email',
88 default => 'admin@domain.tld',
89 },
90 http_proxy => {
91 description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
92 type => 'string',
93 pattern => "http://.*",
94 },
95 avast => {
96 description => "Use Avast Virus Scanner (/usr/bin/scan). You need to buy and install 'Avast Core Security' before you can enable this feature.",
97 type => 'boolean',
98 default => 0,
99 },
100 clamav => {
101 description => "Use ClamAV Virus Scanner. This is the default virus scanner and is enabled by default.",
102 type => 'boolean',
103 default => 1,
104 },
105 custom_check => {
106 description => "Use Custom Check Script. The script has to take the defined arguments and can return Virus findings or a Spamscore.",
107 type => 'boolean',
108 default => 0,
109 },
110 custom_check_path => {
111 description => "Absolute Path to the Custom Check Script",
112 type => 'string', pattern => '^/([^/\0]+\/)+[^/\0]+$',
113 default => '/usr/local/bin/pmg-custom-check',
114 },
115 };
116 }
117
118 sub options {
119 return {
120 advfilter => { optional => 1 },
121 avast => { optional => 1 },
122 clamav => { optional => 1 },
123 statlifetime => { optional => 1 },
124 dailyreport => { optional => 1 },
125 demo => { optional => 1 },
126 email => { optional => 1 },
127 http_proxy => { optional => 1 },
128 custom_check => { optional => 1 },
129 custom_check_path => { optional => 1 },
130 };
131 }
132
133 package PMG::Config::Spam;
134
135 use strict;
136 use warnings;
137
138 use base qw(PMG::Config::Base);
139
140 sub type {
141 return 'spam';
142 }
143
144 sub properties {
145 return {
146 languages => {
147 description => "This option is used to specify which languages are considered OK for incoming mail.",
148 type => 'string',
149 pattern => '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
150 default => 'all',
151 },
152 use_bayes => {
153 description => "Whether to use the naive-Bayesian-style classifier.",
154 type => 'boolean',
155 default => 1,
156 },
157 use_awl => {
158 description => "Use the Auto-Whitelist plugin.",
159 type => 'boolean',
160 default => 1,
161 },
162 use_razor => {
163 description => "Whether to use Razor2, if it is available.",
164 type => 'boolean',
165 default => 1,
166 },
167 wl_bounce_relays => {
168 description => "Whitelist legitimate bounce relays.",
169 type => 'string',
170 },
171 clamav_heuristic_score => {
172 description => "Score for ClamAV heuristics (Google Safe Browsing database, PhishingScanURLs, ...).",
173 type => 'integer',
174 minimum => 0,
175 maximum => 1000,
176 default => 3,
177 },
178 bounce_score => {
179 description => "Additional score for bounce mails.",
180 type => 'integer',
181 minimum => 0,
182 maximum => 1000,
183 default => 0,
184 },
185 rbl_checks => {
186 description => "Enable real time blacklists (RBL) checks.",
187 type => 'boolean',
188 default => 1,
189 },
190 maxspamsize => {
191 description => "Maximum size of spam messages in bytes.",
192 type => 'integer',
193 minimum => 64,
194 default => 256*1024,
195 },
196 };
197 }
198
199 sub options {
200 return {
201 use_awl => { optional => 1 },
202 use_razor => { optional => 1 },
203 wl_bounce_relays => { optional => 1 },
204 languages => { optional => 1 },
205 use_bayes => { optional => 1 },
206 clamav_heuristic_score => { optional => 1 },
207 bounce_score => { optional => 1 },
208 rbl_checks => { optional => 1 },
209 maxspamsize => { optional => 1 },
210 };
211 }
212
213 package PMG::Config::SpamQuarantine;
214
215 use strict;
216 use warnings;
217
218 use base qw(PMG::Config::Base);
219
220 sub type {
221 return 'spamquar';
222 }
223
224 sub properties {
225 return {
226 lifetime => {
227 description => "Quarantine life time (days)",
228 type => 'integer',
229 minimum => 1,
230 default => 7,
231 },
232 authmode => {
233 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.",
234 type => 'string',
235 enum => [qw(ticket ldap ldapticket)],
236 default => 'ticket',
237 },
238 reportstyle => {
239 description => "Spam report style.",
240 type => 'string',
241 enum => [qw(none short verbose custom)],
242 default => 'verbose',
243 },
244 viewimages => {
245 description => "Allow to view images.",
246 type => 'boolean',
247 default => 1,
248 },
249 allowhrefs => {
250 description => "Allow to view hyperlinks.",
251 type => 'boolean',
252 default => 1,
253 },
254 hostname => {
255 description => "Quarantine Host. Useful if you run a Cluster and want users to connect to a specific host.",
256 type => 'string', format => 'address',
257 },
258 port => {
259 description => "Quarantine Port. Useful if you have a reverse proxy or port forwarding for the webinterface. Only used for the generated Spam report.",
260 type => 'integer',
261 minimum => 1,
262 maximum => 65535,
263 default => 8006,
264 },
265 protocol => {
266 description => "Quarantine Webinterface Protocol. Useful if you have a reverse proxy for the webinterface. Only used for the generated Spam report.",
267 type => 'string',
268 enum => [qw(http https)],
269 default => 'https',
270 },
271 mailfrom => {
272 description => "Text for 'From' header in daily spam report mails.",
273 type => 'string',
274 },
275 };
276 }
277
278 sub options {
279 return {
280 mailfrom => { optional => 1 },
281 hostname => { optional => 1 },
282 lifetime => { optional => 1 },
283 authmode => { optional => 1 },
284 reportstyle => { optional => 1 },
285 viewimages => { optional => 1 },
286 allowhrefs => { optional => 1 },
287 port => { optional => 1 },
288 protocol => { optional => 1 },
289 };
290 }
291
292 package PMG::Config::VirusQuarantine;
293
294 use strict;
295 use warnings;
296
297 use base qw(PMG::Config::Base);
298
299 sub type {
300 return 'virusquar';
301 }
302
303 sub properties {
304 return {};
305 }
306
307 sub options {
308 return {
309 lifetime => { optional => 1 },
310 viewimages => { optional => 1 },
311 allowhrefs => { optional => 1 },
312 };
313 }
314
315 package PMG::Config::ClamAV;
316
317 use strict;
318 use warnings;
319
320 use base qw(PMG::Config::Base);
321
322 sub type {
323 return 'clamav';
324 }
325
326 sub properties {
327 return {
328 dbmirror => {
329 description => "ClamAV database mirror server.",
330 type => 'string',
331 default => 'database.clamav.net',
332 },
333 archiveblockencrypted => {
334 description => "Whether to block encrypted archives and documents. Mark encrypted archives and documents as viruses.",
335 type => 'boolean',
336 default => 0,
337 },
338 archivemaxrec => {
339 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.",
340 type => 'integer',
341 minimum => 1,
342 default => 5,
343 },
344 archivemaxfiles => {
345 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.",
346 type => 'integer',
347 minimum => 0,
348 default => 1000,
349 },
350 archivemaxsize => {
351 description => "Files larger than this limit won't be scanned.",
352 type => 'integer',
353 minimum => 1000000,
354 default => 25000000,
355 },
356 maxscansize => {
357 description => "Sets the maximum amount of data to be scanned for each input file.",
358 type => 'integer',
359 minimum => 1000000,
360 default => 100000000,
361 },
362 maxcccount => {
363 description => "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
364 type => 'integer',
365 minimum => 0,
366 default => 0,
367 },
368 safebrowsing => {
369 description => "Enables support for Google Safe Browsing.",
370 type => 'boolean',
371 default => 1
372 },
373 };
374 }
375
376 sub options {
377 return {
378 archiveblockencrypted => { optional => 1 },
379 archivemaxrec => { optional => 1 },
380 archivemaxfiles => { optional => 1 },
381 archivemaxsize => { optional => 1 },
382 maxscansize => { optional => 1 },
383 dbmirror => { optional => 1 },
384 maxcccount => { optional => 1 },
385 safebrowsing => { optional => 1 },
386 };
387 }
388
389 package PMG::Config::Mail;
390
391 use strict;
392 use warnings;
393
394 use PVE::ProcFSTools;
395
396 use base qw(PMG::Config::Base);
397
398 sub type {
399 return 'mail';
400 }
401
402 my $physicalmem = 0;
403 sub physical_memory {
404
405 return $physicalmem if $physicalmem;
406
407 my $info = PVE::ProcFSTools::read_meminfo();
408 my $total = int($info->{memtotal} / (1024*1024));
409
410 return $total;
411 }
412
413 sub get_max_filters {
414 # estimate optimal number of filter servers
415
416 my $max_servers = 5;
417 my $servermem = 120;
418 my $memory = physical_memory();
419 my $add_servers = int(($memory - 512)/$servermem);
420 $max_servers += $add_servers if $add_servers > 0;
421 $max_servers = 40 if $max_servers > 40;
422
423 return $max_servers - 2;
424 }
425
426 sub get_max_smtpd {
427 # estimate optimal number of smtpd daemons
428
429 my $max_servers = 25;
430 my $servermem = 20;
431 my $memory = physical_memory();
432 my $add_servers = int(($memory - 512)/$servermem);
433 $max_servers += $add_servers if $add_servers > 0;
434 $max_servers = 100 if $max_servers > 100;
435 return $max_servers;
436 }
437
438 sub get_max_policy {
439 # estimate optimal number of proxpolicy servers
440 my $max_servers = 2;
441 my $memory = physical_memory();
442 $max_servers = 5 if $memory >= 500;
443 return $max_servers;
444 }
445
446 sub properties {
447 return {
448 int_port => {
449 description => "SMTP port number for outgoing mail (trusted).",
450 type => 'integer',
451 minimum => 1,
452 maximum => 65535,
453 default => 26,
454 },
455 ext_port => {
456 description => "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
457 type => 'integer',
458 minimum => 1,
459 maximum => 65535,
460 default => 25,
461 },
462 relay => {
463 description => "The default mail delivery transport (incoming mails).",
464 type => 'string', format => 'address',
465 },
466 relayport => {
467 description => "SMTP port number for relay host.",
468 type => 'integer',
469 minimum => 1,
470 maximum => 65535,
471 default => 25,
472 },
473 relaynomx => {
474 description => "Disable MX lookups for default relay.",
475 type => 'boolean',
476 default => 0,
477 },
478 smarthost => {
479 description => "When set, all outgoing mails are deliverd to the specified smarthost.",
480 type => 'string', format => 'address',
481 },
482 smarthostport => {
483 description => "SMTP port number for smarthost.",
484 type => 'integer',
485 minimum => 1,
486 maximum => 65535,
487 default => 25,
488 },
489 banner => {
490 description => "ESMTP banner.",
491 type => 'string',
492 maxLength => 1024,
493 default => 'ESMTP Proxmox',
494 },
495 max_filters => {
496 description => "Maximum number of pmg-smtp-filter processes.",
497 type => 'integer',
498 minimum => 3,
499 maximum => 40,
500 default => get_max_filters(),
501 },
502 max_policy => {
503 description => "Maximum number of pmgpolicy processes.",
504 type => 'integer',
505 minimum => 2,
506 maximum => 10,
507 default => get_max_policy(),
508 },
509 max_smtpd_in => {
510 description => "Maximum number of SMTP daemon processes (in).",
511 type => 'integer',
512 minimum => 3,
513 maximum => 100,
514 default => get_max_smtpd(),
515 },
516 max_smtpd_out => {
517 description => "Maximum number of SMTP daemon processes (out).",
518 type => 'integer',
519 minimum => 3,
520 maximum => 100,
521 default => get_max_smtpd(),
522 },
523 conn_count_limit => {
524 description => "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
525 type => 'integer',
526 minimum => 0,
527 default => 50,
528 },
529 conn_rate_limit => {
530 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.",
531 type => 'integer',
532 minimum => 0,
533 default => 0,
534 },
535 message_rate_limit => {
536 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.",
537 type => 'integer',
538 minimum => 0,
539 default => 0,
540 },
541 hide_received => {
542 description => "Hide received header in outgoing mails.",
543 type => 'boolean',
544 default => 0,
545 },
546 maxsize => {
547 description => "Maximum email size. Larger mails are rejected.",
548 type => 'integer',
549 minimum => 1024,
550 default => 1024*1024*10,
551 },
552 dwarning => {
553 description => "SMTP delay warning time (in hours).",
554 type => 'integer',
555 minimum => 0,
556 default => 4,
557 },
558 tls => {
559 description => "Enable TLS.",
560 type => 'boolean',
561 default => 0,
562 },
563 tlslog => {
564 description => "Enable TLS Logging.",
565 type => 'boolean',
566 default => 0,
567 },
568 tlsheader => {
569 description => "Add TLS received header.",
570 type => 'boolean',
571 default => 0,
572 },
573 spf => {
574 description => "Use Sender Policy Framework.",
575 type => 'boolean',
576 default => 1,
577 },
578 greylist => {
579 description => "Use Greylisting.",
580 type => 'boolean',
581 default => 1,
582 },
583 helotests => {
584 description => "Use SMTP HELO tests.",
585 type => 'boolean',
586 default => 0,
587 },
588 rejectunknown => {
589 description => "Reject unknown clients.",
590 type => 'boolean',
591 default => 0,
592 },
593 rejectunknownsender => {
594 description => "Reject unknown senders.",
595 type => 'boolean',
596 default => 0,
597 },
598 verifyreceivers => {
599 description => "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
600 type => 'string',
601 enum => ['450', '550'],
602 },
603 dnsbl_sites => {
604 description => "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
605 type => 'string', format => 'dnsbl-entry-list',
606 },
607 dnsbl_threshold => {
608 description => "The inclusive lower bound for blocking a remote SMTP client, based on its combined DNSBL score (see postscreen_dnsbl_threshold parameter).",
609 type => 'integer',
610 minimum => 0,
611 default => 1
612 },
613 };
614 }
615
616 sub options {
617 return {
618 int_port => { optional => 1 },
619 ext_port => { optional => 1 },
620 smarthost => { optional => 1 },
621 smarthostport => { optional => 1 },
622 relay => { optional => 1 },
623 relayport => { optional => 1 },
624 relaynomx => { optional => 1 },
625 dwarning => { optional => 1 },
626 max_smtpd_in => { optional => 1 },
627 max_smtpd_out => { optional => 1 },
628 greylist => { optional => 1 },
629 helotests => { optional => 1 },
630 tls => { optional => 1 },
631 tlslog => { optional => 1 },
632 tlsheader => { optional => 1 },
633 spf => { optional => 1 },
634 maxsize => { optional => 1 },
635 banner => { optional => 1 },
636 max_filters => { optional => 1 },
637 max_policy => { optional => 1 },
638 hide_received => { optional => 1 },
639 rejectunknown => { optional => 1 },
640 rejectunknownsender => { optional => 1 },
641 conn_count_limit => { optional => 1 },
642 conn_rate_limit => { optional => 1 },
643 message_rate_limit => { optional => 1 },
644 verifyreceivers => { optional => 1 },
645 dnsbl_sites => { optional => 1 },
646 dnsbl_threshold => { optional => 1 },
647 };
648 }
649
650 package PMG::Config;
651
652 use strict;
653 use warnings;
654 use IO::File;
655 use Data::Dumper;
656 use Template;
657
658 use PVE::SafeSyslog;
659 use PVE::Tools qw($IPV4RE $IPV6RE);
660 use PVE::INotify;
661 use PVE::JSONSchema;
662
663 use PMG::Cluster;
664 use PMG::Utils;
665
666 PMG::Config::Admin->register();
667 PMG::Config::Mail->register();
668 PMG::Config::SpamQuarantine->register();
669 PMG::Config::VirusQuarantine->register();
670 PMG::Config::Spam->register();
671 PMG::Config::ClamAV->register();
672
673 # initialize all plugins
674 PMG::Config::Base->init();
675
676 PVE::JSONSchema::register_format(
677 'transport-domain', \&pmg_verify_transport_domain);
678
679 sub pmg_verify_transport_domain {
680 my ($name, $noerr) = @_;
681
682 # like dns-name, but can contain leading dot
683 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
684
685 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
686 return undef if $noerr;
687 die "value does not look like a valid transport domain\n";
688 }
689 return $name;
690 }
691
692 PVE::JSONSchema::register_format(
693 'transport-domain-or-email', \&pmg_verify_transport_domain_or_email);
694
695 sub pmg_verify_transport_domain_or_email {
696 my ($name, $noerr) = @_;
697
698 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
699
700 # email address
701 if ($name =~ m/^(?:[^\s\/\@]+\@)(${namere}\.)*${namere}$/) {
702 return $name;
703 }
704
705 # like dns-name, but can contain leading dot
706 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
707 return undef if $noerr;
708 die "value does not look like a valid transport domain or email address\n";
709 }
710 return $name;
711 }
712
713 PVE::JSONSchema::register_format(
714 'dnsbl-entry', \&pmg_verify_dnsbl_entry);
715
716 sub pmg_verify_dnsbl_entry {
717 my ($name, $noerr) = @_;
718
719 # like dns-name, but can contain trailing filter and weight: 'domain=<FILTER>*<WEIGHT>'
720 # see http://www.postfix.org/postconf.5.html#postscreen_dnsbl_sites
721 # we don't implement the ';' separated numbers in pattern, because this
722 # breaks at PVE::JSONSchema::split_list
723 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
724
725 my $dnsbloctet = qr/[0-9]+|\[(?:[0-9]+\.\.[0-9]+)\]/;
726 my $filterre = qr/=$dnsbloctet(:?\.$dnsbloctet){3}/;
727 if ($name !~ /^(${namere}\.)*${namere}(:?${filterre})?(?:\*\-?\d+)?$/) {
728 return undef if $noerr;
729 die "value '$name' does not look like a valid dnsbl entry\n";
730 }
731 return $name;
732 }
733
734 sub new {
735 my ($type) = @_;
736
737 my $class = ref($type) || $type;
738
739 my $cfg = PVE::INotify::read_file("pmg.conf");
740
741 return bless $cfg, $class;
742 }
743
744 sub write {
745 my ($self) = @_;
746
747 PVE::INotify::write_file("pmg.conf", $self);
748 }
749
750 my $lockfile = "/var/lock/pmgconfig.lck";
751
752 sub lock_config {
753 my ($code, $errmsg) = @_;
754
755 my $p = PVE::Tools::lock_file($lockfile, undef, $code);
756 if (my $err = $@) {
757 $errmsg ? die "$errmsg: $err" : die $err;
758 }
759 }
760
761 # set section values
762 sub set {
763 my ($self, $section, $key, $value) = @_;
764
765 my $pdata = PMG::Config::Base->private();
766
767 my $plugin = $pdata->{plugins}->{$section};
768 die "no such section '$section'" if !$plugin;
769
770 if (defined($value)) {
771 my $tmp = PMG::Config::Base->check_value($section, $key, $value, $section, 0);
772 $self->{ids}->{$section} = { type => $section } if !defined($self->{ids}->{$section});
773 $self->{ids}->{$section}->{$key} = PMG::Config::Base->decode_value($section, $key, $tmp);
774 } else {
775 if (defined($self->{ids}->{$section})) {
776 delete $self->{ids}->{$section}->{$key};
777 }
778 }
779
780 return undef;
781 }
782
783 # get section value or default
784 sub get {
785 my ($self, $section, $key, $nodefault) = @_;
786
787 my $pdata = PMG::Config::Base->private();
788 my $pdesc = $pdata->{propertyList}->{$key};
789 die "no such property '$section/$key'\n"
790 if !(defined($pdesc) && defined($pdata->{options}->{$section}) &&
791 defined($pdata->{options}->{$section}->{$key}));
792
793 if (defined($self->{ids}->{$section}) &&
794 defined(my $value = $self->{ids}->{$section}->{$key})) {
795 return $value;
796 }
797
798 return undef if $nodefault;
799
800 return $pdesc->{default};
801 }
802
803 # get a whole section with default value
804 sub get_section {
805 my ($self, $section) = @_;
806
807 my $pdata = PMG::Config::Base->private();
808 return undef if !defined($pdata->{options}->{$section});
809
810 my $res = {};
811
812 foreach my $key (keys %{$pdata->{options}->{$section}}) {
813
814 my $pdesc = $pdata->{propertyList}->{$key};
815
816 if (defined($self->{ids}->{$section}) &&
817 defined(my $value = $self->{ids}->{$section}->{$key})) {
818 $res->{$key} = $value;
819 next;
820 }
821 $res->{$key} = $pdesc->{default};
822 }
823
824 return $res;
825 }
826
827 # get a whole config with default values
828 sub get_config {
829 my ($self) = @_;
830
831 my $pdata = PMG::Config::Base->private();
832
833 my $res = {};
834
835 foreach my $type (keys %{$pdata->{plugins}}) {
836 my $plugin = $pdata->{plugins}->{$type};
837 $res->{$type} = $self->get_section($type);
838 }
839
840 return $res;
841 }
842
843 sub read_pmg_conf {
844 my ($filename, $fh) = @_;
845
846 local $/ = undef; # slurp mode
847
848 my $raw = <$fh> if defined($fh);
849
850 return PMG::Config::Base->parse_config($filename, $raw);
851 }
852
853 sub write_pmg_conf {
854 my ($filename, $fh, $cfg) = @_;
855
856 my $raw = PMG::Config::Base->write_config($filename, $cfg);
857
858 PVE::Tools::safe_print($filename, $fh, $raw);
859 }
860
861 PVE::INotify::register_file('pmg.conf', "/etc/pmg/pmg.conf",
862 \&read_pmg_conf,
863 \&write_pmg_conf,
864 undef, always_call_parser => 1);
865
866 # parsers/writers for other files
867
868 my $domainsfilename = "/etc/pmg/domains";
869
870 sub postmap_pmg_domains {
871 PMG::Utils::run_postmap($domainsfilename);
872 }
873
874 sub read_pmg_domains {
875 my ($filename, $fh) = @_;
876
877 my $domains = {};
878
879 my $comment = '';
880 if (defined($fh)) {
881 while (defined(my $line = <$fh>)) {
882 chomp $line;
883 next if $line =~ m/^\s*$/;
884 if ($line =~ m/^#(.*)\s*$/) {
885 $comment = $1;
886 next;
887 }
888 if ($line =~ m/^(\S+)\s.*$/) {
889 my $domain = $1;
890 $domains->{$domain} = {
891 domain => $domain, comment => $comment };
892 $comment = '';
893 } else {
894 warn "parse error in '$filename': $line\n";
895 $comment = '';
896 }
897 }
898 }
899
900 return $domains;
901 }
902
903 sub write_pmg_domains {
904 my ($filename, $fh, $domains) = @_;
905
906 foreach my $domain (sort keys %$domains) {
907 my $comment = $domains->{$domain}->{comment};
908 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
909 if defined($comment) && $comment !~ m/^\s*$/;
910
911 PVE::Tools::safe_print($filename, $fh, "$domain 1\n");
912 }
913 }
914
915 PVE::INotify::register_file('domains', $domainsfilename,
916 \&read_pmg_domains,
917 \&write_pmg_domains,
918 undef, always_call_parser => 1);
919
920 my $mynetworks_filename = "/etc/pmg/mynetworks";
921
922 sub read_pmg_mynetworks {
923 my ($filename, $fh) = @_;
924
925 my $mynetworks = {};
926
927 my $comment = '';
928 if (defined($fh)) {
929 while (defined(my $line = <$fh>)) {
930 chomp $line;
931 next if $line =~ m/^\s*$/;
932 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
933 my ($network, $prefix_size, $comment) = ($1, $2, $3);
934 my $cidr = "$network/${prefix_size}";
935 $mynetworks->{$cidr} = {
936 cidr => $cidr,
937 network_address => $network,
938 prefix_size => $prefix_size,
939 comment => $comment // '',
940 };
941 } else {
942 warn "parse error in '$filename': $line\n";
943 }
944 }
945 }
946
947 return $mynetworks;
948 }
949
950 sub write_pmg_mynetworks {
951 my ($filename, $fh, $mynetworks) = @_;
952
953 foreach my $cidr (sort keys %$mynetworks) {
954 my $data = $mynetworks->{$cidr};
955 my $comment = $data->{comment} // '*';
956 PVE::Tools::safe_print($filename, $fh, "$cidr #$comment\n");
957 }
958 }
959
960 PVE::INotify::register_file('mynetworks', $mynetworks_filename,
961 \&read_pmg_mynetworks,
962 \&write_pmg_mynetworks,
963 undef, always_call_parser => 1);
964
965 PVE::JSONSchema::register_format(
966 'tls-policy', \&pmg_verify_tls_policy);
967
968 # TODO: extend to parse attributes of the policy
969 my $VALID_TLS_POLICY_RE = qr/none|may|encrypt|dane|dane-only|fingerprint|verify|secure/;
970 sub pmg_verify_tls_policy {
971 my ($policy, $noerr) = @_;
972
973 if ($policy !~ /^$VALID_TLS_POLICY_RE\b/) {
974 return undef if $noerr;
975 die "value '$policy' does not look like a valid tls policy\n";
976 }
977 return $policy;
978 }
979
980 PVE::JSONSchema::register_format(
981 'tls-policy-strict', \&pmg_verify_tls_policy_strict);
982
983 sub pmg_verify_tls_policy_strict {
984 my ($policy, $noerr) = @_;
985
986 if ($policy !~ /^$VALID_TLS_POLICY_RE$/) {
987 return undef if $noerr;
988 die "value '$policy' does not look like a valid tls policy\n";
989 }
990 return $policy;
991 }
992
993 sub read_tls_policy {
994 my ($filename, $fh) = @_;
995
996 return {} if !defined($fh);
997
998 my $tls_policy = {};
999
1000 while (defined(my $line = <$fh>)) {
1001 chomp $line;
1002 next if $line =~ m/^\s*$/;
1003 next if $line =~ m/^#(.*)\s*$/;
1004
1005 my $parse_error = sub {
1006 my ($err) = @_;
1007 die "parse error in '$filename': $line - $err";
1008 };
1009
1010 if ($line =~ m/^(\S+)\s+(.+)\s*$/) {
1011 my ($domain, $policy) = ($1, $2);
1012
1013 eval {
1014 pmg_verify_transport_domain($domain);
1015 pmg_verify_tls_policy($policy);
1016 };
1017 if (my $err = $@) {
1018 $parse_error->($err);
1019 next;
1020 }
1021
1022 $tls_policy->{$domain} = {
1023 domain => $domain,
1024 policy => $policy,
1025 };
1026 } else {
1027 $parse_error->('wrong format');
1028 }
1029 }
1030
1031 return $tls_policy;
1032 }
1033
1034 sub write_tls_policy {
1035 my ($filename, $fh, $tls_policy) = @_;
1036
1037 return if !$tls_policy;
1038
1039 foreach my $domain (sort keys %$tls_policy) {
1040 my $entry = $tls_policy->{$domain};
1041 PVE::Tools::safe_print(
1042 $filename, $fh, "$entry->{domain} $entry->{policy}\n");
1043 }
1044 }
1045
1046 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
1047 PVE::INotify::register_file('tls_policy', $tls_policy_map_filename,
1048 \&read_tls_policy,
1049 \&write_tls_policy,
1050 undef, always_call_parser => 1);
1051
1052 sub postmap_tls_policy {
1053 PMG::Utils::run_postmap($tls_policy_map_filename);
1054 }
1055
1056 my $transport_map_filename = "/etc/pmg/transport";
1057
1058 sub postmap_pmg_transport {
1059 PMG::Utils::run_postmap($transport_map_filename);
1060 }
1061
1062 sub read_transport_map {
1063 my ($filename, $fh) = @_;
1064
1065 return [] if !defined($fh);
1066
1067 my $res = {};
1068
1069 my $comment = '';
1070
1071 while (defined(my $line = <$fh>)) {
1072 chomp $line;
1073 next if $line =~ m/^\s*$/;
1074 if ($line =~ m/^#(.*)\s*$/) {
1075 $comment = $1;
1076 next;
1077 }
1078
1079 my $parse_error = sub {
1080 my ($err) = @_;
1081 warn "parse error in '$filename': $line - $err";
1082 $comment = '';
1083 };
1084
1085 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
1086 my ($domain, $host, $port) = ($1, $2, $3);
1087
1088 eval { pmg_verify_transport_domain_or_email($domain); };
1089 if (my $err = $@) {
1090 $parse_error->($err);
1091 next;
1092 }
1093 my $use_mx = 1;
1094 if ($host =~ m/^\[(.*)\]$/) {
1095 $host = $1;
1096 $use_mx = 0;
1097 }
1098
1099 eval { PVE::JSONSchema::pve_verify_address($host); };
1100 if (my $err = $@) {
1101 $parse_error->($err);
1102 next;
1103 }
1104
1105 my $data = {
1106 domain => $domain,
1107 host => $host,
1108 port => $port,
1109 use_mx => $use_mx,
1110 comment => $comment,
1111 };
1112 $res->{$domain} = $data;
1113 $comment = '';
1114 } else {
1115 $parse_error->('wrong format');
1116 }
1117 }
1118
1119 return $res;
1120 }
1121
1122 sub write_transport_map {
1123 my ($filename, $fh, $tmap) = @_;
1124
1125 return if !$tmap;
1126
1127 foreach my $domain (sort keys %$tmap) {
1128 my $data = $tmap->{$domain};
1129
1130 my $comment = $data->{comment};
1131 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
1132 if defined($comment) && $comment !~ m/^\s*$/;
1133
1134 my $use_mx = $data->{use_mx};
1135 $use_mx = 0 if $data->{host} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1136
1137 if ($use_mx) {
1138 PVE::Tools::safe_print(
1139 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1140 } else {
1141 PVE::Tools::safe_print(
1142 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1143 }
1144 }
1145 }
1146
1147 PVE::INotify::register_file('transport', $transport_map_filename,
1148 \&read_transport_map,
1149 \&write_transport_map,
1150 undef, always_call_parser => 1);
1151
1152 # config file generation using templates
1153
1154 sub get_template_vars {
1155 my ($self) = @_;
1156
1157 my $vars = { pmg => $self->get_config() };
1158
1159 my $nodename = PVE::INotify::nodename();
1160 my $int_ip = PMG::Cluster::remote_node_ip($nodename);
1161 $vars->{ipconfig}->{int_ip} = $int_ip;
1162
1163 my $transportnets = [];
1164
1165 if (my $tmap = PVE::INotify::read_file('transport')) {
1166 foreach my $domain (sort keys %$tmap) {
1167 my $data = $tmap->{$domain};
1168 my $host = $data->{host};
1169 if ($host =~ m/^$IPV4RE$/) {
1170 push @$transportnets, "$host/32";
1171 } elsif ($host =~ m/^$IPV6RE$/) {
1172 push @$transportnets, "[$host]/128";
1173 }
1174 }
1175 }
1176
1177 $vars->{postfix}->{transportnets} = join(' ', @$transportnets);
1178
1179 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1180
1181 if (my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip, 1)) {
1182 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d+)$/) {
1183 push @$mynetworks, "[$1]/$2";
1184 } else {
1185 push @$mynetworks, $int_net_cidr;
1186 }
1187 } else {
1188 if ($int_ip =~ m/^$IPV6RE$/) {
1189 push @$mynetworks, "[$int_ip]/128";
1190 } else {
1191 push @$mynetworks, "$int_ip/32";
1192 }
1193 }
1194
1195 my $netlist = PVE::INotify::read_file('mynetworks');
1196 foreach my $cidr (sort keys %$netlist) {
1197 if ($cidr =~ m/^($IPV6RE)\/(\d+)$/) {
1198 push @$mynetworks, "[$1]/$2";
1199 } else {
1200 push @$mynetworks, $cidr;
1201 }
1202 }
1203
1204 push @$mynetworks, @$transportnets;
1205
1206 # add default relay to mynetworks
1207 if (my $relay = $self->get('mail', 'relay')) {
1208 if ($relay =~ m/^$IPV4RE$/) {
1209 push @$mynetworks, "$relay/32";
1210 } elsif ($relay =~ m/^$IPV6RE$/) {
1211 push @$mynetworks, "[$relay]/128";
1212 } else {
1213 # DNS name - do nothing ?
1214 }
1215 }
1216
1217 $vars->{postfix}->{mynetworks} = join(' ', @$mynetworks);
1218
1219 # normalize dnsbl_sites
1220 my @dnsbl_sites = PVE::Tools::split_list($vars->{pmg}->{mail}->{dnsbl_sites});
1221 if (scalar(@dnsbl_sites)) {
1222 $vars->{postfix}->{dnsbl_sites} = join(',', @dnsbl_sites);
1223 }
1224
1225 $vars->{postfix}->{dnsbl_threshold} = $self->get('mail', 'dnsbl_threshold');
1226
1227 my $usepolicy = 0;
1228 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1229 $self->get('mail', 'spf');
1230 $vars->{postfix}->{usepolicy} = $usepolicy;
1231
1232 if ($int_ip =~ m/^$IPV6RE$/) {
1233 $vars->{postfix}->{int_ip} = "[$int_ip]";
1234 } else {
1235 $vars->{postfix}->{int_ip} = $int_ip;
1236 }
1237
1238 my $resolv = PVE::INotify::read_file('resolvconf');
1239 $vars->{dns}->{hostname} = $nodename;
1240
1241 my $domain = $resolv->{search} // 'localdomain';
1242 $vars->{dns}->{domain} = $domain;
1243
1244 my $wlbr = "$nodename.$domain";
1245 foreach my $r (PVE::Tools::split_list($vars->{pmg}->{spam}->{wl_bounce_relays})) {
1246 $wlbr .= " $r"
1247 }
1248 $vars->{composed}->{wl_bounce_relays} = $wlbr;
1249
1250 if (my $proxy = $vars->{pmg}->{admin}->{http_proxy}) {
1251 eval {
1252 my $uri = URI->new($proxy);
1253 my $host = $uri->host;
1254 my $port = $uri->port // 8080;
1255 if ($host) {
1256 my $data = { host => $host, port => $port };
1257 if (my $ui = $uri->userinfo) {
1258 my ($username, $pw) = split(/:/, $ui, 2);
1259 $data->{username} = $username;
1260 $data->{password} = $pw if defined($pw);
1261 }
1262 $vars->{proxy} = $data;
1263 }
1264 };
1265 warn "parse http_proxy failed - $@" if $@;
1266 }
1267 $vars->{postgres}->{version} = PMG::Utils::get_pg_server_version();
1268
1269 return $vars;
1270 }
1271
1272 # use one global TT cache
1273 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1274
1275 my $template_toolkit;
1276
1277 sub get_template_toolkit {
1278
1279 return $template_toolkit if $template_toolkit;
1280
1281 $template_toolkit = Template->new({ INCLUDE_PATH => $tt_include_path });
1282
1283 return $template_toolkit;
1284 }
1285
1286 # rewrite file from template
1287 # return true if file has changed
1288 sub rewrite_config_file {
1289 my ($self, $tmplname, $dstfn) = @_;
1290
1291 my $demo = $self->get('admin', 'demo');
1292
1293 if ($demo) {
1294 my $demosrc = "$tmplname.demo";
1295 $tmplname = $demosrc if -f "/var/lib/pmg/templates/$demosrc";
1296 }
1297
1298 my ($perm, $uid, $gid);
1299
1300 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1301 # needed if file contains a HTTPProxyPasswort
1302
1303 $uid = getpwnam('clamav');
1304 $gid = getgrnam('adm');
1305 $perm = 0600;
1306 }
1307
1308 my $tt = get_template_toolkit();
1309
1310 my $vars = $self->get_template_vars();
1311
1312 my $output = '';
1313
1314 $tt->process($tmplname, $vars, \$output) ||
1315 die $tt->error() . "\n";
1316
1317 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1318
1319 return 0 if defined($old) && ($old eq $output); # no change
1320
1321 PVE::Tools::file_set_contents($dstfn, $output, $perm);
1322
1323 if (defined($uid) && defined($gid)) {
1324 chown($uid, $gid, $dstfn);
1325 }
1326
1327 return 1;
1328 }
1329
1330 # rewrite spam configuration
1331 sub rewrite_config_spam {
1332 my ($self) = @_;
1333
1334 my $use_awl = $self->get('spam', 'use_awl');
1335 my $use_bayes = $self->get('spam', 'use_bayes');
1336 my $use_razor = $self->get('spam', 'use_razor');
1337
1338 my $changes = 0;
1339
1340 # delete AW and bayes databases if those features are disabled
1341 if (!$use_awl) {
1342 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1343 }
1344
1345 if (!$use_bayes) {
1346 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1347 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1348 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1349 }
1350
1351 # make sure we have a custom.cf file (else cluster sync fails)
1352 IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1353
1354 $changes = 1 if $self->rewrite_config_file(
1355 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1356
1357 $changes = 1 if $self->rewrite_config_file(
1358 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1359
1360 $changes = 1 if $self->rewrite_config_file(
1361 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1362
1363 $changes = 1 if $self->rewrite_config_file(
1364 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1365
1366 if ($use_razor) {
1367 mkdir "/root/.razor";
1368
1369 $changes = 1 if $self->rewrite_config_file(
1370 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1371
1372 if (! -e '/root/.razor/identity') {
1373 eval {
1374 my $timeout = 30;
1375 PVE::Tools::run_command(['razor-admin', '-discover'], timeout => $timeout);
1376 PVE::Tools::run_command(['razor-admin', '-register'], timeout => $timeout);
1377 };
1378 my $err = $@;
1379 syslog('info', "registering razor failed: $err") if $err;
1380 }
1381 }
1382
1383 return $changes;
1384 }
1385
1386 # rewrite ClamAV configuration
1387 sub rewrite_config_clam {
1388 my ($self) = @_;
1389
1390 return $self->rewrite_config_file(
1391 'clamd.conf.in', '/etc/clamav/clamd.conf');
1392 }
1393
1394 sub rewrite_config_freshclam {
1395 my ($self) = @_;
1396
1397 return $self->rewrite_config_file(
1398 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1399 }
1400
1401 sub rewrite_config_postgres {
1402 my ($self) = @_;
1403
1404 my $pg_maj_version = PMG::Utils::get_pg_server_version();
1405 my $pgconfdir = "/etc/postgresql/$pg_maj_version/main";
1406
1407 my $changes = 0;
1408
1409 $changes = 1 if $self->rewrite_config_file(
1410 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1411
1412 $changes = 1 if $self->rewrite_config_file(
1413 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1414
1415 return $changes;
1416 }
1417
1418 # rewrite /root/.forward
1419 sub rewrite_dot_forward {
1420 my ($self) = @_;
1421
1422 my $dstfn = '/root/.forward';
1423
1424 my $email = $self->get('admin', 'email');
1425
1426 my $output = '';
1427 if ($email && $email =~ m/\s*(\S+)\s*/) {
1428 $output = "$1\n";
1429 } else {
1430 # empty .forward does not forward mails (see man local)
1431 }
1432
1433 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1434
1435 return 0 if defined($old) && ($old eq $output); # no change
1436
1437 PVE::Tools::file_set_contents($dstfn, $output);
1438
1439 return 1;
1440 }
1441
1442 my $write_smtp_whitelist = sub {
1443 my ($filename, $data, $action) = @_;
1444
1445 $action = 'OK' if !$action;
1446
1447 my $old = PVE::Tools::file_get_contents($filename, 1024*1024)
1448 if -f $filename;
1449
1450 my $new = '';
1451 foreach my $k (sort keys %$data) {
1452 $new .= "$k $action\n";
1453 }
1454
1455 return 0 if defined($old) && ($old eq $new); # no change
1456
1457 PVE::Tools::file_set_contents($filename, $new);
1458
1459 PMG::Utils::run_postmap($filename);
1460
1461 return 1;
1462 };
1463
1464 sub rewrite_postfix_whitelist {
1465 my ($rulecache) = @_;
1466
1467 # see man page for regexp_table for postfix regex table format
1468
1469 # we use a hash to avoid duplicate entries in regex tables
1470 my $tolist = {};
1471 my $fromlist = {};
1472 my $clientlist = {};
1473
1474 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1475 my $oclass = ref($obj);
1476 if ($oclass eq 'PMG::RuleDB::Receiver') {
1477 my $addr = PMG::Utils::quote_regex($obj->{address});
1478 $tolist->{"/^$addr\$/"} = 1;
1479 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1480 my $addr = PMG::Utils::quote_regex($obj->{address});
1481 $tolist->{"/^.+\@$addr\$/"} = 1;
1482 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1483 my $addr = $obj->{address};
1484 $addr =~ s|/|\\/|g;
1485 $tolist->{"/^$addr\$/"} = 1;
1486 }
1487 }
1488
1489 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1490 my $oclass = ref($obj);
1491 my $addr = PMG::Utils::quote_regex($obj->{address});
1492 if ($oclass eq 'PMG::RuleDB::EMail') {
1493 my $addr = PMG::Utils::quote_regex($obj->{address});
1494 $fromlist->{"/^$addr\$/"} = 1;
1495 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1496 my $addr = PMG::Utils::quote_regex($obj->{address});
1497 $fromlist->{"/^.+\@$addr\$/"} = 1;
1498 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1499 my $addr = $obj->{address};
1500 $addr =~ s|/|\\/|g;
1501 $fromlist->{"/^$addr\$/"} = 1;
1502 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1503 $clientlist->{$obj->{address}} = 1;
1504 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1505 $clientlist->{$obj->{address}} = 1;
1506 }
1507 }
1508
1509 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1510 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1511 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1512 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1513 };
1514
1515 # rewrite /etc/postfix/*
1516 sub rewrite_config_postfix {
1517 my ($self, $rulecache) = @_;
1518
1519 # make sure we have required files (else postfix start fails)
1520 IO::File->new($transport_map_filename, 'a', 0644);
1521
1522 my $changes = 0;
1523
1524 if ($self->get('mail', 'tls')) {
1525 eval {
1526 PMG::Utils::gen_proxmox_tls_cert();
1527 };
1528 syslog ('info', "generating certificate failed: $@") if $@;
1529 }
1530
1531 $changes = 1 if $self->rewrite_config_file(
1532 'main.cf.in', '/etc/postfix/main.cf');
1533
1534 $changes = 1 if $self->rewrite_config_file(
1535 'master.cf.in', '/etc/postfix/master.cf');
1536
1537 # make sure we have required files (else postfix start fails)
1538 # Note: postmap need a valid /etc/postfix/main.cf configuration
1539 postmap_pmg_domains();
1540 postmap_pmg_transport();
1541 postmap_tls_policy();
1542
1543 rewrite_postfix_whitelist($rulecache) if $rulecache;
1544
1545 # make sure aliases.db is up to date
1546 system('/usr/bin/newaliases');
1547
1548 return $changes;
1549 }
1550
1551 sub rewrite_config {
1552 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1553
1554 $force_restart = {} if ! $force_restart;
1555
1556 my $log_restart = sub {
1557 syslog ('info', "configuration change detected for '$_[0]', restarting");
1558 };
1559
1560 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1561 $force_restart->{postfix}) {
1562 $log_restart->('postfix');
1563 PMG::Utils::service_cmd('postfix', 'reload');
1564 }
1565
1566 if ($self->rewrite_dot_forward() && $restart_services) {
1567 # no need to restart anything
1568 }
1569
1570 if ($self->rewrite_config_postgres() && $restart_services) {
1571 # do nothing (too many side effects)?
1572 # does not happen anyways, because config does not change.
1573 }
1574
1575 if (($self->rewrite_config_spam() && $restart_services) ||
1576 $force_restart->{spam}) {
1577 $log_restart->('pmg-smtp-filter');
1578 PMG::Utils::service_cmd('pmg-smtp-filter', 'restart');
1579 }
1580
1581 if (($self->rewrite_config_clam() && $restart_services) ||
1582 $force_restart->{clam}) {
1583 $log_restart->('clamav-daemon');
1584 PMG::Utils::service_cmd('clamav-daemon', 'restart');
1585 }
1586
1587 if (($self->rewrite_config_freshclam() && $restart_services) ||
1588 $force_restart->{freshclam}) {
1589 $log_restart->('clamav-freshclam');
1590 PMG::Utils::service_cmd('clamav-freshclam', 'restart');
1591 }
1592 }
1593
1594 1;