# The LearningOnline Network with CAPA # Firewall configuration to allow internal LON-CAPA communication between servers # # $Id: Firewall.pm,v 1.2 2009/06/11 13:01:56 raeburn Exp $ # # The LearningOnline Network with CAPA # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # # Startup script for the LON-CAPA network processes # package LONCAPA::Firewall; 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) = @_; 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"; } # # iptables is running with expected chain # if ($fw_chain =~ /^([\w\-]+)$/) { $fw_chain = $1; } else { return 'Chain name has unexpected format'."\n"; } if (ref($ports) ne 'ARRAY') { return 'List of ports to open needed.'; } foreach my $portnum (@{$ports}) { my $port = ''; if ($portnum =~ /^(\d+)$/) { $port = $1; } else { print "Skipped non-numeric port: $portnum\n"; next; } print "Opening firewall access on port $port.\n"; my $result; 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); if (ref($iphost) eq 'HASH') { if (keys(%{$iphost}) > 0) { &firewall_close_anywhere($iptables,$fw_chain,$port); foreach my $key (keys(%{$iphost})) { my $ip = ''; if ($key =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) { if (($1<=255) && ($2<=255) && ($3<=255) && ($4<=255)) { $ip = "$1.$2.$3.$4"; } else { next; } } 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 (@lond_port_open) { push(@opened,$port); print "Port $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"; } 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"; } } 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); } } } foreach my $port (@{$ports}) { if (!grep(/^\Q$port\E$/,@opened)) { return 'Required port not open: '.$port."\n"; } } return 'ok'; } sub firewall_is_port_open { my ($iptables,$fw_chain,$port,$lond_port,$iphost) = @_; # 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+/); } close(PIPE); } } } return $count; } else { if (`$iptables -L -n 2>/dev/null | grep "tcp dpt:$port"`) { return 1; } else { return 0; } } } sub firewall_is_active { if (-e '/proc/net/ip_tables_names') { return 1; } else { return 0; } } sub firewall_close_port { my ($iptables,$fw_chain,$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"; } 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+)$/) { $port = $1; } else { print "Skipped non-numeric port: $portnum\n"; next; } 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; } } 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 $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; } 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"; } } } close(PIPE); } sub get_lond_port { my $perlvarref=&LONCAPA::Configuration::read_conf(); my $lond_port; if (ref($perlvarref) eq 'HASH') { if (defined($perlvarref->{'londPort'})) { $lond_port = $perlvarref->{'londPort'}; } } if (!$lond_port) { print("Unable to determine lond port number from LON-CAPA configuration.\n"); } return $lond_port; } sub get_fw_chain { my $fw_chain = 'RH-Firewall-1-INPUT'; my $suse_config = "/etc/sysconfig/SuSEfirewall2"; if (-e $suse_config) { $fw_chain = 'input_ext'; } else { if (!-e '/etc/sysconfig/iptables') { print("Unable to find iptables file containing static definitions\n"); } } return $fw_chain; } sub get_pathto_iptables { my $iptables; if (-e '/sbin/iptables') { $iptables = '/sbin/iptables'; } elsif (-e '/usr/sbin/iptables') { $iptables = '/usr/sbin/iptables'; } else { print("Unable to find iptables command\n"); } return $iptables; } 1; __END__ =pod =head1 NAME B - dynamic opening/closing of firewall ports =head1 SYNOPSIS use lib '/home/httpd/lib/perl/'; use LONCAPA::Firewall; LONCAPA::Firewall::firewall_open_port(); LONCAPA::Firewall::firewall_close_port(); LONCAPA::Firewall::firewall_is_port_open(); LONCAPA::Firewall::firewall_is_active(); LONCAPA::Firewall::firewall_close_anywhere(); =head1 DESCRIPTION The scripts: /etc/init.d/loncontrol, used to stop or start LON-CAPA services, as well as the setuid script /home/httpd/perl/lciptables, called by loncron for housekeeping tasks, make use of the methods provided by this module to open and close firewall ports (currently the default port: 5663), used for socket-based communication between LON-CAPA servers in the cluster of networked servers to which the server belongs. The following methods are available: =over 4 =item LONCAPA::Firewall::firewall_open_port( $iptables,$fw_chain,$lond_port,$iphost,$port ); =back =over 4 =item LONCAPA::Firewall::firewall_close_port( $iptables,$fw_chain,$lond_port,$ports ); =back =over 4 =item LONCAPA::Firewall::firewall_is_port_open( $iptables,$fw_chain,$port,$lond_port,$iphost ); =back =over 4 =item LONCAPA::Firewall::firewall_is_active(); =back =over 4 =item LONCAPA::Firewall::firewall_close_anywhere( $iptables,$fw_chain,$port ); =back =over 4 =item LONCAPA::Firewall::get_lond_port(); =back =over 4 =item LONCAPA::Firewall::get_fw_chain(); =back =over 4 =item LONCAPA::Firewall::get_pathto_iptables(); =head1 AUTHORS This library is free software; you can redistribute it and/or modify it under the same terms as LON-CAPA itself. =cut