File Coverage

blib/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm
Criterion Covered Total %
statement 59 335 17.6
branch 0 130 0.0
condition 1 49 2.0
subroutine 15 25 60.0
pod 1 3 33.3
total 76 542 14.0


line stmt bran cond sub pod time code
1             # <@LICENSE>
2             # Licensed to the Apache Software Foundation (ASF) under one or more
3             # contributor license agreements. See the NOTICE file distributed with
4             # this work for additional information regarding copyright ownership.
5             # The ASF licenses this file to you under the Apache License, Version 2.0
6             # (the "License"); you may not use this file except in compliance with
7             # the License. You may obtain a copy of the License at:
8             #
9             # http://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing, software
12             # distributed under the License is distributed on an "AS IS" BASIS,
13             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14             # See the License for the specific language governing permissions and
15             # limitations under the License.
16             # </@LICENSE>
17              
18             =head1 NAME
19              
20             URILocalBL - blacklist URIs using local information (ISP names, address lists, and country codes)
21              
22             =head1 SYNOPSIS
23              
24             This plugin creates some new rule test types, such as "uri_block_cc",
25             "uri_block_cidr", and "uri_block_isp". These rules apply to the URIs
26             found in the HTML portion of a message, i.e. <a href=...> markup.
27              
28             loadplugin Mail::SpamAssassin::Plugin::URILocalBL
29              
30             Why local blacklisting? There are a few excellent, effective, and
31             well-maintained DNSBL's out there. But they have several drawbacks:
32              
33             =over 2
34              
35             =item * blacklists can cover tens of thousands of entries, and you can't select which ones you use;
36              
37             =item * verifying that it's correctly configured can be non-trivial;
38              
39             =item * new blacklisting entries may take a while to be detected and entered, so it's not instantaneous.
40              
41             =back
42              
43             Sometimes all you want is a quick, easy, and very surgical blacklisting of
44             a particular site or a particular ISP. This plugin is defined for that
45             exact usage case.
46              
47             =head1 RULE DEFINITIONS AND PRIVILEGED SETTINGS
48              
49             The format for defining a rule is as follows:
50              
51             uri_block_cc SYMBOLIC_TEST_NAME cc1 cc2 cc3 cc4
52              
53             or:
54              
55             uri_block_cont SYMBOLIC_TEST_NAME co1 co2 co3 co4
56              
57             or:
58              
59             uri_block_cidr SYMBOLIC_TEST_NAME a.a.a.a b.b.b.b/cc d.d.d.d-e.e.e.e
60              
61             or:
62              
63             uri_block_isp SYMBOLIC_TEST_NAME "DataRancid" "McCarrier" "Phishers-r-Us"
64              
65             Example rule for matching a URI in China:
66              
67             uri_block_cc TEST1 cn
68              
69             This would block the URL http://www.baidu.com/index.htm. Similarly, to
70             match a Spam-haven netblock:
71              
72             uri_block_cidr TEST2 65.181.64.0/18
73              
74             would match a netblock where several phishing sites were recently hosted.
75              
76             And to block all CIDR blocks registered to an ISP, one might use:
77              
78             uri_block_isp TEST3 "ColoCrossing"
79              
80             if one didn't trust URL's pointing to that organization's clients. Lastly,
81             if there's a country that you want to block but there's an explicit host
82             you wish to exempt from that blacklist, you can use:
83              
84             uri_block_exclude TEST1 www.baidu.com
85              
86             if you wish to exempt URL's referring to this host. The same syntax is
87             applicable to CIDR and ISP blocks as well.
88              
89             =head1 DEPENDENCIES
90              
91             The Country-Code based filtering requires the Geo::IP or GeoIP2 module,
92             which uses either the fremium GeoLiteCountry database, or the commercial
93             version of it called GeoIP from MaxMind.com.
94              
95             The ISP based filtering requires the same module, plus the GeoIPISP database.
96             There is no fremium version of this database, so commercial licensing is
97             required.
98              
99             =cut
100              
101             use Mail::SpamAssassin::Plugin;
102 20     20   142 use Mail::SpamAssassin::Logger;
  20         48  
  20         562  
