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