]> git.proxmox.com Git - pmg-api.git/blame - PMG/Config.pm
PMG/Config.pm: fix ipv6 address quoting for postfix mynetworks
[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);
f609bf7f 995 $vars->{ipconfig}->{int_ip} = $int_ip;
f609bf7f 996
ba323310
DM
997 my $transportnets = [];
998
5e37e665
DM
999 if (my $tmap = PVE::INotify::read_file('transport')) {
1000 foreach my $domain (sort keys %$tmap) {
1001 my $data = $tmap->{$domain};
1002 my $host = $data->{host};
1003 if ($host =~ m/^$IPV4RE$/) {
1004 push @$transportnets, "$host/32";
1005 } elsif ($host =~ m/^$IPV6RE$/) {
1006 push @$transportnets, "[$host]/128";
1007 }
ba323310
DM
1008 }
1009 }
1010
f609bf7f
DM
1011 $vars->{postfix}->{transportnets} = join(' ', @$transportnets);
1012
1013 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
1e88a529
DM
1014
1015 if (my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip, 1)) {
136e6c92
DM
1016 if ($int_net_cidr =~ m/^($IPV6RE)\/(\d+)$/) {
1017 push @$mynetworks, "[$1]/$2";
1018 } else {
1019 push @$mynetworks, $int_net_cidr;
1020 }
1e88a529
DM
1021 } else {
1022 if ($int_ip =~ m/^$IPV6RE$/) {
136e6c92 1023 push @$mynetworks, "[$int_ip]/128";
1e88a529
DM
1024 } else {
1025 push @$mynetworks, "$int_ip/32";
1026 }
1027 }
f609bf7f 1028
bef31f06 1029 my $netlist = PVE::INotify::read_file('mynetworks');
136e6c92
DM
1030 foreach my $cidr (keys %$netlist) {
1031 if ($cidr =~ m/^($IPV6RE)\/(\d+)$/) {
1032 push @$mynetworks, "[$1]/$2";
1033 } else {
1034 push @$mynetworks, $cidr;
1035 }
1036 }
6d473888
DM
1037
1038 push @$mynetworks, @$transportnets;
1039
f609bf7f
DM
1040 # add default relay to mynetworks
1041 if (my $relay = $self->get('mail', 'relay')) {
ba323310 1042 if ($relay =~ m/^$IPV4RE$/) {
f609bf7f 1043 push @$mynetworks, "$relay/32";
ba323310 1044 } elsif ($relay =~ m/^$IPV6RE$/) {
f609bf7f
DM
1045 push @$mynetworks, "[$relay]/128";
1046 } else {
66af5153 1047 # DNS name - do nothing ?
f609bf7f
DM
1048 }
1049 }
1050
1051 $vars->{postfix}->{mynetworks} = join(' ', @$mynetworks);
1052
20125a71
DM
1053 # normalize dnsbl_sites
1054 my @dnsbl_sites = PVE::Tools::split_list($vars->{pmg}->{mail}->{dnsbl_sites});
1055 if (scalar(@dnsbl_sites)) {
1056 $vars->{postfix}->{dnsbl_sites} = join(',', @dnsbl_sites);
1057 }
1058
f609bf7f
DM
1059 my $usepolicy = 0;
1060 $usepolicy = 1 if $self->get('mail', 'greylist') ||
0eae95d3 1061 $self->get('mail', 'spf');
f609bf7f
DM
1062 $vars->{postfix}->{usepolicy} = $usepolicy;
1063
1064 my $resolv = PVE::INotify::read_file('resolvconf');
1065 $vars->{dns}->{hostname} = $nodename;
f609bf7f 1066
a2c4c2c0
DM
1067 my $domain = $resolv->{search} // 'localdomain';
1068 $vars->{dns}->{domain} = $domain;
1069
1070 my $wlbr = "$nodename.$domain";
ed5fa523
DM
1071 foreach my $r (PVE::Tools::split_list($vars->{pmg}->{spam}->{wl_bounce_relays})) {
1072 $wlbr .= " $r"
1073 }
1074 $vars->{composed}->{wl_bounce_relays} = $wlbr;
1075
11081cf6
DM
1076 if (my $proxy = $vars->{pmg}->{admin}->{http_proxy}) {
1077 eval {
1078 my $uri = URI->new($proxy);
1079 my $host = $uri->host;
1080 my $port = $uri->port // 8080;
1081 if ($host) {
1082 my $data = { host => $host, port => $port };
1083 if (my $ui = $uri->userinfo) {
1084 my ($username, $pw) = split(/:/, $ui, 2);
1085 $data->{username} = $username;
1086 $data->{password} = $pw if defined($pw);
1087 }
1088 $vars->{proxy} = $data;
1089 }
1090 };
1091 warn "parse http_proxy failed - $@" if $@;
1092 }
1093
07b3face
DM
1094 return $vars;
1095}
1096
310daf18
DM
1097# use one global TT cache
1098our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ];
1099
1100my $template_toolkit;
1101
1102sub get_template_toolkit {
1103
1104 return $template_toolkit if $template_toolkit;
1105
1106 $template_toolkit = Template->new({ INCLUDE_PATH => $tt_include_path });
1107
1108 return $template_toolkit;
1109}
1110
07b3face
DM
1111# rewrite file from template
1112# return true if file has changed
1113sub rewrite_config_file {
1114 my ($self, $tmplname, $dstfn) = @_;
1115
1116 my $demo = $self->get('admin', 'demo');
1117
07b3face 1118 if ($demo) {
310daf18
DM
1119 my $demosrc = "$tmplname.demo";
1120 $tmplname = $demosrc if -f "/var/lib/pmg/templates/$demosrc";
07b3face
DM
1121 }
1122
c248d69f 1123 my ($perm, $uid, $gid);
07b3face 1124
d1156caa 1125 if ($dstfn eq '/etc/clamav/freshclam.conf') {
07b3face
DM
1126 # needed if file contains a HTTPProxyPasswort
1127
1128 $uid = getpwnam('clamav');
1129 $gid = getgrnam('adm');
1130 $perm = 0600;
1131 }
1132
310daf18 1133 my $tt = get_template_toolkit();
07b3face
DM
1134
1135 my $vars = $self->get_template_vars();
1136
c248d69f 1137 my $output = '';
07b3face 1138
310daf18 1139 $tt->process($tmplname, $vars, \$output) ||
60f82a46 1140 die $tt->error() . "\n";
07b3face
DM
1141
1142 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1143
1144 return 0 if defined($old) && ($old eq $output); # no change
1145
1146 PVE::Tools::file_set_contents($dstfn, $output, $perm);
1147
1148 if (defined($uid) && defined($gid)) {
1149 chown($uid, $gid, $dstfn);
1150 }
1151
1152 return 1;
4ccdc564
DM
1153}
1154
9123cab5
DM
1155# rewrite spam configuration
1156sub rewrite_config_spam {
1157 my ($self) = @_;
1158
1159 my $use_awl = $self->get('spam', 'use_awl');
1160 my $use_bayes = $self->get('spam', 'use_bayes');
1161 my $use_razor = $self->get('spam', 'use_razor');
1162
17424665
DM
1163 my $changes = 0;
1164
9123cab5 1165 # delete AW and bayes databases if those features are disabled
17424665
DM
1166 if (!$use_awl) {
1167 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1168 }
1169
9123cab5 1170 if (!$use_bayes) {
17424665
DM
1171 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1172 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1173 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
9123cab5
DM
1174 }
1175
1176 # make sure we have a custom.cf file (else cluster sync fails)
1177 IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1178
17424665
DM
1179 $changes = 1 if $self->rewrite_config_file(
1180 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1181
1182 $changes = 1 if $self->rewrite_config_file(
1183 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1184
1185 $changes = 1 if $self->rewrite_config_file(
1186 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1187
1188 $changes = 1 if $self->rewrite_config_file(
1189 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
9123cab5
DM
1190
1191 if ($use_razor) {
1192 mkdir "/root/.razor";
17424665
DM
1193
1194 $changes = 1 if $self->rewrite_config_file(
1195 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1196
9123cab5
DM
1197 if (! -e '/root/.razor/identity') {
1198 eval {
1199 my $timeout = 30;
17424665
DM
1200 PVE::Tools::run_command(['razor-admin', '-discover'], timeout => $timeout);
1201 PVE::Tools::run_command(['razor-admin', '-register'], timeout => $timeout);
9123cab5
DM
1202 };
1203 my $err = $@;
b902c0b8 1204 syslog('info', "registering razor failed: $err") if $err;
9123cab5
DM
1205 }
1206 }
17424665
DM
1207
1208 return $changes;
9123cab5
DM
1209}
1210
ac5d1312
DM
1211# rewrite ClamAV configuration
1212sub rewrite_config_clam {
1213 my ($self) = @_;
1214
17424665
DM
1215 return $self->rewrite_config_file(
1216 'clamd.conf.in', '/etc/clamav/clamd.conf');
1217}
1218
1219sub rewrite_config_freshclam {
1220 my ($self) = @_;
1221
1222 return $self->rewrite_config_file(
1223 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
ac5d1312
DM
1224}
1225
86737f12
DM
1226sub rewrite_config_postgres {
1227 my ($self) = @_;
1228
1229 my $pgconfdir = "/etc/postgresql/9.6/main";
1230
17424665
DM
1231 my $changes = 0;
1232
1233 $changes = 1 if $self->rewrite_config_file(
1234 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1235
1236 $changes = 1 if $self->rewrite_config_file(
1237 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1238
1239 return $changes;
86737f12
DM
1240}
1241
1242# rewrite /root/.forward
1243sub rewrite_dot_forward {
1244 my ($self) = @_;
1245
c248d69f 1246 my $dstfn = '/root/.forward';
86737f12 1247
0bb9a01a 1248 my $email = $self->get('admin', 'email');
c248d69f 1249
e14fda7a 1250 my $output = '';
86737f12 1251 if ($email && $email =~ m/\s*(\S+)\s*/) {
c248d69f 1252 $output = "$1\n";
86737f12
DM
1253 } else {
1254 # empty .forward does not forward mails (see man local)
1255 }
17424665 1256
c248d69f
DM
1257 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1258
1259 return 0 if defined($old) && ($old eq $output); # no change
1260
1261 PVE::Tools::file_set_contents($dstfn, $output);
1262
1263 return 1;
86737f12
DM
1264}
1265
d15630a9
DM
1266my $write_smtp_whitelist = sub {
1267 my ($filename, $data, $action) = @_;
1268
1269 $action = 'OK' if !$action;
1270
1271 my $old = PVE::Tools::file_get_contents($filename, 1024*1024)
1272 if -f $filename;
1273
1274 my $new = '';
1275 foreach my $k (sort keys %$data) {
1276 $new .= "$k $action\n";
1277 }
1278
1279 return 0 if defined($old) && ($old eq $new); # no change
1280
1281 PVE::Tools::file_set_contents($filename, $new);
1282
1283 PMG::Utils::run_postmap($filename);
1284
1285 return 1;
1286};
1287
f9967a49 1288sub rewrite_postfix_whitelist {
d15630a9
DM
1289 my ($rulecache) = @_;
1290
1291 # see man page for regexp_table for postfix regex table format
1292
1293 # we use a hash to avoid duplicate entries in regex tables
1294 my $tolist = {};
1295 my $fromlist = {};
1296 my $clientlist = {};
1297
1298 foreach my $obj (@{$rulecache->{"greylist:receiver"}}) {
1299 my $oclass = ref($obj);
1300 if ($oclass eq 'PMG::RuleDB::Receiver') {
1301 my $addr = PMG::Utils::quote_regex($obj->{address});
1302 $tolist->{"/^$addr\$/"} = 1;
1303 } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') {
1304 my $addr = PMG::Utils::quote_regex($obj->{address});
1305 $tolist->{"/^.+\@$addr\$/"} = 1;
1306 } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') {
1307 my $addr = $obj->{address};
1308 $addr =~ s|/|\\/|g;
1309 $tolist->{"/^$addr\$/"} = 1;
1310 }
1311 }
1312
1313 foreach my $obj (@{$rulecache->{"greylist:sender"}}) {
1314 my $oclass = ref($obj);
1315 my $addr = PMG::Utils::quote_regex($obj->{address});
1316 if ($oclass eq 'PMG::RuleDB::EMail') {
1317 my $addr = PMG::Utils::quote_regex($obj->{address});
1318 $fromlist->{"/^$addr\$/"} = 1;
1319 } elsif ($oclass eq 'PMG::RuleDB::Domain') {
1320 my $addr = PMG::Utils::quote_regex($obj->{address});
1321 $fromlist->{"/^.+\@$addr\$/"} = 1;
1322 } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') {
1323 my $addr = $obj->{address};
1324 $addr =~ s|/|\\/|g;
1325 $fromlist->{"/^$addr\$/"} = 1;
1326 } elsif ($oclass eq 'PMG::RuleDB::IPAddress') {
1327 $clientlist->{$obj->{address}} = 1;
1328 } elsif ($oclass eq 'PMG::RuleDB::IPNet') {
1329 $clientlist->{$obj->{address}} = 1;
1330 }
1331 }
1332
1333 $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist);
1334 $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist);
1335 $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist);
1336 $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit');
1337};
1338
f609bf7f
DM
1339# rewrite /etc/postfix/*
1340sub rewrite_config_postfix {
d15630a9 1341 my ($self, $rulecache) = @_;
f609bf7f 1342
3546daf0 1343 # make sure we have required files (else postfix start fails)
3546daf0 1344 IO::File->new($transport_map_filename, 'a', 0644);
f609bf7f 1345
17424665
DM
1346 my $changes = 0;
1347
f609bf7f
DM
1348 if ($self->get('mail', 'tls')) {
1349 eval {
bc44eb02 1350 PMG::Utils::gen_proxmox_tls_cert();
f609bf7f 1351 };
b902c0b8 1352 syslog ('info', "generating certificate failed: $@") if $@;
f609bf7f
DM
1353 }
1354
17424665
DM
1355 $changes = 1 if $self->rewrite_config_file(
1356 'main.cf.in', '/etc/postfix/main.cf');
1357
1358 $changes = 1 if $self->rewrite_config_file(
1359 'master.cf.in', '/etc/postfix/master.cf');
1360
a0d4ce8d
DM
1361 # make sure we have required files (else postfix start fails)
1362 # Note: postmap need a valid /etc/postfix/main.cf configuration
1363 postmap_pmg_domains();
1364 postmap_pmg_transport();
1365 postmap_tls_policy();
1366
f9967a49 1367 rewrite_postfix_whitelist($rulecache) if $rulecache;
d15630a9 1368
f609bf7f
DM
1369 # make sure aliases.db is up to date
1370 system('/usr/bin/newaliases');
17424665
DM
1371
1372 return $changes;
f609bf7f
DM
1373}
1374
f983300f 1375sub rewrite_config {
d15630a9 1376 my ($self, $rulecache, $restart_services, $force_restart) = @_;
c248d69f 1377
798df412
DM
1378 $force_restart = {} if ! $force_restart;
1379
d15630a9 1380 if (($self->rewrite_config_postfix($rulecache) && $restart_services) ||
798df412 1381 $force_restart->{postfix}) {
c248d69f
DM
1382 PMG::Utils::service_cmd('postfix', 'restart');
1383 }
1384
1385 if ($self->rewrite_dot_forward() && $restart_services) {
1386 # no need to restart anything
1387 }
1388
1389 if ($self->rewrite_config_postgres() && $restart_services) {
1390 # do nothing (too many side effects)?
1391 # does not happen anyways, because config does not change.
1392 }
f983300f 1393
798df412
DM
1394 if (($self->rewrite_config_spam() && $restart_services) ||
1395 $force_restart->{spam}) {
c248d69f
DM
1396 PMG::Utils::service_cmd('pmg-smtp-filter', 'restart');
1397 }
1398
798df412
DM
1399 if (($self->rewrite_config_clam() && $restart_services) ||
1400 $force_restart->{clam}) {
8f87fe74 1401 PMG::Utils::service_cmd('clamav-daemon', 'restart');
c248d69f
DM
1402 }
1403
798df412
DM
1404 if (($self->rewrite_config_freshclam() && $restart_services) ||
1405 $force_restart->{freshclam}) {
8f87fe74 1406 PMG::Utils::service_cmd('clamav-freshclam', 'restart');
c248d69f 1407 }
f983300f
DM
1408}
1409
7e0e6dbe 14101;