103 20     20   138 use Mail::SpamAssassin::Constants qw(:ip);
  20         38  
  20         1122  
104 20     20   154 use Mail::SpamAssassin::Util qw(untaint_var);
  20         49  
  20         2289  
105 20     20   138  
  20         40  
  20         866  
106             use Socket;
107 20     20   145  
  20         34  
  20         10756  
108             use strict;
109 20     20   141 use warnings;
  20         36  
  20         467  
110 20     20   95 # use bytes;
  20         48  
  20         647  
111             use re 'taint';
112 20     20   107 use version;
  20         44  
  20         679  
113 20     20   110  
  20         32  
  20         138  
114             our @ISA = qw(Mail::SpamAssassin::Plugin);
115              
116             use constant HAS_GEOIP => eval { require Geo::IP; };
117 20     20   1915 use constant HAS_GEOIP2 => eval { require GeoIP2::Database::Reader; };
  20         37  
  20         43  
  20         3905  
118 20     20   106 use constant HAS_CIDR => eval { require Net::CIDR::Lite; };
  20         36  
  20         34  
  20         3680  
119 20     20   122  
  20         35  
  20         39  
  20         41564  
120             # constructor
121             my $class = shift;
122             my $mailsaobject = shift;
123 61     61 1 177  
124 61         164 # some boilerplate...
125             $class = ref($class) || $class;
126             my $self = $class->SUPER::new($mailsaobject);
127 61   33     427 bless ($self, $class);
128 61         339  
129 61         158 # how to handle failure to get the database handle?
130             # and we don't really have a valid return value...
131             # can we defer getting this handle until we actually see
132             # a uri_block_cc rule?
133              
134             $self->register_eval_rule("check_uri_local_bl");
135              
136 61         267 $self->set_config($mailsaobject->{conf});
137              
138 61         322 return $self;
139             }
140 61         526  
141             my ($self, $conf) = @_;
142             my @cmds;
143              
144 61     61 0 178 my $pluginobj = $self; # allow use inside the closure below
145 61         124  
146             push (@cmds, {
147 61         96 setting => 'uri_block_cc',
148             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
149             is_priv => 1,
150             code => sub {
151             my ($self, $key, $value, $line) = @_;
152              
153             if ($value !~ /^(\S+)\s+(.+)$/) {
154 0     0   0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
155             }
156 0 0       0 my $name = $1;
157 0         0 my $def = $2;
158             my $added_criteria = 0;
159 0         0  
160 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{countries} = {};
161 0         0  
162             # this should match all country codes including satellite providers
163 0         0 while ($def =~ m/^\s*([a-z][a-z0-9])(\s+(.*)|)$/) {
164             my $cc = $1;
165             my $rest = $2;
166 0         0  
167 0         0 #dbg("config: uri_block_cc adding %s to %s\n", $cc, $name);
168 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{countries}->{uc($cc)} = 1;
169             $added_criteria = 1;
170              
171 0         0 $def = $rest;
172 0         0 }
173              
174 0         0 if ($added_criteria == 0) {
175             warn "config: no arguments";
176             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
177 0 0       0 } elsif ($def ne '') {
    0          
178 0         0 warn "config: failed to add invalid rule $name";
179 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
180             }
181 0         0  
182 0         0 dbg("config: uri_block_cc added %s\n", $name);
183              
184             $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
185 0         0 }
186             });
187 0         0  
188             push (@cmds, {
189 61         626 setting => 'uri_block_cont',
190             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
191             is_priv => 1,
192             code => sub {
193             my ($self, $key, $value, $line) = @_;
194              
195             if ($value !~ /^(\S+)\s+(.+)$/) {
196 0     0   0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
197             }
198 0 0       0 my $name = $1;
199 0         0 my $def = $2;
200             my $added_criteria = 0;
201 0         0  
202 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{continents} = {};
203 0         0  
204             # this should match all continent codes
205 0         0 while ($def =~ m/^\s*([a-z]{2})(\s+(.*)|)$/) {
206             my $cont = $1;
207             my $rest = $2;
208 0         0  
209 0         0 # dbg("config: uri_block_cont adding %s to %s\n", $cont, $name);
210 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{continents}->{uc($cont)} = 1;
211             $added_criteria = 1;
212              
213 0         0 $def = $rest;
214 0         0 }
215              
216 0         0 if ($added_criteria == 0) {
217             warn "config: no arguments";
218             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
219 0 0       0 } elsif ($def ne '') {
    0          
220 0         0 warn "config: failed to add invalid rule $name";
221 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
222             }
223 0         0  
224 0         0 dbg("config: uri_block_cont added %s\n", $name);
225              
226             $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
227 0         0 }
228             });
229 0         0
230             push (@cmds, {
231 61         507 setting => 'uri_block_isp',
232             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
233             is_priv => 1,
234             code => sub {
235             my ($self, $key, $value, $line) = @_;
236              
237             if ($value !~ /^(\S+)\s+(.+)$/) {
238 0     0   0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
239             }
240 0 0       0 my $name = $1;
241 0         0 my $def = $2;
242             my $added_criteria = 0;
243 0         0  
244 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{isps} = {};
245 0         0  
246             # gather up quoted strings
247 0         0 while ($def =~ m/^\s*"([^"]*)"(\s+(.*)|)$/) {
248             my $isp = $1;
249             my $rest = $2;
250 0         0  
251 0         0 dbg("config: uri_block_isp adding \"%s\" to %s\n", $isp, $name);
252 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{isps}->{$isp} = 1;
253             $added_criteria = 1;
254 0         0  
255 0         0 $def = $rest;
256 0         0 }
257              
258 0         0 if ($added_criteria == 0) {
259             warn "config: no arguments";
260             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
261 0 0       0 } elsif ($def ne '') {
    0          
262 0         0 warn "config: failed to add invalid rule $name";
263 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
264             }
265 0         0  
266 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
267             }
268             });
269 0         0  
270             push (@cmds, {
271 61         488 setting => 'uri_block_cidr',
272             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
273             is_priv => 1,
274             code => sub {
275             my ($self, $key, $value, $line) = @_;
276              
277             if (!HAS_CIDR) {
278 0     0   0 warn "config: uri_block_cidr not supported, required module Net::CIDR::Lite missing\n";
279             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
280 0 0       0 }
281 0         0  
282 0         0 if ($value !~ /^(\S+)\s+(.+)$/) {
283             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
284             }
285 0 0       0 my $name = $1;
286 0         0 my $def = $2;
287             my $added_criteria = 0;
288 0         0  
289 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr} = new Net::CIDR::Lite;
290 0         0  
291             # match individual IP's, subnets, and ranges
292 0         0 while ($def =~ m/^\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2}|-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?)(\s+(.*)|)$/) {
293             my $addr = $1;
294             my $rest = $3;
295 0         0  
296 0         0 dbg("config: uri_block_cidr adding %s to %s\n", $addr, $name);
297 0         0  
298             eval { $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr}->add_any($addr) };
299 0         0 last if ($@);
300              
301 0         0 $added_criteria = 1;
  0         0  
