blob: 67c791f6d45cd6662e302ec9ea123c53785cd50d [file] [log] [blame]
#########################################################################
package AnyData::Storage::SNMP;
#########################################################################
## XXX: TODO:
## scalar sets?
## multi-hosts
$AnyData::Storage::VERSION = '5.0703';
use strict;
use warnings;
use vars qw(@basecols);
@AnyData::Storage::SNMP::basecols = qw(hostname iid);
$AnyData::Storage::SNMP::iidptr = 1; # must match array column of above
@AnyData::Storage::SNMP::basetypes = qw(OCTETSTR OBJECTID);
$AnyData::Storage::SNMP::debug = 0;
$AnyData::Storage::SNMP::debugre = undef;
use Data::Dumper;
use AnyData::Storage::File;
use SNMP;
SNMP::init_snmp("AnyData::SNMP");
sub new {
# DEBUG("calling AnyData::Storage::SNMP new\n");
# DEBUG("new storage: ",Dumper(\@_),"\n");
my $class = shift;
my $self = shift || {};
$self->{open_mode} = 'c';
return bless $self, $class;
}
sub open_table {
DEBUG("calling AnyData::Storage::SNMP open_table\n");
my ($self, $parser, $table, $mode, $tname) = @_;
$self->{'process_table'} = $tname;
DEBUG("open_table: ",Dumper(\@_),"\n");
}
sub get_col_names {
DEBUG("calling AnyData::Storage::SNMP get_col_names\n");
my ($self, $parser, $tname) = @_;
DEBUG("get_col_names\n",Dumper(\@_),"\n");
$tname = $self->{'process_table'} if (!$tname);
# get cached previous results
return $self->{col_names}{$tname} if (defined($self->{col_names}{$tname}));
# table name
$tname = $self->{'process_table'} if (!$tname);
# mib node setup
my $mib = $SNMP::MIB{$tname} || return warn "no such table $tname";
my $entry = $mib->{'children'}[0];
# base columns and types
my @cols = @AnyData::Storage::SNMP::basecols;
my @types = @AnyData::Storage::SNMP::basetypes;
my %donecol;
my $count = $#cols;
foreach my $index (@{$entry->{'indexes'}}) {
push @cols, $index;
push @types, $SNMP::MIB{$index}{type};
$donecol{$index} = 1;
$count++;
if ($SNMP::MIB{$index}{parent}{label} eq $entry->{label}) {
# this index is a member of this table
$self->{columnmap}[$count] = $index;
$self->{coloffset} += 1;
}
}
# search children list
foreach my $child ( sort { $a->{'subID'} <=> $b->{'subID'} } @{$entry->{'children'}}) {
push @{$self->{real_cols}}, $child->{'label'};
next if ($donecol{$child->{label}});
push @cols, $child->{'label'};
push @types, $child->{'type'};
$count++;
$self->{columnmap}[$count] = $child->{'label'};
}
# save for later.
$parser->{col_names} = \@cols;
$self->{col_types} = \@types;
$self->{col_names} = \@cols;
map { $self->{col_uc_map}{uc($_)} = $_ } @cols;
return \@cols;
}
sub set_col_nums {
DEBUG("calling AnyData::Storage::SNMP set_col_nums\n");
DEBUG("set_col_nums\n",Dumper(\@_),"\n");
my ($self, $tname) = @_;
return $self->{col_nums} if (defined($self->{col_nums}));
my $mib = $SNMP::MIB{$tname};
my $entry = $mib->{'children'}[0];
my (%cols, %mibnodes);
my $cnt = -1;
foreach my $i (@{$self->{col_names}}) {
$cols{$i} = ++$cnt;
}
$self->{col_nums} = \%cols;
return \%cols;
}
# not needed?
sub get_file_handle {
DEBUG("calling AnyData::Storage::SNMP get_file_handle\n");
DEBUG("get_file_handle\n",Dumper(\@_),"\n");
return shift;
}
# not needed?
sub get_file_name {
DEBUG("calling AnyData::Storage::SNMP get_file_name\n");
DEBUG("get_file_name\n",Dumper(\@_),"\n");
my $self = shift;
return $self->{process_table} || $self->{table_name};
}
sub truncate {
DEBUG("calling AnyData::Storage::SNMP truncate\n");
my ($self) = @_;
DEBUG("trunacte, ", Dumper(@_));
# We must know how to delete rows or else this is all pointless.
# return $self->{col_nums}{$tname} if (defined($self->{col_nums}{$tname}));
my $tablemib = $SNMP::MIB{$self->{process_table}};
my $entrymib = $tablemib->{'children'}[0];
my ($delcolumn, $delcolumnname, $delcolumnvalue);
foreach my $child (@{$entrymib->{'children'}}) {
if ($child->{'textualConvention'} eq 'RowStatus') {
$delcolumn = $child->{subID};
$delcolumnname = $child->{label};
$delcolumnvalue = 6; # destroy
last;
}
}
if (!$delcolumn) {
return warn "Can't (or don't know how to) delete from table $self->{process_table}. Failing.\n";
}
# we should have a session, or else something is really wierd but...
$self->{'sess'} = $self->make_session() if (!$self->{'sess'});
# for each key left in our cache, delete it
foreach my $key (keys(%{$self->{'existtest'}})) {
# xxx: fullyqualified oid better
my $vblist = new SNMP::VarList([$delcolumnname, $key,
$delcolumnvalue]);
DEBUG("truncate $key: \n", Dumper($vblist));
$self->{'sess'}->set($vblist) || warn $self->{'sess'}->{ErrorStr};
}
return;
}
sub make_session {
DEBUG("calling AnyData::Storage::SNMP make_session\n");
my $self = shift;
my @args = @_;
my @sessions;
foreach my $key (qw(SecName Version SecLevel AuthPass Community RemotePort Timeout Retries RetryNoSuch SecEngineId ContextEngineId Context AuthProto PrivProto PrivPass)) {
push @args, $key, $self->{$key} if ($self->{$key});
}
foreach my $host (split(/,\s*/,$self->{DestHost})) {
push @sessions, new SNMP::Session(@args, 'DestHost' => $host);
}
return \@sessions;
}
sub file2str {
DEBUG("calling AnyData::Storage::SNMP file2str\n");
my ($self, $parser, $uccols) = @_;
my ($cols, @retcols);
DEBUG("file2str\n",Dumper(\@_),"\n");
restart:
if (!$self->{lastnode}) {
# my @vbstuff = @{$parser->{'col_names'}};
# splice (@vbstuff,0,1+$#AnyData::Storage::SNMP::basecols);
# map { $_ = [ $_ ] } @vbstuff;
# $self->{lastnode} = new SNMP::VarList(@vbstuff);
# splice (@$cols,0,1+$#AnyData::Storage::SNMP::basecols);
map { push @$cols,$self->{col_uc_map}{$_} } @$uccols;
if ($#$cols == -1) {
$cols = $self->{'col_names'};
# remove base columns
splice (@$cols,0,1+$#AnyData::Storage::SNMP::basecols);
# remove not accessible columns
foreach my $col (@$cols) {
my $mib = $SNMP::MIB{$col};
push @retcols, $col if ($mib->{'access'} =~ /Read|Create/);
}
} else {
@retcols = @$cols;
# remove base columns
foreach my $c (@AnyData::Storage::SNMP::basecols) {
@retcols = grep(!/^$c$/, @retcols);
}
# remove non-accessible columns
@retcols = grep {$SNMP::MIB{$_}{'access'} =~ /Read|Create/} @retcols;
}
map { $_ = [ $_ ] } @retcols;
$self->{lastnode} = new SNMP::VarList(@retcols);
}
if (!$self->{'sess'}) {
$self->{'sess'} = $self->make_session();
if ($#{$self->{'sess'}} == 0 && $self->{'silence_single_host'}) {
$self->{'hostname'} = '';
} else {
$self->{'hostname'} = $self->{'sess'}[0]{'DestHost'};
}
}
# perform SNMP operation
my $lastnode = $self->{'lastnode'}[0][0];
my $result;
$result = $self->{'sess'}[0]->getnext($self->{lastnode});
if (!defined($result)) {
warn " getnext of $self->{lastnode}[0][0] returned undef\n";
}
DEBUG(" result: ",Dumper($self->{lastnode}),"\n");
# XXX: check for holes!
# need proper oid compare here for all nodes
if ($self->{'lastnode'}[0][0] ne $lastnode) {
if ($#{$self->{'sess'}} > 0) {
shift @{$self->{'sess'}};
delete($self->{'lastnode'});
@$cols = ();
$self->{'hostname'} = $self->{'sess'}[0]{'DestHost'} if($self->{'hostname'});
goto restart;
}
return undef;
}
# add in basecols information:
my @ret = ($self->{'hostname'}, $self->{'lastnode'}[0][1]);
DEBUG("Dump row results: ",Dumper($self->{'lastnode'}),"\n");
# build result array from result varbind contents
map { $ret[$self->{'col_nums'}{$_->[0]}] = map_data($_); } @{$self->{'lastnode'}};
# store instance ID for later use if deletion is needed later.
$self->{'existtest'}{$self->{'lastnode'}[0][1]} = 1;
DEBUG("Dump row results2: ",Dumper(\@ret),"\n");
return \@ret;
}
sub map_data {
if ($_->[3] eq "OBJECTID") {
$_->[2] = pretty_print_oid(@_);
}
return $_->[2];
}
sub pretty_print_oid {
use NetSNMP::default_store qw(:all);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DONT_BREAKDOWN_OIDS,0);
my $new = SNMP::translateObj($_->[2], 0);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DONT_BREAKDOWN_OIDS,1);
my $new2 = SNMP::translateObj($_->[2], 0);
if ($new) {
$_->[2] = $new2 . "$new";
} else {
$_->[2] = $_->[0] . $_->[1];
}
return $_->[2];
}
sub push_row {
DEBUG("calling AnyData::Storage::SNMP push_row\n");
DEBUG("push_row: ",Dumper(\@_),"\n");
DEBUG("push_row\n");
my ($self, $values, $parser, $cols) = @_;
my @callers = caller(3);
my $mode = $callers[3];
if ($mode =~ /DELETE/) {
DEBUG("not deleting $values->[$AnyData::Storage::SNMP::iidptr]\n");
delete $self->{'existtest'}{$values->[$AnyData::Storage::SNMP::iidptr]};
return;
}
my @origvars;
if ($#$cols == -1) {
# no column info passed in. Update everything (mode probably INSERTS).
# @origvars = @{$self->{'col_names'}}};
# splice (@origvars,0,1+$#AnyData::Storage::SNMP::basecols);
map { push @origvars, $_ if $SNMP::MIB{$_}{'access'} =~ /Write|Create/; } @{$self->{'real_cols'}} ;
DEBUG("set cols: ", Dumper(\@origvars));
} else {
# only update the columns in question. (mode probably UPDATE)
map { push @origvars, $self->{col_uc_map}{$_} } @$cols;
}
my @vars;
foreach my $var (@origvars) {
my $access = $SNMP::MIB{$var}{'access'};
# not in this table, probably (hopefully) an index from another:
next if ($SNMP::MIB{$var}{'parent'}{'parent'}{'label'} ne
$self->{process_table});
DEBUG("$var -> $access\n");
if ($access =~ /(Write|Create)/) {
push @vars, $var;
} elsif ($mode eq 'insert') {
DEBUG("XXX: error if not index\n");
} elsif ($mode eq 'update') {
DEBUG("update to non-writable column attempted (SNMP error coming)\n");
}
}
# generate index OID component if we don't have it.
if ($values->[$AnyData::Storage::SNMP::iidptr] eq '') {
$values->[$AnyData::Storage::SNMP::iidptr] =
$self->make_iid($self->{process_table}, $values);
}
# add in values to varbind columns passed in from incoming parameters
my @newvars;
foreach my $v (@vars) {
my $num = $self->{'col_nums'}{$v};
DEBUG("types: $v -> $num -> ", $self->{'col_types'}[$num],
" -> val=", $values->[$num], "\n");
next if (!defined($values->[$num]));
# build varbind: column-oid, instance-id, value type, value
push @newvars, [$v, $values->[1], $values->[$num],
$self->{'col_types'}[$num]];
};
# create the varbindlist
# print STDERR Dumper(\@newvars);
my $vblist = new SNMP::VarList(@newvars);
# print STDERR Dumper($vblist);
DEBUG("set: ", Dumper($vblist));
$self->{'sess'} = $self->make_session() if (!$self->{'sess'});
if (!$self->{'sess'}[0]) {
warn "couldn't create SNMP session";
} elsif (!$self->{'sess'}[0]->set($vblist)) {
my $err = "$self->{process_table}: " . $self->{'sess'}[0]->{ErrorStr};
if ($self->{'sess'}[0]->{ErrorInd}) {
$err = $err . " (at varbind #"
. $self->{'sess'}[0]->{ErrorInd} . " = " ;
my $dump = Data::Dumper->new([$vblist->[$self->{'sess'}[0]->{ErrorInd} -1]]);
$err .= $dump->Indent(0)->Terse(1)->Dump;
}
warn $err;
}
}
sub seek {
DEBUG("calling AnyData::Storage::SNMP seek\n");
my ($self, $parser) = @_;
DEBUG("seek\n",Dumper(\@_),"\n");
}
sub make_iid {
DEBUG("calling AnyData::Storage::SNMP make_iid\n");
my ($self, $tname, $vals) = @_;
# Get indexes
my $mib = $SNMP::MIB{$tname};
my $entry = $mib->{'children'}[0];
my $indexes = $entry->{'indexes'};
my $iid;
# XXX: implied
# print STDERR "INDEXES: ", Dumper($vals),"\n";
foreach my $index (@$indexes) {
warn "A null index value was found, which I doubt is correct." if (!defined($vals->[$self->{col_nums}{$index}]));
my $val = $vals->[$self->{col_nums}{$index}];
my $type = $SNMP::MIB{$index}->{'type'};
DEBUG("index type: $index -> $type -> $val -> " . length($val) . "\n");
if ($type eq "OCTETSTR") {
$iid .= "." . length($val) . "." . join(".", unpack("c*", $val));
} elsif ($type eq "OBJID") {
$iid .= "." . (scalar grep(/\./,$val) + 1) . "." . $val;
} else {
# should be only an INTEGER?
$iid .= "." . $val;
}
}
DEBUG("made iid: $iid\n");
return $iid;
}
sub DEBUG {
my @info = caller(1);
if ($AnyData::Storage::SNMP::debug
|| ($AnyData::Storage::SNMP::debugre &&
$_[0] =~ /$AnyData::Storage::SNMP::debugre/)) {
DEBUGIT(\@info, @_);
}
}
sub DEBUGIT {
my $info;
if (ref($_[0]) eq 'ARRAY') {
$info = shift @_;
} else {
my @y;
my $c=0;
print STDERR "debug chain: ";
for(@y = caller($c); $#y > -1; $c++, @y = caller($c)) {
print STDERR " $c: $y[3]\n";
}
my @x = caller(1);
$info = \@x;
}
print STDERR "$info->[3]: ";
print STDERR @_;
}
1;