Author: arkurth
Date: Fri Apr  7 22:15:38 2017
New Revision: 1790637

URL: http://svn.apache.org/viewvc?rev=1790637&view=rev
Log:
VCL-972
Initial commit of firewalld.pm module.  It's mostly done but needs some 
finishing touches.  It relies heavily on iptables.pm.

Added:
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/firewalld.pm
Modified:
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm

Added: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/firewalld.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/firewalld.pm?rev=1790637&view=auto
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/firewalld.pm 
(added)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/firewalld.pm Fri 
Apr  7 22:15:38 2017
@@ -0,0 +1,625 @@
+#!/usr/bin/perl -w
+###############################################################################
+# $Id:  $
+###############################################################################
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###############################################################################
+
+=head1 NAME
+
+VCL::Module::OS::Linux::firewall::firewalld.pm
+
+=head1 DESCRIPTION
+
+ This module provides VCL support for firewalld-based firewalls.
+
+=cut
+
+##############################################################################
+package VCL::Module::OS::Linux::firewall::firewalld;
+
+# Specify the lib path using FindBin
+use FindBin;
+use lib "$FindBin::Bin/../../../../..";
+
+# Configure inheritance
+use base qw(VCL::Module::OS::Linux::firewall::iptables);
+
+# Specify the version of this module
+our $VERSION = '2.4';
+
+our @ISA;
+
+# Specify the version of Perl to use
+use 5.008000;
+
+use strict;
+use warnings;
+use diagnostics;
+
+use VCL::utils;
+
+##############################################################################
+
+=head1 OBJECT METHODS
+
+=cut
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 initialize
+
+ Parameters  : none
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub initialize {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my $arguments = shift || {};
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       notify($ERRORS{'DEBUG'}, 0, "initializing " . ref($self) . " object to 
control $computer_name");
+       
+       if (!$self->os->service_exists('firewalld')) {
+               notify($ERRORS{'DEBUG'}, 0, ref($self) . " object not 
initialized to control $computer_name, firewalld service does not exist");
+               return 0;
+       }
+       
+       if (!$self->os->is_service_enabled('firewalld')) {
+               notify($ERRORS{'DEBUG'}, 0, ref($self) . " object not 
initialized to control $computer_name, firewalld service is not enabled");
+               return 0;
+       }
+       
+       if (!$self->os->command_exists('firewall-cmd')) {
+               notify($ERRORS{'DEBUG'}, 0, ref($self) . " object not 
initialized to control $computer_name, firewall-cmd command does not exist");
+               return 0;
+       }
+       
+       notify($ERRORS{'DEBUG'}, 0, ref($self) . " object initialized to 
control $computer_name");
+       return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 process_post_load
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Performs the initial iptables firewall configuration after an
+               image is loaded:
+               * Performs all of the tasks done by
+                 iptables.pm::process_post_load except the pre-VCL 2.5 legacy
+                 cleanup tasks
+               * Removes the ssh protocol from the public zone
+
+=cut
+
+sub process_post_load {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module::OS::Linux::firewall/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my $computer_name = $self->data->get_computer_short_name();
+       
+       notify($ERRORS{'DEBUG'}, 0, "beginning firewalld post-load 
configuration on $computer_name");
+       
+       # Call subroutine in iptables.pm
+       return unless $self->SUPER::process_post_load();
+       
+#### Remove ssh from public zone
+###return unless $self->remove_service('public', 'ssh');
+       
+       $self->save_configuration();
+       
+       notify($ERRORS{'DEBUG'}, 0, "completed firewalld post-load 
configuration on $computer_name");
+       return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 remove_service
+
+ Parameters  : $zone_name, $service_name
+ Returns     : boolean
+ Description : Removes a service from a firewalld zone.
+
+=cut
+
+sub remove_service {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($zone_name, $service_name) = @_;
+       if (!defined($zone_name)) {
+               notify($ERRORS{'WARNING'}, 0, "zone name argument was not 
specified");
+               return;
+       }
+       elsif (!defined($service_name)) {
+               notify($ERRORS{'WARNING'}, 0, "service name argument was not 
specified");
+               return;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --permanent --zone=$zone_name 
--remove-service=$service_name";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
remove '$service_name' service from '$zone_name' zone on $computer_name: 
$command");
+               return;
+       }
+       elsif (grep(/NOT_ENABLED/, @$output)) {
+               notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is not 
enabled in '$zone_name' zone on $computer_name");
+               return 1;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to remove '$service_name' 
service from '$zone_name' zone on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               return;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "removed '$service_name' service from 
'$zone_name' zone on $computer_name, output:\n" . join("\n", @$output));
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_all_direct_rules
+
+ Parameters  : none
+ Returns     : array
+ Description : Calls 'firewall-cmd --permanent --direct --get-all-rules' and
+               returns an array of strings.
+
+=cut
+
+sub get_all_direct_rules {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --permanent --direct --get-all-rules";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
retrieve all firewalld direct rules on $computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve all firewalld 
direct rules on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               return;
+       }
+       
+       # Rules should be in the format:
+       # ipv4 filter vcl-pre_capture 0 --jump ACCEPT --protocol tcp --match 
comment --comment 'VCL: Allow traffic to SSH port 22 from any IP address 
(2017-04-07 17:19:21)' --match tcp --destination-port 22
+       # ipv4 filter INPUT 0 --jump vcl-pre_capture --match comment --comment 
'VCL: jump to rules added during the pre-capture stage (2017-04-07 17:19:21)'
+       my @rules = grep(/^(ipv4|ipv6|eb)/, @$output);
+       
+       notify($ERRORS{'DEBUG'}, 0, "retrieved all firewalld direct rules 
defined on $computer_name:\n" . join("\n", @rules));
+       return @rules;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_direct_chain_rules
+
+ Parameters  : $table_name, $chain_name
+ Returns     : array
+ Description : Calls 'firewall-cmd --permanent --direct --get-rules' and 
returns
+               an array of strings.
+
+=cut
+
+sub get_direct_chain_rules {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name) = @_;
+       if (!$table_name) {
+               notify($ERRORS{'WARNING'}, 0, "table name argument was not 
specified");
+               return;
+       }
+       elsif (!$chain_name) {
+               notify($ERRORS{'WARNING'}, 0, "chain name argument was not 
specified");
+               return;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --permanent --direct --get-rules ipv4 
$table_name $chain_name";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
retrieve firewalld direct rules defined for '$chain_name' chain in 
'$table_name' table on $computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve firewalld 
direct rules defined for '$chain_name' chain in '$table_name' table on 
$computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . 
join("\n", @$output));
+               return;
+       }
+       
+       # All rule lines should begin with an integer:
+       #    0 --jump ACCEPT --source 10.25.7.2 --match comment --comment 'VCL: 
Allow traffic from management node (2017-04-07 15:36:24)'
+       #    1 --jump ACCEPT --source 10.25.7.2
+       my @rules = grep(/^\d+/, @$output);
+       
+       notify($ERRORS{'DEBUG'}, 0, "retrieved firewalld direct rules defined 
for '$chain_name' chain in '$table_name' table on $computer_name:\n" . 
join("\n", @rules));
+       return @rules;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 save_configuration
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Calls 'firewall-cmd --reload'.
+
+=cut
+
+sub save_configuration {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --reload";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
reload firewalld configuration on $computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to reload firewalld 
configuration on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               return 0;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "reloaded firewalld configuration on 
$computer_name");
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 create_chain
+
+ Parameters  : $table_name, $chain_name
+ Returns     : boolean
+ Description : Creates a new chain. Returns true if the chain was successfully
+               created or already exists.
+
+=cut
+
+sub create_chain {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name) = @_;
+       if (!defined($table_name)) {
+               notify($ERRORS{'WARNING'}, 0, "table name argument was not 
specified");
+               return;
+       }
+       elsif (!defined($chain_name)) {
+               notify($ERRORS{'WARNING'}, 0, "chain name argument was not 
specified");
+               return;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --permanent --direct --add-chain ipv4 
$table_name $chain_name";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command 
$computer_name: $command");
+               return;
+       }
+       elsif (grep(/ALREADY_ENABLED/i, @$output)) {
+               notify($ERRORS{'OK'}, 0, "'$chain_name' chain in '$table_name' 
table already exists on $computer_name");
+               return 1;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to create '$chain_name' 
chain in '$table_name' table on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               return 0;
+       }
+       elsif (!grep(/success/, @$output)) {
+               notify($ERRORS{'WARNING'}, 0, "potentially failed to create 
'$chain_name' chain in '$table_name' table on $computer_name, output does not 
contain 'success', exit status: $exit_status, command:\n$command\noutput:\n" . 
join("\n", @$output));
+               return 0;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "created '$chain_name' chain in 
'$table_name' table on $computer_name");
+               #$self->save_configuration();
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 remove_direct_chain_rules
+
+ Parameters  : $table_name, $chain_name
+ Returns     : boolean
+ Description : Flushes (deletes) rules from the specified chain.
+
+=cut
+
+sub remove_direct_chain_rules {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name) = @_;
+       if (!defined($table_name)) {
+               notify($ERRORS{'WARNING'}, 0, "table name argument was not 
specified");
+               return;
+       }
+       elsif (!defined($chain_name)) {
+               notify($ERRORS{'WARNING'}, 0, "chain name argument was not 
specified");
+               return;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       # !!! WARNING !!!
+       # DON'T USE --remove-rules
+       # With firewall-cmd version 0.4.3.2, this option removes rules from ALL 
direct chains, not just the one specified
+       #my $command = "firewall-cmd --permanent --direct --remove-rules ipv4 
$table_name $chain_name";
+       
+       my @rules = $self->get_direct_chain_rules($table_name, $chain_name);
+       for my $rule (@rules) {
+               # [--permanent] --direct --remove-rule { ipv4 | ipv6 | eb } 
table chain priority args
+               my $command = "firewall-cmd --permanent --direct --remove-rule 
ipv4 $table_name $chain_name $rule";
+               my ($exit_status, $output) = $self->os->execute($command, 0);
+               if (!defined($output)) {
+                       notify($ERRORS{'WARNING'}, 0, "failed to execute 
command $computer_name: $command");
+                       return;
+               }
+               elsif ($exit_status ne '0') {
+                       notify($ERRORS{'WARNING'}, 0, "failed to remove rule 
from '$chain_name' chain in '$table_name' table on $computer_name: '$rule', 
exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", 
@$output));
+                       return 0;
+               }
+               else {
+                       notify($ERRORS{'OK'}, 0, "removed direct rule from 
'$chain_name' chain in '$table_name' table on $computer_name: '$rule'");
+               }
+       }
+       
+       notify($ERRORS{'OK'}, 0, "removed all direct rules from '$chain_name' 
chain in '$table_name' table on $computer_name");
+       return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_chain
+
+ Parameters  : $table_name, $chain_name
+ Returns     : boolean
+ Description : Deletes an existing chain. Returns true if the chain was
+               successfully deleted or doesn't exist.
+
+=cut
+
+sub delete_chain {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name) = @_;
+       if (!defined($table_name)) {
+               notify($ERRORS{'WARNING'}, 0, "table name argument was not 
specified");
+               return;
+       }
+       elsif (!defined($chain_name)) {
+               notify($ERRORS{'WARNING'}, 0, "chain name argument was not 
specified");
+               return;
+       }
+       
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       # Delete all rules which reference the chain being deleted or else the 
chain can't be deleted
+       # Do this BEFORE checking if the chain exists to clean up leftover 
references in direct.xml
+       if (!$self->delete_chain_references($table_name, $chain_name)) {
+               notify($ERRORS{'WARNING'}, 0, "unable to delete '$chain_name' 
chain from '$table_name' table on $computer_name, failed to delete all rules 
which reference the chain prior to deletion");
+               return;
+       }
+
+       $self->remove_direct_chain_rules($table_name, $chain_name) || return;
+       
+       my $command = "firewall-cmd --permanent --direct --remove-chain ipv4 
$table_name $chain_name";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command 
$computer_name: $command");
+               return;
+       }
+       elsif (grep(/NOT_ENABLED/i, @$output)) {
+               notify($ERRORS{'OK'}, 0, "'$chain_name' chain in '$table_name' 
does not exist on $computer_name");
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to delete '$chain_name' 
chain in '$table_name' table on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               return 0;
+       }
+       elsif (!grep(/success/, @$output)) {
+               notify($ERRORS{'WARNING'}, 0, "potentially failed to delete 
'$chain_name' chain in '$table_name' table on $computer_name, output does not 
contain 'success', exit status: $exit_status, command:\n$command\noutput:\n" . 
join("\n", @$output));
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "deleted '$chain_name' chain in 
'$table_name' table on $computer_name");
+               #$self->save_configuration();
+       }
+       
+       return $self->clean_direct_xml($table_name . '.*jump\s+' . $chain_name);
+       #$self->delete_chain_references($table_name, $chain_name);
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 clean_direct_xml
+
+ Parameters  : $regex_pattern
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub clean_direct_xml {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my $regex_pattern = shift;
+       if (!defined($regex_pattern)) {
+               notify($ERRORS{'WARNING'}, 0, "regex pattern argument was not 
supplied");
+               return;
+       }
+       
+       $self->os->firewall->save_configuration();
+       
+       my @keep_lines;
+       my @prune_lines;
+       my $file_path = '/etc/firewalld/direct.xml';
+       my @lines = $self->os->get_file_contents($file_path);
+       for my $line (@lines) {
+               if ($line =~ /$regex_pattern/i) {
+                       push @prune_lines, $line;
+               }
+               else {
+                       push @keep_lines, $line;
+               }
+       }
+       
+       if (@prune_lines) {
+               my $updated_contents = join("\n", @keep_lines);
+               notify($ERRORS{'DEBUG'}, 0, "pruning the following lines from 
$file_path matching pattern: '$regex_pattern'\n" . join("\n", @prune_lines) . 
"\nnew file contents:\n$updated_contents");
+               return $self->os->create_text_file($file_path, 
$updated_contents);
+       }
+       else {
+               notify($ERRORS{'DEBUG'}, 0, "no lines were pruned from 
$file_path matching pattern: '$regex_pattern'");
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _insert_rule
+
+ Parameters  : $table_name, $chain_name, $argument_string
+ Returns     : boolean
+ Description : Executes the command to insert a firewalld direct rule. This is 
a
+               helper subroutine and should only be called by
+               iptable.pm::insert_rule.
+
+=cut
+
+sub _insert_rule {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name, $argument_string) = @_;
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --permanent --direct --add-rule ipv4 
$table_name $chain_name 0 $argument_string";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to add 
direct firewalld rule to $chain_name chain in $table_name table on 
$computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to add direct firewalld 
rule to $chain_name chain in $table_name table on $computer_name, exit status: 
$exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+               return 0;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "added direct firewalld rule to 
$chain_name chain in $table_name table on $computer_name, command: $command, 
output:\n" . join("\n", @$output));
+               #$self->save_configuration();
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _delete_rule
+
+ Parameters  : $table_name, $chain_name, $rule_specification_string
+ Returns     : boolean
+ Description : Deletes a firewalld direct rule. This should only used as a
+               helper subroutine.
+
+=cut
+
+sub _delete_rule {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name, $rule_specification_string) = @_;
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "firewall-cmd --permanent --direct --remove-rule ipv4 
$table_name $chain_name 0 $rule_specification_string";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
delete firewalld direct rule on $computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to delete firewalld 
direct rule on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               return;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "deleted firewalld direct rule on 
$computer_name, command: '$command', output:\n" . join("\n", @$output));
+               #$self->save_configuration();
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+1;
+__END__
+
+=head1 SEE ALSO
+
+L<http://cwiki.apache.org/VCL/>
+
+=cut

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm?rev=1790637&r1=1790636&r2=1790637&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm 
(original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/firewall/iptables.pm Fri 
Apr  7 22:15:38 2017
@@ -139,7 +139,7 @@ sub process_post_load {
                }
        }
        
-       # Create a chain and add a jump rule to INPUT
+       # Create a chain and add a jump rule to INPUT chain
        $self->create_chain('filter', $post_load_chain_name);
        if (!$self->insert_rule('filter', 'INPUT',
                {
@@ -156,7 +156,7 @@ sub process_post_load {
                notify($ERRORS{'WARNING'}, 0, "failed to complete firewall 
post-load configuration on $computer_name, failed to create rule in INPUT chain 
to jump to '$post_load_chain_name' chain");
                return;
        }
-       
+
        # Allow traffic from any of the management node IP addresses
        if (!$self->insert_rule('filter', $post_load_chain_name,
                {
@@ -174,7 +174,7 @@ sub process_post_load {
                notify($ERRORS{'WARNING'}, 0, "failed to complete firewall 
post-load configuration on $computer_name, failed to add rule allowing traffic 
from management node IP addresses to $post_load_chain_name chain");
                return;
        }
-       
+
        # Delete other vcl-* chains added by vcld
        my $table_info = $self->get_table_info();
        for my $chain_name (keys %$table_info) {
@@ -183,27 +183,29 @@ sub process_post_load {
                }
        }
        
-       # Legacy code may have been used previously for a reservation, before 
an upgrade
-       # Clean up old connect method rules from the INPUT chain
-       # Delete all rules from INPUT chain matching connect method protocols 
and ports
-       $self->delete_connect_method_rules();
-       
-       # Delete all TCP/22 rules
-       # Images captured prior to VCL 2.5 are saved with an expicit TCP/22 
allow rule from any address
-       $self->delete_rules('filter', 'INPUT',
-               {
-                       "match_extensions" => {
-                               "tcp" => {
-                                       "dport" => 22,
+       if (!$self->isa('VCL::Module::OS::Linux::firewall::firewalld')) {
+               # Legacy code may have been used previously for a reservation, 
before an upgrade
+               # Clean up old connect method rules from the INPUT chain
+               # Delete all rules from INPUT chain matching connect method 
protocols and ports
+               $self->delete_connect_method_rules();
+               
+               # Delete all TCP/22 rules
+               # Images captured prior to VCL 2.5 are saved with an expicit 
TCP/22 allow rule from any address
+               $self->delete_rules('filter', 'INPUT',
+                       {
+                               "match_extensions" => {
+                                       "tcp" => {
+                                               "dport" => 22,
+                                       },
                                },
-                       },
-                       "parameters" => {
-                               "jump" => "ACCEPT",
-                       },
-               }
-       );
-       
-       $self->save_configuration();
+                               "parameters" => {
+                                       "jump" => "ACCEPT",
+                               },
+                       }
+               );
+               
+               $self->save_configuration();
+       }
        
        notify($ERRORS{'DEBUG'}, 0, "completed firewall post-load configuration 
on $computer_name");
        return 1;
@@ -243,7 +245,7 @@ sub process_reserved {
        my $reserved_chain_name = $self->get_reserved_chain_name();
        
        # Delete existing chain if one exists to prevent inconsistent results
-       # Create a chain and add a jump rule to INPUT
+       # Create a chain and add a jump rule to INPUT chain
        $self->create_chain('filter', $reserved_chain_name);
        if (!$self->insert_rule('filter', 'INPUT',
                {
@@ -340,7 +342,7 @@ sub process_inuse {
        my $reserved_chain_name = $self->get_reserved_chain_name();
        
        # Delete existing chain if one exists to prevent inconsistent results
-       # Create a chain and add a jump rule to INPUT
+       # Create a chain and add a jump rule to INPUT chain
        $self->create_chain('filter', $inuse_chain_name);
        if (!$self->insert_rule('filter', 'INPUT',
                {
@@ -442,7 +444,7 @@ sub process_pre_capture {
        
        my $pre_capture_chain_name = $self->get_pre_capture_chain_name();
        
-       # Create a chain and add a jump rule to INPUT
+       # Create a chain and add a jump rule to INPUT chain
        if (!$self->create_chain('filter', $pre_capture_chain_name)) {
                notify($ERRORS{'WARNING'}, 0, "failed to complete firewall 
pre-capture configuration on $computer_name, failed to create 
'$pre_capture_chain_name' chain");
                return;
@@ -484,24 +486,26 @@ sub process_pre_capture {
                return;
        }
        
-       # Delete all rules explicitly defined for any of the management node IP 
addresses
-       # Legacy firewall code would add rules directly to the filter/INPUT 
table for each management node address
-       my @mn_ip_addresses = $self->mn_os->get_ip_addresses();
-       for my $mn_ip_address (@mn_ip_addresses) {
-               $self->delete_rules('filter', 'INPUT',
-                       {
-                               'parameters' => {
-                                       'source' => $mn_ip_address,
-                               },
-                       }
-               );
+       if (!$self->isa('VCL::Module::OS::Linux::firewall::firewalld')) {
+               # Delete all rules explicitly defined for any of the management 
node IP addresses
+               # Legacy firewall code would add rules directly to the 
filter/INPUT table for each management node address
+               my @mn_ip_addresses = $self->mn_os->get_ip_addresses();
+               for my $mn_ip_address (@mn_ip_addresses) {
+                       $self->delete_rules('filter', 'INPUT',
+                               {
+                                       'parameters' => {
+                                               'source' => $mn_ip_address,
+                                       },
+                               }
+                       );
+               }
+               
+               # Legacy code may have been used previously for a reservation, 
before an upgrade
+               # Clean up old connect method rules from the INPUT chain
+               # Delete all rules from INPUT chain matching connect method 
protocols and ports
+               $self->delete_connect_method_rules();
        }
        
-       # Legacy code may have been used previously for a reservation, before 
an upgrade
-       # Clean up old connect method rules from the INPUT chain
-       # Delete all rules from INPUT chain matching connect method protocols 
and ports
-       $self->delete_connect_method_rules();
-       
        # Delete other vcl-* chains added by vcld
        my $table_info = $self->get_table_info();
        for my $chain_name (keys %$table_info) {
@@ -547,7 +551,7 @@ sub process_cluster {
        # This subroutine really should only need to be called once
        $self->delete_chain('filter', $cluster_chain_name);
        
-       # Create a chain and add a jump rule to INPUT
+       # Create a chain and add a jump rule to INPUT chain
        if (!$self->create_chain('filter', $cluster_chain_name)) {
                notify($ERRORS{'WARNING'}, 0, "failed to complete firewall 
cluster configuration on $computer_name, failed to create '$cluster_chain_name' 
chain");
                return;
@@ -720,22 +724,101 @@ sub insert_rule {
                return 1;
        }
        
-       my $command = "/sbin/iptables -t $table_name -I $chain_name";
+       # Convert the specification into valid iptables command arguments
+       my $argument_string = 
$self->get_insert_rule_argument_string($rule_specification_hashref);
+       if (!$argument_string) {
+               notify($ERRORS{'WARNING'}, 0, "failed to add iptables rule to 
$chain_name chain in $table_name table on $computer_name, rule specification 
hash reference could not be converted into an iptables command argument 
string:\n" . format_data($rule_specification_hashref));
+               return;
+       }
+       
+       my $semaphore = $self->get_iptables_semaphore();
+       return $self->_insert_rule($table_name, $chain_name, $argument_string);
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _insert_rule
+
+ Parameters  : $table_name, $chain_name, $argument_string
+ Returns     : boolean
+ Description : Executes the command to insert a rule. This is a helper
+               subroutine and should only be called by insert_rule.
+
+=cut
+
+sub _insert_rule {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name, $argument_string) = @_;
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "/sbin/iptables -t $table_name -I $chain_name 
$argument_string";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command on 
$computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to add iptables rule to 
$chain_name chain in $table_name table on $computer_name, exit status: 
$exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+               return 0;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "added iptables rule to $chain_name 
chain in $table_name table on $computer_name, command: $command");
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_insert_rule_argument_string
+
+ Parameters  : $rule_specification_hashref
+ Returns     : string
+ Description : 
+
+=cut
+
+sub get_insert_rule_argument_string {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($rule_specification_hashref) = @_;
+       if (!$rule_specification_hashref) {
+               notify($ERRORS{'WARNING'}, 0, "rule specification hash 
reference argument was not specified");
+               return;
+       }
+       elsif (!ref($rule_specification_hashref) || 
ref($rule_specification_hashref) ne 'HASH') {
+               notify($ERRORS{'WARNING'}, 0, "rule specification argument is 
not a hash reference:\n" . format_data($rule_specification_hashref));
+               return;
+       }
+       elsif (!scalar(keys(%$rule_specification_hashref))) {
+               notify($ERRORS{'WARNING'}, 0, "rule specification argument does 
not contain any keys");
+               return;
+       }
+       
+       my $argument_string;
        
-       # Add the parameters to the command
+       # Add the parameters to the arguments string
        for my $parameter (sort keys 
%{$rule_specification_hashref->{parameters}}) {
                my $value = 
$rule_specification_hashref->{parameters}{$parameter};
                
                if ($parameter =~ /^\!/) {
-                       $command .= " !";
+                       $argument_string .= "! ";
                        $parameter =~ s/^\!//;
                }
-               $command .= " --$parameter $value";
+               $argument_string .= "--$parameter $value ";
        }
        
-       # Add the match extension to the command
+       # Add the match extension to the arguments string
        for my $match_extension (sort keys 
%{$rule_specification_hashref->{match_extensions}}) {
-               $command .= " --match $match_extension";
+               $argument_string .= "--match $match_extension ";
                for my $option (sort keys 
%{$rule_specification_hashref->{match_extensions}{$match_extension}}) {
                        my $value = 
$rule_specification_hashref->{match_extensions}{$match_extension}{$option};
                        
@@ -744,40 +827,28 @@ sub insert_rule {
                        }
                        
                        if ($option =~ /^\!/) {
-                               $command .= " !";
+                               $argument_string .= "! ";
                                $option =~ s/^\!//;
                        }
                        
-                       $command .= " ";
-                       $command .= "--$option " if $option;
-                       $command .= $value;
+                       $argument_string .= "--$option " if $option;
+                       $argument_string .= "$value ";
                }
        }
        
-       # Add the target extensions to the command
+       # Add the target extensions to the arguments string
        for my $target_extension (sort keys 
%{$rule_specification_hashref->{target_extensions}}) {
-               $command .= " --jump $target_extension";
+               $argument_string .= "--jump $target_extension ";
                for my $option (sort keys 
%{$rule_specification_hashref->{target_extensions}{$target_extension}}) {
                        my $value = 
$rule_specification_hashref->{target_extensions}{$target_extension}{$option};
-                       $command .= " --$option " if $option;
-                       $command .= $value;
+                       $argument_string .= "--$option " if $option;
+                       $argument_string .= "$value ";
                }
        }
        
-       my $semaphore = $self->get_iptables_semaphore();
-       my ($exit_status, $output) = $self->os->execute($command, 0);
-       if (!defined($output)) {
-               notify($ERRORS{'WARNING'}, 0, "failed to execute command on 
$computer_name: $command");
-               return;
-       }
-       elsif ($exit_status ne '0') {
-               notify($ERRORS{'WARNING'}, 0, "failed to add iptables rule to 
$chain_name chain in $table_name table on $computer_name, exit status: 
$exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
-               return 0;
-       }
-       else {
-               notify($ERRORS{'OK'}, 0, "added iptables rule to $chain_name 
chain in $table_name table on $computer_name, command: $command");
-               return 1;
-       }
+       $argument_string =~ s/\s+$//g;
+       
+       return $argument_string;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -865,7 +936,7 @@ sub get_matching_rules {
                notify($ERRORS{'WARNING'}, 0, "failed to determine if any rules 
match on $computer_name, attempt to collapse the rule specification hash 
reference argument produced a result with no keys:\n" . 
format_data($rule_specification_hashref));
                return;
        }
-       notify($ERRORS{'DEBUG'}, 0, "checking if $chain_name chain in 
$table_name table on $computer_name has any rules matching specifications:\n" . 
format_data($collapsed_specification));
+       #notify($ERRORS{'DEBUG'}, 0, "checking if $chain_name chain in 
$table_name table on $computer_name has any rules matching specifications:\n" . 
format_data($collapsed_specification));
        
        # Some iptables options may take multiple forms
        # Attempt to try all forms
@@ -925,7 +996,7 @@ sub get_matching_rules {
        }
        
        my $matching_rule_count = scalar(@matching_rules);
-       notify($ERRORS{'DEBUG'}, 0, "found $matching_rule_count matching rule" 
. ($matching_rule_count == 1 ? '' : 's'));
+       #notify($ERRORS{'DEBUG'}, 0, "found $matching_rule_count matching rule" 
. ($matching_rule_count == 1 ? '' : 's')) if $matching_rule_count;
        return @matching_rules;
 }
 
@@ -1000,25 +1071,50 @@ sub delete_rules {
                
                notify($ERRORS{'DEBUG'}, 0, "attempting to delete rule on 
$computer_name: $rule_specification_string");
                my $semaphore = $self->get_iptables_semaphore();
-               my $command = "/sbin/iptables --delete $chain_name -t 
$table_name $rule_specification_string";
-               my ($exit_status, $output) = $self->os->execute($command, 0);
-               if (!defined($output)) {
-                       notify($ERRORS{'WARNING'}, 0, "failed to execute 
command on $computer_name: $command");
-                       return;
-               }
-               elsif ($exit_status ne '0') {
-                       notify($ERRORS{'WARNING'}, 0, "failed to delete rule on 
$computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . 
join("\n", @$output));
-                       return;
-               }
-               else {
-                       notify($ERRORS{'OK'}, 0, "deleted rule on 
$computer_name with specification: '$rule_specification_string'");
-               }
+               $self->_delete_rule($table_name, $chain_name, 
$rule_specification_string) || return;
        }
        return 1;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 _delete_rule
+
+ Parameters  : $table_name, $chain_name, $rule_specification_string
+ Returns     : boolean
+ Description : Executes the command to delete a rule. This is a helper
+               subroutine and should only be called by delete_rules.
+
+=cut
+
+sub _delete_rule {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my ($table_name, $chain_name, $rule_specification_string) = @_;
+       my $computer_name = $self->data->get_computer_hostname();
+       
+       my $command = "/sbin/iptables --delete $chain_name -t $table_name 
$rule_specification_string";
+       my ($exit_status, $output) = $self->os->execute($command, 0);
+       if (!defined($output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command on 
$computer_name: $command");
+               return;
+       }
+       elsif ($exit_status ne '0') {
+               notify($ERRORS{'WARNING'}, 0, "failed to delete rule on 
$computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . 
join("\n", @$output));
+               return;
+       }
+       else {
+               notify($ERRORS{'OK'}, 0, "deleted rule on $computer_name with 
specification: '$rule_specification_string'");
+               return 1;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 delete_connect_method_rules
 
  Parameters  : none
@@ -1214,7 +1310,7 @@ sub delete_chain_references {
        for my $referencing_chain_name (keys %$table_info) {
                for my $rule (@{$table_info->{$referencing_chain_name}{rules}}) 
{
                        my $rule_specification_string = 
$rule->{rule_specification};
-                       if ($rule_specification_string =~ /-j 
$chain_name(\s|$)/) {
+                       if ($rule_specification_string =~ /(-j|--jump) 
$chain_name(\s|$)/) {
                                notify($ERRORS{'DEBUG'}, 0, "rule in 
'$table_name' table references '$chain_name' chain, referencing chain: 
$referencing_chain_name, rule specification: $rule_specification_string");
                                if (!$self->delete_rules($table_name, 
$referencing_chain_name, {'rule_specification' => $rule_specification_string})) 
{
                                        return;
@@ -1325,13 +1421,7 @@ sub flush_chain {
        
        my $computer_name = $self->data->get_computer_hostname();
        
-       my $command = "/sbin/iptables --flush";
-       my $chain_text = 'all chains';
-       if ($chain_name ne '*') {
-               $chain_text = "'$chain_name' chain";
-               $command .= " $chain_name";
-       }
-       $command .= " --table $table_name";
+       my $command = "/sbin/iptables --flush $chain_name --table $table_name";
        
        my $semaphore = $self->get_iptables_semaphore();
        my ($exit_status, $output) = $self->os->execute($command, 0);
@@ -1340,11 +1430,11 @@ sub flush_chain {
                return;
        }
        elsif ($exit_status ne '0') {
-               notify($ERRORS{'WARNING'}, 0, "failed to flush $chain_text in 
'$table_name' table on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
+               notify($ERRORS{'WARNING'}, 0, "failed to flush '$chain_name' 
chain in '$table_name' table on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
                return 0;
        }
        else {
-               notify($ERRORS{'OK'}, 0, "flushed $chain_text in '$table_name' 
table on $computer_name");
+               notify($ERRORS{'OK'}, 0, "flushed '$chain_name' chain in 
'$table_name' table on $computer_name");
                return 1;
        }
 }
@@ -1353,7 +1443,7 @@ sub flush_chain {
 
 =head2 get_table_info
 
- Parameters  : $table_name (optional)
+ Parameters  : $table_name, $no_cache
  Returns     : boolean
  Description : Retrieves the configuration of an iptables table and constructs 
a
                hash reference. Information from the 'filter' table is returned
@@ -1409,7 +1499,13 @@ sub get_table_info {
                return 0;
        }
        
-       my $table_name = shift || 'filter';
+       my ($table_name, $no_cache) = @_;
+       
+       $table_name = 'filter' unless $table_name;
+       
+       if (!$no_cache && defined($self->{table_info}) && 
defined($self->{table_info}{$table_name})) {
+               return $self->{table_info}{$table_name};
+       }
        
        $ENV{iptables_get_table_info_count}{$table_name}++;
        
@@ -1426,9 +1522,44 @@ sub get_table_info {
                notify($ERRORS{'WARNING'}, 0, "failed to list rules from 
'$table_name' table on $computer_name, exit status: $exit_status, 
command:\n$command\noutput:\n" . join("\n", @$output));
                return 0;
        }
-
+       
+       my @lines = @$output;
+       
+       if ($self->can('get_all_direct_rules')) {
+               # Convert:
+               #    ipv4 filter vcl-pre_capture 0 --jump ACCEPT --protocol tcp 
--match comment --comment 'VCL: ...' --match tcp --destination-port 22
+               # To:
+               #    -A vcl-pre_capture -p tcp -m comment --comment "VCL: ..." 
-m tcp --dport 22 -j ACCEPT
+               DIRECT_RULE: for my $direct_rule 
($self->get_all_direct_rules()) {
+                       my ($rule_protocol, $rule_table, $rule_chain, 
$rule_priority, $rule_specification) = $direct_rule =~
+                               /^
+                               (\S+)\s+
+                               (\S+)\s+
+                               (\S+)\s+
+                               (\d+)\s+
+                               (\S.*)
+                               $/x
+                       ;
+                       if (!defined($rule_specification)) {
+                               notify($ERRORS{'WARNING'}, 0, "failed to parse 
firewalld direct rule: $direct_rule");
+                               next DIRECT_RULE;
+                       }
+                       elsif ($rule_table ne $table_name) {
+                               notify($ERRORS{'DEBUG'}, 0, "ignoring rule, 
table does not match '$table_name': $direct_rule");
+                               next DIRECT_RULE;
+                       }
+                       
+                       my $converted_rule = "-A $rule_chain 
$rule_specification";
+                       notify($ERRORS{'DEBUG'}, 0, "converted iptables direct 
rule to iptables format:\n" .
+                               "direct rule     : $direct_rule\n" .
+                               "iptables format : $converted_rule"
+                       );
+                       push @lines, $converted_rule;
+               }
+       }
+       
        my $table_info = {};
-       LINE: for my $line (@$output) {
+       LINE: for my $line (@lines) {
                # Split the rule, samples:
                #    -P OUTPUT ACCEPT
                #    -N vcld-3115
@@ -1528,13 +1659,27 @@ sub get_table_info {
                                $rule->{parameters}{$target_parameter} = 
$target;
                                
                                my $target_extension_option_name;
-                               my @target_extension_option_sections = 
split(/\s+/, $target_extension_option_string);
+                               
+                               # Need to split line not just by spaces, but 
also find sections enclosed in quotes:
+                               #    -j REJECT --reject-with 
icmp-host-prohibited
+                               #    -j LOG --log-prefix "IN_public_DROP: "
+                               my @target_extension_option_sections = 
$target_extension_option_string =~
+                               /
+                                       (
+                                               ['"][^'"]*['"]
+                                               |
+                                               [^\s]+
+                                       )
+                               /gx;
+                               
                                TARGET_OPTION_SECTION: for my 
$target_extension_option_section (@target_extension_option_sections) {
                                        # Stop parsing if the start of a match 
extension specification if found
                                        if ($target_extension_option_section =~ 
/^(-m|--match)$/) {
                                                last TARGET_OPTION_SECTION;
                                        }
                                        
+                                       
+                                       
                                        # Check if this is the beginning of a 
target extension option
                                        if ($target_extension_option_section =~ 
/^[-]+(\w[\w-]+)/) {
                                                $target_extension_option_name = 
$1;
@@ -1544,8 +1689,9 @@ sub get_table_info {
                                        elsif (!$target_extension_option_name) {
                                                # If here, the section should 
be a target extension option value
                                                notify($ERRORS{'WARNING'}, 0, 
"failed to parse iptables rule, target extension option name was not detected 
before this section: '$target_extension_option_section'\n" .
-                                                       "iptables command: 
$line\n" .
-                                                       "preceeding target 
parameter: $target_parameter --> $target"
+                                                       "output line: $line\n" .
+                                                       "preceeding target 
parameter: $target_parameter\n" .
+                                                       "target value: $target"
                                                );
                                                next LINE;
                                        }
@@ -1572,8 +1718,20 @@ sub get_table_info {
                        
                        
                        # The only text remaining in $rule_specification_string 
should be match extension information
-                       # Split the remaining string by spaces
-                       my @match_extension_sections = split(/\s+/, 
$rule_specification_string);
+                       
+                       # --match comment--comment 'my comment'
+                       # --match tcp--destination-port
+                       $rule_specification_string =~ s/(--match [^\s-]+)--/$1 
--/g;
+                       
+                       # Split the remaining string by spaces or sections 
enclosed in quotes
+                       my @match_extension_sections = 
$rule_specification_string =~
+                               /
+                                       (
+                                               ['"][^'"]*['"]
+                                               |
+                                               [^\s]+
+                                       )
+                               /gx;
                        
                        # Match extensions will be in the form:
                        # -m,--match <module> [!] -<x>,--<option> <value> [[!] 
-<x>,--<option> <value>...]
@@ -1582,6 +1740,8 @@ sub get_table_info {
                        my $match_extension_option_inverted = 0;
                        my $comment;
                        
+                       
+                       
                        MATCH_EXTENSION_SECTION: for my 
$match_extension_section (@match_extension_sections) {
                                next MATCH_EXTENSION_SECTION if 
!$match_extension_section;
                                
@@ -1660,6 +1820,7 @@ sub get_table_info {
                }
        }
        
+       $self->{table_info}{$table_name} = $table_info;
        #notify($ERRORS{'DEBUG'}, 0, "retrieved rules from iptables $table_name 
table from $computer_name:\n" . format_data($table_info));
        return $table_info;
 }


Reply via email to