Make it work with badip system active
[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                         push @out, $_;
48                 }
49                 $fh->close;
50         } else {
51                 LogDbg('err', "DXCIDR: $fn read error ($!)");
52         }
53         return @out;
54 }
55
56 sub _load
57 {
58         my $suffix = shift;
59         my @in = _read($suffix);
60         return scalar add(@in);
61 }
62
63 sub _put
64 {
65         my $suffix = shift;
66         my $fn = _fn() . ".$suffix";
67         my $r = rand;
68         my $fh = IO::File->new (">$fn.$r");
69         my $count = 0;
70         if ($fh) {
71                 for ($ipv4->list, $ipv6->list) {
72                         $fh->print("$_\n");
73                         ++$count;
74                 }
75                 move "$fn.$r", $fn;
76                 LogDbg('cmd', "DXCIDR: put (re-)written $fn");
77         } else {
78                 LogDbg('err', "DXCIDR: cannot write $fn.$r $!");
79         }
80         return $count;
81 }
82
83 sub append
84 {
85         return 0 unless $active;
86         
87         my $suffix = shift;
88         my @in = @_;
89         my @out;
90         
91         if ($suffix) {
92                 my $fn = _fn() . ".$suffix";
93                 my $fh = IO::File->new;
94                 if ($fh->open("$fn", "a+")) {
95                         $fh->seek(0, 2);        # belt and braces !!
96                         print $fh "$_\n" for @in;
97                         $fh->close;
98                 } else {
99                         LogDbg('err', "DXCIDR::append error appending to $fn $!");
100                 }
101         } else {
102                 LogDbg('err', "DXCIDR::append require badip suffix");
103         }
104         return scalar @in;
105 }
106
107 sub add
108 {
109         return 0 unless $active;
110         my $count = 0;
111         
112         for my $ip (@_) {
113                 # protect against stupid or malicious
114                 next if $ip =~ /^127\./;
115                 next if $ip =~ /^::1$/;
116                 if ($ip =~ /\./) {
117                         $ipv4->add_any($ip);
118                         ++$count;
119                         ++$count4;
120                 } elsif ($ip =~ /:/) {
121                         $ipv6->add_any($ip);
122                         ++$count;
123                         ++$count6;
124                 } else {
125                         LogDbg('err', "DXCIDR::add non-ip address '$ip' read");
126                 }
127         }
128         return $count;
129 }
130
131 sub clean_prep
132 {
133         return unless $active;
134
135         if ($ipv4 && $count4) {
136                 $ipv4->clean;
137                 $ipv4->prep_find;
138         }
139         if ($ipv6 && $count6) {
140                 $ipv6->clean;
141                 $ipv6->prep_find;
142         }
143 }
144
145 sub _sort
146 {
147         my @in;
148         my @out;
149         for (@_) {
150                 push @in, [inet_pton(m|:|?AF_INET6:AF_INET, $_), split m|/|];
151         }
152         @out = sort {$a->[0] <=> $b->[0]} @in;
153         return map { "$_->[1]/$_->[2]"} @out;
154 }
155
156 sub list
157 {
158         return () unless $active;
159         my @out;
160         push @out, $ipv4->list if $count4;
161         push @out, $ipv6->list if $count6;
162         return _sort(@out);
163 }
164
165 sub find
166 {
167         return 0 unless $active;
168         return 0 unless $_[0];
169
170         if ($_[0] =~ /\./) {
171                 return $ipv4->find($_[0]) if $count4;
172         }
173         return $ipv6->find($_[0]) if $count6;
174 }
175
176 sub init
177 {
178         eval { require Net::CIDR::Lite };
179         if ($@) {
180                 LogDbg('DXProt', "DXCIDR: load (cpanm) the perl module Net::CIDR::Lite to check for bad IP addresses (or CIDR ranges)");
181                 return;
182         }
183
184         import Net::CIDR::Lite;
185         $active = 1;
186
187         my $fn = _fn();
188         if (-e $fn) {
189                 move $fn, "$fn.base";
190         }
191
192         _touch("$fn.local");
193         
194         reload();
195
196 }
197
198 sub _touch
199 {
200         my $fn = shift;
201         my $now = time;
202         local (*TMP);
203         utime ($now, $now, $fn) || open (TMP, ">>$fn") || LogDbg('err', "DXCIDR::touch: Couldn't touch $fn: $!");
204 }
205
206 sub reload
207 {
208         return 0 unless $active;
209
210         new();
211
212         my $count = 0;
213         my $files = 0;
214
215         LogDbg('DXProt', "DXCIDR::reload reload database" );
216
217         my $dir;
218         opendir($dir, $main::local_data);
219         while (my $fn = readdir $dir) {
220                 next unless my ($suffix) = $fn =~ /^badip\.(\w+)$/;
221                 my $c = _load($suffix);
222                 LogDbg('DXProt', "DXCIDR::reload: $fn read containing $c ip addresses" );
223                 $count += $c;
224                 $files++;
225         }
226         closedir $dir;
227         
228         LogDbg('DXProt', "DXCIDR::reload $count ip addresses found (IPV4: $count4 IPV6: $count6) in $files badip files" );
229
230         return $count;
231 }
232
233 sub new
234 {
235         return 0 unless $active;
236
237         $ipv4 = Net::CIDR::Lite->new;
238         $ipv6 = Net::CIDR::Lite->new;
239         $count4 = $count6 = 0; 
240 }
241
242 1;