File Coverage

blib/lib/Net/DNSBL/MultiDaemon.pm
Criterion Covered Total %
statement 281 457 61.4
branch 150 318 47.1
condition 113 312 36.2
subroutine 20 24 83.3
pod 3 12 25.0
total 567 1123 50.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package Net::DNSBL::MultiDaemon;
3              
4 16     16   19045 use strict;
  16         28  
  16         672  
5             #use diagnostics;
6              
7 16         5247 use vars qw(
8             $VERSION @ISA @EXPORT_OK %EXPORT_TAGS *R_Sin
9             $D_CLRRUN $D_SHRTHD $D_TIMONLY $D_QRESP $D_NOTME $D_ANSTOP $D_VERBOSE
10 16     16   75 );
  16         24  
11             require Exporter;
12             @ISA = qw(Exporter);
13              
14             # DEBUG is a set of semaphores
15             $D_CLRRUN = 0x1; # clear run flag and force unconditional return
16             $D_SHRTHD = 0x2; # return short header message
17             $D_TIMONLY = 0x4; # exit at end of timer section
18             $D_QRESP = 0x8; # return query response message
19             $D_NOTME = 0x10; # return received response not for me
20             $D_ANSTOP = 0x20; # clear run OK flag if ANSWER present
21             $D_VERBOSE = 0x40; # verbose debug statements to STDERR
22              
23             $VERSION = do { my @r = (q$Revision: 0.39 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
24              
25             @EXPORT_OK = qw(
26             run
27             bl_lookup
28             set_extension
29             );
30             %EXPORT_TAGS = (
31             debug => [qw($D_CLRRUN $D_SHRTHD $D_TIMONLY $D_QRESP $D_NOTME $D_ANSTOP $D_VERBOSE uniqueID)],
32             );
33             Exporter::export_ok_tags('debug');
34              
35             my $FATans = 0; # this causes a response size overflow from some DNSBLS that have
36             # many mirrors, so only the local host authority record is returned
37              
38 6     6 0 5136 sub fatreturn { return $FATans }; # for testing
39              
40             my $eXT = undef; # extension code for "Private Use" as defined in outlined in RFC-6195
41             # Query types
42             # Classes
43             # Types
44              
45 16     16   16935 use Socket;
  16         74202  
  16         10355  
46 16         4553 use Net::DNS::Codes qw(
47             TypeTxt
48             T_A
49             T_AAAA
50             T_ANY
51             T_MX
52             T_CNAME
53             T_NS
54             T_TXT
55             T_SOA
56             T_AXFR
57             T_PTR
58             C_IN
59             PACKETSZ
60             HFIXEDSZ
61             QUERY
62             NOTIMP
63             FORMERR
64             NOERROR
65             REFUSED
66             NXDOMAIN
67             SERVFAIL
68             BITS_QUERY
69             RD
70             QR
71             CD
72 16     16   17038 );
  16         29332  
73 16         1696 use Net::DNS::ToolKit 0.16 qw(
74             newhead
75             gethead
76             get_ns
77 16     16   15500 );
  16         621377  
78 16     16   16870 use Net::DNS::ToolKit::RR;
  16         68339  
  16         752  
79             #use Net::DNS::ToolKit::Debug qw(
80             # print_head
81             # print_buf
82             #);
83              
84             #use Data::Dumper;
85              
86 16         30860 use Net::DNSBL::Utilities 0.07 qw(
87             s_response
88             not_found
89             write_stats
90             statinit
91             A1271
92             A1272
93             A1274
94             A1275
95             A1276
96             A1277
97             list2NetAddr
98             matchNetAddr
99             setAUTH
100             setRA
101 16     16   9358 );
  16         369  
102              
103             # target for queries about DNSBL zones, create once per session
104             # this is a global so it can be altered during testing
105             *R_Sin = \scalar sockaddr_in(53,scalar get_ns());
106              
107             =head1 NAME
108              
109             Net::DNSBL::MultiDaemon - multi DNSBL prioritization
110              
111             =head1 SYNOPSIS
112              
113             use Net::DNSBL::MultiDaemon qw(
114             :debug
115             run
116             bl_lookup
117             set_extension
118             );
119              
120             run($BLzone,$L,$R,$DNSBL,$STATs,$Run,$Sfile,$StatStamp,$DEBUG)
121             bl_lookup($put,$mp,$rtp,$sinaddr,$alarm,$rid,$id,$rip,$type,$zone,@blist);
122              
123             =head1 DESCRIPTION
124              
125             B is the Perl module that implements the B
126             daemon.
127              
128             B is a DNS emulator daemon that increases the efficacy of DNSBL
129             look-ups in a mail system. B may be used as a stand-alone DNSBL
130             or as a plug-in for a standard BIND 9 installation.
131             B shares a common configuration file format with the
132             Mail::SpamCannibal sc_BLcheck.pl script so that DNSBL's can be maintained in
133             a common configuration file for an entire mail installation.
134              
135             Because DNSBL usefulness is dependent on the nature and source of spam sent to a
136             specific site and because sometimes DNSBL's may provide intermittant
137             service, B interrogates them sorted in the order of B
138             successful hits>. DNSBL's that do not respond within the configured timeout
139             period are not interrogated at all after 6 consecutive failures, and
140             thereafter will be retried not more often than once every hour until they
141             come back online. This eliminates the need to place DNSBL's in a particular order in
142             your MTA's config file or periodically monitor the DNSBL statistics and/or update
143             the MTA config file.
144              
145             In addition to optimizing DNSBL interrogation, B may be
146             configured to locally accept or reject specified IP's, IP ranges and to
147             reject specified countries by 2 character country code. By adding a DNSBL
148             entry of B, IP's will be rejected that do not return some kind
149             of valid reverse DNS lookup. In addition, IP's can be rejected that have a
150             PTR record that matchs a configurable GENERIC 'regexp' set.
151              
152             Reject codes are as follows:
153              
154             query 2.0.0.127.{zonename} 127.0.0.2
155             blocked by configured DNSBL 127.0.0.2
156             no reverse DNS 127.0.0.4
157             BLOCKED (local blacklist) 127.0.0.5
158             Blocked by Country 127.0.0.6
159             Blocked GENERIC 127.0.0.7
160              
161             =head1 OPERATION
162              
163             The configuration file for B contains optional IGNORE (always
164             pass), optional BLOCK (always reject), and optional BBC (block by country) entries against
165             which all received queries are checked before external DNSBL's are queried.
166             IP's which pass IGNORE, BLOCK, and BBC test are then checked against the
167             prioritized list of DNSBL's to try when looking up an IP address for blacklisting.
168             Internally, B maintains this list in sorted order (including
169             'in-addr.arpa') based on the number of responses that
170             resulted in an acceptable A record being returned from the DNSBL query. For
171             each IP address query sent to B, a query is sent to each
172             configured DNSBL sequentially until all DNSBL's have been queried or an
173             acceptable A record is returned.
174              
175             Let us say for example that blackholes.easynet.nl (below) will return an A record
176             and list.dsbl.org, bl.spamcop.net, dynablock.easynet.nl, will not.
177              
178             LIST
179             9451 list.dsbl.org
180             6516 bl.spamcop.net
181             2350 dynablock.easynet.nl
182             575 blackholes.easynet.nl
183             327 cbl.abuseat.org
184             309 dnsbl.sorbs.net
185             195 dnsbl.njabl.org
186             167 sbl.spamhaus.org
187             22 spews.dnsbl.net.au
188             6 relays.ordb.org
189             1 proxies.blackholes.easynet.nl
190             0 dsbl.org
191              
192             A query to B (pseudo.dnsbl in this example) looks like this
193              
194             QUERY
195             1.2.3.4.pseudo.dnsbl
196             |
197             V
198             ####################
199             # multi_dnsbl #
200             ####################
201             | RESPONSE
202             +--> 1.2.3.4.list.dsbl.org NXDOMAIN
203             |
204             +--> 1.2.3.4.bl.spamcop.net NXDOMAIN
205             |
206             +--> 1.2.3.4.dynablock.easynet.nl NXDOMAIN
207             |
208             +--> 1.2.3.4.blackholes.easynet.nl A-127.0.0.2
209              
210             The A record is returned to originator of the Query and the statistics count
211             on blackholes.easynet.nl is incremented by one.
212              
213             =head1 INSTALLATION / CONFIGURATION / OPERATION
214              
215             B can be installed as either a standalone DNSBL or as a plug-in
216             to a BIND 9 installation on the same host. In either case, copy the
217             rc.multi_daemon script to the appropriate startup directory on your host and
218             modify the start, stop, restart scripts as required. Operation of the script
219             is as follows:
220              
221             Syntax: ./rc.multi_dnsbl start /path/to/config.file
222             ./rc.multi_dnsbl start -v /path/to/config.file
223             ./rc.multi_dnsbl stop /path/to/config.file
224             ./rc.multi_dnsbl restart /path/to/config.file
225              
226             The -v switch will print the scripts
227             actions verbosely to the STDERR.
228              
229             =head2 CONFIGURATION FILE
230              
231             The configuration file for B shares a common format with the
232             Mail::SpamCannibal sc_BLcheck.pl script, facilitating common maintenance of
233             DNSBL's for your MTA installation.
234              
235             The sample configuration file
236             B is heavily commented with the details for each
237             configuration element. If you plan to use a common configuration file in a
238             SpamCannibal installation, simply add the following elements to the
239             B file:
240              
241             MDstatfile => '/path/to/statistics/file.txt',
242             MDpidpath => '/path/to/pidfiles', # /var/run
243             MDzone => 'pseudo.dnsbl',
244              
245             # OPTIONAL
246             MDstatrefresh => 300, # seconds
247             MDipaddr => '0.0.0.0', # PROBABLY NOT WHAT YOU WANT
248             MDport => 9953,
249             MDcache => 10000, # an entry takes ~400 bytes
250             # default 10000 (to small)
251            
252             ### WARNING ###
253             failure to set MDipaddr to a valid ip address will result
254             in the authority section return an NS record of INADDR_ANY
255             This will return an invalid NS record in stand alone operation
256              
257             =head2 STANDALONE OPERATION
258              
259             For standalone operation, simply set B, nothing more is
260             required.
261              
262             Interrogating the installation will then return the first
263             match from the configured list of DNSBL servers.
264              
265             i.e. dig 2.0.0.127.pseudo.dnsbl
266              
267             .... results
268              
269             =head2 PLUGIN to BIND 9
270              
271             B may be used as a plugin helper for a standard bind 9
272             installation by adding a B zone to the configuration file as
273             follows:
274              
275             //zone pseudo.dnsbl
276             zone "pseudo.dnsbl" in {
277             type forward;
278             forward only;
279             forwarders {
280             127.0.0.1 port 9953;
281             };
282             };
283              
284             You may also wish to add one or more of the following statements with
285             appropriate address_match_lists to restrict access to the facility.
286              
287             allow-notify {};
288             allow-query { address_match_list };
289             allow-recursion { address_match_list };
290             allow-transfer {};
291              
292             =head2 MTA CONFIGURATION
293              
294             Access to DNSBL lookup is configured in the normal fashion for each MTA.
295             Since MTA's generally must interrogate on port 53, B must be
296             installed on a stand-alone server or as a plugin for BIND 9.
297              
298             A typical configuration line for B configuration file is shown
299             below:
300              
301             FEATURE(`dnsbl',`pseudo.dnsbl',
302             `554 Rejected $&{client_addr} found in http://www.my.blacklist.org')dnl
303              
304             =head1 SYSTEM SIGNALS
305              
306             B responds to the following system signals:
307              
308             =over 4
309              
310             =item * TERM
311              
312             Operations the statistics file is updated with the internal counts and the
313             daemon then exits.
314              
315             =item * HUP
316              
317             Operations are stopped including an update of the optional statistics file,
318             the configuration file is re-read and operations are restarted.
319              
320             =item * USR1
321              
322             The statistics file is updated on the next second tick.
323              
324             =item * USR2
325              
326             The statistics file is deleted, internal statistics then a new (empty)
327             statistics file is written on the next second tick.
328              
329             =back
330              
331             =head1 PERL MODULE DESCRIPTION
332              
333             B provides most of the functions that implement
334             B which is an MTA helper that interrogates a list of
335             DNSBL servers in preferential order based on their success rate.
336              
337             The following describes the workings of individual functions
338             used to implement B.
339              
340             =over 4
341              
342             =item * run($BLzone,$L,$R,$DNSBL,$STATs,$Run,$Sfile,$StatStamp,$DEBUG);
343              
344             This function is the 'run' portion for the DNSBL multidaemon
345              
346             input:
347             $BLzone zone name,
348             $L local listen socket object pointer,
349             $R remote socket object pointer,
350             $DNSBL config hash pointer,
351             $STATs statistics hash pointer
352             $Run pointer to stats refresh time, # must be non-zero
353             $Sfile statistics file path,
354             $StatStamp stat file initial time stamp
355              
356             returns: nothing
357              
358             =over 2
359              
360             =item * $BLzone
361              
362             The fully qualified domain name of the blacklist lookup
363              
364             =item * $L
365              
366             A pointer to a UDP listener object
367              
368             =item * $R
369              
370             A pointer to a unbound UDP socket
371             used for interogation and receiving replies for the multiple DNSBL's
372              
373             =item * $DNSBL
374              
375             A pointer to the configuration hash of the form:
376              
377             $DNSBL = {
378             # Always allow these addresses
379             'IGNORE' => [ # OPTIONAL
380             # a single address
381             '11.22.33.44',
382             # a range of ip's, ONLY VALID WITHIN THE SAME CLASS 'C'
383             '22.33.44.55 - 22.33.44.65',
384             # a CIDR range
385             '5.6.7.16/28',
386             # a range specified with a netmask
387             '7.8.9.128/255.255.255.240',
388             # you may want these
389             '10.0.0.0/8',
390             '172.16.0.0/12',
391             '192.168.0.0/16',
392             # this should ALWAYS be here
393             '127.0.0.0/8', # ignore all test entries and localhost
394             ],
395              
396             # Do rhbl lookups only, default false
397             # all other rejection classes are disabled, IGNORE, BLOCK, BBC, in-addr.arpa
398             # RHBL need only be "true" for operation. If OPTIONAL URBL conditioning
399             # is needed, then the parameters in the has must be added
400             RHBL => { # optional URBL preparation
401             urblwhite => [
402             '/path/to/cached/whitefile',
403             '/path/to/local/file' # see format of spamassassin file
404             ],
405             urblblack => [
406             '/path/to/local/blacklist'
407             ],
408             # NOTE: level 3 tld's should be first before level 2 tld's
409             urbltlds => [
410             '/path/to/cached/tld3file',
411             '/path/to/cached/tld2file'
412             ],
413             urlwhite => [
414             'http://spamassasin.googlecode.com/svn-history/r6/trunk/share/spamassassin/25_uribl.cf',
415             '/path/to/cached/whitefile'
416             ],
417             urltld3 => [
418             'http://george.surbl.org/three-level-tlds',
419             '/path/to/cached/tld3file'
420             ],
421             urltld2 => [
422             'http://george.surbl.org/two-level-tlds',
423             '/path/to/cached/tld2file'
424             ],
425             },
426              
427             # Authoratative answers
428             'AUTH' => 0,
429              
430             # Always reject these addresses
431             'BLOCK' => [ # OPTIONAL
432             # same format as above
433             ],
434              
435             # Always block these countries
436             'BBC' => [qw(CN TW RO )],
437              
438             # Check for reverse lookup failures - OPTIONAL
439             'in-addr.arpa' => {
440             timeout => 15, # default timeout is 30
441             },
442              
443             # RBL zones as follows: OPTIONAL
444             'domain.name' => {
445             # mark this dnsbl to require right hand side domain processing
446             # requires URBL::Prepare
447             # NOT IMPLEMENTED
448             # urbl => 1,
449             acceptany => 'comment - treat any response as valid',
450             # or
451             accept => {
452             '127.0.0.2' => 'comment',
453             '127.0.0.3' => 'comment',
454             },
455             # or
456             # mask the low 8 bits and accept any true result
457             acceptmask => 0x3D, # accepts 0011 1101
458              
459             # timeout => 30, # default seconds to wait for dnsbl
460             },
461              
462             'next.domain' = {
463             etc....
464             # included but extracted external to B
465              
466             MDzone => 'pseudo.dnsbl',
467             MDstatfile => '/path/to/statistics/file.txt',
468             MDpidpath => '/path/to/pidfiles
469             # OPTIONAL, defaults shown
470             # MDstatrefresh => 300, # max seconds for refresh
471             # MDipaddr => '0.0.0.0', # PROBABLY NOT WHAT YOU WANT
472             # MDport => 9953,
473             # syslog. Specify the facility, one of:
474             # LOG_EMERG LOG_ALERT LOG_CRIT LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG
475             # MDsyslog => 'LOG_WARNING',
476             #
477             # cache lookups using the TTL of the providing DNSBL
478             # each cache entry takes about 400 bytes, minimum size = 1000
479             # MDcache => 1000, # 1000 is too small
480             };
481              
482             Zone labels that are not of the form *.*... are ignored, making this hash
483             table fully compatible with the SpamCannibal sc_Blacklist.conf file.
484              
485             =item * $STATs
486              
487             A pointer to a statistics collection array of the form:
488              
489             $STATs = {
490             'domain.name' => count,
491             etc...,
492             'CountryCode' => count,
493             etc...
494             };
495              
496             Initialize this array with
497             cntinit($DNSBL,$cp) L/cntinit, then
498             list2hash($BBC,$cp) L/list2hash, then
499             statinit($Sfile,$cp) L/statinit, below.
500              
501             =item * $Run
502              
503             A POINTER to the time in seconds to refresh the $STATs backing file. Even if
504             there is not backing file used, this value must be a positive integer.
505             Setting this value to zero will stop the daemon and force a restart. It is
506             used by $SIG{HUP} to restart the daemon.
507              
508             =item * $Sfile
509              
510             The path to the STATISTICS backing file.
511              
512             i.e. /some/path/to/filename.ext
513              
514             If $Sfile is undefined, then the time stamp need not be defined
515              
516             =item * $StatTimestamp
517              
518             Normally the value returned by
519             statinit($Sfile,$cp) L/statinit, below.
520              
521             =back
522              
523             =cut
524              
525             my %AVGs = (); # averages
526             my %CNTs = (); # current counts
527             my $tick = 0; # second ticker
528             my $interval = 300; # averaging interval
529             my $bucket = 24 * 60 * 60; # 24 hours for now...
530             my $weight = 5; # weight new stuff higher than old stuff
531             my $csize = 0; # cache size and switch
532             my $cused = 0; # cache in use
533             my ($now, $next);
534             my $newstat; # new statistics flag, used by run
535              
536             sub average {
537 578     578 0 3611 my $STATs = shift;
538 578         963 my $multiplier = $bucket / ($bucket + (($now + $interval - $next) * $weight));
539 578         584 $next = $now + $interval; # next average event
540 578         1188 foreach (keys %$STATs) {
541 1734 50       3803 next unless $_ =~ /\./; # only real domains
542 1734 50       3037 next unless exists $CNTs{"$_"};
543 1734         3852 $AVGs{"$_"} = ($AVGs{"$_"} + ($weight * $CNTs{"$_"})) * $multiplier;
544 1734         6591 $CNTs{"$_"} = 0;
545             }
546             }
547              
548             # increment statistics for "real" DNSBL's
549             # input: STATS pointer
550             # DNSBL string
551              
552             sub bump_stats {
553 1     1 0 4 my($STATs, $blist_0) = @_;
554 1         4 $STATs->{"$blist_0"} += 1; # bump statistics count
555 1 50       6 if (exists $CNTs{"$blist_0"}) {
556 0         0 $CNTs{"$blist_0"} += 1;
557             } else {
558 1         3 $CNTs{"$blist_0"} = 1;
559 1         3 $AVGs{"$blist_0"} = 1;
560             }
561 1 50       6 $newstat = 1 unless $newstat; # notify refresh that update may be needed
562             }
563              
564             sub by_average {
565 198     198 0 447 my($STATs,$a,$b) = @_;;
566 198 100 100     1007 if (exists $AVGs{"$b"} && exists $AVGs{"$a"}) {
    100          
    100          
567 4   33     27 return ($AVGs{"$b"} <=> $AVGs{"$a"})
568             ||
569             ($STATs->{"$b"} <=> $STATs->{"$a"});
570             }
571             elsif (exists $AVGs{"$b"}) {
572 2         9 return 1;
573             }
574             elsif (exists $AVGs{"$a"}) {
575 2         7 return -1;
576             } else {
577 190         693 return ($STATs->{"$b"} <=> $STATs->{"$a"});
578             }
579             }
580              
581             # reverse digits in ipV4 address
582             #
583             # input: ip
584             # returns: reversed ip
585             #
586             sub revIP {
587 0     0 0 0 join('.',reverse split /\./,$_[0]);
588             }
589              
590             # cache takes about 400 bytes per entry
591             #
592             my %cache = (
593             #
594             # ip address => {
595             # expires => time, now + TTL from response or 3600 minimum
596             # used => time, time cache item was last used
597             # who => $blist[0], which DNSBL caused caching
598             # txt => 'string', txt from our config file or empty
599             # },
600             );
601             my @topurge; # working array
602              
603             # for testing
604             # set now and next, csize return pointers to internal averaging arrays and cache
605             #
606             sub set_nownext {
607 580     580 0 9724 ($now,$next,$csize) = @_;
608 580         1085 return($interval,\%AVGs,\%CNTs,\%cache,\@topurge);
609             }
610              
611             # purge cache when called from "run"
612              
613             my $prp = -1; # run pointer, see "mode" below
614             my $pai; # array index
615             my $pnd; # array end
616              
617             # piecewise purge of expired cache items performs gnome sort while purging
618             #
619             # followed by conditional purge of cache size overrun of oldest touched
620             # cache items or those that will expire the soonest
621             #
622             # input: nothing
623             # returns: mode
624             # -1 waiting to be initialized
625             # 0 purging expired elements + gnome sort
626             # 1 purging cache overrun
627              
628             sub purge_cache {
629 339 100   339 0 3791 if ($prp == 0) { # run state to purge expired elements
    100          
630 323         396 my $k1 = $topurge[$pai];
631             #print STDERR "$pnd, $pai";
632 323 100       494 if (exists $cache{$k1}) {
633 322         335 my $j = $pai +1;
634 322         335 my $k2 = $topurge[$j];
635 322 100       765 if ($cache{$k1}->{expires} < $now) {
    100          
636 1         4 delete $cache{$k1};
637 1         3 splice(@topurge,$pai,1); # remove element from cache array
638 1         3 $pnd--;
639             #print STDERR " delete k1 = $k1\n";
640             }
641             elsif (exists $cache{$k2}) {
642 320 100 33     1435 if ($cache{$k2}->{expires} < $now) {
    100 66        
643 5         13 delete $cache{$k2};
644 5         9 splice(@topurge,$j,1); # remove element from cache array
645 5         8 $pnd--;
646             #print STDERR " delete k2 = $k2\n";
647             }
648             elsif ( $cache{$k1}->{used} > $cache{$k2}->{used} # oldest use
649             || ($cache{$k1}->{used} == $cache{$k2}->{used} # or if equal,
650             && $cache{$k1}->{expires} > $cache{$k2}->{expires}) # expires soonest
651             ) {
652 140         270 @topurge[$pai,$j] = @topurge[$j,$pai];
653 140         153 $pai--;
654 140 100       296 $pai = 0 if $pai < 0;
655             #print STDERR " swap k1, k2 - $k1 <=> $k2\n";
656             }
657             else {
658 175         236 $pai++;
659             #print STDERR " k1, k2 ok - $k1 : $k2\n";
660             }
661             }
662             else {
663 1         2 splice(@topurge,$j,1); # remove element from cache array
664 1         2 $pnd--;
665             #print STDERR " remove k2 = $k2\n";
666             }
667             }
668             else {
669 1         4 splice(@topurge,$pai,1); # remove element from cache array
670 1         3 $pnd--;
671             #print STDERR " remove k1 = $k1\n";
672             }
673 323 100       877 return $prp if $pai < $pnd; # reached end?
674             # done, set next state
675 4         6 $pnd++;
676 4         6 $pnd -= $csize;
677 4 100       9 if ($pnd > 0) { # must delete overrun elements
678 2         3 $prp = 1;
679 2         6 $pai = 0;
680             } else {
681 2         4 $prp = -1; # set to initialization state
682             }
683             }
684             elsif ($prp > 0) { # remove cache over run
685 12         17 my $k = $topurge[$pai];
686 12 50       43 delete $cache{$k} if exists $cache{$k};
687 12         14 $pai++;
688 12 100       24 unless ($pai < $pnd) {
689 2         4 $prp = -1;
690             }
691             }
692             else {
693 4 50       17 return $prp unless $csize; # not enabled
694 4         35 $pnd = @topurge = keys %cache;
695 4         8 $cused = $pnd; # update amount of cache in use
696 4 50       14 return $prp unless $pnd; # nothing to do
697 4         5 $pnd--; # end of array
698 4         6 $pai = 0; # array index
699 4         7 $prp = 0; # run state sort
700             }
701 20         37 return $prp;
702             }
703              
704             # setURBLdom
705             #
706             # sets breadcrumbs for stripped domain for URBL's
707             #
708             # input: remote IP or domain
709             # remote ID
710             # notRHBL
711             # ubl method pointer
712             # blacklist host array pointer UNUSED
713             # remoteThreads ptr
714             # return:
715             # SCALAR $rid
716             # ARRAY ($rid,$whitelistedDomain,$SURBLookupDomain)
717             # or false or false
718              
719             # $bap is unused
720              
721             sub setURBLdom {
722 23     23 0 85 my($rip,$rid,$notRHBL,$ubl,$bap,$rtp,$n) = @_;
723 23 50 33     134 if ($notRHBL || ! $ubl) { # don't even need to check
724 23 50       178 return wantarray ? ($rid) : $rid; # or URBL::Prepare not loaded
725             }
726 0 0       0 $rid = uniqueID() unless $rid; # set $rid if it is empty
727 0 0       0 $rtp->{$rid} = {} unless exists $rtp->{$rid};
728              
729 0         0 my $domain = '';
730 0         0 my $white = $ubl->urblwhite($rip);
731 0 0       0 unless ($white) {
732 0         0 $domain = $ubl->urbldomain($rip);
733             }
734              
735 0         0 $rtp->{$rid}->{urbl} = $domain;
736 0         0 $rtp->{$rid}->{N} = $n;
737 0 0       0 return wantarray ? ($rid,$white,$domain) : $rid;
738             }
739              
740             sub run {
741 43     43 1 17930186 my ($BLzone,$L,$R,$DNSBL,$STATs,$Run,$Sfile,$StatStamp,$DEBUG) = @_;
742             #open(Tmp,'>>/tmp/multidnsbl.log');
743             #print Tmp "---------------------------\n";
744 43   100 26   1459 local *_alarm = sub {return $DNSBL->{"$_[0]"}->{timeout} || 30};
  26         786  
745 43         222 $BLzone = lc $BLzone;
746 43   50     1075 my $myip = $DNSBL->{MDipaddr} || '';
747 43 50 33     349 if ($myip && $myip ne '0.0.0.0') {
748 0         0 $myip = inet_aton($myip);
749             } else {
750 43         6069 $myip = A1271;
751             }
752 43 50       149 $DEBUG = 0 unless $DEBUG;
753 43 100       199 my $ROK = ($DEBUG & $D_CLRRUN) ? 0:1;
754              
755 43         354 my ( $msg, $t, $targetIP, $cc, $comment,
756             $Oname,$Otype,$Oclass,$Ottl,$Ordlength,$Odata,
757             $off,$id,$qr,$opcode,$aa,$tc,$rd,$ra,$mbz,$ad,$cd,$rcode,
758             $qdcount,$ancount,$nscount,$arcount,
759             $name,$type,$class,
760             $ttl,$rdl,@rdata,
761             $l_Sin,$rip,$zone,@blist,
762             %remoteThreads,$rid,
763             $rin,$rout,$nfound,
764             $BBC,@NAignore,@NAblock,
765             $notRHBL,$ubl);
766              
767 43         104 my $LogLevel = 0;
768 43 50       224 if ($DNSBL->{MDsyslog}) { # if logging requested
769 0         0 require Unix::Syslog;
770 0         0 import Unix::Syslog @Unix::Syslog::EXPORT_OK;
771 0         0 $LogLevel = eval "$DNSBL->{MDsyslog}";
772             ## NOTE, logging must be initiated by the caller
773             }
774              
775             # generate NetAddr objects for addresses to always pass
776 43 50 66     500 if ($DNSBL->{IGNORE} && ref $DNSBL->{IGNORE} eq 'ARRAY' && @{$DNSBL->{IGNORE}}) {
  2   66     8  
777 2         31 list2NetAddr($DNSBL->{IGNORE},\@NAignore);
778             }
779              
780             # generate NetAddr objects for addresses to always reject
781 43 50 66     254 if ($DNSBL->{BLOCK} && ref $DNSBL->{BLOCK} eq 'ARRAY' && @{$DNSBL->{BLOCK}}) {
  1   66     6  
782 1         42 list2NetAddr($DNSBL->{BLOCK},\@NAblock);
783             }
784              
785             # fetch pointer to Geo::IP methods
786 43 50 33     360 if ($DNSBL->{BBC} && ref $DNSBL->{BBC} eq 'ARRAY' && @{$DNSBL->{BBC}} && eval { require Geo::IP::PurePerl }) {
  0   33     0  
  0   0     0  
787 0         0 $BBC = new Geo::IP::PurePerl;
788             } else {
789 43         799 $DNSBL->{BBC} = '';
790             }
791              
792             # check for caching
793 43 50       185 if (exists $DNSBL->{MDcache}) {
794 0         0 $csize = $DNSBL->{MDcache};
795 0 0       0 $csize = 10000 if $DNSBL->{MDcache} < 10000;
796             }
797              
798             # check for right hand side block list operation
799 43 50       153 if ($DNSBL->{RHBL}) {
800 0         0 $notRHBL = 0;
801 0 0 0     0 if (ref $DNSBL->{RHBL} &&
      0        
      0        
802             ((exists $DNSBL->{RHBL}->{urbltlds} && ref($DNSBL->{RHBL}->{urbltlds}) eq 'ARRAY') ||
803             (exists $DNSBL->{RHBL}->{urblwhite} && ref($DNSBL->{RHBL}->{urblwhite}) eq 'ARRAY') ||
804             (exists $DNSBL->{RHBL}->{urblblack} && ref($DNSBL->{RHBL}->{urblblack}) eq 'ARRAY')) &&
805             eval {
806 16     16   137 no warnings;
  16         24  
  16         416774  
807 0         0 require URBL::Prepare;
808             }
809             ) {
810 0         0 $ubl = new URBL::Prepare;
811 0 0 0     0 if (exists $DNSBL->{RHBL}->{urlwhite} && ref($DNSBL->{RHBL}->{urlwhite}) eq 'ARRAY') {
812 0         0 $ubl->loadcache(@{$DNSBL->{RHBL}->{urlwhite}}); # cache whitelist file
  0         0  
813             }
814 0 0 0     0 if (exists $DNSBL->{RHBL}->{urltld3} && ref($DNSBL->{RHBL}->{urltld3}) eq 'ARRAY') {
815 0         0 $ubl->loadcache(@{$DNSBL->{RHBL}->{urltld3}}); # cache tld3 file
  0         0  
816             }
817 0 0 0     0 if (exists $DNSBL->{RHBL}->{urltld2} && ref($DNSBL->{RHBL}->{urltld2}) eq 'ARRAY') {
818 0         0 $ubl->loadcache(@{$DNSBL->{RHBL}->{urltld2}}); # cache tld2 file
  0         0  
819             }
820 0         0 $ubl->cachetlds($DNSBL->{RHBL}->{urbltlds});
821 0         0 $ubl->cachewhite($DNSBL->{RHBL}->{urblwhite});
822 0         0 $ubl->cacheblack($DNSBL->{RHBL}->{urblblack});
823             }
824             } else {
825 43         106 $notRHBL = 1;
826             }
827             #select Tmp;
828             #$| = 1;
829             #print Tmp "running $$\n";
830             #select STDOUT;
831              
832              
833             # set up GENERIC PTR tests
834 43         110 my($iptr,$regexptr);
835 43 50 33     247 if ( exists $DNSBL->{GENERIC} &&
      33        
      0        
      0        
836             ref $DNSBL->{GENERIC} eq 'HASH' &&
837             ($regexptr = $DNSBL->{GENERIC}->{regexp}) &&
838             ref $regexptr eq 'ARRAY' &&
839             @$regexptr > 0 ) {
840             #print Tmp "regexptr setup, @$regexptr\n";
841 0 0 0     0 unless ( $DNSBL->{GENERIC}->{ignore} &&
      0        
842             'ARRAY' eq ref ($iptr = $DNSBL->{GENERIC}->{ignore}) &&
843             @$iptr > 0 ) {
844 0         0 undef $iptr;
845             }
846             } else {
847             #print Tmp "regexptr FAILED\n";
848 43         98 undef $regexptr;
849             }
850              
851 43         206 my $filenoL = fileno($L);
852 43         187 my $filenoR = fileno($R);
853              
854 43         262 $now = time;
855 43         138 $next = $now + $interval;
856 43         74 $newstat = 0; # new statistics flag
857 43         105 my $refresh = $now + $$Run; # update statistics "then"
858              
859 43     0   2092 local $SIG{USR1} = sub {$newstat = 2}; # force write of stats now
  0         0  
860             local $SIG{USR2} = sub { # kill and regenerate statfile
861 0 0   0   0 return unless $Sfile;
862 0         0 unlink $Sfile;
863 0         0 foreach(keys %$STATs) {
864 0         0 $STATs->{"$_"} = 0;
865 0         0 %AVGs = ();
866 0         0 %CNTs = ();
867             }
868 0         0 $StatStamp = statinit($Sfile,$STATs);
869 0 0       0 syslog($LogLevel,"received USR2, clear stats\n")
870             if $LogLevel;
871 0         0 $newstat = 2; # re-write on next second tick
872 43         1296 };
873              
874 43         404 my $SOAptr = [ # set up bogus SOA
875             $BLzone,
876             &T_SOA,
877             &C_IN,
878             0, # ttl of SOA record
879             $BLzone,
880             'root.'. $BLzone,
881             $now,
882             86400,
883             43200,
884             172800,
885             3600, # cache negative TTL's for an hour
886             ];
887              
888 43         1920 my ($get,$put,$parse) = new Net::DNS::ToolKit::RR;
889              
890 43         2628 my $numberoftries = 6;
891              
892 43         91 my %deadDNSBL;
893 43         724 foreach(keys %$STATs) {
894 216 100       1416 next unless $_ =~ /\./; # only real domains
895 87         572 $deadDNSBL{"$_"} = 1; # initialize dead DNSBL timers
896             }
897              
898 43   66     119 do {
899 70         315 $rin = '';
900 70         528 vec($rin,$filenoL,1) = 1; # always listening to local port
901 70 100       344 (vec($rin,$filenoR,1) = 1) # listen to remote only if traffic expected
902             if %remoteThreads;
903 70         24142740 $nfound = select($rout=$rin,undef,undef,1); # tick each second
904 70 100       416 if ($nfound > 0) {
905             ###################### IF PROCESS REQUEST ########################
906 60         340 while (vec($rout,$filenoL,1)) { # process request
907 45 50       358 last unless ($l_Sin = recv($L,$msg,PACKETSZ,0)); # ignore receive errors
908 45 100       5120 if (length($msg) < HFIXEDSZ) { # ignore if less then header size
909 3 50       272 return 'short header' if $DEBUG & $D_SHRTHD;
910 0         0 last;
911             }
912 42         853 ($off,$id,$qr,$opcode,$aa,$tc,$rd,$ra,$mbz,$ad,$cd,$rcode,
913             $qdcount,$ancount,$nscount,$arcount)
914             = gethead(\$msg);
915 42 100       193 if ($qr) {
916 1 50       34 return 'query response' if $DEBUG & $D_QRESP;
917 0         0 last;
918             }
919 41         102 $comment = 'no bl';
920 41         1578 setAUTH(0); # clear authority
921 41         1421 setRA($rd);
922             # if OPCODE
923 41 50 33     338 if ($eXT && exists $eXT->{OPCODE} && $eXT->{OPCODE}->($eXT,$get,$put,\$msg,
    100 33        
    100 66        
    100 66        
    100 33        
    100 66        
      33        
      66        
      33        
      33        
      66        
924             $off,$id,$qr,$opcode,$aa,$tc,$rd,$ra,$mbz,$ad,$cd,$rcode,$qdcount,$ancount,$nscount,$arcount)) {
925             ; # message updated
926 0         0 $comment = 'mdextension opcode';
927             } elsif ($opcode != QUERY) {
928 1         25 s_response(\$msg,NOTIMP,$id,1,0,0,0);
929 1         3 $comment = 'not implemented';
930             } elsif (
931             $qdcount != 1 ||
932             $ancount ||
933             $nscount ||
934             $arcount
935             ) {
936 4         53 s_response(\$msg,FORMERR,$id,$qdcount,$ancount,$nscount,$arcount);
937 4         16 $comment = 'format error 1';
938             } elsif (
939             (($off,$name,$type,$class) = $get->Question(\$msg,$off)) &&
940             ! $name) { # name must exist
941 1         66 s_response(\$msg,FORMERR,$id,1,0,0,0);
942 1         4 $comment = 'format error 2';
943             # if CLASS
944             } elsif (!($eXT && exists $eXT->{CLASS} && $eXT->{CLASS}->($eXT,$get,$put,$id,$opcode,\$name,\$type,\$class)) &&
945             $class != C_IN) { # class must be C_IN
946 1         68 s_response(\$msg,REFUSED,$id,$qdcount,$ancount,$nscount,$arcount);
947 1         4 $comment = 'refused';
948             # if NAME
949             } elsif (($eXT && exists $eXT->{NAME} && $eXT->{NAME}->($eXT,$get,$put,$id,$opcode,\$name,\$type,\$class)) ||
950             $name !~ /$BLzone$/i) { # question must be for this zone
951 1         128 s_response(\$msg,NXDOMAIN,$id,1,0,0,0);
952 1         4 $comment = 'not this zone';
953             } else {
954             # THIS IS OUR ZONE request, generate a thread to handle it
955              
956 33 50       3426 print STDERR $name,' ',TypeTxt->{$type},' ' if $DEBUG & $D_VERBOSE;
957              
958             # if TYPE
959 33 50 33     278 if ($eXT && exists $eXT->{TYPE} && (my $rv = $eXT->{TYPE}->($eXT,$get,$put,$id,$opcode,\$name,\$type,\$class))) {
    100 33        
    50 66        
    100 100        
    100 66        
      100        
      100        
      100        
      66        
960 0         0 $msg = $rv;
961 0         0 $comment = 'Extension type';
962             } elsif ( $type == T_A ||
963             $type == T_ANY ||
964             $type == T_TXT) {
965 27 100 66     13774 if (( $notRHBL &&
    50 66        
      66        
      33        
      33        
      33        
      33        
      0        
      0        
      66        
      66        
966             $name =~ /^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))\.(.+)/ &&
967             ($rip = $1) &&
968             ($targetIP = "$5.$4.$3.$2") &&
969             ($zone = $6) &&
970             $BLzone eq lc $zone) ||
971             # check for valid RFC1034 domain name, but allow digits in the first character
972             (!$notRHBL && # check RHBL zones
973             ###### CHANGE this REGEXP to alter permissible domain name patterns
974             $name =~ /^([a-zA-Z0-9][a-zA-Z0-9\.\-]+[a-zA-Z0-9])\.$BLzone$/ && # valid domain name
975             ($rip = $1) &&
976             ($targetIP = '' || 1) &&
977             ($zone = $BLzone))) {
978 25         53 my $expires;
979             # if CACHE
980 25 50 33     723 if ($eXT && exists $eXT->{CACHE} && (my $rv = $eXT->{CACHE}->($eXT,$get,$put,$id,$opcode,$rip,\$name,\$type,\$class,$ubl))) {
    50 33        
    50 33        
    50 33        
    50 33        
    100 33        
    100 66        
    50 100        
      66        
      66        
      33        
      33        
      33        
981 0         0 $msg = $rv;
982             }
983             # if local white/black lists
984             elsif (!$notRHBL && $ubl && # right side checking and local white/black lists
985             do {
986 0 0       0 if ($ubl->urblwhite($rip)) {
    0          
987 0         0 not_found($put,$name,$type,$id,\$msg,$SOAptr);
988 0         0 $rv = 'whitelisted';
989             }
990             elsif ($ubl->urblblack($rip)) {
991 0         0 ($msg) = _ansrbak($put,$id,1,$rip,$zone,$type,3600,A1272,$BLzone,$myip,'blacklisted');
992 0         0 $rv = 'blacklisted';
993             }
994             }
995             ) {
996 0         0 $comment = $rv;
997             }
998             elsif ($rip eq '2.0.0.127') { # checkfor DNSBL test
999 0         0 ($msg) = _ansrbak($put,$id,1,$rip,$zone,$type,3600,A1272,$BLzone,$myip,'DNSBL test response to 127.0.0.2');
1000 0         0 $comment = 'just testing';
1001             }
1002             ### NOTE, $now does not get updated very often if the host is busy processing in this routine, but at least every 5 minutes.... good enough
1003             elsif ( $csize && # cacheing enabled
1004             exists $cache{$rip} && # item exists in cache
1005             ($expires = $cache{$rip}->{expires}) > $now ) { # cache not expired
1006 0         0 $cache{$rip}->{used} = $now; # update last used time
1007 0         0 my $blist_0 = $cache{$rip}->{who};
1008 0         0 my $txt = $cache{$rip}->{txt};
1009 0 0       0 $txt = $txt ? $txt . $targetIP : '';
1010 0         0 ($msg) = _ansrbak($put,$id,1,$rip,$zone,$type,$expires - $now,A1272,$BLzone,$myip,$txt); # send cached record
1011 0         0 $comment = 'cache record';
1012 0         0 bump_stats($STATs,$blist_0);
1013             }
1014             elsif ($type == T_TXT) { # none of the rest of static stuff has TXT records
1015 0         0 not_found($put,$name,$type,$id,\$msg,$SOAptr);
1016 0         0 $comment = 'no TXT';
1017             }
1018             elsif ($notRHBL && @NAignore && matchNetAddr($targetIP,\@NAignore)) { # check for IP's to always pass
1019 1         152 not_found($put,$name,$type,$id,\$msg,$SOAptr); # return unconditional NOT FOUND
1020 1         1997 $STATs->{WhiteList} += 1; # bump WhiteList count
1021 1         3 $comment = 'IGNORE';
1022             }
1023 0         0 elsif ($notRHBL && @NAblock && matchNetAddr($targetIP,\@NAblock)) { # check for IP's to always block
1024 1         44 ($msg) = _ansrbak($put,$id,1,$rip,$zone,$type,3600,A1275,$BLzone,$myip); # answer 127.0.0.5
1025 1         6 $STATs->{BlackList} += 1; # bump BlackList count
1026 1         2 $comment = 'BLOCK';
1027             }
1028             elsif ($notRHBL && $BBC && # check for IP's to block by country
1029             ($cc = $BBC->country_code_by_addr($targetIP)) &&
1030             (grep($cc eq $_,@{$DNSBL->{BBC}}))) {
1031 0         0 ($msg) = _ansrbak($put,$id,1,$rip,$zone,$type,3600,A1276,$BLzone,$myip); # answer 127.0.0.6
1032 0         0 $STATs->{$cc} += 1; # bump statistics count
1033 0 0       0 $newstat = 1 unless $newstat; # notify refresh that update may be needed
1034 0         0 $comment = "block $cc";
1035             }
1036             else {
1037             #test here for GENERIC
1038 23         3173 @blist = ();
1039 23         823 foreach(sort { by_average($STATs,$a,$b) } keys %$STATs) {
  186         516  
1040 116 100       8749 next unless $_ =~ /\./; # drop passed,white,black,bbc entries
1041 47         93 push @blist, $_;
1042             }
1043 23 50       104 push @blist, 'genericPTR' if $regexptr;
1044             # add bread crumbs for Extensions if necessary
1045 23         39 $rid = undef; # trial remote ID
1046 23 50 33     90 if ($eXT && exists $eXT->{LOOKUP}) {
1047 0         0 $rid = uniqueID();
1048 0         0 $rid = $eXT->{LOOKUP}->($eXT,$get,$put,$rid,$id,$opcode,\$name,\$type,\$class,\%remoteThreads);
1049             }
1050 23         139 $rid = setURBLdom($rip,$rid,$notRHBL,$ubl,$DNSBL->{$blist[0]},\%remoteThreads,0); # initialize urbl domain lookup name
1051 23         107 bl_lookup($put,\$msg,\%remoteThreads,$l_Sin,_alarm($blist[0]),$rid,$id,$rip,$type,$zone,@blist);
1052 23         1791 send($R,$msg,0,$R_Sin); # udp may not block
1053 23 50       114 print STDERR $blist[0] if $DEBUG & $D_VERBOSE;
1054 23         91 last;
1055             }
1056             }
1057             elsif ($BLzone eq lc $name && $type != T_TXT) {
1058 0         0 my $noff = newhead(\$msg,
1059             $id,
1060             BITS_QUERY | QR,
1061             1,1,1,0,
1062             );
1063 0         0 ($noff,my @dnptrs) = $put->Question(\$msg,$noff, # 1 question
1064             $name,$type,C_IN); # type is T_A
1065 0         0 ($noff,@dnptrs) = $put->A(\$msg,$noff,\@dnptrs, # 1 answer
1066             $name,T_A,C_IN,86400,$myip);
1067 0         0 ($noff,@dnptrs) = $put->NS(\$msg,$noff,\@dnptrs, # 1 authority
1068             $name,T_NS,C_IN,86400,$BLzone);
1069             }
1070             else {
1071 2         51 not_found($put,$name,$type,$id,\$msg,$SOAptr);
1072             }
1073             } elsif ($type == T_NS && $BLzone eq lc $name) { # respond with myip address
1074 0         0 my $noff = newhead(\$msg,
1075             $id,
1076             BITS_QUERY | QR,
1077             1,1,0,1,
1078             );
1079 0         0 ($noff,my @dnptrs) = $put->Question(\$msg,$noff, # 1 question
1080             $name,$type,C_IN); # type is T_NS
1081 0         0 ($noff,@dnptrs) = $put->NS(\$msg,$noff,\@dnptrs, # 1 answer
1082             $name,T_NS,C_IN,$86400,$BLzone);
1083 0         0 ($noff,@dnptrs) = $put->A(\$msg,$noff,\@dnptrs, # 1 additional glue
1084             $BLzone,T_A,C_IN,86400,$myip);
1085             } elsif ($type == T_NS || # answer common queries with a not found
1086             $type == T_MX ||
1087             $type == T_SOA ||
1088             $type == T_CNAME ||
1089             $type == T_TXT) {
1090 4         327 not_found($put,$name,$type,$id,\$msg,$SOAptr);
1091             } elsif ($type == T_AXFR) {
1092 1         132 s_response(\$msg,REFUSED,$id,1,0,0,0);
1093 1         11 $comment = 'refused AXFR';
1094             } else {
1095 1         71 s_response(\$msg,NOTIMP,$id,1,0,0,0);
1096 1         7 $comment = 'not implemented';
1097             }
1098             }
1099 18         22460 send($L,$msg,0,$l_Sin); # udp may not block on send
1100 18 50       86 print STDERR " $comment\n" if $DEBUG & $D_VERBOSE;
1101             #print Tmp "$comment\n";
1102 18         47 last;
1103             }
1104             ##################### IF RESPONSE ###############################
1105 56         895 while (vec($rout,$filenoR,1)) { # A response
1106 15         783 undef $msg;
1107 15 50       489 last unless recv($R,$msg,,PACKETSZ,0); # ignore receive errors
1108 15 100       975 if (length($msg) < HFIXEDSZ) { # ignore if less then header size
1109 5 50       925 return 'short header' if $DEBUG & $D_SHRTHD;
1110 0         0 last;
1111             }
1112 10         450 ($off,$rid,$qr,$opcode,$aa,$tc,$rd,$ra,$mbz,$ad,$cd,$rcode,
1113             $qdcount,$ancount,$nscount,$arcount)
1114             = gethead(\$msg);
1115             #print Tmp "GOT $rid, rcode=$rcode\n";
1116 10 100 33     646 unless ( $tc == 0 &&
      33        
      33        
      33        
      33        
      66        
1117             $qr == 1 &&
1118             $opcode == QUERY &&
1119             ($rcode == NOERROR || $rcode == NXDOMAIN || $rcode == SERVFAIL) &&
1120             $qdcount == 1 &&
1121             exists $remoteThreads{$rid}) { # must not be my question!
1122 4 50       1632 return 'not me 1' if $DEBUG & $D_NOTME;
1123 0         0 last;
1124             }
1125 6         164 ($l_Sin,$rip,$id,$type,$zone,@blist) = @{$remoteThreads{$rid}->{args}};
  6         141  
1126 6 50       130 my $urbldom = exists $remoteThreads{$rid}->{urbl} ? $remoteThreads{$rid}->{urbl} : '';
1127 6         100 ($off,$name,$t,$class) = $get->Question(\$msg,$off);
1128 6         171 my($answer,$attl,@generic);
1129 6 50 33     124 if ($ancount && $rcode == &NOERROR) {
    0 0        
      0        
1130 6 50       231 $name =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\.(.+)$/ || $name =~ /^([a-zA-Z0-9][a-zA-Z0-9\.\-]+[a-zA-Z0-9])\.($blist[0])$/;
1131 6         56 my $z = lc $2;
1132             #print Tmp "RESPONSE U $urbldom, R $rip, One $1, N $name, Z $z\n";
1133 6 50 33     137 $z = ($z eq lc $blist[0]) || ($z eq 'in-addr.arpa' && $blist[0] eq 'genericPTR')
1134             ? 1 : 0;
1135 6 0 0     100 unless ( $z && # not my question
      33        
      0        
      0        
      0        
1136             ((!$urbldom && $rip eq $1) ||
1137             ($urbldom && $urbldom eq $1)) && # not my question
1138             ($t == T_A || $t == T_PTR) && # not my question
1139             $class == C_IN) { # not my question
1140 6 50       497 return 'not me 2' if $DEBUG & $D_NOTME;
1141 0         0 last;
1142             }
1143 0         0 undef $answer;
1144              
1145 0         0 setAUTH($aa); # mirror out authority state
1146 0         0 setRA($rd);
1147              
1148             ANSWER:
1149 0         0 foreach(0..$ancount -1) {
1150 0         0 ($off,$name,$t,$class,$ttl,$rdl,@rdata) = $get->next(\$msg,$off);
1151 0 0       0 next if $answer; # throw away unneeded answers
1152 0 0 0     0 if ($t == T_A) {
    0          
1153 0 0       0 if (exists $DNSBL->{"$blist[0]"}->{acceptany}) {
1154 0         0 $answer = A1272;
1155 0         0 $attl = $ttl;
1156 0         0 last ANSWER;
1157             }
1158 0 0       0 my $mask = (exists $DNSBL->{"$blist[0]"}->{acceptmask})
1159             ? $DNSBL->{"$blist[0]"}->{acceptmask} : 0;
1160 0         0 while($answer = shift @rdata) { # see if answer is on accept list
1161 0         0 my $IP = inet_ntoa($answer);
1162 0 0 0     0 if ($mask & unpack("N",$answer) || grep($IP eq $_,keys %{$DNSBL->{"$blist[0]"}->{accept}})) {
  0         0  
1163 0         0 $answer = A1272;
1164 0         0 $attl = $ttl; # preserve TTL of this responder
1165 0         0 last ANSWER;
1166             }
1167 0         0 undef $answer;
1168             } # end of rdata
1169             }
1170             elsif ($t == T_PTR && $blist[0] eq 'genericPTR') { # duplicates in-addr.arpa lookup, inefficient, but does not happen often
1171             #print Tmp "add $rdata[0]\n";
1172 0         0 push @generic, $rdata[0];
1173             }
1174             } # end of each ANSWER
1175 0         0 $ttl = $attl; # restore responder TTL
1176             }
1177             elsif ($t == T_PTR && ($rcode == NXDOMAIN || $rcode == SERVFAIL)) { # no reverse lookup
1178             #print Tmp "PTR w/ NXDOMAIN or SERVFAIL\n";
1179 0         0 $answer = A1274;
1180 0         0 $ttl = 3600;
1181 0         0 $nscount = $arcount = 0;
1182             }
1183              
1184 0 0       0 if (@generic) {
1185 0         0 my @names;
1186 0         0 foreach my $g (@generic) {
1187 0 0 0     0 last if $iptr && grep($g =~ /$_/i, @$iptr);
1188 0 0 0     0 push @names, $g if $g && ! grep($g =~ /$_/i, @$regexptr);
1189             }
1190 0 0       0 $answer = A1277 unless @names;
1191 0         0 $ttl = 3600;
1192             }
1193 0 0       0 if ($answer) { # if valid answer
    0          
1194 0         0 my $txt = '';
1195 0 0 0     0 if ( $csize && # caching enabled && answer is from a real DSNBL
      0        
1196             ($answer == A1272 || $answer == A1274 || $answer == A1277) ) {
1197              
1198             # ip address => {
1199             # expires => time, now + TTL from response or 3600 minimum
1200             # used => time, time cache item was last used
1201             # who => $blist[0], which DNSBL caused caching
1202             # txt => 'string', txt from our config file or empty
1203             # },
1204 0 0       0 $txt = $DNSBL->{$blist[0]}->{error} if exists $DNSBL->{$blist[0]};
1205 0 0       0 my $trailer = $notRHBL ? revIP($rip) : '';
1206 0 0       0 $txt = $txt ? $txt . $trailer : '';
1207 0         0 $cache{$rip} = {
1208             expires => $now + $ttl, # use responding DNSBL remaining ttl
1209             used => $now,
1210             who => $blist[0],
1211             txt => $txt
1212             };
1213             }
1214 0         0 bump_stats($STATs,$blist[0]);
1215             # $STATs->{"$blist[0]"} += 1; # bump statistics count
1216             # if (exists $CNTs{"$blist[0]"}) {
1217             # $CNTs{"$blist[0]"} += 1;
1218             # } else {
1219             # $CNTs{"$blist[0]"} = 1;
1220             # $AVGs{"$blist[0]"} = 1;
1221             # }
1222             # $newstat = 1 unless $newstat; # notify refresh that update may be needed
1223              
1224 0 0       0 my($nmsg,$noff,@dnptrs) = ($FATans) # make proto answer
1225             ? _ansrbak($put,$id,$nscount + $arcount +1,$rip,$zone,$type,$ttl,$answer,$BLzone,$myip,$txt)
1226             : _ansrbak($put,$id,1,$rip,$zone,$type,$ttl,$answer,$BLzone,$myip,$txt);
1227             ## add the ns section from original reply into the authority section so we can see where it came from, it won't hurt anything
1228 0 0       0 if ($FATans) {
1229 0         0 foreach(0..$nscount -1) {
1230 0         0 ($off,$Oname,$Otype,$Oclass,$Ottl,$Ordlength,$Odata)
1231             = $get->next(\$msg,$off);
1232 0         0 ($noff,@dnptrs) = $put->NS(\$nmsg,$noff,\@dnptrs,
1233             $Oname,$Otype,$Oclass,$Ottl,$Odata);
1234             }
1235              
1236             # add the authority section from original reply so we can see where it came from
1237 0         0 foreach(0..$arcount -1) {
1238 0         0 ($off,$Oname,$Otype,$Oclass,$Ottl,$Ordlength,$Odata)
1239             = $get->next(\$msg,$off);
1240 0 0       0 if ($Otype == T_A) {
    0          
1241 0         0 ($noff,@dnptrs) = $put->A(\$nmsg,$noff,\@dnptrs,
1242             $Oname,$Otype,$Oclass,$Ottl,$Odata);
1243             } elsif ($Otype == T_AAAA) {
1244 0         0 ($noff,@dnptrs) = $put->AAAA(\$nmsg,$noff,\@dnptrs,
1245             $Oname,$Otype,$Oclass,$Ottl,$Odata);
1246             } else {
1247 0         0 next; # skip unknown authority types
1248             }
1249             }
1250             } # end FATans
1251             # if ANSWER
1252 0 0 0     0 if ($eXT && exists $eXT->{ANSWER} && $eXT->{ANSWER}->($eXT,$get,$put,$rid,$ttl,\$nmsg,\%remoteThreads)) {
      0        
1253             ; # will update $nmsg
1254             }
1255 0         0 delete $remoteThreads{$rid};
1256 0         0 $msg = $nmsg;
1257 0 0       0 $ROK = 0 if $DEBUG & $D_ANSTOP;
1258             }
1259             # no answer
1260             elsif (do {
1261 0 0       0 print STDERR '+' if $DEBUG & $D_VERBOSE;
1262             #print Tmp "While eliminate $rid $blist[0]\n";
1263 0         0 my $rv = 0;
1264 0         0 while(!$rv) {
1265 0         0 shift @blist;
1266 0 0       0 unless (@blist) {
1267 0         0 $rv = 1;
1268             } else {
1269 0 0       0 last unless $deadDNSBL{"$blist[0]"} > $numberoftries; # ignore hosts that don't answer
1270             }
1271             }
1272 0         0 $rv;
1273             }) { # if no more hosts
1274             # if NOTFOUND
1275 0 0 0     0 not_found($put,$rip .'.'. $zone,$type,$id,\$msg,$SOAptr) # send not found response
      0        
1276             unless $eXT && exists $eXT->{NOTFOUND} && $eXT->{NOTFOUND}->($eXT,$get,$put,$rid,$rip,\$type,\$zone,\$msg,\%remoteThreads);
1277 0         0 delete $remoteThreads{$rid};
1278             # endif
1279 0         0 $STATs->{Passed} += 1;
1280 0 0       0 $newstat = 1 unless $newstat; # notify refresh that update may be needed
1281             } else {
1282 0         0 $deadDNSBL{"$blist[0]"} = 1; # reset retry count
1283             #print Tmp "NOTFOUND bl_lookup, R \n";
1284 0         0 $rid = setURBLdom($rip,$rid,$notRHBL,$ubl,$DNSBL->{$blist[0]},\%remoteThreads,1); # initialize urbl domain lookup name
1285 0         0 bl_lookup($put,\$msg,\%remoteThreads,$l_Sin,_alarm($blist[0]),$rid,$id,$rip,$type,$zone,@blist);
1286 0 0       0 print STDERR $blist[0] if $DEBUG & $D_VERBOSE;
1287 0         0 send($R,$msg,0,$R_Sin); # udp may not block
1288 0         0 last;
1289             }
1290 0         0 send($L,$msg,0,$l_Sin);
1291              
1292 0 0       0 if ($DEBUG & $D_VERBOSE) {
1293 0 0       0 if ($answer) {
1294 0         0 print STDERR ' ',inet_ntoa($answer),"\n";
1295             } else {
1296 0         0 print STDERR " no bl\n";
1297             }
1298             }
1299 0         0 last;
1300             }
1301             }
1302             ##################### TIMEOUT, do busywork #######################
1303             else { # must be timeout
1304 10         42 my $prpshadow = $prp;
1305 10         30 $now = time; # check various alarm status
1306 10 50       63 unless ($now < $next) {
1307 0         0 average($STATs);
1308 0 0       0 purge_cache() if $prp < 0; # initiate cache purge every 5 minutes or so
1309             }
1310 10 50       165 purge_cache() unless $prpshadow < 0; # run cache purge thread unless just initiated
1311 10         117 foreach $rid (keys %remoteThreads) {
1312 10 100       161 next unless $remoteThreads{$rid}->{expire} < $now; # expired??
1313              
1314 5         17 ($l_Sin,$rip,$id,$type,$zone,@blist) = @{$remoteThreads{$rid}->{args}};
  5         206  
1315              
1316 5 50       43 if (++$deadDNSBL{"$blist[0]"} > $numberoftries) {
1317 0         0 $deadDNSBL{"$blist[0]"} = 3600; # wait an hour to retry
1318 0 0       0 if ($LogLevel) {
1319 0         0 syslog($LogLevel, "timeout connecting to $blist[0]\n");
1320             }
1321             }
1322              
1323 5 100       29 if ($blist[0] eq 'in-addr.arpa') { # expired reverse DNS lookup ?
    100          
1324 1         9 delete $remoteThreads{$rid};
1325 1         5 $deadDNSBL{"$blist[0]"} = 0; # reset timeout (this one never expires)
1326 1 50       30 my $txt = exists $DNSBL->{$blist[0]}
1327             ? $DNSBL->{$blist[0]}->{error}
1328             : '';
1329 1         24 $cache{$rip} = {
1330             expires => $now + 3600, # always an hour
1331             used => $now,
1332             who => $blist[0],
1333             txt => $txt
1334             };
1335 1         7 bump_stats($STATs,$blist[0]);
1336             # $STATs->{"$blist[0]"} += 1; # bump statistics count
1337             # if (exists $CNTs{"$blist[0]"}) {
1338             # $CNTs{"$blist[0]"} += 1;
1339             # } else {
1340             # $CNTs{"$blist[0]"} = 1;
1341             # $AVGs{"$blist[0]"} = 1;
1342             # }
1343             # $newstat = 1 unless $newstat; # notify refresh that update may be needed
1344 1         18 ($msg) = _ansrbak($put,$id,1,$rip,$zone,$type,3600,A1274,$BLzone,$myip,$txt);
1345 1         63 send($L,$msg,0,$l_Sin);
1346 1 50       8 print STDERR " expired Rdns\n" if $DEBUG & $D_VERBOSE;
1347             }
1348             elsif (do {
1349 4 50       21 print STDERR '?' if $DEBUG & $D_VERBOSE;
1350 4         10 my $rv = 0;
1351 4         19 while(!$rv) {
1352 4         9 shift @blist;
1353 4 100       51 unless (@blist) {
1354 1         83 $rv = 1;
1355             } else {
1356 3 50       19 last unless $deadDNSBL{"$blist[0]"} > $numberoftries; # ignore hosts that don't answer
1357             }
1358             }
1359 4         19 $rv;
1360             }) { # if no more hosts
1361             # if NOTFOUND
1362 1 0 33     33 not_found($put,$rip .'.'. $BLzone,$type,$id,\$msg,$SOAptr) # send not found response
      33        
1363             unless $eXT && exists $eXT->{NOTFOUND} && $eXT->{NOTFOUND}->($eXT,$get,$put,$rid,$rip,\$type,\$BLzone,\$msg,\%remoteThreads);
1364 1         4399 delete $remoteThreads{$rid};
1365             # endif
1366 1         6 $STATs->{Passed} += 1; # count messages that pass thru this filter
1367 1 50       7 $newstat = 1 unless $newstat; # notify refresh that update may be needed
1368 1         71 send($L,$msg,0,$l_Sin);
1369 1 50       12 print STDERR " no bl\n" if $DEBUG & $D_VERBOSE;
1370             } else {
1371             #print Tmp "second NOTFOUND\n";
1372 3         28 bl_lookup($put,\$msg,\%remoteThreads,$l_Sin,_alarm($blist[0]),$rid,$id,$rip,$type,$zone,@blist);
1373 3         289 send($R,$msg,0,$R_Sin); # udp may not block
1374 3 50       24 print STDERR $blist[0] if $DEBUG & $D_VERBOSE;
1375             }
1376             }
1377 10         66 foreach(keys %deadDNSBL) { # eventually retry dead DNSBL
1378 26 50       100 --$deadDNSBL{"$_"} if $deadDNSBL{"$_"} > $numberoftries;
1379             }
1380 10 100 66     123 if ($newstat > 1 ||
      33        
1381             ($refresh < $now && $newstat)) { # update stats file
1382 1         14 write_stats($Sfile,$STATs,$StatStamp,$csize,$cused);
1383 1         4 $refresh = $now + $$Run;
1384 1         4 $newstat = 0;
1385             }
1386 10 50       205 return 'caught timer' if $DEBUG & $D_TIMONLY;
1387             }
1388             } while($$Run && $ROK);
1389 24 50       1372 write_stats($Sfile,$STATs,$StatStamp,$csize,$cused) if $newstat; # always update on exit if needed
1390             }
1391              
1392             # answer back prototype
1393             #
1394             # input: $put,$id,$arcount,$rip,$zone,$type,$ttl,$answer,$BLzone,$myip,$withtxt,$CD
1395             # returns: $message,$off,@dnptrs
1396             #
1397             sub _ansrbak {
1398 2     2   19 my($put,$id,$arc,$rip,$zone,$type,$ttl,$ans,$BLzone,$myip,$withtxt,$CD) = @_;
1399 2 50 33     31 my $haveA = ($type == T_A || $type == T_ANY) ? 1 : 0;
1400 2 50 33     33 my $haveT = (($type == T_ANY || $type == T_TXT) && $withtxt) ? 1 : 0;
1401 2 50       151 $CD = $CD ? 0 : CD;
1402 2         40 my $nmsg;
1403 2         5 my $nans = $haveA + $haveT;
1404 2         8 my $noff = newhead(\$nmsg,
1405             $id,
1406             BITS_QUERY | QR,
1407             1,$nans,1,$arc,
1408             );
1409 2         138 ($noff,my @dnptrs) = $put->Question(\$nmsg,$noff, # 1 question
1410             $rip .'.'. $zone,$type,C_IN); # type is T_A or T_ANY or T_TXT
1411 2 50       68 if ($haveA) {
1412 2         10 ($noff,@dnptrs) = $put->A(\$nmsg,$noff,\@dnptrs, # add 1 answer
1413             $rip .'.'. $zone,T_A,C_IN,$ttl,$ans);
1414             }
1415 2 50       7407 if ($haveT) {
1416 0         0 ($noff,@dnptrs) = $put->TXT(\$nmsg,$noff,\@dnptrs,
1417             $rip .'.'. $zone,T_TXT,C_IN,$ttl,$withtxt);
1418             }
1419 2         15 ($noff,@dnptrs) = $put->NS(\$nmsg,$noff,\@dnptrs, # 1 authority
1420             $zone,T_NS,C_IN,86400,$BLzone);
1421 2         3304 ($noff,@dnptrs) = $put->A(\$nmsg,$noff,\@dnptrs, # 1 additional glue
1422             $BLzone,T_A,C_IN,86400,$myip); # show MYIP
1423 2         144 return($nmsg,$noff,@dnptrs)
1424             }
1425              
1426             =item * bl_lookup($put,$mp,$rtp,$sinaddr,$alarm,$rid,$id,$rip,$type,$zone,@blist);
1427              
1428             Generates a query message for the first DNSBL in the @blist array. Creates
1429             a thread entry for the response and subsequent queries should the first one fail.
1430              
1431             input: put,
1432             message pointer,
1433             remote thread pointer,
1434             sockinaddr,
1435             connection timeout,
1436             remote id or undef to create
1437             id of question,
1438             reverse IP address in text
1439             type of query received, (used in response)
1440             ORIGINAL zone (case preserved),
1441             array of remaining DNSBL's in sorted order
1442             returns: nothing, puts stuff in thread queue
1443              
1444             extra: if URBL processing is required,
1445             $remoteThreads{$rid}->{urbl}
1446             is set to the domain to look up
1447              
1448             =cut
1449              
1450             # This function returns an integer between 1 -> 65535 in a pseudo-random
1451             # repeatable order. Seeds with $$ by default, can be seeded with any integer;
1452             #
1453              
1454             my $id = $$;
1455              
1456             sub uniqueID {
1457 54 100   54 0 84806475 $id = $_[0] ? ($_[0] % 65536) : $id;
1458 54 50 33     772 $id = 1 if $id < 1 || $id > 65534;
1459 54         198 $id++;
1460             }
1461              
1462             sub bl_lookup {
1463 27     27 1 501485 my($put,$mp,$rtp,$sinaddr,$alarm,$rid,$id,$rip,$type,$zone,@blist) = @_;
1464 27 100       187 $rid = uniqueID unless $rid;
1465 27         276 my $off = newhead($mp,
1466             $rid,
1467             BITS_QUERY | RD,
1468             1,0,0,0,
1469             );
1470 27 50       3247 my $blist = ($blist[0] eq 'genericPTR')
1471             ? 'in-addr.arpa'
1472             : $blist[0];
1473              
1474 27 100       134 my $Qtype = ($blist eq 'in-addr.arpa')
1475             ? &T_PTR
1476             : &T_A;
1477              
1478             # send conditioned URBL request if that is what is needed
1479 27 50       532 if ($rtp->{$rid}->{urbl}) {
1480 0         0 $put->Question($mp,$off,$rtp->{$rid}->{urbl}.'.'. $blist,$Qtype,C_IN);
1481             } else {
1482 27         140 $put->Question($mp,$off,$rip .'.'. $blist,$Qtype,C_IN);
1483             }
1484 27 50       2670 $rtp->{$rid} = {} unless exists $rtp->{$rid};
1485 27         364 $rtp->{$rid}->{args} = [$sinaddr,$rip,$id,$type,$zone,@blist];
1486 27         172 $rtp->{$rid}->{expire} = time + $alarm;
1487             #print Tmp "$blist => ",Dumper($rtp);
1488             }
1489              
1490             =item * set_extension($pointer);
1491              
1492             This function sets a pointer to user defined extensions to
1493             Net::DNSBL::MultiDaemon.
1494              
1495             Pointer is of the form:
1496              
1497             $Extension ->{
1498             OPCODE => value,
1499             CLASS => subref->($Extension,internal args),
1500             NAME => subref->($Extension,internal args),
1501             TYPE => subref->($Extension,internal args),
1502             LOOKUP => subref->($Extension,internal args),
1503             ANSWER => subref->($Extension,internal args),
1504             NOTFOUND => subref->($Extension,internal args)
1505             };
1506              
1507             The pointer should be blessed into the package of the caller if the calling
1508             package needs to store persistant variables for its own instance. The subref
1509             will be called with the first argument of $Extension.
1510              
1511             Care should be taken to NOT instantiate a %remoteThreads in the CLASS, NAME,
1512             TYPE section unless it is know that it will be found and expired/deleted.
1513              
1514             Read the code if you wish to add an extension
1515              
1516             =back
1517              
1518             =cut
1519              
1520             sub set_extension {
1521 0     0 1   $eXT = shift;
1522             }
1523              
1524             =head1 DEPENDENCIES
1525              
1526             Unix::Syslog
1527             Geo::IP::PurePerl [conditional for country codes]
1528             NetAddr::IP
1529             Net::DNS::Codes
1530             Net::DNS::ToolKit
1531              
1532             =head1 EXPORT_OK
1533              
1534             run
1535             bl_lookup
1536              
1537             =head1 EXPORT_TAGS :debug
1538              
1539             DEBUG is a set of semaphores for the 'run' function
1540              
1541             $D_CLRRUN = 0x1; # clear run flag and force unconditional return
1542             $D_SHRTHD = 0x2; # return short header message
1543             $D_TIMONLY = 0x4; # exit at end of timer section
1544             $D_QRESP = 0x8; # return query response message
1545             $D_NOTME = 0x10; # return received response not for me
1546             $D_ANSTOP = 0x20; # clear run OK flag if ANSWER present
1547             $D_VERBOSE = 0x40; # verbose debug statements to STDERR
1548              
1549             =head1 AUTHOR
1550              
1551             Michael Robinton, michael@bizsystems.com
1552              
1553             =head1 COPYRIGHT
1554              
1555             Copyright 2003 - 2014, Michael Robinton & BizSystems
1556             This program is free software; you can redistribute it and/or modify
1557             it under the terms as Perl itself or the GNU General Public License
1558             as published by the Free Software Foundation; either version 2 of
1559             the License, or (at your option) any later version.
1560              
1561             This program is distributed in the hope that it will be useful,
1562             but WITHOUT ANY WARRANTY; without even the implied warranty of
1563             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1564             GNU General Public License for more details.
1565              
1566             You should have received a copy of the GNU General Public License
1567             along with this program; if not, write to the Free Software
1568             Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
1569              
1570             =head1 SEE ALSO
1571              
1572             L, L, L, L, L, L
1573              
1574             =cut
1575              
1576             1;