302 0 0       0  
303             $def = $rest;
304 0         0 }
305              
306 0         0 if ($added_criteria == 0) {
307             warn "config: no arguments";
308             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
309 0 0       0 } elsif ($def ne '') {
    0          
310 0         0 warn "config: failed to add invalid rule $name";
311 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
312             }
313 0         0  
314 0         0 # optimize the ranges
315             $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr}->clean();
316              
317             $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
318 0         0 }
319             });
320 0         0  
321             push (@cmds, {
322 61         490 setting => 'uri_block_exclude',
323             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
324             is_priv => 1,
325             code => sub {
326             my ($self, $key, $value, $line) = @_;
327              
328             if ($value !~ /^(\S+)\s+(.+)$/) {
329 0     0   0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
330             }
331 0 0       0 my $name = $1;
332 0         0 my $def = $2;
333             my $added_criteria = 0;
334 0         0  
335 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{exclusions} = {};
336 0         0  
337             # match individual IP's, or domain names
338 0         0 while ($def =~ m/^\s*((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(([a-z0-9][-a-z0-9]*[a-z0-9](\.[a-z0-9][-a-z0-9]*[a-z0-9]){1,})))(\s+(.*)|)$/) {
339             my $addr = $1;
340             my $rest = $6;
341 0         0  
342 0         0 dbg("config: uri_block_exclude adding %s to %s\n", $addr, $name);
343 0         0  
344             $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{exclusions}->{$addr} = 1;
345 0         0  
346             $added_criteria = 1;
347 0         0  
348             $def = $rest;
349 0         0 }
350              
351 0         0 if ($added_criteria == 0) {
352             warn "config: no arguments";
353             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
354 0 0       0 } elsif ($def ne '') {
    0          
355 0         0 warn "config: failed to add invalid rule $name";
356 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
357             }
358 0         0  
359 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
360             }
361             });
362 0         0  
363             =over 2
364 61         456  
365             =item uri_country_db_path STRING
366              
367             This option tells SpamAssassin where to find the MaxMind country GeoIP2
368             database. Country or City database are both supported.
369              
370             =back
371              
372             =cut
373              
374             push (@cmds, {
375             setting => 'uri_country_db_path',
376             is_priv => 1,
377             default => undef,
378             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
379             code => sub {
380             my ($self, $key, $value, $line) = @_;
381             if (!defined $value || !length $value) {
382             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
383 0     0   0 }
384 0 0 0     0 if (!-f $value) {
385 0         0 info("config: uri_country_db_path \"$value\" is not accessible");
386             $self->{uri_country_db_path} = $value;
387 0 0       0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
388 0         0 }
389 0         0  
390 0         0 $self->{uri_country_db_path} = $value;
391             }
392             });
393 0         0  
394             =over 2
395 61         514  
396             =item uri_country_db_isp_path STRING
397              
398             This option tells SpamAssassin where to find the MaxMind isp GeoIP2 database.
399              
400             =back
401              
402             =cut
403              
404             push (@cmds, {
405             setting => 'uri_country_db_isp_path',
406             is_priv => 1,
407             default => undef,
408             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
409             code => sub {
410             my ($self, $key, $value, $line) = @_;
411             if (!defined $value || !length $value) {
412             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
413 0     0   0 }
414 0 0 0     0 if (!-f $value) {
415 0         0 info("config: uri_country_db_isp_path \"$value\" is not accessible");
416             $self->{uri_country_db_isp_path} = $value;
417 0 0       0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
418 0         0 }
419 0         0  
420 0         0 $self->{uri_country_db_isp_path} = $value;
421             }
422             });
423 0         0
424             $conf->{parser}->register_commands(\@cmds);
425 61         478 }
426              
427 61         277 my ($self, $permsg) = @_;
428              
429             my $cc;
430             my $cont;
431 0     0 0   my $db_info;
432             my $isp;
433 0          
434             my $conf_country_db_path = $self->{'main'}{'resolver'}{'conf'}->{uri_country_db_path};
435 0           my $conf_country_db_isp_path = $self->{'main'}{'resolver'}{'conf'}->{uri_country_db_isp_path};
436 0           # If country_db_path is set I am using GeoIP2 api
437             if ( HAS_GEOIP2 and ( ( defined $conf_country_db_path ) or ( defined $conf_country_db_isp_path ) ) ) {
438 0            
439 0           eval {
440             $self->{geoip} = GeoIP2::Database::Reader->new(
441 0 0 0       file => $conf_country_db_path,
    0 0        
442             locales => [ 'en' ]
443 0           ) if (( defined $conf_country_db_path ) && ( -f $conf_country_db_path));
444 0 0 0       if ( defined ($conf_country_db_path) ) {
445             $db_info = sub { return "GeoIP2 " . ($self->{geoip}->metadata()->description()->{en} || '?') };
446             warn "$conf_country_db_path not found" unless $self->{geoip};
447             }
448 0 0          
449 0   0 0     $self->{geoisp} = GeoIP2::Database::Reader->new(
  0            
450 0 0         file => $conf_country_db_isp_path,
451             locales => [ 'en' ]
452             ) if (( defined $conf_country_db_isp_path ) && ( -f $conf_country_db_isp_path));
453 0 0 0       if ( defined ($conf_country_db_isp_path) ) {
454             warn "$conf_country_db_isp_path not found" unless $self->{geoisp};
455             }
456             $self->{use_geoip2} = 1;
457 0 0         };
458 0 0         if ($@ || !($self->{geoip} || $self->{geoisp})) {
459             $@ =~ s/\s+Trace begun.*//s;
460 0           warn "URILocalBL: GeoIP2 load failed: $@\n";
461             return 0;
462 0 0 0       }
      0        
463 0            
464 0           } elsif ( HAS_GEOIP ) {
465 0           BEGIN {
466             Geo::IP->import( qw(GEOIP_MEMORY_CACHE GEOIP_CHECK_CACHE GEOIP_ISP_EDITION) );
467             }
468             $self->{use_geoip2} = 0;
469             # need GeoIP C library 1.6.3 and GeoIP perl API 1.4.4 or later to avoid messages leaking - Bug 7153
470 20     20   26084 my $gic_wanted = version->parse('v1.6.3');
471             my $gic_have = version->parse(Geo::IP->lib_version());
472 0           my $gip_wanted = version->parse('v1.4.4');
473             my $gip_have = version->parse($Geo::IP::VERSION);
474 0            
475 0           # this code burps an ugly message if it fails, but that's redirected elsewhere
476 0           my $flags = 0;
477 0           my $flag_isp = 0;
478             my $flag_silent = 0;
479             eval '$flags = GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE' if ($gip_have >= $gip_wanted);
480 0           eval '$flag_silent = GEOIP_SILENCE' if ($gip_have >= $gip_wanted);
481 0           eval '$flag_isp = GEOIP_ISP_EDITION' if ($gip_have >= $gip_wanted);
482 0            
483 0 0         eval {
484 0 0         if ($flag_silent && $gic_have >= $gic_wanted) {
485 0 0         $self->{geoip} = Geo::IP->new($flags | $flag_silent);
486             $self->{geoisp} = Geo::IP->open_type($flag_isp, $flag_silent | $flags);
487 0           } else {
488 0 0 0       open(OLDERR, ">&STDERR");
489 0           open(STDERR, ">", "/dev/null");
490 0           $self->{geoip} = Geo::IP->new($flags);
491             $self->{geoisp} = Geo::IP->open_type($flag_isp);
492 0           open(STDERR, ">&OLDERR");
493 0           close(OLDERR);
494 0           }
495 0           };
496 0           if ($@ || !($self->{geoip} || $self->{geoisp})) {
497 0           $@ =~ s/\s+Trace begun.*//s;
498             warn "URILocalBL: GeoIP load failed: $@\n";
499             return 0;
500 0 0 0       }
      0        
501 0            
502 0           $db_info = sub { return "Geo::IP " . ($self->{geoip}->database_info || '?') };
503 0           } else {
504             dbg("No GeoIP module available");
505             return 0;
506 0   0 0     }
  0            
