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
|
%#<% Data::Format::HTML->new->format($index{by_path}) %>
% my $json = "JSON"->new->canonical;
<% $json->encode($result) %>
<%init>
#<%once> #enable me in production
use SNMP;
SNMP::initMib();
my $mib = \%SNMP::MIB;
# make an index of the leaf nodes
my %index = (
by_objectID => {}, # {.1.3.6.1.2.1.1.1}
by_fullname => {}, # {iso.org.dod.internet.mgmt.mib-2.system.sysDescr}
by_path => {}, # {iso}{org}{dod}{internet}{mgmt}{mib-2}{system}{sysDescr}
module => {}, #{SNMPv2-MIB}{by_path}{iso}{org}...
#{SNMPv2-MIB}{by_fullname}{iso.org...}
);
my %name_of_oid = (); # '.1.3.6.1' => 'iso.org.dod.internet'
# build up path names
my $fullname;
$fullname = sub {
my $oid = shift;
return $name_of_oid{$oid} if exists $name_of_oid{$oid};
my $object = $mib->{$oid};
my $myname = '.' . $object->{label};
# cut off the last element and recurse
$oid =~ /^(\.[\d\.]+)?(\.\d+)$/;
if ( length($1) ) {
$myname = $fullname->($1) . $myname;
}
return $name_of_oid{$oid} = $myname
};
my @oids = keys(%$mib); # dotted numeric OIDs
foreach my $oid (@oids) {
my $object = {};
%$object = %{ $mib->{$oid} }; # untie it
# and remove references
delete $object->{parent};
delete $object->{children};
delete $object->{nextNode};
$index{by_objectID}{$oid} = $object;
my $myname = $fullname->($oid);
$object->{fullname} = $myname;
$index{by_fullname}{$myname} = $object;
my $moduleID = $object->{moduleID};
$index{module}{$moduleID} ||= { by_fullname => {}, by_path => {} };
$index{module}{$moduleID}{by_fullname}{$myname} = $object;
}
my @names = sort {$a cmp $b} keys %{ $index{by_fullname} };
foreach my $myname (@names) {
my $obj = $index{by_fullname}{$myname};
my $moduleID = $obj->{moduleID};
my @parts = split('\.', $myname);
shift @parts; # always starts with an empty string
for ($index{by_path}, $index{module}{$moduleID}{by_path}) {
my $subindex = $_;
for my $this_part (@parts) {
$subindex = $subindex->{$this_part} ||= {};
}
# $subindex now = $index{by_path}{foo}{bar}{baz}.
# set {''} = the object with that name.
# and set object $index{by_path}{foo}{bar}{baz}{''} =
# the object named .foo.bar.baz
$subindex->{''} = $obj;
}
}
#</%once>
#<%init>
# no ACL for this
my $sub = $cgi->param('sub');
my $result = {};
if ( $sub eq 'search' ) {
warn "search: ".$cgi->param('arg')."\n";
my ($module, $string) = split(':', $cgi->param('arg'), 2);
my $idx; # the branch of the index to use for this search
if ( $module eq 'ANY' ) {
$idx = \%index;
} elsif (exists($index{module}{$module}) ) {
$idx = $index{module}{$module};
} else {
warn "unknown MIB moduleID: $module\n";
$idx = {}; # will return nothing, because you've somehow sent a bad moduleID
}
if ( exists($index{by_fullname}{$string}) ) {
warn "exact match\n";
# don't make this module-selective--if the path matches an existing
# object, return that object
%$result = %{ $index{by_fullname}{$string} }; # put the object info in $result
#warn Dumper $result;
}
my @choices; # menu options to return
if ( $string =~ /^[\.\d]+$/ ) {
# then this is a numeric path
# ignore the module filter, and return everything starting with $string
if ( $string =~ /^\./ ) {
@choices = grep /^\Q$string\E/, keys %{$index{by_objectID}};
} else {
# or everything containing it
@choices = grep /\Q$string\E/, keys %{$index{by_objectID}};
}
@choices = map { $index{by_objectID}{$_}->{fullname} } @choices;
} elsif ( $string eq '' or $string =~ /^\./ ) {
# then this is an absolute path
my @parts = split('\.', $string);
shift @parts;
my $subindex = $idx->{by_path};
my $path = '';
@choices = keys %$subindex;
# walk all the specified path parts
foreach my $this_part (@parts) {
# stop before walking off the map
last if !exists($subindex->{$this_part});
$subindex = $subindex->{$this_part};
$path .= '.' . $this_part;
@choices = grep {$_} keys %$subindex;
}
# skip uninteresting nodes: those that aren't accessible nodes (have no
# data type), and have only one path forward
while ( scalar(@choices) == 1
and (!exists $subindex->{''} or $subindex->{''}->{type} eq '') ) {
$subindex = $subindex->{ $choices[0] };
$path .= '.' . $choices[0];
@choices = grep {$_} keys %$subindex;
}
# if we are on an existing node, and the entered path didn't exactly
# match another node, return the current node as the result
if (!keys %$result and exists($subindex->{''})) {
%$result = %{ $subindex->{''} };
}
# prepend the path up to this point
foreach (@choices) {
$_ = $path.'.'.$_;
# also label accessible nodes for the UI
if ( exists($subindex->{$_}{''}) and $subindex->{$_}{''}{'type'} ) {
$_ .= '-';
}
}
# also include one level above the originally requested path,
# for tree-like navigation
if ( $string =~ /^(.+)\.[^\.]+/ ) {
unshift @choices, $1;
}
} else {
# then this is a full-text search
warn "/$string/\n";
@choices = grep /\Q$string\E/i, keys(%{ $idx->{by_fullname} });
}
@choices = sort @choices;
$result->{choices} = \@choices;
} elsif ( $sub eq 'get_module_list' ) {
$result = { modules => [ sort keys(%{ $index{module} }) ] };
}
</%init>
|