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             package Mail::SpamAssassin::Plugin::URILocalBL;
102 19     19   148 use Mail::SpamAssassin::Plugin;
  19         45  
  19         638  
103 19     19   107 use Mail::SpamAssassin::Logger;
  19         53  
  19         1160  
104 19     19   123 use Mail::SpamAssassin::Constants qw(:ip);
  19         44  
  19         2315  
105 19     19   156 use Mail::SpamAssassin::Util qw(untaint_var);
  19         52  
  19         959  
106              
107 19     19   137 use Socket;
  19         38  
  19         11690  
108              
109 19     19   162 use strict;
  19         42  
  19         469  
110 19     19   154 use warnings;
  19         40  
  19         699  
111             # use bytes;
112 19     19   128 use re 'taint';
  19         40  
  19         673  
113 19     19   618 use version;
  19         2077  
  19         163  
114              
115             our @ISA = qw(Mail::SpamAssassin::Plugin);
116              
117 19     19   2108 use constant HAS_GEOIP => eval { require Geo::IP; };
  19         43  
  19         45  
  19         4490  
118 19     19   111 use constant HAS_GEOIP2 => eval { require GeoIP2::Database::Reader; };
  19         39  
  19         39  
  19         4258  
119 19     19   132 use constant HAS_CIDR => eval { require Net::CIDR::Lite; };
  19         40  
  19         34  
  19         45338  
120              
121             # constructor
122             sub new {
123 60     60 1 234 my $class = shift;
124 60         196 my $mailsaobject = shift;
125              
126             # some boilerplate...
127 60   33     564 $class = ref($class) || $class;
128 60         368 my $self = $class->SUPER::new($mailsaobject);
129 60         204 bless ($self, $class);
130              
131             # how to handle failure to get the database handle?
132             # and we don't really have a valid return value...
133             # can we defer getting this handle until we actually see
134             # a uri_block_cc rule?
135              
136 60         319 $self->register_eval_rule("check_uri_local_bl");
137              
138 60         406 $self->set_config($mailsaobject->{conf});
139              
140 60         666 return $self;
141             }
142              
143             sub set_config {
144 60     60 0 227 my ($self, $conf) = @_;
145 60         137 my @cmds;
146              
147 60         135 my $pluginobj = $self; # allow use inside the closure below
148              
149             push (@cmds, {
150             setting => 'uri_block_cc',
151             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
152             is_priv => 1,
153             code => sub {
154 0     0   0 my ($self, $key, $value, $line) = @_;
155              
156 0 0       0 if ($value !~ /^(\S+)\s+(.+)$/) {
157 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
158             }
159 0         0 my $name = $1;
160 0         0 my $def = $2;
161 0         0 my $added_criteria = 0;
162              
163 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{countries} = {};
164              
165             # this should match all country codes including satellite providers
166 0         0 while ($def =~ m/^\s*([a-z][a-z0-9])(\s+(.*)|)$/) {
167 0         0 my $cc = $1;
168 0         0 my $rest = $2;
169              
170             #dbg("config: uri_block_cc adding %s to %s\n", $cc, $name);
171 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{countries}->{uc($cc)} = 1;
172 0         0 $added_criteria = 1;
173              
174 0         0 $def = $rest;
175             }
176              
177 0 0       0 if ($added_criteria == 0) {
    0          
178 0         0 warn "config: no arguments";
179 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
180             } elsif ($def ne '') {
181 0         0 warn "config: failed to add invalid rule $name";
182 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
183             }
184              
185 0         0 dbg("config: uri_block_cc added %s\n", $name);
186              
187 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
188             }
189 60         702 });
190              
191             push (@cmds, {
192             setting => 'uri_block_cont',
193             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
194             is_priv => 1,
195             code => sub {
196 0     0   0 my ($self, $key, $value, $line) = @_;
197              
198 0 0       0 if ($value !~ /^(\S+)\s+(.+)$/) {
199 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
200             }
201 0         0 my $name = $1;
202 0         0 my $def = $2;
203 0         0 my $added_criteria = 0;
204              
205 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{continents} = {};
206              
207             # this should match all continent codes
208 0         0 while ($def =~ m/^\s*([a-z]{2})(\s+(.*)|)$/) {
209 0         0 my $cont = $1;
210 0         0 my $rest = $2;
211              
212             # dbg("config: uri_block_cont adding %s to %s\n", $cont, $name);
213 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{continents}->{uc($cont)} = 1;
214 0         0 $added_criteria = 1;
215              
216 0         0 $def = $rest;
217             }
218              
219 0 0       0 if ($added_criteria == 0) {
    0          
220 0         0 warn "config: no arguments";
221 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
222             } elsif ($def ne '') {
223 0         0 warn "config: failed to add invalid rule $name";
224 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
225             }
226              
227 0         0 dbg("config: uri_block_cont added %s\n", $name);
228              
229 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
230             }
231 60         603 });
232            
233             push (@cmds, {
234             setting => 'uri_block_isp',
235             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
236             is_priv => 1,
237             code => sub {
238 0     0   0 my ($self, $key, $value, $line) = @_;
239              
240 0 0       0 if ($value !~ /^(\S+)\s+(.+)$/) {
241 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
242             }
243 0         0 my $name = $1;
244 0         0 my $def = $2;
245 0         0 my $added_criteria = 0;
246              
247 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{isps} = {};
248              
249             # gather up quoted strings
250 0         0 while ($def =~ m/^\s*"([^"]*)"(\s+(.*)|)$/) {
251 0         0 my $isp = $1;
252 0         0 my $rest = $2;
253              
254 0         0 dbg("config: uri_block_isp adding \"%s\" to %s\n", $isp, $name);
255 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{isps}->{$isp} = 1;
256 0         0 $added_criteria = 1;
257              
258 0         0 $def = $rest;
259             }
260              
261 0 0       0 if ($added_criteria == 0) {
    0          
262 0         0 warn "config: no arguments";
263 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
264             } elsif ($def ne '') {
265 0         0 warn "config: failed to add invalid rule $name";
266 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
267             }
268              
269 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
270             }
271 60         568 });
272              
273             push (@cmds, {
274             setting => 'uri_block_cidr',
275             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
276             is_priv => 1,
277             code => sub {
278 0     0   0 my ($self, $key, $value, $line) = @_;
279              
280 0 0       0 if (!HAS_CIDR) {
281 0         0 warn "config: uri_block_cidr not supported, required module Net::CIDR::Lite missing\n";
282 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
283             }
284              
285 0 0       0 if ($value !~ /^(\S+)\s+(.+)$/) {
286 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
287             }
288 0         0 my $name = $1;
289 0         0 my $def = $2;
290 0         0 my $added_criteria = 0;
291              
292 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr} = new Net::CIDR::Lite;
293              
294             # match individual IP's, subnets, and ranges
295 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+(.*)|)$/) {
296 0         0 my $addr = $1;
297 0         0 my $rest = $3;
298              
299 0         0 dbg("config: uri_block_cidr adding %s to %s\n", $addr, $name);
300              
301 0         0 eval { $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr}->add_any($addr) };
  0         0  
