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