]>
Commit | Line | Data |
---|---|---|
c881fe35 DM |
1 | package PMG::RuleCache; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use DBI; | |
c881fe35 DM |
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 | ||
64ba4c0d DC |
31 | my $type_map = { |
32 | 0 => "from", | |
33 | 1 => "to", | |
34 | 2 => "when", | |
35 | 3 => "what", | |
36 | 4 => "action", | |
37 | }; | |
38 | ||
c881fe35 DM |
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 " . | |
f6b69037 | 48 | "ORDER BY Priority DESC, ID DESC"); |
c881fe35 DM |
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 | ||
c9bb8609 DM |
61 | $sha1->add(join(',', $ref->{id}, $ref->{name}, $ref->{priority}, $ref->{active}, |
62 | $ref->{direction}) . "|"); | |
c881fe35 | 63 | |
64ba4c0d DC |
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 => [] }; | |
c881fe35 DM |
69 | |
70 | my $sth1 = $dbh->prepare( | |
71 | "SELECT Objectgroup_ID, Grouptype FROM RuleGroup " . | |
72 | "where RuleGroup.Rule_ID = '$ruleid' " . | |
73 | "ORDER BY Grouptype, Objectgroup_ID"); | |
74 | ||
75 | $sth1->execute(); | |
76 | while (my $ref1 = $sth1->fetchrow_hashref()) { | |
77 | my $gtype = $ref1->{grouptype}; | |
78 | my $groupid = $ref1->{objectgroup_id}; | |
64ba4c0d | 79 | my $objects = []; |
c881fe35 DM |
80 | |
81 | my $sth2 = $dbh->prepare( | |
82 | "SELECT ID FROM Object where Objectgroup_ID = '$groupid' " . | |
83 | "ORDER BY ID"); | |
84 | $sth2->execute(); | |
85 | while (my $ref2 = $sth2->fetchrow_hashref()) { | |
86 | my $objid = $ref2->{'id'}; | |
87 | my $obj = $self->_get_object($objid); | |
88 | ||
89 | $sha1->add (join (',', $objid, $gtype, $groupid) . "|"); | |
90 | $sha1->add ($obj->{digest}, "|"); | |
91 | ||
64ba4c0d DC |
92 | push @$objects, $obj; |
93 | ||
94 | if ($gtype == 3) { # what | |
5e809f47 DC |
95 | if ($obj->otype == PMG::RuleDB::ArchiveFilter->otype || |
96 | $obj->otype == PMG::RuleDB::MatchArchiveFilename->otype) | |
97 | { | |
c881fe35 DM |
98 | if ($rule->{direction} == 0) { |
99 | $self->{archivefilter_in} = 1; | |
100 | } elsif ($rule->{direction} == 1) { | |
101 | $self->{archivefilter_out} = 1; | |
102 | } else { | |
103 | $self->{archivefilter_in} = 1; | |
104 | $self->{archivefilter_out} = 1; | |
105 | } | |
106 | } | |
107 | } elsif ($gtype == 4) { # action | |
c881fe35 DM |
108 | $self->{"$ruleid:final"} = 1 if $obj->final(); |
109 | } | |
110 | } | |
111 | $sth2->finish(); | |
64ba4c0d DC |
112 | |
113 | my $group = { | |
114 | objects => $objects, | |
115 | }; | |
116 | ||
117 | my $type = $type_map->{$gtype}; | |
118 | push $self->{"$ruleid:$type"}->{groups}->@*, $group; | |
c881fe35 DM |
119 | } |
120 | ||
121 | $sth1->finish(); | |
c881fe35 DM |
122 | } |
123 | ||
124 | # Cache Greylist Exclusion | |
125 | $sth = $dbh->prepare( | |
126 | "SELECT object.id FROM object, objectgroup " . | |
127 | "WHERE class = 'greylist' AND " . | |
128 | "objectgroup.id = object.objectgroup_id " . | |
129 | "ORDER BY object.id"); | |
130 | ||
131 | $sth->execute(); | |
132 | my $grey_excl_sender = (); | |
133 | my $grey_excl_receiver = (); | |
134 | while (my $ref2 = $sth->fetchrow_hashref()) { | |
135 | my $obj = $self->_get_object ($ref2->{'id'}); | |
136 | ||
137 | if ($obj->receivertest()) { | |
138 | push @$grey_excl_receiver, $obj; | |
139 | } else { | |
140 | push @$grey_excl_sender, $obj; | |
141 | } | |
c9bb8609 | 142 | $sha1->add ($ref2->{'id'}, "|"); |
c881fe35 DM |
143 | $sha1->add ($obj->{digest}, "|"); |
144 | } | |
145 | ||
146 | $self->{"greylist:sender"} = $grey_excl_sender; | |
147 | $self->{"greylist:receiver"} = $grey_excl_receiver; | |
148 | ||
149 | $sth->finish(); | |
150 | }; | |
151 | my $err = $@; | |
152 | ||
153 | $dbh->rollback; # end transaction | |
154 | ||
b902c0b8 | 155 | syslog ('err', "unable to load rulecache : $err") if $err; |
c881fe35 DM |
156 | |
157 | $self->{rules} = $rules; | |
158 | ||
159 | $self->{digest} = $sha1->hexdigest; | |
160 | ||
161 | return $self; | |
162 | } | |
163 | ||
164 | sub final { | |
165 | my ($self, $ruleid) = @_; | |
166 | ||
9ef3f143 | 167 | defined($ruleid) || die "undefined rule id: ERROR"; |
c881fe35 DM |
168 | |
169 | return $self->{"$ruleid:final"}; | |
170 | } | |
171 | ||
172 | sub rules { | |
173 | my ($self) = @_; | |
174 | ||
175 | $self->{rules}; | |
176 | } | |
177 | ||
178 | sub _get_object { | |
179 | my ($self, $objid) = @_; | |
180 | ||
181 | my $cid = $objid % $ocache_size; | |
182 | ||
183 | my $obj = $self->{ocache}[$cid]; | |
184 | ||
185 | if (!defined ($obj) || $obj->{id} != $objid) { | |
186 | $obj = $self->{ruledb}->load_object($objid); | |
187 | $self->{ocache}[$cid] = $obj; | |
188 | } | |
189 | ||
9ef3f143 | 190 | $obj || die "unable to get object $objid: ERROR"; |
c881fe35 DM |
191 | |
192 | return $obj; | |
193 | } | |
194 | ||
195 | sub get_actions { | |
196 | my ($self, $ruleid) = @_; | |
197 | ||
9ef3f143 | 198 | defined($ruleid) || die "undefined rule id: ERROR"; |
c881fe35 | 199 | |
64ba4c0d DC |
200 | my $actions = $self->{"$ruleid:action"}; |
201 | ||
202 | return undef if scalar($actions->{groups}->@*) == 0; | |
203 | ||
204 | my $res = []; | |
205 | for my $action ($actions->{groups}->@*) { | |
206 | push $res->@*, $action->{objects}->@*; | |
207 | } | |
208 | return $res; | |
c881fe35 DM |
209 | } |
210 | ||
211 | sub greylist_match { | |
212 | my ($self, $addr, $ip) = @_; | |
213 | ||
214 | my $grey = $self->{"greylist:sender"}; | |
215 | ||
216 | foreach my $obj (@$grey) { | |
217 | if ($obj->who_match ($addr, $ip)) { | |
218 | return 1; | |
219 | } | |
220 | } | |
221 | ||
222 | return 0; | |
223 | } | |
224 | ||
225 | sub greylist_match_receiver { | |
226 | my ($self, $addr) = @_; | |
227 | ||
228 | my $grey = $self->{"greylist:receiver"}; | |
229 | ||
230 | foreach my $obj (@$grey) { | |
231 | if ($obj->who_match($addr)) { | |
232 | return 1; | |
233 | } | |
234 | } | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
239 | sub from_match { | |
240 | my ($self, $ruleid, $addr, $ip, $ldap) = @_; | |
241 | ||
242 | my $from = $self->{"$ruleid:from"}; | |
243 | ||
64ba4c0d | 244 | return 1 if scalar($from->{groups}->@*) == 0; |
c881fe35 | 245 | |
61954d8d | 246 | # postfix prefixes ipv6 addresses with IPv6: |
a34b95db | 247 | if (defined($ip) && $ip =~ /^IPv6:(.*)/) { |
61954d8d DC |
248 | $ip = $1; |
249 | } | |
250 | ||
64ba4c0d DC |
251 | for my $group ($from->{groups}->@*) { |
252 | for my $obj ($group->{objects}->@*) { | |
253 | return 1 if $obj->who_match($addr, $ip, $ldap); | |
254 | } | |
c881fe35 DM |
255 | } |
256 | ||
257 | return 0; | |
258 | } | |
259 | ||
260 | sub to_match { | |
261 | my ($self, $ruleid, $addr, $ldap) = @_; | |
262 | ||
263 | my $to = $self->{"$ruleid:to"}; | |
264 | ||
64ba4c0d | 265 | return 1 if scalar($to->{groups}->@*) == 0; |
c881fe35 | 266 | |
64ba4c0d DC |
267 | for my $group ($to->{groups}->@*) { |
268 | for my $obj ($group->{objects}->@*) { | |
269 | return 1 if $obj->who_match($addr, undef, $ldap); | |
270 | } | |
c881fe35 DM |
271 | } |
272 | ||
64ba4c0d | 273 | |
c881fe35 DM |
274 | return 0; |
275 | } | |
276 | ||
277 | sub when_match { | |
278 | my ($self, $ruleid, $time) = @_; | |
279 | ||
280 | my $when = $self->{"$ruleid:when"}; | |
281 | ||
64ba4c0d | 282 | return 1 if scalar($when->{groups}->@*) == 0; |
c881fe35 | 283 | |
64ba4c0d DC |
284 | for my $group ($when->{groups}->@*) { |
285 | for my $obj ($group->{objects}->@*) { | |
286 | return 1 if $obj->when_match($time); | |
287 | } | |
c881fe35 DM |
288 | } |
289 | ||
290 | return 0; | |
291 | } | |
292 | ||
293 | sub what_match { | |
294 | my ($self, $ruleid, $queue, $element, $msginfo, $dbh) = @_; | |
295 | ||
296 | my $what = $self->{"$ruleid:what"}; | |
297 | ||
298 | my $res; | |
299 | ||
300 | # $res->{marks} is used by mark specific actions like remove-attachments | |
301 | # $res->{$target}->{marks} is only used in apply_rules() to exclude some | |
302 | # targets (spam blacklist and whitelist) | |
303 | ||
64ba4c0d | 304 | if (scalar($what->{groups}->@*) == 0) { |
c881fe35 DM |
305 | # match all targets |
306 | foreach my $target (@{$msginfo->{targets}}) { | |
307 | $res->{$target}->{marks} = []; | |
308 | } | |
309 | ||
310 | $res->{marks} = []; | |
311 | return $res; | |
312 | } | |
313 | ||
314 | my $marks; | |
315 | ||
64ba4c0d DC |
316 | for my $group ($what->{groups}->@*) { |
317 | for my $obj ($group->{objects}->@*) { | |
318 | if (!$obj->can('what_match_targets')) { | |
319 | if (my $match = $obj->what_match($queue, $element, $msginfo, $dbh)) { | |
320 | push @$marks, @$match; | |
321 | } | |
c881fe35 DM |
322 | } |
323 | } | |
324 | } | |
325 | ||
326 | foreach my $target (@{$msginfo->{targets}}) { | |
327 | $res->{$target}->{marks} = $marks; | |
328 | $res->{marks} = $marks; | |
329 | } | |
330 | ||
64ba4c0d DC |
331 | for my $group ($what->{groups}->@*) { |
332 | for my $obj ($group->{objects}->@*) { | |
333 | if ($obj->can ("what_match_targets")) { | |
334 | my $target_info; | |
335 | if ($target_info = $obj->what_match_targets($queue, $element, $msginfo, $dbh)) { | |
336 | foreach my $k (keys %$target_info) { | |
337 | $res->{$k} = $target_info->{$k}; | |
338 | } | |
c881fe35 DM |
339 | } |
340 | } | |
341 | } | |
342 | } | |
343 | ||
344 | return $res; | |
345 | } | |
346 | ||
347 | 1; |