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