File Coverage

blib/lib/DNS/BL/cmds/connect/db.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package DNS::BL::cmds::connect::db;
2              
3 2     2   1455 use DNS::BL;
  2         4  
  2         67  
4              
5 2     2   45 use 5.006001;
  2         9  
  2         123  
6 2     2   13 use strict;
  2         4  
  2         63  
7 2     2   10 use warnings;
  2         4  
  2         66  
8 2     2   10 use Fcntl qw(:DEFAULT);
  2         4  
  2         1262  
9              
10 2     2   999 use MLDBM qw(DB_File Storable);
  0            
  0            
11              
12             use vars qw/@ISA/;
13              
14             @ISA = qw/DNS::BL::cmds/;
15              
16             use Carp;
17              
18             our $VERSION = '0.00_01';
19             $VERSION = eval $VERSION; # see L
20              
21             # Preloaded methods go here.
22              
23             =pod
24              
25             =head1 NAME
26              
27             DNS::BL::cmds::connect::db - Implement the DB connect command for DNS::BL
28              
29             =head1 SYNOPSIS
30              
31             use DNS::BL::cmds::connect::db;
32              
33             =head1 DESCRIPTION
34              
35             This module implements the connection to a DB backend where C
36             data will be stored. On each call to this class' methods, a hash will
37             be Cd and then Cd. This guarantees that the underlying
38             DB structure will be unlocked and available for other commands that
39             may, for instance, replace or manipulate the hash "from under us".
40              
41             The following methods are implemented by this module:
42              
43             =over
44              
45             =item C<-Eexecute()>
46              
47             See L for information on this method's purpose.
48              
49             The connect command follows a syntax such as
50              
51             connect db ...
52              
53             Note that the 'connect' token must be removed by the calling class,
54             usually C. B are key - value pairs
55             specifying different parameters as described below. Unknown parameters
56             are reported as errors. The complete calling sequence is as
57              
58             connect db file "filename" [mode bulk]
59              
60             Where "filename" refers to the DB file where data is to be found. If
61             the file does not exist, it will be created (provided that permissions
62             allow).
63              
64             If "mode bulk" is indicated, arrangements are made to tie() to the
65             database once. This makes the operation slightly faster, but increases
66             the chance of collision when concurrent access to the backing store is
67             performed.
68              
69             This class will be Cd and then, its C method invoked
70             following the same protocol outlined in L. Prior C
71             information is to be removed by the calling class.
72              
73             =cut
74              
75             sub execute
76             {
77             my $bl = shift;
78             my $command = shift; # Expect "db"
79             my %args = @_;
80              
81             my @known = qw/file mode/;
82              
83             unless ($command eq 'db')
84             {
85             return wantarray ?
86             (&DNS::BL::DNSBL_ESYNTAX(),
87             "'" . __PACKAGE__ . "' invoked by connect type '$command'")
88             : &DNS::BL::DNSBL_ESYNTAX();
89             }
90              
91             for my $k (keys %args)
92             {
93             unless (grep { $k eq $_ } @known)
94             {
95             return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(),
96             "Unknown argument '$k' to 'connect db'")
97             : &DNS::BL::DNSBL_ESYNTAX();
98             }
99             }
100              
101             unless (exists $args{file} and length($args{file}))
102             {
103             return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(),
104             "Missing file name for 'connect db'")
105             : &DNS::BL::DNSBL_ESYNTAX();
106             }
107              
108             # Store the passed data
109             $args{_class} = __PACKAGE__;
110              
111             if (exists $args{mode})
112             {
113             if ($args{mode} eq 'bulk')
114             {
115             my %db = ();
116             unless (tie %db, 'MLDBM', $args{file}, O_CREAT|O_RDWR, 0640)
117             {
118             return wantarray ?
119             (&DNS::BL::DNSBL_ECONNECT(),
120             "Cannot tie to file '$args{file}'")
121             : &DNS::BL::DNSBL_ECONNECT();
122             }
123             $args{_db} = \%db;
124             }
125             else
126             {
127             return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(),
128             "Missing or wrong name for 'connect db mode'")
129             : &DNS::BL::DNSBL_ESYNTAX();
130             }
131             }
132              
133             $bl->set("_connect", \%args);
134              
135             # Add I/O methods to the $bl object so that further calls can be
136             # processed
137              
138             $bl->set("_read", \&_read);
139             $bl->set("_match", \&_match);
140             $bl->set("_write", \&_write);
141             $bl->set("_erase", \&_delete);
142             $bl->set("_commit", \&_commit);
143            
144             return wantarray ? (&DNS::BL::DNSBL_OK, "Connected to DB") :
145             &DNS::BL::DNSBL_OK;
146             };
147              
148             sub _portal
149             {
150             my $bl = shift; # Calling BL object
151             my $data = $bl->get('_connect');
152              
153             my %r = (); # Placeholder for the DB
154              
155             unless ($data or $data->{_class} eq __PACKAGE__)
156             {
157             return wantarray ?
158             (&DNS::BL::DNSBL_ESYNTAX(),
159             "->write can only be called while 'connect db' is in effect")
160             : &DNS::BL::DNSBL_ESYNTAX();
161             }
162              
163             if (exists $data->{_db})
164             {
165             return wantarray ? (&DNS::BL::DNSBL_OK, "DB tied", $data->{_db}) :
166             &DNS::BL::DNSBL_OK;
167             }
168             else
169             {
170             unless (tie %r, 'MLDBM', $data->{file}, O_CREAT|O_RDWR, 0640)
171             {
172             return wantarray ?
173             (&DNS::BL::DNSBL_ECONNECT(),
174             "Cannot tie to file '" . $data->{file} . "'")
175             : &DNS::BL::DNSBL_ECONNECT();
176             }
177             return wantarray ? (&DNS::BL::DNSBL_OK, "DB tied", \%r) :
178             &DNS::BL::DNSBL_OK;
179             }
180             }
181              
182             sub _write
183             {
184             my $bl = shift;
185             my $e = shift;
186              
187             my @r = _portal($bl);
188             return wantarray ? @r : $r[0] if $r[0] != &DNS::BL::DNSBL_OK;
189              
190             my $db = $r[2];
191              
192             # The index update is only needed if no prior entry exists.
193             unless (exists $db->{$e->addr->network->cidr})
194             {
195             # Build the index until level - 1
196             if ($e->addr->masklen > 0)
197             {
198             for my $m (0 .. $e->addr->masklen - 1)
199             {
200             my $f = NetAddr::IP->new($e->addr->addr . "/$m")->network;
201             my @c = grep { $_->contains($e->addr) } $f->split($m + 1);
202             my $i = $db->{'index:' . $f} || [];
203             unless (grep { 'index:' . $c[0]->cidr eq $_ } @$i)
204             {
205             push @$i, 'index:' . $c[0]->cidr;
206             $db->{'index:' . $f} = $i;
207             }
208             }
209             }
210              
211             # Build the last index level
212             my $i = $db->{'index:' . $e->addr->network->cidr} || [];
213             unless (grep { 'index:' . $e->addr->network->cidr eq $_ } @$i)
214             {
215             push @$i, 'node:' . $e->addr->network->cidr;
216             $db->{'index:' . $e->addr->network->cidr} = $i;
217             }
218             }
219              
220             # Store the actual entry in the hash
221             $db->{$e->addr->network->cidr} = $e;
222              
223             return wantarray ? (&DNS::BL::DNSBL_OK, "OK - Done") :
224             &DNS::BL::DNSBL_OK;
225             }
226              
227             sub _read
228             {
229             my $bl = shift;
230             my $e = shift;
231              
232             my @r = _portal($bl);
233             return wantarray ? @r : $r[0] if $r[0] != &DNS::BL::DNSBL_OK;
234              
235             my $db = $r[2];
236              
237             my @ret = ();
238             my $index = $db->{'index:' . $e->addr->network->cidr} || [];
239              
240             # Use the index to find the entries that must be attached
241             while (@$index)
242             {
243             my $l = shift @$index;
244             # print "_read: Index checking $l out of ", 0 + @$index, "\n";
245             if (substr($l, 0, 5) eq 'node:')
246             {
247             my $ip = new NetAddr::IP substr($l, 5);
248             # print "_read: Consider $ip\n";
249             push @ret, $ip if $e->addr->contains($ip);
250             }
251             elsif (substr($l, 0, 6) eq 'index:')
252             {
253             my $ip = new NetAddr::IP substr($l, 6);
254             if ($e->addr->contains($ip))
255             {
256             my $i = $db->{$l};
257             push @$index, @$i if $i;
258             # print "_read: Add $l to queue\n";
259             }
260             }
261             }
262            
263             @ret = grep { defined $_ } map { $db->{$_->network->cidr} } @ret;
264              
265             return (&DNS::BL::DNSBL_OK, scalar @ret . " entries found",
266             @ret) if @ret;
267             return (&DNS::BL::DNSBL_ENOTFOUND, "No entries matched");
268             }
269              
270             sub _match
271             {
272             my $bl = shift;
273             my $e = shift;
274              
275             my @r = _portal($bl);
276             return wantarray ? @r : $r[0] if $r[0] != &DNS::BL::DNSBL_OK;
277              
278             my $db = $r[2];
279              
280             my @ret = ();
281             my $index = $db->{'index:' . NetAddr::IP->new('any')->network->cidr} || [];
282              
283             # Use the index to find the entries that must be attached
284             while (@$index)
285             {
286             my $l = shift @$index;
287             # print "_match: Index checking $l out of ", 0 + @$index, "\n";
288             if (substr($l, 0, 5) eq 'node:')
289             {
290             my $ip = new NetAddr::IP substr($l, 5);
291             # print "_match: Consider $ip\n";
292             push @ret, $ip if $e->addr->within($ip);
293             }
294             elsif (substr($l, 0, 6) eq 'index:')
295             {
296             my $ip = new NetAddr::IP substr($l, 6);
297             if ($e->addr->within($ip))
298             {
299             my $i = $db->{$l};
300             push @$index, @$i if $i;
301             # print "_match: Add $l to queue\n";
302             }
303             }
304             }
305            
306             @ret = grep { defined $_ } map { $db->{$_->network->cidr} } @ret;
307              
308             return (&DNS::BL::DNSBL_OK, scalar @ret . " entries found",
309             @ret) if @ret;
310             return (&DNS::BL::DNSBL_ENOTFOUND, "No entries matched");
311             }
312              
313             sub _commit
314             {
315             return wantarray ? (&DNS::BL::DNSBL_OK, "commit is not required with DB")
316             : &DNS::BL::DNSBL_OK;
317             }
318              
319             sub _delete
320             {
321             my $bl = shift;
322             my $e = shift;
323              
324             my @r = _portal($bl);
325             return wantarray ? @r : $r[0] if $r[0] != &DNS::BL::DNSBL_OK;
326              
327             my $db = $r[2];
328             my $num = 0;
329             my @ret = ();
330             my $index = $db->{'index:' . $e->addr->network->cidr} || [];
331              
332             # Use the index to find which entries must be deleted
333             while (@$index)
334             {
335             my $l = shift @$index;
336             # print "_delete: Index checking $l out of ", 0 + @$index, "\n";
337             if (substr($l, 0, 5) eq 'node:')
338             {
339             my $ip = new NetAddr::IP substr($l, 5);
340             # print "_delete: Consider $ip\n";
341             push @ret, $ip if $e->addr->contains($ip);
342             }
343             elsif (substr($l, 0, 6) eq 'index:')
344             {
345             my $ip = new NetAddr::IP substr($l, 6);
346             if ($e->addr->contains($ip))
347             {
348             my $i = $db->{$l};
349             push @$index, @$i if $i;
350             # print "_delete: Add $l to queue\n";
351             }
352             }
353             }
354              
355             # Based on the hits, delete entries from the hash and from
356             # the cache
357             for my $n (@ret)
358             {
359             # print "_delete: deleting 'node:" . $n->network->cidr . "'\n";
360             delete $db->{$n->network->cidr};
361             ++ $num;
362              
363             for my $m (reverse 0 .. $n->masklen)
364             {
365             my $k = 'index:'
366             . NetAddr::IP->new($n->addr . "/$m")->network->cidr;
367             # print "_delete: Check cache for $k\n";
368             my $i = $db->{$k} || [];
369             my @rem = ();
370              
371             push @rem, grep { substr($_, 0, 6) eq 'index:'
372             and exists $db->{$_} } @$i;
373              
374             push @rem,
375             grep { $_ }
376             map { $_->[1] if exists $db->{$_->[0]} }
377             map { [ substr($_, 5), $_ ] }
378             grep { substr($_, 0, 5) eq 'node:' }
379             @$i;
380              
381             # print "_delete: db $k -> [", join(',', @$i) || 'empty', "]\n";
382             # print "_delete: rem $k -> [", join(',', @rem) || 'empty', "]\n";
383             # print "_delete: comp=", ($#rem == $#$i), ", rem=",
384             # scalar @rem, ", i=", scalar @$i, "\n";
385             # print "_delete: rem=",
386             # map { defined $_ ? $_ ? $_ : 'false' : 'undef' } @rem, "\n";
387             # print "_delete: i=",
388             # map { defined $_ ? $_ ? $_ : 'false' : 'undef' } @$i, "\n";
389              
390             if (@rem == @$i)
391             {
392             # print "_delete: This node was unchanged - Skip the rest\n";
393             last;
394             }
395             elsif (@rem)
396             {
397             $db->{$k} = \@rem;
398             # print "_delete: rebuild index node '$k'\n";
399             }
400             else
401             {
402             # print "_delete: delete index node '$k'\n";
403             delete $db->{$k};
404             }
405             }
406             }
407              
408             if ($num)
409             {
410             return (&DNS::BL::DNSBL_OK, "$num entries deleted");
411             }
412             else
413             {
414             return (&DNS::BL::DNSBL_ENOTFOUND, "No entries deleted");
415             }
416             }
417              
418             sub DNS::BL::cmds::_db_dump::execute
419             {
420             my $bl = shift;
421             my %db = ();
422              
423             my $data = $bl->get('_connect');
424              
425             unless ($data or $data->{_class} eq __PACKAGE__)
426             {
427             return wantarray ?
428             (&DNS::BL::DNSBL_ESYNTAX(),
429             "'db_dump' can only be called while 'connect db' is in effect")
430             : &DNS::BL::DNSBL_ESYNTAX();
431             }
432              
433             unless (tie %db, 'MLDBM', $data->{file}, O_RDONLY, 0640)
434             {
435             return wantarray ?
436             (&DNS::BL::DNSBL_ECONNECT(),
437             "Cannot tie to file '" . $data->{file} . "'")
438             : &DNS::BL::DNSBL_ECONNECT();
439             }
440              
441             print Data::Dumper->Dump([ \%db ]);
442              
443             untie %db;
444              
445             return wantarray ? (&DNS::BL::DNSBL_OK, "OK - Done") :
446             &DNS::BL::DNSBL_OK;
447             }
448              
449             1;
450             __END__