]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/RuleCache.pm
4bde2e7cbd5bb088edba69e312fc40caae6dcf69
[pmg-api.git] / src / PMG / RuleCache.pm
1 package PMG::RuleCache;
2
3 use strict;
4 use warnings;
5 use DBI;
6
7 use PVE::SafeSyslog;
8
9 use PMG::Utils;
10 use PMG::RuleDB;
11 use Digest::SHA;
12
13 my $ocache_size = 1023;
14
15 sub new {
16 my ($type, $ruledb) = @_;
17
18 my $self;
19
20 $self->{ruledb} = $ruledb;
21 $self->{ocache} = ();
22
23 bless $self, $type;
24
25 my $rules = ();
26
27 my $dbh = $ruledb->{dbh};
28
29 my $sha1 = Digest::SHA->new;
30
31 my $type_map = {
32 0 => "from",
33 1 => "to",
34 2 => "when",
35 3 => "what",
36 4 => "action",
37 };
38
39 eval {
40 $dbh->begin_work;
41
42 # read a consistent snapshot
43 $dbh->do("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
44
45 my $sth = $dbh->prepare(
46 "SELECT ID, Name, Priority, Active, Direction FROM Rule " .
47 "where Active > 0 " .
48 "ORDER BY Priority DESC, ID DESC");
49
50 $sth->execute();
51
52 while (my $ref = $sth->fetchrow_hashref()) {
53 my $ruleid = $ref->{id};
54 my $rule = PMG::RuleDB::Rule->new(
55 $ref->{name}, $ref->{priority}, $ref->{active},
56 $ref->{direction});
57
58 $rule->{id} = $ruleid;
59 push @$rules, $rule;
60
61 $sha1->add(join(',', $ref->{id}, $ref->{name}, $ref->{priority}, $ref->{active},
62 $ref->{direction}) . "|");
63
64 $self->{"$ruleid:from"} = { groups => [] };
65 $self->{"$ruleid:to"} = { groups => [] };
66 $self->{"$ruleid:when"} = { groups => [] };
67 $self->{"$ruleid:what"} = { groups => [] };
68 $self->{"$ruleid:action"} = { groups => [] };
69
70 my $attribute_sth = $dbh->prepare("SELECT * FROM Rule_Attributes WHERE Rule_ID = ? ORDER BY Name");
71 $attribute_sth->execute($ruleid);
72
73 my $rule_attributes = [];
74 while (my $ref = $attribute_sth->fetchrow_hashref()) {
75 if ($ref->{name} =~ m/^(from|to|when|what)-(and|invert)$/) {
76 my $type = $1;
77 my $prop = $2;
78 my $value = $ref->{value};
79 $self->{"${ruleid}:${type}"}->{$prop} = $value;
80
81 $sha1->add("${ruleid}:${type}-${prop}=${value}|");
82 }
83 }
84
85 my $sth1 = $dbh->prepare(
86 "SELECT Objectgroup_ID, Grouptype FROM RuleGroup " .
87 "where RuleGroup.Rule_ID = '$ruleid' " .
88 "ORDER BY Grouptype, Objectgroup_ID");
89
90 $sth1->execute();
91 while (my $ref1 = $sth1->fetchrow_hashref()) {
92 my $gtype = $ref1->{grouptype};
93 my $groupid = $ref1->{objectgroup_id};
94 my $objects = [];
95
96 my $sth2 = $dbh->prepare(
97 "SELECT ID FROM Object where Objectgroup_ID = '$groupid' " .
98 "ORDER BY ID");
99 $sth2->execute();
100 while (my $ref2 = $sth2->fetchrow_hashref()) {
101 my $objid = $ref2->{'id'};
102 my $obj = $self->_get_object($objid);
103
104 $sha1->add (join (',', $objid, $gtype, $groupid) . "|");
105 $sha1->add ($obj->{digest}, "|");
106
107 push @$objects, $obj;
108
109 if ($gtype == 3) { # what
110 if ($obj->otype == PMG::RuleDB::ArchiveFilter->otype ||
111 $obj->otype == PMG::RuleDB::MatchArchiveFilename->otype)
112 {
113 if ($rule->{direction} == 0) {
114 $self->{archivefilter_in} = 1;
115 } elsif ($rule->{direction} == 1) {
116 $self->{archivefilter_out} = 1;
117 } else {
118 $self->{archivefilter_in} = 1;
119 $self->{archivefilter_out} = 1;
120 }
121 }
122 } elsif ($gtype == 4) { # action
123 $self->{"$ruleid:final"} = 1 if $obj->final();
124 }
125 }
126 $sth2->finish();
127
128 my $group = {
129 objects => $objects,
130 };
131
132 my $objectgroup_sth = $dbh->prepare("SELECT * FROM Objectgroup_Attributes WHERE Objectgroup_ID = ?");
133 $objectgroup_sth->execute($groupid);
134
135 while (my $ref = $objectgroup_sth->fetchrow_hashref()) {
136 $group->{and} = $ref->{value} if $ref->{name} eq 'and';
137 $group->{invert} = $ref->{value} if $ref->{name} eq 'invert';
138 }
139 $sha1->add (join(',', $groupid, $group->{and} // 0, $group->{invert} // 0), "|");
140
141 my $type = $type_map->{$gtype};
142 push $self->{"$ruleid:$type"}->{groups}->@*, $group;
143 }
144
145 $sth1->finish();
146 }
147
148 # Cache Greylist Exclusion
149 $sth = $dbh->prepare(
150 "SELECT object.id FROM object, objectgroup " .
151 "WHERE class = 'greylist' AND " .
152 "objectgroup.id = object.objectgroup_id " .
153 "ORDER BY object.id");
154
155 $sth->execute();
156 my $grey_excl_sender = ();
157 my $grey_excl_receiver = ();
158 while (my $ref2 = $sth->fetchrow_hashref()) {
159 my $obj = $self->_get_object ($ref2->{'id'});
160
161 if ($obj->receivertest()) {
162 push @$grey_excl_receiver, $obj;
163 } else {
164 push @$grey_excl_sender, $obj;
165 }
166 $sha1->add ($ref2->{'id'}, "|");
167 $sha1->add ($obj->{digest}, "|");
168 }
169
170 $self->{"greylist:sender"} = $grey_excl_sender;
171 $self->{"greylist:receiver"} = $grey_excl_receiver;
172
173 $sth->finish();
174 };
175 my $err = $@;
176
177 $dbh->rollback; # end transaction
178
179 syslog ('err', "unable to load rulecache : $err") if $err;
180
181 $self->{rules} = $rules;
182
183 $self->{digest} = $sha1->hexdigest;
184
185 return $self;
186 }
187
188 sub final {
189 my ($self, $ruleid) = @_;
190
191 defined($ruleid) || die "undefined rule id: ERROR";
192
193 return $self->{"$ruleid:final"};
194 }
195
196 sub rules {
197 my ($self) = @_;
198
199 $self->{rules};
200 }
201
202 sub _get_object {
203 my ($self, $objid) = @_;
204
205 my $cid = $objid % $ocache_size;
206
207 my $obj = $self->{ocache}[$cid];
208
209 if (!defined ($obj) || $obj->{id} != $objid) {
210 $obj = $self->{ruledb}->load_object($objid);
211 $self->{ocache}[$cid] = $obj;
212 }
213
214 $obj || die "unable to get object $objid: ERROR";
215
216 return $obj;
217 }
218
219 sub get_actions {
220 my ($self, $ruleid) = @_;
221
222 defined($ruleid) || die "undefined rule id: ERROR";
223
224 my $actions = $self->{"$ruleid:action"};
225
226 return undef if scalar($actions->{groups}->@*) == 0;
227
228 my $res = [];
229 for my $action ($actions->{groups}->@*) {
230 push $res->@*, $action->{objects}->@*;
231 }
232 return $res;
233 }
234
235 sub greylist_match {
236 my ($self, $addr, $ip) = @_;
237
238 my $grey = $self->{"greylist:sender"};
239
240 foreach my $obj (@$grey) {
241 if ($obj->who_match ($addr, $ip)) {
242 return 1;
243 }
244 }
245
246 return 0;
247 }
248
249 sub greylist_match_receiver {
250 my ($self, $addr) = @_;
251
252 my $grey = $self->{"greylist:receiver"};
253
254 foreach my $obj (@$grey) {
255 if ($obj->who_match($addr)) {
256 return 1;
257 }
258 }
259
260 return 0;
261 }
262
263 sub from_match {
264 my ($self, $ruleid, $addr, $ip, $ldap) = @_;
265
266 my $from = $self->{"$ruleid:from"};
267
268 return 1 if scalar($from->{groups}->@*) == 0;
269
270 # postfix prefixes ipv6 addresses with IPv6:
271 if (defined($ip) && $ip =~ /^IPv6:(.*)/) {
272 $ip = $1;
273 }
274
275 for my $group ($from->{groups}->@*) {
276 for my $obj ($group->{objects}->@*) {
277 return 1 if $obj->who_match($addr, $ip, $ldap);
278 }
279 }
280
281 return 0;
282 }
283
284 sub to_match {
285 my ($self, $ruleid, $addr, $ldap) = @_;
286
287 my $to = $self->{"$ruleid:to"};
288
289 return 1 if scalar($to->{groups}->@*) == 0;
290
291 for my $group ($to->{groups}->@*) {
292 for my $obj ($group->{objects}->@*) {
293 return 1 if $obj->who_match($addr, undef, $ldap);
294 }
295 }
296
297
298 return 0;
299 }
300
301 sub when_match {
302 my ($self, $ruleid, $time) = @_;
303
304 my $when = $self->{"$ruleid:when"};
305
306 return 1 if scalar($when->{groups}->@*) == 0;
307
308 for my $group ($when->{groups}->@*) {
309 for my $obj ($group->{objects}->@*) {
310 return 1 if $obj->when_match($time);
311 }
312 }
313
314 return 0;
315 }
316
317 sub what_match {
318 my ($self, $ruleid, $queue, $element, $msginfo, $dbh) = @_;
319
320 my $what = $self->{"$ruleid:what"};
321
322 my $marks;
323 my $spaminfo;
324
325 if (scalar($what->{groups}->@*) == 0) {
326 # match all targets
327 foreach my $target (@{$msginfo->{targets}}) {
328 $marks->{$target} = [];
329 }
330 return ($marks, $spaminfo);
331 }
332
333 for my $group ($what->{groups}->@*) {
334 for my $obj ($group->{objects}->@*) {
335 if (!$obj->can('what_match_targets')) {
336 if (my $match = $obj->what_match($queue, $element, $msginfo, $dbh)) {
337 for my $target ($msginfo->{targets}->@*) {
338 push $marks->{$target}->@*, $match->@*;
339 }
340 }
341 } else {
342 if (my $target_info = $obj->what_match_targets($queue, $element, $msginfo, $dbh)) {
343 foreach my $k (keys $target_info->%*) {
344 push $marks->{$k}->@*, $target_info->{$k}->{marks}->@*;
345 # only save spaminfo once
346 $spaminfo = $target_info->{$k}->{spaminfo} if !defined($spaminfo);
347 }
348 }
349 }
350 }
351 }
352
353 return ($marks, $spaminfo);
354 }
355
356 1;