]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/RuleCache.pm
fd22a16ddb851da359eff5d417b116419d8b286d
[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 $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};
79 my $objects = [];
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
92 push @$objects, $obj;
93
94 if ($gtype == 3) { # what
95 if ($obj->otype == PMG::RuleDB::ArchiveFilter->otype ||
96 $obj->otype == PMG::RuleDB::MatchArchiveFilename->otype)
97 {
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
108 $self->{"$ruleid:final"} = 1 if $obj->final();
109 }
110 }
111 $sth2->finish();
112
113 my $group = {
114 objects => $objects,
115 };
116
117 my $type = $type_map->{$gtype};
118 push $self->{"$ruleid:$type"}->{groups}->@*, $group;
119 }
120
121 $sth1->finish();
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 }
142 $sha1->add ($ref2->{'id'}, "|");
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
155 syslog ('err', "unable to load rulecache : $err") if $err;
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
167 defined($ruleid) || die "undefined rule id: ERROR";
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
190 $obj || die "unable to get object $objid: ERROR";
191
192 return $obj;
193 }
194
195 sub get_actions {
196 my ($self, $ruleid) = @_;
197
198 defined($ruleid) || die "undefined rule id: ERROR";
199
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;
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
244 return 1 if scalar($from->{groups}->@*) == 0;
245
246 # postfix prefixes ipv6 addresses with IPv6:
247 if (defined($ip) && $ip =~ /^IPv6:(.*)/) {
248 $ip = $1;
249 }
250
251 for my $group ($from->{groups}->@*) {
252 for my $obj ($group->{objects}->@*) {
253 return 1 if $obj->who_match($addr, $ip, $ldap);
254 }
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
265 return 1 if scalar($to->{groups}->@*) == 0;
266
267 for my $group ($to->{groups}->@*) {
268 for my $obj ($group->{objects}->@*) {
269 return 1 if $obj->who_match($addr, undef, $ldap);
270 }
271 }
272
273
274 return 0;
275 }
276
277 sub when_match {
278 my ($self, $ruleid, $time) = @_;
279
280 my $when = $self->{"$ruleid:when"};
281
282 return 1 if scalar($when->{groups}->@*) == 0;
283
284 for my $group ($when->{groups}->@*) {
285 for my $obj ($group->{objects}->@*) {
286 return 1 if $obj->when_match($time);
287 }
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
304 if (scalar($what->{groups}->@*) == 0) {
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
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 }
322 }
323 }
324 }
325
326 foreach my $target (@{$msginfo->{targets}}) {
327 $res->{$target}->{marks} = $marks;
328 $res->{marks} = $marks;
329 }
330
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 }
339 }
340 }
341 }
342 }
343
344 return $res;
345 }
346
347 1;