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