]> git.proxmox.com Git - pmg-api.git/blame - PMG/Config.pm
PMG/Config.pm: new helper set()
[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." },
16 section_id => {
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
30 if ($type eq 'ldap') {
31 $sectionId =~ s/^ldap_//;
32 return "$type: $sectionId\n";
33 } else {
34 return "section: $type\n";
35 }
36}
37
38
39sub parse_section_header {
40 my ($class, $line) = @_;
41
42 if ($line =~ m/^(ldap|section):\s*(\S+)\s*$/) {
43 my ($raw_type, $raw_id) = (lc($1), $2);
44 my $type = $raw_type eq 'section' ? $raw_id : $raw_type;
45 my $section_id = "${raw_type}_${raw_id}";
46 my $errmsg = undef; # set if you want to skip whole section
47 eval { PVE::JSONSchema::pve_verify_configid($raw_id); };
48 $errmsg = $@ if $@;
49 my $config = {}; # to return additional attributes
50 return ($type, $section_id, $errmsg, $config);
51 }
52 return undef;
53}
54
ac5d1312 55package PMG::Config::Admin;
7e0e6dbe
DM
56
57use strict;
58use warnings;
59
60use base qw(PMG::Config::Base);
61
62sub type {
ac5d1312 63 return 'admin';
7e0e6dbe
DM
64}
65
66sub properties {
67 return {
68 dailyreport => {
69 description => "Send daily reports.",
70 type => 'boolean',
71 default => 1,
72 },
f62194b2
DM
73 demo => {
74 description => "Demo mode - do not start SMTP filter.",
75 type => 'boolean',
76 default => 0,
77 },
78 email => {
79 description => "Administrator E-Mail address.",
80 type => 'string', format => 'email',
81 default => 'admin@domain.tld',
ac5d1312
DM
82 },
83 proxyport => {
84 description => "HTTP proxy port.",
85 type => 'integer',
86 minimum => 1,
87 default => 8080,
88 },
89 proxyserver => {
90 description => "HTTP proxy server address.",
91 type => 'string',
92 },
93 proxyuser => {
94 description => "HTTP proxy user name.",
95 type => 'string',
96 },
97 proxypassword => {
98 description => "HTTP proxy password.",
99 type => 'string',
100 },
7e0e6dbe
DM
101 };
102}
103
104sub options {
105 return {
106 dailyreport => { optional => 1 },
f62194b2 107 demo => { optional => 1 },
ac5d1312
DM
108 proxyport => { optional => 1 },
109 proxyserver => { optional => 1 },
110 proxyuser => { optional => 1 },
111 proxypassword => { optional => 1 },
7e0e6dbe
DM
112 };
113}
114
115package PMG::Config::Spam;
116
117use strict;
118use warnings;
119
120use base qw(PMG::Config::Base);
121
122sub type {
123 return 'spam';
124}
125
126sub properties {
127 return {
1ccc8e95
DM
128 languages => {
129 description => "This option is used to specify which languages are considered OK for incoming mail.",
130 type => 'string',
131 pattern => '(all|([a-z][a-z])+( ([a-z][a-z])+)*)',
132 default => 'all',
133 },
134 use_bayes => {
135 description => "Whether to use the naive-Bayesian-style classifier.",
136 type => 'boolean',
137 default => 1,
138 },
582cfacf
DM
139 use_awl => {
140 description => "Use the Auto-Whitelist plugin.",
141 type => 'boolean',
142 default => 1,
143 },
144 use_razor => {
145 description => "Whether to use Razor2, if it is available.",
146 type => 'boolean',
147 default => 1,
148 },
03ac6d8f
DM
149 use_ocr => {
150 description => "Enable OCR to scan pictures.",
151 type => 'boolean',
152 default => 0,
153 },
1ccc8e95
DM
154 wl_bounce_relays => {
155 description => "Whitelist legitimate bounce relays.",
156 type => 'string',
157 },
7e0e6dbe
DM
158 bounce_score => {
159 description => "Additional score for bounce mails.",
160 type => 'integer',
161 minimum => 0,
162 maximum => 1000,
163 default => 0,
164 },
f62194b2
DM
165 rbl_checks => {
166 description => "Enable real time blacklists (RBL) checks.",
167 type => 'boolean',
168 default => 1,
169 },
170 maxspamsize => {
171 description => "Maximum size of spam messages in bytes.",
172 type => 'integer',
173 minimim => 64,
174 default => 200*1024,
175 },
7e0e6dbe
DM
176 };
177}
178
179sub options {
180 return {
582cfacf
DM
181 use_awl => { optional => 1 },
182 use_razor => { optional => 1 },
03ac6d8f 183 use_ocr => { optional => 1 },
1ccc8e95
DM
184 wl_bounce_relays => { optional => 1 },
185 languages => { optional => 1 },
186 use_bayes => { optional => 1 },
7e0e6dbe 187 bounce_score => { optional => 1 },
f62194b2
DM
188 rbl_checks => { optional => 1 },
189 maxspamsize => { optional => 1 },
190 };
191}
192
193package PMG::Config::ClamAV;
194
195use strict;
196use warnings;
197
198use base qw(PMG::Config::Base);
199
200sub type {
201 return 'clamav';
202}
203
204sub properties {
205 return {
ac5d1312
DM
206 dbmirror => {
207 description => "ClamAV database mirror server.",
208 type => 'string',
209 default => 'database.clamav.net',
210 },
211 archiveblockencrypted => {
212 description => "Wether to block encrypted archives. Mark encrypted archives as viruses.",
213 type => 'boolean',
214 default => 0,
215 },
216 archivemaxrec => {
217 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.",
218 minimum => 1,
219 default => 5,
220 },
f62194b2 221 archivemaxfiles => {
ac5d1312 222 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
223 type => 'integer',
224 minimum => 0,
225 default => 1000,
226 },
ac5d1312
DM
227 archivemaxsize => {
228 description => "Files larger than this limit won't be scanned.",
229 type => 'integer',
230 minimum => 1000000,
231 default => 25000000,
232 },
233 maxscansize => {
234 description => "Sets the maximum amount of data to be scanned for each input file.",
235 type => 'integer',
236 minimum => 1000000,
237 default => 100000000,
238 },
239 maxcccount => {
240 description => "This option sets the lowest number of Credit Card or Social Security numbers found in a file to generate a detect.",
241 type => 'integer',
242 minimum => 0,
243 default => 0,
244 },
f62194b2
DM
245 };
246}
247
248sub options {
249 return {
ac5d1312
DM
250 archiveblockencrypted => { optional => 1 },
251 archivemaxrec => { optional => 1 },
f62194b2 252 archivemaxfiles => { optional => 1 },
ac5d1312
DM
253 archivemaxsize => { optional => 1 },
254 maxscansize => { optional => 1 },
255 dbmirror => { optional => 1 },
256 maxcccount => { optional => 1 },
7e0e6dbe
DM
257 };
258}
259
260package PMG::Config::LDAP;
261
262use strict;
263use warnings;
264
265use base qw(PMG::Config::Base);
266
267sub type {
268 return 'ldap';
269}
270
271sub properties {
272 return {
273 mode => {
274 description => "LDAP protocol mode ('ldap' or 'ldaps').",
275 type => 'string',
276 enum => ['ldap', 'ldaps'],
277 default => 'ldap',
278 },
279 };
280}
281
282sub options {
283 return {
284 mode => { optional => 1 },
285 };
286}
f62194b2 287
d9dc3c08
DM
288package PMG::Config::Mail;
289
290use strict;
291use warnings;
292
f62194b2
DM
293use PVE::ProcFSTools;
294
d9dc3c08
DM
295use base qw(PMG::Config::Base);
296
297sub type {
298 return 'mail';
299}
300
f62194b2
DM
301my $physicalmem = 0;
302sub physical_memory {
303
304 return $physicalmem if $physicalmem;
305
306 my $info = PVE::ProcFSTools::read_meminfo();
307 my $total = int($info->{memtotal} / (1024*1024));
308
309 return $total;
310}
311
312sub get_max_filters {
313 # estimate optimal number of filter servers
314
315 my $max_servers = 5;
316 my $servermem = 120;
317 my $memory = physical_memory();
318 my $add_servers = int(($memory - 512)/$servermem);
319 $max_servers += $add_servers if $add_servers > 0;
320 $max_servers = 40 if $max_servers > 40;
321
322 return $max_servers - 2;
323}
324
d9dc3c08
DM
325sub properties {
326 return {
327 banner => {
328 description => "ESMTP banner.",
329 type => 'string',
330 maxLength => 1024,
331 default => 'ESMTP Proxmox',
332 },
f62194b2
DM
333 max_filters => {
334 description => "Maximum number of filter processes.",
335 type => 'integer',
336 minimum => 3,
337 maximum => 40,
338 default => get_max_filters(),
339 },
340 hide_received => {
341 description => "Hide received header in outgoing mails.",
342 type => 'boolean',
ac5d1312
DM
343 default => 0,
344 },
345 max_size => {
346 description => "Maximum email size. Larger mails are rejected.",
347 type => 'integer',
348 minimum => 1024,
349 default => 1024*1024*10,
f62194b2 350 },
d9dc3c08
DM
351 };
352}
353
354sub options {
355 return {
ac5d1312 356 max_size => { optional => 1 },
d9dc3c08 357 banner => { optional => 1 },
f62194b2
DM
358 max_filters => { optional => 1 },
359 hide_received => { optional => 1 },
d9dc3c08
DM
360 };
361}
7e0e6dbe
DM
362package PMG::Config;
363
364use strict;
365use warnings;
9123cab5 366use IO::File;
7e0e6dbe 367use Data::Dumper;
4ccdc564 368use Template;
7e0e6dbe 369
9123cab5 370use PVE::SafeSyslog;
7e0e6dbe
DM
371use PVE::Tools;
372use PVE::INotify;
373
4ccdc564
DM
374use PMG::AtomicFile;
375
ac5d1312 376PMG::Config::Admin->register();
d9dc3c08 377PMG::Config::Mail->register();
7e0e6dbe
DM
378PMG::Config::Spam->register();
379PMG::Config::LDAP->register();
f62194b2 380PMG::Config::ClamAV->register();
7e0e6dbe
DM
381
382# initialize all plugins
383PMG::Config::Base->init();
384
f62194b2
DM
385
386sub new {
387 my ($type) = @_;
388
389 my $class = ref($type) || $type;
390
391 my $cfg = PVE::INotify::read_file("pmg.conf");
392
393 return bless $cfg, $class;
394}
395
062f0498
DM
396# set section values
397# this does not work for ldap entries
398sub set {
399 my ($self, $section, $key, $value) = @_;
400
401 my $pdata = PMG::Config::Base->private();
402
403 die "internal error" if $section eq 'ldap';
404
405 my $plugin = $pdata->{plugins}->{$section};
406 die "no such section '$section'" if !$plugin;
407
408 my $configid = "section_$section";
409 if (defined($value)) {
410 my $tmp = PMG::Config::Base->check_value($section, $key, $value, $section, 0);
411 print Dumper($self->{ids});
412 $self->{ids}->{$configid} = { type => $section } if !defined($self->{ids}->{$configid});
413 $self->{ids}->{$configid}->{$key} = PMG::Config::Base->decode_value($section, $key, $tmp);
414 } else {
415 if (defined($self->{ids}->{$configid})) {
416 delete $self->{ids}->{$configid}->{$key};
417 }
418 }
419
420 return undef;
421}
422
f62194b2
DM
423# get section value or default
424# this does not work for ldap entries
425sub get {
426 my ($self, $section, $key) = @_;
427
428 my $pdata = PMG::Config::Base->private();
429 return undef if !defined($pdata->{options}->{$section});
430 return undef if !defined($pdata->{options}->{$section}->{$key});
431 my $pdesc = $pdata->{propertyList}->{$key};
432 return undef if !defined($pdesc);
433
434 my $configid = "section_$section";
435 if (defined($self->{ids}->{$configid}) &&
436 defined(my $value = $self->{ids}->{$configid}->{$key})) {
437 return $value;
1ccc8e95 438 }
f62194b2
DM
439
440 return $pdesc->{default};
441}
442
1ccc8e95
DM
443# get a whole section with default value
444# this does not work for ldap entries
445sub get_section {
446 my ($self, $section) = @_;
447
448 my $pdata = PMG::Config::Base->private();
449 return undef if !defined($pdata->{options}->{$section});
450
451 my $res = {};
452
453 foreach my $key (keys %{$pdata->{options}->{$section}}) {
454
455 my $pdesc = $pdata->{propertyList}->{$key};
456
457 my $configid = "section_$section";
458 if (defined($self->{ids}->{$configid}) &&
459 defined(my $value = $self->{ids}->{$configid}->{$key})) {
460 $res->{$key} = $value;
461 next;
462 }
463 $res->{$key} = $pdesc->{default};
464 }
465
466 return $res;
467}
468
be16be07
DM
469# get a whole config with default values
470# this does not work for ldap entries
471sub get_config {
472 my ($self) = @_;
473
9dab5fe5
DM
474 my $pdata = PMG::Config::Base->private();
475
be16be07
DM
476 my $res = {};
477
9dab5fe5
DM
478 foreach my $type (keys %{$pdata->{plugins}}) {
479 next if $type eq 'ldap';
480 my $plugin = $pdata->{plugins}->{$type};
481 $res->{$type} = $self->get_section($type);
be16be07
DM
482 }
483
484 return $res;
485}
486
7e0e6dbe
DM
487sub read_pmg_conf {
488 my ($filename, $fh) = @_;
f62194b2 489
7e0e6dbe 490 local $/ = undef; # slurp mode
f62194b2 491
7e0e6dbe
DM
492 my $raw = <$fh>;
493
494 return PMG::Config::Base->parse_config($filename, $raw);
495}
496
497sub write_pmg_conf {
498 my ($filename, $fh, $cfg) = @_;
499
500 my $raw = PMG::Config::Base->write_config($filename, $cfg);
501
502 PVE::Tools::safe_print($filename, $fh, $raw);
503}
504
f62194b2
DM
505PVE::INotify::register_file('pmg.conf', "/etc/proxmox/pmg.conf",
506 \&read_pmg_conf,
7e0e6dbe
DM
507 \&write_pmg_conf);
508
509
4ccdc564
DM
510# config file generation using templates
511
512sub rewrite_config_file {
513 my ($self, $tmplname, $dstfn) = @_;
514
515 my $demo = $self->get('admin', 'demo');
516
517 my $srcfn = ($tmplname =~ m|^.?/|) ?
518 $tmplname : "/var/lib/pmg/templates/$tmplname";
519
520 if ($demo) {
521 my $demosrc = "$srcfn.demo";
522 $srcfn = $demosrc if -f $demosrc;
523 }
524
525 my $srcfd = IO::File->new ($srcfn, "r")
526 || die "cant read template '$srcfn' - $!: ERROR";
527 my $dstfd = PMG::AtomicFile->open ($dstfn, "w")
528 || die "cant open config file '$dstfn' - $!: ERROR";
529
530 if ($dstfn eq '/etc/fetchmailrc') {
531 my ($login, $pass, $uid, $gid) = getpwnam('fetchmail');
532 if ($uid && $gid) {
533 chown($uid, $gid, ${*$dstfd}{'io_atomicfile_temp'});
534 }
535 chmod (0600, ${*$dstfd}{'io_atomicfile_temp'});
536 } elsif ($dstfn eq '/etc/clamav/freshclam.conf') {
537 # needed if file contains a HTTPProxyPasswort
538
539 my $uid = getpwnam('clamav');
540 my $gid = getgrnam('adm');
541
542 if ($uid && $gid) {
543 chown ($uid, $gid, ${*$dstfd}{'io_atomicfile_temp'});
544 }
545 chmod (0600, ${*$dstfd}{'io_atomicfile_temp'});
546 }
547
548 my $template = Template->new({});
549
550 my $vars = { pmg => $self->get_config() };
551
552 $template->process($srcfd, $vars, $dstfd) ||
553 die $template->error();
554
555 $srcfd->close();
556 $dstfd->close (1);
557}
558
559sub rewrite_config_script {
560 my ($self, $tmplname, $dstfn) = @_;
561
562 $self->rewrite_config_file($tmplname, $dstfn);
563 system("chmod +x $dstfn");
564}
565
9123cab5
DM
566# rewrite spam configuration
567sub rewrite_config_spam {
568 my ($self) = @_;
569
570 my $use_awl = $self->get('spam', 'use_awl');
571 my $use_bayes = $self->get('spam', 'use_bayes');
572 my $use_razor = $self->get('spam', 'use_razor');
573
574 # delete AW and bayes databases if those features are disabled
575 unlink '/root/.spamassassin/auto-whitelist' if !$use_awl;
576 if (!$use_bayes) {
577 unlink '/root/.spamassassin/bayes_journal';
578 unlink '/root/.spamassassin/bayes_seen';
579 unlink '/root/.spamassassin/bayes_toks';
580 }
581
582 # make sure we have a custom.cf file (else cluster sync fails)
583 IO::File->new('/etc/mail/spamassassin/custom.cf', 'a', 0644);
584
4ccdc564
DM
585 $self->rewrite_config_file('local.cf.in', '/etc/mail/spamassassin/local.cf');
586 $self->rewrite_config_file('init.pre.in', '/etc/mail/spamassassin/init.pre');
587 $self->rewrite_config_file('v310.pre.in', '/etc/mail/spamassassin/v310.pre');
588 $self->rewrite_config_file('v320.pre.in', '/etc/mail/spamassassin/v320.pre');
9123cab5
DM
589
590 if ($use_razor) {
591 mkdir "/root/.razor";
4ccdc564 592 $self->rewrite_config_file('razor-agent.conf.in', '/root/.razor/razor-agent.conf');
9123cab5
DM
593 if (! -e '/root/.razor/identity') {
594 eval {
595 my $timeout = 30;
596 PVE::Tools::run_command (['razor-admin', '-discover'], timeout => $timeout);
597 PVE::Tools::run_command (['razor-admin', '-register'], timeout => $timeout);
598 };
599 my $err = $@;
600 syslog('info', msgquote ("registering razor failed: $err")) if $err;
601 }
602 }
603}
604
ac5d1312
DM
605# rewrite ClamAV configuration
606sub rewrite_config_clam {
607 my ($self) = @_;
608
4ccdc564
DM
609 $self->rewrite_config_file('clamd.conf.in', '/etc/clamav/clamd.conf');
610 $self->rewrite_config_file('freshclam.conf.in', '/etc/clamav/freshclam.conf');
ac5d1312
DM
611}
612
7e0e6dbe 6131;