--- loncom/configuration/Firewall.pm 2009/07/17 00:15:49 1.5 +++ loncom/configuration/Firewall.pm 2010/03/25 01:47:45 1.6 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Firewall configuration to allow internal LON-CAPA communication between servers # -# $Id: Firewall.pm,v 1.5 2009/07/17 00:15:49 raeburn Exp $ +# $Id: Firewall.pm,v 1.6 2010/03/25 01:47:45 raeburn Exp $ # # The LearningOnline Network with CAPA # @@ -36,23 +36,21 @@ use strict; use lib '/home/httpd/perl/lib'; use LONCAPA::Configuration; -# Firewall code is based on the code in FC2 /etc/init.d/ntpd - sub firewall_open_port { - my ($iptables,$fw_chain,$lond_port,$iphost,$ports) = @_; + my ($iptables,$fw_chains,$lond_port,$iphost,$ports) = @_; return 'inactive firewall' if (!&firewall_is_active()); return 'port number unknown' if !$lond_port; - my @opened; - if (! `$iptables -L -n 2>/dev/null | grep $fw_chain | wc -l`) { - return 'Expected chain "'.$fw_chain.'" missing from iptables'."\n"; + return 'invalid firewall chain' unless (ref($fw_chains) eq 'ARRAY'); + my (@opened,@chains,@badchains,@okchains); + foreach my $chain (@{$fw_chains}) { + if ($chain =~ /^([\w\-]+)$/) { + push(@okchains,$1); + } else { + push(@badchains,$chain); + } } - # - # iptables is running with expected chain - # - if ($fw_chain =~ /^([\w\-]+)$/) { - $fw_chain = $1; - } else { - return 'Chain name has unexpected format'."\n"; + if (!@okchains) { + return 'None of the chain names has the expected format'."\n"; } if (ref($ports) ne 'ARRAY') { return 'List of ports to open needed.'; @@ -70,10 +68,15 @@ sub firewall_open_port { if ($port eq $lond_port) { # For lond port, restrict the servers allowed to attempt to communicate # to include only source IPs in the LON-CAPA cluster. - my (@port_error,@command_error,@lond_port_open); + my (@port_error,%command_error,@lond_port_open, + @lond_port_curropen); if (ref($iphost) eq 'HASH') { - if (keys(%{$iphost}) > 0) { - &firewall_close_anywhere($iptables,$fw_chain,$port); + if (keys(%{$iphost}) > 0) { + my %curropen; + foreach my $fw_chain (@okchains) { + &firewall_close_anywhere($iptables,$fw_chain,$port); + my $current = &firewall_is_port_open($iptables,$fw_chain,$port,$lond_port,$iphost,\%curropen); + } foreach my $key (keys(%{$iphost})) { my $ip = ''; if ($key =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) { @@ -85,46 +88,82 @@ sub firewall_open_port { } else { next; } - my $firewall_command = - "$iptables -I $fw_chain -p tcp -s $ip -d 0/0 --dport $port -j ACCEPT"; - system($firewall_command); - my $return_status = $?>>8; - if ($return_status == 1) { - push (@port_error,$ip); - } elsif ($return_status == 2) { - push(@command_error,$ip); - } elsif ($return_status == 0) { - push(@lond_port_open,$ip); + if ($curropen{$ip}) { + push(@lond_port_curropen,$ip); + } else { + foreach my $fw_chain (@okchains) { + my $firewall_command = + "$iptables -I $fw_chain -p tcp -s $ip -d 0/0 --dport $port -j ACCEPT"; + system($firewall_command); + my $return_status = $?>>8; + if ($return_status == 1) { + unless(grep(/^\Q$ip\E$/,@port_error)) { + push (@port_error,$ip); + } + } elsif ($return_status == 2) { + push(@{$command_error{$fw_chain}},$ip); + } elsif ($return_status == 0) { + push(@lond_port_open,$ip); + last; + } + } } } } } + if (@lond_port_curropen) { + unless (grep(/^\Q$port\E$/,@opened)) { + push(@opened,$port); + } + print "Port already open for ".scalar(@lond_port_curropen)." IP addresses\n"; + } if (@lond_port_open) { - push(@opened,$port); - print "Port $port opened for ".scalar(@lond_port_open)." IP addresses\n"; + unless (grep(/^\Q$port\E$/,@opened)) { + push(@opened,$port); + } + print "Port opened for ".scalar(@lond_port_open)." IP addresses\n"; } if (@port_error) { - print "Error opening port $port for following IP addresses: ".join(', ',@port_error)."\n"; + print "Error opening port for following IP addresses: ".join(', ',@port_error)."\n"; } - if (@command_error) { - print "Bad command error opening port for following IP addresses: ". - join(', ',@command_error)."\n". - 'Command was: "'."$iptables -I $fw_chain -p tcp -s ".'$ip'." -d 0/0 --dport $port -j ACCEPT".'", where $ip is IP address'."\n"; + if (keys(%command_error) > 0) { + foreach my $chain (sort(keys(%command_error))) { + if (ref($command_error{$chain}) eq 'ARRAY') { + if (@{$command_error{$chain}}) { + print "Bad command error opening port for following IP addresses: ". + join(', ',@{$command_error{$chain}})."\n". + 'Command was: "'."$iptables -I $chain -p tcp -s ".'$ip'." -d 0/0 --dport $port -j ACCEPT".'", where $ip is IP address'."\n"; + } + } + } } } else { - my $firewall_command = - "$iptables -I $fw_chain -p tcp -d 0/0 --dport $port -j ACCEPT"; - system($firewall_command); - my $return_status = $?>>8; - if ($return_status == 1) { - # Error - print "Error opening port.\n"; - } elsif ($return_status == 2) { - # Bad command - print "Bad command error opening port. Command was\n". - " ".$firewall_command."\n"; - } elsif ($return_status == 0) { - push(@opened,$port); + my (@port_errors,%command_errors); + foreach my $fw_chain (@okchains) { + my $firewall_command = + "$iptables -I $fw_chain -p tcp -d 0/0 --dport $port -j ACCEPT"; + system($firewall_command); + my $return_status = $?>>8; + if ($return_status == 1) { + push(@port_errors,$fw_chain); + } elsif ($return_status == 2) { + $command_errors{$fw_chain} = $firewall_command; + } elsif ($return_status == 0) { + push(@opened,$port); + last; + } + } + unless (grep(/^\Q$port\E$/,@opened)) { + if (@port_errors) { + print "Error opening port for chains: ". + join(', ',@port_errors).".\n"; + } + if (keys(%command_errors)) { + foreach my $fw_chain (sort(keys(%command_errors))) { + print "Bad command error opening port for chain: $fw_chain. Command was\n". + " ".$command_errors{$fw_chain}."\n"; + } + } } } } @@ -137,33 +176,37 @@ sub firewall_open_port { } sub firewall_is_port_open { - my ($iptables,$fw_chain,$port,$lond_port,$iphost) = @_; + my ($iptables,$fw_chain,$port,$lond_port,$iphost,$curropen) = @_; # for lond port returns number of source IPs for which firewall port is open # for other ports returns 1 if the firewall port is open, 0 if not. # # check if firewall is active or installed return if (! &firewall_is_active()); - if ($port eq $lond_port) { - my $count ++; - if (ref($iphost) eq 'HASH') { - if (keys(%{$iphost}) > 0) { - foreach my $ip (keys(%{$iphost})) { - open(PIPE,"$iptables -L $fw_chain -n 2>/dev/null"); - while() { - $count++ if (/^ACCEPT\s+tcp\s+\-{2}\s+\Q$ip\E\s+/); + my $count = 0; + if (open(PIPE,"$iptables -L $fw_chain -n 2>/dev/null |")) { + while() { + if ($port eq $lond_port) { + if (ref($iphost) eq 'HASH') { + if (/^ACCEPT\s+tcp\s+\-{2}\s+([\S]+)\s+/) { + my $ip = $1; + if ($iphost->{$ip}) { + $count ++; + if (ref($curropen) eq 'HASH') { + $curropen->{$ip} ++; + } + } } - close(PIPE); + } + } else { + if (/tcp dpt\:\Q$port\E/) { + $count ++; + last; } } } - return $count; - } else { - if (`$iptables -L -n 2>/dev/null | grep "tcp dpt:$port"`) { - return 1; - } else { - return 0; - } + close(PIPE); } + return $count; } sub firewall_is_active { @@ -175,20 +218,24 @@ sub firewall_is_active { } sub firewall_close_port { - my ($iptables,$fw_chain,$lond_port,$ports) = @_; + my ($iptables,$fw_chains,$lond_port,$ports) = @_; return 'inactive firewall' if (!&firewall_is_active()); return 'port number unknown' if !$lond_port; - if (! `$iptables -L -n 2>/dev/null | grep $fw_chain | wc -l`) { - return 'Expected chain "'.$fw_chain.'" missing from iptables'."\n"; + return 'invalid firewall chain' unless (ref($fw_chains) eq 'ARRAY'); + my (@opened,@chains,@badchains,@okchains); + foreach my $chain (@{$fw_chains}) { + if ($chain =~ /^([\w\-]+)$/) { + push(@okchains,$1); + } else { + push(@badchains,$chain); + } + } + if (!@okchains) { + return 'None of the chain names has the expected format'."\n"; } if (ref($ports) ne 'ARRAY') { return 'List of ports to close needed.'; } - if ($fw_chain =~ /^([\w\-]+)$/) { - $fw_chain = $1; - } else { - return 'Chain name has unexpected format'."\n"; - } foreach my $portnum (@{$ports}) { my $port = ''; if ($portnum =~ /^(\d+)$/) { @@ -199,58 +246,75 @@ sub firewall_close_port { } print "Closing firewall access on port $port\n"; if (($port ne '') && ($port eq $lond_port)) { - my (@port_error,@command_error,@lond_port_close); - my %to_close; - open(PIPE, "$iptables -n -L $fw_chain |"); - while () { - chomp(); - next unless (/dpt:\Q$port\E\s*$/); - if (/^ACCEPT\s+tcp\s+\-{2}\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+/) { - $to_close{$1} = $port; + foreach my $fw_chain (@okchains) { + my (@port_error,@command_error,@lond_port_close); + my %to_close; + if (open(PIPE, "$iptables -n -L $fw_chain |")) { + while () { + chomp(); + next unless (/dpt:\Q$port\E\s*$/); + if (/^ACCEPT\s+tcp\s+\-{2}\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+/) { + $to_close{$1} = $port; + } + } + close(PIPE); + } + if (keys(%to_close) > 0) { + foreach my $ip (keys(%to_close)) { + my $firewall_command = + "$iptables -D $fw_chain -p tcp -s $ip -d 0/0 --dport $port -j ACCEPT"; + system($firewall_command); + my $return_status = $?>>8; + if ($return_status == 1) { + push (@port_error,$ip); + } elsif ($return_status == 2) { + push(@command_error,$ip); + } elsif ($return_status == 0) { + push(@lond_port_close,$ip); + } + } + } + if (@lond_port_close) { + print "Port closed for ".scalar(@lond_port_close)." IP addresses\n"; + } + if (@port_error) { + print "Error closing port for following IP addresses: ".join(', ',@port_error)."\n"; + } + if (@command_error) { + print "Bad command error opening port for following IP addresses: ". + join(', ',@command_error)."\n". + 'Command was: "'."$iptables -D $fw_chain -p tcp -s ".'$ip'." -d 0/0 --dport $port -j ACCEPT".'", where $ip is IP address'."\n"; } } - close(PIPE); - if (keys(%to_close) > 0) { - foreach my $ip (keys(%to_close)) { + } else { + foreach my $fw_chain (@okchains) { + my (@port_error,@command_error,@lond_port_close); + my $to_close; + if (open(PIPE, "$iptables -n -L $fw_chain |")) { + while () { + chomp(); + next unless (/dpt:\Q$port\E\s*$/); + $to_close = 1; + } + close(PIPE); + } + if ($to_close) { my $firewall_command = - "$iptables -D $fw_chain -p tcp -s $ip -d 0/0 --dport $port -j ACCEPT"; + "$iptables -D $fw_chain -p tcp -d 0/0 --dport $port -j ACCEPT"; system($firewall_command); my $return_status = $?>>8; if ($return_status == 1) { - push (@port_error,$ip); + # Error + print "Error closing port for chain: $fw_chain.\n"; } elsif ($return_status == 2) { - push(@command_error,$ip); - } elsif ($return_status == 0) { - push(@lond_port_close,$ip); + # Bad command + print "Bad command error closing port. Command was\n". + " ".$firewall_command."\n"; + } else { + print "Port closed for chain $fw_chain.\n"; } } } - if (@lond_port_close) { - print "Port $port closed for ".scalar(@lond_port_close)." IP addresses\n"; - } - if (@port_error) { - print "Error closing port $port for following IP addresses: ".join(', ',@port_error)."\n"; - } - if (@command_error) { - print "Bad command error opening port for following IP addresses: ". - join(', ',@command_error)."\n". - 'Command was: "'."$iptables -D $fw_chain -p tcp -s ".'$ip'." -d 0/0 --dport $port -j ACCEPT".'", where $ip is IP address'."\n"; - } - } else { - my $firewall_command = - "$iptables -D $fw_chain -p tcp -d 0/0 --dport $port -j ACCEPT"; - system($firewall_command); - my $return_status = $?>>8; - if ($return_status == 1) { - # Error - print "Error closing port.\n"; - } elsif ($return_status == 2) { - # Bad command - print "Bad command error closing port. Command was\n". - " ".$firewall_command."\n"; - } else { - print "Port closed.\n"; - } } } return; @@ -258,25 +322,26 @@ sub firewall_close_port { sub firewall_close_anywhere { my ($iptables,$fw_chain,$port) = @_; - open(PIPE, "$iptables --line-numbers -n -L $fw_chain |"); - while () { - next unless (/dpt:\Q$port\E/); - chomp(); - if (/^(\d+)\s+ACCEPT\s+tcp\s+\-{2}\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0/) { - my $firewall_command = "$iptables -D $fw_chain $1"; - system($firewall_command); - my $return_status = $?>>8; - if ($return_status == 1) { - print 'Error closing port '.$port.' for source "anywhere"'."\n"; - } elsif ($return_status == 2) { - print 'Bad command error closing port '.$port.' for source "anywhere". Command was'."\n". - ' '.$firewall_command."\n"; - } else { - print 'Port '.$port.' closed for source "anywhere"'."\n"; + if (open(PIPE, "$iptables --line-numbers -n -L $fw_chain |")) { + while () { + next unless (/dpt:\Q$port\E/); + chomp(); + if (/^(\d+)\s+ACCEPT\s+tcp\s+\-{2}\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0/) { + my $firewall_command = "$iptables -D $fw_chain $1"; + system($firewall_command); + my $return_status = $?>>8; + if ($return_status == 1) { + print 'Error closing port '.$port.' for source "anywhere"'."\n"; + } elsif ($return_status == 2) { + print 'Bad command error closing port '.$port.' for source "anywhere". Command was'."\n". + ' '.$firewall_command."\n"; + } else { + print 'Port '.$port.' closed for source "anywhere"'."\n"; + } } } + close(PIPE); } - close(PIPE); } sub get_lond_port { @@ -293,28 +358,41 @@ sub get_lond_port { return $lond_port; } -sub get_fw_chain { +sub get_fw_chains { my ($iptables) = @_; - my $fw_chain = 'RH-Firewall-1-INPUT'; + my @fw_chains; my $suse_config = "/etc/sysconfig/SuSEfirewall2"; if (-e $suse_config) { - $fw_chain = 'input_ext'; + push(@fw_chains,'input_ext'); } else { if (!-e '/etc/sysconfig/iptables') { if (!-e '/var/lib/iptables') { print("Unable to find iptables file containing static definitions\n"); } + push(@fw_chains,'RH-Firewall-1-INPUT'); } if ($iptables eq '') { $iptables = &get_pathto_iptables(); } - my $count = `$iptables -L -n 2>/dev/null |grep $fw_chain |wc -l`; - chomp($count); - if (!$count) { - $fw_chain ='INPUT'; + my %counts; + my @posschains = ('RH-Firewall-1-INPUT','INPUT'); + if (open(PIPE,"$iptables -L -n |")) { + while() { + foreach my $chain (@posschains) { + if (/(\Q$chain\E)/) { + $counts{$1} ++; + } + } + } + close(PIPE); + } + foreach my $fw_chain (@posschains) { + if ($counts{$fw_chain}) { + push(@fw_chains,$fw_chain); + } } } - return $fw_chain; + return @fw_chains; } sub get_pathto_iptables { @@ -362,19 +440,19 @@ The following methods are available: =over 4 -=item LONCAPA::Firewall::firewall_open_port( $iptables,$fw_chain,$lond_port,$iphost,$port ); +=item LONCAPA::Firewall::firewall_open_port( $iptables,$fw_chains,$lond_port,$iphost,$port ); =back =over 4 -=item LONCAPA::Firewall::firewall_close_port( $iptables,$fw_chain,$lond_port,$ports ); +=item LONCAPA::Firewall::firewall_close_port( $iptables,$fw_chains,$lond_port,$ports ); =back =over 4 -=item LONCAPA::Firewall::firewall_is_port_open( $iptables,$fw_chain,$port,$lond_port,$iphost ); +=item LONCAPA::Firewall::firewall_is_port_open( $iptables,$fw_chain,$port,$lond_port,$iphost,$curropen ); =back @@ -398,7 +476,7 @@ The following methods are available: =over 4 -=item LONCAPA::Firewall::get_fw_chain(); +=item LONCAPA::Firewall::get_fw_chains(); =back