6 use POSIX
":sys_wait_h";
18 our $default_db_name = "Proxmox_ruledb";
20 our $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 ' .
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)';
33 my ($database, $host, $port) = @_;
37 $database //= $default_db_name;
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
45 my $dsn = "dbi:Pg:dbname=$database;host=$host;port=$port;";
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);
58 $rdb = DBI-
>connect($dsn, 'root', undef,
59 { PrintError
=> 0, RaiseError
=> 1 });
63 sigaction
(SIGALRM
, $oldaction); # restore original handler
69 my $dsn = "DBI:Pg:dbname=$database;host=/var/run/postgresql;port=$port";
71 my $dbh = DBI-
>connect($dsn, $> == 0 ?
'root' : 'www-data', undef,
72 { PrintError
=> 0, RaiseError
=> 1 });
78 sub postgres_admin_cmd
{
79 my ($cmd, $options, @params) = @_;
81 $cmd = ref($cmd) ?
$cmd : [ $cmd ];
82 my $uid = getpwnam('postgres') || die "getpwnam postgres failed\n";
85 $! && die "setuid postgres ($uid) failed - $!\n";
87 PVE
::Tools
::run_command
([@$cmd, '-U', 'postgres', @params], %$options);
93 postgres_admin_cmd
('dropdb', undef, $dbname);
98 my $database_list = {};
103 my ($name, $owner) = map { PVE
::Tools
::trim
($_) } split(/\|/, $line);
104 return if !$name || !$owner;
106 $database_list->{$name} = { owner
=> $owner };
109 postgres_admin_cmd
('psql', { outfunc
=> $parser }, '--list', '--quiet', '--tuples-only');
111 return $database_list;
114 my $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));
130 CREATE INDEX CGreylist_Instance_Sender_Index ON CGreylist (Instance, Sender);
132 CREATE INDEX CGreylist_ExTime_Index ON CGreylist (ExTime);
134 CREATE INDEX CGreylist_MTime_Index ON CGreylist (MTime);
137 my $clusterinfo_ctablecmd = <<__EOD;
138 CREATE TABLE ClusterInfo
139 (CID INTEGER NOT NULL,
140 Name VARCHAR NOT NULL,
143 PRIMARY KEY (CID, Name))
146 my $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,
166 CREATE INDEX DailyStat_MTime_Index ON DailyStat (MTime);
170 my $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));
188 CREATE INDEX DomainStat_MTime_Index ON DomainStat (MTime);
191 my $statinfo_ctablecmd = <<__EOD;
192 CREATE TABLE StatInfo
193 (Name VARCHAR(255) NOT NULL UNIQUE,
199 my $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));
207 CREATE INDEX VirusInfo_MTime_Index ON VirusInfo (MTime);
211 # mail storage stable
213 # V - Virus quarantine
214 # S - Spam quarantine
215 # D - Delayed Mails - not implemented
216 # A - Held for Audit - not implemented
221 my $cmailstore_ctablecmd = <<__EOD;
222 CREATE TABLE CMailStore
223 (CID INTEGER DEFAULT 0 NOT NULL,
224 RID INTEGER NOT NULL,
226 Time INTEGER NOT NULL,
227 QType "char" NOT NULL,
228 Bytes INTEGER NOT NULL,
229 Spamlevel INTEGER NOT 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);
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),
242 Status "char" NOT NULL,
243 MTime INTEGER NOT NULL);
245 CREATE INDEX CMailStore_ID_Index ON CMSReceivers (CMailStore_CID, CMailStore_RID);
247 CREATE INDEX CMSReceivers_MTime_Index ON CMSReceivers (MTime);
251 my $cstatistic_ctablecmd = <<__EOD;
252 CREATE TABLE CStatistic
253 (CID INTEGER DEFAULT 0 NOT NULL,
254 RID INTEGER NOT NULL,
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));
265 CREATE INDEX CStatistic_Time_Index ON CStatistic (Time);
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);
273 CREATE INDEX CStatistic_ID_Index ON CReceivers (CStatistic_CID, CStatistic_RID);
276 # user preferences (black an whitelists, ...)
277 # Name: perference name ('BL' -> blacklist, 'WL' -> whitelist)
278 # Data: arbitrary data
279 my $userprefs_ctablecmd = <<__EOD;
280 CREATE TABLE UserPrefs
284 MTime INTEGER NOT NULL,
285 PRIMARY KEY (PMail, Name));
287 CREATE INDEX UserPrefs_MTime_Index ON UserPrefs (MTime);
291 sub cond_create_dbtable
{
292 my ($dbh, $name, $ctablecmd) = @_;
297 my $cmd = "SELECT tablename FROM pg_tables " .
298 "WHERE tablename = lower ('$name')";
300 my $sth = $dbh->prepare ($cmd);
304 if (!(my $ref = $sth->fetchrow_hashref())) {
305 $dbh->do ($ctablecmd);
321 $dbname = $default_db_name if !$dbname;
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'); };
326 # also create 'www-data' (and give it read-only access below)
327 eval { postgres_admin_cmd
('createuser', $silent_opts, '-I', '-D', 'www-data'); };
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)
332 postgres_admin_cmd
('createdb', undef, '-E', 'sql_ascii', '-T', 'template0',
333 '--lc-collate=C', '--lc-ctype=C', $dbname);
335 my $dbh = open_ruledb
($dbname);
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\"");
342 CREATE TABLE Attribut
343 (Object_ID INTEGER NOT NULL,
344 Name VARCHAR(20) NOT NULL,
346 PRIMARY KEY (Object_ID, Name));
348 CREATE INDEX Attribut_Object_ID_Index ON Attribut(Object_ID);
352 ObjectType INTEGER NOT NULL,
353 Objectgroup_ID INTEGER NOT NULL,
357 CREATE TABLE Objectgroup
359 Name VARCHAR(255) NOT NULL,
360 Info VARCHAR(255) NULL,
361 Class VARCHAR(10) NOT NULL,
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,
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));
379 $cgreylist_ctablecmd;
381 $clusterinfo_ctablecmd;
383 $daily_stat_ctablecmd;
385 $domain_stat_ctablecmd;
389 $cmailstore_ctablecmd;
391 $cstatistic_ctablecmd;
393 $userprefs_ctablecmd;
395 $virusinfo_stat_ctablecmd;
402 sub cond_create_action_quarantine
{
405 my $dbh = $ruledb->{dbh
};
408 my $sth = $dbh->prepare(
409 "SELECT * FROM Objectgroup, Object " .
410 "WHERE Object.ObjectType = ? AND Objectgroup.Class = ? " .
411 "AND Object.objectgroup_id = Objectgroup.id");
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
418 ($obj, $txt, 'Move to quarantine.');
423 sub cond_create_std_actions
{
426 cond_create_action_quarantine
($ruledb);
428 #cond_create_action_report_spam($ruledb);
435 my $dbh = $ruledb->{dbh
};
437 # make sure we do not use slow sequential scans when upgraing
438 # database (before analyze can gather statistics)
439 $dbh->do("set enable_seqscan = false");
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,
453 foreach my $table (keys %$tables) {
454 cond_create_dbtable
($dbh, $table, $tables->{$tables});
457 cond_create_std_actions
($ruledb);
459 # upgrade tables here if necessary
461 # update obsolete content type names
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';");
469 foreach my $table (keys %$tables) {
470 eval { $dbh->do("ANALYZE $table"); };
476 my ($ruledb, $reset, $testmode) = @_;
478 my $dbh = $ruledb->{dbh
};
481 # Greylist Objectgroup
482 my $greylistgroup = PMG
::RuleDB
::Group-
>new
483 ("GreyExclusion", "-", "greylist");
484 $ruledb->save_group ($greylistgroup);
487 # we do not touch greylist objects
488 my $glids = "SELECT object.ID FROM Object, Objectgroup WHERE " .
489 "objectgroup_id = objectgroup.id and class = 'greylist'";
491 $dbh->do ("DELETE FROM Rule; " .
492 "DELETE FROM RuleGroup; " .
493 "DELETE FROM Attribut WHERE Object_ID NOT IN ($glids); " .
494 "DELETE FROM Object WHERE ID NOT IN ($glids); " .
495 "DELETE FROM Objectgroup WHERE class != 'greylist';");
501 my $obj = PMG
::RuleDB
::EMail-
>new ('nomail@fromthisdomain.com');
502 my $blacklist = $ruledb->create_group_with_obj(
503 $obj, 'Blacklist', 'Global blacklist');
506 $obj = PMG
::RuleDB
::EMail-
>new('mail@fromthisdomain.com');
507 my $whitelist = $ruledb->create_group_with_obj(
508 $obj, 'Whitelist', 'Global whitelist');
513 $obj = PMG
::RuleDB
::TimeFrame-
>new(8*60, 16*60);
514 my $working_hours =$ruledb->create_group_with_obj($obj, 'Office Hours' ,
515 'Usual office hours');
520 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('image/.*');
521 my $img_content = $ruledb->create_group_with_obj(
522 $obj, 'Images', 'All kinds of graphic files');
525 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('audio/.*');
526 my $mm_content = $ruledb->create_group_with_obj(
527 $obj, 'Multimedia', 'Audio and Video');
529 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('video/.*');
530 $ruledb->group_add_object($mm_content, $obj);
533 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/vnd\.ms-excel');
534 my $office_content = $ruledb->create_group_with_obj(
535 $obj, 'Office Files', 'Common Office Files');
537 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
538 'application/vnd\.ms-powerpoint');
540 $ruledb->group_add_object($office_content, $obj);
542 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/msword');
543 $ruledb->group_add_object ($office_content, $obj);
545 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
546 'application/vnd\.openxmlformats-officedocument\..*');
547 $ruledb->group_add_object($office_content, $obj);
549 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
550 'application/vnd\.oasis\.opendocument\..*');
551 $ruledb->group_add_object($office_content, $obj);
553 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
554 'application/vnd\.stardivision\..*');
555 $ruledb->group_add_object($office_content, $obj);
557 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
558 'application/vnd\.sun\.xml\..*');
559 $ruledb->group_add_object($office_content, $obj);
562 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
563 'application/x-ms-dos-executable');
564 my $exe_content = $ruledb->create_group_with_obj(
565 $obj, 'Dangerous Content', 'executable files and partial messages');
567 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-java');
568 $ruledb->group_add_object($exe_content, $obj);
569 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/javascript');
570 $ruledb->group_add_object($exe_content, $obj);
571 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-executable');
572 $ruledb->group_add_object($exe_content, $obj);
573 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-ms-dos-executable');
574 $ruledb->group_add_object($exe_content, $obj);
575 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('message/partial');
576 $ruledb->group_add_object($exe_content, $obj);
577 $obj = PMG
::RuleDB
::MatchFilename-
>new('.*\.(vbs|pif|lnk|shs|shb)');
578 $ruledb->group_add_object($exe_content, $obj);
579 $obj = PMG
::RuleDB
::MatchFilename-
>new('.*\.\{.+\}');
580 $ruledb->group_add_object($exe_content, $obj);
583 $obj = PMG
::RuleDB
::Virus-
>new();
584 my $virus = $ruledb->create_group_with_obj(
585 $obj, 'Virus', 'Matches virus infected mail');
590 $obj = PMG
::RuleDB
::Spam-
>new(3);
591 my $spam3 = $ruledb->create_group_with_obj(
592 $obj, 'Spam (Level 3)', 'Matches possible spam mail');
594 $obj = PMG
::RuleDB
::Spam-
>new(5);
595 my $spam5 = $ruledb->create_group_with_obj(
596 $obj, 'Spam (Level 5)', 'Matches possible spam mail');
598 $obj = PMG
::RuleDB
::Spam-
>new(10);
599 my $spam10 = $ruledb->create_group_with_obj(
600 $obj, 'Spam (Level 10)', 'Matches possible spam mail');
605 $obj = PMG
::RuleDB
::ModField-
>new('X-SPAM-LEVEL', '__SPAM_INFO__');
606 my $mod_spam_level = $ruledb->create_group_with_obj(
607 $obj, 'Modify Spam Level',
608 'Mark mail as spam by adding a header tag.');
611 $obj = PMG
::RuleDB
::ModField-
>new('subject', 'SPAM: __SUBJECT__');
612 my $mod_spam_subject = $ruledb->create_group_with_obj(
613 $obj, 'Modify Spam Subject',
614 'Mark mail as spam by modifying the subject.');
616 # Remove matching attachments
617 $obj = PMG
::RuleDB
::Remove-
>new(0);
618 my $remove = $ruledb->create_group_with_obj(
619 $obj, 'Remove attachments', 'Remove matching attachments');
621 # Remove all attachments
622 $obj = PMG
::RuleDB
::Remove-
>new(1);
623 my $remove_all = $ruledb->create_group_with_obj(
624 $obj, 'Remove all attachments', 'Remove all attachments');
627 $obj = PMG
::RuleDB
::Accept-
>new();
628 my $accept = $ruledb->create_group_with_obj(
629 $obj, 'Accept', 'Accept mail for Delivery');
632 $obj = PMG
::RuleDB
::Block-
>new ();
633 my $block = $ruledb->create_group_with_obj($obj, 'Block', 'Block mail');
636 $obj = PMG
::RuleDB
::Quarantine-
>new();
637 my $quarantine = $ruledb->create_group_with_obj(
638 $obj, 'Quarantine', 'Move mail to quarantine');
641 $obj = PMG
::RuleDB
::Notify-
>new('__ADMIN__');
642 my $notify_admin = $ruledb->create_group_with_obj(
643 $obj, 'Notify Admin', 'Send notification');
646 $obj = PMG
::RuleDB
::Notify-
>new('__SENDER__');
647 my $notify_sender = $ruledb->create_group_with_obj(
648 $obj, 'Notify Sender', 'Send notification');
651 $obj = PMG
::RuleDB
::Disclaimer-
>new ();
652 my $add_discl = $ruledb->create_group_with_obj(
653 $obj, 'Disclaimer', 'Add Disclaimer');
655 # Attach original mail
656 #$obj = Proxmox::RuleDB::Attach->new ();
657 #my $attach_orig = $ruledb->create_group_with_obj ($obj, 'Attach Original Mail',
658 # 'Attach Original Mail');
660 ####################### RULES ##################################
662 ## Block Dangerous Files
663 my $rule = PMG
::RuleDB
::Rule-
>new ('Block Dangerous Files', 93, 1, 0);
664 $ruledb->save_rule ($rule);
666 $ruledb->rule_add_what_group ($rule, $exe_content);
667 $ruledb->rule_add_action ($rule, $remove);
670 $rule = PMG
::RuleDB
::Rule-
>new ('Block Viruses', 96, 1, 0);
671 $ruledb->save_rule ($rule);
673 $ruledb->rule_add_what_group ($rule, $virus);
674 $ruledb->rule_add_action ($rule, $notify_admin);
677 $ruledb->rule_add_action ($rule, $block);
679 $ruledb->rule_add_action ($rule, $quarantine);
683 $rule = PMG
::RuleDB
::Rule-
>new ('Virus Alert', 96, 1, 1);
684 $ruledb->save_rule ($rule);
686 $ruledb->rule_add_what_group ($rule, $virus);
687 $ruledb->rule_add_action ($rule, $notify_sender);
688 $ruledb->rule_add_action ($rule, $notify_admin);
689 $ruledb->rule_add_action ($rule, $block);
692 $rule = PMG
::RuleDB
::Rule-
>new ('Blacklist', 98, 1, 0);
693 $ruledb->save_rule ($rule);
695 $ruledb->rule_add_from_group ($rule, $blacklist);
696 $ruledb->rule_add_action ($rule, $block);
700 $rule = PMG
::RuleDB
::Rule-
>new ('Modify Header', 90, 1, 0);
701 $ruledb->save_rule ($rule);
702 $ruledb->rule_add_action ($rule, $mod_spam_level);
706 $rule = PMG
::RuleDB
::Rule-
>new ('Whitelist', 85, 1, 0);
707 $ruledb->save_rule ($rule);
709 $ruledb->rule_add_from_group ($rule, $whitelist);
710 $ruledb->rule_add_action ($rule, $accept);
713 $rule = PMG
::RuleDB
::Rule-
>new ('Mark Spam', 80, 1, 0);
714 $ruledb->save_rule ($rule);
716 $ruledb->rule_add_what_group ($rule, $spam10);
717 $ruledb->rule_add_action ($rule, $mod_spam_level);
718 $ruledb->rule_add_action ($rule, $mod_spam_subject);
720 # Quarantine/Mark Spam (Level 3)
721 $rule = PMG
::RuleDB
::Rule-
>new ('Quarantine/Mark Spam (Level 3)', 80, 1, 0);
722 $ruledb->save_rule ($rule);
724 $ruledb->rule_add_what_group ($rule, $spam3);
725 $ruledb->rule_add_action ($rule, $mod_spam_subject);
726 $ruledb->rule_add_action ($rule, $quarantine);
727 #$ruledb->rule_add_action ($rule, $count_spam);
730 # Quarantine/Mark Spam (Level 5)
731 $rule = PMG
::RuleDB
::Rule-
>new ('Quarantine/Mark Spam (Level 5)', 79, 0, 0);
732 $ruledb->save_rule ($rule);
734 $ruledb->rule_add_what_group ($rule, $spam5);
735 $ruledb->rule_add_action ($rule, $mod_spam_subject);
736 $ruledb->rule_add_action ($rule, $quarantine);
738 ## Block Spam Level 10
739 $rule = PMG
::RuleDB
::Rule-
>new ('Block Spam (Level 10)', 78, 0, 0);
740 $ruledb->save_rule ($rule);
742 $ruledb->rule_add_what_group ($rule, $spam10);
743 $ruledb->rule_add_action ($rule, $block);
745 ## Block Outgoing Spam
746 $rule = PMG
::RuleDB
::Rule-
>new ('Block outgoing Spam', 70, 0, 1);
747 $ruledb->save_rule ($rule);
749 $ruledb->rule_add_what_group ($rule, $spam3);
750 $ruledb->rule_add_action ($rule, $notify_admin);
751 $ruledb->rule_add_action ($rule, $notify_sender);
752 $ruledb->rule_add_action ($rule, $block);
755 $rule = PMG
::RuleDB
::Rule-
>new ('Add Disclaimer', 60, 0, 1);
756 $ruledb->save_rule ($rule);
757 $ruledb->rule_add_action ($rule, $add_discl);
759 # Block Multimedia Files
760 $rule = PMG
::RuleDB
::Rule-
>new ('Block Multimedia Files', 87, 0, 2);
761 $ruledb->save_rule ($rule);
763 $ruledb->rule_add_what_group ($rule, $mm_content);
764 $ruledb->rule_add_action ($rule, $remove);
766 #$ruledb->rule_add_from_group ($rule, $anybody);
767 #$ruledb->rule_add_from_group ($rule, $trusted);
768 #$ruledb->rule_add_to_group ($rule, $anybody);
769 #$ruledb->rule_add_what_group ($rule, $ct_filter);
770 #$ruledb->rule_add_action ($rule, $add_discl);
771 #$ruledb->rule_add_action ($rule, $remove);
772 #$ruledb->rule_add_action ($rule, $bcc);
773 #$ruledb->rule_add_action ($rule, $storeq);
774 #$ruledb->rule_add_action ($rule, $accept);
776 cond_create_std_actions
($ruledb);
779 sub get_remote_time
{
782 my $sth = $rdb->prepare("SELECT EXTRACT (EPOCH FROM TIMESTAMP (0) WITH TIME ZONE 'now') as ctime;");
784 my $ctinfo = $sth->fetchrow_hashref();
787 return $ctinfo ?
$ctinfo->{ctime
} : 0;
791 my ($lcid, $database) = @_;
793 die "got unexpected cid for new master" if !$lcid;
798 $dbh = open_ruledb
($database);
802 print STDERR
"update quarantine database\n";
803 $dbh->do ("UPDATE CMailStore SET CID = $lcid WHERE CID = 0;" .
804 "UPDATE CMSReceivers SET CMailStore_CID = $lcid WHERE CMailStore_CID = 0;");
806 print STDERR
"update statistic database\n";
807 $dbh->do ("UPDATE CStatistic SET CID = $lcid WHERE CID = 0;" .
808 "UPDATE CReceivers SET CStatistic_CID = $lcid WHERE CStatistic_CID = 0;");
810 print STDERR
"update greylist database\n";
811 $dbh->do ("UPDATE CGreylist SET CID = $lcid WHERE CID = 0;");
818 $dbh->rollback if $err;
825 sub purge_statistic_database
{
826 my ($dbh, $statlifetime) = @_;
828 return if $statlifetime <= 0;
830 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
831 my $end = timelocal
(0, 0, 0, $mday, $mon, $year);
832 my $start = $end - $statlifetime*86400;
834 # delete statistics older than $start
841 my $sth = $dbh->prepare("DELETE FROM CStatistic WHERE time < $start");
847 $sth = $dbh->prepare(
848 "DELETE FROM CReceivers WHERE NOT EXISTS " .
849 "(SELECT * FROM CStatistic WHERE CID = CStatistic_CID AND RID = CStatistic_RID)");
863 sub purge_quarantine_database
{
864 my ($dbh, $qtype, $lifetime) = @_;
866 my $spooldir = $PMG::MailQueue
::spooldir
;
868 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
869 my $end = timelocal
(0, 0, 0, $mday, $mon, $year);
870 my $start = $end - $lifetime*86400;
872 my $sth = $dbh->prepare(
873 "SELECT file FROM CMailStore WHERE time < $start AND QType = '$qtype'");
879 while (my $ref = $sth->fetchrow_hashref()) {
880 my $filename = "$spooldir/$ref->{file}";
881 $count++ if unlink($filename);
887 "DELETE FROM CMailStore WHERE time < $start AND QType = '$qtype';" .
888 "DELETE FROM CMSReceivers WHERE NOT EXISTS " .
889 "(SELECT * FROM CMailStore WHERE CID = CMailStore_CID AND RID = CMailStore_RID)");
895 my ($ldb, $rdb, $table) = @_;
899 my $sth = $ldb->column_info(undef, undef, $table, undef);
900 my $attrs = $sth->fetchall_arrayref({});
903 foreach my $ref (@$attrs) {
904 push @col_arr, $ref->{COLUMN_NAME
};
909 my $cols = join(', ', @col_arr);
910 $cols || die "unable to fetch column definitions of table '$table' : ERROR";
912 $rdb->do("COPY $table ($cols) TO STDOUT");
917 $ldb->do("COPY $table ($cols) FROM stdin");
919 while ($rdb->pg_getcopydata($data) >= 0) {
920 $ldb->pg_putcopydata($data);
923 $ldb->pg_putcopyend();
926 $ldb->pg_putcopyend();
931 sub copy_selected_data
{
932 my ($dbh, $select_sth, $table, $attrs, $callback) = @_;
936 my $insert_sth = $dbh->prepare(
937 "INSERT INTO ${table}(" . join(',', @$attrs) . ') ' .
938 'VALUES (' . join(',', ('?') x
scalar(@$attrs)) . ')');
940 while (my $ref = $select_sth->fetchrow_hashref()) {
941 $callback->($ref) if $callback;
943 $insert_sth->execute(map { $ref->{$_} } @$attrs);
949 sub update_master_clusterinfo
{
950 my ($clientcid) = @_;
952 my $dbh = open_ruledb
();
954 $dbh->do("DELETE FROM ClusterInfo WHERE CID = $clientcid");
956 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'VirusInfo');
958 foreach my $table (@mt) {
959 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $clientcid, 'lastmt_$table', " .
960 "EXTRACT(EPOCH FROM now())");
964 sub update_client_clusterinfo
{
965 my ($mastercid) = @_;
967 my $dbh = open_ruledb
();
969 $dbh->do ("DELETE FROM StatInfo"); # not needed at node
971 $dbh->do ("DELETE FROM ClusterInfo WHERE CID = $mastercid");
973 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CMailStore', " .
974 "COALESCE (max (rid), -1) FROM CMailStore WHERE cid = $mastercid");
976 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CStatistic', " .
977 "COALESCE (max (rid), -1) FROM CStatistic WHERE cid = $mastercid");
979 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'VirusInfo');
981 foreach my $table (@mt) {
982 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastmt_$table', " .
983 "COALESCE (max (mtime), 0) FROM $table");
987 sub create_clusterinfo_default
{
988 my ($dbh, $rcid, $name, $ivalue, $svalue) = @_;
990 my $sth = $dbh->prepare("SELECT * FROM ClusterInfo WHERE CID = ? AND Name = ?");
991 $sth->execute($rcid, $name);
992 if (!$sth->fetchrow_hashref()) {
993 $dbh->do("INSERT INTO ClusterInfo (CID, Name, IValue, SValue) " .
994 "VALUES (?, ?, ?, ?)", undef,
995 $rcid, $name, $ivalue, $svalue);
1000 sub read_int_clusterinfo
{
1001 my ($dbh, $rcid, $name) = @_;
1003 my $sth = $dbh->prepare(
1004 "SELECT ivalue as value FROM ClusterInfo " .
1005 "WHERE cid = ? AND NAME = ?");
1006 $sth->execute($rcid, $name);
1007 my $cinfo = $sth->fetchrow_hashref();
1010 return $cinfo->{value
};
1013 sub write_maxint_clusterinfo
{
1014 my ($dbh, $rcid, $name, $value) = @_;
1016 $dbh->do("UPDATE ClusterInfo SET ivalue = GREATEST(ivalue, ?) " .
1017 "WHERE cid = ? AND name = ?", undef,
1018 $value, $rcid, $name);
1024 my $ni = $cinfo->{master
};
1026 die "no master defined - unable to sync data from master\n" if !$ni;
1028 my $master_ip = $ni->{ip
};
1029 my $master_cid = $ni->{cid
};
1030 my $master_name = $ni->{name
};
1032 my $fn = "/tmp/masterdb$$.tar";
1035 my $dbname = $default_db_name;
1038 print STDERR
"copying master database from '${master_ip}'\n";
1040 open (my $fh, ">", $fn) || die "open '$fn' failed - $!\n";
1043 ['/usr/bin/ssh', '-o', 'BatchMode=yes',
1044 '-o', "HostKeyAlias=${master_name}",
1045 $master_ip, 'pg_dump'],
1046 { output
=> '>&' . fileno($fh) },
1047 $dbname, '-F', 'c');
1053 print STDERR
"copying master database finished (got $size bytes)\n";
1055 print STDERR
"delete local database\n";
1057 postgres_admin_cmd
('dropdb', undef, $dbname , '--if-exists');
1059 print STDERR
"create new local database\n";
1061 postgres_admin_cmd
('createdb', undef, $dbname);
1063 print STDERR
"insert received data into local database\n";
1069 if ($line =~ m/restoring data for table \"(.+)\"/) {
1070 print STDERR
"restoring table $1\n";
1071 } elsif (!$mess && ($line =~ m/creating (INDEX|CONSTRAINT)/)) {
1072 $mess = "creating indexes";
1073 print STDERR
"$mess\n";
1080 errmsg
=> "pg_restore failed"
1083 postgres_admin_cmd
('pg_restore', $opts, '-d', $dbname, '-v', $fn);
1085 print STDERR
"run analyze to speed up database queries\n";
1087 postgres_admin_cmd
('psql', { input
=> 'analyze;' }, $dbname);
1089 update_client_clusterinfo
($master_cid);
1099 sub cluster_sync_status
{
1106 foreach my $ni (values %{$cinfo->{ids
}}) {
1107 next if $cinfo->{local}->{cid
} == $ni->{cid
}; # skip local CID
1108 $minmtime->{$ni->{cid
}} = 0;
1112 $dbh = open_ruledb
();
1114 my $sth = $dbh->prepare(
1115 "SELECT cid, MIN (ivalue) as minmtime FROM ClusterInfo " .
1116 "WHERE name = 'lastsync' AND ivalue > 0 " .
1121 while (my $info = $sth->fetchrow_hashref()) {
1122 foreach my $ni (values %{$cinfo->{ids
}}) {
1123 next if $cinfo->{local}->{cid
} == $ni->{cid
}; # skip local CID
1124 if ($ni->{cid
} == $info->{cid
}) { # node exists
1125 $minmtime->{$ni->{cid
}} = $info->{minmtime
};
1134 $dbh->disconnect() if $dbh;
1136 syslog
('err', $err) if $err;
1141 sub load_mail_data
{
1142 my ($dbh, $cid, $rid) = @_;
1144 my $sth = $dbh->prepare(
1145 "SELECT * FROM CMailStore, CMSReceivers WHERE " .
1146 "CID = $cid and RID = $rid AND " .
1147 "CID = CMailStore_CID AND RID = CMailStore_RID");
1150 my $res = $sth->fetchrow_hashref();
1154 die "no such mail (C${cid}R${rid})\n" if !defined($res);
1160 my $pid_file = '/var/run/pmg-smtp-filter.pid';
1161 my $pid = PVE
::Tools
::file_read_firstline
($pid_file);
1165 return kill (10, $pid); # send SIGUSR1