302 0 0       0 last if ($@);
303              
304 0         0 $added_criteria = 1;
305              
306 0         0 $def = $rest;
307             }
308              
309 0 0       0 if ($added_criteria == 0) {
    0          
310 0         0 warn "config: no arguments";
311 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
312             } elsif ($def ne '') {
313 0         0 warn "config: failed to add invalid rule $name";
314 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
315             }
316              
317             # optimize the ranges
318 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr}->clean();
319              
320 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
321             }
322 60         600 });
323              
324             push (@cmds, {
325             setting => 'uri_block_exclude',
326             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
327             is_priv => 1,
328             code => sub {
329 0     0   0 my ($self, $key, $value, $line) = @_;
330              
331 0 0       0 if ($value !~ /^(\S+)\s+(.+)$/) {
332 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
333             }
334 0         0 my $name = $1;
335 0         0 my $def = $2;
336 0         0 my $added_criteria = 0;
337              
338 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{exclusions} = {};
339              
340             # match individual IP's, or domain names
341 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+(.*)|)$/) {
342 0         0 my $addr = $1;
343 0         0 my $rest = $6;
344              
345 0         0 dbg("config: uri_block_exclude adding %s to %s\n", $addr, $name);
346              
347 0         0 $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{exclusions}->{$addr} = 1;
348              
349 0         0 $added_criteria = 1;
350              
351 0         0 $def = $rest;
352             }
353              
354 0 0       0 if ($added_criteria == 0) {
    0          
355 0         0 warn "config: no arguments";
356 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
357             } elsif ($def ne '') {
358 0         0 warn "config: failed to add invalid rule $name";
359 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
360             }
361              
362 0         0 $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
363             }
364 60         543 });
365              
366             =over 2
367              
368             =item uri_country_db_path STRING
369              
370             This option tells SpamAssassin where to find the MaxMind country GeoIP2
371             database. Country or City database are both supported.
372              
373             =back
374              
375             =cut
376              
377             push (@cmds, {
378             setting => 'uri_country_db_path',
379             is_priv => 1,
380             default => undef,
381             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
382             code => sub {
383 0     0   0 my ($self, $key, $value, $line) = @_;
384 0 0 0     0 if (!defined $value || !length $value) {
385 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
386             }
387 0 0       0 if (!-f $value) {
388 0         0 info("config: uri_country_db_path \"$value\" is not accessible");
389 0         0 $self->{uri_country_db_path} = $value;
390 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
391             }
392              
393 0         0 $self->{uri_country_db_path} = $value;
394             }
395 60         634 });
396              
397             =over 2
398              
399             =item uri_country_db_isp_path STRING
400              
401             This option tells SpamAssassin where to find the MaxMind isp GeoIP2 database.
402              
403             =back
404              
405             =cut
406              
407             push (@cmds, {
408             setting => 'uri_country_db_isp_path',
409             is_priv => 1,
410             default => undef,
411             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
412             code => sub {
413 0     0   0 my ($self, $key, $value, $line) = @_;
414 0 0 0     0 if (!defined $value || !length $value) {
415 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
416             }
417 0 0       0 if (!-f $value) {
418 0         0 info("config: uri_country_db_isp_path \"$value\" is not accessible");
419 0         0 $self->{uri_country_db_isp_path} = $value;
420 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
421             }
422              
423 0         0 $self->{uri_country_db_isp_path} = $value;
424             }
425 60         504 });
426            
427 60         328 $conf->{parser}->register_commands(\@cmds);
428             }
429              
430             sub check_uri_local_bl {
431 0     0 0   my ($self, $permsg) = @_;
432              
433 0           my $cc;
434             my $cont;
435 0           my $db_info;
436 0           my $isp;
437            
438 0           my $conf_country_db_path = $self->{'main'}{'resolver'}{'conf'}->{uri_country_db_path};
439 0           my $conf_country_db_isp_path = $self->{'main'}{'resolver'}{'conf'}->{uri_country_db_isp_path};
440             # If country_db_path is set I am using GeoIP2 api
441 0 0 0       if ( HAS_GEOIP2 and ( ( defined $conf_country_db_path ) or ( defined $conf_country_db_isp_path ) ) ) {
    0 0        
442              
443 0           eval {
444 0 0 0       $self->{geoip} = GeoIP2::Database::Reader->new(
445             file => $conf_country_db_path,
446             locales => [ 'en' ]
447             ) if (( defined $conf_country_db_path ) && ( -f $conf_country_db_path));
448 0 0         if ( defined ($conf_country_db_path) ) {
449 0   0 0     $db_info = sub { return "GeoIP2 " . ($self->{geoip}->metadata()->description()->{en} || '?') };
  0            
450 0 0         warn "$conf_country_db_path not found" unless $self->{geoip};
451             }
452              
453 0 0 0       $self->{geoisp} = GeoIP2::Database::Reader->new(
454             file => $conf_country_db_isp_path,
455             locales => [ 'en' ]
456             ) if (( defined $conf_country_db_isp_path ) && ( -f $conf_country_db_isp_path));
457 0 0         if ( defined ($conf_country_db_isp_path) ) {
458 0 0         warn "$conf_country_db_isp_path not found" unless $self->{geoisp};
459             }
460 0           $self->{use_geoip2} = 1;
461             };
462 0 0 0       if ($@ || !($self->{geoip} || $self->{geoisp})) {
      0        
463 0           $@ =~ s/\s+Trace begun.*//s;
464 0           warn "URILocalBL: GeoIP2 load failed: $@\n";
465 0           return 0;
466             }
467              
468             } elsif ( HAS_GEOIP ) {
469             BEGIN {
470 19     19   29188 Geo::IP->import( qw(GEOIP_MEMORY_CACHE GEOIP_CHECK_CACHE GEOIP_ISP_EDITION) );
471             }
472 0           $self->{use_geoip2} = 0;
473             # need GeoIP C library 1.6.3 and GeoIP perl API 1.4.4 or later to avoid messages leaking - Bug 7153
474 0           my $gic_wanted = version->parse('v1.6.3');
475 0           my $gic_have = version->parse(Geo::IP->lib_version());
476 0           my $gip_wanted = version->parse('v1.4.4');
477 0           my $gip_have = version->parse($Geo::IP::VERSION);
478              
479             # this code burps an ugly message if it fails, but that's redirected elsewhere
480 0           my $flags = 0;
481 0           my $flag_isp = 0;
482 0           my $flag_silent = 0;
483 0 0         eval '$flags = GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE' if ($gip_wanted >= $gip_have);
484 0 0         eval '$flag_silent = Geo::IP::GEOIP_SILENCE' if ($gip_wanted >= $gip_have);
485 0 0         eval '$flag_isp = GEOIP_ISP_EDITION' if ($gip_wanted >= $gip_have);
486              
487 0           eval {
488 0 0 0       if ($flag_silent && $gic_wanted >= $gic_have) {
489 0           $self->{geoip} = Geo::IP->new($flags | $flag_silent);
490 0           $self->{geoisp} = Geo::IP->open_type($flag_isp | $flag_silent | $flags);
491             } else {
492 0           open(OLDERR, ">&STDERR");
493 0           open(STDERR, ">", "/dev/null");
494 0           $self->{geoip} = Geo::IP->new($flags);
495 0           $self->{geoisp} = Geo::IP->open_type($flag_isp);
496 0           open(STDERR, ">&OLDERR");
497 0           close(OLDERR);
498             }
499             };
500 0 0 0       if ($@ || !($self->{geoip} || $self->{geoisp})) {
      0        
501 0           $@ =~ s/\s+Trace begun.*//s;
502 0           warn "URILocalBL: GeoIP load failed: $@\n";
503 0           return 0;
504             }
505              
506 0   0 0     $db_info = sub { return "Geo::IP " . ($self->{geoip}->database_info || '?') };
  0            
507             } else {
508 0           dbg("No GeoIP module available");
509 0           return 0;
510             }
511              
512 0           my %uri_detail = %{ $permsg->get_uri_detail_list() };
  0            
513 0           my $test = $permsg->{current_rule_name};
514 0           my $rule = $permsg->{conf}->{uri_local_bl}->{$test};
515              
516 0           my %hit_tests;
517 0           my $got_hit = 0;
518 0           my @addrs;
519 0           my $IP_ADDRESS = IP_ADDRESS;
520            
521 0 0         if ( defined $self->{geoip} ) {
522 0           dbg("check: uri_local_bl evaluating rule %s using database %s\n", $test, $db_info->());
523             } else {
524 0           dbg("check: uri_local_bl evaluating rule %s\n", $test);
525             }
526              
527 0           my $dns_available = $permsg->is_dns_available();
528              
529 0           while (my ($raw, $info) = each %uri_detail) {
530              
531 0 0         next unless $info->{hosts};
532              
533             # look for W3 links only
534 0 0 0       next unless (defined $info->{types}->{a} || defined $info->{types}->{parsed});
535              
536 0           while (my($host, $domain) = each %{$info->{hosts}}) {
  0            
537              
538             # skip if the domain name was matched
539 0 0 0       if (exists $rule->{exclusions} && exists $rule->{exclusions}->{$domain}) {
540 0           dbg("check: uri_local_bl excludes %s as *.%s\n", $host, $domain);
541 0           next;
542             }
543              
544 0 0         if($host !~ /^$IP_ADDRESS$/) {
545 0 0         if (!$dns_available) {
546 0           dbg("check: uri_local_bl skipping $host, dns not available");
547 0           next;
548             }
549             # this would be best cached from prior lookups
550 0           @addrs = gethostbyname($host);
551             # convert to string values address list
552 0           @addrs = map { inet_ntoa($_); } @addrs[4..$#addrs];
  0            
553             } else {
554 0           @addrs = ($host);
555             }
556              
557 0           dbg("check: uri_local_bl %s addrs %s\n", $host, join(', ', @addrs));
558              
559 0           for my $ip (@addrs) {
560             # skip if the address was matched
561 0 0 0       if (exists $rule->{exclusions} && exists $rule->{exclusions}->{$ip}) {
562 0           dbg("check: uri_local_bl excludes %s(%s)\n", $host, $ip);
563 0           next;
564             }
565              
566 0 0         if (exists $rule->{countries}) {
567 0           dbg("check: uri_local_bl countries %s\n", join(' ', sort keys %{$rule->{countries}}));
  0            
568              
569 0 0         if ( $self->{use_geoip2} == 1 ) {
570 0           my $country;
571 0 0         if (index($self->{geoip}->metadata()->description()->{en}, 'City') != -1) {
572 0           $country = $self->{geoip}->city( ip => $ip );
573             } else {
574 0           $country = $self->{geoip}->country( ip => $ip );
575             }
576 0           my $country_rec = $country->country();
577 0           $cc = $country_rec->iso_code();
578             } else {
579 0           $cc = $self->{geoip}->country_code_by_addr($ip);
580             }
581              
582 0 0         dbg("check: uri_local_bl host %s(%s) maps to %s\n", $host, $ip, (defined $cc ? $cc : "(undef)"));
583              
584             # handle there being no associated country (yes, there are holes in
585             # the database).
586 0 0         next unless defined $cc;
587              
588             # not in blacklist
589 0 0         next unless (exists $rule->{countries}->{$cc});
590              
591 0           dbg("check: uri_block_cc host %s(%s) matched\n", $host, $ip);
592              
593 0 0         if (would_log('dbg', 'rules') > 1) {
594 0           dbg("check: uri_block_cc criteria for $test met");
595             }
596            
597 0           $permsg->test_log("Host: $host in $cc");
598 0           $hit_tests{$test} = 1;
599              
600             # reset hash
601 0           keys %uri_detail;
602             }
603              
604 0 0         if (exists $rule->{continents}) {
605 0           dbg("check: uri_local_bl continents %s\n", join(' ', sort keys %{$rule->{continents}}));
  0            
606              
607 0 0         if ( $self->{use_geoip2} == 1 ) {
608 0           my $country = $self->{geoip}->country( ip => $ip );
609 0           my $cont_rec = $country->continent();
610 0           $cont = $cont_rec->{code};
611             } else {
612 0           $cc = $self->{geoip}->country_code_by_addr($ip);
613 0           $cont = $self->{geoip}->continent_code_by_country_code($cc);
614             }
615            
616 0 0         dbg("check: uri_local_bl host %s(%s) maps to %s\n", $host, $ip, (defined $cont ? $cont : "(undef)"));
617              
618             # handle there being no associated continent (yes, there are holes in
619             # the database).
620 0 0         next unless defined $cont;
621              
622             # not in blacklist
623 0 0         next unless (exists $rule->{continents}->{$cont});
624              
625 0           dbg("check: uri_block_cont host %s(%s) matched\n", $host, $ip);
626              
627 0 0         if (would_log('dbg', 'rules') > 1) {
628 0           dbg("check: uri_block_cont criteria for $test met");
629             }
630              
631 0           $permsg->test_log("Host: $host in $cont");
632 0           $hit_tests{$test} = 1;
633              
634             # reset hash
635 0           keys %uri_detail;
636             }
637              
638 0 0         if (exists $rule->{isps}) {
639 0           dbg("check: uri_local_bl isps %s\n", join(' ', map { '"' . $_ . '"'; } sort keys %{$rule->{isps}}));
  0            
  0            
640              
641 0 0         if ( $self->{use_geoip2} == 1 ) {
642 0           $isp = $self->{geoisp}->isp(ip => $ip);
643             } else {
644 0           $isp = $self->{geoisp}->isp_by_name($ip);
645             }
646              
647 0 0         dbg("check: uri_local_bl isp %s(%s) maps to %s\n", $host, $ip, (defined $isp ? '"' . $isp . '"' : "(undef)"));
648              
649             # handle there being no associated country
650 0 0         next unless defined $isp;
651              
652             # not in blacklist
653 0 0         next unless (exists $rule->{isps}->{$isp});
654              
655 0           dbg("check: uri_block_isp host %s(%s) matched\n", $host, $ip);
656              
657 0 0         if (would_log('dbg', 'rules') > 1) {
658 0           dbg("check: uri_block_isp criteria for $test met");
659             }
660            
661 0           $permsg->test_log("Host: $host in \"$isp\"");
662 0           $hit_tests{$test} = 1;
663              
664             # reset hash
665 0           keys %uri_detail;
666             }
667              
668 0 0         if (exists $rule->{cidr}) {
669 0           dbg("check: uri_block_cidr list %s\n", join(' ', $rule->{cidr}->list_range()));
670              
671 0 0         next unless ($rule->{cidr}->find($ip));
672              
673 0           dbg("check: uri_block_cidr host %s(%s) matched\n", $host, $ip);
674              
675 0 0         if (would_log('dbg', 'rules') > 1) {
676 0           dbg("check: uri_block_cidr criteria for $test met");
677             }
678              
679 0           $permsg->test_log("Host: $host as $ip");
680 0           $hit_tests{$test} = 1;
681              
682             # reset hash
683 0           keys %uri_detail;
684             }
685             }
686             }
687             # cycle through all tests hitted by the uri
688 0           while((my $test_ok) = each %hit_tests) {
689 0           $permsg->got_hit($test_ok);
690 0           $got_hit = 1;
691             }
692 0 0         if($got_hit == 1) {
693 0           return 1;
694             } else {
695 0           keys %hit_tests;
696             }
697             }
698              
699 0           dbg("check: uri_local_bl %s no match\n", $test);
700              
701 0           return 0;
702             }
703              
704             1;
705