]> git.proxmox.com Git - pmg-api.git/blob - PMG/Config.pm
fix #2276: restore line format for pmg-log-tracker
[pmg-api.git] / 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. Mark encrypted archives 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
665 PMG::Config::Admin->register();
666 PMG::Config::Mail->register();
667 PMG::Config::SpamQuarantine->register();
668 PMG::Config::VirusQuarantine->register();
669 PMG::Config::Spam->register();
670 PMG::Config::ClamAV->register();
671
672 # initialize all plugins
673 PMG::Config::Base->init();
674
675 PVE::JSONSchema::register_format(
676 'transport-domain', \&pmg_verify_transport_domain);
677
678 sub pmg_verify_transport_domain {
679 my ($name, $noerr) = @_;
680
681 # like dns-name, but can contain leading dot
682 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
683
684 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
685 return undef if $noerr;
686 die "value does not look like a valid transport domain\n";
687 }
688 return $name;
689 }
690
691 PVE::JSONSchema::register_format(
692 'transport-domain-or-email', \&pmg_verify_transport_domain_or_email);
693
694 sub pmg_verify_transport_domain_or_email {
695 my ($name, $noerr) = @_;
696
697 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
698
699 # email address
700 if ($name =~ m/^(?:[^\s\/\@]+\@)(${namere}\.)*${namere}$/) {
701 return $name;
702 }
703
704 # like dns-name, but can contain leading dot
705 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
706 return undef if $noerr;
707 die "value does not look like a valid transport domain or email address\n";
708 }
709 return $name;
710 }
711
712 PVE::JSONSchema::register_format(
713 'dnsbl-entry', \&pmg_verify_dnsbl_entry);
714
715 sub pmg_verify_dnsbl_entry {
716 my ($name, $noerr) = @_;
717
718 # like dns-name, but can contain trailing filter and weight: 'domain=<FILTER>*<WEIGHT>'
719 # see http://www.postfix.org/postconf.5.html#postscreen_dnsbl_sites
720 # we don't implement the ';' separated numbers in pattern, because this
721 # breaks at PVE::JSONSchema::split_list
722 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
723
724 my $dnsbloctet = qr/[0-9]+|\[(?:[0-9]+\.\.[0-9]+)\]/;
725 my $filterre = qr/=$dnsbloctet(:?\.$dnsbloctet){3}/;
726 if ($name !~ /^(${namere}\.)*${namere}(:?${filterre})?(?:\*\-?\d+)?$/) {
727 return undef if $noerr;
728 die "value '$name' does not look like a valid dnsbl entry\n";
729 }
730 return $name;
731 }
732
733 sub new {
734 my ($type) = @_;
735
736 my $class = ref($type) || $type;
737
738 my $cfg = PVE::INotify::read_file("pmg.conf");
739
740 return bless $cfg, $class;
741 }
742
743 sub write {
744 my ($self) = @_;
745
746 PVE::INotify::write_file("pmg.conf", $self);
747 }
748
749 my $lockfile = "/var/lock/pmgconfig.lck";
750
751 sub lock_config {
752 my ($code, $errmsg) = @_;
753
754 my $p = PVE::Tools::lock_file($lockfile, undef, $code);
755 if (my $err = $@) {
756 $errmsg ? die "$errmsg: $err" : die $err;
757 }
758 }
759
760 # set section values
761 sub set {
762 my ($self, $section, $key, $value) = @_;
763
764 my $pdata = PMG::Config::Base->private();
765
766 my $plugin = $pdata->{plugins}->{$section};
767 die "no such section '$section'" if !$plugin;
768
769 if (defined($value)) {
770 my $tmp = PMG::Config::Base->check_value($section, $key, $value, $section, 0);
771 $self->{ids}->{$section} = { type => $section } if !defined($self->{ids}->{$section});
772 $self->{ids}->{$section}->{$key} = PMG::Config::Base->decode_value($section, $key, $tmp);
773 } else {
774 if (defined($self->{ids}->{$section})) {
775 delete $self->{ids}->{$section}->{$key};
776 }
777 }
778
779 return undef;
780 }
781
782 # get section value or default
783 sub get {
784 my ($self, $section, $key, $nodefault) = @_;
785
786 my $pdata = PMG::Config::Base->private();
787 my $pdesc = $pdata->{propertyList}->{$key};
788 die "no such property '$section/$key'\n"
789 if !(defined($pdesc) && defined($pdata->{options}->{$section}) &&
790 defined($pdata->{options}->{$section}->{$key}));
791
792 if (defined($self->{ids}->{$section}) &&
793 defined(my $value = $self->{ids}->{$section}->{$key})) {
794 return $value;
795 }
796
797 return undef if $nodefault;
798
799 return $pdesc->{default};
800 }
801
802 # get a whole section with default value
803 sub get_section {
804 my ($self, $section) = @_;
805
806 my $pdata = PMG::Config::Base->private();
807 return undef if !defined($pdata->{options}->{$section});
808
809 my $res = {};
810
811 foreach my $key (keys %{$pdata->{options}->{$section}}) {
812
813 my $pdesc = $pdata->{propertyList}->{$key};
814
815 if (defined($self->{ids}->{$section}) &&
816 defined(my $value = $self->{ids}->{$section}->{$key})) {
817 $res->{$key} = $value;
818 next;
819 }
820 $res->{$key} = $pdesc->{default};
821 }
822
823 return $res;
824 }
825
826 # get a whole config with default values
827 sub get_config {
828 my ($self) = @_;
829
830 my $pdata = PMG::Config::Base->private();
831
832 my $res = {};
833
834 foreach my $type (keys %{$pdata->{plugins}}) {
835 my $plugin = $pdata->{plugins}->{$type};
836 $res->{$type} = $self->get_section($type);
837 }
838
839 return $res;
840 }
841
842 sub read_pmg_conf {
843 my ($filename, $fh) = @_;
844
845 local $/ = undef; # slurp mode
846
847 my $raw = <$fh> if defined($fh);
848
849 return PMG::Config::Base->parse_config($filename, $raw);
850 }
851
852 sub write_pmg_conf {
853 my ($filename, $fh, $cfg) = @_;
854
855 my $raw = PMG::Config::Base->write_config($filename, $cfg);
856
857 PVE::Tools::safe_print($filename, $fh, $raw);
858 }
859
860 PVE::INotify::register_file('pmg.conf', "/etc/pmg/pmg.conf",
861 \&read_pmg_conf,
862 \&write_pmg_conf,
863 undef, always_call_parser => 1);
864
865 # parsers/writers for other files
866
867 my $domainsfilename = "/etc/pmg/domains";
868
869 sub postmap_pmg_domains {
870 PMG::Utils::run_postmap($domainsfilename);
871 }
872
873 sub read_pmg_domains {
874 my ($filename, $fh) = @_;
875
876 my $domains = {};
877
878 my $comment = '';
879 if (defined($fh)) {
880 while (defined(my $line = <$fh>)) {
881 chomp $line;
882 next if $line =~ m/^\s*$/;
883 if ($line =~ m/^#(.*)\s*$/) {
884 $comment = $1;
885 next;
886 }
887 if ($line =~ m/^(\S+)\s.*$/) {
888 my $domain = $1;
889 $domains->{$domain} = {
890 domain => $domain, comment => $comment };
891 $comment = '';
892 } else {
893 warn "parse error in '$filename': $line\n";
894 $comment = '';
895 }
896 }
897 }
898
899 return $domains;
900 }
901
902 sub write_pmg_domains {
903 my ($filename, $fh, $domains) = @_;
904
905 foreach my $domain (sort keys %$domains) {
906 my $comment = $domains->{$domain}->{comment};
907 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
908 if defined($comment) && $comment !~ m/^\s*$/;
909
910 PVE::Tools::safe_print($filename, $fh, "$domain 1\n");
911 }
912 }
913
914 PVE::INotify::register_file('domains', $domainsfilename,
915 \&read_pmg_domains,
916 \&write_pmg_domains,
917 undef, always_call_parser => 1);
918
919 my $mynetworks_filename = "/etc/pmg/mynetworks";
920
921 sub read_pmg_mynetworks {
922 my ($filename, $fh) = @_;
923
924 my $mynetworks = {};
925
926 my $comment = '';
927 if (defined($fh)) {
928 while (defined(my $line = <$fh>)) {
929 chomp $line;
930 next if $line =~ m/^\s*$/;
931 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
932 my ($network, $prefix_size, $comment) = ($1, $2, $3);
933 my $cidr = "$network/${prefix_size}";
934 $mynetworks->{$cidr} = {
935 cidr => $cidr,
936 network_address => $network,
937 prefix_size => $prefix_size,
938 comment => $comment // '',
939 };
940 } else {
941 warn "parse error in '$filename': $line\n";
942 }
943 }
944 }
945
946 return $mynetworks;
947 }
948
949 sub write_pmg_mynetworks {
950 my ($filename, $fh, $mynetworks) = @_;
951
952 foreach my $cidr (sort keys %$mynetworks) {
953 my $data = $mynetworks->{$cidr};
954 my $comment = $data->{comment} // '*';
955 PVE::Tools::safe_print($filename, $fh, "$cidr #$comment\n");
956 }
957 }
958
959 PVE::INotify::register_file('mynetworks', $mynetworks_filename,
960 \&read_pmg_mynetworks,
961 \&write_pmg_mynetworks,
962 undef, always_call_parser => 1);
963
964 PVE::JSONSchema::register_format(
965 'tls-policy', \&pmg_verify_tls_policy);
966
967 # TODO: extend to parse attributes of the policy
968 my $VALID_TLS_POLICY_RE = qr/none|may|encrypt|dane|dane-only|fingerprint|verify|secure/;
969 sub pmg_verify_tls_policy {
970 my ($policy, $noerr) = @_;
971
972 if ($policy !~ /^$VALID_TLS_POLICY_RE\b/) {
973 return undef if $noerr;
974 die "value '$policy' does not look like a valid tls policy\n";
975 }
976 return $policy;
977 }
978
979 PVE::JSONSchema::register_format(
980 'tls-policy-strict', \&pmg_verify_tls_policy_strict);
981
982 sub pmg_verify_tls_policy_strict {
983 my ($policy, $noerr) = @_;
984
985 if ($policy !~ /^$VALID_TLS_POLICY_RE$/) {
986 return undef if $noerr;
987 die "value '$policy' does not look like a valid tls policy\n";
988 }
989 return $policy;
990 }
991
992 sub read_tls_policy {
993 my ($filename, $fh) = @_;
994
995 return {} if !defined($fh);
996
997 my $tls_policy = {};
998
999 while (defined(my $line = <$fh>)) {
1000 chomp $line;
1001 next if $line =~ m/^\s*$/;
1002 next if $line =~ m/^#(.*)\s*$/;
1003
1004 my $parse_error = sub {
1005 my ($err) = @_;
1006 die "parse error in '$filename': $line - $err";
1007 };
1008
1009 if ($line =~ m/^(\S+)\s+(.+)\s*$/) {
1010 my ($domain, $policy) = ($1, $2);
1011
1012 eval {
1013 pmg_verify_transport_domain($domain);
1014 pmg_verify_tls_policy($policy);
1015 };
1016 if (my $err = $@) {
1017 $parse_error->($err);
1018 next;
1019 }
1020
1021 $tls_policy->{$domain} = {
1022 domain => $domain,
1023 policy => $policy,
1024 };
1025 } else {
1026 $parse_error->('wrong format');
1027 }
1028 }
1029
1030 return $tls_policy;
1031 }
1032
1033 sub write_tls_policy {
1034 my ($filename, $fh, $tls_policy) = @_;
1035
1036 return if !$tls_policy;
1037
1038 foreach my $domain (sort keys %$tls_policy) {
1039 my $entry = $tls_policy->{$domain};
1040 PVE::Tools::safe_print(
1041 $filename, $fh, "$entry->{domain} $entry->{policy}\n");
1042 }
1043 }
1044
1045 my $tls_policy_map_filename = "/etc/pmg/tls_policy";
1046 PVE::INotify::register_file('tls_policy', $tls_policy_map_filename,
1047 \&read_tls_policy,
1048 \&write_tls_policy,
1049 undef, always_call_parser => 1);
1050
1051 sub postmap_tls_policy {
1052 PMG::Utils::run_postmap($tls_policy_map_filename);
1053 }
1054
1055 my $transport_map_filename = "/etc/pmg/transport";
1056
1057 sub postmap_pmg_transport {
1058 PMG::Utils::run_postmap($transport_map_filename);
1059 }
1060
1061 sub read_transport_map {
1062 my ($filename, $fh) = @_;
1063
1064 return [] if !defined($fh);
1065
1066 my $res = {};
1067
1068 my $comment = '';
1069
1070 while (defined(my $line = <$fh>)) {
1071 chomp $line;
1072 next if $line =~ m/^\s*$/;
1073 if ($line =~ m/^#(.*)\s*$/) {
1074 $comment = $1;
1075 next;
1076 }
1077
1078 my $parse_error = sub {
1079 my ($err) = @_;
1080 warn "parse error in '$filename': $line - $err";
1081 $comment = '';
1082 };
1083
1084 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
1085 my ($domain, $host, $port) = ($1, $2, $3);
1086
1087 eval { pmg_verify_transport_domain_or_email($domain); };
1088 if (my $err = $@) {
1089 $parse_error->($err);
1090 next;
1091 }
1092 my $use_mx = 1;
1093 if ($host =~ m/^\[(.*)\]$/) {
1094 $host = $1;
1095 $use_mx = 0;
1096 }
1097
1098 eval { PVE::JSONSchema::pve_verify_address($host); };
1099 if (my $err = $@) {
1100 $parse_error->($err);
1101 next;
1102 }
1103
1104 my $data = {
1105 domain => $domain,
1106 host => $host,
1107 port => $port,
1108 use_mx => $use_mx,
1109 comment => $comment,
1110 };
1111 $res->{$domain} = $data;
1112 $comment = '';
1113 } else {
1114 $parse_error->('wrong format');
1115 }
1116 }
1117
1118 return $res;
1119 }
1120
1121 sub write_transport_map {
1122 my ($filename, $fh, $tmap) = @_;
1123
1124 return if !$tmap;
1125
1126 foreach my $domain (sort keys %$tmap) {
1127 my $data = $tmap->{$domain};
1128
1129 my $comment = $data->{comment};
1130 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
1131 if defined($comment) && $comment !~ m/^\s*$/;
1132
1133 my $use_mx = $data->{use_mx};
1134 $use_mx = 0 if $data->{host} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
1135
1136 if ($use_mx) {
1137 PVE::Tools::safe_print(
1138 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
1139 } else {
1140 PVE::Tools::safe_print(
1141 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
1142 }
1143 }
1144 }
1145
1146 PVE::INotify::register_file('transport', $transport_map_filename,
1147 \&read_transport_map,
1148 \&write_transport_map,
1149 undef, always_call_parser => 1);
1150
1151 # config file generation using templates
1152
1153 sub get_template_vars {
1154 my ($self) = @_;
1155
1156 my $vars = { pmg => $self->get_config() };
1157
1158 my $nodename = PVE::INotify::nodename();
1159 my $int_ip = PMG::Cluster::remote_node_ip($nodename);
1160 $vars->{ipconfig}->{int_ip} = $int_ip;
1161
1162 my $transportnets = [];
1163
1164 if (my $tmap = PVE::INotify::read_file('transport')) {
1165 foreach my $domain (sort keys %$tmap) {
1166 my $data = $tmap->{$domain};
1167 my $host = $data->{host};
1168 if ($host =~ m/^$IPV4RE$/) {
1169 push @$transportnets, "$host/32";
1170 } elsif ($host =~ m/^$IPV6RE$/) {
1171 push @$transportnets, "[$host]/128";
1172 }
1173 }
1174 }
1175
1176 $vars->{postfix}->{transportnets} = join(' ', @$transportnets);
1177
1178 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1179
1180 if (my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip, 1)) {
1181 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d+)$/) {
1182 push @$mynetworks, "[$1]/$2";
1183 } else {
1184 push @$mynetworks, $int_net_cidr;
1185 }
1186 } else {
1187 if ($int_ip =~ m/^$IPV6RE$/) {
1188 push @$mynetworks, "[$int_ip]/128";
1189 } else {
1190 push @$mynetworks, "$int_ip/32";
1191 }
1192 }
1193
1194 my $netlist = PVE::INotify::read_file('mynetworks');
1195 foreach my $cidr (sort keys %$netlist) {
1196 if ($cidr =~ m/^($IPV6RE)\/(\d+)$/) {
1197 push @$mynetworks, "[$1]/$2";
1198 } else {
1199 push @$mynetworks, $cidr;
1200 }
1201 }
1202
1203 push @$mynetworks, @$transportnets;
1204
1205 # add default relay to mynetworks
1206 if (my $relay = $self->get('mail', 'relay')) {
1207 if ($relay =~ m/^$IPV4RE$/) {
1208 push @$mynetworks, "$relay/32";
1209 } elsif ($relay =~ m/^$IPV6RE$/) {
1210 push @$mynetworks, "[$relay]/128";
1211 } else {
1212 # DNS name - do nothing ?
1213 }
1214 }
1215
1216 $vars->{postfix}->{mynetworks} = join(' ', @$mynetworks);
1217
1218 # normalize dnsbl_sites
1219 my @dnsbl_sites = PVE::Tools::split_list($vars->{pmg}->{mail}->{dnsbl_sites});
1220 if (scalar(@dnsbl_sites)) {
1221 $vars->{postfix}->{dnsbl_sites} = join(',', @dnsbl_sites);
1222 }
1223
1224 $vars->{postfix}->{dnsbl_threshold} = $self->get('mail', 'dnsbl_threshold');
1225
1226 my $usepolicy = 0;
1227 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1228 $self->get('mail', 'spf');
1229 $vars->{postfix}->{usepolicy} = $usepolicy;
1230
1231 if ($int_ip =~ m/^$IPV6RE$/) {
1232 $vars->{postfix}->{int_ip} = "[$int_ip]";
1233 } else {
1234 $vars->{postfix}->{int_ip} = $int_ip;
1235 }
1236
1237 my $resolv = PVE::INotify::read_file('resolvconf');
1238 $vars->{dns}->{hostname} = $nodename;
1239
1240 my $domain = $resolv->{search} // 'localdomain';
1241 $vars->{dns}->{domain} = $domain;
1242
1243 my $wlbr = "$nodename.$domain";
1244 foreach my $r (PVE::Tools::split_list($vars->{pmg}->{spam}->{wl_bounce_relays})) {
1245 $wlbr .= " $r"
1246 }
1247 $vars->{composed}->{wl_bounce_relays} = $wlbr;
1248
1249 if (my $proxy = $vars->{pmg}->{admin}->{http_proxy}) {
1250 eval {
1251 my $uri = URI->new($proxy);
1252 my $host = $uri->host;
1253 my $port = $uri->port // 8080;
1254 if ($host) {
1255 my $data = { host => $host, port => $port };
1256 if (my $ui = $uri->userinfo) {
1257 my ($username, $pw) = split(/:/, $ui, 2);
1258 $data->{username} = $username;
1259 $data->{password} = $pw if defined($pw);
1260 }
1261 $vars->{proxy} = $data;
1262 }
1263 };
1264 warn "parse http_proxy failed - $@" if $@;
1265 }
1266
1267 return $vars;
1268 }
1269
1270 # use one global TT cache
1271 our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1272
1273 my $template_toolkit;
1274
1275 sub get_template_toolkit {
1276
1277 return $template_toolkit if $template_toolkit;
1278
1279 $template_toolkit = Template->new({ INCLUDE_PATH => $tt_include_path });
1280
1281 return $template_toolkit;
1282 }
1283
1284 # rewrite file from template
1285 # return true if file has changed
1286 sub rewrite_config_file {
1287 my ($self, $tmplname, $dstfn) = @_;
1288
1289 my $demo = $self->get('admin', 'demo');
1290
1291 if ($demo) {
1292 my $demosrc = "$tmplname.demo";
1293 $tmplname = $demosrc if -f "/var/lib/pmg/templates/$demosrc";
1294 }
1295
1296 my ($perm, $uid, $gid);
1297
1298 if ($dstfn eq '/etc/clamav/freshclam.conf') {
1299 # needed if file contains a HTTPProxyPasswort
1300
1301 $uid = getpwnam('clamav');
1302 $gid = getgrnam('adm');
1303 $perm = 0600;
1304 }
1305
1306 my $tt = get_template_toolkit();
1307
1308 my $vars = $self->get_template_vars();
1309
1310 my $output = '';
1311
1312 $tt->process($tmplname, $vars, \$output) ||
1313 die $tt->error() . "\n";
1314
1315 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1316
1317 return 0 if defined($old) && ($old eq $output); # no change
1318
1319 PVE::Tools::file_set_contents($dstfn, $output, $perm);
1320
1321 if (defined($uid) && defined($gid)) {
1322 chown($uid, $gid, $dstfn);
1323 }
1324
1325 return 1;
1326 }
1327
1328 # rewrite spam configuration
1329 sub rewrite_config_spam {
1330 my ($self) = @_;
1331
1332 my $use_awl = $self->get('spam', 'use_awl');
1333 my $use_bayes = $self->get('spam', 'use_bayes');
1334 my $use_razor = $self->get('spam', 'use_razor');
1335
1336 my $changes = 0;
1337
1338 # delete AW and bayes databases if those features are disabled
1339 if (!$use_awl) {
1340 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1341 }
1342
1343 if (!$use_bayes) {
1344 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1345 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1346 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
1347 }
1348
1349 # make sure we have a custom.cf file (else cluster sync fails)
1350 IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1351
1352 $changes = 1 if $self->rewrite_config_file(
1353 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1354
1355 $changes = 1 if $self->rewrite_config_file(
1356 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1357
1358 $changes = 1 if $self->rewrite_config_file(
1359 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1360
1361 $changes = 1 if $self->rewrite_config_file(
1362 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
1363
1364 if ($use_razor) {
1365 mkdir "/root/.razor";
1366
1367 $changes = 1 if $self->rewrite_config_file(
1368 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1369
1370 if (! -e '/root/.razor/identity') {
1371 eval {
1372 my $timeout = 30;
1373 PVE::Tools::run_command(['razor-admin', '-discover'], timeout => $timeout);
1374 PVE::Tools::run_command(['razor-admin', '-register'], timeout => $timeout);
1375 };
1376 my $err = $@;
1377 syslog('info', "registering razor failed: $err") if $err;
1378 }
1379 }
1380
1381 return $changes;
1382 }
1383
1384 # rewrite ClamAV configuration
1385 sub rewrite_config_clam {
1386 my ($self) = @_;
1387
1388 return $self->rewrite_config_file(
1389 'clamd.conf.in', '/etc/clamav/clamd.conf');
1390 }
1391
1392 sub rewrite_config_freshclam {
1393 my ($self) = @_;
1394
1395 return $self->rewrite_config_file(
1396 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
1397 }
1398
1399 sub rewrite_config_postgres {
1400 my ($self) = @_;
1401
1402 my $pgconfdir = "/etc/postgresql/9.6/main";
1403
1404 my $changes = 0;
1405
1406 $changes = 1 if $self->rewrite_config_file(
1407 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1408
1409 $changes = 1 if $self->rewrite_config_file(
1410 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1411
1412 return $changes;
1413 }
1414
1415 # rewrite /root/.forward
1416 sub rewrite_dot_forward {
1417 my ($self) = @_;
1418
1419 my $dstfn = '/root/.forward';
1420
1421 my $email = $self->get('admin', 'email');
1422
1423 my $output = '';
1424 if ($email && $email =~ m/\s*(\S+)\s*/) {
1425 $output = "$1\n";
1426 } else {
1427 # empty .forward does not forward mails (see man local)
1428 }
1429
1430 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1431
1432 return 0 if defined($old) && ($old eq $output); # no change
1433
1434 PVE::Tools::file_set_contents($dstfn, $output);
1435
1436 return 1;
1437 }
1438
1439 my $write_smtp_whitelist = sub {
1440 my ($filename, $data, $action) = @_;
1441
1442 $action = 'OK' if !$action;
1443
1444 my $old = PVE::Tools::file_get_contents($filename, 1024*1024)
1445 if -f $filename;
1446
1447 my $new = '';
1448 foreach my $k (sort keys %$data) {
1449 $new .= "$k $action\n";
1450 }
1451
1452 return 0 if defined($old) && ($old eq $new); # no change
1453
1454 PVE::Tools::file_set_contents($filename, $new);
1455
1456 PMG::Utils::run_postmap($filename);
1457
1458 return 1;
1459 };
1460
1461 sub rewrite_postfix_whitelist {
1462 my ($rulecache) = @_;
1463
1464 # see man page for regexp_table for postfix regex table format
1465
1466 # we use a hash to avoid duplicate entries in regex tables
1467 my $tolist = {};
1468 my $fromlist = {};
1469 my $clientlist = {};
1470
1471 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1472 my $oclass = ref($obj);
1473 if ($oclass eq 'PMG::RuleDB::Receiver') {
1474 my $addr = PMG::Utils::quote_regex($obj->{address});
1475 $tolist->{"/^$addr\$/"} = 1;
1476 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1477 my $addr = PMG::Utils::quote_regex($obj->{address});
1478 $tolist->{"/^.+\@$addr\$/"} = 1;
1479 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1480 my $addr = $obj->{address};
1481 $addr =~ s|/|\\/|g;
1482 $tolist->{"/^$addr\$/"} = 1;
1483 }
1484 }
1485
1486 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1487 my $oclass = ref($obj);
1488 my $addr = PMG::Utils::quote_regex($obj->{address});
1489 if ($oclass eq 'PMG::RuleDB::EMail') {
1490 my $addr = PMG::Utils::quote_regex($obj->{address});
1491 $fromlist->{"/^$addr\$/"} = 1;
1492 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1493 my $addr = PMG::Utils::quote_regex($obj->{address});
1494 $fromlist->{"/^.+\@$addr\$/"} = 1;
1495 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1496 my $addr = $obj->{address};
1497 $addr =~ s|/|\\/|g;
1498 $fromlist->{"/^$addr\$/"} = 1;
1499 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1500 $clientlist->{$obj->{address}} = 1;
1501 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1502 $clientlist->{$obj->{address}} = 1;
1503 }
1504 }
1505
1506 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1507 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1508 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1509 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1510 };
1511
1512 # rewrite /etc/postfix/*
1513 sub rewrite_config_postfix {
1514 my ($self, $rulecache) = @_;
1515
1516 # make sure we have required files (else postfix start fails)
1517 IO::File->new($transport_map_filename, 'a', 0644);
1518
1519 my $changes = 0;
1520
1521 if ($self->get('mail', 'tls')) {
1522 eval {
1523 PMG::Utils::gen_proxmox_tls_cert();
1524 };
1525 syslog ('info', "generating certificate failed: $@") if $@;
1526 }
1527
1528 $changes = 1 if $self->rewrite_config_file(
1529 'main.cf.in', '/etc/postfix/main.cf');
1530
1531 $changes = 1 if $self->rewrite_config_file(
1532 'master.cf.in', '/etc/postfix/master.cf');
1533
1534 # make sure we have required files (else postfix start fails)
1535 # Note: postmap need a valid /etc/postfix/main.cf configuration
1536 postmap_pmg_domains();
1537 postmap_pmg_transport();
1538 postmap_tls_policy();
1539
1540 rewrite_postfix_whitelist($rulecache) if $rulecache;
1541
1542 # make sure aliases.db is up to date
1543 system('/usr/bin/newaliases');
1544
1545 return $changes;
1546 }
1547
1548 sub rewrite_config {
1549 my ($self, $rulecache, $restart_services, $force_restart) = @_;
1550
1551 $force_restart = {} if ! $force_restart;
1552
1553 my $log_restart = sub {
1554 syslog ('info', "configuration change detected for '$_[0]', restarting");
1555 };
1556
1557 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
1558 $force_restart->{postfix}) {
1559 $log_restart->('postfix');
1560 PMG::Utils::service_cmd('postfix', 'reload');
1561 }
1562
1563 if ($self->rewrite_dot_forward() && $restart_services) {
1564 # no need to restart anything
1565 }
1566
1567 if ($self->rewrite_config_postgres() && $restart_services) {
1568 # do nothing (too many side effects)?
1569 # does not happen anyways, because config does not change.
1570 }
1571
1572 if (($self->rewrite_config_spam() && $restart_services) ||
1573 $force_restart->{spam}) {
1574 $log_restart->('pmg-smtp-filter');
1575 PMG::Utils::service_cmd('pmg-smtp-filter', 'restart');
1576 }
1577
1578 if (($self->rewrite_config_clam() && $restart_services) ||
1579 $force_restart->{clam}) {
1580 $log_restart->('clamav-daemon');
1581 PMG::Utils::service_cmd('clamav-daemon', 'restart');
1582 }
1583
1584 if (($self->rewrite_config_freshclam() && $restart_services) ||
1585 $force_restart->{freshclam}) {
1586 $log_restart->('clamav-freshclam');
1587 PMG::Utils::service_cmd('clamav-freshclam', 'restart');
1588 }
1589 }
1590
1591 1;