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