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 $local_stat_ctablecmd = <<__EOD;
147 CREATE TABLE LocalStat
148 (Time INTEGER NOT NULL UNIQUE,
149 RBLCount INTEGER DEFAULT 0 NOT NULL,
150 CID INTEGER NOT NULL,
151 MTime INTEGER NOT NULL,
152 PRIMARY KEY (Time, CID));
154 CREATE INDEX LocalStat_MTime_Index ON LocalStat (MTime);
158 my $daily_stat_ctablecmd = <<__EOD;
159 CREATE TABLE DailyStat
160 (Time INTEGER NOT NULL UNIQUE,
161 CountIn INTEGER NOT NULL,
162 CountOut INTEGER NOT NULL,
163 BytesIn REAL NOT NULL,
164 BytesOut REAL NOT NULL,
165 VirusIn INTEGER NOT NULL,
166 VirusOut INTEGER NOT NULL,
167 SpamIn INTEGER NOT NULL,
168 SpamOut INTEGER NOT NULL,
169 BouncesIn INTEGER NOT NULL,
170 BouncesOut INTEGER NOT NULL,
171 GreylistCount INTEGER NOT NULL,
172 SPFCount INTEGER NOT NULL,
173 PTimeSum REAL NOT NULL,
174 MTime INTEGER NOT NULL,
175 RBLCount INTEGER DEFAULT 0 NOT NULL,
178 CREATE INDEX DailyStat_MTime_Index ON DailyStat (MTime);
182 my $domain_stat_ctablecmd = <<__EOD;
183 CREATE TABLE DomainStat
184 (Time INTEGER NOT NULL,
185 Domain VARCHAR(255) NOT NULL,
186 CountIn INTEGER NOT NULL,
187 CountOut INTEGER NOT NULL,
188 BytesIn REAL NOT NULL,
189 BytesOut REAL NOT NULL,
190 VirusIn INTEGER NOT NULL,
191 VirusOut INTEGER NOT NULL,
192 SpamIn INTEGER NOT NULL,
193 SpamOut INTEGER NOT NULL,
194 BouncesIn INTEGER NOT NULL,
195 BouncesOut INTEGER NOT NULL,
196 PTimeSum REAL NOT NULL,
197 MTime INTEGER NOT NULL,
198 PRIMARY KEY (Time, Domain));
200 CREATE INDEX DomainStat_MTime_Index ON DomainStat (MTime);
203 my $statinfo_ctablecmd = <<__EOD;
204 CREATE TABLE StatInfo
205 (Name VARCHAR(255) NOT NULL UNIQUE,
211 my $virusinfo_stat_ctablecmd = <<__EOD;
212 CREATE TABLE VirusInfo
213 (Time INTEGER NOT NULL,
214 Name VARCHAR NOT NULL,
215 Count INTEGER NOT NULL,
216 MTime INTEGER NOT NULL,
217 PRIMARY KEY (Time, Name));
219 CREATE INDEX VirusInfo_MTime_Index ON VirusInfo (MTime);
223 # mail storage stable
225 # V - Virus quarantine
226 # S - Spam quarantine
227 # D - Delayed Mails - not implemented
228 # A - Held for Audit - not implemented
233 my $cmailstore_ctablecmd = <<__EOD;
234 CREATE TABLE CMailStore
235 (CID INTEGER DEFAULT 0 NOT NULL,
236 RID INTEGER NOT NULL,
238 Time INTEGER NOT NULL,
239 QType "char" NOT NULL,
240 Bytes INTEGER NOT NULL,
241 Spamlevel INTEGER NOT NULL,
243 Sender VARCHAR(255) NOT NULL,
244 Header VARCHAR NOT NULL,
245 File VARCHAR(255) NOT NULL,
246 PRIMARY KEY (CID, RID));
247 CREATE INDEX CMailStore_Time_Index ON CMailStore (Time);
249 CREATE TABLE CMSReceivers
250 (CMailStore_CID INTEGER NOT NULL,
251 CMailStore_RID INTEGER NOT NULL,
252 PMail VARCHAR(255) NOT NULL,
253 Receiver VARCHAR(255),
254 Status "char" NOT NULL,
255 MTime INTEGER NOT NULL);
257 CREATE INDEX CMailStore_ID_Index ON CMSReceivers (CMailStore_CID, CMailStore_RID);
259 CREATE INDEX CMSReceivers_MTime_Index ON CMSReceivers (MTime);
263 my $cstatistic_ctablecmd = <<__EOD;
264 CREATE TABLE CStatistic
265 (CID INTEGER DEFAULT 0 NOT NULL,
266 RID INTEGER NOT NULL,
268 Time INTEGER NOT NULL,
269 Bytes INTEGER NOT NULL,
270 Direction Boolean NOT NULL,
271 Spamlevel INTEGER NOT NULL,
272 VirusInfo VARCHAR(255) NULL,
273 PTime INTEGER NOT NULL,
274 Sender VARCHAR(255) NOT NULL,
275 PRIMARY KEY (CID, RID));
277 CREATE INDEX CStatistic_Time_Index ON CStatistic (Time);
279 CREATE TABLE CReceivers
280 (CStatistic_CID INTEGER NOT NULL,
281 CStatistic_RID INTEGER NOT NULL,
282 Receiver VARCHAR(255) NOT NULL,
283 Blocked Boolean NOT NULL);
285 CREATE INDEX CStatistic_ID_Index ON CReceivers (CStatistic_CID, CStatistic_RID);
288 # user preferences (black an whitelists, ...)
289 # Name: perference name ('BL' -> blacklist, 'WL' -> whitelist)
290 # Data: arbitrary data
291 my $userprefs_ctablecmd = <<__EOD;
292 CREATE TABLE UserPrefs
296 MTime INTEGER NOT NULL,
297 PRIMARY KEY (PMail, Name));
299 CREATE INDEX UserPrefs_MTime_Index ON UserPrefs (MTime);
303 sub cond_create_dbtable
{
304 my ($dbh, $name, $ctablecmd) = @_;
309 my $cmd = "SELECT tablename FROM pg_tables " .
310 "WHERE tablename = lower ('$name')";
312 my $sth = $dbh->prepare($cmd);
316 if (!(my $ref = $sth->fetchrow_hashref())) {
317 $dbh->do ($ctablecmd);
333 $dbname = $default_db_name if !$dbname;
335 my $silent_opts = { outfunc
=> sub {}, errfunc
=> sub {} };
336 # make sure we have user 'root'
337 eval { postgres_admin_cmd
('createuser', $silent_opts, '-D', 'root'); };
338 # also create 'www-data' (and give it read-only access below)
339 eval { postgres_admin_cmd
('createuser', $silent_opts, '-I', '-D', 'www-data'); };
341 # use sql_ascii to avoid any character set conversions, and be compatible with
342 # older postgres versions (update from 8.1 must be possible)
344 postgres_admin_cmd
('createdb', undef, '-E', 'sql_ascii', '-T', 'template0',
345 '--lc-collate=C', '--lc-ctype=C', $dbname);
347 my $dbh = open_ruledb
($dbname);
349 # make sure 'www-data' can read all tables
350 $dbh->do("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO \"www-data\"");
354 CREATE TABLE Attribut
355 (Object_ID INTEGER NOT NULL,
356 Name VARCHAR(20) NOT NULL,
358 PRIMARY KEY (Object_ID, Name));
360 CREATE INDEX Attribut_Object_ID_Index ON Attribut(Object_ID);
364 ObjectType INTEGER NOT NULL,
365 Objectgroup_ID INTEGER NOT NULL,
369 CREATE TABLE Objectgroup
371 Name VARCHAR(255) NOT NULL,
372 Info VARCHAR(255) NULL,
373 Class VARCHAR(10) NOT NULL,
378 Name VARCHAR(255) NULL,
379 Priority INTEGER NOT NULL,
380 Active INTEGER NOT NULL DEFAULT 0,
381 Direction INTEGER NOT NULL DEFAULT 2,
382 Count INTEGER NOT NULL DEFAULT 0,
385 CREATE TABLE RuleGroup
386 (Objectgroup_ID INTEGER NOT NULL,
387 Rule_ID INTEGER NOT NULL,
388 Grouptype INTEGER NOT NULL,
389 PRIMARY KEY (Objectgroup_ID, Rule_ID, Grouptype));
391 $cgreylist_ctablecmd;
393 $clusterinfo_ctablecmd;
395 $local_stat_ctablecmd;
397 $daily_stat_ctablecmd;
399 $domain_stat_ctablecmd;
403 $cmailstore_ctablecmd;
405 $cstatistic_ctablecmd;
407 $userprefs_ctablecmd;
409 $virusinfo_stat_ctablecmd;
416 sub cond_create_action_quarantine
{
419 my $dbh = $ruledb->{dbh
};
422 my $sth = $dbh->prepare(
423 "SELECT * FROM Objectgroup, Object " .
424 "WHERE Object.ObjectType = ? AND Objectgroup.Class = ? " .
425 "AND Object.objectgroup_id = Objectgroup.id");
427 my $otype = PMG
::RuleDB
::Quarantine
::otype
();
428 if ($sth->execute($otype, 'action') <= 0) {
429 my $obj = PMG
::RuleDB
::Quarantine-
>new ();
430 my $txt = decode_entities
(PMG
::RuleDB
::Quarantine-
>otype_text);
431 my $quarantine = $ruledb->create_group_with_obj
432 ($obj, $txt, 'Move to quarantine.');
437 sub cond_create_std_actions
{
440 cond_create_action_quarantine
($ruledb);
442 #cond_create_action_report_spam($ruledb);
449 my $dbh = $ruledb->{dbh
};
451 # make sure we do not use slow sequential scans when upgraing
452 # database (before analyze can gather statistics)
453 $dbh->do("set enable_seqscan = false");
456 'LocalStat', $local_stat_ctablecmd,
457 'DailyStat', $daily_stat_ctablecmd,
458 'DomainStat', $domain_stat_ctablecmd,
459 'StatInfo', $statinfo_ctablecmd,
460 'CMailStore', $cmailstore_ctablecmd,
461 'UserPrefs', $userprefs_ctablecmd,
462 'CGreylist', $cgreylist_ctablecmd,
463 'CStatistic', $cstatistic_ctablecmd,
464 'ClusterInfo', $clusterinfo_ctablecmd,
465 'VirusInfo', $virusinfo_stat_ctablecmd,
468 foreach my $table (keys %$tables) {
469 cond_create_dbtable
($dbh, $table, $tables->{$table});
472 cond_create_std_actions
($ruledb);
474 # upgrade tables here if necessary
476 # update obsolete content type names
478 $dbh->do("UPDATE Object " .
479 "SET value = 'content-type:application/java-vm' ".
480 "WHERE objecttype = 3003 " .
481 "AND value = 'content-type:application/x-java-vm';");
484 foreach my $table (keys %$tables) {
485 eval { $dbh->do("ANALYZE $table"); };
493 my ($ruledb, $reset, $testmode) = @_;
495 my $dbh = $ruledb->{dbh
};
498 # Greylist Objectgroup
499 my $greylistgroup = PMG
::RuleDB
::Group-
>new
500 ("GreyExclusion", "-", "greylist");
501 $ruledb->save_group ($greylistgroup);
504 # we do not touch greylist objects
505 my $glids = "SELECT object.ID FROM Object, Objectgroup WHERE " .
506 "objectgroup_id = objectgroup.id and class = 'greylist'";
508 $dbh->do ("DELETE FROM Rule; " .
509 "DELETE FROM RuleGroup; " .
510 "DELETE FROM Attribut WHERE Object_ID NOT IN ($glids); " .
511 "DELETE FROM Object WHERE ID NOT IN ($glids); " .
512 "DELETE FROM Objectgroup WHERE class != 'greylist';");
518 my $obj = PMG
::RuleDB
::EMail-
>new ('nomail@fromthisdomain.com');
519 my $blacklist = $ruledb->create_group_with_obj(
520 $obj, 'Blacklist', 'Global blacklist');
523 $obj = PMG
::RuleDB
::EMail-
>new('mail@fromthisdomain.com');
524 my $whitelist = $ruledb->create_group_with_obj(
525 $obj, 'Whitelist', 'Global whitelist');
530 $obj = PMG
::RuleDB
::TimeFrame-
>new(8*60, 16*60);
531 my $working_hours =$ruledb->create_group_with_obj($obj, 'Office Hours' ,
532 'Usual office hours');
537 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('image/.*');
538 my $img_content = $ruledb->create_group_with_obj(
539 $obj, 'Images', 'All kinds of graphic files');
542 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('audio/.*');
543 my $mm_content = $ruledb->create_group_with_obj(
544 $obj, 'Multimedia', 'Audio and Video');
546 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('video/.*');
547 $ruledb->group_add_object($mm_content, $obj);
550 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/vnd\.ms-excel');
551 my $office_content = $ruledb->create_group_with_obj(
552 $obj, 'Office Files', 'Common Office Files');
554 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
555 'application/vnd\.ms-powerpoint');
557 $ruledb->group_add_object($office_content, $obj);
559 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/msword');
560 $ruledb->group_add_object ($office_content, $obj);
562 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
563 'application/vnd\.openxmlformats-officedocument\..*');
564 $ruledb->group_add_object($office_content, $obj);
566 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
567 'application/vnd\.oasis\.opendocument\..*');
568 $ruledb->group_add_object($office_content, $obj);
570 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
571 'application/vnd\.stardivision\..*');
572 $ruledb->group_add_object($office_content, $obj);
574 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
575 'application/vnd\.sun\.xml\..*');
576 $ruledb->group_add_object($office_content, $obj);
579 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new(
580 'application/x-ms-dos-executable');
581 my $exe_content = $ruledb->create_group_with_obj(
582 $obj, 'Dangerous Content', 'executable files and partial messages');
584 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-java');
585 $ruledb->group_add_object($exe_content, $obj);
586 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/javascript');
587 $ruledb->group_add_object($exe_content, $obj);
588 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-executable');
589 $ruledb->group_add_object($exe_content, $obj);
590 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('application/x-ms-dos-executable');
591 $ruledb->group_add_object($exe_content, $obj);
592 $obj = PMG
::RuleDB
::ContentTypeFilter-
>new('message/partial');
593 $ruledb->group_add_object($exe_content, $obj);
594 $obj = PMG
::RuleDB
::MatchFilename-
>new('.*\.(vbs|pif|lnk|shs|shb)');
595 $ruledb->group_add_object($exe_content, $obj);
596 $obj = PMG
::RuleDB
::MatchFilename-
>new('.*\.\{.+\}');
597 $ruledb->group_add_object($exe_content, $obj);
600 $obj = PMG
::RuleDB
::Virus-
>new();
601 my $virus = $ruledb->create_group_with_obj(
602 $obj, 'Virus', 'Matches virus infected mail');
607 $obj = PMG
::RuleDB
::Spam-
>new(3);
608 my $spam3 = $ruledb->create_group_with_obj(
609 $obj, 'Spam (Level 3)', 'Matches possible spam mail');
611 $obj = PMG
::RuleDB
::Spam-
>new(5);
612 my $spam5 = $ruledb->create_group_with_obj(
613 $obj, 'Spam (Level 5)', 'Matches possible spam mail');
615 $obj = PMG
::RuleDB
::Spam-
>new(10);
616 my $spam10 = $ruledb->create_group_with_obj(
617 $obj, 'Spam (Level 10)', 'Matches possible spam mail');
622 $obj = PMG
::RuleDB
::ModField-
>new('X-SPAM-LEVEL', '__SPAM_INFO__');
623 my $mod_spam_level = $ruledb->create_group_with_obj(
624 $obj, 'Modify Spam Level',
625 'Mark mail as spam by adding a header tag.');
628 $obj = PMG
::RuleDB
::ModField-
>new('subject', 'SPAM: __SUBJECT__');
629 my $mod_spam_subject = $ruledb->create_group_with_obj(
630 $obj, 'Modify Spam Subject',
631 'Mark mail as spam by modifying the subject.');
633 # Remove matching attachments
634 $obj = PMG
::RuleDB
::Remove-
>new(0);
635 my $remove = $ruledb->create_group_with_obj(
636 $obj, 'Remove attachments', 'Remove matching attachments');
638 # Remove all attachments
639 $obj = PMG
::RuleDB
::Remove-
>new(1);
640 my $remove_all = $ruledb->create_group_with_obj(
641 $obj, 'Remove all attachments', 'Remove all attachments');
644 $obj = PMG
::RuleDB
::Accept-
>new();
645 my $accept = $ruledb->create_group_with_obj(
646 $obj, 'Accept', 'Accept mail for Delivery');
649 $obj = PMG
::RuleDB
::Block-
>new ();
650 my $block = $ruledb->create_group_with_obj($obj, 'Block', 'Block mail');
653 $obj = PMG
::RuleDB
::Quarantine-
>new();
654 my $quarantine = $ruledb->create_group_with_obj(
655 $obj, 'Quarantine', 'Move mail to quarantine');
658 $obj = PMG
::RuleDB
::Notify-
>new('__ADMIN__');
659 my $notify_admin = $ruledb->create_group_with_obj(
660 $obj, 'Notify Admin', 'Send notification');
663 $obj = PMG
::RuleDB
::Notify-
>new('__SENDER__');
664 my $notify_sender = $ruledb->create_group_with_obj(
665 $obj, 'Notify Sender', 'Send notification');
668 $obj = PMG
::RuleDB
::Disclaimer-
>new ();
669 my $add_discl = $ruledb->create_group_with_obj(
670 $obj, 'Disclaimer', 'Add Disclaimer');
672 # Attach original mail
673 #$obj = Proxmox::RuleDB::Attach->new ();
674 #my $attach_orig = $ruledb->create_group_with_obj ($obj, 'Attach Original Mail',
675 # 'Attach Original Mail');
677 ####################### RULES ##################################
679 ## Block Dangerous Files
680 my $rule = PMG
::RuleDB
::Rule-
>new ('Block Dangerous Files', 93, 1, 0);
681 $ruledb->save_rule ($rule);
683 $ruledb->rule_add_what_group ($rule, $exe_content);
684 $ruledb->rule_add_action ($rule, $remove);
687 $rule = PMG
::RuleDB
::Rule-
>new ('Block Viruses', 96, 1, 0);
688 $ruledb->save_rule ($rule);
690 $ruledb->rule_add_what_group ($rule, $virus);
691 $ruledb->rule_add_action ($rule, $notify_admin);
694 $ruledb->rule_add_action ($rule, $block);
696 $ruledb->rule_add_action ($rule, $quarantine);
700 $rule = PMG
::RuleDB
::Rule-
>new ('Virus Alert', 96, 1, 1);
701 $ruledb->save_rule ($rule);
703 $ruledb->rule_add_what_group ($rule, $virus);
704 $ruledb->rule_add_action ($rule, $notify_sender);
705 $ruledb->rule_add_action ($rule, $notify_admin);
706 $ruledb->rule_add_action ($rule, $block);
709 $rule = PMG
::RuleDB
::Rule-
>new ('Blacklist', 98, 1, 0);
710 $ruledb->save_rule ($rule);
712 $ruledb->rule_add_from_group ($rule, $blacklist);
713 $ruledb->rule_add_action ($rule, $block);
717 $rule = PMG
::RuleDB
::Rule-
>new ('Modify Header', 90, 1, 0);
718 $ruledb->save_rule ($rule);
719 $ruledb->rule_add_action ($rule, $mod_spam_level);
723 $rule = PMG
::RuleDB
::Rule-
>new ('Whitelist', 85, 1, 0);
724 $ruledb->save_rule ($rule);
726 $ruledb->rule_add_from_group ($rule, $whitelist);
727 $ruledb->rule_add_action ($rule, $accept);
730 $rule = PMG
::RuleDB
::Rule-
>new ('Mark Spam', 80, 1, 0);
731 $ruledb->save_rule ($rule);
733 $ruledb->rule_add_what_group ($rule, $spam10);
734 $ruledb->rule_add_action ($rule, $mod_spam_level);
735 $ruledb->rule_add_action ($rule, $mod_spam_subject);
737 # Quarantine/Mark Spam (Level 3)
738 $rule = PMG
::RuleDB
::Rule-
>new ('Quarantine/Mark Spam (Level 3)', 80, 1, 0);
739 $ruledb->save_rule ($rule);
741 $ruledb->rule_add_what_group ($rule, $spam3);
742 $ruledb->rule_add_action ($rule, $mod_spam_subject);
743 $ruledb->rule_add_action ($rule, $quarantine);
744 #$ruledb->rule_add_action ($rule, $count_spam);
747 # Quarantine/Mark Spam (Level 5)
748 $rule = PMG
::RuleDB
::Rule-
>new ('Quarantine/Mark Spam (Level 5)', 79, 0, 0);
749 $ruledb->save_rule ($rule);
751 $ruledb->rule_add_what_group ($rule, $spam5);
752 $ruledb->rule_add_action ($rule, $mod_spam_subject);
753 $ruledb->rule_add_action ($rule, $quarantine);
755 ## Block Spam Level 10
756 $rule = PMG
::RuleDB
::Rule-
>new ('Block Spam (Level 10)', 78, 0, 0);
757 $ruledb->save_rule ($rule);
759 $ruledb->rule_add_what_group ($rule, $spam10);
760 $ruledb->rule_add_action ($rule, $block);
762 ## Block Outgoing Spam
763 $rule = PMG
::RuleDB
::Rule-
>new ('Block outgoing Spam', 70, 0, 1);
764 $ruledb->save_rule ($rule);
766 $ruledb->rule_add_what_group ($rule, $spam3);
767 $ruledb->rule_add_action ($rule, $notify_admin);
768 $ruledb->rule_add_action ($rule, $notify_sender);
769 $ruledb->rule_add_action ($rule, $block);
772 $rule = PMG
::RuleDB
::Rule-
>new ('Add Disclaimer', 60, 0, 1);
773 $ruledb->save_rule ($rule);
774 $ruledb->rule_add_action ($rule, $add_discl);
776 # Block Multimedia Files
777 $rule = PMG
::RuleDB
::Rule-
>new ('Block Multimedia Files', 87, 0, 2);
778 $ruledb->save_rule ($rule);
780 $ruledb->rule_add_what_group ($rule, $mm_content);
781 $ruledb->rule_add_action ($rule, $remove);
783 #$ruledb->rule_add_from_group ($rule, $anybody);
784 #$ruledb->rule_add_from_group ($rule, $trusted);
785 #$ruledb->rule_add_to_group ($rule, $anybody);
786 #$ruledb->rule_add_what_group ($rule, $ct_filter);
787 #$ruledb->rule_add_action ($rule, $add_discl);
788 #$ruledb->rule_add_action ($rule, $remove);
789 #$ruledb->rule_add_action ($rule, $bcc);
790 #$ruledb->rule_add_action ($rule, $storeq);
791 #$ruledb->rule_add_action ($rule, $accept);
793 cond_create_std_actions
($ruledb);
798 sub get_remote_time
{
801 my $sth = $rdb->prepare("SELECT EXTRACT (EPOCH FROM TIMESTAMP (0) WITH TIME ZONE 'now') as ctime;");
803 my $ctinfo = $sth->fetchrow_hashref();
806 return $ctinfo ?
$ctinfo->{ctime
} : 0;
810 my ($lcid, $database) = @_;
812 die "got unexpected cid for new master" if !$lcid;
817 $dbh = open_ruledb
($database);
821 print STDERR
"update quarantine database\n";
822 $dbh->do ("UPDATE CMailStore SET CID = $lcid WHERE CID = 0;" .
823 "UPDATE CMSReceivers SET CMailStore_CID = $lcid WHERE CMailStore_CID = 0;");
825 print STDERR
"update statistic database\n";
826 $dbh->do ("UPDATE CStatistic SET CID = $lcid WHERE CID = 0;" .
827 "UPDATE CReceivers SET CStatistic_CID = $lcid WHERE CStatistic_CID = 0;");
829 print STDERR
"update greylist database\n";
830 $dbh->do ("UPDATE CGreylist SET CID = $lcid WHERE CID = 0;");
832 print STDERR
"update localstat database\n";
833 $dbh->do ("UPDATE LocalStat SET CID = $lcid WHERE CID = 0;");
840 $dbh->rollback if $err;
847 sub purge_statistic_database
{
848 my ($dbh, $statlifetime) = @_;
850 return if $statlifetime <= 0;
852 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
853 my $end = timelocal
(0, 0, 0, $mday, $mon, $year);
854 my $start = $end - $statlifetime*86400;
856 # delete statistics older than $start
863 my $sth = $dbh->prepare("DELETE FROM CStatistic WHERE time < $start");
869 $sth = $dbh->prepare(
870 "DELETE FROM CReceivers WHERE NOT EXISTS " .
871 "(SELECT * FROM CStatistic WHERE CID = CStatistic_CID AND RID = CStatistic_RID)");
885 sub purge_quarantine_database
{
886 my ($dbh, $qtype, $lifetime) = @_;
888 my $spooldir = $PMG::MailQueue
::spooldir
;
890 my (undef, undef, undef, $mday, $mon, $year) = localtime(time());
891 my $end = timelocal
(0, 0, 0, $mday, $mon, $year);
892 my $start = $end - $lifetime*86400;
894 my $sth = $dbh->prepare(
895 "SELECT file FROM CMailStore WHERE time < $start AND QType = '$qtype'");
901 while (my $ref = $sth->fetchrow_hashref()) {
902 my $filename = "$spooldir/$ref->{file}";
903 $count++ if unlink($filename);
909 "DELETE FROM CMailStore WHERE time < $start AND QType = '$qtype';" .
910 "DELETE FROM CMSReceivers WHERE NOT EXISTS " .
911 "(SELECT * FROM CMailStore WHERE CID = CMailStore_CID AND RID = CMailStore_RID)");
916 sub get_quarantine_count
{
917 my ($dbh, $qtype) = @_;
919 # Note;: We try to estimate used disk space - each mail
920 # is stored in an extra file ...
924 my $sth = $dbh->prepare(
925 "SELECT count(ID) as count, sum (ceil((Bytes+$bs-1)/$bs)*$bs) / (1024*1024) as mbytes, " .
926 "avg(Bytes) as avgbytes, avg(Spamlevel) as avgspam " .
927 "FROM CMailStore WHERE QType = ?");
929 $sth->execute($qtype);
931 my $ref = $sth->fetchrow_hashref();
935 foreach my $k (qw(count mbytes avgbytes avgspam)) {
943 my ($ldb, $rdb, $table) = @_;
947 my $sth = $ldb->column_info(undef, undef, $table, undef);
948 my $attrs = $sth->fetchall_arrayref({});
951 foreach my $ref (@$attrs) {
952 push @col_arr, $ref->{COLUMN_NAME
};
957 my $cols = join(', ', @col_arr);
958 $cols || die "unable to fetch column definitions of table '$table' : ERROR";
960 $rdb->do("COPY $table ($cols) TO STDOUT");
965 $ldb->do("COPY $table ($cols) FROM stdin");
967 while ($rdb->pg_getcopydata($data) >= 0) {
968 $ldb->pg_putcopydata($data);
971 $ldb->pg_putcopyend();
974 $ldb->pg_putcopyend();
979 sub copy_selected_data
{
980 my ($dbh, $select_sth, $table, $attrs, $callback) = @_;
984 my $insert_sth = $dbh->prepare(
985 "INSERT INTO ${table}(" . join(',', @$attrs) . ') ' .
986 'VALUES (' . join(',', ('?') x
scalar(@$attrs)) . ')');
988 while (my $ref = $select_sth->fetchrow_hashref()) {
989 $callback->($ref) if $callback;
991 $insert_sth->execute(map { $ref->{$_} } @$attrs);
997 sub update_master_clusterinfo
{
998 my ($clientcid) = @_;
1000 my $dbh = open_ruledb
();
1002 $dbh->do("DELETE FROM ClusterInfo WHERE CID = $clientcid");
1004 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
1006 foreach my $table (@mt) {
1007 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $clientcid, 'lastmt_$table', " .
1008 "EXTRACT(EPOCH FROM now())");
1012 sub update_client_clusterinfo
{
1013 my ($mastercid) = @_;
1015 my $dbh = open_ruledb
();
1017 $dbh->do ("DELETE FROM StatInfo"); # not needed at node
1019 $dbh->do ("DELETE FROM ClusterInfo WHERE CID = $mastercid");
1021 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CMailStore', " .
1022 "COALESCE (max (rid), -1) FROM CMailStore WHERE cid = $mastercid");
1024 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastid_CStatistic', " .
1025 "COALESCE (max (rid), -1) FROM CStatistic WHERE cid = $mastercid");
1027 my @mt = ('CMSReceivers', 'CGreylist', 'UserPrefs', 'DomainStat', 'DailyStat', 'LocalStat', 'VirusInfo');
1029 foreach my $table (@mt) {
1030 $dbh->do ("INSERT INTO ClusterInfo (cid, name, ivalue) select $mastercid, 'lastmt_$table', " .
1031 "COALESCE (max (mtime), 0) FROM $table");
1035 sub create_clusterinfo_default
{
1036 my ($dbh, $rcid, $name, $ivalue, $svalue) = @_;
1038 my $sth = $dbh->prepare("SELECT * FROM ClusterInfo WHERE CID = ? AND Name = ?");
1039 $sth->execute($rcid, $name);
1040 if (!$sth->fetchrow_hashref()) {
1041 $dbh->do("INSERT INTO ClusterInfo (CID, Name, IValue, SValue) " .
1042 "VALUES (?, ?, ?, ?)", undef,
1043 $rcid, $name, $ivalue, $svalue);
1048 sub read_int_clusterinfo
{
1049 my ($dbh, $rcid, $name) = @_;
1051 my $sth = $dbh->prepare(
1052 "SELECT ivalue as value FROM ClusterInfo " .
1053 "WHERE cid = ? AND NAME = ?");
1054 $sth->execute($rcid, $name);
1055 my $cinfo = $sth->fetchrow_hashref();
1058 return $cinfo->{value
};
1061 sub write_maxint_clusterinfo
{
1062 my ($dbh, $rcid, $name, $value) = @_;
1064 $dbh->do("UPDATE ClusterInfo SET ivalue = GREATEST(ivalue, ?) " .
1065 "WHERE cid = ? AND name = ?", undef,
1066 $value, $rcid, $name);
1072 my $ni = $cinfo->{master
};
1074 die "no master defined - unable to sync data from master\n" if !$ni;
1076 my $master_ip = $ni->{ip
};
1077 my $master_cid = $ni->{cid
};
1078 my $master_name = $ni->{name
};
1080 my $fn = "/tmp/masterdb$$.tar";
1083 my $dbname = $default_db_name;
1086 print STDERR
"copying master database from '${master_ip}'\n";
1088 open (my $fh, ">", $fn) || die "open '$fn' failed - $!\n";
1091 ['/usr/bin/ssh', '-o', 'BatchMode=yes',
1092 '-o', "HostKeyAlias=${master_name}",
1093 $master_ip, 'pg_dump'],
1094 { output
=> '>&' . fileno($fh) },
1095 $dbname, '-F', 'c');
1101 print STDERR
"copying master database finished (got $size bytes)\n";
1103 print STDERR
"delete local database\n";
1105 postgres_admin_cmd
('dropdb', undef, $dbname , '--if-exists');
1107 print STDERR
"create new local database\n";
1109 postgres_admin_cmd
('createdb', undef, $dbname);
1111 print STDERR
"insert received data into local database\n";
1117 if ($line =~ m/restoring data for table \"(.+)\"/) {
1118 print STDERR
"restoring table $1\n";
1119 } elsif (!$mess && ($line =~ m/creating (INDEX|CONSTRAINT)/)) {
1120 $mess = "creating indexes";
1121 print STDERR
"$mess\n";
1128 errmsg
=> "pg_restore failed"
1131 postgres_admin_cmd
('pg_restore', $opts, '-d', $dbname, '-v', $fn);
1133 print STDERR
"run analyze to speed up database queries\n";
1135 postgres_admin_cmd
('psql', { input
=> 'analyze;' }, $dbname);
1137 update_client_clusterinfo
($master_cid);
1147 sub cluster_sync_status
{
1154 foreach my $ni (values %{$cinfo->{ids
}}) {
1155 next if $cinfo->{local}->{cid
} == $ni->{cid
}; # skip local CID
1156 $minmtime->{$ni->{cid
}} = 0;
1160 $dbh = open_ruledb
();
1162 my $sth = $dbh->prepare(
1163 "SELECT cid, MIN (ivalue) as minmtime FROM ClusterInfo " .
1164 "WHERE name = 'lastsync' AND ivalue > 0 " .
1169 while (my $info = $sth->fetchrow_hashref()) {
1170 foreach my $ni (values %{$cinfo->{ids
}}) {
1171 next if $cinfo->{local}->{cid
} == $ni->{cid
}; # skip local CID
1172 if ($ni->{cid
} == $info->{cid
}) { # node exists
1173 $minmtime->{$ni->{cid
}} = $info->{minmtime
};
1182 $dbh->disconnect() if $dbh;
1184 syslog
('err', $err) if $err;
1189 sub load_mail_data
{
1190 my ($dbh, $cid, $rid) = @_;
1192 my $sth = $dbh->prepare(
1193 "SELECT * FROM CMailStore, CMSReceivers WHERE " .
1194 "CID = $cid and RID = $rid AND " .
1195 "CID = CMailStore_CID AND RID = CMailStore_RID");
1198 my $res = $sth->fetchrow_hashref();
1202 die "no such mail (C${cid}R${rid})\n" if !defined($res);
1208 my $pid_file = '/var/run/pmg-smtp-filter.pid';
1209 my $pid = PVE
::Tools
::file_read_firstline
($pid_file);
1213 return 0 if $pid !~ m/^(\d+)$/;
1214 $pid = $1; # untaint
1216 return kill (10, $pid); # send SIGUSR1