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