store local and base badips in separate files
[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         my $suffix = shift;
86         my @in = @_;
87         my @out;
88         
89         if ($suffix) {
90                 my $fn = _fn() . ".$suffix";
91                 my $r = rand;
92                 my $fh = IO::File->new (">>$fn.$r");
93                 if ($fh) {
94                         print $fh "$_\n" for @in;
95                         $fh->close;
96                         move "$fn.$r", $fn;
97                 } else {
98                         LogDbg('err', "DXCIDR::append error appending to $fn.$r $!");
99                 }
100         } else {
101                 LogDbg('err', "DXCIDR::append require badip suffix");
102         }
103         return scalar @in;
104 }
105
106 sub add
107 {
108         my $count = 0;
109         
110         for my $ip (@_) {
111                 # protect against stupid or malicious
112                 next if $ip =~ /^127\./;
113                 next if $ip =~ /^::1$/;
114                 if ($ip =~ /\./) {
115                         $ipv4->add_any($ip);
116                         ++$count;
117                         ++$count4;
118                 } elsif ($ip =~ /:/) {
119                         $ipv6->add_any($ip);
120                         ++$count;
121                         ++$count6;
122                 } else {
123                         LogDbg('err', "DXCIDR::add non-ip address '$ip' read");
124                 }
125         }
126         return $count;
127 }
128
129 sub clean_prep
130 {
131         if ($ipv4 && $count4) {
132                 $ipv4->clean;
133                 $ipv4->prep_find;
134         }
135         if ($ipv6 && $count6) {
136                 $ipv6->clean;
137                 $ipv6->prep_find;
138         }
139 }
140
141 sub _sort
142 {
143         my @in;
144         my @out;
145         for (@_) {
146                 push @in, [inet_pton(m|:|?AF_INET6:AF_INET, $_), split m|/|];
147         }
148         @out = sort {$a->[0] <=> $b->[0]} @in;
149         return map { "$_->[1]/$_->[2]"} @out;
150 }
151
152 sub list
153 {
154         my @out;
155         push @out, $ipv4->list if $count4;
156         push @out, $ipv6->list if $count6;
157         return _sort(@out);
158 }
159
160 sub find
161 {
162         return 0 unless $active;
163         return 0 unless $_[0];
164
165         if ($_[0] =~ /\./) {
166                 return $ipv4->find($_[0]) if $count4;
167         }
168         return $ipv6->find($_[0]) if $count6;
169 }
170
171 sub init
172 {
173         eval { require Net::CIDR::Lite };
174         if ($@) {
175                 LogDbg('DXProt', "DXCIDR: load (cpanm) the perl module Net::CIDR::Lite to check for bad IP addresses (or CIDR ranges)");
176                 return;
177         }
178
179         import Net::CIDR::Lite;
180         $active = 1;
181
182         my $fn = _fn();
183         if (-e $fn) {
184                 move $fn, "$fn.base";
185         }
186
187         _touch("$fn.local");
188         
189         reload();
190
191 }
192
193 sub _touch
194 {
195         my $fn = shift;
196         my $now = time;
197         local (*TMP);
198         utime ($now, $now, $fn) || open (TMP, ">>$fn") || LogDbg('err', "DXCIDR::touch: Couldn't touch $fn: $!");
199 }
200
201 sub reload
202 {
203         new();
204
205         my $count = _load('base');
206         $count += _load('local');
207
208         LogDbg('DXProt', "DXCIDR::reload $count ip addresses found (IPV4: $count4 IPV6: $count6)" );
209
210         return $count;
211 }
212
213 sub new
214 {
215         $ipv4 = Net::CIDR::Lite->new;
216         $ipv6 = Net::CIDR::Lite->new;
217         $count4 = $count6 = 0; 
218 }
219
220 1;