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