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