6 use POSIX
":sys_wait_h";
19 our $default_db_name = "Proxmox_ruledb";
21 our $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 ' .
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)';
34 my ($database, $host, $port) = @_;
38 $database //= $default_db_name;
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
46 my $dsn = "dbi:Pg:dbname=$database;host=$host;port=$port;";
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);
59 $rdb = DBI-
>connect($dsn, 'root', undef,
60 { PrintError
=> 0, RaiseError
=> 1 });
64 sigaction
(SIGALRM
, $oldaction); # restore original handler
70 my $dsn = "DBI:Pg:dbname=$database;host=/var/run/postgresql;port=$port";
72 my $dbh = DBI-
>connect($dsn, $> == 0 ?
'root' : 'www-data', undef,
73 { PrintError
=> 0, RaiseError
=> 1 });
79 sub postgres_admin_cmd
{
80 my ($cmd, $options, @params) = @_;
82 $cmd = ref($cmd) ?
$cmd : [ $cmd ];
83 my $uid = getpwnam('postgres') || die "getpwnam postgres failed\n";
86 $! && die "setuid postgres ($uid) failed - $!\n";
88 PVE
::Tools
::run_command
([@$cmd, '-U', 'postgres', @params], %$options);
94 postgres_admin_cmd
('dropdb', undef, $dbname);
99 my $database_list = {};
104 my ($name, $owner) = map { PVE
::Tools
::trim
($_) } split(/\|/, $line);
105 return if !$name || !$owner;
107 $database_list->{$name} = { owner
=> $owner };
110 postgres_admin_cmd
('psql', { outfunc
=> $parser }, '--list', '--quiet', '--tuples-only');
112 return $database_list;
115 my $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));
131 CREATE INDEX CGreylist_Instance_Sender_Index ON CGreylist (Instance, Sender);
133 CREATE INDEX CGreylist_ExTime_Index ON CGreylist (ExTime);
135 CREATE INDEX CGreylist_MTime_Index ON CGreylist (MTime);
138 my $clusterinfo_ctablecmd = <<__EOD;
139 CREATE TABLE ClusterInfo
140 (CID INTEGER NOT NULL,
141 Name VARCHAR NOT NULL,
144 PRIMARY KEY (CID, Name))
147 my $local_stat_ctablecmd = <<__EOD;
148 CREATE TABLE LocalStat
149 (Time INTEGER NOT NULL UNIQUE,
150 RBLCount INTEGER DEFAULT 0 NOT NULL,
151 PregreetCount INTEGER DEFAULT 0 NOT NULL,
152 CID INTEGER NOT NULL,
153 MTime INTEGER NOT NULL,
154 PRIMARY KEY (Time, CID));
156 CREATE INDEX LocalStat_MTime_Index ON LocalStat (MTime);
160 my $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,
180 CREATE INDEX DailyStat_MTime_Index ON DailyStat (MTime);
184 my $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));
202 CREATE INDEX DomainStat_MTime_Index ON DomainStat (MTime);
205 my $statinfo_ctablecmd = <<__EOD;
206 CREATE TABLE StatInfo
207 (Name VARCHAR(255) NOT NULL UNIQUE,
213 my $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));
221 CREATE INDEX VirusInfo_MTime_Index ON VirusInfo (MTime);
225 # mail storage stable
227 # V - Virus quarantine
228 # S - Spam quarantine
229 # D - Delayed Mails - not implemented
230 # A - Held for Audit - not implemented
235 my $cmailstore_ctablecmd = <<__EOD;
236 CREATE TABLE CMailStore
237 (CID INTEGER DEFAULT 0 NOT NULL,
238 RID INTEGER NOT NULL,
240 Time INTEGER NOT NULL,
241 QType "char" NOT NULL,
242 Bytes INTEGER NOT NULL,
243 Spamlevel INTEGER NOT 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);
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),
256 Status "char" NOT NULL,
257 MTime INTEGER NOT NULL);
259 CREATE INDEX CMailStore_ID_Index ON CMSReceivers (CMailStore_CID, CMailStore_RID);
261 CREATE INDEX CMSReceivers_MTime_Index ON CMSReceivers (MTime);
265 my $cstatistic_ctablecmd = <<__EOD;
266 CREATE TABLE CStatistic
267 (CID INTEGER DEFAULT 0 NOT NULL,
268 RID INTEGER NOT NULL,
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));
279 CREATE INDEX CStatistic_Time_Index ON CStatistic (Time);
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);
287 CREATE INDEX CStatistic_ID_Index ON CReceivers (CStatistic_CID, CStatistic_RID);
290 # user preferences (black an whitelists, ...)
291 # Name: perference name ('BL' -> blacklist, 'WL' -> whitelist)
292 # Data: arbitrary data
293 my $userprefs_ctablecmd = <<__EOD;
294 CREATE TABLE UserPrefs
298 MTime INTEGER NOT NULL,
299 PRIMARY KEY (PMail, Name));
301 CREATE INDEX UserPrefs_MTime_Index ON UserPrefs (MTime);
305 sub cond_create_dbtable
{
306 my ($dbh, $name, $ctablecmd) = @_;
311 my $cmd = "SELECT tablename FROM pg_tables " .
312 "WHERE tablename = lower ('$name')";
314 my $sth = $dbh->prepare($cmd);
318 if (!(my $ref = $sth->fetchrow_hashref())) {
319 $dbh->do ($ctablecmd);
335 $dbname = $default_db_name if !$dbname;
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'); };
340 # also create 'www-data' (and give it read-only access below)
341 eval { postgres_admin_cmd
('createuser', $silent_opts, '-I', '-D', 'www-data'); };
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)
346 postgres_admin_cmd
('createdb', undef, '-E', 'sql_ascii', '-T', 'template0',
347 '--lc-collate=C', '--lc-ctype=C', $dbname);
349 my $dbh = open_ruledb
($dbname);
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\"");
356 CREATE TABLE Attribut
357 (Object_ID INTEGER NOT NULL,
358 Name VARCHAR(20) NOT NULL,
360 PRIMARY KEY (Object_ID, Name));
362 CREATE INDEX Attribut_Object_ID_Index ON Attribut(Object_ID);
366 ObjectType INTEGER NOT NULL,
367 Objectgroup_ID INTEGER NOT NULL,
371 CREATE TABLE Objectgroup
373 Name VARCHAR(255) NOT NULL,
374 Info VARCHAR(255) NULL,
375 Class VARCHAR(10) NOT NULL,
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,
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));
393 $cgreylist_ctablecmd;
395 $clusterinfo_ctablecmd;
397 $local_stat_ctablecmd;
399 $daily_stat_ctablecmd;
401 $domain_stat_ctablecmd;
405 $cmailstore_ctablecmd;
407 $cstatistic_ctablecmd;
409 $userprefs_ctablecmd;
411 $virusinfo_stat_ctablecmd;
418 sub cond_create_action_quarantine
{
421 my $dbh = $ruledb->{dbh
};
424 my $sth = $dbh->prepare(
425 "SELECT * FROM Objectgroup, Object " .
426 "WHERE Object.ObjectType = ? AND Objectgroup.Class = ? " .
427 "AND Object.objectgroup_id = Objectgroup.id");
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
434 ($obj, $txt, 'Move to quarantine.');
439 sub cond_create_std_actions
{
442 cond_create_action_quarantine
($ruledb);
444 #cond_create_action_report_spam($ruledb);
451 my $dbh = $ruledb->{dbh
};
453 # make sure we do not use slow sequential scans when upgraing
454 # database (before analyze can gather statistics)
455 $dbh->do("set enable_seqscan = false");
458 'LocalStat', $local_stat_ctablecmd,
459 'DailyStat', $daily_stat_ctablecmd,
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,
470 foreach my $table (keys %$tables) {
471 cond_create_dbtable
($dbh, $table, $tables->{$table});
474 cond_create_std_actions
($ruledb);
476 # upgrade tables here if necessary
478 $dbh->do("ALTER TABLE LocalStat ADD COLUMN " .
479 "PregreetCount INTEGER DEFAULT 0 NOT NULL");
482 # update obsolete content type names
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';");
490 foreach my $table (keys %$tables) {
491 eval { $dbh->do("ANALYZE $table"); };
499 my ($ruledb, $reset, $testmode) = @_;
501 my $dbh = $ruledb->{dbh
};
504 # Greylist Objectgroup
505 my $greylistgroup = PMG
::RuleDB
::Group-
>new
506 ("GreyExclusion", "-", "greylist");
507 $ruledb->save_group ($greylistgroup);
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'";
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';");
524 my $obj = PMG
::RuleDB
::EMail-
>new ('nomail@fromthisdomain.com');
525 my $blacklist = $ruledb->create_group_with_obj(
526 $obj, 'Blacklist', 'Global blacklist');
529 $obj = PMG
::RuleDB
::EMail-
>new('mail@fromthisdomain.com');
530 my $whitelist = $ruledb->create_group_with_obj(
531 $obj, 'Whitelist', 'Global whitelist');
536 $obj = PMG
::RuleDB
::TimeFrame-
>new(8*60, 16*60);
537 my $working_hours =$ruledb->create_group_with_obj($obj, 'Office Hours' ,
538 'Usual office hours');
543 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('image/.*');
544 my $img_content = $ruledb->create_group_with_obj(
545 $obj, 'Images', 'All kinds of graphic files');
548 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('audio/.*');
549 my $mm_content = $ruledb->create_group_with_obj(
550 $obj, 'Multimedia', 'Audio and Video');
552 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('video/.*');
553 $ruledb->group_add_object($mm_content, $obj);
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');
560 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
561 'application/vnd\.ms-powerpoint');
563 $ruledb->group_add_object($office_content, $obj);
565 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/msword');
566 $ruledb->group_add_object ($office_content, $obj);
568 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
569 'application/vnd\.openxmlformats-officedocument\..*');
570 $ruledb->group_add_object($office_content, $obj);
572 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
573 'application/vnd\.oasis\.opendocument\..*');
574 $ruledb->group_add_object($office_content, $obj);
576 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
577 'application/vnd\.stardivision\..*');
578 $ruledb->group_add_object($office_content, $obj);
580 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
581 'application/vnd\.sun\.xml\..*');
582 $ruledb->group_add_object($office_content, $obj);
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');
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);
596 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-ms-dos-executable');
597 $ruledb->group_add_object($exe_content, $obj);
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);
602 $obj = PMG
::RuleDB
::MatchFilename-
>new('.*\.\{.+\}');
603 $ruledb->group_add_object($exe_content, $obj);
606 $obj = PMG
::RuleDB
::Virus-
>new();
607 my $virus = $ruledb->create_group_with_obj(
608 $obj, 'Virus', 'Matches virus infected mail');
613 $obj = PMG
::RuleDB
::Spam-
>new(3);
614 my $spam3 = $ruledb->create_group_with_obj(
615 $obj, 'Spam (Level 3)', 'Matches possible spam mail');
617 $obj = PMG
::RuleDB
::Spam-
>new(5);
618 my $spam5 = $ruledb->create_group_with_obj(
619 $obj, 'Spam (Level 5)', 'Matches possible spam mail');
621 $obj = PMG
::RuleDB
::Spam-
>new(10);
622 my $spam10 = $ruledb->create_group_with_obj(
623 $obj, 'Spam (Level 10)', 'Matches possible spam mail');
628 $obj = PMG
::RuleDB
::ModField-
>new('X-SPAM-LEVEL', '__SPAM_INFO__');
629 my $mod_spam_level = $ruledb->create_group_with_obj(
630 $obj, 'Modify Spam Level',
631 'Mark mail as spam by adding a header tag.');
634 $obj = PMG
::RuleDB
::ModField-
>new('subject', 'SPAM: __SUBJECT__');
635 my $mod_spam_subject = $ruledb->create_group_with_obj(
636 $obj, 'Modify Spam Subject',
637 'Mark mail as spam by modifying the subject.');
639 # Remove matching attachments
640 $obj = PMG
::RuleDB
::Remove-
>new(0);
641 my $remove = $ruledb->create_group_with_obj(
642 $obj, 'Remove attachments', 'Remove matching attachments');
644 # Remove all attachments
645 $obj = PMG
::RuleDB
::Remove-
>new(1);
646 my $remove_all = $ruledb->create_group_with_obj(
647 $obj, 'Remove all attachments', 'Remove all attachments');
650 $obj = PMG
::RuleDB
::Accept-
>new();
651 my $accept = $ruledb->create_group_with_obj(
652 $obj, 'Accept', 'Accept mail for Delivery');
655 $obj = PMG
::RuleDB
::Block-
>new ();
656 my $block = $ruledb->create_group_with_obj($obj, 'Block', 'Block mail');
659 $obj = PMG
::RuleDB
::Quarantine-
>new();
660 my $quarantine = $ruledb->create_group_with_obj(
661 $obj, 'Quarantine', 'Move mail to quarantine');
664 $obj = PMG
::RuleDB
::Notify-
>new('__ADMIN__');
665 my $notify_admin = $ruledb->create_group_with_obj(
666 $obj, 'Notify Admin', 'Send notification');
669 $obj = PMG
::RuleDB
::Notify-
>new('__SENDER__');
670 my $notify_sender = $ruledb->create_group_with_obj(
671 $obj, 'Notify Sender', 'Send notification');
674 $obj = PMG
::RuleDB
::Disclaimer-
>new ();
675 my $add_discl = $ruledb->create_group_with_obj(
676 $obj, 'Disclaimer', 'Add Disclaimer');
678 # Attach original mail
679 #$obj = Proxmox::RuleDB::Attach->new ();
680 #my $attach_orig = $ruledb->create_group_with_obj ($obj, 'Attach Original Mail',
681 # 'Attach Original Mail');
683 ####################### RULES ##################################
685 ## Block Dangerous Files
686 my $rule = PMG
::RuleDB
::Rule-
>new ('Block Dangerous Files', 93, 1, 0);
687 $ruledb->save_rule ($rule);
689 $ruledb->rule_add_what_group ($rule, $exe_content);
690 $ruledb->rule_add_action ($rule, $remove);
693 $rule = PMG
::RuleDB
::Rule-
>new ('Block Viruses', 96, 1, 0);
694 $ruledb->save_rule ($rule);
696 $ruledb->rule_add_what_group ($rule, $virus);
697 $ruledb->rule_add_action ($rule, $notify_admin);
700 $ruledb->rule_add_action ($rule, $block);
702 $ruledb->rule_add_action ($rule, $quarantine);
706 $rule = PMG
::RuleDB
::Rule-
>new ('Virus Alert', 96, 1, 1);
707 $ruledb->save_rule ($rule);
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);
715 $rule = PMG
::RuleDB
::Rule-
>new ('Blacklist', 98, 1, 0);
716 $ruledb->save_rule ($rule);
718 $ruledb->rule_add_from_group ($rule, $blacklist);
719 $ruledb->rule_add_action ($rule, $block);
723 $rule = PMG
::RuleDB
::Rule-
>new ('Modify Header', 90, 1, 0);
724 $ruledb->save_rule ($rule);
725 $ruledb->rule_add_action ($rule, $mod_spam_level);
729 $rule = PMG
::RuleDB
::Rule-
>new ('Whitelist', 85, 1, 0);
730 $ruledb->save_rule ($rule);
732 $ruledb->rule_add_from_group ($rule, $whitelist);
733 $ruledb->rule_add_action ($rule, $accept);
736 $rule = PMG
::RuleDB
::Rule-
>new ('Mark Spam', 80, 1, 0);
737 $ruledb->save_rule ($rule);
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);
743 # Quarantine/Mark Spam (Level 3)
744 $rule = PMG
::RuleDB
::Rule-
>new ('Quarantine/Mark Spam (Level 3)', 80, 1, 0);
745 $ruledb->save_rule ($rule);
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);
753 # Quarantine/Mark Spam (Level 5)
754 $rule = PMG
::RuleDB
::Rule-
>new ('Quarantine/Mark Spam (Level 5)', 79, 0, 0);
755 $ruledb->save_rule ($rule);
757 $ruledb->rule_add_what_group ($rule, $spam5);
758 $ruledb->rule_add_action ($rule, $mod_spam_subject);
759 $ruledb->rule_add_action ($rule, $quarantine);
761 ## Block Spam Level 10
762 $rule = PMG
::RuleDB
::Rule-
>new ('Block Spam (Level 10)', 78, 0, 0);
763 $ruledb->save_rule ($rule);
765 $ruledb->rule_add_what_group ($rule, $spam10);
766 $ruledb->rule_add_action ($rule, $block);
768 ## Block Outgoing Spam
769 $rule = PMG
::RuleDB
::Rule-
>new ('Block outgoing Spam', 70, 0, 1);
770 $ruledb->save_rule ($rule);
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);
778 $rule = PMG
::RuleDB
::Rule-
>new ('Add Disclaimer', 60, 0, 1);
779 $ruledb->save_rule ($rule);
780 $ruledb->rule_add_action ($rule, $add_discl);
782 # Block Multimedia Files
783 $rule = PMG
::RuleDB
::Rule-
>new ('Block Multimedia Files', 87, 0, 2);
784 $ruledb->save_rule ($rule);
786 $ruledb->rule_add_what_group ($rule, $mm_content);
787 $ruledb->rule_add_action ($rule, $remove);
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);
799 cond_create_std_actions
($ruledb);
804 sub get_remote_time
{
807 my $sth = $rdb->prepare("SELECT EXTRACT (EPOCH FROM TIMESTAMP (0) WITH TIME ZONE 'now') as ctime;");
809 my $ctinfo = $sth->fetchrow_hashref();
812 return $ctinfo ?
$ctinfo->{ctime
} : 0;
816 my ($lcid, $database) = @_;
818 die "got unexpected cid for new master" if !$lcid;
823 $dbh = open_ruledb
($database);
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;");
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;");
835 print STDERR
"update greylist database\n";
836 $dbh->do ("UPDATE CGreylist SET CID = $lcid WHERE CID = 0;");
838 print STDERR
"update localstat database\n";
839 $dbh->do ("UPDATE LocalStat SET CID = $lcid WHERE CID = 0;");
846 $dbh->rollback if $err;
853 sub purge_statistic_database
{
854 my ($dbh, $statlifetime) = @_;
856 return if $statlifetime <= 0;
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;
862 # delete statistics older than $start
869 my $sth = $dbh->prepare("DELETE FROM CStatistic WHERE time < $start");
875 $sth = $dbh->prepare(
876 "DELETE FROM CReceivers WHERE NOT EXISTS " .
877 "(SELECT * FROM CStatistic WHERE CID = CStatistic_CID AND RID = CStatistic_RID)");
891 sub purge_quarantine_database
{
892 my ($dbh, $qtype, $lifetime) = @_;
894 my $spooldir = $PMG::MailQueue
::spooldir
;
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;
900 my $sth = $dbh->prepare(
901 "SELECT file FROM CMailStore WHERE time < $start AND QType = '$qtype'");
907 while (my $ref = $sth->fetchrow_hashref()) {
908 my $filename = "$spooldir/$ref->{file}";
909 $count++ if unlink($filename);
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)");
922 sub get_quarantine_count
{
923 my ($dbh, $qtype) = @_;
925 # Note;: We try to estimate used disk space - each mail
926 # is stored in an extra file ...
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 = ?");
935 $sth->execute($qtype);
937 my $ref = $sth->fetchrow_hashref();
941 foreach my $k (qw(count mbytes avgbytes avgspam)) {
949 my ($ldb, $rdb, $table) = @_;
953 my $sth = $ldb->column_info(undef, undef, $table, undef);
954 my $attrs = $sth->fetchall_arrayref({});
957 foreach my $ref (@$attrs) {
958 push @col_arr, $ref->{COLUMN_NAME
};
963 my $cols = join(', ', @col_arr);
964 $cols || die "unable to fetch column definitions of table '$table' : ERROR";
966 $rdb->do("COPY $table ($cols) TO STDOUT");
971 $ldb->do("COPY $table ($cols) FROM stdin");
973 while ($rdb->pg_getcopydata($data) >= 0) {
974 $ldb->pg_putcopydata($data);
977 $ldb->pg_putcopyend();
980 $ldb->pg_putcopyend();
985 sub copy_selected_data
{
986 my ($dbh, $select_sth, $table, $attrs, $callback) = @_;
990 my $insert_sth = $dbh->prepare(
991 "INSERT INTO ${table}(" . join(',', @$attrs) . ') ' .
992 'VALUES (' . join(',', ('?') x
scalar(@$attrs)) . ')');
994 while (my $ref = $select_sth->fetchrow_hashref()) {
995 $callback->($ref) if $callback;
997 $insert_sth->execute(map { $ref->{$_} } @$attrs);
1003 sub update_master_clusterinfo
{
1004 my ($clientcid) = @_;
1006 my $dbh = open_ruledb
();
1008 $dbh->do("DELETE FROM ClusterInfo WHERE CID = $clientcid");
1010 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
1012 foreach my $table (@mt) {
1013 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $clientcid, 'lastmt_$table', " .
1014 "EXTRACT(EPOCH FROM now())");
1018 sub update_client_clusterinfo
{
1019 my ($mastercid) = @_;
1021 my $dbh = open_ruledb
();
1023 $dbh->do ("DELETE FROM StatInfo"); # not needed at node
1025 $dbh->do ("DELETE FROM ClusterInfo WHERE CID = $mastercid");
1027 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CMailStore', " .
1028 "COALESCE (max (rid), -1) FROM CMailStore WHERE cid = $mastercid");
1030 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CStatistic', " .
1031 "COALESCE (max (rid), -1) FROM CStatistic WHERE cid = $mastercid");
1033 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
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");
1041 sub create_clusterinfo_default
{
1042 my ($dbh, $rcid, $name, $ivalue, $svalue) = @_;
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);
1054 sub read_int_clusterinfo
{
1055 my ($dbh, $rcid, $name) = @_;
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();
1064 return $cinfo->{value
};
1067 sub write_maxint_clusterinfo
{
1068 my ($dbh, $rcid, $name, $value) = @_;
1070 $dbh->do("UPDATE ClusterInfo SET ivalue = GREATEST(ivalue, ?) " .
1071 "WHERE cid = ? AND name = ?", undef,
1072 $value, $rcid, $name);
1078 my $ni = $cinfo->{master
};
1080 die "no master defined - unable to sync data from master\n" if !$ni;
1082 my $master_ip = $ni->{ip
};
1083 my $master_cid = $ni->{cid
};
1084 my $master_name = $ni->{name
};
1086 my $fn = "/tmp/masterdb$$.tar";
1089 my $dbname = $default_db_name;
1092 print STDERR
"copying master database from '${master_ip}'\n";
1094 open (my $fh, ">", $fn) || die "open '$fn' failed - $!\n";
1096 my $cmd = ['/usr/bin/ssh', '-o', 'BatchMode=yes',
1097 '-o', "HostKeyAlias=${master_name}", $master_ip,
1098 'pg_dump', $dbname, '-F', 'c' ];
1100 PVE
::Tools
::run_command
($cmd, output
=> '>&' . fileno($fh));
1106 print STDERR
"copying master database finished (got $size bytes)\n";
1108 print STDERR
"delete local database\n";
1110 postgres_admin_cmd
('dropdb', undef, $dbname , '--if-exists');
1112 print STDERR
"create new local database\n";
1114 postgres_admin_cmd
('createdb', undef, $dbname);
1116 print STDERR
"insert received data into local database\n";
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";
1133 errmsg
=> "pg_restore failed"
1136 postgres_admin_cmd
('pg_restore', $opts, '-d', $dbname, '-v', $fn);
1138 print STDERR
"run analyze to speed up database queries\n";
1140 postgres_admin_cmd
('psql', { input
=> 'analyze;' }, $dbname);
1142 update_client_clusterinfo
($master_cid);
1152 sub cluster_sync_status
{
1159 foreach my $ni (values %{$cinfo->{ids
}}) {
1160 next if $cinfo->{local}->{cid
} == $ni->{cid
}; # skip local CID
1161 $minmtime->{$ni->{cid
}} = 0;
1165 $dbh = open_ruledb
();
1167 my $sth = $dbh->prepare(
1168 "SELECT cid, MIN (ivalue) as minmtime FROM ClusterInfo " .
1169 "WHERE name = 'lastsync' AND ivalue > 0 " .
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
};
1187 $dbh->disconnect() if $dbh;
1189 syslog
('err', $err) if $err;
1194 sub load_mail_data
{
1195 my ($dbh, $cid, $rid, $pmail) = @_;
1197 my $sth = $dbh->prepare(
1198 "SELECT * FROM CMailStore, CMSReceivers WHERE " .
1199 ($pmail ?
'pmail = ' . $dbh->quote($pmail) . " AND " : '') .
1200 "CID = $cid and RID = $rid AND " .
1201 "CID = CMailStore_CID AND RID = CMailStore_RID");
1204 my $res = $sth->fetchrow_hashref();
1208 die "no such mail (C${cid}R${rid})\n" if !defined($res);
1216 # Note: we pass $ruledb when modifying SMTP whitelist
1217 if (defined($ruledb)) {
1219 my $rulecache = PMG
::RuleCache-
>new($ruledb);
1220 PMG
::Config
::rewrite_postfix_whitelist
($rulecache);
1223 warn "problems updating SMTP whitelist - $err";
1227 my $pid_file = '/var/run/pmg-smtp-filter.pid';
1228 my $pid = PVE
::Tools
::file_read_firstline
($pid_file);
1232 return 0 if $pid !~ m/^(\d+)$/;
1233 $pid = $1; # untaint
1235 return kill (10, $pid); # send SIGUSR1