######################################################################### package AnyData::Storage::SNMP; ######################################################################### ## XXX: TODO: ## scalar sets? ## multi-hosts $AnyData::Storage::VERSION = '5.0901'; 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;