summaryrefslogtreecommitdiff
path: root/perl/AnyData_SNMP/Storage.pm
blob: 196946f8bf3728ee74c015aa1e799d66e6d10920 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
#########################################################################
package AnyData::Storage::SNMP;
#########################################################################

## XXX: TODO:
##   scalar sets?
##   multi-hosts

$AnyData::Storage::VERSION = '5.07021';
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;