File Coverage

blib/lib/Cisco/Regex.pm
Criterion Covered Total %
statement 59 70 84.2
branch 16 26 61.5
condition 10 32 31.2
subroutine 8 11 72.7
pod 4 6 66.6
total 97 145 66.9


line stmt bran cond sub pod time code
1             # Cisco::Regex.pm
2             # $Id: Regex.pm,v 0.92 2014/05/21 17:56:28 jkister Exp $
3             # Copyright (c) 2014 Jeremy Kister.
4             # Released under Perl's Artistic License.
5              
6             $Cisco::Regex::VERSION = "0.92";
7              
8             =head1 NAME
9              
10             Cisco::Regex - Utility to verify basic syntax of Cisco IOS standard
11             and extended IPv4 access-lists.
12              
13             =head1 SYNOPSIS
14              
15             use Cisco::Regex;
16              
17             my $r = Cisco::Regex->new;
18             my $std_regex = $r->regex('standard');
19             my $ext_regex = $r->regex('extended');
20            
21             my $isok = $r->standard($line);
22             my $isok = $r->extended($line);
23             my $isok = $r->auto($line);
24              
25            
26             =head1 DESCRIPTION
27              
28             C was made to lint access-lists before sending them to a
29             Cisco IOS device. Only syntax checking is performed; no logical check
30             is even attempted.
31              
32              
33             =head1 CONSTRUCTOR
34              
35             my $r = Cisco::Regex->new( debug => 0,
36             addr => $addr_regex,
37             protocol => $protocol_regex,
38             network => $network_regex,
39             port => $port_regex,
40             ports => $ports_regex,
41             )
42              
43             =over 4
44              
45             =item C
46              
47             control ancillary/informational messages being printed.
48              
49             ADVANCED OPTIONS
50              
51             =item C
52              
53             replace the built in 'addr' regex with the supplied regex.
54              
55             =item C
56              
57             replace the built in 'protocol' regex with the supplied regex.
58              
59             =item C
60              
61             replace the built in 'network' regex with the supplied regex.
62              
63             =item C
64              
65             replace the built in 'port' regex with the supplied regex.
66              
67             =item C
68              
69             replace the built in 'ports' regex with the supplied regex.
70              
71             =back
72              
73             =head1 USAGE
74              
75             =over 4
76              
77             =item C
78              
79             will return a regular expression for matching yourself.
80             Valid arguments are:
81              
82             =over 4
83              
84             =item C
85              
86             returns what an ip address should look like
87              
88             =item C
89              
90             returns what a protocol should look like
91              
92             =item C
93              
94             returns what a network statement should look like
95              
96             =item C
97              
98             returns what port properties should look like
99              
100             =item C
101              
102             for access-list 1-99 & 1300-1999 syntax matching
103              
104             =item C
105              
106             for access-list 100-199 & 2000-2699 syntax matching
107              
108             =back
109              
110             =item C
111              
112             check the provided line against the 'standard' regex.
113              
114             =item C
115              
116             check the provided line against the 'extended' regex.
117              
118             =item C
119              
120             checks if the line matches either a standard or an extended access-list
121              
122             =back
123              
124             =head1 EXAMPLES
125              
126             use strict;
127             use Cisco::Regex;
128              
129             my @std_lines = ('access-list 15 permit 10.0.0.0 0.255.255.255',
130             'access-list 15 permit 10.0.0.0 0.255.255.255 any',
131             );
132             for my $line (@std_lines){
133             my $isok = $r->standard($line);
134             if( $isok ){
135             print "OK: $line\n";
136             }else{
137             print "BAD: $line\n";
138             }
139             }
140              
141             my @ext_lines = ('access-list 115 permit udp 10.0.0.0 0.255.255.255 eq 5060 any log',
142             'access-list 115 permit 10.0.0.0 0.255.255.255 any',
143             );
144              
145             for my $line (@ext_lines){
146             my $isok = $r->extended($line);
147             if( $isok ){
148             print "OK: $line\n";
149             }else{
150             print "BAD: $line\n";
151             }
152             }
153              
154             my $acl = 'access-list 2100 permit tcp any 10.0.0.0 0.0.0.255 eq 22';
155             my $ext_regex = $r->regex('extended');
156             if( $acl =~ m/$ext_regex/ ){
157             print "acl looks okay\n";
158             }
159            
160              
161             =head1 CAVEATS aka TODO
162              
163             =over 4
164              
165             =item IPv4 only
166              
167             =item named access-lists not supported
168              
169             =item hosts/netmasks not checked to be on valid network boundaries
170              
171             =item not all syntax is understood, e.g.: options values, precedence, tos, and time-range
172              
173             =item syntax checking is good but not strict. e.g.:
174              
175             access-list 115 permit ip any any eq http (ip vs tcp)
176              
177             access-list 115 permit tcp any any eq syslog (tcp vs udp)
178              
179             access-list 115 permit 10.0.0.0 255.255.255.0 any (vs 0.0.0.255)
180              
181              
182             =back
183              
184             =head1 AUTHOR
185              
186             Jeremy Kister : http://jeremy.kister.net./
187              
188             =cut
189              
190             package Cisco::Regex;
191              
192 1     1   18837 use strict;
  1         3  
  1         1297  
