]>
Commit | Line | Data |
---|---|---|
7e0e6dbe DM |
1 | package PMG::Config::Base; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
11081cf6 | 5 | use URI; |
7e0e6dbe DM |
6 | use Data::Dumper; |
7 | ||
8 | use PVE::Tools; | |
9 | use PVE::JSONSchema qw(get_standard_option); | |
10 | use PVE::SectionConfig; | |
11 | ||
12 | use base qw(PVE::SectionConfig); | |
13 | ||
14 | my $defaultData = { | |
15 | propertyList => { | |
16 | type => { description => "Section type." }, | |
ef6f5dd1 | 17 | section => { |
7e0e6dbe DM |
18 | description => "Secion ID.", |
19 | type => 'string', format => 'pve-configid', | |
20 | }, | |
21 | }, | |
22 | }; | |
23 | ||
24 | sub private { | |
25 | return $defaultData; | |
26 | } | |
27 | ||
28 | sub format_section_header { | |
29 | my ($class, $type, $sectionId) = @_; | |
30 | ||
d79b9b0c DM |
31 | die "internal error ($type ne $sectionId)" if $type ne $sectionId; |
32 | ||
33 | return "section: $type\n"; | |
7e0e6dbe DM |
34 | } |
35 | ||
36 | ||
37 | sub parse_section_header { | |
38 | my ($class, $line) = @_; | |
39 | ||
d79b9b0c DM |
40 | if ($line =~ m/^section:\s*(\S+)\s*$/) { |
41 | my $section = $1; | |
7e0e6dbe | 42 | my $errmsg = undef; # set if you want to skip whole section |
d79b9b0c | 43 | eval { PVE::JSONSchema::pve_verify_configid($section); }; |
7e0e6dbe DM |
44 | $errmsg = $@ if $@; |
45 | my $config = {}; # to return additional attributes | |
d79b9b0c | 46 | return ($section, $section, $errmsg, $config); |
7e0e6dbe DM |
47 | } |
48 | return undef; | |
49 | } | |
50 | ||
ac5d1312 | 51 | package PMG::Config::Admin; |
7e0e6dbe DM |
52 | |
53 | use strict; | |
54 | use warnings; | |
55 | ||
56 | use base qw(PMG::Config::Base); | |
57 | ||
58 | sub type { | |
ac5d1312 | 59 | return 'admin'; |
7e0e6dbe DM |
60 | } |
61 | ||
62 | sub properties { | |
63 | return { | |
44017d49 DM |
64 | advfilter => { |
65 | description => "Use advanced filters for statistic.", | |
66 | type => 'boolean', | |
67 | default => 1, | |
68 | }, | |
7e0e6dbe DM |
69 | dailyreport => { |
70 | description => "Send daily reports.", | |
71 | type => 'boolean', | |
72 | default => 1, | |
73 | }, | |
5a6956b7 DM |
74 | statlifetime => { |
75 | description => "User Statistics Lifetime (days)", | |
76 | type => 'integer', | |
77 | default => 7, | |
78 | minimum => 1, | |
79 | }, | |
f62194b2 DM |
80 | demo => { |
81 | description => "Demo mode - do not start SMTP filter.", | |
82 | type => 'boolean', | |
83 | default => 0, | |
84 | }, | |
85 | email => { | |
86 | description => "Administrator E-Mail address.", | |
87 | type => 'string', format => 'email', | |
88 | default => 'admin@domain.tld', | |
ac5d1312 | 89 | }, |
11081cf6 DM |
90 | http_proxy => { |
91 | description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')", | |
ac5d1312 | 92 | type => 'string', |
11081cf6 | 93 | pattern => "http://.*", |
ac5d1312 | 94 | }, |
7e0e6dbe DM |
95 | }; |
96 | } | |
97 | ||
98 | sub options { | |
99 | return { | |
44017d49 | 100 | advfilter => { optional => 1 }, |
5a6956b7 | 101 | statlifetime => { optional => 1 }, |
7e0e6dbe | 102 | dailyreport => { optional => 1 }, |
f62194b2 | 103 | demo => { optional => 1 }, |
3d812daf | 104 | email => { optional => 1 }, |
11081cf6 | 105 | http_proxy => { optional => 1 }, |
7e0e6dbe DM |
106 | }; |
107 | } | |
108 | ||
109 | package PMG::Config::Spam; | |
110 | ||
111 | use strict; | |
112 | use warnings; | |
113 | ||
114 | use base qw(PMG::Config::Base); | |
115 | ||
116 | sub type { | |
117 | return 'spam'; | |
118 | } | |
119 | ||
120 | sub properties { | |
121 | return { | |
1ccc8e95 DM |
122 | languages => { |
123 | description => "This option is used to specify which languages are considered OK for incoming mail.", | |
124 | type => 'string', | |
125 | pattern => '(all|([a-z][a-z])+( ([a-z][a-z])+)*)', | |
126 | default => 'all', | |
127 | }, | |
128 | use_bayes => { | |
129 | description => "Whether to use the naive-Bayesian-style classifier.", | |
130 | type => 'boolean', | |
131 | default => 1, | |
132 | }, | |
582cfacf DM |
133 | use_awl => { |
134 | description => "Use the Auto-Whitelist plugin.", | |
135 | type => 'boolean', | |
136 | default => 1, | |
137 | }, | |
138 | use_razor => { | |
139 | description => "Whether to use Razor2, if it is available.", | |
140 | type => 'boolean', | |
141 | default => 1, | |
142 | }, | |
1ccc8e95 DM |
143 | wl_bounce_relays => { |
144 | description => "Whitelist legitimate bounce relays.", | |
145 | type => 'string', | |
146 | }, | |
7e0e6dbe DM |
147 | bounce_score => { |
148 | description => "Additional score for bounce mails.", | |
149 | type => 'integer', | |
150 | minimum => 0, | |
151 | maximum => 1000, | |
152 | default => 0, | |
153 | }, | |
f62194b2 DM |
154 | rbl_checks => { |
155 | description => "Enable real time blacklists (RBL) checks.", | |
156 | type => 'boolean', | |
157 | default => 1, | |
158 | }, | |
159 | maxspamsize => { | |
160 | description => "Maximum size of spam messages in bytes.", | |
161 | type => 'integer', | |
4d76e24e | 162 | minimum => 64, |
f62194b2 DM |
163 | default => 200*1024, |
164 | }, | |
7e0e6dbe DM |
165 | }; |
166 | } | |
167 | ||
168 | sub options { | |
169 | return { | |
582cfacf DM |
170 | use_awl => { optional => 1 }, |
171 | use_razor => { optional => 1 }, | |
1ccc8e95 DM |
172 | wl_bounce_relays => { optional => 1 }, |
173 | languages => { optional => 1 }, | |
174 | use_bayes => { optional => 1 }, | |
7e0e6dbe | 175 | bounce_score => { optional => 1 }, |
f62194b2 DM |
176 | rbl_checks => { optional => 1 }, |
177 | maxspamsize => { optional => 1 }, | |
178 | }; | |
179 | } | |
180 | ||
fc070a06 DM |
181 | package PMG::Config::SpamQuarantine; |
182 | ||
183 | use strict; | |
184 | use warnings; | |
185 | ||
186 | use base qw(PMG::Config::Base); | |
187 | ||
188 | sub type { | |
189 | return 'spamquar'; | |
190 | } | |
191 | ||
192 | sub properties { | |
193 | return { | |
194 | lifetime => { | |
195 | description => "Quarantine life time (days)", | |
196 | type => 'integer', | |
197 | minimum => 1, | |
198 | default => 7, | |
199 | }, | |
200 | authmode => { | |
201 | 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.", | |
202 | type => 'string', | |
203 | enum => [qw(ticket ldap ldapticket)], | |
204 | default => 'ticket', | |
205 | }, | |
206 | reportstyle => { | |
207 | description => "Spam report style.", | |
208 | type => 'string', | |
77f73dc3 | 209 | enum => [qw(none short verbose custom)], |
fc070a06 DM |
210 | default => 'verbose', |
211 | }, | |
212 | viewimages => { | |
213 | description => "Allow to view images.", | |
214 | type => 'boolean', | |
215 | default => 1, | |
216 | }, | |
217 | allowhrefs => { | |
218 | description => "Allow to view hyperlinks.", | |
219 | type => 'boolean', | |
220 | default => 1, | |
1ac4c2e6 DM |
221 | }, |
222 | hostname => { | |
223 | description => "Quarantine Host. Usefule if you run a Cluster and want users to connect to a specific host.", | |
224 | type => 'string', format => 'address', | |
225 | }, | |
226 | mailfrom => { | |
227 | description => "Text for 'From' header in daily spam report mails.", | |
228 | type => 'string', | |
229 | }, | |
fc070a06 DM |
230 | }; |
231 | } | |
232 | ||
233 | sub options { | |
234 | return { | |
1ac4c2e6 DM |
235 | mailfrom => { optional => 1 }, |
236 | hostname => { optional => 1 }, | |
fc070a06 DM |
237 | lifetime => { optional => 1 }, |
238 | authmode => { optional => 1 }, | |
239 | reportstyle => { optional => 1 }, | |
240 | viewimages => { optional => 1 }, | |
241 | allowhrefs => { optional => 1 }, | |
242 | }; | |
243 | } | |
244 | ||
245 | package PMG::Config::VirusQuarantine; | |
246 | ||
247 | use strict; | |
248 | use warnings; | |
249 | ||
250 | use base qw(PMG::Config::Base); | |
251 | ||
252 | sub type { | |
253 | return 'virusquar'; | |
254 | } | |
255 | ||
256 | sub properties { | |
257 | return {}; | |
258 | } | |
259 | ||
260 | sub options { | |
261 | return { | |
262 | lifetime => { optional => 1 }, | |
263 | viewimages => { optional => 1 }, | |
264 | allowhrefs => { optional => 1 }, | |
265 | }; | |
266 | } | |
267 | ||
f62194b2 DM |
268 | package PMG::Config::ClamAV; |
269 | ||
270 | use strict; | |
271 | use warnings; | |
272 | ||
273 | use base qw(PMG::Config::Base); | |
274 | ||
275 | sub type { | |
276 | return 'clamav'; | |
277 | } | |
278 | ||
279 | sub properties { | |
280 | return { | |
ac5d1312 DM |
281 | dbmirror => { |
282 | description => "ClamAV database mirror server.", | |
283 | type => 'string', | |
284 | default => 'database.clamav.net', | |
285 | }, | |
286 | archiveblockencrypted => { | |
287 | description => "Wether to block encrypted archives. Mark encrypted archives as viruses.", | |
288 | type => 'boolean', | |
289 | default => 0, | |
290 | }, | |
291 | archivemaxrec => { | |
292 | 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 | 293 | type => 'integer', |
ac5d1312 DM |
294 | minimum => 1, |
295 | default => 5, | |
296 | }, | |
f62194b2 | 297 | archivemaxfiles => { |
ac5d1312 | 298 | 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 |
299 | type => 'integer', |
300 | minimum => 0, | |
301 | default => 1000, | |
302 | }, | |
ac5d1312 DM |
303 | archivemaxsize => { |
304 | description => "Files larger than this limit won't be scanned.", | |
305 | type => 'integer', | |
306 | minimum => 1000000, | |
307 | default => 25000000, | |
308 | }, | |
309 | maxscansize => { | |
310 | description => "Sets the maximum amount of data to be scanned for each input file.", | |
311 | type => 'integer', | |
312 | minimum => 1000000, | |
313 | default => 100000000, | |
314 | }, | |
315 | maxcccount => { | |
316 | description => "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.", | |
317 | type => 'integer', | |
318 | minimum => 0, | |
319 | default => 0, | |
320 | }, | |
9860e592 DM |
321 | safebrowsing => { |
322 | description => "Enables support for Google Safe Browsing.", | |
323 | type => 'boolean', | |
324 | default => 1 | |
325 | }, | |
f62194b2 DM |
326 | }; |
327 | } | |
328 | ||
329 | sub options { | |
330 | return { | |
ac5d1312 DM |
331 | archiveblockencrypted => { optional => 1 }, |
332 | archivemaxrec => { optional => 1 }, | |
f62194b2 | 333 | archivemaxfiles => { optional => 1 }, |
ac5d1312 DM |
334 | archivemaxsize => { optional => 1 }, |
335 | maxscansize => { optional => 1 }, | |
336 | dbmirror => { optional => 1 }, | |
337 | maxcccount => { optional => 1 }, | |
9860e592 | 338 | safebrowsing => { optional => 1 }, |
7e0e6dbe DM |
339 | }; |
340 | } | |
341 | ||
d9dc3c08 DM |
342 | package PMG::Config::Mail; |
343 | ||
344 | use strict; | |
345 | use warnings; | |
346 | ||
f62194b2 DM |
347 | use PVE::ProcFSTools; |
348 | ||
d9dc3c08 DM |
349 | use base qw(PMG::Config::Base); |
350 | ||
351 | sub type { | |
352 | return 'mail'; | |
353 | } | |
354 | ||
f62194b2 DM |
355 | my $physicalmem = 0; |
356 | sub physical_memory { | |
357 | ||
358 | return $physicalmem if $physicalmem; | |
359 | ||
360 | my $info = PVE::ProcFSTools::read_meminfo(); | |
361 | my $total = int($info->{memtotal} / (1024*1024)); | |
362 | ||
363 | return $total; | |
364 | } | |
365 | ||
366 | sub get_max_filters { | |
367 | # estimate optimal number of filter servers | |
368 | ||
369 | my $max_servers = 5; | |
370 | my $servermem = 120; | |
371 | my $memory = physical_memory(); | |
372 | my $add_servers = int(($memory - 512)/$servermem); | |
373 | $max_servers += $add_servers if $add_servers > 0; | |
374 | $max_servers = 40 if $max_servers > 40; | |
375 | ||
376 | return $max_servers - 2; | |
377 | } | |
378 | ||
f609bf7f DM |
379 | sub get_max_smtpd { |
380 | # estimate optimal number of smtpd daemons | |
381 | ||
382 | my $max_servers = 25; | |
383 | my $servermem = 20; | |
384 | my $memory = physical_memory(); | |
385 | my $add_servers = int(($memory - 512)/$servermem); | |
386 | $max_servers += $add_servers if $add_servers > 0; | |
387 | $max_servers = 100 if $max_servers > 100; | |
388 | return $max_servers; | |
389 | } | |
390 | ||
03907162 DM |
391 | sub get_max_policy { |
392 | # estimate optimal number of proxpolicy servers | |
393 | my $max_servers = 2; | |
394 | my $memory = physical_memory(); | |
395 | $max_servers = 5 if $memory >= 500; | |
396 | return $max_servers; | |
397 | } | |
f609bf7f | 398 | |
d9dc3c08 DM |
399 | sub properties { |
400 | return { | |
75a20f14 DM |
401 | int_port => { |
402 | description => "SMTP port number for outgoing mail (trusted).", | |
403 | type => 'integer', | |
404 | minimum => 1, | |
405 | maximum => 65535, | |
406 | default => 25, | |
407 | }, | |
408 | ext_port => { | |
7b19cc5c | 409 | description => "SMTP port number for incoming mail (untrusted). This must be a different number than 'int_port'.", |
75a20f14 DM |
410 | type => 'integer', |
411 | minimum => 1, | |
412 | maximum => 65535, | |
413 | default => 26, | |
414 | }, | |
f609bf7f DM |
415 | relay => { |
416 | description => "The default mail delivery transport (incoming mails).", | |
66af5153 | 417 | type => 'string', format => 'address', |
f609bf7f DM |
418 | }, |
419 | relayport => { | |
420 | description => "SMTP port number for relay host.", | |
421 | type => 'integer', | |
422 | minimum => 1, | |
423 | maximum => 65535, | |
424 | default => 25, | |
425 | }, | |
426 | relaynomx => { | |
427 | description => "Disable MX lookups for default relay.", | |
428 | type => 'boolean', | |
429 | default => 0, | |
430 | }, | |
431 | smarthost => { | |
432 | description => "When set, all outgoing mails are deliverd to the specified smarthost.", | |
3bb296d4 | 433 | type => 'string', format => 'address', |
f609bf7f | 434 | }, |
d9dc3c08 DM |
435 | banner => { |
436 | description => "ESMTP banner.", | |
437 | type => 'string', | |
438 | maxLength => 1024, | |
439 | default => 'ESMTP Proxmox', | |
440 | }, | |
f62194b2 | 441 | max_filters => { |
03907162 | 442 | description => "Maximum number of pmg-smtp-filter processes.", |
f62194b2 DM |
443 | type => 'integer', |
444 | minimum => 3, | |
445 | maximum => 40, | |
446 | default => get_max_filters(), | |
447 | }, | |
03907162 DM |
448 | max_policy => { |
449 | description => "Maximum number of pmgpolicy processes.", | |
450 | type => 'integer', | |
451 | minimum => 2, | |
452 | maximum => 10, | |
453 | default => get_max_policy(), | |
454 | }, | |
f609bf7f DM |
455 | max_smtpd_in => { |
456 | description => "Maximum number of SMTP daemon processes (in).", | |
457 | type => 'integer', | |
458 | minimum => 3, | |
459 | maximum => 100, | |
460 | default => get_max_smtpd(), | |
461 | }, | |
462 | max_smtpd_out => { | |
463 | description => "Maximum number of SMTP daemon processes (out).", | |
464 | type => 'integer', | |
465 | minimum => 3, | |
466 | maximum => 100, | |
467 | default => get_max_smtpd(), | |
468 | }, | |
469 | conn_count_limit => { | |
470 | description => "How many simultaneous connections any client is allowed to make to this service. To disable this feature, specify a limit of 0.", | |
471 | type => 'integer', | |
472 | minimum => 0, | |
473 | default => 50, | |
474 | }, | |
475 | conn_rate_limit => { | |
476 | 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.", | |
477 | type => 'integer', | |
478 | minimum => 0, | |
479 | default => 0, | |
480 | }, | |
481 | message_rate_limit => { | |
482 | 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.", | |
483 | type => 'integer', | |
484 | minimum => 0, | |
485 | default => 0, | |
486 | }, | |
f62194b2 DM |
487 | hide_received => { |
488 | description => "Hide received header in outgoing mails.", | |
489 | type => 'boolean', | |
ac5d1312 DM |
490 | default => 0, |
491 | }, | |
f609bf7f | 492 | maxsize => { |
ac5d1312 DM |
493 | description => "Maximum email size. Larger mails are rejected.", |
494 | type => 'integer', | |
495 | minimum => 1024, | |
496 | default => 1024*1024*10, | |
f62194b2 | 497 | }, |
f609bf7f DM |
498 | dwarning => { |
499 | description => "SMTP delay warning time (in hours).", | |
500 | type => 'integer', | |
501 | minimum => 0, | |
502 | default => 4, | |
503 | }, | |
504 | use_rbl => { | |
4d76e24e | 505 | description => "Use Realtime Blacklists.", |
f609bf7f DM |
506 | type => 'boolean', |
507 | default => 1, | |
508 | }, | |
509 | tls => { | |
589be6da DM |
510 | description => "Enable TLS.", |
511 | type => 'boolean', | |
512 | default => 0, | |
513 | }, | |
514 | tlslog => { | |
515 | description => "Enable TLS Logging.", | |
516 | type => 'boolean', | |
517 | default => 0, | |
518 | }, | |
519 | tlsheader => { | |
520 | description => "Add TLS received header.", | |
f609bf7f DM |
521 | type => 'boolean', |
522 | default => 0, | |
523 | }, | |
524 | spf => { | |
4d76e24e | 525 | description => "Use Sender Policy Framework.", |
f609bf7f DM |
526 | type => 'boolean', |
527 | default => 1, | |
528 | }, | |
529 | greylist => { | |
4d76e24e | 530 | description => "Use Greylisting.", |
f609bf7f DM |
531 | type => 'boolean', |
532 | default => 1, | |
533 | }, | |
534 | helotests => { | |
4d76e24e | 535 | description => "Use SMTP HELO tests.", |
f609bf7f DM |
536 | type => 'boolean', |
537 | default => 0, | |
538 | }, | |
539 | rejectunknown => { | |
4d76e24e | 540 | description => "Reject unknown clients.", |
f609bf7f DM |
541 | type => 'boolean', |
542 | default => 0, | |
543 | }, | |
544 | rejectunknownsender => { | |
4d76e24e | 545 | description => "Reject unknown senders.", |
f609bf7f DM |
546 | type => 'boolean', |
547 | default => 0, | |
548 | }, | |
549 | verifyreceivers => { | |
3791e936 | 550 | description => "Enable receiver verification. The value spefifies the numerical reply code when the Postfix SMTP server rejects a recipient address.", |
90822f27 DM |
551 | type => 'string', |
552 | enum => ['450', '550'], | |
f609bf7f DM |
553 | }, |
554 | dnsbl_sites => { | |
555 | description => "Optional list of DNS white/blacklist domains (see postscreen_dnsbl_sites parameter).", | |
556 | type => 'string', | |
557 | }, | |
d9dc3c08 DM |
558 | }; |
559 | } | |
560 | ||
561 | sub options { | |
562 | return { | |
75a20f14 DM |
563 | int_port => { optional => 1 }, |
564 | ext_port => { optional => 1 }, | |
3d9837d9 | 565 | smarthost => { optional => 1 }, |
f609bf7f DM |
566 | relay => { optional => 1 }, |
567 | relayport => { optional => 1 }, | |
568 | relaynomx => { optional => 1 }, | |
569 | dwarning => { optional => 1 }, | |
570 | max_smtpd_in => { optional => 1 }, | |
571 | max_smtpd_out => { optional => 1 }, | |
572 | greylist => { optional => 1 }, | |
573 | helotests => { optional => 1 }, | |
574 | use_rbl => { optional => 1 }, | |
575 | tls => { optional => 1 }, | |
589be6da DM |
576 | tlslog => { optional => 1 }, |
577 | tlsheader => { optional => 1 }, | |
f609bf7f DM |
578 | spf => { optional => 1 }, |
579 | maxsize => { optional => 1 }, | |
d9dc3c08 | 580 | banner => { optional => 1 }, |
f62194b2 | 581 | max_filters => { optional => 1 }, |
03907162 | 582 | max_policy => { optional => 1 }, |
f62194b2 | 583 | hide_received => { optional => 1 }, |
f609bf7f DM |
584 | rejectunknown => { optional => 1 }, |
585 | rejectunknownsender => { optional => 1 }, | |
586 | conn_count_limit => { optional => 1 }, | |
587 | conn_rate_limit => { optional => 1 }, | |
588 | message_rate_limit => { optional => 1 }, | |
589 | verifyreceivers => { optional => 1 }, | |
590 | dnsbl_sites => { optional => 1 }, | |
d9dc3c08 DM |
591 | }; |
592 | } | |
7e0e6dbe DM |
593 | package PMG::Config; |
594 | ||
595 | use strict; | |
596 | use warnings; | |
9123cab5 | 597 | use IO::File; |
7e0e6dbe | 598 | use Data::Dumper; |
4ccdc564 | 599 | use Template; |
7e0e6dbe | 600 | |
9123cab5 | 601 | use PVE::SafeSyslog; |
ba323310 | 602 | use PVE::Tools qw($IPV4RE $IPV6RE); |
7e0e6dbe | 603 | use PVE::INotify; |
b86ac4eb | 604 | use PVE::JSONSchema; |
7e0e6dbe | 605 | |
ac5d1312 | 606 | PMG::Config::Admin->register(); |
d9dc3c08 | 607 | PMG::Config::Mail->register(); |
fc070a06 DM |
608 | PMG::Config::SpamQuarantine->register(); |
609 | PMG::Config::VirusQuarantine->register(); | |
7e0e6dbe | 610 | PMG::Config::Spam->register(); |
f62194b2 | 611 | PMG::Config::ClamAV->register(); |
7e0e6dbe DM |
612 | |
613 | # initialize all plugins | |
614 | PMG::Config::Base->init(); | |
615 | ||
b86ac4eb DM |
616 | PVE::JSONSchema::register_format( |
617 | 'transport-domain', \&pmg_verify_transport_domain); | |
618 | sub pmg_verify_transport_domain { | |
619 | my ($name, $noerr) = @_; | |
620 | ||
621 | # like dns-name, but can contain leading dot | |
622 | my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)"; | |
623 | ||
624 | if ($name !~ /^\.?(${namere}\.)*${namere}$/) { | |
625 | return undef if $noerr; | |
626 | die "value does not look like a valid transport domain\n"; | |
627 | } | |
628 | return $name; | |
629 | } | |
f62194b2 DM |
630 | |
631 | sub new { | |
632 | my ($type) = @_; | |
633 | ||
634 | my $class = ref($type) || $type; | |
635 | ||
636 | my $cfg = PVE::INotify::read_file("pmg.conf"); | |
637 | ||
638 | return bless $cfg, $class; | |
639 | } | |
640 | ||
be6e2db9 DM |
641 | sub write { |
642 | my ($self) = @_; | |
643 | ||
644 | PVE::INotify::write_file("pmg.conf", $self); | |
645 | } | |
646 | ||
f21d933c DM |
647 | my $lockfile = "/var/lock/pmgconfig.lck"; |
648 | ||
649 | sub lock_config { | |
650 | my ($code, $errmsg) = @_; | |
651 | ||
652 | my $p = PVE::Tools::lock_file($lockfile, undef, $code); | |
653 | if (my $err = $@) { | |
654 | $errmsg ? die "$errmsg: $err" : die $err; | |
655 | } | |
656 | } | |
657 | ||
062f0498 | 658 | # set section values |
062f0498 DM |
659 | sub set { |
660 | my ($self, $section, $key, $value) = @_; | |
661 | ||
662 | my $pdata = PMG::Config::Base->private(); | |
663 | ||
062f0498 DM |
664 | my $plugin = $pdata->{plugins}->{$section}; |
665 | die "no such section '$section'" if !$plugin; | |
666 | ||
062f0498 DM |
667 | if (defined($value)) { |
668 | my $tmp = PMG::Config::Base->check_value($section, $key, $value, $section, 0); | |
d79b9b0c DM |
669 | $self->{ids}->{$section} = { type => $section } if !defined($self->{ids}->{$section}); |
670 | $self->{ids}->{$section}->{$key} = PMG::Config::Base->decode_value($section, $key, $tmp); | |
062f0498 | 671 | } else { |
d79b9b0c DM |
672 | if (defined($self->{ids}->{$section})) { |
673 | delete $self->{ids}->{$section}->{$key}; | |
062f0498 DM |
674 | } |
675 | } | |
676 | ||
677 | return undef; | |
678 | } | |
679 | ||
f62194b2 | 680 | # get section value or default |
f62194b2 | 681 | sub get { |
11081cf6 | 682 | my ($self, $section, $key, $nodefault) = @_; |
f62194b2 DM |
683 | |
684 | my $pdata = PMG::Config::Base->private(); | |
f62194b2 | 685 | my $pdesc = $pdata->{propertyList}->{$key}; |
3d9837d9 DM |
686 | die "no such property '$section/$key'\n" |
687 | if !(defined($pdesc) && defined($pdata->{options}->{$section}) && | |
688 | defined($pdata->{options}->{$section}->{$key})); | |
f62194b2 | 689 | |
d79b9b0c DM |
690 | if (defined($self->{ids}->{$section}) && |
691 | defined(my $value = $self->{ids}->{$section}->{$key})) { | |
f62194b2 | 692 | return $value; |
1ccc8e95 | 693 | } |
f62194b2 | 694 | |
11081cf6 DM |
695 | return undef if $nodefault; |
696 | ||
f62194b2 DM |
697 | return $pdesc->{default}; |
698 | } | |
699 | ||
1ccc8e95 | 700 | # get a whole section with default value |
1ccc8e95 DM |
701 | sub 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 |
725 | sub 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 |
740 | sub 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 | ||
750 | sub 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 | 758 | PVE::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 | 765 | my $domainsfilename = "/etc/pmg/domains"; |
f609bf7f | 766 | |
c3f4336c DM |
767 | sub postmap_pmg_domains { |
768 | PMG::Utils::run_postmap($domainsfilename); | |
769 | } | |
770 | ||
f609bf7f DM |
771 | sub 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 | ||
800 | sub 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 | ||
812 | PVE::INotify::register_file('domains', $domainsfilename, | |
813 | \&read_pmg_domains, | |
814 | \&write_pmg_domains, | |
815 | undef, always_call_parser => 1); | |
816 | ||
bef31f06 DM |
817 | my $mynetworks_filename = "/etc/pmg/mynetworks"; |
818 | ||
819 | sub postmap_pmg_mynetworks { | |
820 | PMG::Utils::run_postmap($mynetworks_filename); | |
821 | } | |
822 | ||
823 | sub 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 | ||
851 | sub 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 | ||
861 | PVE::INotify::register_file('mynetworks', $mynetworks_filename, | |
862 | \&read_pmg_mynetworks, | |
863 | \&write_pmg_mynetworks, | |
864 | undef, always_call_parser => 1); | |
865 | ||
cd533938 | 866 | my $transport_map_filename = "/etc/pmg/transport"; |
3546daf0 | 867 | |
3118b703 DM |
868 | sub postmap_pmg_transport { |
869 | PMG::Utils::run_postmap($transport_map_filename); | |
870 | } | |
871 | ||
3546daf0 DM |
872 | sub 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 | 932 | sub 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 | ||
957 | PVE::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 |
964 | sub 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 | ||
11081cf6 DM |
1018 | if (my $proxy = $vars->{pmg}->{admin}->{http_proxy}) { |
1019 | eval { | |
1020 | my $uri = URI->new($proxy); | |
1021 | my $host = $uri->host; | |
1022 | my $port = $uri->port // 8080; | |
1023 | if ($host) { | |
1024 | my $data = { host => $host, port => $port }; | |
1025 | if (my $ui = $uri->userinfo) { | |
1026 | my ($username, $pw) = split(/:/, $ui, 2); | |
1027 | $data->{username} = $username; | |
1028 | $data->{password} = $pw if defined($pw); | |
1029 | } | |
1030 | $vars->{proxy} = $data; | |
1031 | } | |
1032 | }; | |
1033 | warn "parse http_proxy failed - $@" if $@; | |
1034 | } | |
1035 | ||
07b3face DM |
1036 | return $vars; |
1037 | } | |
1038 | ||
310daf18 DM |
1039 | # use one global TT cache |
1040 | our $tt_include_path = ['/etc/pmg/templates' ,'/var/lib/pmg/templates' ]; | |
1041 | ||
1042 | my $template_toolkit; | |
1043 | ||
1044 | sub get_template_toolkit { | |
1045 | ||
1046 | return $template_toolkit if $template_toolkit; | |
1047 | ||
1048 | $template_toolkit = Template->new({ INCLUDE_PATH => $tt_include_path }); | |
1049 | ||
1050 | return $template_toolkit; | |
1051 | } | |
1052 | ||
07b3face DM |
1053 | # rewrite file from template |
1054 | # return true if file has changed | |
1055 | sub rewrite_config_file { | |
1056 | my ($self, $tmplname, $dstfn) = @_; | |
1057 | ||
1058 | my $demo = $self->get('admin', 'demo'); | |
1059 | ||
07b3face | 1060 | if ($demo) { |
310daf18 DM |
1061 | my $demosrc = "$tmplname.demo"; |
1062 | $tmplname = $demosrc if -f "/var/lib/pmg/templates/$demosrc"; | |
07b3face DM |
1063 | } |
1064 | ||
c248d69f | 1065 | my ($perm, $uid, $gid); |
07b3face | 1066 | |
07b3face DM |
1067 | if ($dstfn eq '/etc/fetchmailrc') { |
1068 | (undef, undef, $uid, $gid) = getpwnam('fetchmail'); | |
1069 | $perm = 0600; | |
1070 | } elsif ($dstfn eq '/etc/clamav/freshclam.conf') { | |
1071 | # needed if file contains a HTTPProxyPasswort | |
1072 | ||
1073 | $uid = getpwnam('clamav'); | |
1074 | $gid = getgrnam('adm'); | |
1075 | $perm = 0600; | |
1076 | } | |
1077 | ||
310daf18 | 1078 | my $tt = get_template_toolkit(); |
07b3face DM |
1079 | |
1080 | my $vars = $self->get_template_vars(); | |
1081 | ||
c248d69f | 1082 | my $output = ''; |
07b3face | 1083 | |
310daf18 | 1084 | $tt->process($tmplname, $vars, \$output) || |
60f82a46 | 1085 | die $tt->error() . "\n"; |
07b3face DM |
1086 | |
1087 | my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn; | |
1088 | ||
1089 | return 0 if defined($old) && ($old eq $output); # no change | |
1090 | ||
1091 | PVE::Tools::file_set_contents($dstfn, $output, $perm); | |
1092 | ||
1093 | if (defined($uid) && defined($gid)) { | |
1094 | chown($uid, $gid, $dstfn); | |
1095 | } | |
1096 | ||
1097 | return 1; | |
4ccdc564 DM |
1098 | } |
1099 | ||
9123cab5 DM |
1100 | # rewrite spam configuration |
1101 | sub rewrite_config_spam { | |
1102 | my ($self) = @_; | |
1103 | ||
1104 | my $use_awl = $self->get('spam', 'use_awl'); | |
1105 | my $use_bayes = $self->get('spam', 'use_bayes'); | |
1106 | my $use_razor = $self->get('spam', 'use_razor'); | |
1107 | ||
17424665 DM |
1108 | my $changes = 0; |
1109 | ||
9123cab5 | 1110 | # delete AW and bayes databases if those features are disabled |
17424665 DM |
1111 | if (!$use_awl) { |
1112 | $changes = 1 if unlink '/root/.spamassassin/auto-whitelist'; | |
1113 | } | |
1114 | ||
9123cab5 | 1115 | if (!$use_bayes) { |
17424665 DM |
1116 | $changes = 1 if unlink '/root/.spamassassin/bayes_journal'; |
1117 | $changes = 1 if unlink '/root/.spamassassin/bayes_seen'; | |
1118 | $changes = 1 if unlink '/root/.spamassassin/bayes_toks'; | |
9123cab5 DM |
1119 | } |
1120 | ||
1121 | # make sure we have a custom.cf file (else cluster sync fails) | |
1122 | IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644); | |
1123 | ||
17424665 DM |
1124 | $changes = 1 if $self->rewrite_config_file( |
1125 | 'local.cf.in', '/etc/mail/spamassassin/local.cf'); | |
1126 | ||
1127 | $changes = 1 if $self->rewrite_config_file( | |
1128 | 'init.pre.in', '/etc/mail/spamassassin/init.pre'); | |
1129 | ||
1130 | $changes = 1 if $self->rewrite_config_file( | |
1131 | 'v310.pre.in', '/etc/mail/spamassassin/v310.pre'); | |
1132 | ||
1133 | $changes = 1 if $self->rewrite_config_file( | |
1134 | 'v320.pre.in', '/etc/mail/spamassassin/v320.pre'); | |
9123cab5 DM |
1135 | |
1136 | if ($use_razor) { | |
1137 | mkdir "/root/.razor"; | |
17424665 DM |
1138 | |
1139 | $changes = 1 if $self->rewrite_config_file( | |
1140 | 'razor-agent.conf.in', '/root/.razor/razor-agent.conf'); | |
1141 | ||
9123cab5 DM |
1142 | if (! -e '/root/.razor/identity') { |
1143 | eval { | |
1144 | my $timeout = 30; | |
17424665 DM |
1145 | PVE::Tools::run_command(['razor-admin', '-discover'], timeout => $timeout); |
1146 | PVE::Tools::run_command(['razor-admin', '-register'], timeout => $timeout); | |
9123cab5 DM |
1147 | }; |
1148 | my $err = $@; | |
b902c0b8 | 1149 | syslog('info', "registering razor failed: $err") if $err; |
9123cab5 DM |
1150 | } |
1151 | } | |
17424665 DM |
1152 | |
1153 | return $changes; | |
9123cab5 DM |
1154 | } |
1155 | ||
ac5d1312 DM |
1156 | # rewrite ClamAV configuration |
1157 | sub rewrite_config_clam { | |
1158 | my ($self) = @_; | |
1159 | ||
17424665 DM |
1160 | return $self->rewrite_config_file( |
1161 | 'clamd.conf.in', '/etc/clamav/clamd.conf'); | |
1162 | } | |
1163 | ||
1164 | sub rewrite_config_freshclam { | |
1165 | my ($self) = @_; | |
1166 | ||
1167 | return $self->rewrite_config_file( | |
1168 | 'freshclam.conf.in', '/etc/clamav/freshclam.conf'); | |
ac5d1312 DM |
1169 | } |
1170 | ||
86737f12 DM |
1171 | sub rewrite_config_postgres { |
1172 | my ($self) = @_; | |
1173 | ||
1174 | my $pgconfdir = "/etc/postgresql/9.6/main"; | |
1175 | ||
17424665 DM |
1176 | my $changes = 0; |
1177 | ||
1178 | $changes = 1 if $self->rewrite_config_file( | |
1179 | 'pg_hba.conf.in', "$pgconfdir/pg_hba.conf"); | |
1180 | ||
1181 | $changes = 1 if $self->rewrite_config_file( | |
1182 | 'postgresql.conf.in', "$pgconfdir/postgresql.conf"); | |
1183 | ||
1184 | return $changes; | |
86737f12 DM |
1185 | } |
1186 | ||
1187 | # rewrite /root/.forward | |
1188 | sub rewrite_dot_forward { | |
1189 | my ($self) = @_; | |
1190 | ||
c248d69f | 1191 | my $dstfn = '/root/.forward'; |
86737f12 | 1192 | |
0bb9a01a | 1193 | my $email = $self->get('admin', 'email'); |
c248d69f | 1194 | |
e14fda7a | 1195 | my $output = ''; |
86737f12 | 1196 | if ($email && $email =~ m/\s*(\S+)\s*/) { |
c248d69f | 1197 | $output = "$1\n"; |
86737f12 DM |
1198 | } else { |
1199 | # empty .forward does not forward mails (see man local) | |
1200 | } | |
17424665 | 1201 | |
c248d69f DM |
1202 | my $old = PVE::Tools::file_get_contents($dstfn, 128*1024) if -f $dstfn; |
1203 | ||
1204 | return 0 if defined($old) && ($old eq $output); # no change | |
1205 | ||
1206 | PVE::Tools::file_set_contents($dstfn, $output); | |
1207 | ||
1208 | return 1; | |
86737f12 DM |
1209 | } |
1210 | ||
d15630a9 DM |
1211 | my $write_smtp_whitelist = sub { |
1212 | my ($filename, $data, $action) = @_; | |
1213 | ||
1214 | $action = 'OK' if !$action; | |
1215 | ||
1216 | my $old = PVE::Tools::file_get_contents($filename, 1024*1024) | |
1217 | if -f $filename; | |
1218 | ||
1219 | my $new = ''; | |
1220 | foreach my $k (sort keys %$data) { | |
1221 | $new .= "$k $action\n"; | |
1222 | } | |
1223 | ||
1224 | return 0 if defined($old) && ($old eq $new); # no change | |
1225 | ||
1226 | PVE::Tools::file_set_contents($filename, $new); | |
1227 | ||
1228 | PMG::Utils::run_postmap($filename); | |
1229 | ||
1230 | return 1; | |
1231 | }; | |
1232 | ||
1233 | my $rewrite_config_whitelist = sub { | |
1234 | my ($rulecache) = @_; | |
1235 | ||
1236 | # see man page for regexp_table for postfix regex table format | |
1237 | ||
1238 | # we use a hash to avoid duplicate entries in regex tables | |
1239 | my $tolist = {}; | |
1240 | my $fromlist = {}; | |
1241 | my $clientlist = {}; | |
1242 | ||
1243 | foreach my $obj (@{$rulecache->{"greylist:receiver"}}) { | |
1244 | my $oclass = ref($obj); | |
1245 | if ($oclass eq 'PMG::RuleDB::Receiver') { | |
1246 | my $addr = PMG::Utils::quote_regex($obj->{address}); | |
1247 | $tolist->{"/^$addr\$/"} = 1; | |
1248 | } elsif ($oclass eq 'PMG::RuleDB::ReceiverDomain') { | |
1249 | my $addr = PMG::Utils::quote_regex($obj->{address}); | |
1250 | $tolist->{"/^.+\@$addr\$/"} = 1; | |
1251 | } elsif ($oclass eq 'PMG::RuleDB::ReceiverRegex') { | |
1252 | my $addr = $obj->{address}; | |
1253 | $addr =~ s|/|\\/|g; | |
1254 | $tolist->{"/^$addr\$/"} = 1; | |
1255 | } | |
1256 | } | |
1257 | ||
1258 | foreach my $obj (@{$rulecache->{"greylist:sender"}}) { | |
1259 | my $oclass = ref($obj); | |
1260 | my $addr = PMG::Utils::quote_regex($obj->{address}); | |
1261 | if ($oclass eq 'PMG::RuleDB::EMail') { | |
1262 | my $addr = PMG::Utils::quote_regex($obj->{address}); | |
1263 | $fromlist->{"/^$addr\$/"} = 1; | |
1264 | } elsif ($oclass eq 'PMG::RuleDB::Domain') { | |
1265 | my $addr = PMG::Utils::quote_regex($obj->{address}); | |
1266 | $fromlist->{"/^.+\@$addr\$/"} = 1; | |
1267 | } elsif ($oclass eq 'PMG::RuleDB::WhoRegex') { | |
1268 | my $addr = $obj->{address}; | |
1269 | $addr =~ s|/|\\/|g; | |
1270 | $fromlist->{"/^$addr\$/"} = 1; | |
1271 | } elsif ($oclass eq 'PMG::RuleDB::IPAddress') { | |
1272 | $clientlist->{$obj->{address}} = 1; | |
1273 | } elsif ($oclass eq 'PMG::RuleDB::IPNet') { | |
1274 | $clientlist->{$obj->{address}} = 1; | |
1275 | } | |
1276 | } | |
1277 | ||
1278 | $write_smtp_whitelist->("/etc/postfix/senderaccess", $fromlist); | |
1279 | $write_smtp_whitelist->("/etc/postfix/rcptaccess", $tolist); | |
1280 | $write_smtp_whitelist->("/etc/postfix/clientaccess", $clientlist); | |
1281 | $write_smtp_whitelist->("/etc/postfix/postscreen_access", $clientlist, 'permit'); | |
1282 | }; | |
1283 | ||
f609bf7f DM |
1284 | # rewrite /etc/postfix/* |
1285 | sub rewrite_config_postfix { | |
d15630a9 | 1286 | my ($self, $rulecache) = @_; |
f609bf7f | 1287 | |
3546daf0 | 1288 | # make sure we have required files (else postfix start fails) |
b7298186 | 1289 | postmap_pmg_domains(); |
ba323310 | 1290 | postmap_pmg_transport(); |
bef31f06 | 1291 | postmap_pmg_mynetworks(); |
b7298186 | 1292 | |
3546daf0 | 1293 | IO::File->new($transport_map_filename, 'a', 0644); |
f609bf7f | 1294 | |
17424665 DM |
1295 | my $changes = 0; |
1296 | ||
f609bf7f DM |
1297 | if ($self->get('mail', 'tls')) { |
1298 | eval { | |
bc44eb02 | 1299 | PMG::Utils::gen_proxmox_tls_cert(); |
f609bf7f | 1300 | }; |
b902c0b8 | 1301 | syslog ('info', "generating certificate failed: $@") if $@; |
f609bf7f DM |
1302 | } |
1303 | ||
17424665 DM |
1304 | $changes = 1 if $self->rewrite_config_file( |
1305 | 'main.cf.in', '/etc/postfix/main.cf'); | |
1306 | ||
1307 | $changes = 1 if $self->rewrite_config_file( | |
1308 | 'master.cf.in', '/etc/postfix/master.cf'); | |
1309 | ||
c90f3170 | 1310 | $rewrite_config_whitelist->($rulecache) if $rulecache; |
d15630a9 DM |
1311 | |
1312 | # fixme: rewrite_config_tls_policy ($class); | |
f609bf7f DM |
1313 | |
1314 | # make sure aliases.db is up to date | |
1315 | system('/usr/bin/newaliases'); | |
17424665 DM |
1316 | |
1317 | return $changes; | |
f609bf7f DM |
1318 | } |
1319 | ||
f983300f | 1320 | sub rewrite_config { |
d15630a9 | 1321 | my ($self, $rulecache, $restart_services, $force_restart) = @_; |
c248d69f | 1322 | |
798df412 DM |
1323 | $force_restart = {} if ! $force_restart; |
1324 | ||
d15630a9 | 1325 | if (($self->rewrite_config_postfix($rulecache) && $restart_services) || |
798df412 | 1326 | $force_restart->{postfix}) { |
c248d69f DM |
1327 | PMG::Utils::service_cmd('postfix', 'restart'); |
1328 | } | |
1329 | ||
1330 | if ($self->rewrite_dot_forward() && $restart_services) { | |
1331 | # no need to restart anything | |
1332 | } | |
1333 | ||
1334 | if ($self->rewrite_config_postgres() && $restart_services) { | |
1335 | # do nothing (too many side effects)? | |
1336 | # does not happen anyways, because config does not change. | |
1337 | } | |
f983300f | 1338 | |
798df412 DM |
1339 | if (($self->rewrite_config_spam() && $restart_services) || |
1340 | $force_restart->{spam}) { | |
c248d69f DM |
1341 | PMG::Utils::service_cmd('pmg-smtp-filter', 'restart'); |
1342 | } | |
1343 | ||
798df412 DM |
1344 | if (($self->rewrite_config_clam() && $restart_services) || |
1345 | $force_restart->{clam}) { | |
8f87fe74 | 1346 | PMG::Utils::service_cmd('clamav-daemon', 'restart'); |
c248d69f DM |
1347 | } |
1348 | ||
798df412 DM |
1349 | if (($self->rewrite_config_freshclam() && $restart_services) || |
1350 | $force_restart->{freshclam}) { | |
8f87fe74 | 1351 | PMG::Utils::service_cmd('clamav-freshclam', 'restart'); |
c248d69f | 1352 | } |
f983300f DM |
1353 | } |
1354 | ||
7e0e6dbe | 1355 | 1; |