File Coverage

blib/lib/IPTables/Parse.pm
Criterion Covered Total %
statement 18 390 4.6
branch 0 248 0.0
condition 0 225 0.0
subroutine 6 19 31.5
pod 5 12 41.6
total 29 894 3.2


line stmt bran cond sub pod time code
1             #
2             ##################################################################
3             #
4             # File: IPTables::Parse.pm
5             #
6             # Purpose: Perl interface to parse iptables and ip6tables rulesets.
7             #
8             # Author: Michael Rash (mbr@cipherdyne.org)
9             #
10             # Version: 1.4
11             #
12             ##################################################################
13             #
14              
15             package IPTables::Parse;
16              
17 1     1   34756 use 5.006;
  1         3  
  1         32  
18 1     1   478 use POSIX ":sys_wait_h";
  1         4729  
  1         5  
19 1     1   889 use Carp;
  1         6  
  1         46  
20 1     1   4 use strict;
  1         1  
  1         27  
21 1     1   3 use warnings;
  1         1  
  1         24  
22 1     1   4 use vars qw($VERSION);
  1         6  
  1         4444  
23              
24             $VERSION = '1.4';
25              
26             sub new() {
27 0     0 0   my $class = shift;
28 0           my %args = @_;
29              
30 0           my $ipt_bin = '/sbin/iptables';
31 0           my $ipt6_bin = '/sbin/ip6tables';
32 0           my $fwc_bin = '/usr/bin/firewall-cmd';
33              
34 0   0       my $self = {
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
35             _iptables => $args{'iptables'} || $args{'ip6tables'} || '',
36             _firewall_cmd => $args{'firewall-cmd'} || '',
37             _fwd_args => $args{'fwd_args'} || '--direct --passthrough ipv4',
38             _ipv6 => $args{'use_ipv6'} || 0,
39             _iptout => $args{'iptout'} || '/tmp/ipt.out',
40             _ipterr => $args{'ipterr'} || '/tmp/ipt.err',
41             _ipt_alarm => $args{'ipt_alarm'} || 30,
42             _debug => $args{'debug'} || 0,
43             _verbose => $args{'verbose'} || 0,
44             _ipt_rules_file => $args{'ipt_rules_file'} || '',
45             _ipt_exec_style => $args{'ipt_exec_style'} || 'waitpid',
46             _ipt_exec_sleep => $args{'ipt_exec_sleep'} || 0,
47             _sigchld_handler => $args{'sigchld_handler'} || \&REAPER,
48             _skip_ipt_exec_check => $args{'skip_ipt_exec_check'} || 0
49             };
50              
51 0 0         if ($self->{'_skip_ipt_exec_check'}) {
52 0 0 0       unless ($self->{'_firewall_cmd'} or $self->{'_iptables'}) {
53             ### default
54 0           $self->{'_iptables'} = $ipt_bin;
55             }
56             } else {
57 0 0         if ($self->{'_firewall_cmd'}) {
    0          
58 0 0         croak "[*] $self->{'_firewall_cmd'} incorrect path.\n"
59             unless -e $self->{'_firewall_cmd'};
60 0 0         croak "[*] $self->{'_firewall_cmd'} not executable.\n"
61             unless -x $self->{'_firewall_cmd'};
62             } elsif ($self->{'_iptables'}) {
63 0 0         croak "[*] $self->{'_iptables'} incorrect path.\n"
64             unless -e $self->{'_iptables'};
65 0 0         croak "[*] $self->{'_iptables'} not executable.\n"
66             unless -x $self->{'_iptables'};
67             } else {
68             ### check for firewall-cmd first since systems with it
69             ### will have iptables installed as well (but firewall-cmd
70             ### should be used instead if it exists)
71 0 0 0       if (-e $fwc_bin and -x $fwc_bin) {
    0 0        
    0 0        
72 0           $self->{'_firewall_cmd'} = $fwc_bin;
73             } elsif (-e $ipt_bin and -x $ipt_bin) {
74 0           $self->{'_iptables'} = $ipt_bin;
75             } elsif (-e $ipt6_bin and -x $ipt6_bin) {
76 0           $self->{'_iptables'} = $ipt6_bin;
77             } else {
78 0           croak "[*] Could not find/execute iptables, " .
79             "specify path via _iptables\n";
80             }
81             }
82             }
83              
84 0 0 0       if ($self->{'_ipv6'} and $self->{'_iptables'} eq $ipt_bin) {
85 0 0 0       if (-e $ipt6_bin and -x $ipt6_bin) {
86 0           $self->{'_iptables'} = $ipt6_bin;
87             } else {
88 0           croak "[*] Could not find/execute ip6tables, " .
89             "specify path via _iptables\n";
90             }
91             }
92              
93             ### set the firewall binary name
94 0           $self->{'_ipt_bin_name'} = 'iptables';
95 0 0         if ($self->{'_firewall_cmd'}) {
96 0 0         $self->{'_ipt_bin_name'} = $1 if $self->{'_firewall_cmd'} =~ m|.*/(\S+)|;
97             } else {
98 0 0         $self->{'_ipt_bin_name'} = $1 if $self->{'_iptables'} =~ m|.*/(\S+)|;
99             }
100              
101             ### handle ipv6
102 0 0         if ($self->{'_ipv6'}) {
103 0 0         if ($self->{'_firewall_cmd'}) {
104 0 0         if ($self->{'_fwd_args'} =~ /ipv4/i) {
105 0           $self->{'_fwd_args'} = '--direct --passthrough ipv6';
106             }
107             } else {
108 0 0         if ($self->{'_ipt_bin_name'} eq 'iptables') {
109 0 0         unless ($self->{'_skip_ipt_exec_check'}) {
110 0           croak "[*] use_ipv6 is true, " .
111             "but $self->{'_iptables'} not ip6tables.\n";
112             }
113             }
114             }
115             }
116              
117 0 0         $self->{'_ipv6'} = 1 if $self->{'_ipt_bin_name'} eq 'ip6tables';
118 0 0         if ($self->{'_firewall_cmd'}) {
119 0 0         $self->{'_ipv6'} = 1 if $self->{'_fwd_args'} =~ /ipv6/;
120             }
121              
122             ### set the main command string to allow for iptables execution
123             ### via firewall-cmd if necessary
124 0           $self->{'_cmd'} = $self->{'_iptables'};
125 0 0         if ($self->{'_firewall_cmd'}) {
126 0           $self->{'_cmd'} = "$self->{'_firewall_cmd'} $self->{'_fwd_args'}";
127             }
128              
129 0           $self->{'parse_keys'} = &parse_keys();
130              
131 0           bless $self, $class;
132             }
133              
134             sub parse_keys() {
135 0     0 0   my $self = shift;
136              
137             ### only used for IPv4 + NAT
138 0           my $ipv4_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
139              
140 0           my %keys = (
141             'regular' => {
142             'packets' => {
143             'regex' => '',
144             'ipt_match' => ''
145             },
146             'bytes' => {
147             'regex' => '',
148             'ipt_match' => ''
149             },
150             'target' => {
151             'regex' => '',
152             'ipt_match' => ''
153             },
154             'protocol' => {
155             'regex' => '',
156             'ipt_match' => '-p'
157             },
158             'proto' => {
159             'regex' => '',
160             'ipt_match' => '-p'
161             },
162             'intf_in' => {
163             'regex' => '',
164             'ipt_match' => '-i'
165             },
166             'intf_out' => {
167             'regex' => '',
168             'ipt_match' => '-o'
169             },
170             'src' => {
171             'regex' => '',
172             'ipt_match' => '-s'
173             },
174             'dst' => {
175             'regex' => '',
176             'ipt_match' => '-d'
177             }
178             },
179             'extended' => {
180             's_port' => {
181             'regex' => qr/\bspts?:(\S+)/,
182             'ipt_match' => '--sport'
183             },
184             'sport' => {
185             'regex' => qr/\bspts?:(\S+)/,
186             'ipt_match' => '--sport'
187             },
188             'd_port' => {
189             'regex' => qr/\bdpts?:(\S+)/,
190             'ipt_match' => '--dport'
191             },
192             'dport' => {
193             'regex' => qr/\bdpts?:(\S+)/,
194             'ipt_match' => '--dport'
195             },
196             'to_ip' => {
197             'regex' => qr/\bto:($ipv4_re):\d+/,
198             'ipt_match' => ''
199             },
200             'to_port' => {
201             'regex' => qr/\bto:$ipv4_re:(\d+)/,
202             'ipt_match' => ''
203             },
204             'mac_source' => {
205             'regex' => qr/\bMAC\s+(\S+)/,
206             'ipt_match' => '-m mac --mac-source'
207             },
208             'state' => {
209             'regex' => qr/\bstate\s+(\S+)/,
210             'ipt_match' => '-m state --state'
211             },
212             'ctstate' => {
213             'regex' => qr/\bctstate\s+(\S+)/,
214             'ipt_match' => '-m conntrack --ctstate'
215             },
216             'comment' => {
217             'regex' => qr|\/\*\s(.*?)\s\*\/|,
218             'ipt_match' => '-m comment --comment',
219             'use_quotes' => 1
220             },
221             'string' => {
222             'regex' => qr|STRING\s+match\s+\"(.*?)\"|,
223             'ipt_match' => '-m string --algo bm --string',
224             'use_quotes' => 1
225             },
226             'length' => {
227             'regex' => qr|\blength\s(\S+)|,
228             'ipt_match' => '-m length --length',
229             },
230             },
231             'raw' => ''
232             );
233              
234 0           return \%keys;
235             }
236              
237             sub list_table_chains() {
238 0     0 1   my $self = shift;
239 0   0       my $table = shift || croak '[*] Specify a table, e.g. "nat"';
240 0   0       my $file = shift || '';
241              
242 0           my @ipt_lines = ();
243 0           my @chains = ();
244              
245 0 0 0       if ($self->{'_ipt_rules_file'} and not $file) {
246 0           $file = $self->{'_ipt_rules_file'};
247             }
248              
249 0 0         if ($file) {
250             ### read the iptables rules out of $file instead of executing
251             ### the iptables command.
252 0 0         open F, "< $file" or croak "[*] Could not open file $file: $!";
253 0           @ipt_lines = ;
254 0           close F;
255             } else {
256 0           my ($rv, $out_ar, $err_ar) = $self->exec_iptables(
257             "$self->{'_cmd'} -t $table -v -n -L");
258 0           @ipt_lines = @$out_ar;
259             }
260              
261 0           for (@ipt_lines) {
262 0 0         if (/^\s*Chain\s+(\w+)/) {
263 0           push @chains, $1;
264             }
265             }
266 0           return \@chains;
267             }
268              
269             sub chain_policy() {
270 0     0 1   my $self = shift;
271 0   0       my $table = shift || croak '[*] Specify a table, e.g. "nat"';
272 0   0       my $chain = shift || croak '[*] Specify a chain, e.g. "OUTPUT"';
273 0   0       my $file = shift || '';
274              
275 0           my @ipt_lines = ();
276              
277 0 0 0       if ($self->{'_ipt_rules_file'} and not $file) {
278 0           $file = $self->{'_ipt_rules_file'};
279             }
280              
281 0 0         if ($file) {
282             ### read the iptables rules out of $file instead of executing
283             ### the iptables command.
284 0 0         open F, "< $file" or croak "[*] Could not open file $file: $!";
285 0           @ipt_lines = ;
286 0           close F;
287             } else {
288 0           my ($rv, $out_ar, $err_ar) = $self->exec_iptables(
289             "$self->{'_cmd'} -t $table -v -n -L $chain");
290 0           @ipt_lines = @$out_ar;
291             }
292              
293 0           my $policy = '';
294              
295 0           for my $line (@ipt_lines) {
296             ### Chain INPUT (policy ACCEPT 16 packets, 800 bytes)
297 0 0         if ($line =~ /^\s*Chain\s+$chain\s+\(policy\s+(\w+)/) {
298 0           $policy = $1;
299 0           last;
300             }
301             }
302              
303 0           return $policy;
304             }
305              
306             sub chain_action_rules() {
307 0     0 0   return &chain_rules();
308             }
309              
310             sub chain_rules() {
311 0     0 1   my $self = shift;
312 0   0       my $table = shift || croak '[*] Specify a table, e.g. "nat"';
313 0   0       my $chain = shift || croak '[*] Specify a chain, e.g. "OUTPUT"';
314 0   0       my $file = shift || '';
315              
316 0           my $found_chain = 0;
317 0           my @ipt_lines = ();
318              
319             ### only used for IPv4 + NAT
320 0           my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
321              
322             ### array of hash refs
323 0           my @chain = ();
324 0           my @global_accept_state = ();
325              
326 0 0 0       if ($self->{'_ipt_rules_file'} and not $file) {
327 0           $file = $self->{'_ipt_rules_file'};
328             }
329              
330 0 0         if ($file) {
331             ### read the iptables rules out of $file instead of executing
332             ### the iptables command.
333 0 0         open F, "< $file" or croak "[*] Could not open file $file: $!";
334 0           @ipt_lines = ;
335 0           close F;
336             } else {
337 0           my ($rv, $out_ar, $err_ar) = $self->exec_iptables(
338             "$self->{'_cmd'} -t $table -v -n -L $chain");
339 0           @ipt_lines = @$out_ar;
340             }
341              
342             ### determine the output style (e.g. "-nL -v" or just plain "-nL"; if the
343             ### policy data came from a file then -v might not have been used)
344 0           my $ipt_verbose = 0;
345 0           for my $line (@ipt_lines) {
346 0 0         if ($line =~ /^\s*pkts\s+bytes\s+target/) {
347 0           $ipt_verbose = 1;
348 0           last;
349             }
350             }
351              
352 0           LINE: for my $line (@ipt_lines) {
353 0           chomp $line;
354              
355 0 0 0       last LINE if ($found_chain and $line =~ /^\s*Chain\s+/);
356              
357 0 0         if ($line =~ /^\s*Chain\s+$chain\s+\(/i) {
358 0           $found_chain = 1;
359 0           next LINE;
360             }
361 0 0         if ($ipt_verbose) {
362 0 0         next LINE if $line =~ /^\s*pkts\s+bytes\s+target\s/i;
363             } else {
364 0 0         next LINE if $line =~ /^\s*target\s+prot/i;
365             }
366 0 0         next LINE unless $found_chain;
367 0 0         next LINE unless $line;
368              
369             ### initialize hash
370 0           my %rule = (
371             'extended' => '',
372             'raw' => $line
373             );
374 0           for my $key (keys %{$self->{'parse_keys'}->{'regular'}}) {
  0            
375 0           $rule{$key} = '';
376             }
377 0           for my $key (keys %{$self->{'parse_keys'}->{'extended'}}) {
  0            
378 0           $rule{$key} = '';
379             }
380              
381 0 0         if ($ipt_verbose) {
382              
383             ### iptables:
384             ### 0 0 ACCEPT tcp -- eth1 * 192.168.10.3 0.0.0.0/0 tcp dpt:80
385             ### 0 0 ACCEPT tcp -- eth1 * 192.168.10.15 0.0.0.0/0 tcp dpt:22
386             ### 33 2348 ACCEPT tcp -- eth1 * 192.168.10.2 0.0.0.0/0 tcp dpt:22
387             ### 0 0 ACCEPT tcp -- eth1 * 192.168.10.2 0.0.0.0/0 tcp dpt:80
388             ### 0 0 DNAT tcp -- * * 123.123.123.123 0.0.0.0/0 tcp dpt:55000 to:192.168.12.12:80
389              
390             ### ip6tables:
391             ### 0 0 ACCEPT tcp * * ::/0 fe80::aa:0:1/128 tcp dpt:12345
392             ### 0 0 LOG all * * ::/0 ::/0 LOG flags 0 level 4
393              
394 0           my $match_re = qr/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+\-\-\s+
395             (\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)/x;
396              
397 0 0 0       if ($self->{'_ipt_bin_name'} eq 'ip6tables'
      0        
398             or ($self->{'_ipt_bin_name'} eq 'firewall-cmd'
399             and $self->{'_fwd_args'} =~ /\sipv6/)) {
400 0           $match_re = qr/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+
401             (\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)/x;
402             }
403              
404 0 0         if ($line =~ $match_re) {
405 0           $rule{'packets'} = $1;
406 0           $rule{'bytes'} = $2;
407 0           $rule{'target'} = $3;
408 0           $rule{'protocol'} = $rule{'proto'} = lc($4);
409 0           $rule{'intf_in'} = $5;
410 0           $rule{'intf_out'} = $6;
411 0           $rule{'src'} = $7;
412 0           $rule{'dst'} = $8;
413 0   0       $rule{'extended'} = $9 || '';
414              
415 0           &parse_rule_extended(\%rule, $self->{'parse_keys'}->{'extended'});
416             }
417             } else {
418              
419             ### iptables:
420             ### ACCEPT tcp -- 164.109.8.0/24 0.0.0.0/0 tcp dpt:22 flags:0x16/0x02
421             ### ACCEPT tcp -- 216.109.125.67 0.0.0.0/0 tcp dpts:7000:7500
422             ### ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpts:7000:7500
423             ### ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:!7000
424             ### ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
425             ### ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:35000 dpt:5000
426             ### ACCEPT tcp -- 10.1.1.1 0.0.0.0/0
427              
428             ### LOG all -- 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 4 prefix `DROP '
429             ### LOG all -- 127.0.0.2 0.0.0.0/0 LOG flags 0 level 4
430             ### DNAT tcp -- 123.123.123.123 0.0.0.0/0 tcp dpt:55000 to:192.168.12.12:80
431              
432             ### ip6tables:
433             ### ACCEPT tcp ::/0 fe80::aa:0:1/128 tcp dpt:12345
434             ### LOG all ::/0 ::/0 LOG flags 0 level 4
435              
436 0           my $match_re = qr/^\s*(\S+)\s+(\S+)\s+\-\-\s+(\S+)\s+(\S+)\s*(.*)/;
437              
438 0 0 0       if ($self->{'_ipt_bin_name'} eq 'ip6tables'
      0        
439             or ($self->{'_ipt_bin_name'} eq 'firewall-cmd'
440             and $self->{'_fwd_args'} =~ /\sipv6/)) {
441 0           $match_re = qr/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)/;
442             }
443              
444 0 0         if ($line =~ $match_re) {
445 0           $rule{'target'} = $1;
446 0           my $proto = $2;
447 0 0         $proto = 'all' if $proto eq '0';
448 0           $rule{'protocol'} = $rule{'proto'} = $proto;
449 0           $rule{'src'} = $3;
450 0           $rule{'dst'} = $4;
451 0   0       $rule{'extended'} = $5 || '';
452              
453 0           &parse_rule_extended(\%rule, $self->{'parse_keys'}->{'extended'});
454             }
455             }
456 0           push @chain, \%rule;
457             }
458 0           return \@chain;
459             }
460              
461             sub parse_rule_extended() {
462 0     0 0   my ($rule_hr, $ext_keys_hr) = @_;
463              
464 0           for my $key (keys %$ext_keys_hr) {
465 0 0         if ($rule_hr->{'extended'}
466             =~ /$ext_keys_hr->{$key}->{'regex'}/) {
467 0           $rule_hr->{$key} = $1;
468             }
469             }
470              
471 0 0 0       if ($rule_hr->{'protocol'} eq '0') {
    0          
472 0           $rule_hr->{'s_port'} = $rule_hr->{'sport'} = 0;
473 0           $rule_hr->{'d_port'} = $rule_hr->{'dport'} = 0;
474             } elsif ($rule_hr->{'protocol'} eq 'tcp'
475             or $rule_hr->{'protocol'} eq 'udp') {
476 0 0         $rule_hr->{'s_port'} = $rule_hr->{'sport'} = 0
477             if $rule_hr->{'s_port'} eq '';
478 0 0         $rule_hr->{'d_port'} = $rule_hr->{'dport'} = 0
479             if $rule_hr->{'d_port'} eq '';
480             }
481              
482 0           return;
483             }
484              
485             sub default_drop() {
486 0     0 1   my $self = shift;
487 0   0       my $table = shift || croak "[*] Specify a table, e.g. \"nat\"";
488 0   0       my $chain = shift || croak "[*] Specify a chain, e.g. \"OUTPUT\"";
489 0   0       my $file = shift || '';
490              
491 0           my @ipt_lines = ();
492              
493 0 0 0       if ($self->{'_ipt_rules_file'} and not $file) {
494 0           $file = $self->{'_ipt_rules_file'};
495             }
496              
497 0 0         if ($file) {
498             ### read the iptables rules out of $file instead of executing
499             ### the iptables command.
500 0 0         open F, "< $file" or croak "[*] Could not open file $file: $!";
501 0           @ipt_lines = ;
502 0           close F;
503             } else {
504             ### FIXME -v for interfaces?
505 0           my ($rv, $out_ar, $err_ar) = $self->exec_iptables(
506             "$self->{'_cmd'} -t $table -n -L $chain");
507 0           @ipt_lines = @$out_ar;
508             }
509              
510 0 0         return "[-] Could not get $self->{'_ipt_bin_name'} output!", 0
511             unless @ipt_lines;
512              
513 0           my %protocols = ();
514 0           my $found_chain = 0;
515 0           my $found_default_drop = 0;
516 0           my $rule_ctr = 1;
517 0           my $prefix;
518 0           my $policy = 'ACCEPT';
519 0           my $any_ip_re = qr/(?:0\.){3}0\x2f0|\x3a{2}\x2f0/;
520              
521 0           LINE: for my $line (@ipt_lines) {
522 0           chomp $line;
523              
524 0 0 0       last if ($found_chain and $line =~ /^\s*Chain\s+/);
525              
526             ### Chain INPUT (policy DROP)
527             ### Chain FORWARD (policy ACCEPT)
528 0 0         if ($line =~ /^\s*Chain\s+$chain\s+\(policy\s+(\w+)\)/) {
529 0           $policy = $1;
530 0           $found_chain = 1;
531             }
532 0 0         next LINE if $line =~ /^\s*target\s/i;
533 0 0         next LINE unless $found_chain;
534              
535             ### include ULOG target as well
536 0           my $log_re = qr/^\s*U?LOG\s+(\w+)\s+\-\-\s+.*
537             $any_ip_re\s+$any_ip_re\s+(.*)/x;
538 0           my $drop_re = qr/^DROP\s+(\w+)\s+\-\-\s+.*
539             $any_ip_re\s+$any_ip_re\s*$/x;
540              
541 0 0 0       if ($self->{'_ipt_bin_name'} eq 'ip6tables'
      0        
542             or ($self->{'_ipt_bin_name'} eq 'firewall-cmd'
543             and $self->{'_fwd_args'} =~ /ipv6/)) {
544 0           $log_re = qr/^\s*U?LOG\s+(\w+)\s+
545             $any_ip_re\s+$any_ip_re\s+(.*)/x;
546 0           $drop_re = qr/^DROP\s+(\w+)\s+
547             $any_ip_re\s+$any_ip_re\s*$/x;
548             }
549              
550             ### might as well pick up any default logging rules as well
551 0 0 0       if ($line =~ $log_re) {
    0          
552 0           my $proto = $1;
553 0           my $p_tmp = $2;
554 0           my $prefix = 'NONE';
555              
556             ### some recent iptables versions return "0" instead of "all"
557             ### for the protocol number
558 0 0         $proto = 'all' if $proto eq '0';
559             ### LOG flags 0 level 4 prefix `DROP '
560 0 0 0       if ($p_tmp && $p_tmp =~ m|LOG.*\s+prefix\s+
561             \`\s*(.+?)\s*\'|x) {
562 0           $prefix = $1;
563             }
564             ### $proto may equal "all" here
565 0           $protocols{$proto}{'LOG'}{'prefix'} = $prefix;
566 0           $protocols{$proto}{'LOG'}{'rulenum'} = $rule_ctr;
567             } elsif ($policy eq 'ACCEPT' and $line =~ $drop_re) {
568 0           my $proto = $1;
569 0 0         $proto = 'all' if $proto eq '0';
570             ### DROP all -- 0.0.0.0/0 0.0.0.0/0
571 0           $protocols{$1}{'DROP'} = $rule_ctr;
572 0           $found_default_drop = 1;
573             }
574 0           $rule_ctr++;
575             }
576              
577             ### if the policy in the chain is DROP, then we don't
578             ### necessarily need to find a default DROP rule.
579 0 0         if ($policy eq 'DROP') {
580 0           $protocols{'all'}{'DROP'} = 0;
581 0           $found_default_drop = 1;
582             }
583              
584 0 0 0       return "[-] There are no default drop rules in the " .
585             "$self->{'_ipt_bin_name'} policy!", 0
586             unless %protocols and $found_default_drop;
587              
588 0           return \%protocols, 1;
589             }
590              
591             sub default_log() {
592 0     0 1   my $self = shift;
593 0   0       my $table = shift || croak "[*] Specify a table, e.g. \"nat\"";
594 0   0       my $chain = shift || croak "[*] Specify a chain, e.g. \"OUTPUT\"";
595 0   0       my $file = shift || '';
596              
597 0           my $any_ip_re = qr/(?:0\.){3}0\x2f0|\x3a{2}\x2f0/;
598 0           my @ipt_lines = ();
599 0           my %log_chains = ();
600 0           my %log_rules = ();
601              
602 0 0 0       if ($self->{'_ipt_rules_file'} and not $file) {
603 0           $file = $self->{'_ipt_rules_file'};
604             }
605              
606             ### note that we are not restricting the view to the current chain
607             ### with the iptables -nL output; we are going to parse the given
608             ### chain and all chains to which packets are jumped from the given
609             ### chain.
610 0 0         if ($file) {
611             ### read the iptables rules out of $file instead of executing
612             ### the iptables command.
613 0 0         open F, "< $file" or croak "[*] Could not open file $file: $!";
614 0           @ipt_lines = ;
615 0           close F;
616             } else {
617 0           my ($rv, $out_ar, $err_ar) = $self->exec_iptables(
618             "$self->{'_cmd'} -t $table -n -L $chain");
619 0           @ipt_lines = @$out_ar;
620             }
621              
622             ### determine the output style (e.g. "-nL -v" or just plain "-nL"; if the
623             ### policy data came from a file then -v might not have been used)
624 0           my $ipt_verbose = 0;
625 0           for my $line (@ipt_lines) {
626 0 0         if ($line =~ /^\s*pkts\s+bytes\s+target/) {
627 0           $ipt_verbose = 1;
628 0           last;
629             }
630             }
631              
632 0 0         return "[-] Could not get $self->{'_ipt_bin_name'} output!", 0
633             unless @ipt_lines;
634              
635             ### first get all logging rules and associated chains
636 0           my $log_chain;
637              
638 0           for my $line (@ipt_lines) {
639 0           chomp $line;
640              
641             ### Chain INPUT (policy DROP)
642             ### Chain fwsnort_INPUT_eth1 (1 references)
643 0 0 0       if ($line =~ /^\s*Chain\s+(.*?)\s+\(/ and
644             $line !~ /0\s+references/) {
645 0           $log_chain = $1;
646             }
647 0 0         $log_chain = '' unless $line =~ /\S/;
648 0 0         next unless $log_chain;
649              
650 0           my $proto = '';
651 0           my $found = 0;
652 0 0         if ($ipt_verbose) {
653 0 0 0       if ($self->{'_ipt_bin_name'} eq 'ip6tables'
      0        
654             or ($self->{'_ipt_bin_name'} eq 'firewall-cmd'
655             and $self->{'_fwd_args'} =~ /\sipv6/)) {
656 0 0         if ($line =~ m|^\s*\d+\s+\d+\s*U?LOG\s+(\w+)\s+
657             \S+\s+\S+\s+$any_ip_re
658             \s+$any_ip_re\s+.*U?LOG|x) {
659 0           $proto = $1;
660 0           $found = 1;
661             }
662             } else {
663 0 0         if ($line =~ m|^\s*\d+\s+\d+\s*U?LOG\s+(\w+)\s+\-\-\s+
664             \S+\s+\S+\s+$any_ip_re
665             \s+$any_ip_re\s+.*U?LOG|x) {
666 0           $proto = $1;
667 0           $found = 1;
668             }
669             }
670             } else {
671 0 0 0       if ($self->{'_ipt_bin_name'} eq 'ip6tables'
      0        
672             or ($self->{'_ipt_bin_name'} eq 'firewall-cmd'
673             and $self->{'_fwd_args'} =~ /\sipv6/)) {
674 0 0         if ($line =~ m|^\s*U?LOG\s+(\w+)\s+$any_ip_re
675             \s+$any_ip_re\s+.*U?LOG|x) {
676 0           $proto = $1;
677 0           $found = 1;
678             }
679             } else {
680 0 0         if ($line =~ m|^\s*U?LOG\s+(\w+)\s+\-\-\s+$any_ip_re
681             \s+$any_ip_re\s+.*U?LOG|x) {
682 0           $proto = $1;
683 0           $found = 1;
684             }
685             }
686             }
687              
688 0 0         if ($found) {
689 0 0         $proto = 'all' if $proto eq '0';
690             ### the above regex allows the limit target to be used
691 0           $log_chains{$log_chain}{$proto} = ''; ### protocol
692 0 0         $log_rules{$proto} = '' if $log_chain eq $chain;
693             }
694             }
695              
696 0 0         return "[-] There are no default logging rules " .
697             "in the $self->{'_ipt_bin_name'} policy!", 0 unless %log_chains;
698              
699 0           my %sub_chains = ();
700              
701             ### get all sub-chains of the main chain we passed into default_log()
702 0           &sub_chains($chain, \%sub_chains, \@ipt_lines);
703              
704             ### see which (if any) logging rules can be mapped back to the
705             ### main chain we passed in.
706 0           for my $log_chain (keys %log_chains) {
707 0 0         if (defined $sub_chains{$log_chain}) {
708             ### the logging rule is in the main chain (e.g. INPUT)
709 0           for my $proto (keys %{$log_chains{$log_chain}}) {
  0            
710 0           $log_rules{$proto} = '';
711             }
712             }
713             }
714              
715 0           return \%log_rules, 1;
716             }
717              
718             sub sub_chains() {
719 0     0 0   my ($start_chain, $chains_hr, $ipt_lines_ar) = @_;
720 0           my $found = 0;
721 0           for my $line (@$ipt_lines_ar) {
722 0           chomp $line;
723             ### Chain INPUT (policy DROP)
724             ### Chain fwsnort_INPUT_eth1 (1 references)
725 0 0 0       if ($line =~ /^\s*Chain\s+$start_chain\s+\(/ and
726             $line !~ /0\s+references/) {
727 0           $found = 1;
728 0           next;
729             }
730 0 0         next unless $found;
731 0 0 0       if ($found and $line =~ /^\s*Chain\s/) {
732 0           last;
733             }
734 0 0         if ($line =~ m|^\s*(\S+)\s+\S+\s+|) {
735 0           my $new_chain = $1;
736 0 0 0       if ($new_chain ne 'LOG'
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
737             and $new_chain ne 'DROP'
738             and $new_chain ne 'REJECT'
739             and $new_chain ne 'ACCEPT'
740             and $new_chain ne 'RETURN'
741             and $new_chain ne 'QUEUE'
742             and $new_chain ne 'SNAT'
743             and $new_chain ne 'DNAT'
744             and $new_chain ne 'MASQUERADE'
745             and $new_chain ne 'pkts'
746             and $new_chain ne 'Chain'
747             and $new_chain ne 'target') {
748 0           $chains_hr->{$new_chain} = '';
749 0           &sub_chains($new_chain, $chains_hr, $ipt_lines_ar);
750             }
751             }
752             }
753 0           return;
754             }
755              
756             sub exec_iptables() {
757 0     0 0   my $self = shift;
758 0   0       my $cmd = shift || croak "[*] Must specify an " .
759             "$self->{'_ipt_bin_name'} command to run.";
760 0           my $iptout = $self->{'_iptout'};
761 0           my $ipterr = $self->{'_ipterr'};
762 0           my $debug = $self->{'_debug'};
763 0           my $ipt_alarm = $self->{'_ipt_alarm'};
764 0           my $verbose = $self->{'_verbose'};
765 0           my $ipt_exec_style = $self->{'_ipt_exec_style'};
766 0           my $ipt_exec_sleep = $self->{'_ipt_exec_sleep'};
767 0           my $sigchld_handler = $self->{'_sigchld_handler'};
768              
769 0 0 0       croak "[*] $cmd does not look like an $self->{'_ipt_bin_name'} command."
      0        
      0        
      0        
      0        
770             unless $cmd =~ m|^\s*iptables| or $cmd =~ m|^\S+/iptables|
771             or $cmd =~ m|^\s*ip6tables| or $cmd =~ m|^\S+/ip6tables|
772             or $cmd =~ m|^\s*firewall-cmd| or $cmd =~ m|^\S+/firewall-cmd|;
773              
774 0           my $rv = 1;
775 0           my @stdout = ();
776 0           my @stderr = ();
777              
778 0           my $fh = *STDERR;
779 0 0         $fh = *STDOUT if $verbose;
780              
781 0 0 0       if ($debug or $verbose) {
782 0           print $fh localtime() . " [+] IPTables::Parse::",
783             "exec_iptables(${ipt_exec_style}()) $cmd\n";
784 0 0         if ($ipt_exec_sleep > 0) {
785 0           print $fh localtime() . " [+] IPTables::Parse::",
786             "exec_iptables() sleep seconds: $ipt_exec_sleep\n";
787             }
788             }
789              
790 0 0         if ($ipt_exec_sleep > 0) {
791 0 0 0       if ($debug or $verbose) {
792 0           print $fh localtime() . " [+] IPTables::Parse: ",
793             "sleeping for $ipt_exec_sleep seconds before ",
794             "executing $self->{'_ipt_bin_name'} command.\n";
795             }
796 0           sleep $ipt_exec_sleep;
797             }
798              
799 0 0         if ($ipt_exec_style eq 'system') {
    0          
800 0           system qq{$cmd > $iptout 2> $ipterr};
801             } elsif ($ipt_exec_style eq 'popen') {
802 0 0         open CMD, "$cmd 2> $ipterr |" or croak "[*] Could not execute $cmd: $!";
803 0           @stdout = ;
804 0           close CMD;
805 0 0         open F, "> $iptout" or croak "[*] Could not open $iptout: $!";
806 0           print F for @stdout;
807 0           close F;
808             } else {
809 0           my $ipt_pid;
810              
811 0 0 0       if ($debug or $verbose) {
812 0           print $fh localtime() . " [+] IPTables::Parse: " .
813             "Setting SIGCHLD handler to: " . $sigchld_handler . "\n";
814             }
815              
816 0           local $SIG{'CHLD'} = $sigchld_handler;
817 0 0         if ($ipt_pid = fork()) {
818 0           eval {
819             ### iptables should never take longer than 30 seconds to execute,
820             ### unless there is some absolutely enormous policy or the kernel
821             ### is exceedingly busy
822 0     0     local $SIG{'ALRM'} = sub {die "[*] $self->{'_ipt_bin_name'} " .
823 0           "command timeout.\n"};
824 0           alarm $ipt_alarm;
825 0           waitpid($ipt_pid, 0);
826 0           alarm 0;
827             };
828 0 0         if ($@) {
829 0 0         kill 9, $ipt_pid unless kill 15, $ipt_pid;
830             }
831             } else {
832 0 0         croak "[*] Could not fork $self->{'_ipt_bin_name'}: $!"
833             unless defined $ipt_pid;
834              
835             ### exec the iptables command and preserve stdout and stderr
836 0           exec qq{$cmd > $iptout 2> $ipterr};
837             }
838             }
839              
840 0 0         if (-e $iptout) {
841 0 0         open F, "< $iptout" or croak "[*] Could not open $iptout";
842 0           @stdout = ;
843 0           close F;
844             }
845 0 0         if (-e $ipterr) {
846 0 0         open F, "< $ipterr" or croak "[*] Could not open $ipterr";
847 0           @stderr = ;
848 0           close F;
849              
850 0 0         $rv = 0 if @stderr;
851             }
852              
853 0 0         if (@stdout) {
854 0 0         if ($stdout[$#stdout] =~ /^success/) {
855 0           pop @stdout;
856             }
857 0 0         if ($self->{'_ipt_bin_name'} eq 'firewall-cmd') {
858 0           for (@stdout) {
859 0 0         if (/COMMAND_FAILED/) {
860 0           $rv = 0;
861 0           last;
862             }
863             }
864             }
865             }
866              
867 0 0 0       if ($debug or $verbose) {
868 0           print $fh localtime() . " $self->{'_ipt_bin_name'} " .
869             "command stdout:\n";
870 0           for my $line (@stdout) {
871 0 0         if ($line =~ /\n$/) {
872 0           print $fh $line;
873             } else {
874 0           print $fh $line, "\n";
875             }
876             }
877 0           print $fh localtime() . " $self->{'_ipt_bin_name'} " .
878             "command stderr:\n";
879 0           for my $line (@stderr) {
880 0 0         if ($line =~ /\n$/) {
881 0           print $fh $line;
882             } else {
883 0           print $fh $line, "\n";
884             }
885             }
886             }
887              
888 0 0 0       if ($debug or $verbose) {
889 0           print $fh localtime() . " Return value: $rv\n";
890             }
891              
892 0           return $rv, \@stdout, \@stderr;
893             }
894              
895             sub REAPER {
896 0     0 0   my $stiff;
897 0           while(($stiff = waitpid(-1,WNOHANG))>0){
898             # do something with $stiff if you want
899             }
900 0           local $SIG{'CHLD'} = \&REAPER;
901 0           return;
902             }
903              
904             1;
905             __END__