193              
194 1     1 0 9 sub Version { $Cisco::Regex::VERSION }
195              
196             sub new {
197 1     1 0 881 my $class = shift;
198 1         3 my %args = @_;
199              
200 1   33     14 my $addr = $args{addr} ||
201             qr{
202             (?:
203             (?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})
204             [.]
205             (?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})
206             [.]
207             (?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})
208             [.]
209             (?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})
210             )
211             }x;
212            
213 1   33     11 my $protocol = $args{protocol} ||
214             qr{
215             (?:
216             ahp|eigrp|esp|gre|icmp|igmp|ip|ipinip|nos|ospf|pcp|pim|tcp|udp
217             |
218             (?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})
219             )
220             }x;
221            
222 1   33     198 my $network = $args{network} ||
223             qr{
224             (?:host\s+\S+)
225             |
226             (?:$addr\s+$addr)
227             |
228             any
229             }x;
230              
231 1   33     12 my $port = $args{port} ||
232             qr{
233             (?:
234             6553[0-5]
235             |
236             655[0-2]\d
237             |
238             65[0-4]\d{2}
239             |
240             6[0-4]\d{3}
241             |
242             [1-5]\d{4}
243             |
244             [1-9]\d{1,3}
245             |
246             \d
247             )
248             }x;
249              
250 1   33     9 my $pnames = $args{pnames} ||
251             qr{
252             (?:bgp|chargen|cmd|daytime|discard|domain|drip|echo|exec|
253             finger|ftp|ftp-data|gopher|hostname|ident|irc|klogin|
254             kshell|login|lpd|nntp|pim-auto-rp|pop2|pop3|smtp|sunrpc|
255             tacacs|talk|telnet|time|uucp|whois|www
256             |
257             biff|bootpc|bootps|dnsix|isakmp|mobile-ip|nameserver|
258             netbios-dgm|netbios-ns|netbios-ss|non500-isakmp|ntp|rip|
259             snmp|snmptrap|syslog|tftp|who|xdmcp
260             )
261             }x;
262              
263 1   33     645 my $ports = $args{ports} ||
264             qr{
265             (?:n?eq\s+(?:$port|$pnames))
266             |
267             (?:range\s+$port\s+$port)
268             |
269             (?:lt\s+(?:$port|$pnames))
270             |
271             (?:gt\s+(?:$port|$pnames))
272             |
273             (?:ack|dscp|established|fin|fragments|psh|rst|syn|urg)
274             }x;
275            
276 1         122 my $standard = qr{
277             ^access-list
278             \s+
279             (?\S+)
280             \s+
281             (?(?:permit|deny))
282             \s+
283             (?$network)
284             (?:\s+(?log(?:\s+\S+)?))? # optional log with optional tag
285             $
286             }x;
287            
288 1         1181 my $extended = qr{
289             ^access-list
290             \s+
291             (?\S+)
292             \s+
293             (?(?:permit|deny))
294             \s+
295             (?$protocol)
296             \s+
297             (?$network)
298             (?:\s+(?$ports))?
299             \s+
300             (?$network)
301             (?:\s+(?$ports))?
302             (?:\s+(?log(?:\s+\S+)?))? # maybe log with optional tag
303             $
304             }x;
305              
306 1         21 my $self = bless(\%args, $class);
307            
308 1         10 $self->{class} = $class;
309 1         9 $self->{regex} = { addr => $addr,
310             protocol => $protocol,
311             network => $network,
312             ports => $ports,
313             standard => $standard,
314             extended => $extended,
315             };
316            
317 1         12 return($self);
318             }
319              
320             sub regex {
321 0     0 1 0 my $self = shift;
322 0   0     0 my $obj = shift || die "regex(): must specify regex object you're looking for\n";
323              
324 0         0 return $self->{regex}{$obj};
325             }
326              
327             sub extended {
328 9     9 1 4359 my $self = shift;
329 9         21 my $raw = join('', @_);
330              
331 9         23 my $clean = $self->_clean($raw);
332              
333 9 50 33     88 return 1 if( $clean =~ /^no\s+access-list\s+(?:1[0-9][0-9]|2[0-6][0-9]{2})$/ || $clean =~ /^$/ || $clean =~ /^end$/ );
      33        
334 9 50       43 return 1 if( $clean =~ /^access-list\s+(?:1[0-9][0-9]|2[0-6][0-9]{2})\s+remark\s+/ );
335              
336 9 100       116 if( $clean =~ m/$self->{regex}{extended}/ ){
337 1     1   1230 $self->_debug( "Name: $+{name}" );
  1         628  
  1         758  
  4         43  
338 4         25 $self->_debug( "Action: $+{action}" );
339 4         23 $self->_debug( "Proto: $+{proto}" );
340 4         23 $self->_debug( "Source: $+{source}" );
341 4 100       31 $self->_debug( "Ports: $+{src_ports}" ) if defined $+{src_ports};
342 4         25 $self->_debug( "Dest: $+{destination}" );
343 4 100       31 $self->_debug( "Ports: $+{dst_ports}" ) if defined $+{dst_ports};
344 4 100       31 $self->_debug( "Log: $+{log}" ) if defined $+{log};
345 4         12 $self->_debug( "" );
346              
347 4         11 return 1;
348             }
349            
350 5         13 return 0;
351             }
352              
353             sub standard {
354 6     6 1 2150 my $self = shift;
355 6         16 my $raw = join('', @_);
356              
357 6         14 my $clean = $self->_clean($raw);
358              
359 6 50 33     51 return 1 if( $clean =~ /^no\s+access-list\s+(?:[1-9]?[0-9]|1[3-9][0-9]{2})$/ || $clean =~ /^$/ || $clean =~ /^end$/ );
      33        
360 6 50       18 return 1 if( $clean =~ /^access-list\s+(?:[1-9]?[0-9]|1[3-9][0-9]{2})\s+remark\s+/ );
361              
362 6 100       60 if( $clean =~ m/$self->{regex}{standard}/ ){
363 2         19 $self->_debug( "Name: $+{name}" );
364 2         12 $self->_debug( "Action: $+{action}" );
365 2         13 $self->_debug( "Source: $+{source}" );
366 2 50       14 $self->_debug( "Log: $+{log}" ) if defined $+{log};
367 2         5 $self->_debug( "" );
368              
369 2         4 return 1;
370             }
371              
372            
373 4         11 return 0;
374             }
375              
376             sub auto {
377 0     0 1 0 my $self = shift;
378 0         0 my $line = join('', @_);
379              
380 0 0       0 return 1 if( $self->standard($line) );
381 0 0       0 return 1 if( $self->extended($line) );
382              
383 0         0 return 0;
384             }
385              
386             sub _clean {
387 15     15   19 my $self = shift;
388 15         24 my $line = join('', @_);
389              
390 15         28 $line =~ s/!.*//g;
391 15         62 $line =~ s/^\s*//g;
392 15         151 $line =~ s/\s*$//g;
393              
394 15         30 return $line;
395             }
396              
397             sub _warn {
398 0     0   0 my $self = shift;
399 0         0 my $msg = join('', @_);
400              
401 0         0 warn "$self->{class}: $msg\n";
402             }
403              
404             sub _debug {
405 38     38   48 my $self = shift;
406              
407 38 50       86 $self->_warn(@_) if $self->{debug};
408             }
409              
410              
411             1;