]> git.proxmox.com Git - pmg-api.git/blame - PMG/Config.pm
PMG/API2/Services.pm: remove cron from default service list
[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 },
9860e592
DM
323 safebrowsing => {
324 description => "Enables support for Google Safe Browsing.",
325 type => 'boolean',
326 default => 1
327 },
f62194b2
DM
328 };
329}
330
331sub options {
332 return {
ac5d1312
DM
333 archiveblockencrypted => { optional => 1 },
334 archivemaxrec => { optional => 1 },
f62194b2 335 archivemaxfiles => { optional => 1 },
ac5d1312
DM
336 archivemaxsize => { optional => 1 },
337 maxscansize => { optional => 1 },
338 dbmirror => { optional => 1 },
339 maxcccount => { optional => 1 },
9860e592 340 safebrowsing => { optional => 1 },
7e0e6dbe
DM
341 };
342}
343
d9dc3c08
DM
344package PMG::Config::Mail;
345
346use strict;
347use warnings;
348
f62194b2
DM
349use PVE::ProcFSTools;
350
d9dc3c08
DM
351use base qw(PMG::Config::Base);
352
353sub type {
354 return 'mail';
355}
356
f62194b2
DM
357my $physicalmem = 0;
358sub physical_memory {
359
360 return $physicalmem if $physicalmem;
361
362 my $info = PVE::ProcFSTools::read_meminfo();
363 my $total = int($info->{memtotal} / (1024*1024));
364
365 return $total;
366}
367
368sub get_max_filters {
369 # estimate optimal number of filter servers
370
371 my $max_servers = 5;
372 my $servermem = 120;
373 my $memory = physical_memory();
374 my $add_servers = int(($memory - 512)/$servermem);
375 $max_servers += $add_servers if $add_servers > 0;
376 $max_servers = 40 if $max_servers > 40;
377
378 return $max_servers - 2;
379}
380
f609bf7f
DM
381sub get_max_smtpd {
382 # estimate optimal number of smtpd daemons
383
384 my $max_servers = 25;
385 my $servermem = 20;
386 my $memory = physical_memory();
387 my $add_servers = int(($memory - 512)/$servermem);
388 $max_servers += $add_servers if $add_servers > 0;
389 $max_servers = 100 if $max_servers > 100;
390 return $max_servers;
391}
392
03907162
DM
393sub get_max_policy {
394 # estimate optimal number of proxpolicy servers
395 my $max_servers = 2;
396 my $memory = physical_memory();
397 $max_servers = 5 if $memory >= 500;
398 return $max_servers;
399}
f609bf7f 400
d9dc3c08
DM
401sub properties {
402 return {
75a20f14
DM
403 int_port => {
404 description => "SMTP port number for outgoing mail (trusted).",
405 type => 'integer',
406 minimum => 1,
407 maximum => 65535,
408 default => 25,
409 },
410 ext_port => {
7b19cc5c 411 description => "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.",
75a20f14
DM
412 type => 'integer',
413 minimum => 1,
414 maximum => 65535,
415 default => 26,
416 },
f609bf7f
DM
417 relay => {
418 description => "The default mail delivery transport (incoming mails).",
66af5153 419 type => 'string', format => 'address',
f609bf7f
DM
420 },
421 relayport => {
422 description => "SMTP port number for relay host.",
423 type => 'integer',
424 minimum => 1,
425 maximum => 65535,
426 default => 25,
427 },
428 relaynomx => {
429 description => "Disable MX lookups for default relay.",
430 type => 'boolean',
431 default => 0,
432 },
433 smarthost => {
434 description => "When set, all outgoing mails are deliverd to the specified smarthost.",
3bb296d4 435 type => 'string', format => 'address',
f609bf7f 436 },
d9dc3c08
DM
437 banner => {
438 description => "ESMTP banner.",
439 type => 'string',
440 maxLength => 1024,
441 default => 'ESMTP Proxmox',
442 },
f62194b2 443 max_filters => {
03907162 444 description => "Maximum number of pmg-smtp-filter processes.",
f62194b2
DM
445 type => 'integer',
446 minimum => 3,
447 maximum => 40,
448 default => get_max_filters(),
449 },
03907162
DM
450 max_policy => {
451 description => "Maximum number of pmgpolicy processes.",
452 type => 'integer',
453 minimum => 2,
454 maximum => 10,
455 default => get_max_policy(),
456 },
f609bf7f
DM
457 max_smtpd_in => {
458 description => "Maximum number of SMTP daemon processes (in).",
459 type => 'integer',
460 minimum => 3,
461 maximum => 100,
462 default => get_max_smtpd(),
463 },
464 max_smtpd_out => {
465 description => "Maximum number of SMTP daemon processes (out).",
466 type => 'integer',
467 minimum => 3,
468 maximum => 100,
469 default => get_max_smtpd(),
470 },
471 conn_count_limit => {
472 description => "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.",
473 type => 'integer',
474 minimum => 0,
475 default => 50,
476 },
477 conn_rate_limit => {
478 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.",
479 type => 'integer',
480 minimum => 0,
481 default => 0,
482 },
483 message_rate_limit => {
484 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.",
485 type => 'integer',
486 minimum => 0,
487 default => 0,
488 },
f62194b2
DM
489 hide_received => {
490 description => "Hide received header in outgoing mails.",
491 type => 'boolean',
ac5d1312
DM
492 default => 0,
493 },
f609bf7f 494 maxsize => {
ac5d1312
DM
495 description => "Maximum email size. Larger mails are rejected.",
496 type => 'integer',
497 minimum => 1024,
498 default => 1024*1024*10,
f62194b2 499 },
f609bf7f
DM
500 dwarning => {
501 description => "SMTP delay warning time (in hours).",
502 type => 'integer',
503 minimum => 0,
504 default => 4,
505 },
506 use_rbl => {
4d76e24e 507 description => "Use Realtime Blacklists.",
f609bf7f
DM
508 type => 'boolean',
509 default => 1,
510 },
511 tls => {
589be6da
DM
512 description => "Enable TLS.",
513 type => 'boolean',
514 default => 0,
515 },
516 tlslog => {
517 description => "Enable TLS Logging.",
518 type => 'boolean',
519 default => 0,
520 },
521 tlsheader => {
522 description => "Add TLS received header.",
f609bf7f
DM
523 type => 'boolean',
524 default => 0,
525 },
526 spf => {
4d76e24e 527 description => "Use Sender Policy Framework.",
f609bf7f
DM
528 type => 'boolean',
529 default => 1,
530 },
531 greylist => {
4d76e24e 532 description => "Use Greylisting.",
f609bf7f
DM
533 type => 'boolean',
534 default => 1,
535 },
536 helotests => {
4d76e24e 537 description => "Use SMTP HELO tests.",
f609bf7f
DM
538 type => 'boolean',
539 default => 0,
540 },
541 rejectunknown => {
4d76e24e 542 description => "Reject unknown clients.",
f609bf7f
DM
543 type => 'boolean',
544 default => 0,
545 },
546 rejectunknownsender => {
4d76e24e 547 description => "Reject unknown senders.",
f609bf7f
DM
548 type => 'boolean',
549 default => 0,
550 },
551 verifyreceivers => {
3791e936 552 description => "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.",
90822f27
DM
553 type => 'string',
554 enum => ['450', '550'],
f609bf7f
DM
555 },
556 dnsbl_sites => {
557 description => "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).",
558 type => 'string',
559 },
d9dc3c08
DM
560 };
561}
562
563sub options {
564 return {
75a20f14
DM
565 int_port => { optional => 1 },
566 ext_port => { optional => 1 },
3d9837d9 567 smarthost => { optional => 1 },
f609bf7f
DM
568 relay => { optional => 1 },
569 relayport => { optional => 1 },
570 relaynomx => { optional => 1 },
571 dwarning => { optional => 1 },
572 max_smtpd_in => { optional => 1 },
573 max_smtpd_out => { optional => 1 },
574 greylist => { optional => 1 },
575 helotests => { optional => 1 },
576 use_rbl => { optional => 1 },
577 tls => { optional => 1 },
589be6da
DM
578 tlslog => { optional => 1 },
579 tlsheader => { optional => 1 },
f609bf7f
DM
580 spf => { optional => 1 },
581 maxsize => { optional => 1 },
d9dc3c08 582 banner => { optional => 1 },
f62194b2 583 max_filters => { optional => 1 },
03907162 584 max_policy => { optional => 1 },
f62194b2 585 hide_received => { optional => 1 },
f609bf7f
DM
586 rejectunknown => { optional => 1 },
587 rejectunknownsender => { optional => 1 },
588 conn_count_limit => { optional => 1 },
589 conn_rate_limit => { optional => 1 },
590 message_rate_limit => { optional => 1 },
591 verifyreceivers => { optional => 1 },
592 dnsbl_sites => { optional => 1 },
d9dc3c08
DM
593 };
594}
7e0e6dbe
DM
595package PMG::Config;
596
597use strict;
598use warnings;
9123cab5 599use IO::File;
7e0e6dbe 600use Data::Dumper;
4ccdc564 601use Template;
7e0e6dbe 602
9123cab5 603use PVE::SafeSyslog;
ba323310 604use PVE::Tools qw($IPV4RE $IPV6RE);
7e0e6dbe 605use PVE::INotify;
b86ac4eb 606use PVE::JSONSchema;
7e0e6dbe 607
ac5d1312 608PMG::Config::Admin->register();
d9dc3c08 609PMG::Config::Mail->register();
fc070a06
DM
610PMG::Config::SpamQuarantine->register();
611PMG::Config::VirusQuarantine->register();
7e0e6dbe 612PMG::Config::Spam->register();
f62194b2 613PMG::Config::ClamAV->register();
7e0e6dbe
DM
614
615# initialize all plugins
616PMG::Config::Base->init();
617
b86ac4eb
DM
618PVE::JSONSchema::register_format(
619 'transport-domain', \&pmg_verify_transport_domain);
620sub pmg_verify_transport_domain {
621 my ($name, $noerr) = @_;
622
623 # like dns-name, but can contain leading dot
624 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
625
626 if ($name !~ /^\.?(${namere}\.)*${namere}$/) {
627 return undef if $noerr;
628 die "value does not look like a valid transport domain\n";
629 }
630 return $name;
631}
f62194b2
DM
632
633sub new {
634 my ($type) = @_;
635
636 my $class = ref($type) || $type;
637
638 my $cfg = PVE::INotify::read_file("pmg.conf");
639
640 return bless $cfg, $class;
641}
642
be6e2db9
DM
643sub write {
644 my ($self) = @_;
645
646 PVE::INotify::write_file("pmg.conf", $self);
647}
648
f21d933c
DM
649my $lockfile = "/var/lock/pmgconfig.lck";
650
651sub lock_config {
652 my ($code, $errmsg) = @_;
653
654 my $p = PVE::Tools::lock_file($lockfile, undef, $code);
655 if (my $err = $@) {
656 $errmsg ? die "$errmsg: $err" : die $err;
657 }
658}
659
062f0498 660# set section values
062f0498
DM
661sub set {
662 my ($self, $section, $key, $value) = @_;
663
664 my $pdata = PMG::Config::Base->private();
665
062f0498
DM
666 my $plugin = $pdata->{plugins}->{$section};
667 die "no such section '$section'" if !$plugin;
668
062f0498
DM
669 if (defined($value)) {
670 my $tmp = PMG::Config::Base->check_value($section, $key, $value, $section, 0);
d79b9b0c
DM
671 $self->{ids}->{$section} = { type => $section } if !defined($self->{ids}->{$section});
672 $self->{ids}->{$section}->{$key} = PMG::Config::Base->decode_value($section, $key, $tmp);
062f0498 673 } else {
d79b9b0c
DM
674 if (defined($self->{ids}->{$section})) {
675 delete $self->{ids}->{$section}->{$key};
062f0498
DM
676 }
677 }
678
679 return undef;
680}
681
f62194b2 682# get section value or default
f62194b2
DM
683sub get {
684 my ($self, $section, $key) = @_;
685
686 my $pdata = PMG::Config::Base->private();
f62194b2 687 my $pdesc = $pdata->{propertyList}->{$key};
3d9837d9
DM
688 die "no such property '$section/$key'\n"
689 if !(defined($pdesc) && defined($pdata->{options}->{$section}) &&
690 defined($pdata->{options}->{$section}->{$key}));
f62194b2 691
d79b9b0c
DM
692 if (defined($self->{ids}->{$section}) &&
693 defined(my $value = $self->{ids}->{$section}->{$key})) {
f62194b2 694 return $value;
1ccc8e95 695 }
f62194b2
DM
696
697 return $pdesc->{default};
698}
699
1ccc8e95 700# get a whole section with default value
1ccc8e95
DM
701sub get_section {
702 my ($self, $section) = @_;
703
704 my $pdata = PMG::Config::Base->private();
705 return undef if !defined($pdata->{options}->{$section});
706
707 my $res = {};
708
709 foreach my $key (keys %{$pdata->{options}->{$section}}) {
710
711 my $pdesc = $pdata->{propertyList}->{$key};
712
d79b9b0c
DM
713 if (defined($self->{ids}->{$section}) &&
714 defined(my $value = $self->{ids}->{$section}->{$key})) {
1ccc8e95
DM
715 $res->{$key} = $value;
716 next;
717 }
718 $res->{$key} = $pdesc->{default};
719 }
720
721 return $res;
722}
723
be16be07 724# get a whole config with default values
be16be07
DM
725sub get_config {
726 my ($self) = @_;
727
9dab5fe5
DM
728 my $pdata = PMG::Config::Base->private();
729
be16be07
DM
730 my $res = {};
731
9dab5fe5 732 foreach my $type (keys %{$pdata->{plugins}}) {
9dab5fe5
DM
733 my $plugin = $pdata->{plugins}->{$type};
734 $res->{$type} = $self->get_section($type);
be16be07
DM
735 }
736
737 return $res;
738}
739
7e0e6dbe
DM
740sub read_pmg_conf {
741 my ($filename, $fh) = @_;
f62194b2 742
7e0e6dbe 743 local $/ = undef; # slurp mode
f62194b2 744
9dfe7c16 745 my $raw = <$fh> if defined($fh);
7e0e6dbe
DM
746
747 return PMG::Config::Base->parse_config($filename, $raw);
748}
749
750sub write_pmg_conf {
751 my ($filename, $fh, $cfg) = @_;
752
753 my $raw = PMG::Config::Base->write_config($filename, $cfg);
754
755 PVE::Tools::safe_print($filename, $fh, $raw);
756}
757
3278b571 758PVE::INotify::register_file('pmg.conf', "/etc/pmg/pmg.conf",
f62194b2 759 \&read_pmg_conf,
9dfe7c16
DM
760 \&write_pmg_conf,
761 undef, always_call_parser => 1);
7e0e6dbe 762
f609bf7f
DM
763# parsers/writers for other files
764
3278b571 765my $domainsfilename = "/etc/pmg/domains";
f609bf7f 766
c3f4336c
DM
767sub postmap_pmg_domains {
768 PMG::Utils::run_postmap($domainsfilename);
769}
770
f609bf7f
DM
771sub read_pmg_domains {
772 my ($filename, $fh) = @_;
773
b7298186 774 my $domains = {};
f609bf7f 775
b7298186 776 my $comment = '';
f609bf7f
DM
777 if (defined($fh)) {
778 while (defined(my $line = <$fh>)) {
3118b703
DM
779 chomp $line;
780 next if $line =~ m/^\s*$/;
b7298186
DM
781 if ($line =~ m/^#(.*)\s*$/) {
782 $comment = $1;
783 next;
784 }
785 if ($line =~ m/^(\S+)\s.*$/) {
f609bf7f 786 my $domain = $1;
b7298186
DM
787 $domains->{$domain} = {
788 domain => $domain, comment => $comment };
789 $comment = '';
3118b703
DM
790 } else {
791 warn "parse error in '$filename': $line\n";
792 $comment = '';
f609bf7f
DM
793 }
794 }
795 }
796
797 return $domains;
798}
799
800sub write_pmg_domains {
b7298186
DM
801 my ($filename, $fh, $domains) = @_;
802
803 foreach my $domain (sort keys %$domains) {
804 my $comment = $domains->{$domain}->{comment};
805 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
806 if defined($comment) && $comment !~ m/^\s*$/;
f609bf7f 807
6b31da64 808 PVE::Tools::safe_print($filename, $fh, "$domain 1\n");
f609bf7f
DM
809 }
810}
811
812PVE::INotify::register_file('domains', $domainsfilename,
813 \&read_pmg_domains,
814 \&write_pmg_domains,
815 undef, always_call_parser => 1);
816
bef31f06
DM
817my $mynetworks_filename = "/etc/pmg/mynetworks";
818
819sub postmap_pmg_mynetworks {
820 PMG::Utils::run_postmap($mynetworks_filename);
821}
822
823sub read_pmg_mynetworks {
824 my ($filename, $fh) = @_;
825
826 my $mynetworks = {};
827
828 my $comment = '';
829 if (defined($fh)) {
830 while (defined(my $line = <$fh>)) {
831 chomp $line;
832 next if $line =~ m/^\s*$/;
833 if ($line =~ m!^((?:$IPV4RE|$IPV6RE))/(\d+)\s*(?:#(.*)\s*)?$!) {
834 my ($network, $prefix_size, $comment) = ($1, $2, $3);
835 my $cidr = "$network/${prefix_size}";
836 $mynetworks->{$cidr} = {
837 cidr => $cidr,
838 network_address => $network,
839 prefix_size => $prefix_size,
840 comment => $comment // '',
841 };
842 } else {
843 warn "parse error in '$filename': $line\n";
844 }
845 }
846 }
847
848 return $mynetworks;
849}
850
851sub write_pmg_mynetworks {
852 my ($filename, $fh, $mynetworks) = @_;
853
854 foreach my $cidr (sort keys %$mynetworks) {
855 my $data = $mynetworks->{$cidr};
856 my $comment = $data->{comment} // '*';
857 PVE::Tools::safe_print($filename, $fh, "$cidr #$comment\n");
858 }
859}
860
861PVE::INotify::register_file('mynetworks', $mynetworks_filename,
862 \&read_pmg_mynetworks,
863 \&write_pmg_mynetworks,
864 undef, always_call_parser => 1);
865
cd533938 866my $transport_map_filename = "/etc/pmg/transport";
3546daf0 867
3118b703
DM
868sub postmap_pmg_transport {
869 PMG::Utils::run_postmap($transport_map_filename);
870}
871
3546daf0
DM
872sub read_transport_map {
873 my ($filename, $fh) = @_;
874
875 return [] if !defined($fh);
876
877 my $res = {};
878
3118b703 879 my $comment = '';
b7c49fec 880
3546daf0
DM
881 while (defined(my $line = <$fh>)) {
882 chomp $line;
883 next if $line =~ m/^\s*$/;
3118b703
DM
884 if ($line =~ m/^#(.*)\s*$/) {
885 $comment = $1;
886 next;
887 }
3546daf0 888
b7c49fec
DM
889 my $parse_error = sub {
890 my ($err) = @_;
891 warn "parse error in '$filename': $line - $err";
892 $comment = '';
893 };
894
ba323310 895 if ($line =~ m/^(\S+)\s+smtp:(\S+):(\d+)\s*$/) {
3118b703 896 my ($domain, $host, $port) = ($1, $2, $3);
3546daf0 897
b7c49fec
DM
898 eval { pmg_verify_transport_domain($domain); };
899 if (my $err = $@) {
900 $parse_error->($err);
901 next;
902 }
53904163 903 my $use_mx = 1;
3546daf0
DM
904 if ($host =~ m/^\[(.*)\]$/) {
905 $host = $1;
53904163 906 $use_mx = 0;
3546daf0
DM
907 }
908
b7c49fec
DM
909 eval { PVE::JSONSchema::pve_verify_address($host); };
910 if (my $err = $@) {
911 $parse_error->($err);
912 next;
913 }
914
3118b703
DM
915 my $data = {
916 domain => $domain,
917 host => $host,
918 port => $port,
53904163 919 use_mx => $use_mx,
3118b703
DM
920 comment => $comment,
921 };
922 $res->{$domain} = $data;
923 $comment = '';
924 } else {
b7c49fec 925 $parse_error->('wrong format');
3546daf0
DM
926 }
927 }
928
3118b703 929 return $res;
3546daf0
DM
930}
931
cd533938 932sub write_transport_map {
3546daf0
DM
933 my ($filename, $fh, $tmap) = @_;
934
935 return if !$tmap;
936
3118b703
DM
937 foreach my $domain (sort keys %$tmap) {
938 my $data = $tmap->{$domain};
3546daf0 939
3118b703
DM
940 my $comment = $data->{comment};
941 PVE::Tools::safe_print($filename, $fh, "#$comment\n")
942 if defined($comment) && $comment !~ m/^\s*$/;
943
ba323310
DM
944 my $use_mx = $data->{use_mx};
945 $use_mx = 0 if $data->{host} =~ m/^(?:$IPV4RE|$IPV6RE)$/;
946
947 if ($use_mx) {
3118b703 948 PVE::Tools::safe_print(
53904163 949 $filename, $fh, "$data->{domain} smtp:$data->{host}:$data->{port}\n");
3118b703
DM
950 } else {
951 PVE::Tools::safe_print(
53904163 952 $filename, $fh, "$data->{domain} smtp:[$data->{host}]:$data->{port}\n");
3546daf0
DM
953 }
954 }
955}
956
957PVE::INotify::register_file('transport', $transport_map_filename,
958 \&read_transport_map,
cd533938 959 \&write_transport_map,
3546daf0 960 undef, always_call_parser => 1);
7e0e6dbe 961
4ccdc564
DM
962# config file generation using templates
963
07b3face
DM
964sub get_template_vars {
965 my ($self) = @_;
4ccdc564
DM
966
967 my $vars = { pmg => $self->get_config() };
968
f609bf7f
DM
969 my $nodename = PVE::INotify::nodename();
970 my $int_ip = PMG::Cluster::remote_node_ip($nodename);
971 my $int_net_cidr = PMG::Utils::find_local_network_for_ip($int_ip);
f609bf7f
DM
972 $vars->{ipconfig}->{int_ip} = $int_ip;
973 # $vars->{ipconfig}->{int_net_cidr} = $int_net_cidr;
f609bf7f 974
ba323310
DM
975 my $transportnets = [];
976
977 my $tmap = PVE::INotify::read_file('transport');
978 foreach my $domain (sort keys %$tmap) {
979 my $data = $tmap->{$domain};
980 my $host = $data->{host};
981 if ($host =~ m/^$IPV4RE$/) {
982 push @$transportnets, "$host/32";
983 } elsif ($host =~ m/^$IPV6RE$/) {
984 push @$transportnets, "[$host]/128";
985 }
986 }
987
f609bf7f
DM
988 $vars->{postfix}->{transportnets} = join(' ', @$transportnets);
989
990 my $mynetworks = [ '127.0.0.0/8', '[::1]/128' ];
991 push @$mynetworks, @$transportnets;
992 push @$mynetworks, $int_net_cidr;
bef31f06 993 push @$mynetworks, 'hash:/etc/pmg/mynetworks';
f609bf7f 994
bef31f06 995 my $netlist = PVE::INotify::read_file('mynetworks');
f609bf7f
DM
996 # add default relay to mynetworks
997 if (my $relay = $self->get('mail', 'relay')) {
ba323310 998 if ($relay =~ m/^$IPV4RE$/) {
f609bf7f 999 push @$mynetworks, "$relay/32";
ba323310 1000 } elsif ($relay =~ m/^$IPV6RE$/) {
f609bf7f
DM
1001 push @$mynetworks, "[$relay]/128";
1002 } else {
66af5153 1003 # DNS name - do nothing ?
f609bf7f
DM
1004 }
1005 }
1006
1007 $vars->{postfix}->{mynetworks} = join(' ', @$mynetworks);
1008
1009 my $usepolicy = 0;
1010 $usepolicy = 1 if $self->get('mail', 'greylist') ||
1011 $self->get('mail', 'spf') || $self->get('mail', 'use_rbl');
1012 $vars->{postfix}->{usepolicy} = $usepolicy;
1013
1014 my $resolv = PVE::INotify::read_file('resolvconf');
1015 $vars->{dns}->{hostname} = $nodename;
1016 $vars->{dns}->{domain} = $resolv->{search};
1017
07b3face
DM
1018 return $vars;
1019}
1020
1021# rewrite file from template
1022# return true if file has changed
1023sub rewrite_config_file {
1024 my ($self, $tmplname, $dstfn) = @_;
1025
1026 my $demo = $self->get('admin', 'demo');
1027
1028 my $srcfn = ($tmplname =~ m|^.?/|) ?
1029 $tmplname : "/var/lib/pmg/templates/$tmplname";
1030
1031 if ($demo) {
1032 my $demosrc = "$srcfn.demo";
1033 $srcfn = $demosrc if -f $demosrc;
1034 }
1035
c248d69f 1036 my ($perm, $uid, $gid);
07b3face
DM
1037
1038 my $srcfd = IO::File->new ($srcfn, "r")
1039 || die "cant read template '$srcfn' - $!: ERROR";
1040
1041 if ($dstfn eq '/etc/fetchmailrc') {
1042 (undef, undef, $uid, $gid) = getpwnam('fetchmail');
1043 $perm = 0600;
1044 } elsif ($dstfn eq '/etc/clamav/freshclam.conf') {
1045 # needed if file contains a HTTPProxyPasswort
1046
1047 $uid = getpwnam('clamav');
1048 $gid = getgrnam('adm');
1049 $perm = 0600;
1050 }
1051
1052 my $template = Template->new({});
1053
1054 my $vars = $self->get_template_vars();
1055
c248d69f 1056 my $output = '';
07b3face
DM
1057
1058 $template->process($srcfd, $vars, \$output) ||
4ccdc564
DM
1059 die $template->error();
1060
1061 $srcfd->close();
07b3face
DM
1062
1063 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1064
1065 return 0 if defined($old) && ($old eq $output); # no change
1066
1067 PVE::Tools::file_set_contents($dstfn, $output, $perm);
1068
1069 if (defined($uid) && defined($gid)) {
1070 chown($uid, $gid, $dstfn);
1071 }
1072
1073 return 1;
4ccdc564
DM
1074}
1075
9123cab5
DM
1076# rewrite spam configuration
1077sub rewrite_config_spam {
1078 my ($self) = @_;
1079
1080 my $use_awl = $self->get('spam', 'use_awl');
1081 my $use_bayes = $self->get('spam', 'use_bayes');
1082 my $use_razor = $self->get('spam', 'use_razor');
1083
17424665
DM
1084 my $changes = 0;
1085
9123cab5 1086 # delete AW and bayes databases if those features are disabled
17424665
DM
1087 if (!$use_awl) {
1088 $changes = 1 if unlink '/root/.spamassassin/auto-whitelist';
1089 }
1090
9123cab5 1091 if (!$use_bayes) {
17424665
DM
1092 $changes = 1 if unlink '/root/.spamassassin/bayes_journal';
1093 $changes = 1 if unlink '/root/.spamassassin/bayes_seen';
1094 $changes = 1 if unlink '/root/.spamassassin/bayes_toks';
9123cab5
DM
1095 }
1096
1097 # make sure we have a custom.cf file (else cluster sync fails)
1098 IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
1099
17424665
DM
1100 $changes = 1 if $self->rewrite_config_file(
1101 'local.cf.in', '/etc/mail/spamassassin/local.cf');
1102
1103 $changes = 1 if $self->rewrite_config_file(
1104 'init.pre.in', '/etc/mail/spamassassin/init.pre');
1105
1106 $changes = 1 if $self->rewrite_config_file(
1107 'v310.pre.in', '/etc/mail/spamassassin/v310.pre');
1108
1109 $changes = 1 if $self->rewrite_config_file(
1110 'v320.pre.in', '/etc/mail/spamassassin/v320.pre');
9123cab5
DM
1111
1112 if ($use_razor) {
1113 mkdir "/root/.razor";
17424665
DM
1114
1115 $changes = 1 if $self->rewrite_config_file(
1116 'razor-agent.conf.in', '/root/.razor/razor-agent.conf');
1117
9123cab5
DM
1118 if (! -e '/root/.razor/identity') {
1119 eval {
1120 my $timeout = 30;
17424665
DM
1121 PVE::Tools::run_command(['razor-admin', '-discover'], timeout => $timeout);
1122 PVE::Tools::run_command(['razor-admin', '-register'], timeout => $timeout);
9123cab5
DM
1123 };
1124 my $err = $@;
1125 syslog('info', msgquote ("registering razor failed: $err")) if $err;
1126 }
1127 }
17424665
DM
1128
1129 return $changes;
9123cab5
DM
1130}
1131
ac5d1312
DM
1132# rewrite ClamAV configuration
1133sub rewrite_config_clam {
1134 my ($self) = @_;
1135
17424665
DM
1136 return $self->rewrite_config_file(
1137 'clamd.conf.in', '/etc/clamav/clamd.conf');
1138}
1139
1140sub rewrite_config_freshclam {
1141 my ($self) = @_;
1142
1143 return $self->rewrite_config_file(
1144 'freshclam.conf.in', '/etc/clamav/freshclam.conf');
ac5d1312
DM
1145}
1146
86737f12
DM
1147sub rewrite_config_postgres {
1148 my ($self) = @_;
1149
1150 my $pgconfdir = "/etc/postgresql/9.6/main";
1151
17424665
DM
1152 my $changes = 0;
1153
1154 $changes = 1 if $self->rewrite_config_file(
1155 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf");
1156
1157 $changes = 1 if $self->rewrite_config_file(
1158 'postgresql.conf.in', "$pgconfdir/postgresql.conf");
1159
1160 return $changes;
86737f12
DM
1161}
1162
1163# rewrite /root/.forward
1164sub rewrite_dot_forward {
1165 my ($self) = @_;
1166
c248d69f 1167 my $dstfn = '/root/.forward';
86737f12 1168
0bb9a01a 1169 my $email = $self->get('admin', 'email');
c248d69f 1170
e14fda7a 1171 my $output = '';
86737f12 1172 if ($email && $email =~ m/\s*(\S+)\s*/) {
c248d69f 1173 $output = "$1\n";
86737f12
DM
1174 } else {
1175 # empty .forward does not forward mails (see man local)
1176 }
17424665 1177
c248d69f
DM
1178 my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn;
1179
1180 return 0 if defined($old) && ($old eq $output); # no change
1181
1182 PVE::Tools::file_set_contents($dstfn, $output);
1183
1184 return 1;
86737f12
DM
1185}
1186
f609bf7f
DM
1187# rewrite /etc/postfix/*
1188sub rewrite_config_postfix {
1189 my ($self) = @_;
1190
3546daf0 1191 # make sure we have required files (else postfix start fails)
b7298186 1192 postmap_pmg_domains();
ba323310 1193 postmap_pmg_transport();
bef31f06 1194 postmap_pmg_mynetworks();
b7298186 1195
3546daf0 1196 IO::File->new($transport_map_filename, 'a', 0644);
f609bf7f 1197
17424665
DM
1198 my $changes = 0;
1199
f609bf7f
DM
1200 if ($self->get('mail', 'tls')) {
1201 eval {
bc44eb02 1202 PMG::Utils::gen_proxmox_tls_cert();
f609bf7f
DM
1203 };
1204 syslog ('info', msgquote ("generating certificate failed: $@")) if $@;
1205 }
1206
17424665
DM
1207 $changes = 1 if $self->rewrite_config_file(
1208 'main.cf.in', '/etc/postfix/main.cf');
1209
1210 $changes = 1 if $self->rewrite_config_file(
1211 'master.cf.in', '/etc/postfix/master.cf');
1212
f609bf7f
DM
1213 #rewrite_config_transports ($class);
1214 #rewrite_config_whitelist ($class);
1215 #rewrite_config_tls_policy ($class);
1216
1217 # make sure aliases.db is up to date
1218 system('/usr/bin/newaliases');
17424665
DM
1219
1220 return $changes;
f609bf7f
DM
1221}
1222
f983300f 1223sub rewrite_config {
c248d69f
DM
1224 my ($self, $restart_services) = @_;
1225
1226 if ($self->rewrite_config_postfix() && $restart_services) {
1227 PMG::Utils::service_cmd('postfix', 'restart');
1228 }
1229
1230 if ($self->rewrite_dot_forward() && $restart_services) {
1231 # no need to restart anything
1232 }
1233
1234 if ($self->rewrite_config_postgres() && $restart_services) {
1235 # do nothing (too many side effects)?
1236 # does not happen anyways, because config does not change.
1237 }
f983300f 1238
c248d69f
DM
1239 if ($self->rewrite_config_spam() && $restart_services) {
1240 PMG::Utils::service_cmd('pmg-smtp-filter', 'restart');
1241 }
1242
1243 if ($self->rewrite_config_clam() && $restart_services) {
8f87fe74 1244 PMG::Utils::service_cmd('clamav-daemon', 'restart');
c248d69f
DM
1245 }
1246
1247 if ($self->rewrite_config_freshclam() && $restart_services) {
8f87fe74 1248 PMG::Utils::service_cmd('clamav-freshclam', 'restart');
c248d69f 1249 }
17424665 1250
f983300f
DM
1251}
1252
7e0e6dbe 12531;