507              
508 0           my %uri_detail = %{ $permsg->get_uri_detail_list() };
509 0           my $test = $permsg->{current_rule_name};
510             my $rule = $permsg->{conf}->{uri_local_bl}->{$test};
511              
512 0           my %hit_tests;
  0            
513 0           my $got_hit = 0;
514 0           my @addrs;
515             my $IP_ADDRESS = IP_ADDRESS;
516 0          
517 0           if ( defined $self->{geoip} ) {
518 0           dbg("check: uri_local_bl evaluating rule %s using database %s\n", $test, $db_info->());
519 0           } else {
520             dbg("check: uri_local_bl evaluating rule %s\n", $test);
521 0 0         }
522 0            
523             my $dns_available = $permsg->is_dns_available();
524 0            
525             while (my ($raw, $info) = each %uri_detail) {
526              
527 0           next unless $info->{hosts};
528              
529 0           # look for W3 links only
530             next unless (defined $info->{types}->{a} || defined $info->{types}->{parsed});
531 0 0          
532             while (my($host, $domain) = each %{$info->{hosts}}) {
533              
534 0 0 0       # skip if the domain name was matched
535             if (exists $rule->{exclusions} && exists $rule->{exclusions}->{$domain}) {
536 0           dbg("check: uri_local_bl excludes %s as *.%s\n", $host, $domain);
  0            
537             next;
538             }
539 0 0 0        
540 0           if($host !~ /^$IP_ADDRESS$/) {
541 0           if (!$dns_available) {
542             dbg("check: uri_local_bl skipping $host, dns not available");
543             next;
544 0 0         }
545 0 0         # this would be best cached from prior lookups
546 0           @addrs = gethostbyname($host);
547 0           # convert to string values address list
548             @addrs = map { inet_ntoa($_); } @addrs[4..$#addrs];
549             } else {
550 0           @addrs = ($host);
551             }
552 0            
  0            
553             dbg("check: uri_local_bl %s addrs %s\n", $host, join(', ', @addrs));
554 0            
555             for my $ip (@addrs) {
556             # skip if the address was matched
557 0           if (exists $rule->{exclusions} && exists $rule->{exclusions}->{$ip}) {
558             dbg("check: uri_local_bl excludes %s(%s)\n", $host, $ip);
559 0           next;
560             }
561 0 0 0        
562 0           if (exists $rule->{countries}) {
563 0           dbg("check: uri_local_bl countries %s\n", join(' ', sort keys %{$rule->{countries}}));
564              
565             if ( $self->{use_geoip2} == 1 ) {
566 0 0         my $country;
567 0           if (index($self->{geoip}->metadata()->description()->{en}, 'City') != -1) {
  0            
568             $country = $self->{geoip}->city( ip => $ip );
569 0 0         } else {
570 0           $country = $self->{geoip}->country( ip => $ip );
571 0 0         }
572 0           my $country_rec = $country->country();
573             $cc = $country_rec->iso_code();
574 0           } else {
575             $cc = $self->{geoip}->country_code_by_addr($ip);
576 0           }
577 0            
578             dbg("check: uri_local_bl host %s(%s) maps to %s\n", $host, $ip, (defined $cc ? $cc : "(undef)"));
579 0            
580             # handle there being no associated country (yes, there are holes in
581             # the database).
582 0 0         next unless defined $cc;
583              
584             # not in blacklist
585             next unless (exists $rule->{countries}->{$cc});
586 0 0          
587             dbg("check: uri_block_cc host %s(%s) matched\n", $host, $ip);
588              
589 0 0         if (would_log('dbg', 'rules') > 1) {
590             dbg("check: uri_block_cc criteria for $test met");
591 0           }
592            
593 0 0         $permsg->test_log("Host: $host in $cc");
594 0           $hit_tests{$test} = 1;
595              
596             # reset hash
597 0           keys %uri_detail;
598 0           }
599              
600             if (exists $rule->{continents}) {
601 0           dbg("check: uri_local_bl continents %s\n", join(' ', sort keys %{$rule->{continents}}));
602              
603             if ( $self->{use_geoip2} == 1 ) {
604 0 0         my $country = $self->{geoip}->country( ip => $ip );
605 0           my $cont_rec = $country->continent();
  0            
606             $cont = $cont_rec->{code};
607 0 0         } else {
608 0           $cc = $self->{geoip}->country_code_by_addr($ip);
609 0           $cont = $self->{geoip}->continent_code_by_country_code($cc);
610 0           }
611            
612 0           dbg("check: uri_local_bl host %s(%s) maps to %s\n", $host, $ip, (defined $cont ? $cont : "(undef)"));
613 0            
614             # handle there being no associated continent (yes, there are holes in
615             # the database).
616 0 0         next unless defined $cont;
617              
618             # not in blacklist
619             next unless (exists $rule->{continents}->{$cont});
620 0 0          
621             dbg("check: uri_block_cont host %s(%s) matched\n", $host, $ip);
622              
623 0 0         if (would_log('dbg', 'rules') > 1) {
624             dbg("check: uri_block_cont criteria for $test met");
625 0           }
626              
627 0 0         $permsg->test_log("Host: $host in $cont");
628 0           $hit_tests{$test} = 1;
629              
630             # reset hash
631 0           keys %uri_detail;
632 0           }
633              
634             if (exists $rule->{isps}) {
635 0           dbg("check: uri_local_bl isps %s\n", join(' ', map { '"' . $_ . '"'; } sort keys %{$rule->{isps}}));
636              
637             if ( $self->{use_geoip2} == 1 ) {
638 0 0         $isp = $self->{geoisp}->isp(ip => $ip);
639 0           } else {
  0            
  0            
640             $isp = $self->{geoisp}->isp_by_name($ip);
641 0 0         }
642 0            
643             dbg("check: uri_local_bl isp %s(%s) maps to %s\n", $host, $ip, (defined $isp ? '"' . $isp . '"' : "(undef)"));
644 0            
645             # handle there being no associated country
646             next unless defined $isp;
647 0 0          
648             # not in blacklist
649             next unless (exists $rule->{isps}->{$isp});
650 0 0          
651             dbg("check: uri_block_isp host %s(%s) matched\n", $host, $ip);
652              
653 0 0         if (would_log('dbg', 'rules') > 1) {
654             dbg("check: uri_block_isp criteria for $test met");
655 0           }
656            
657 0 0         $permsg->test_log("Host: $host in \"$isp\"");
658 0           $hit_tests{$test} = 1;
659              
660             # reset hash
661 0           keys %uri_detail;
662 0           }
663              
664             if (exists $rule->{cidr}) {
665 0           dbg("check: uri_block_cidr list %s\n", join(' ', $rule->{cidr}->list_range()));
666              
667             next unless ($rule->{cidr}->find($ip));
668 0 0          
669 0           dbg("check: uri_block_cidr host %s(%s) matched\n", $host, $ip);
670              
671 0 0         if (would_log('dbg', 'rules') > 1) {
672             dbg("check: uri_block_cidr criteria for $test met");
673 0           }
674              
675 0 0         $permsg->test_log("Host: $host as $ip");
676 0           $hit_tests{$test} = 1;
677              
678             # reset hash
679 0           keys %uri_detail;
680 0           }
681             }
682             }
683 0           # cycle through all tests hitted by the uri
684             while((my $test_ok) = each %hit_tests) {
685             $permsg->got_hit($test_ok);
686             $got_hit = 1;
687             }
688 0           if($got_hit == 1) {
689 0           return 1;
690 0           } else {
691             keys %hit_tests;
692 0 0         }
693 0           }
694              
695 0           dbg("check: uri_local_bl %s no match\n", $test);
696              
697             return 0;
698             }
699 0            
700             1;
701 0