]> git.proxmox.com Git - pmg-api.git/blame - PMG/DBTools.pm
bump version to 5.1-4
[pmg-api.git] / PMG / DBTools.pm
CommitLineData
a355f100
DM
1package PMG::DBTools;
2
3use strict;
4use warnings;
5
6use POSIX ":sys_wait_h";
7use POSIX ':signal_h';
8use DBI;
8fb6f404 9use Time::Local;
a355f100 10
f3464b71 11use PVE::SafeSyslog;
a355f100
DM
12use PVE::Tools;
13
f3464b71 14use PMG::Utils;
0a580593 15use PMG::RuleDB;
1af3c560 16use PMG::MailQueue;
f9967a49 17use PMG::Config;
0a580593 18
987ee804
DM
19our $default_db_name = "Proxmox_ruledb";
20
2e049252
DM
21our $cgreylist_merge_sql =
22 'INSERT INTO CGREYLIST (IPNet,Host,Sender,Receiver,Instance,RCTime,' .
23 'ExTime,Delay,Blocked,Passed,MTime,CID) ' .
24 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' .
25 'ON CONFLICT (IPNet,Sender,Receiver) DO UPDATE SET ' .
0c6e9b56
DM
26 'Host = CASE WHEN CGREYLIST.MTime >= excluded.MTime THEN CGREYLIST.Host ELSE excluded.Host END,' .
27 'CID = GREATEST(CGREYLIST.CID, excluded.CID), RCTime = LEAST(CGREYLIST.RCTime, excluded.RCTime),' .
28 'ExTime = GREATEST(CGREYLIST.ExTime, excluded.ExTime),' .
29 'Delay = GREATEST(CGREYLIST.Delay, excluded.Delay),' .
30 'Blocked = GREATEST(CGREYLIST.Blocked, excluded.Blocked),' .
31 'Passed = GREATEST(CGREYLIST.Passed, excluded.Passed)';
2e049252 32
a355f100
DM
33sub open_ruledb {
34 my ($database, $host, $port) = @_;
35
5475e159 36 $port //= 5432;
a355f100 37
5475e159 38 $database //= $default_db_name;
a355f100
DM
39
40 if ($host) {
41
155b0da9
DM
42 # Note: pmgtunnel uses UDP sockets inside directory '/var/run/pmgtunnel',
43 # and the cluster 'cid' as port number. You can connect to the
44 # socket with: host => /var/run/pmgtunnel, port => $cid
45
a355f100
DM
46 my $dsn = "dbi:Pg:dbname=$database;host=$host;port=$port;";
47
48 my $timeout = 5;
49 # only low level alarm interface works for DBI->connect
50 my $mask = POSIX::SigSet->new(SIGALRM);
51 my $action = POSIX::SigAction->new(sub { die "connect timeout\n" }, $mask);
52 my $oldaction = POSIX::SigAction->new();
53 sigaction(SIGALRM, $action, $oldaction);
54
55 my $rdb;
56
57 eval {
58 alarm($timeout);
5475e159 59 $rdb = DBI->connect($dsn, 'root', undef,
a355f100
DM
60 { PrintError => 0, RaiseError => 1 });
61 alarm(0);
62 };
63 alarm(0);
64 sigaction(SIGALRM, $oldaction); # restore original handler
f1bf78ff 65
a355f100
DM
66 die $@ if $@;
67
68 return $rdb;
69 } else {
155b0da9 70 my $dsn = "DBI:Pg:dbname=$database;host=/var/run/postgresql;port=$port";
a355f100 71
5475e159 72 my $dbh = DBI->connect($dsn, $> == 0 ? 'root' : 'www-data', undef,
a355f100
DM
73 { PrintError => 0, RaiseError => 1 });
74
75 return $dbh;
76 }
77}
78
97cab76a
DM
79sub postgres_admin_cmd {
80 my ($cmd, $options, @params) = @_;
81
82 $cmd = ref($cmd) ? $cmd : [ $cmd ];
83 my $uid = getpwnam('postgres') || die "getpwnam postgres failed\n";
84
85 local $> = $uid;
86 $! && die "setuid postgres ($uid) failed - $!\n";
87
88 PVE::Tools::run_command([@$cmd, '-U', 'postgres', @params], %$options);
89}
90
a355f100
DM
91sub delete_ruledb {
92 my ($dbname) = @_;
93
97cab76a 94 postgres_admin_cmd('dropdb', undef, $dbname);
a355f100
DM
95}
96
f1bf78ff
DM
97sub database_list {
98
99 my $database_list = {};
100
101 my $parser = sub {
102 my $line = shift;
103
104 my ($name, $owner) = map { PVE::Tools::trim($_) } split(/\|/, $line);
105 return if !$name || !$owner;
106
107 $database_list->{$name} = { owner => $owner };
108 };
109
97cab76a 110 postgres_admin_cmd('psql', { outfunc => $parser }, '--list', '--quiet', '--tuples-only');
f1bf78ff
DM
111
112 return $database_list;
113}
114
f1bf78ff
DM
115my $cgreylist_ctablecmd = <<__EOD;
116 CREATE TABLE CGreylist
117 (IPNet VARCHAR(16) NOT NULL,
118 Host INTEGER NOT NULL,
119 Sender VARCHAR(255) NOT NULL,
120 Receiver VARCHAR(255) NOT NULL,
121 Instance VARCHAR(255),
122 RCTime INTEGER NOT NULL,
123 ExTime INTEGER NOT NULL,
124 Delay INTEGER NOT NULL DEFAULT 0,
125 Blocked INTEGER NOT NULL,
126 Passed INTEGER NOT NULL,
127 CID INTEGER NOT NULL,
128 MTime INTEGER NOT NULL,
129 PRIMARY KEY (IPNet, Sender, Receiver));
130
131 CREATE INDEX CGreylist_Instance_Sender_Index ON CGreylist (Instance, Sender);
132
133 CREATE INDEX CGreylist_ExTime_Index ON CGreylist (ExTime);
134
135 CREATE INDEX CGreylist_MTime_Index ON CGreylist (MTime);
136__EOD
137
138my $clusterinfo_ctablecmd = <<__EOD;
139 CREATE TABLE ClusterInfo
140 (CID INTEGER NOT NULL,
141 Name VARCHAR NOT NULL,
142 IValue INTEGER,
143 SValue VARCHAR,
144 PRIMARY KEY (CID, Name))
145__EOD
146
5e1408fd
DM
147my $local_stat_ctablecmd = <<__EOD;
148 CREATE TABLE LocalStat
ac28e80e 149 (Time INTEGER NOT NULL,
5e1408fd 150 RBLCount INTEGER DEFAULT 0 NOT NULL,
70aafbae 151 PregreetCount INTEGER DEFAULT 0 NOT NULL,
5e1408fd
DM
152 CID INTEGER NOT NULL,
153 MTime INTEGER NOT NULL,
154 PRIMARY KEY (Time, CID));
155
156 CREATE INDEX LocalStat_MTime_Index ON LocalStat (MTime);
157__EOD
158
159
f1bf78ff
DM
160my $daily_stat_ctablecmd = <<__EOD;
161 CREATE TABLE DailyStat
162 (Time INTEGER NOT NULL UNIQUE,
163 CountIn INTEGER NOT NULL,
164 CountOut INTEGER NOT NULL,
165 BytesIn REAL NOT NULL,
166 BytesOut REAL NOT NULL,
167 VirusIn INTEGER NOT NULL,
168 VirusOut INTEGER NOT NULL,
169 SpamIn INTEGER NOT NULL,
170 SpamOut INTEGER NOT NULL,
171 BouncesIn INTEGER NOT NULL,
172 BouncesOut INTEGER NOT NULL,
173 GreylistCount INTEGER NOT NULL,
174 SPFCount INTEGER NOT NULL,
175 PTimeSum REAL NOT NULL,
176 MTime INTEGER NOT NULL,
177 RBLCount INTEGER DEFAULT 0 NOT NULL,
178 PRIMARY KEY (Time));
179
180 CREATE INDEX DailyStat_MTime_Index ON DailyStat (MTime);
181
182__EOD
183
184my $domain_stat_ctablecmd = <<__EOD;
185 CREATE TABLE DomainStat
186 (Time INTEGER NOT NULL,
187 Domain VARCHAR(255) NOT NULL,
188 CountIn INTEGER NOT NULL,
189 CountOut INTEGER NOT NULL,
190 BytesIn REAL NOT NULL,
191 BytesOut REAL NOT NULL,
192 VirusIn INTEGER NOT NULL,
193 VirusOut INTEGER NOT NULL,
194 SpamIn INTEGER NOT NULL,
195 SpamOut INTEGER NOT NULL,
196 BouncesIn INTEGER NOT NULL,
197 BouncesOut INTEGER NOT NULL,
198 PTimeSum REAL NOT NULL,
199 MTime INTEGER NOT NULL,
200 PRIMARY KEY (Time, Domain));
201
202 CREATE INDEX DomainStat_MTime_Index ON DomainStat (MTime);
203__EOD
204
205my $statinfo_ctablecmd = <<__EOD;
206 CREATE TABLE StatInfo
207 (Name VARCHAR(255) NOT NULL UNIQUE,
208 IValue INTEGER,
209 SValue VARCHAR(255),
210 PRIMARY KEY (Name))
211__EOD
212
213my $virusinfo_stat_ctablecmd = <<__EOD;
214 CREATE TABLE VirusInfo
215 (Time INTEGER NOT NULL,
216 Name VARCHAR NOT NULL,
217 Count INTEGER NOT NULL,
218 MTime INTEGER NOT NULL,
219 PRIMARY KEY (Time, Name));
220
221 CREATE INDEX VirusInfo_MTime_Index ON VirusInfo (MTime);
222
223__EOD
224
ef5c77eb 225# mail storage table
f1bf78ff
DM
226# QTypes
227# V - Virus quarantine
228# S - Spam quarantine
229# D - Delayed Mails - not implemented
230# A - Held for Audit - not implemented
231# Status
232# N - new
233# D - deleted
234
235my $cmailstore_ctablecmd = <<__EOD;
236 CREATE TABLE CMailStore
237 (CID INTEGER DEFAULT 0 NOT NULL,
238 RID INTEGER NOT NULL,
239 ID SERIAL UNIQUE,
240 Time INTEGER NOT NULL,
241 QType "char" NOT NULL,
242 Bytes INTEGER NOT NULL,
243 Spamlevel INTEGER NOT NULL,
244 Info VARCHAR NULL,
245 Sender VARCHAR(255) NOT NULL,
246 Header VARCHAR NOT NULL,
247 File VARCHAR(255) NOT NULL,
248 PRIMARY KEY (CID, RID));
249 CREATE INDEX CMailStore_Time_Index ON CMailStore (Time);
250
251 CREATE TABLE CMSReceivers
252 (CMailStore_CID INTEGER NOT NULL,
253 CMailStore_RID INTEGER NOT NULL,
254 PMail VARCHAR(255) NOT NULL,
255 Receiver VARCHAR(255),
f2eb5f90 256 TicketID INTEGER NOT NULL,
f1bf78ff
DM
257 Status "char" NOT NULL,
258 MTime INTEGER NOT NULL);
259
260 CREATE INDEX CMailStore_ID_Index ON CMSReceivers (CMailStore_CID, CMailStore_RID);
261
262 CREATE INDEX CMSReceivers_MTime_Index ON CMSReceivers (MTime);
263
264__EOD
265
266my $cstatistic_ctablecmd = <<__EOD;
267 CREATE TABLE CStatistic
268 (CID INTEGER DEFAULT 0 NOT NULL,
269 RID INTEGER NOT NULL,
270 ID SERIAL UNIQUE,
271 Time INTEGER NOT NULL,
272 Bytes INTEGER NOT NULL,
273 Direction Boolean NOT NULL,
274 Spamlevel INTEGER NOT NULL,
275 VirusInfo VARCHAR(255) NULL,
276 PTime INTEGER NOT NULL,
277 Sender VARCHAR(255) NOT NULL,
278 PRIMARY KEY (CID, RID));
279
280 CREATE INDEX CStatistic_Time_Index ON CStatistic (Time);
281
282 CREATE TABLE CReceivers
283 (CStatistic_CID INTEGER NOT NULL,
284 CStatistic_RID INTEGER NOT NULL,
285 Receiver VARCHAR(255) NOT NULL,
286 Blocked Boolean NOT NULL);
287
288 CREATE INDEX CStatistic_ID_Index ON CReceivers (CStatistic_CID, CStatistic_RID);
289__EOD
290
291# user preferences (black an whitelists, ...)
292# Name: perference name ('BL' -> blacklist, 'WL' -> whitelist)
293# Data: arbitrary data
294my $userprefs_ctablecmd = <<__EOD;
295 CREATE TABLE UserPrefs
296 (PMail VARCHAR,
297 Name VARCHAR(255),
298 Data VARCHAR,
299 MTime INTEGER NOT NULL,
300 PRIMARY KEY (PMail, Name));
301
302 CREATE INDEX UserPrefs_MTime_Index ON UserPrefs (MTime);
303
304__EOD
758c7b6b 305
0a580593
DM
306sub cond_create_dbtable {
307 my ($dbh, $name, $ctablecmd) = @_;
308
309 eval {
310 $dbh->begin_work;
311
312 my $cmd = "SELECT tablename FROM pg_tables " .
313 "WHERE tablename = lower ('$name')";
314
5e1408fd 315 my $sth = $dbh->prepare($cmd);
758c7b6b 316
0a580593
DM
317 $sth->execute();
318
319 if (!(my $ref = $sth->fetchrow_hashref())) {
320 $dbh->do ($ctablecmd);
321 }
758c7b6b 322
0a580593
DM
323 $sth->finish();
324
325 $dbh->commit;
326 };
327 if (my $err = $@) {
328 $dbh->rollback;
9ef3f143 329 die $err;
0a580593
DM
330 }
331}
f1bf78ff 332
f2eb5f90
DM
333sub database_column_exists {
334 my ($dbh, $table, $column) = @_;
335
336 my $sth = $dbh->prepare(
337 "SELECT column_name FROM information_schema.columns " .
338 "WHERE table_name = ? and column_name = ?");
339 $sth->execute(lc($table), lc($column));
340 my $res = $sth->fetchrow_hashref();
341 return defined($res);
342}
343
2cc31eaa
DC
344my $createdb = sub {
345 my ($dbname) = @_;
346 postgres_admin_cmd(
347 'createdb',
348 undef,
349 '-E', 'sql_ascii',
350 '-T', 'template0',
351 '--lc-collate=C',
352 '--lc-ctype=C',
353 $dbname,
354 );
355};
356
f1bf78ff
DM
357sub create_ruledb {
358 my ($dbname) = @_;
359
987ee804 360 $dbname = $default_db_name if !$dbname;
f1bf78ff 361
97cab76a
DM
362 my $silent_opts = { outfunc => sub {}, errfunc => sub {} };
363 # make sure we have user 'root'
364 eval { postgres_admin_cmd('createuser', $silent_opts, '-D', 'root'); };
5475e159
DM
365 # also create 'www-data' (and give it read-only access below)
366 eval { postgres_admin_cmd('createuser', $silent_opts, '-I', '-D', 'www-data'); };
97cab76a 367
f1bf78ff
DM
368 # use sql_ascii to avoid any character set conversions, and be compatible with
369 # older postgres versions (update from 8.1 must be possible)
f1bf78ff 370
2cc31eaa 371 $createdb->($dbname);
f1bf78ff
DM
372
373 my $dbh = open_ruledb($dbname);
374
5475e159
DM
375 # make sure 'www-data' can read all tables
376 $dbh->do("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO \"www-data\"");
377
f1bf78ff
DM
378 $dbh->do (
379<<EOD
380 CREATE TABLE Attribut
381 (Object_ID INTEGER NOT NULL,
382 Name VARCHAR(20) NOT NULL,
383 Value BYTEA NULL,
384 PRIMARY KEY (Object_ID, Name));
385
386 CREATE INDEX Attribut_Object_ID_Index ON Attribut(Object_ID);
387
388 CREATE TABLE Object
389 (ID SERIAL UNIQUE,
390 ObjectType INTEGER NOT NULL,
391 Objectgroup_ID INTEGER NOT NULL,
392 Value BYTEA NULL,
393 PRIMARY KEY (ID));
394
395 CREATE TABLE Objectgroup
396 (ID SERIAL UNIQUE,
397 Name VARCHAR(255) NOT NULL,
398 Info VARCHAR(255) NULL,
399 Class VARCHAR(10) NOT NULL,
400 PRIMARY KEY (ID));
401
402 CREATE TABLE Rule
403 (ID SERIAL UNIQUE,
404 Name VARCHAR(255) NULL,
405 Priority INTEGER NOT NULL,
406 Active INTEGER NOT NULL DEFAULT 0,
407 Direction INTEGER NOT NULL DEFAULT 2,
408 Count INTEGER NOT NULL DEFAULT 0,
409 PRIMARY KEY (ID));
410
411 CREATE TABLE RuleGroup
412 (Objectgroup_ID INTEGER NOT NULL,
413 Rule_ID INTEGER NOT NULL,
414 Grouptype INTEGER NOT NULL,
415 PRIMARY KEY (Objectgroup_ID, Rule_ID, Grouptype));
416
417 $cgreylist_ctablecmd;
418
419 $clusterinfo_ctablecmd;
420
5e1408fd
DM
421 $local_stat_ctablecmd;
422
f1bf78ff
DM
423 $daily_stat_ctablecmd;
424
425 $domain_stat_ctablecmd;
426
427 $statinfo_ctablecmd;
428
429 $cmailstore_ctablecmd;
430
431 $cstatistic_ctablecmd;
432
433 $userprefs_ctablecmd;
434
435 $virusinfo_stat_ctablecmd;
436EOD
437 );
438
439 return $dbh;
440}
441
0a580593
DM
442sub cond_create_action_quarantine {
443 my ($ruledb) = @_;
444
445 my $dbh = $ruledb->{dbh};
446
447 eval {
448 my $sth = $dbh->prepare(
449 "SELECT * FROM Objectgroup, Object " .
450 "WHERE Object.ObjectType = ? AND Objectgroup.Class = ? " .
451 "AND Object.objectgroup_id = Objectgroup.id");
452
453 my $otype = PMG::RuleDB::Quarantine::otype();
454 if ($sth->execute($otype, 'action') <= 0) {
455 my $obj = PMG::RuleDB::Quarantine->new ();
456 my $txt = decode_entities(PMG::RuleDB::Quarantine->otype_text);
457 my $quarantine = $ruledb->create_group_with_obj
9578dcd7 458 ($obj, $txt, 'Move to quarantine.');
0a580593
DM
459 }
460 };
461}
462
463sub cond_create_std_actions {
464 my ($ruledb) = @_;
465
466 cond_create_action_quarantine($ruledb);
467
468 #cond_create_action_report_spam($ruledb);
469}
470
471
472sub upgradedb {
473 my ($ruledb) = @_;
474
475 my $dbh = $ruledb->{dbh};
476
758c7b6b 477 # make sure we do not use slow sequential scans when upgraing
0a580593
DM
478 # database (before analyze can gather statistics)
479 $dbh->do("set enable_seqscan = false");
480
97cab76a 481 my $tables = {
5e1408fd
DM
482 'LocalStat', $local_stat_ctablecmd,
483 'DailyStat', $daily_stat_ctablecmd,
97cab76a
DM
484 'DomainStat', $domain_stat_ctablecmd,
485 'StatInfo', $statinfo_ctablecmd,
486 'CMailStore', $cmailstore_ctablecmd,
487 'UserPrefs', $userprefs_ctablecmd,
488 'CGreylist', $cgreylist_ctablecmd,
489 'CStatistic', $cstatistic_ctablecmd,
490 'ClusterInfo', $clusterinfo_ctablecmd,
491 'VirusInfo', $virusinfo_stat_ctablecmd,
492 };
493
494 foreach my $table (keys %$tables) {
7f2a3858 495 cond_create_dbtable($dbh, $table, $tables->{$table});
97cab76a 496 }
0a580593 497
e7c865af 498 cond_create_std_actions($ruledb);
0a580593 499
dc295201 500 # upgrade tables here if necessary
7d980415 501 if (!database_column_exists($dbh, 'LocalStat', 'PregreetCount')) {
e17403ff
DM
502 $dbh->do("ALTER TABLE LocalStat ADD COLUMN " .
503 "PregreetCount INTEGER DEFAULT 0 NOT NULL");
7d980415 504 }
70aafbae 505
ac28e80e
DM
506 eval { $dbh->do("ALTER TABLE LocalStat DROP CONSTRAINT localstat_time_key"); };
507 # ignore errors here
508
509
f2eb5f90
DM
510 # add missing TicketID to CMSReceivers
511 if (!database_column_exists($dbh, 'CMSReceivers', 'TicketID')) {
512 eval {
513 $dbh->begin_work;
514 $dbh->do("CREATE SEQUENCE cmsreceivers_ticketid_seq");
515 $dbh->do("ALTER TABLE CMSReceivers ADD COLUMN " .
516 "TicketID INTEGER NOT NULL " .
517 "DEFAULT nextval('cmsreceivers_ticketid_seq')");
518 $dbh->do("ALTER TABLE CMSReceivers ALTER COLUMN " .
519 "TicketID DROP DEFAULT");
520 $dbh->do("DROP SEQUENCE cmsreceivers_ticketid_seq");
521 $dbh->commit;
522 };
523 if (my $err = $@) {
524 $dbh->rollback;
525 die $err;
526 }
527 }
528
0a580593
DM
529 # update obsolete content type names
530 eval {
e7c865af
DM
531 $dbh->do("UPDATE Object " .
532 "SET value = 'content-type:application/java-vm' ".
533 "WHERE objecttype = 3003 " .
534 "AND value = 'content-type:application/x-java-vm';");
0a580593
DM
535 };
536
97cab76a
DM
537 foreach my $table (keys %$tables) {
538 eval { $dbh->do("ANALYZE $table"); };
539 warn $@ if $@;
540 }
79bc9b56
DM
541
542 reload_ruledb();
0a580593
DM
543}
544
545sub init_ruledb {
546 my ($ruledb, $reset, $testmode) = @_;
547
548 my $dbh = $ruledb->{dbh};
549
550 if (!$reset) {
551 # Greylist Objectgroup
552 my $greylistgroup = PMG::RuleDB::Group->new
553 ("GreyExclusion", "-", "greylist");
554 $ruledb->save_group ($greylistgroup);
555
556 } else {
557 # we do not touch greylist objects
558 my $glids = "SELECT object.ID FROM Object, Objectgroup WHERE " .
559 "objectgroup_id = objectgroup.id and class = 'greylist'";
560
561 $dbh->do ("DELETE FROM Rule; " .
562 "DELETE FROM RuleGroup; " .
563 "DELETE FROM Attribut WHERE Object_ID NOT IN ($glids); " .
564 "DELETE FROM Object WHERE ID NOT IN ($glids); " .
565 "DELETE FROM Objectgroup WHERE class != 'greylist';");
566 }
567
568 # WHO Objects
569
570 # Blacklist
571 my $obj = PMG::RuleDB::EMail->new ('nomail@fromthisdomain.com');
572 my $blacklist = $ruledb->create_group_with_obj(
573 $obj, 'Blacklist', 'Global blacklist');
758c7b6b 574
0a580593
DM
575 # Whitelist
576 $obj = PMG::RuleDB::EMail->new('mail@fromthisdomain.com');
577 my $whitelist = $ruledb->create_group_with_obj(
578 $obj, 'Whitelist', 'Global whitelist');
579
580 # WHEN Objects
581
582 # Working hours
a29b9649 583 $obj = PMG::RuleDB::TimeFrame->new(8*60, 16*60);
0a580593
DM
584 my $working_hours =$ruledb->create_group_with_obj($obj, 'Office Hours' ,
585 'Usual office hours');
586
587 # WHAT Objects
588
0a580593 589 # Images
a29b9649
DM
590 $obj = PMG::RuleDB::ContentTypeFilter->new('image/.*');
591 my $img_content = $ruledb->create_group_with_obj(
592 $obj, 'Images', 'All kinds of graphic files');
593
0a580593 594 # Multimedia
a29b9649
DM
595 $obj = PMG::RuleDB::ContentTypeFilter->new('audio/.*');
596 my $mm_content = $ruledb->create_group_with_obj(
597 $obj, 'Multimedia', 'Audio and Video');
0a580593 598
a29b9649
DM
599 $obj = PMG::RuleDB::ContentTypeFilter->new('video/.*');
600 $ruledb->group_add_object($mm_content, $obj);
0a580593
DM
601
602 # Office Files
a29b9649
DM
603 $obj = PMG::RuleDB::ContentTypeFilter->new('application/vnd\.ms-excel');
604 my $office_content = $ruledb->create_group_with_obj(
605 $obj, 'Office Files', 'Common Office Files');
758c7b6b 606
a29b9649
DM
607 $obj = PMG::RuleDB::ContentTypeFilter->new(
608 'application/vnd\.ms-powerpoint');
758c7b6b 609
a29b9649 610 $ruledb->group_add_object($office_content, $obj);
758c7b6b 611
a29b9649 612 $obj = PMG::RuleDB::ContentTypeFilter->new('application/msword');
0a580593 613 $ruledb->group_add_object ($office_content, $obj);
758c7b6b 614
a29b9649
DM
615 $obj = PMG::RuleDB::ContentTypeFilter->new(
616 'application/vnd\.openxmlformats-officedocument\..*');
617 $ruledb->group_add_object($office_content, $obj);
758c7b6b 618
a29b9649
DM
619 $obj = PMG::RuleDB::ContentTypeFilter->new(
620 'application/vnd\.oasis\.opendocument\..*');
621 $ruledb->group_add_object($office_content, $obj);
0a580593 622
a29b9649
DM
623 $obj = PMG::RuleDB::ContentTypeFilter->new(
624 'application/vnd\.stardivision\..*');
625 $ruledb->group_add_object($office_content, $obj);
758c7b6b 626
a29b9649
DM
627 $obj = PMG::RuleDB::ContentTypeFilter->new(
628 'application/vnd\.sun\.xml\..*');
629 $ruledb->group_add_object($office_content, $obj);
758c7b6b 630
0a580593 631 # Dangerous Content
a29b9649
DM
632 $obj = PMG::RuleDB::ContentTypeFilter->new(
633 'application/x-ms-dos-executable');
634 my $exe_content = $ruledb->create_group_with_obj(
635 $obj, 'Dangerous Content', 'executable files and partial messages');
758c7b6b 636
a29b9649
DM
637 $obj = PMG::RuleDB::ContentTypeFilter->new('application/x-java');
638 $ruledb->group_add_object($exe_content, $obj);
639 $obj = PMG::RuleDB::ContentTypeFilter->new('application/javascript');
640 $ruledb->group_add_object($exe_content, $obj);
641 $obj = PMG::RuleDB::ContentTypeFilter->new('application/x-executable');
642 $ruledb->group_add_object($exe_content, $obj);
79ef2452
DM
643 $obj = PMG::RuleDB::ContentTypeFilter->new('application/x-ms-dos-executable');
644 $ruledb->group_add_object($exe_content, $obj);
a29b9649
DM
645 $obj = PMG::RuleDB::ContentTypeFilter->new('message/partial');
646 $ruledb->group_add_object($exe_content, $obj);
647 $obj = PMG::RuleDB::MatchFilename->new('.*\.(vbs|pif|lnk|shs|shb)');
648 $ruledb->group_add_object($exe_content, $obj);
be6c5e4a 649 $obj = PMG::RuleDB::MatchFilename->new('.*\.\{.+\}');
a29b9649 650 $ruledb->group_add_object($exe_content, $obj);
0a580593
DM
651
652 # Virus
758c7b6b
DM
653 $obj = PMG::RuleDB::Virus->new();
654 my $virus = $ruledb->create_group_with_obj(
655 $obj, 'Virus', 'Matches virus infected mail');
656
0a580593
DM
657 # WHAT Objects
658
659 # Spam
758c7b6b
DM
660 $obj = PMG::RuleDB::Spam->new(3);
661 my $spam3 = $ruledb->create_group_with_obj(
662 $obj, 'Spam (Level 3)', 'Matches possible spam mail');
e7c865af 663
758c7b6b
DM
664 $obj = PMG::RuleDB::Spam->new(5);
665 my $spam5 = $ruledb->create_group_with_obj(
666 $obj, 'Spam (Level 5)', 'Matches possible spam mail');
e7c865af 667
758c7b6b
DM
668 $obj = PMG::RuleDB::Spam->new(10);
669 my $spam10 = $ruledb->create_group_with_obj(
670 $obj, 'Spam (Level 10)', 'Matches possible spam mail');
0a580593
DM
671
672 # ACTIONS
673
758c7b6b
DM
674 # Mark Spam
675 $obj = PMG::RuleDB::ModField->new('X-SPAM-LEVEL', '__SPAM_INFO__');
676 my $mod_spam_level = $ruledb->create_group_with_obj(
e7c865af 677 $obj, 'Modify Spam Level',
758c7b6b 678 'Mark mail as spam by adding a header tag.');
0a580593
DM
679
680 # Mark Spam
758c7b6b
DM
681 $obj = PMG::RuleDB::ModField->new('subject', 'SPAM: __SUBJECT__');
682 my $mod_spam_subject = $ruledb->create_group_with_obj(
e7c865af 683 $obj, 'Modify Spam Subject',
758c7b6b 684 'Mark mail as spam by modifying the subject.');
e7c865af 685
0a580593 686 # Remove matching attachments
758c7b6b
DM
687 $obj = PMG::RuleDB::Remove->new(0);
688 my $remove = $ruledb->create_group_with_obj(
689 $obj, 'Remove attachments', 'Remove matching attachments');
e7c865af 690
0a580593 691 # Remove all attachments
758c7b6b
DM
692 $obj = PMG::RuleDB::Remove->new(1);
693 my $remove_all = $ruledb->create_group_with_obj(
694 $obj, 'Remove all attachments', 'Remove all attachments');
0a580593
DM
695
696 # Accept
758c7b6b
DM
697 $obj = PMG::RuleDB::Accept->new();
698 my $accept = $ruledb->create_group_with_obj(
699 $obj, 'Accept', 'Accept mail for Delivery');
0a580593
DM
700
701 # Block
758c7b6b
DM
702 $obj = PMG::RuleDB::Block->new ();
703 my $block = $ruledb->create_group_with_obj($obj, 'Block', 'Block mail');
0a580593
DM
704
705 # Quarantine
758c7b6b
DM
706 $obj = PMG::RuleDB::Quarantine->new();
707 my $quarantine = $ruledb->create_group_with_obj(
708 $obj, 'Quarantine', 'Move mail to quarantine');
0a580593
DM
709
710 # Notify Admin
758c7b6b
DM
711 $obj = PMG::RuleDB::Notify->new('__ADMIN__');
712 my $notify_admin = $ruledb->create_group_with_obj(
713 $obj, 'Notify Admin', 'Send notification');
0a580593
DM
714
715 # Notify Sender
758c7b6b
DM
716 $obj = PMG::RuleDB::Notify->new('__SENDER__');
717 my $notify_sender = $ruledb->create_group_with_obj(
718 $obj, 'Notify Sender', 'Send notification');
0a580593
DM
719
720 # Add Disclaimer
758c7b6b
DM
721 $obj = PMG::RuleDB::Disclaimer->new ();
722 my $add_discl = $ruledb->create_group_with_obj(
723 $obj, 'Disclaimer', 'Add Disclaimer');
0a580593
DM
724
725 # Attach original mail
726 #$obj = Proxmox::RuleDB::Attach->new ();
758c7b6b 727 #my $attach_orig = $ruledb->create_group_with_obj ($obj, 'Attach Original Mail',
0a580593
DM
728 # 'Attach Original Mail');
729
730 ####################### RULES ##################################
731
732 ## Block Dangerous Files
a29b9649 733 my $rule = PMG::RuleDB::Rule->new ('Block Dangerous Files', 93, 1, 0);
0a580593
DM
734 $ruledb->save_rule ($rule);
735
736 $ruledb->rule_add_what_group ($rule, $exe_content);
737 $ruledb->rule_add_action ($rule, $remove);
738
739 ## Block Viruses
a29b9649 740 $rule = PMG::RuleDB::Rule->new ('Block Viruses', 96, 1, 0);
0a580593
DM
741 $ruledb->save_rule ($rule);
742
743 $ruledb->rule_add_what_group ($rule, $virus);
744 $ruledb->rule_add_action ($rule, $notify_admin);
758c7b6b 745
0a580593
DM
746 if ($testmode) {
747 $ruledb->rule_add_action ($rule, $block);
748 } else {
749 $ruledb->rule_add_action ($rule, $quarantine);
750 }
751
752 ## Virus Alert
a29b9649 753 $rule = PMG::RuleDB::Rule->new ('Virus Alert', 96, 1, 1);
0a580593
DM
754 $ruledb->save_rule ($rule);
755
756 $ruledb->rule_add_what_group ($rule, $virus);
757 $ruledb->rule_add_action ($rule, $notify_sender);
758 $ruledb->rule_add_action ($rule, $notify_admin);
759 $ruledb->rule_add_action ($rule, $block);
758c7b6b 760
0a580593 761 ## Blacklist
a29b9649 762 $rule = PMG::RuleDB::Rule->new ('Blacklist', 98, 1, 0);
0a580593
DM
763 $ruledb->save_rule ($rule);
764
765 $ruledb->rule_add_from_group ($rule, $blacklist);
766 $ruledb->rule_add_action ($rule, $block);
767
768 ## Modify header
769 if (!$testmode) {
a29b9649 770 $rule = PMG::RuleDB::Rule->new ('Modify Header', 90, 1, 0);
0a580593
DM
771 $ruledb->save_rule ($rule);
772 $ruledb->rule_add_action ($rule, $mod_spam_level);
773 }
774
775 ## Whitelist
a29b9649 776 $rule = PMG::RuleDB::Rule->new ('Whitelist', 85, 1, 0);
0a580593
DM
777 $ruledb->save_rule ($rule);
778
779 $ruledb->rule_add_from_group ($rule, $whitelist);
780 $ruledb->rule_add_action ($rule, $accept);
781
782 if ($testmode) {
a29b9649 783 $rule = PMG::RuleDB::Rule->new ('Mark Spam', 80, 1, 0);
0a580593
DM
784 $ruledb->save_rule ($rule);
785
786 $ruledb->rule_add_what_group ($rule, $spam10);
787 $ruledb->rule_add_action ($rule, $mod_spam_level);
788 $ruledb->rule_add_action ($rule, $mod_spam_subject);
789 } else {
790 # Quarantine/Mark Spam (Level 3)
a29b9649 791 $rule = PMG::RuleDB::Rule->new ('Quarantine/Mark Spam (Level 3)', 80, 1, 0);
0a580593
DM
792 $ruledb->save_rule ($rule);
793
794 $ruledb->rule_add_what_group ($rule, $spam3);
795 $ruledb->rule_add_action ($rule, $mod_spam_subject);
796 $ruledb->rule_add_action ($rule, $quarantine);
797 #$ruledb->rule_add_action ($rule, $count_spam);
798 }
799
800 # Quarantine/Mark Spam (Level 5)
1a3a9999 801 $rule = PMG::RuleDB::Rule->new ('Quarantine/Mark Spam (Level 5)', 81, 0, 0);
0a580593
DM
802 $ruledb->save_rule ($rule);
803
804 $ruledb->rule_add_what_group ($rule, $spam5);
805 $ruledb->rule_add_action ($rule, $mod_spam_subject);
806 $ruledb->rule_add_action ($rule, $quarantine);
807
808 ## Block Spam Level 10
1a3a9999 809 $rule = PMG::RuleDB::Rule->new ('Block Spam (Level 10)', 82, 0, 0);
0a580593
DM
810 $ruledb->save_rule ($rule);
811
812 $ruledb->rule_add_what_group ($rule, $spam10);
813 $ruledb->rule_add_action ($rule, $block);
814
815 ## Block Outgoing Spam
a29b9649 816 $rule = PMG::RuleDB::Rule->new ('Block outgoing Spam', 70, 0, 1);
0a580593
DM
817 $ruledb->save_rule ($rule);
818
819 $ruledb->rule_add_what_group ($rule, $spam3);
820 $ruledb->rule_add_action ($rule, $notify_admin);
821 $ruledb->rule_add_action ($rule, $notify_sender);
822 $ruledb->rule_add_action ($rule, $block);
823
824 ## Add disclaimer
a29b9649 825 $rule = PMG::RuleDB::Rule->new ('Add Disclaimer', 60, 0, 1);
0a580593
DM
826 $ruledb->save_rule ($rule);
827 $ruledb->rule_add_action ($rule, $add_discl);
828
829 # Block Multimedia Files
a29b9649 830 $rule = PMG::RuleDB::Rule->new ('Block Multimedia Files', 87, 0, 2);
0a580593
DM
831 $ruledb->save_rule ($rule);
832
833 $ruledb->rule_add_what_group ($rule, $mm_content);
834 $ruledb->rule_add_action ($rule, $remove);
835
836 #$ruledb->rule_add_from_group ($rule, $anybody);
837 #$ruledb->rule_add_from_group ($rule, $trusted);
838 #$ruledb->rule_add_to_group ($rule, $anybody);
839 #$ruledb->rule_add_what_group ($rule, $ct_filter);
840 #$ruledb->rule_add_action ($rule, $add_discl);
841 #$ruledb->rule_add_action ($rule, $remove);
842 #$ruledb->rule_add_action ($rule, $bcc);
843 #$ruledb->rule_add_action ($rule, $storeq);
844 #$ruledb->rule_add_action ($rule, $accept);
845
846 cond_create_std_actions ($ruledb);
8c002247
DM
847
848 reload_ruledb();
0a580593
DM
849}
850
8f4e102b
DM
851sub get_remote_time {
852 my ($rdb) = @_;
853
854 my $sth = $rdb->prepare("SELECT EXTRACT (EPOCH FROM TIMESTAMP (0) WITH TIME ZONE 'now') as ctime;");
855 $sth->execute();
856 my $ctinfo = $sth->fetchrow_hashref();
857 $sth->finish ();
858
859 return $ctinfo ? $ctinfo->{ctime} : 0;
860}
861
cfdf6608
DM
862sub init_masterdb {
863 my ($lcid, $database) = @_;
864
865 die "got unexpected cid for new master" if !$lcid;
866
867 my $dbh;
868
869 eval {
870 $dbh = open_ruledb($database);
871
872 $dbh->begin_work;
873
874 print STDERR "update quarantine database\n";
875 $dbh->do ("UPDATE CMailStore SET CID = $lcid WHERE CID = 0;" .
876 "UPDATE CMSReceivers SET CMailStore_CID = $lcid WHERE CMailStore_CID = 0;");
877
878 print STDERR "update statistic database\n";
879 $dbh->do ("UPDATE CStatistic SET CID = $lcid WHERE CID = 0;" .
880 "UPDATE CReceivers SET CStatistic_CID = $lcid WHERE CStatistic_CID = 0;");
881
882 print STDERR "update greylist database\n";
883 $dbh->do ("UPDATE CGreylist SET CID = $lcid WHERE CID = 0;");
884
5e1408fd
DM
885 print STDERR "update localstat database\n";
886 $dbh->do ("UPDATE LocalStat SET CID = $lcid WHERE CID = 0;");
887
cfdf6608
DM
888 $dbh->commit;
889 };
890 my $err = $@;
891
892 if ($dbh) {
893 $dbh->rollback if $err;
894 $dbh->disconnect();
895 }
896
897 die $err if $err;
898}
899
8fb6f404
DM
900sub purge_statistic_database {
901 my ($dbh, $statlifetime) = @_;
902
903 return if $statlifetime <= 0;
904
905 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
906 my $end = timelocal(0, 0, 0, $mday, $mon, $year);
907 my $start = $end - $statlifetime*86400;
908
909 # delete statistics older than $start
910
911 my $rows = 0;
912
913 eval {
914 $dbh->begin_work;
915
916 my $sth = $dbh->prepare("DELETE FROM CStatistic WHERE time < $start");
917 $sth->execute;
918 $rows = $sth->rows;
919 $sth->finish;
920
921 if ($rows > 0) {
922 $sth = $dbh->prepare(
923 "DELETE FROM CReceivers WHERE NOT EXISTS " .
924 "(SELECT * FROM CStatistic WHERE CID = CStatistic_CID AND RID = CStatistic_RID)");
925
926 $sth->execute;
927 }
928 $dbh->commit;
929 };
930 if (my $err = $@) {
931 $dbh->rollback;
932 die $err;
933 }
934
935 return $rows;
936}
937
1af3c560
DM
938sub purge_quarantine_database {
939 my ($dbh, $qtype, $lifetime) = @_;
940
941 my $spooldir = $PMG::MailQueue::spooldir;
942
943 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
944 my $end = timelocal(0, 0, 0, $mday, $mon, $year);
945 my $start = $end - $lifetime*86400;
946
947 my $sth = $dbh->prepare(
948 "SELECT file FROM CMailStore WHERE time < $start AND QType = '$qtype'");
949
950 $sth->execute();
951
b8b8c31b
DM
952 my $count = 0;
953
1af3c560
DM
954 while (my $ref = $sth->fetchrow_hashref()) {
955 my $filename = "$spooldir/$ref->{file}";
b8b8c31b 956 $count++ if unlink($filename);
1af3c560
DM
957 }
958
959 $sth->finish();
960
961 $dbh->do(
962 "DELETE FROM CMailStore WHERE time < $start AND QType = '$qtype';" .
963 "DELETE FROM CMSReceivers WHERE NOT EXISTS " .
964 "(SELECT * FROM CMailStore WHERE CID = CMailStore_CID AND RID = CMailStore_RID)");
b8b8c31b
DM
965
966 return $count;
1af3c560
DM
967}
968
8af3f74c
DM
969sub get_quarantine_count {
970 my ($dbh, $qtype) = @_;
971
972 # Note;: We try to estimate used disk space - each mail
973 # is stored in an extra file ...
974
975 my $bs = 4096;
976
977 my $sth = $dbh->prepare(
978 "SELECT count(ID) as count, sum (ceil((Bytes+$bs-1)/$bs)*$bs) / (1024*1024) as mbytes, " .
979 "avg(Bytes) as avgbytes, avg(Spamlevel) as avgspam " .
980 "FROM CMailStore WHERE QType = ?");
981
982 $sth->execute($qtype);
983
984 my $ref = $sth->fetchrow_hashref();
985
986 $sth->finish;
987
13a3f36c
DM
988 foreach my $k (qw(count mbytes avgbytes avgspam)) {
989 $ref->{$k} //= 0;
990 }
991
8af3f74c
DM
992 return $ref;
993}
994
da686811
DM
995sub copy_table {
996 my ($ldb, $rdb, $table) = @_;
997
998 $table = lc($table);
999
1000 my $sth = $ldb->column_info(undef, undef, $table, undef);
1001 my $attrs = $sth->fetchall_arrayref({});
1002
1003 my @col_arr;
1004 foreach my $ref (@$attrs) {
1005 push @col_arr, $ref->{COLUMN_NAME};
1006 }
1007
1008 $sth->finish();
1009
1010 my $cols = join(', ', @col_arr);
1011 $cols || die "unable to fetch column definitions of table '$table' : ERROR";
1012
1013 $rdb->do("COPY $table ($cols) TO STDOUT");
1014
1015 my $data = '';
1016
1017 eval {
1018 $ldb->do("COPY $table ($cols) FROM stdin");
1019
1020 while ($rdb->pg_getcopydata($data) >= 0) {
1021 $ldb->pg_putcopydata($data);
1022 }
1023
1024 $ldb->pg_putcopyend();
1025 };
1026 if (my $err = $@) {
1027 $ldb->pg_putcopyend();
1028 die $err;
1029 }
1030}
1031
6cf5b4aa
DM
1032sub copy_selected_data {
1033 my ($dbh, $select_sth, $table, $attrs, $callback) = @_;
1034
1035 my $count = 0;
1036
1037 my $insert_sth = $dbh->prepare(
c2e5905e 1038 "INSERT INTO ${table}(" . join(',', @$attrs) . ') ' .
6cf5b4aa
DM
1039 'VALUES (' . join(',', ('?') x scalar(@$attrs)) . ')');
1040
1041 while (my $ref = $select_sth->fetchrow_hashref()) {
1042 $callback->($ref) if $callback;
1043 $count++;
1044 $insert_sth->execute(map { $ref->{$_} } @$attrs);
1045 }
1046
1047 return $count;
1048}
1049
987ee804
DM
1050sub update_master_clusterinfo {
1051 my ($clientcid) = @_;
1052
1053 my $dbh = open_ruledb();
1054
1055 $dbh->do("DELETE FROM ClusterInfo WHERE CID = $clientcid");
1056
5e1408fd 1057 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
987ee804
DM
1058
1059 foreach my $table (@mt) {
1060 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $clientcid, 'lastmt_$table', " .
1061 "EXTRACT(EPOCH FROM now())");
1062 }
1063}
1064
1065sub update_client_clusterinfo {
1066 my ($mastercid) = @_;
1067
1068 my $dbh = open_ruledb();
1069
1070 $dbh->do ("DELETE FROM StatInfo"); # not needed at node
1071
1072 $dbh->do ("DELETE FROM ClusterInfo WHERE CID = $mastercid");
1073
1074 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CMailStore', " .
1075 "COALESCE (max (rid), -1) FROM CMailStore WHERE cid = $mastercid");
1076
1077 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CStatistic', " .
1078 "COALESCE (max (rid), -1) FROM CStatistic WHERE cid = $mastercid");
1079
5e1408fd 1080 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
987ee804
DM
1081
1082 foreach my $table (@mt) {
1083 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastmt_$table', " .
1084 "COALESCE (max (mtime), 0) FROM $table");
1085 }
1086}
1087
7b313034
DM
1088sub create_clusterinfo_default {
1089 my ($dbh, $rcid, $name, $ivalue, $svalue) = @_;
1090
1091 my $sth = $dbh->prepare("SELECT * FROM ClusterInfo WHERE CID = ? AND Name = ?");
1092 $sth->execute($rcid, $name);
1093 if (!$sth->fetchrow_hashref()) {
1094 $dbh->do("INSERT INTO ClusterInfo (CID, Name, IValue, SValue) " .
1095 "VALUES (?, ?, ?, ?)", undef,
1096 $rcid, $name, $ivalue, $svalue);
1097 }
1098 $sth->finish();
1099}
1100
1101sub read_int_clusterinfo {
1102 my ($dbh, $rcid, $name) = @_;
1103
1104 my $sth = $dbh->prepare(
1105 "SELECT ivalue as value FROM ClusterInfo " .
1106 "WHERE cid = ? AND NAME = ?");
1107 $sth->execute($rcid, $name);
1108 my $cinfo = $sth->fetchrow_hashref();
1109 $sth->finish();
1110
1111 return $cinfo->{value};
1112}
1113
1114sub write_maxint_clusterinfo {
1115 my ($dbh, $rcid, $name, $value) = @_;
1116
2e049252 1117 $dbh->do("UPDATE ClusterInfo SET ivalue = GREATEST(ivalue, ?) " .
7b313034
DM
1118 "WHERE cid = ? AND name = ?", undef,
1119 $value, $rcid, $name);
1120}
1121
987ee804
DM
1122sub init_nodedb {
1123 my ($cinfo) = @_;
1124
1125 my $ni = $cinfo->{master};
1126
1127 die "no master defined - unable to sync data from master\n" if !$ni;
1128
1129 my $master_ip = $ni->{ip};
1130 my $master_cid = $ni->{cid};
22c16ff6 1131 my $master_name = $ni->{name};
987ee804
DM
1132
1133 my $fn = "/tmp/masterdb$$.tar";
1134 unlink $fn;
1135
1136 my $dbname = $default_db_name;
1137
1138 eval {
1139 print STDERR "copying master database from '${master_ip}'\n";
1140
97cab76a
DM
1141 open (my $fh, ">", $fn) || die "open '$fn' failed - $!\n";
1142
b8c0c87a
DM
1143 my $cmd = ['/usr/bin/ssh', '-o', 'BatchMode=yes',
1144 '-o', "HostKeyAlias=${master_name}", $master_ip,
1145 'pg_dump', $dbname, '-F', 'c' ];
1146
1147 PVE::Tools::run_command($cmd, output => '>&' . fileno($fh));
22c16ff6 1148
97cab76a 1149 close($fh);
987ee804
DM
1150
1151 my $size = -s $fn;
1152
1153 print STDERR "copying master database finished (got $size bytes)\n";
1154
1155 print STDERR "delete local database\n";
1156
97cab76a 1157 postgres_admin_cmd('dropdb', undef, $dbname , '--if-exists');
987ee804
DM
1158
1159 print STDERR "create new local database\n";
1160
2cc31eaa 1161 $createdb->($dbname);
987ee804
DM
1162
1163 print STDERR "insert received data into local database\n";
1164
1165 my $mess;
1166 my $parser = sub {
1167 my $line = shift;
1168
1169 if ($line =~ m/restoring data for table \"(.+)\"/) {
1170 print STDERR "restoring table $1\n";
1171 } elsif (!$mess && ($line =~ m/creating (INDEX|CONSTRAINT)/)) {
1172 $mess = "creating indexes";
1173 print STDERR "$mess\n";
1174 }
1175 };
1176
97cab76a
DM
1177 my $opts = {
1178 outfunc => $parser,
1179 errfunc => $parser,
1180 errmsg => "pg_restore failed"
1181 };
1182
1183 postgres_admin_cmd('pg_restore', $opts, '-d', $dbname, '-v', $fn);
987ee804
DM
1184
1185 print STDERR "run analyze to speed up database queries\n";
1186
97cab76a 1187 postgres_admin_cmd('psql', { input => 'analyze;' }, $dbname);
987ee804
DM
1188
1189 update_client_clusterinfo($master_cid);
1190 };
1191
1192 my $err = $@;
1193
1194 unlink $fn;
1195
1196 die $err if $err;
1197}
1198
f3464b71
DM
1199sub cluster_sync_status {
1200 my ($cinfo) = @_;
1201
1202 my $dbh;
1203
1204 my $minmtime;
1205
1206 foreach my $ni (values %{$cinfo->{ids}}) {
1207 next if $cinfo->{local}->{cid} == $ni->{cid}; # skip local CID
1208 $minmtime->{$ni->{cid}} = 0;
1209 }
1210
1211 eval {
1212 $dbh = open_ruledb();
1213
1214 my $sth = $dbh->prepare(
1215 "SELECT cid, MIN (ivalue) as minmtime FROM ClusterInfo " .
1216 "WHERE name = 'lastsync' AND ivalue > 0 " .
1217 "GROUP BY cid");
1218
1219 $sth->execute();
1220
1221 while (my $info = $sth->fetchrow_hashref()) {
1222 foreach my $ni (values %{$cinfo->{ids}}) {
1223 next if $cinfo->{local}->{cid} == $ni->{cid}; # skip local CID
1224 if ($ni->{cid} == $info->{cid}) { # node exists
1225 $minmtime->{$ni->{cid}} = $info->{minmtime};
1226 }
1227 }
1228 }
1229
2ab22e49 1230 $sth->finish();
f3464b71
DM
1231 };
1232 my $err = $@;
1233
1234 $dbh->disconnect() if $dbh;
1235
b902c0b8 1236 syslog('err', $err) if $err;
f3464b71
DM
1237
1238 return $minmtime;
1239}
1240
2ab22e49 1241sub load_mail_data {
666b5e8f 1242 my ($dbh, $cid, $rid, $ticketid) = @_;
2ab22e49
DM
1243
1244 my $sth = $dbh->prepare(
1245 "SELECT * FROM CMailStore, CMSReceivers WHERE " .
666b5e8f 1246 "CID = ? AND RID = ? AND TicketID = ? AND " .
2ab22e49 1247 "CID = CMailStore_CID AND RID = CMailStore_RID");
666b5e8f 1248 $sth->execute($cid, $rid, $ticketid);
2ab22e49
DM
1249
1250 my $res = $sth->fetchrow_hashref();
1251
1252 $sth->finish();
1253
666b5e8f 1254 die "no such mail (C${cid}R${rid}T${ticketid})\n" if !defined($res);
2ab22e49
DM
1255
1256 return $res;
1257}
1258
cbef3ff8 1259sub reload_ruledb {
f9967a49
DM
1260 my ($ruledb) = @_;
1261
1262 # Note: we pass $ruledb when modifying SMTP whitelist
1263 if (defined($ruledb)) {
1264 eval {
1265 my $rulecache = PMG::RuleCache->new($ruledb);
1266 PMG::Config::rewrite_postfix_whitelist($rulecache);
1267 };
1268 if (my $err = $@) {
1269 warn "problems updating SMTP whitelist - $err";
1270 }
1271 }
1272
cbef3ff8
DM
1273 my $pid_file = '/var/run/pmg-smtp-filter.pid';
1274 my $pid = PVE::Tools::file_read_firstline($pid_file);
1275
1276 return 0 if !$pid;
1277
14e192ba
DM
1278 return 0 if $pid !~ m/^(\d+)$/;
1279 $pid = $1; # untaint
1280
cbef3ff8
DM
1281 return kill (10, $pid); # send SIGUSR1
1282}
1283
a355f100 12841;