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