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