package DNS::BL::cmds::connect::dbi; use DNS::BL; use 5.006001; use strict; use warnings; use Fcntl qw(:DEFAULT); use DBI; use vars qw/@ISA/; @ISA = qw/DNS::BL::cmds/; use Carp; our $VERSION = '0.00_01'; $VERSION = eval $VERSION; # see L # Preloaded methods go here. =pod =head1 NAME DNS::BL::cmds::connect::dbi - Implement the DB connect command with DBI for DNS::BL =head1 SYNOPSIS use DNS::BL::cmds::connect::dbi; =head1 DESCRIPTION This module implements the connection to a DB backend where C data will be stored. This backend is implemented through L. The following methods are implemented by this module: =over =item C<-Eexecute()> See L for information on this method's purpose. The connect command follows a syntax such as connect dbi ... Note that the 'connect' token must be removed by the calling class, usually C. B are key - value pairs specifying different parameters as described below. Unknown parameters are reported as errors. The complete calling sequence is as connect dbi [user username] [password pwd] dsn dsn-string bl list Where each of the arguments mean the following: =over =item B The string that should be passed to DBI as the backend identifier. =item B The username for connecting to the server. If left unspecified, defaults to "dnsbl-ro". =item B The password for connecting as the given user. Defaults to a blank password. =item B The name of the list on which you want to operate. This is a local convention and every site has its own set of lists. =back This class will be Cd and then, its C method invoked following the same protocol outlined in L. Prior C information is to be removed by the calling class. =cut sub execute { my $bl = shift; my $command = shift; # Expect "dbi" my %args = @_; my @known = qw/dsn user password bl/; unless ($command eq 'dbi') { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "'" . __PACKAGE__ . "' invoked by connect type '$command'") : &DNS::BL::DNSBL_ESYNTAX(); } unless (exists $args{dsn} and length($args{dsn})) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "Missing argument 'dsn' for 'connect dbi'") : &DNS::BL::DNSBL_ESYNTAX(); } unless (exists $args{bl} and length($args{bl})) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "Missing argument 'bl' for 'connect dbi'") : &DNS::BL::DNSBL_ESYNTAX(); } for my $k (keys %args) { unless (grep { $k eq $_ } @known) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "Unknown argument '$k' to 'connect dbi'") : &DNS::BL::DNSBL_ESYNTAX(); } } my $dbh = DBI->connect($args{dsn}, (exists $args{user} ? $args{user} : ''), (exists $args{password} ? $args{password} : ''), { RaiseError => 0, PrintWarn => 0, Warn => 0, PrintError => 0, AutoCommit => 0 }); unless ($dbh) { return wantarray ? (&DNS::BL::DNSBL_ECONNECT, "Connect failed: DBI Error: $DBI::errstr") : &DNS::BL::DNSBL_ECONNECT; } # $sth is a hashref where all the prepared queries will be # stored. my $sth = {}; # Prepare the query for inserting an entry into a given dnsbl. This # is used by write. Must be called with the following positional # arguments: # # Start_CIDR, End_CIDR, Created, Text, Return, dnsbl_name $sth->{add} = $dbh->prepare(<{add}) { return wantarray ? (&DNS::BL::DNSBL_ECONNECT, "Connect failed: Error preparing 'add' " . "SQL statement: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_ECONNECT; } # Prepare the query implementing the ->read() semantics. Must be # called with the following arguments # # Start_CIDR, End_CIDR, dnsbl_name $sth->{read} = $dbh->prepare(<= ? and e.End_CIDR <= ? and b.Name = ? and b.Id = e.Bls_Id ; END_OF_SQL ); unless ($sth->{read}) { return wantarray ? (&DNS::BL::DNSBL_ECONNECT, "Connect failed: Error preparing 'read' " . "SQL statement: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_ECONNECT; } # Prepare the query implementing the ->match() semantics. Must be # called with the following arguments # # Start_CIDR, End_CIDR, dnsbl_name $sth->{match} = $dbh->prepare(<= ? and b.Name = ? and b.Id = e.Bls_Id ; END_OF_SQL ); unless ($sth->{match}) { return wantarray ? (&DNS::BL::DNSBL_ECONNECT, "Connect failed: Error preparing 'match' " . "SQL statement: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_ECONNECT; } # Prepare the query implementing the ->erase() semantics. Must be # called with the following arguments # # Start_CIDR, End_CIDR, dnsbl_name $sth->{erase} = $dbh->prepare(<= ? and entries.End_CIDR <= ? and bls.Name = ? and bls.Id = entries.Bls_Id ; END_OF_SQL ); unless ($sth->{erase}) { return wantarray ? (&DNS::BL::DNSBL_ECONNECT, "Connect failed: Error preparing 'erase' " . "SQL statement: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_ECONNECT; } # Store the private data $args{_class} = __PACKAGE__; $args{_sth} = $sth; $args{_dbh} = $dbh; $bl->set("_connect", \%args); # Add I/O methods to the $bl object so that further calls can be # processed $bl->set("_read", \&_read); $bl->set("_match", \&_match); $bl->set("_write", \&_write); $bl->set("_erase", \&_delete); $bl->set("_commit", \&_commit); return wantarray ? (&DNS::BL::DNSBL_OK, "Connected to DBI") : &DNS::BL::DNSBL_OK; }; sub _write { my $bl = shift; my $e = shift; my $data = $bl->get('_connect'); unless ($data or $data->{_class} eq __PACKAGE__) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "->write can only be called while 'connect dbi' is in effect") : &DNS::BL::DNSBL_ESYNTAX(); } if ($data->{_sth}->{add}->execute(scalar $e->addr->network->numeric, scalar $e->addr->broadcast->numeric, $e->time, $e->desc, $e->value, $data->{bl}) and (my $rows = $data->{_sth}->{add}->rows) != 0) { return wantarray ? (&DNS::BL::DNSBL_OK, "OK - $rows inserted") : &DNS::BL::DNSBL_OK; } else { return wantarray ? (&DNS::BL::DNSBL_EOTHER, "Failed: (" . ($rows || '0') . " rows inserted) " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_EOTHER; } } sub _read { my $bl = shift; my $e = shift; my $data = $bl->get('_connect'); unless ($data or $data->{_class} eq __PACKAGE__) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "->read can only be called while 'connect dbi' is in effect") : &DNS::BL::DNSBL_ESYNTAX(); } my @ret = (); if ($data->{_sth}->{read}->execute(scalar $e->addr->network->numeric, scalar $e->addr->broadcast->numeric, $data->{bl})) { while (my $r_ref = $data->{_sth}->{read}->fetchrow_arrayref) { my $ip = new NetAddr::IP (NetAddr::IP->new($r_ref->[0])->addr . '-' . NetAddr::IP->new($r_ref->[1])->addr); # warn "** Read fetched IP: $ip\n"; my $ne = new DNS::BL::Entry; $ne->addr($ip); $ne->desc($r_ref->[2]); $ne->value($r_ref->[3]); $ne->time($r_ref->[4]); push @ret, $ne; } } else { return wantarray ? (&DNS::BL::DNSBL_EOTHER, "Failed: to ->read: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_EOTHER; } return (&DNS::BL::DNSBL_OK, scalar @ret . " entries found", @ret) if @ret; return wantarray ? (&DNS::BL::DNSBL_ENOTFOUND, "No entries matched") : &DNS::BL::DNSBL_ENOTFOUND; } sub _match { my $bl = shift; my $e = shift; my $data = $bl->get('_connect'); unless ($data or $data->{_class} eq __PACKAGE__) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "->match can only be called while 'connect dbi' is in effect") : &DNS::BL::DNSBL_ESYNTAX(); } my @ret = (); if ($data->{_sth}->{match}->execute(scalar $e->addr->network->numeric, scalar $e->addr->broadcast->numeric, $data->{bl})) { while (my $r_ref = $data->{_sth}->{match}->fetchrow_arrayref) { my $ip = new NetAddr::IP (NetAddr::IP->new($r_ref->[0])->addr . '-' . NetAddr::IP->new($r_ref->[1])->addr); # warn "** Match fetched IP: $ip\n"; my $ne = new DNS::BL::Entry; $ne->addr($ip); $ne->desc($r_ref->[2]); $ne->value($r_ref->[3]); $ne->time($r_ref->[4]); push @ret, $ne; } } else { return wantarray ? (&DNS::BL::DNSBL_EOTHER, "Failed: to ->read: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_EOTHER; } return (&DNS::BL::DNSBL_OK, scalar @ret . " entries found", @ret) if @ret; return wantarray ? (&DNS::BL::DNSBL_ENOTFOUND, "No entries matched") : &DNS::BL::DNSBL_ENOTFOUND; } sub _commit { my $bl = shift; my $e = shift; my $data = $bl->get('_connect'); unless ($data or $data->{_class} eq __PACKAGE__) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "->commit can only be called while 'connect dbi' is in effect") : &DNS::BL::DNSBL_ESYNTAX(); } if ($data->{_dbh}->commit) { return wantarray ? (&DNS::BL::DNSBL_OK, "OK - Committed") : &DNS::BL::DNSBL_OK; } else { return wantarray ? (&DNS::BL::DNSBL_EOTHER, "Failed: " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_EOTHER; } } sub _delete { my $bl = shift; my $e = shift; my $data = $bl->get('_connect'); unless ($data or $data->{_class} eq __PACKAGE__) { return wantarray ? (&DNS::BL::DNSBL_ESYNTAX(), "->delete can only be called while 'connect dbi' is in effect") : &DNS::BL::DNSBL_ESYNTAX(); } if ($data->{_sth}->{erase}->execute(scalar $e->addr->network->numeric, scalar $e->addr->broadcast->numeric, $data->{bl}) and (my $rows = $data->{_sth}->{erase}->rows) != 0) { return wantarray ? (&DNS::BL::DNSBL_OK, "OK - $rows entries deleted") : &DNS::BL::DNSBL_OK; } else { return wantarray ? (&DNS::BL::DNSBL_EOTHER, "Failed: (" . ($rows || '0') . " rows deleted) " . ($DBI::errstr || "No DBI error")) : &DNS::BL::DNSBL_EOTHER; } } 1; __END__ =pod =back =head2 EXPORT None by default. =head1 HISTORY $Log: dbi.pm,v $ Revision 1.4 2004/12/24 19:19:11 lem Passes all tests. Ready for real-world... Revision 1.3 2004/12/24 12:59:25 lem Full functionality with some casual testing. Seems ready to be a RC. Revision 1.2 2004/12/21 21:19:29 lem dsn is a mandatory argument Revision 1.1 2004/12/21 21:17:38 lem Added boilerplate DBI connector. =head1 SEE ALSO Perl(1), L, L. =head1 AUTHOR Luis Muņoz, Eluismunoz@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright 2004 by Luis Muņoz This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut