File Coverage

blib/lib/SNMP/Trapinfo.pm
Criterion Covered Total %
statement 155 156 99.3
branch 65 66 98.4
condition 11 12 91.6
subroutine 18 18 100.0
pod 6 11 54.5
total 255 263 96.9


line stmt bran cond sub pod time code
1             package SNMP::Trapinfo;
2              
3 1     1   71326 use 5.008004;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         19  
5 1     1   4 use warnings;
  1         2  
  1         37  
6 1     1   6 use Carp;
  1         2  
  1         65  
7 1     1   618 use Safe; # Safe module, creates a compartment for eval's and tests for disabled commands
  1         38499  
  1         1650  
8              
9             our $VERSION = '1.06';
10              
11             sub AUTOLOAD {
12 212     212   1643 my $self = shift;
13 212         335 my $attr = our $AUTOLOAD;
14 212         759 $attr =~ s/.*:://;
15 212 100       817 return if $attr =~ /^DESTROY$/;
16 197 100       369 if (@_) {
17 45         106 return $self->{$attr} = shift;
18             } else {
19 152         514 return $self->{$attr};
20             }
21             }
22              
23             sub new {
24 16     16 1 3297 my ($class, $data, $opts) = @_;
25 16 100       289 croak "Must specify data source (either GLOB or scalar ref)" unless $data;
26 15         52 my $self = {
27             data => {},
28             P => [],
29             V => [],
30             };
31 15         33 $self = bless $self, $class;
32              
33 15         44 return $self->read($data, $opts);
34             }
35              
36             sub trapname {
37 8     8 1 22 my $self = shift;
38 8         38 my $trapname = $self->data->{"SNMPv2-MIB::snmpTrapOID"};
39 8   100     51 return $trapname || undef;
40             }
41              
42             sub packet {
43 3     3 1 7 my $self = shift;
44 3 50 66     18 if ($_[0] && ref \$_[0] eq "SCALAR") {
45 0         0 return $self->{packet} = shift;
46             }
47 3         7 my $opts;
48 3 100       8 $opts = shift if (ref $_[0] eq "HASH");
49 3         8 $_ = $self->{packet};
50 3 100       7 if ($opts->{hide_passwords}) {
51 2         6 $_ = $self->_hide_passwords( $_ );
52             }
53 3         18 return $_;
54             }
55              
56             sub _hide_passwords {
57 4     4   10 my ($self, $string) = @_;
58 4         30 $string =~ s/\nSNMP-COMMUNITY-MIB::snmpTrapCommunity.0 "(.*?)"/\nSNMP-COMMUNITY-MIB::snmpTrapCommunity.0 "*****"/;
59 4         15 return $string;
60             }
61              
62             # Initialise the Safe compartment once
63             # Operators can be listed as follows
64             # perl -MOpcode=opdump -e 'opdump'
65             #
66             # http://search.cpan.org/~nwclark/perl-5.8.8/ext/Opcode/Opcode.pm
67             #
68             # We want to allow // m// s/// && || ! !~ != >= > == < <= =~ lt gt le ge ne eq not and or + - % * x .
69             #
70             my $cmp = new Safe;
71             $cmp->permit_only( qw( :base_core :base_mem :base_loop print sprintf prtf padsv padav padhv padany localtime rv2gv ) );
72              
73             sub expand {
74 73     73 1 5258 my $self = shift;
75 73         113 my $string = shift;
76 73 100       176 return "" if ! defined $string;
77 72         101 my $key;
78 72         494 while ( ($key) = ($string =~ /\$\{([\w\-\.\*:]+)}/) ) {
79 79         137 my $newval;
80 79         284 my ($action, $line) = $key =~ /^([PV])(\d+)?$/;
81 79 100 100     340 if ($action && $line) {
    100          
    100          
    100          
82 37         106 $newval = $self->$action($line);
83 37 100       87 if (!defined $newval) {
84 9         18 $newval = "(null)";
85             }
86             } elsif ($key eq "DUMP") {
87 1         3 my %h = %{$self->data};
  1         5  
88 1         3 delete $h{"SNMP-COMMUNITY-MIB::snmpTrapCommunity"};
89 1         10 $newval = join(" ", map {"$_=".$self->data->{$_}} (sort keys %h) );
  6         22  
90             } elsif ($key eq "TRAPNAME") {
91 3         11 $newval = $self->trapname;
92             } elsif ($key eq "HOSTIP") {
93 1         20 $newval = $self->hostip;
94             } else {
95 37 100       75 if ($key =~ /\*/) {
96 7         19 $newval = $self->match_key($key);
97 7 100       20 if (!defined $newval) {
98 1         7 $newval = "(null)";
99             }
100             } else {
101 30         150 $newval = $self->data->{$key};
102 30 100       71 if (!defined $newval) {
103 4         7 $newval = "(null)";
104             }
105             }
106             }
107              
108             # Must use same match as while loop
109             # Otherwise possible infinite loop
110             # though not sure why (see tests for examples)
111             #$string =~ s/\${$key}/$newval/;
112 79         463 $string =~ s/\$\{([\w\-\.\*:]+)\}/$newval/;
113             }
114              
115             # eval calculation performed within Safe for security
116 72         289 my $eval_re = qr/eval\s*{(.*?)}/;
117 72         335 while ( ($key) = ($string =~ /$eval_re/) ) {
118 7   100     26 my $eval_result = $cmp->reval($key) || '';
119 7         3738 $string =~ s/$eval_re/$eval_result/;
120             }
121 72         406 return $string;
122             }
123              
124             sub eval {
125 22     22 1 1191 my ($self, $string) = @_;
126 22         53 my $code = $self->expand($string);
127 22         118 $self->last_eval_string($code);
128              
129 22         123 my $rc = $cmp->reval("$code", 1); # ($code to run, 1 for 'use strict;')
130 22 100       13117 if ($@) {
131 4         23 return undef;
132             } else {
133 18 100       120 return $rc ? 1 : 0;
134             }
135             }
136              
137             sub match_key {
138 12     12 0 26 my ($self, $key) = @_;
139 12         39 my @parts = split('\.', $key);
140 12         21 POSSIBLE: foreach my $possible (keys %{$self->data}) {
  12         62  
141 109         205 my @possible = split('\.', $possible);
142 109 100       217 next unless @possible == @parts;
143 57         116 for (my $i=0; $i < @parts; $i++) {
144 74 100       128 next if ($parts[$i] eq "*");
145 65 100       119 if ($parts[$i] ne $possible[$i]) {
146 50         89 next POSSIBLE;
147             }
148             }
149 7         26 return $self->data->{$possible};
150             }
151 5         29 return undef;
152             }
153              
154             sub cleanup_string {
155 140     140 0 180 my $self = shift;
156 140         183 my $string = shift;
157             # This is an SNMP OID name...
158 140 100       364 if ($string =~ /^[A-Za-z].*\:\:[A-Za-z].*$/) {
159             # Drop single trailing digits
160 75 100       185 if (! ($string =~ /\d\.\d+$/)) {
161 69         175 $string =~ s/\.\d+$//;
162             }
163             }
164             # Remove trailing spaces
165 140         222 $string =~ s/ +$//;
166 140         242 return $string;
167             }
168              
169             sub read {
170 15     15 0 35 my ($self, $data, $opts) = @_;
171 15 100       62 if (ref \$data eq "GLOB") {
    100          
172 3         14 local $/="#---next trap---#\n";
173 3   100     49 $self->{packet} = <$data> || return undef;
174 2         22 chomp($self->{packet});
175             } elsif (ref \$data eq "REF") {
176 10         26 $self->{packet} = $$data;
177             } else {
178 2         246 croak "Bad ref";
179             }
180 12 100       35 if ($opts->{hide_passwords}) {
181 2         6 $self->{packet} = $self->_hide_passwords( $self->{packet} );
182             }
183 12         431 $self->{packet} =~ s/\n*$//;
184 12         72 my @packet = split("\n", $self->{packet});
185             {
186             # Go through the array and look for lines that might be joinable.
187             # Assume multi-lines are surrounded by quotes, so only one quote
188             # character means join the next line onto the current one
189              
190             # Work from a copy of the packet in case we decide to make no changes
191 12         23 my @copy_packet = @packet;
  12         34  
192 12         16 my @new_packet;
193 12         20 my $within_quotes=0;
194             # use defined here to allow blank lines through
195 12         29 while( defined( my $line = shift @copy_packet)) {
196 104 100       166 if($within_quotes) {
197 5         12 $new_packet[-1] .= "\n".$line;
198 5         9 my $quotes = $new_packet[-1] =~ tr/"/"/;
199 5 100       12 $within_quotes = 0 if( $quotes % 2 == 0 );
200 5         11 next;
201             }
202              
203 99         144 push(@new_packet, $line);
204              
205             {
206 99         112 my $quotes = $new_packet[-1] =~ tr/"/"/;
  99         143  
207 99 100       255 $within_quotes = 1 if( $quotes % 2 == 1 );
208             }
209             }
210              
211             # Only rewrite the packet if it looks like we have correctly
212             # joined up all the lines
213 12 100       47 @packet=@new_packet if($within_quotes == 0 );
214             }
215 12         27 chomp($_ = shift @packet);
216 12         76 $self->hostname($_);
217 12         28 $self->{P}->[0] = $_;
218              
219 12 100       30 return undef if (!@packet); # No IP address given. This is a malformed packet
220              
221 11         19 chomp($_ = shift @packet);
222 11         21 $self->{P}->[1] = $_;
223             # Extra stuff around the IP packet in Net-SNMP 5.2.1
224 11         35 s/^.*?\[//;
225 11         33 s/\].*$//;
226 11         51 $self->hostip($_);
227              
228 11         20 my $i = 1; # Start at 1 because want to increment array at beginning because of next
229 11         25 foreach $_ (@packet) {
230 77         100 $i++;
231             # Ignore spaces in middle
232 77         304 my ($key, $value) = /^([^ ]+) +([^ ].*)$/s;
233             # If syntax is wrong, ignore this line
234 77 100       173 next unless defined $key;
235 74         121 $key = $self->cleanup_string($key);
236 74 100       141 if ($key ne "SNMPv2-MIB::snmpTrapOID") {
237 66         117 $value = $self->cleanup_string($value);
238             }
239 74         233 $self->data->{$key} = $value;
240 74         242 $key =~ s/^[^:]+:://;
241 74         167 $self->{P}->[$i] = $key;
242 74         148 $self->{V}->[$i] = $value;
243             }
244 11         69 return $self;
245             }
246              
247             sub fully_translated {
248 2     2 1 6 my $self = shift;
249 2 100       5 if ($self->trapname =~ /\.\d+$/) {
250 1         6 return 0;
251             } else {
252 1         5 return 1;
253             }
254             }
255              
256             sub P {
257 16     16 0 36 my ($self, $line) = @_;
258 16         47 $_ = $self->{P}->[--$line];
259 16         42 return $_;
260             }
261              
262             sub V {
263 29     29 0 59 my ($self, $line) = @_;
264 29         73 $_ = $self->{V}->[--$line];
265 29         70 return $_;
266             }
267              
268             1;
269             __END__