821152209a73c6e7cd7353f3bf2b25e8de905d1c
[spider.git] / perl / DXCIDR.pm
1 #
2 # IP Address block list / checker
3 #
4 # This is a DXSpider compatible, optional skin over Net::CIDR::Lite
5 # If Net::CIDR::Lite is not present, then a find will always returns 0
6 #
7
8 package DXCIDR;
9
10 use strict;
11 use warnings;
12 use 5.16.1;
13 use DXVars;
14 use DXDebug;
15 use DXUtil;
16 use DXLog;
17 use IO::File;
18 use File::Copy;
19
20 use Socket qw(AF_INET AF_INET6 inet_pton inet_ntop);
21
22 our $active = 0;
23 our $badipfn = "badip";
24 my $ipv4;
25 my $ipv6;
26 my $count4 = 0;
27 my $count6 = 0;
28
29 sub _fn
30 {
31         return localdata($badipfn);
32 }
33
34 sub _read
35 {
36         my $suffix = shift;
37         my $fn = _fn();
38         $fn .= ".$suffix" if $suffix;
39         my $fh = IO::File->new($fn);
40         my @out;
41
42         if ($fh) {
43                 while (<$fh>) {
44                         chomp;
45                         next if /^\s*\#/;
46                         next unless /[\.:]/;
47                         next unless $_;
48                         push @out, $_;
49                 }
50                 $fh->close;
51         } else {
52                 LogDbg('err', "DXCIDR: $fn read error ($!)");
53         }
54         return @out;
55 }
56
57 sub _load
58 {
59         my $suffix = shift;
60         my @in = _read($suffix);
61         return scalar add(@in);
62 }
63
64 sub _put
65 {
66         my $suffix = shift;
67         my $fn = _fn() . ".$suffix";
68         my $r = rand;
69         my $fh = IO::File->new (">$fn.$r");
70         my $count = 0;
71         if ($fh) {
72                 for ($ipv4->list, $ipv6->list) {
73                         $fh->print("$_\n");
74                         ++$count;
75                 }
76                 move "$fn.$r", $fn;
77                 LogDbg('cmd', "DXCIDR: put (re-)written $fn");
78         } else {
79                 LogDbg('err', "DXCIDR: cannot write $fn.$r $!");
80         }
81         return $count;
82 }
83
84 sub append
85 {
86         return 0 unless $active;
87         
88         my $suffix = shift;
89         my @in = @_;
90         my @out;
91         
92         if ($suffix) {
93                 my $fn = _fn() . ".$suffix";
94                 my $fh = IO::File->new;
95                 if ($fh->open("$fn", "a+")) {
96                         $fh->seek(0, 2);        # belt and braces !!
97                         print $fh "$_\n" for @in;
98                         $fh->close;
99                 } else {
100                         LogDbg('err', "DXCIDR::append error appending to $fn $!");
101                 }
102         } else {
103                 LogDbg('err', "DXCIDR::append require badip suffix");
104         }
105         return scalar @in;
106 }
107
108 sub add
109 {
110         return 0 unless $active;
111         my $count = 0;
112         
113         for my $ip (@_) {
114                 # protect against stupid or malicious
115                 next if $ip =~ /^127\./;
116                 next if $ip =~ /^::1$/;
117                 if ($ip =~ /\./) {
118                         $ipv4->add_any($ip);
119                         ++$count;
120                         ++$count4;
121                 } elsif ($ip =~ /:/) {
122                         $ipv6->add_any($ip);
123                         ++$count;
124                         ++$count6;
125                 } else {
126                         LogDbg('err', "DXCIDR::add non-ip address '$ip' read");
127                 }
128         }
129         return $count;
130 }
131
132 sub clean_prep
133 {
134         return unless $active;
135
136         if ($ipv4 && $count4) {
137                 $ipv4->clean;
138                 $ipv4->prep_find;
139         }
140         if ($ipv6 && $count6) {
141                 $ipv6->clean;
142                 $ipv6->prep_find;
143         }
144 }
145
146 sub _sort
147 {
148         my @in;
149         my @out;
150         for (@_) {
151                 push @in, [inet_pton(m|:|?AF_INET6:AF_INET, $_), split m|/|];
152         }
153         @out = sort {$a->[0] <=> $b->[0]} @in;
154         return map { "$_->[1]/$_->[2]"} @out;
155 }
156
157 sub list
158 {
159         return () unless $active;
160         my @out;
161         push @out, $ipv4->list if $count4;
162         push @out, $ipv6->list if $count6;
163         return _sort(@out);
164 }
165
166 sub find
167 {
168         return 0 unless $active;
169         return 0 unless $_[0];
170
171         if ($_[0] =~ /\./) {
172                 return $ipv4->find($_[0]) if $count4;
173         }
174         return $ipv6->find($_[0]) if $count6;
175 }
176
177 sub init
178 {
179         eval { require Net::CIDR::Lite };
180         if ($@) {
181                 LogDbg('DXProt', "DXCIDR: load (cpanm) the perl module Net::CIDR::Lite to check for bad IP addresses (or CIDR ranges)");
182                 return;
183         }
184
185         import Net::CIDR::Lite;
186         $active = 1;
187
188         my $fn = _fn();
189         if (-e $fn) {
190                 move $fn, "$fn.base";
191         }
192
193         _touch("$fn.local");
194         
195         reload();
196
197 }
198
199 sub _touch
200 {
201         my $fn = shift;
202         my $now = time;
203         local (*TMP);
204         utime ($now, $now, $fn) || open (TMP, ">>$fn") || LogDbg('err', "DXCIDR::touch: Couldn't touch $fn: $!");
205 }
206
207 sub reload
208 {
209         return 0 unless $active;
210
211         new();
212
213         my $count = 0;
214         my $files = 0;
215
216         LogDbg('DXProt', "DXCIDR::reload reload database" );
217
218         my $dir;
219         opendir($dir, $main::local_data);
220         while (my $fn = readdir $dir) {
221                 next unless my ($suffix) = $fn =~ /^badip\.(\w+)$/;
222                 my $c = _load($suffix);
223                 LogDbg('DXProt', "DXCIDR::reload: $fn read containing $c ip addresses" );
224                 $count += $c;
225                 $files++;
226         }
227         closedir $dir;
228         
229         LogDbg('DXProt', "DXCIDR::reload $count ip addresses found (IPV4: $count4 IPV6: $count6) in $files badip files" );
230
231         return $count;
232 }
233
234 sub new
235 {
236         return 0 unless $active;
237
238         $ipv4 = Net::CIDR::Lite->new;
239         $ipv6 = Net::CIDR::Lite->new;
240         $count4 = $count6 = 0; 
241 }
242
243 1;