]> git.proxmox.com Git - pmg-api.git/blame - PMG/DBTools.pm
PMG/API2/MyNetworks.pm: fix links attribute
[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
149 (Time INTEGER NOT NULL UNIQUE,
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
f1bf78ff
DM
344sub create_ruledb {
345 my ($dbname) = @_;
346
987ee804 347 $dbname = $default_db_name if !$dbname;
f1bf78ff 348
97cab76a
DM
349 my $silent_opts = { outfunc => sub {}, errfunc => sub {} };
350 # make sure we have user 'root'
351 eval { postgres_admin_cmd('createuser', $silent_opts, '-D', 'root'); };
5475e159
DM
352 # also create 'www-data' (and give it read-only access below)
353 eval { postgres_admin_cmd('createuser', $silent_opts, '-I', '-D', 'www-data'); };
97cab76a 354
f1bf78ff
DM
355 # use sql_ascii to avoid any character set conversions, and be compatible with
356 # older postgres versions (update from 8.1 must be possible)
f1bf78ff 357
97cab76a
DM
358 postgres_admin_cmd('createdb', undef, '-E', 'sql_ascii', '-T', 'template0',
359 '--lc-collate=C', '--lc-ctype=C', $dbname);
f1bf78ff
DM
360
361 my $dbh = open_ruledb($dbname);
362
5475e159
DM
363 # make sure 'www-data' can read all tables
364 $dbh->do("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO \"www-data\"");
365
f1bf78ff
DM
366 $dbh->do (
367<<EOD
368 CREATE TABLE Attribut
369 (Object_ID INTEGER NOT NULL,
370 Name VARCHAR(20) NOT NULL,
371 Value BYTEA NULL,
372 PRIMARY KEY (Object_ID, Name));
373
374 CREATE INDEX Attribut_Object_ID_Index ON Attribut(Object_ID);
375
376 CREATE TABLE Object
377 (ID SERIAL UNIQUE,
378 ObjectType INTEGER NOT NULL,
379 Objectgroup_ID INTEGER NOT NULL,
380 Value BYTEA NULL,
381 PRIMARY KEY (ID));
382
383 CREATE TABLE Objectgroup
384 (ID SERIAL UNIQUE,
385 Name VARCHAR(255) NOT NULL,
386 Info VARCHAR(255) NULL,
387 Class VARCHAR(10) NOT NULL,
388 PRIMARY KEY (ID));
389
390 CREATE TABLE Rule
391 (ID SERIAL UNIQUE,
392 Name VARCHAR(255) NULL,
393 Priority INTEGER NOT NULL,
394 Active INTEGER NOT NULL DEFAULT 0,
395 Direction INTEGER NOT NULL DEFAULT 2,
396 Count INTEGER NOT NULL DEFAULT 0,
397 PRIMARY KEY (ID));
398
399 CREATE TABLE RuleGroup
400 (Objectgroup_ID INTEGER NOT NULL,
401 Rule_ID INTEGER NOT NULL,
402 Grouptype INTEGER NOT NULL,
403 PRIMARY KEY (Objectgroup_ID, Rule_ID, Grouptype));
404
405 $cgreylist_ctablecmd;
406
407 $clusterinfo_ctablecmd;
408
5e1408fd
DM
409 $local_stat_ctablecmd;
410
f1bf78ff
DM
411 $daily_stat_ctablecmd;
412
413 $domain_stat_ctablecmd;
414
415 $statinfo_ctablecmd;
416
417 $cmailstore_ctablecmd;
418
419 $cstatistic_ctablecmd;
420
421 $userprefs_ctablecmd;
422
423 $virusinfo_stat_ctablecmd;
424EOD
425 );
426
427 return $dbh;
428}
429
0a580593
DM
430sub cond_create_action_quarantine {
431 my ($ruledb) = @_;
432
433 my $dbh = $ruledb->{dbh};
434
435 eval {
436 my $sth = $dbh->prepare(
437 "SELECT * FROM Objectgroup, Object " .
438 "WHERE Object.ObjectType = ? AND Objectgroup.Class = ? " .
439 "AND Object.objectgroup_id = Objectgroup.id");
440
441 my $otype = PMG::RuleDB::Quarantine::otype();
442 if ($sth->execute($otype, 'action') <= 0) {
443 my $obj = PMG::RuleDB::Quarantine->new ();
444 my $txt = decode_entities(PMG::RuleDB::Quarantine->otype_text);
445 my $quarantine = $ruledb->create_group_with_obj
9578dcd7 446 ($obj, $txt, 'Move to quarantine.');
0a580593
DM
447 }
448 };
449}
450
451sub cond_create_std_actions {
452 my ($ruledb) = @_;
453
454 cond_create_action_quarantine($ruledb);
455
456 #cond_create_action_report_spam($ruledb);
457}
458
459
460sub upgradedb {
461 my ($ruledb) = @_;
462
463 my $dbh = $ruledb->{dbh};
464
758c7b6b 465 # make sure we do not use slow sequential scans when upgraing
0a580593
DM
466 # database (before analyze can gather statistics)
467 $dbh->do("set enable_seqscan = false");
468
97cab76a 469 my $tables = {
5e1408fd
DM
470 'LocalStat', $local_stat_ctablecmd,
471 'DailyStat', $daily_stat_ctablecmd,
97cab76a
DM
472 'DomainStat', $domain_stat_ctablecmd,
473 'StatInfo', $statinfo_ctablecmd,
474 'CMailStore', $cmailstore_ctablecmd,
475 'UserPrefs', $userprefs_ctablecmd,
476 'CGreylist', $cgreylist_ctablecmd,
477 'CStatistic', $cstatistic_ctablecmd,
478 'ClusterInfo', $clusterinfo_ctablecmd,
479 'VirusInfo', $virusinfo_stat_ctablecmd,
480 };
481
482 foreach my $table (keys %$tables) {
7f2a3858 483 cond_create_dbtable($dbh, $table, $tables->{$table});
97cab76a 484 }
0a580593 485
e7c865af 486 cond_create_std_actions($ruledb);
0a580593 487
dc295201 488 # upgrade tables here if necessary
7d980415 489 if (!database_column_exists($dbh, 'LocalStat', 'PregreetCount')) {
e17403ff
DM
490 $dbh->do("ALTER TABLE LocalStat ADD COLUMN " .
491 "PregreetCount INTEGER DEFAULT 0 NOT NULL");
7d980415 492 }
70aafbae 493
f2eb5f90
DM
494 # add missing TicketID to CMSReceivers
495 if (!database_column_exists($dbh, 'CMSReceivers', 'TicketID')) {
496 eval {
497 $dbh->begin_work;
498 $dbh->do("CREATE SEQUENCE cmsreceivers_ticketid_seq");
499 $dbh->do("ALTER TABLE CMSReceivers ADD COLUMN " .
500 "TicketID INTEGER NOT NULL " .
501 "DEFAULT nextval('cmsreceivers_ticketid_seq')");
502 $dbh->do("ALTER TABLE CMSReceivers ALTER COLUMN " .
503 "TicketID DROP DEFAULT");
504 $dbh->do("DROP SEQUENCE cmsreceivers_ticketid_seq");
505 $dbh->commit;
506 };
507 if (my $err = $@) {
508 $dbh->rollback;
509 die $err;
510 }
511 }
512
0a580593
DM
513 # update obsolete content type names
514 eval {
e7c865af
DM
515 $dbh->do("UPDATE Object " .
516 "SET value = 'content-type:application/java-vm' ".
517 "WHERE objecttype = 3003 " .
518 "AND value = 'content-type:application/x-java-vm';");
0a580593
DM
519 };
520
97cab76a
DM
521 foreach my $table (keys %$tables) {
522 eval { $dbh->do("ANALYZE $table"); };
523 warn $@ if $@;
524 }
79bc9b56
DM
525
526 reload_ruledb();
0a580593
DM
527}
528
529sub init_ruledb {
530 my ($ruledb, $reset, $testmode) = @_;
531
532 my $dbh = $ruledb->{dbh};
533
534 if (!$reset) {
535 # Greylist Objectgroup
536 my $greylistgroup = PMG::RuleDB::Group->new
537 ("GreyExclusion", "-", "greylist");
538 $ruledb->save_group ($greylistgroup);
539
540 } else {
541 # we do not touch greylist objects
542 my $glids = "SELECT object.ID FROM Object, Objectgroup WHERE " .
543 "objectgroup_id = objectgroup.id and class = 'greylist'";
544
545 $dbh->do ("DELETE FROM Rule; " .
546 "DELETE FROM RuleGroup; " .
547 "DELETE FROM Attribut WHERE Object_ID NOT IN ($glids); " .
548 "DELETE FROM Object WHERE ID NOT IN ($glids); " .
549 "DELETE FROM Objectgroup WHERE class != 'greylist';");
550 }
551
552 # WHO Objects
553
554 # Blacklist
555 my $obj = PMG::RuleDB::EMail->new ('nomail@fromthisdomain.com');
556 my $blacklist = $ruledb->create_group_with_obj(
557 $obj, 'Blacklist', 'Global blacklist');
758c7b6b 558
0a580593
DM
559 # Whitelist
560 $obj = PMG::RuleDB::EMail->new('mail@fromthisdomain.com');
561 my $whitelist = $ruledb->create_group_with_obj(
562 $obj, 'Whitelist', 'Global whitelist');
563
564 # WHEN Objects
565
566 # Working hours
a29b9649 567 $obj = PMG::RuleDB::TimeFrame->new(8*60, 16*60);
0a580593
DM
568 my $working_hours =$ruledb->create_group_with_obj($obj, 'Office Hours' ,
569 'Usual office hours');
570
571 # WHAT Objects
572
0a580593 573 # Images
a29b9649
DM
574 $obj = PMG::RuleDB::ContentTypeFilter->new('image/.*');
575 my $img_content = $ruledb->create_group_with_obj(
576 $obj, 'Images', 'All kinds of graphic files');
577
0a580593 578 # Multimedia
a29b9649
DM
579 $obj = PMG::RuleDB::ContentTypeFilter->new('audio/.*');
580 my $mm_content = $ruledb->create_group_with_obj(
581 $obj, 'Multimedia', 'Audio and Video');
0a580593 582
a29b9649
DM
583 $obj = PMG::RuleDB::ContentTypeFilter->new('video/.*');
584 $ruledb->group_add_object($mm_content, $obj);
0a580593
DM
585
586 # Office Files
a29b9649
DM
587 $obj = PMG::RuleDB::ContentTypeFilter->new('application/vnd\.ms-excel');
588 my $office_content = $ruledb->create_group_with_obj(
589 $obj, 'Office Files', 'Common Office Files');
758c7b6b 590
a29b9649
DM
591 $obj = PMG::RuleDB::ContentTypeFilter->new(
592 'application/vnd\.ms-powerpoint');
758c7b6b 593
a29b9649 594 $ruledb->group_add_object($office_content, $obj);
758c7b6b 595
a29b9649 596 $obj = PMG::RuleDB::ContentTypeFilter->new('application/msword');
0a580593 597 $ruledb->group_add_object ($office_content, $obj);
758c7b6b 598
a29b9649
DM
599 $obj = PMG::RuleDB::ContentTypeFilter->new(
600 'application/vnd\.openxmlformats-officedocument\..*');
601 $ruledb->group_add_object($office_content, $obj);
758c7b6b 602
a29b9649
DM
603 $obj = PMG::RuleDB::ContentTypeFilter->new(
604 'application/vnd\.oasis\.opendocument\..*');
605 $ruledb->group_add_object($office_content, $obj);
0a580593 606
a29b9649
DM
607 $obj = PMG::RuleDB::ContentTypeFilter->new(
608 'application/vnd\.stardivision\..*');
609 $ruledb->group_add_object($office_content, $obj);
758c7b6b 610
a29b9649
DM
611 $obj = PMG::RuleDB::ContentTypeFilter->new(
612 'application/vnd\.sun\.xml\..*');
613 $ruledb->group_add_object($office_content, $obj);
758c7b6b 614
0a580593 615 # Dangerous Content
a29b9649
DM
616 $obj = PMG::RuleDB::ContentTypeFilter->new(
617 'application/x-ms-dos-executable');
618 my $exe_content = $ruledb->create_group_with_obj(
619 $obj, 'Dangerous Content', 'executable files and partial messages');
758c7b6b 620
a29b9649
DM
621 $obj = PMG::RuleDB::ContentTypeFilter->new('application/x-java');
622 $ruledb->group_add_object($exe_content, $obj);
623 $obj = PMG::RuleDB::ContentTypeFilter->new('application/javascript');
624 $ruledb->group_add_object($exe_content, $obj);
625 $obj = PMG::RuleDB::ContentTypeFilter->new('application/x-executable');
626 $ruledb->group_add_object($exe_content, $obj);
79ef2452
DM
627 $obj = PMG::RuleDB::ContentTypeFilter->new('application/x-ms-dos-executable');
628 $ruledb->group_add_object($exe_content, $obj);
a29b9649
DM
629 $obj = PMG::RuleDB::ContentTypeFilter->new('message/partial');
630 $ruledb->group_add_object($exe_content, $obj);
631 $obj = PMG::RuleDB::MatchFilename->new('.*\.(vbs|pif|lnk|shs|shb)');
632 $ruledb->group_add_object($exe_content, $obj);
be6c5e4a 633 $obj = PMG::RuleDB::MatchFilename->new('.*\.\{.+\}');
a29b9649 634 $ruledb->group_add_object($exe_content, $obj);
0a580593
DM
635
636 # Virus
758c7b6b
DM
637 $obj = PMG::RuleDB::Virus->new();
638 my $virus = $ruledb->create_group_with_obj(
639 $obj, 'Virus', 'Matches virus infected mail');
640
0a580593
DM
641 # WHAT Objects
642
643 # Spam
758c7b6b
DM
644 $obj = PMG::RuleDB::Spam->new(3);
645 my $spam3 = $ruledb->create_group_with_obj(
646 $obj, 'Spam (Level 3)', 'Matches possible spam mail');
e7c865af 647
758c7b6b
DM
648 $obj = PMG::RuleDB::Spam->new(5);
649 my $spam5 = $ruledb->create_group_with_obj(
650 $obj, 'Spam (Level 5)', 'Matches possible spam mail');
e7c865af 651
758c7b6b
DM
652 $obj = PMG::RuleDB::Spam->new(10);
653 my $spam10 = $ruledb->create_group_with_obj(
654 $obj, 'Spam (Level 10)', 'Matches possible spam mail');
0a580593
DM
655
656 # ACTIONS
657
758c7b6b
DM
658 # Mark Spam
659 $obj = PMG::RuleDB::ModField->new('X-SPAM-LEVEL', '__SPAM_INFO__');
660 my $mod_spam_level = $ruledb->create_group_with_obj(
e7c865af 661 $obj, 'Modify Spam Level',
758c7b6b 662 'Mark mail as spam by adding a header tag.');
0a580593
DM
663
664 # Mark Spam
758c7b6b
DM
665 $obj = PMG::RuleDB::ModField->new('subject', 'SPAM: __SUBJECT__');
666 my $mod_spam_subject = $ruledb->create_group_with_obj(
e7c865af 667 $obj, 'Modify Spam Subject',
758c7b6b 668 'Mark mail as spam by modifying the subject.');
e7c865af 669
0a580593 670 # Remove matching attachments
758c7b6b
DM
671 $obj = PMG::RuleDB::Remove->new(0);
672 my $remove = $ruledb->create_group_with_obj(
673 $obj, 'Remove attachments', 'Remove matching attachments');
e7c865af 674
0a580593 675 # Remove all attachments
758c7b6b
DM
676 $obj = PMG::RuleDB::Remove->new(1);
677 my $remove_all = $ruledb->create_group_with_obj(
678 $obj, 'Remove all attachments', 'Remove all attachments');
0a580593
DM
679
680 # Accept
758c7b6b
DM
681 $obj = PMG::RuleDB::Accept->new();
682 my $accept = $ruledb->create_group_with_obj(
683 $obj, 'Accept', 'Accept mail for Delivery');
0a580593
DM
684
685 # Block
758c7b6b
DM
686 $obj = PMG::RuleDB::Block->new ();
687 my $block = $ruledb->create_group_with_obj($obj, 'Block', 'Block mail');
0a580593
DM
688
689 # Quarantine
758c7b6b
DM
690 $obj = PMG::RuleDB::Quarantine->new();
691 my $quarantine = $ruledb->create_group_with_obj(
692 $obj, 'Quarantine', 'Move mail to quarantine');
0a580593
DM
693
694 # Notify Admin
758c7b6b
DM
695 $obj = PMG::RuleDB::Notify->new('__ADMIN__');
696 my $notify_admin = $ruledb->create_group_with_obj(
697 $obj, 'Notify Admin', 'Send notification');
0a580593
DM
698
699 # Notify Sender
758c7b6b
DM
700 $obj = PMG::RuleDB::Notify->new('__SENDER__');
701 my $notify_sender = $ruledb->create_group_with_obj(
702 $obj, 'Notify Sender', 'Send notification');
0a580593
DM
703
704 # Add Disclaimer
758c7b6b
DM
705 $obj = PMG::RuleDB::Disclaimer->new ();
706 my $add_discl = $ruledb->create_group_with_obj(
707 $obj, 'Disclaimer', 'Add Disclaimer');
0a580593
DM
708
709 # Attach original mail
710 #$obj = Proxmox::RuleDB::Attach->new ();
758c7b6b 711 #my $attach_orig = $ruledb->create_group_with_obj ($obj, 'Attach Original Mail',
0a580593
DM
712 # 'Attach Original Mail');
713
714 ####################### RULES ##################################
715
716 ## Block Dangerous Files
a29b9649 717 my $rule = PMG::RuleDB::Rule->new ('Block Dangerous Files', 93, 1, 0);
0a580593
DM
718 $ruledb->save_rule ($rule);
719
720 $ruledb->rule_add_what_group ($rule, $exe_content);
721 $ruledb->rule_add_action ($rule, $remove);
722
723 ## Block Viruses
a29b9649 724 $rule = PMG::RuleDB::Rule->new ('Block Viruses', 96, 1, 0);
0a580593
DM
725 $ruledb->save_rule ($rule);
726
727 $ruledb->rule_add_what_group ($rule, $virus);
728 $ruledb->rule_add_action ($rule, $notify_admin);
758c7b6b 729
0a580593
DM
730 if ($testmode) {
731 $ruledb->rule_add_action ($rule, $block);
732 } else {
733 $ruledb->rule_add_action ($rule, $quarantine);
734 }
735
736 ## Virus Alert
a29b9649 737 $rule = PMG::RuleDB::Rule->new ('Virus Alert', 96, 1, 1);
0a580593
DM
738 $ruledb->save_rule ($rule);
739
740 $ruledb->rule_add_what_group ($rule, $virus);
741 $ruledb->rule_add_action ($rule, $notify_sender);
742 $ruledb->rule_add_action ($rule, $notify_admin);
743 $ruledb->rule_add_action ($rule, $block);
758c7b6b 744
0a580593 745 ## Blacklist
a29b9649 746 $rule = PMG::RuleDB::Rule->new ('Blacklist', 98, 1, 0);
0a580593
DM
747 $ruledb->save_rule ($rule);
748
749 $ruledb->rule_add_from_group ($rule, $blacklist);
750 $ruledb->rule_add_action ($rule, $block);
751
752 ## Modify header
753 if (!$testmode) {
a29b9649 754 $rule = PMG::RuleDB::Rule->new ('Modify Header', 90, 1, 0);
0a580593
DM
755 $ruledb->save_rule ($rule);
756 $ruledb->rule_add_action ($rule, $mod_spam_level);
757 }
758
759 ## Whitelist
a29b9649 760 $rule = PMG::RuleDB::Rule->new ('Whitelist', 85, 1, 0);
0a580593
DM
761 $ruledb->save_rule ($rule);
762
763 $ruledb->rule_add_from_group ($rule, $whitelist);
764 $ruledb->rule_add_action ($rule, $accept);
765
766 if ($testmode) {
a29b9649 767 $rule = PMG::RuleDB::Rule->new ('Mark Spam', 80, 1, 0);
0a580593
DM
768 $ruledb->save_rule ($rule);
769
770 $ruledb->rule_add_what_group ($rule, $spam10);
771 $ruledb->rule_add_action ($rule, $mod_spam_level);
772 $ruledb->rule_add_action ($rule, $mod_spam_subject);
773 } else {
774 # Quarantine/Mark Spam (Level 3)
a29b9649 775 $rule = PMG::RuleDB::Rule->new ('Quarantine/Mark Spam (Level 3)', 80, 1, 0);
0a580593
DM
776 $ruledb->save_rule ($rule);
777
778 $ruledb->rule_add_what_group ($rule, $spam3);
779 $ruledb->rule_add_action ($rule, $mod_spam_subject);
780 $ruledb->rule_add_action ($rule, $quarantine);
781 #$ruledb->rule_add_action ($rule, $count_spam);
782 }
783
784 # Quarantine/Mark Spam (Level 5)
a29b9649 785 $rule = PMG::RuleDB::Rule->new ('Quarantine/Mark Spam (Level 5)', 79, 0, 0);
0a580593
DM
786 $ruledb->save_rule ($rule);
787
788 $ruledb->rule_add_what_group ($rule, $spam5);
789 $ruledb->rule_add_action ($rule, $mod_spam_subject);
790 $ruledb->rule_add_action ($rule, $quarantine);
791
792 ## Block Spam Level 10
a29b9649 793 $rule = PMG::RuleDB::Rule->new ('Block Spam (Level 10)', 78, 0, 0);
0a580593
DM
794 $ruledb->save_rule ($rule);
795
796 $ruledb->rule_add_what_group ($rule, $spam10);
797 $ruledb->rule_add_action ($rule, $block);
798
799 ## Block Outgoing Spam
a29b9649 800 $rule = PMG::RuleDB::Rule->new ('Block outgoing Spam', 70, 0, 1);
0a580593
DM
801 $ruledb->save_rule ($rule);
802
803 $ruledb->rule_add_what_group ($rule, $spam3);
804 $ruledb->rule_add_action ($rule, $notify_admin);
805 $ruledb->rule_add_action ($rule, $notify_sender);
806 $ruledb->rule_add_action ($rule, $block);
807
808 ## Add disclaimer
a29b9649 809 $rule = PMG::RuleDB::Rule->new ('Add Disclaimer', 60, 0, 1);
0a580593
DM
810 $ruledb->save_rule ($rule);
811 $ruledb->rule_add_action ($rule, $add_discl);
812
813 # Block Multimedia Files
a29b9649 814 $rule = PMG::RuleDB::Rule->new ('Block Multimedia Files', 87, 0, 2);
0a580593
DM
815 $ruledb->save_rule ($rule);
816
817 $ruledb->rule_add_what_group ($rule, $mm_content);
818 $ruledb->rule_add_action ($rule, $remove);
819
820 #$ruledb->rule_add_from_group ($rule, $anybody);
821 #$ruledb->rule_add_from_group ($rule, $trusted);
822 #$ruledb->rule_add_to_group ($rule, $anybody);
823 #$ruledb->rule_add_what_group ($rule, $ct_filter);
824 #$ruledb->rule_add_action ($rule, $add_discl);
825 #$ruledb->rule_add_action ($rule, $remove);
826 #$ruledb->rule_add_action ($rule, $bcc);
827 #$ruledb->rule_add_action ($rule, $storeq);
828 #$ruledb->rule_add_action ($rule, $accept);
829
830 cond_create_std_actions ($ruledb);
8c002247
DM
831
832 reload_ruledb();
0a580593
DM
833}
834
8f4e102b
DM
835sub get_remote_time {
836 my ($rdb) = @_;
837
838 my $sth = $rdb->prepare("SELECT EXTRACT (EPOCH FROM TIMESTAMP (0) WITH TIME ZONE 'now') as ctime;");
839 $sth->execute();
840 my $ctinfo = $sth->fetchrow_hashref();
841 $sth->finish ();
842
843 return $ctinfo ? $ctinfo->{ctime} : 0;
844}
845
cfdf6608
DM
846sub init_masterdb {
847 my ($lcid, $database) = @_;
848
849 die "got unexpected cid for new master" if !$lcid;
850
851 my $dbh;
852
853 eval {
854 $dbh = open_ruledb($database);
855
856 $dbh->begin_work;
857
858 print STDERR "update quarantine database\n";
859 $dbh->do ("UPDATE CMailStore SET CID = $lcid WHERE CID = 0;" .
860 "UPDATE CMSReceivers SET CMailStore_CID = $lcid WHERE CMailStore_CID = 0;");
861
862 print STDERR "update statistic database\n";
863 $dbh->do ("UPDATE CStatistic SET CID = $lcid WHERE CID = 0;" .
864 "UPDATE CReceivers SET CStatistic_CID = $lcid WHERE CStatistic_CID = 0;");
865
866 print STDERR "update greylist database\n";
867 $dbh->do ("UPDATE CGreylist SET CID = $lcid WHERE CID = 0;");
868
5e1408fd
DM
869 print STDERR "update localstat database\n";
870 $dbh->do ("UPDATE LocalStat SET CID = $lcid WHERE CID = 0;");
871
cfdf6608
DM
872 $dbh->commit;
873 };
874 my $err = $@;
875
876 if ($dbh) {
877 $dbh->rollback if $err;
878 $dbh->disconnect();
879 }
880
881 die $err if $err;
882}
883
8fb6f404
DM
884sub purge_statistic_database {
885 my ($dbh, $statlifetime) = @_;
886
887 return if $statlifetime <= 0;
888
889 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
890 my $end = timelocal(0, 0, 0, $mday, $mon, $year);
891 my $start = $end - $statlifetime*86400;
892
893 # delete statistics older than $start
894
895 my $rows = 0;
896
897 eval {
898 $dbh->begin_work;
899
900 my $sth = $dbh->prepare("DELETE FROM CStatistic WHERE time < $start");
901 $sth->execute;
902 $rows = $sth->rows;
903 $sth->finish;
904
905 if ($rows > 0) {
906 $sth = $dbh->prepare(
907 "DELETE FROM CReceivers WHERE NOT EXISTS " .
908 "(SELECT * FROM CStatistic WHERE CID = CStatistic_CID AND RID = CStatistic_RID)");
909
910 $sth->execute;
911 }
912 $dbh->commit;
913 };
914 if (my $err = $@) {
915 $dbh->rollback;
916 die $err;
917 }
918
919 return $rows;
920}
921
1af3c560
DM
922sub purge_quarantine_database {
923 my ($dbh, $qtype, $lifetime) = @_;
924
925 my $spooldir = $PMG::MailQueue::spooldir;
926
927 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
928 my $end = timelocal(0, 0, 0, $mday, $mon, $year);
929 my $start = $end - $lifetime*86400;
930
931 my $sth = $dbh->prepare(
932 "SELECT file FROM CMailStore WHERE time < $start AND QType = '$qtype'");
933
934 $sth->execute();
935
b8b8c31b
DM
936 my $count = 0;
937
1af3c560
DM
938 while (my $ref = $sth->fetchrow_hashref()) {
939 my $filename = "$spooldir/$ref->{file}";
b8b8c31b 940 $count++ if unlink($filename);
1af3c560
DM
941 }
942
943 $sth->finish();
944
945 $dbh->do(
946 "DELETE FROM CMailStore WHERE time < $start AND QType = '$qtype';" .
947 "DELETE FROM CMSReceivers WHERE NOT EXISTS " .
948 "(SELECT * FROM CMailStore WHERE CID = CMailStore_CID AND RID = CMailStore_RID)");
b8b8c31b
DM
949
950 return $count;
1af3c560
DM
951}
952
8af3f74c
DM
953sub get_quarantine_count {
954 my ($dbh, $qtype) = @_;
955
956 # Note;: We try to estimate used disk space - each mail
957 # is stored in an extra file ...
958
959 my $bs = 4096;
960
961 my $sth = $dbh->prepare(
962 "SELECT count(ID) as count, sum (ceil((Bytes+$bs-1)/$bs)*$bs) / (1024*1024) as mbytes, " .
963 "avg(Bytes) as avgbytes, avg(Spamlevel) as avgspam " .
964 "FROM CMailStore WHERE QType = ?");
965
966 $sth->execute($qtype);
967
968 my $ref = $sth->fetchrow_hashref();
969
970 $sth->finish;
971
13a3f36c
DM
972 foreach my $k (qw(count mbytes avgbytes avgspam)) {
973 $ref->{$k} //= 0;
974 }
975
8af3f74c
DM
976 return $ref;
977}
978
da686811
DM
979sub copy_table {
980 my ($ldb, $rdb, $table) = @_;
981
982 $table = lc($table);
983
984 my $sth = $ldb->column_info(undef, undef, $table, undef);
985 my $attrs = $sth->fetchall_arrayref({});
986
987 my @col_arr;
988 foreach my $ref (@$attrs) {
989 push @col_arr, $ref->{COLUMN_NAME};
990 }
991
992 $sth->finish();
993
994 my $cols = join(', ', @col_arr);
995 $cols || die "unable to fetch column definitions of table '$table' : ERROR";
996
997 $rdb->do("COPY $table ($cols) TO STDOUT");
998
999 my $data = '';
1000
1001 eval {
1002 $ldb->do("COPY $table ($cols) FROM stdin");
1003
1004 while ($rdb->pg_getcopydata($data) >= 0) {
1005 $ldb->pg_putcopydata($data);
1006 }
1007
1008 $ldb->pg_putcopyend();
1009 };
1010 if (my $err = $@) {
1011 $ldb->pg_putcopyend();
1012 die $err;
1013 }
1014}
1015
6cf5b4aa
DM
1016sub copy_selected_data {
1017 my ($dbh, $select_sth, $table, $attrs, $callback) = @_;
1018
1019 my $count = 0;
1020
1021 my $insert_sth = $dbh->prepare(
c2e5905e 1022 "INSERT INTO ${table}(" . join(',', @$attrs) . ') ' .
6cf5b4aa
DM
1023 'VALUES (' . join(',', ('?') x scalar(@$attrs)) . ')');
1024
1025 while (my $ref = $select_sth->fetchrow_hashref()) {
1026 $callback->($ref) if $callback;
1027 $count++;
1028 $insert_sth->execute(map { $ref->{$_} } @$attrs);
1029 }
1030
1031 return $count;
1032}
1033
987ee804
DM
1034sub update_master_clusterinfo {
1035 my ($clientcid) = @_;
1036
1037 my $dbh = open_ruledb();
1038
1039 $dbh->do("DELETE FROM ClusterInfo WHERE CID = $clientcid");
1040
5e1408fd 1041 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
987ee804
DM
1042
1043 foreach my $table (@mt) {
1044 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $clientcid, 'lastmt_$table', " .
1045 "EXTRACT(EPOCH FROM now())");
1046 }
1047}
1048
1049sub update_client_clusterinfo {
1050 my ($mastercid) = @_;
1051
1052 my $dbh = open_ruledb();
1053
1054 $dbh->do ("DELETE FROM StatInfo"); # not needed at node
1055
1056 $dbh->do ("DELETE FROM ClusterInfo WHERE CID = $mastercid");
1057
1058 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CMailStore', " .
1059 "COALESCE (max (rid), -1) FROM CMailStore WHERE cid = $mastercid");
1060
1061 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CStatistic', " .
1062 "COALESCE (max (rid), -1) FROM CStatistic WHERE cid = $mastercid");
1063
5e1408fd 1064 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
987ee804
DM
1065
1066 foreach my $table (@mt) {
1067 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastmt_$table', " .
1068 "COALESCE (max (mtime), 0) FROM $table");
1069 }
1070}
1071
7b313034
DM
1072sub create_clusterinfo_default {
1073 my ($dbh, $rcid, $name, $ivalue, $svalue) = @_;
1074
1075 my $sth = $dbh->prepare("SELECT * FROM ClusterInfo WHERE CID = ? AND Name = ?");
1076 $sth->execute($rcid, $name);
1077 if (!$sth->fetchrow_hashref()) {
1078 $dbh->do("INSERT INTO ClusterInfo (CID, Name, IValue, SValue) " .
1079 "VALUES (?, ?, ?, ?)", undef,
1080 $rcid, $name, $ivalue, $svalue);
1081 }
1082 $sth->finish();
1083}
1084
1085sub read_int_clusterinfo {
1086 my ($dbh, $rcid, $name) = @_;
1087
1088 my $sth = $dbh->prepare(
1089 "SELECT ivalue as value FROM ClusterInfo " .
1090 "WHERE cid = ? AND NAME = ?");
1091 $sth->execute($rcid, $name);
1092 my $cinfo = $sth->fetchrow_hashref();
1093 $sth->finish();
1094
1095 return $cinfo->{value};
1096}
1097
1098sub write_maxint_clusterinfo {
1099 my ($dbh, $rcid, $name, $value) = @_;
1100
2e049252 1101 $dbh->do("UPDATE ClusterInfo SET ivalue = GREATEST(ivalue, ?) " .
7b313034
DM
1102 "WHERE cid = ? AND name = ?", undef,
1103 $value, $rcid, $name);
1104}
1105
987ee804
DM
1106sub init_nodedb {
1107 my ($cinfo) = @_;
1108
1109 my $ni = $cinfo->{master};
1110
1111 die "no master defined - unable to sync data from master\n" if !$ni;
1112
1113 my $master_ip = $ni->{ip};
1114 my $master_cid = $ni->{cid};
22c16ff6 1115 my $master_name = $ni->{name};
987ee804
DM
1116
1117 my $fn = "/tmp/masterdb$$.tar";
1118 unlink $fn;
1119
1120 my $dbname = $default_db_name;
1121
1122 eval {
1123 print STDERR "copying master database from '${master_ip}'\n";
1124
97cab76a
DM
1125 open (my $fh, ">", $fn) || die "open '$fn' failed - $!\n";
1126
b8c0c87a
DM
1127 my $cmd = ['/usr/bin/ssh', '-o', 'BatchMode=yes',
1128 '-o', "HostKeyAlias=${master_name}", $master_ip,
1129 'pg_dump', $dbname, '-F', 'c' ];
1130
1131 PVE::Tools::run_command($cmd, output => '>&' . fileno($fh));
22c16ff6 1132
97cab76a 1133 close($fh);
987ee804
DM
1134
1135 my $size = -s $fn;
1136
1137 print STDERR "copying master database finished (got $size bytes)\n";
1138
1139 print STDERR "delete local database\n";
1140
97cab76a 1141 postgres_admin_cmd('dropdb', undef, $dbname , '--if-exists');
987ee804
DM
1142
1143 print STDERR "create new local database\n";
1144
97cab76a 1145 postgres_admin_cmd('createdb', undef, $dbname);
987ee804
DM
1146
1147 print STDERR "insert received data into local database\n";
1148
1149 my $mess;
1150 my $parser = sub {
1151 my $line = shift;
1152
1153 if ($line =~ m/restoring data for table \"(.+)\"/) {
1154 print STDERR "restoring table $1\n";
1155 } elsif (!$mess && ($line =~ m/creating (INDEX|CONSTRAINT)/)) {
1156 $mess = "creating indexes";
1157 print STDERR "$mess\n";
1158 }
1159 };
1160
97cab76a
DM
1161 my $opts = {
1162 outfunc => $parser,
1163 errfunc => $parser,
1164 errmsg => "pg_restore failed"
1165 };
1166
1167 postgres_admin_cmd('pg_restore', $opts, '-d', $dbname, '-v', $fn);
987ee804
DM
1168
1169 print STDERR "run analyze to speed up database queries\n";
1170
97cab76a 1171 postgres_admin_cmd('psql', { input => 'analyze;' }, $dbname);
987ee804
DM
1172
1173 update_client_clusterinfo($master_cid);
1174 };
1175
1176 my $err = $@;
1177
1178 unlink $fn;
1179
1180 die $err if $err;
1181}
1182
f3464b71
DM
1183sub cluster_sync_status {
1184 my ($cinfo) = @_;
1185
1186 my $dbh;
1187
1188 my $minmtime;
1189
1190 foreach my $ni (values %{$cinfo->{ids}}) {
1191 next if $cinfo->{local}->{cid} == $ni->{cid}; # skip local CID
1192 $minmtime->{$ni->{cid}} = 0;
1193 }
1194
1195 eval {
1196 $dbh = open_ruledb();
1197
1198 my $sth = $dbh->prepare(
1199 "SELECT cid, MIN (ivalue) as minmtime FROM ClusterInfo " .
1200 "WHERE name = 'lastsync' AND ivalue > 0 " .
1201 "GROUP BY cid");
1202
1203 $sth->execute();
1204
1205 while (my $info = $sth->fetchrow_hashref()) {
1206 foreach my $ni (values %{$cinfo->{ids}}) {
1207 next if $cinfo->{local}->{cid} == $ni->{cid}; # skip local CID
1208 if ($ni->{cid} == $info->{cid}) { # node exists
1209 $minmtime->{$ni->{cid}} = $info->{minmtime};
1210 }
1211 }
1212 }
1213
2ab22e49 1214 $sth->finish();
f3464b71
DM
1215 };
1216 my $err = $@;
1217
1218 $dbh->disconnect() if $dbh;
1219
b902c0b8 1220 syslog('err', $err) if $err;
f3464b71
DM
1221
1222 return $minmtime;
1223}
1224
2ab22e49 1225sub load_mail_data {
666b5e8f 1226 my ($dbh, $cid, $rid, $ticketid) = @_;
2ab22e49
DM
1227
1228 my $sth = $dbh->prepare(
1229 "SELECT * FROM CMailStore, CMSReceivers WHERE " .
666b5e8f 1230 "CID = ? AND RID = ? AND TicketID = ? AND " .
2ab22e49 1231 "CID = CMailStore_CID AND RID = CMailStore_RID");
666b5e8f 1232 $sth->execute($cid, $rid, $ticketid);
2ab22e49
DM
1233
1234 my $res = $sth->fetchrow_hashref();
1235
1236 $sth->finish();
1237
666b5e8f 1238 die "no such mail (C${cid}R${rid}T${ticketid})\n" if !defined($res);
2ab22e49
DM
1239
1240 return $res;
1241}
1242
cbef3ff8 1243sub reload_ruledb {
f9967a49
DM
1244 my ($ruledb) = @_;
1245
1246 # Note: we pass $ruledb when modifying SMTP whitelist
1247 if (defined($ruledb)) {
1248 eval {
1249 my $rulecache = PMG::RuleCache->new($ruledb);
1250 PMG::Config::rewrite_postfix_whitelist($rulecache);
1251 };
1252 if (my $err = $@) {
1253 warn "problems updating SMTP whitelist - $err";
1254 }
1255 }
1256
cbef3ff8
DM
1257 my $pid_file = '/var/run/pmg-smtp-filter.pid';
1258 my $pid = PVE::Tools::file_read_firstline($pid_file);
1259
1260 return 0 if !$pid;
1261
14e192ba
DM
1262 return 0 if $pid !~ m/^(\d+)$/;
1263 $pid = $1; # untaint
1264
cbef3ff8
DM
1265 return kill (10, $pid); # send SIGUSR1
1266}
1267
a355f100 12681;