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