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