![]() ![]() | ![]() |
- Use LONCAPA:distro() to get Linux distro.
1: # The LearningOnline Network with CAPA 2: # Firewall configuration to allow internal LON-CAPA communication between servers 3: # 4: # $Id: Firewall.pm,v 1.12 2013/02/08 14:55:12 raeburn Exp $ 5: # 6: # The LearningOnline Network with CAPA 7: # 8: # Copyright Michigan State University Board of Trustees 9: # 10: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 11: # 12: # LON-CAPA is free software; you can redistribute it and/or modify 13: # it under the terms of the GNU General Public License as published by 14: # the Free Software Foundation; either version 2 of the License, or 15: # (at your option) any later version. 16: # 17: # LON-CAPA is distributed in the hope that it will be useful, 18: # but WITHOUT ANY WARRANTY; without even the implied warranty of 19: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20: # GNU General Public License for more details. 21: # 22: # You should have received a copy of the GNU General Public License 23: # along with LON-CAPA; if not, write to the Free Software 24: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 25: # 26: # /home/httpd/html/adm/gpl.txt 27: # 28: # http://www.lon-capa.org/ 29: # 30: # Startup script for the LON-CAPA network processes 31: # 32: 33: package LONCAPA::Firewall; 34: 35: use strict; 36: use lib '/home/httpd/perl/lib'; 37: use LONCAPA::Configuration; 38: use LONCAPA; 39: 40: sub firewall_open_port { 41: my ($iptables,$fw_chains,$lond_port,$iphost,$ports) = @_; 42: return 'inactive firewall' if (!&firewall_is_active()); 43: return 'port number unknown' if !$lond_port; 44: return 'invalid firewall chain' unless (ref($fw_chains) eq 'ARRAY'); 45: my (@opened,@chains,@badchains,@okchains); 46: foreach my $chain (@{$fw_chains}) { 47: if ($chain =~ /^([\w\-]+)$/) { 48: push(@okchains,$1); 49: } else { 50: push(@badchains,$chain); 51: } 52: } 53: if (!@okchains) { 54: return 'None of the chain names has the expected format'."\n"; 55: } 56: if (ref($ports) ne 'ARRAY') { 57: return 'List of ports to open needed.'; 58: } 59: foreach my $portnum (@{$ports}) { 60: my $port = ''; 61: if ($portnum =~ /^(\d+)$/) { 62: $port = $1; 63: } else { 64: print "Skipped non-numeric port: $portnum\n"; 65: next; 66: } 67: print "Opening firewall access on port $port.\n"; 68: my $result; 69: if ($port eq $lond_port) { 70: # For lond port, restrict the servers allowed to attempt to communicate 71: # to include only source IPs in the LON-CAPA cluster. 72: my (@port_error,%command_error,@lond_port_open, 73: @lond_port_curropen); 74: if (ref($iphost) eq 'HASH') { 75: if (keys(%{$iphost}) > 0) { 76: my %curropen; 77: foreach my $fw_chain (@okchains) { 78: &firewall_close_anywhere($iptables,$fw_chain,$port); 79: my $current = &firewall_is_port_open($iptables,$fw_chain,$port,$lond_port,$iphost,\%curropen); 80: } 81: foreach my $key (keys(%{$iphost})) { 82: my $ip = ''; 83: if ($key =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) { 84: if (($1<=255) && ($2<=255) && ($3<=255) && ($4<=255)) { 85: $ip = "$1.$2.$3.$4"; 86: } else { 87: next; 88: } 89: } else { 90: next; 91: } 92: if ($curropen{$ip}) { 93: push(@lond_port_curropen,$ip); 94: } else { 95: foreach my $fw_chain (@okchains) { 96: my $firewall_command = 97: "$iptables -I $fw_chain -p tcp -s $ip -d 0/0 --dport $port -j ACCEPT"; 98: system($firewall_command); 99: my $return_status = $?>>8; 100: if ($return_status == 1) { 101: unless(grep(/^\Q$ip\E$/,@port_error)) { 102: push (@port_error,$ip); 103: } 104: } elsif ($return_status == 2) { 105: push(@{$command_error{$fw_chain}},$ip); 106: } elsif ($return_status == 0) { 107: push(@lond_port_open,$ip); 108: last; 109: } 110: } 111: } 112: } 113: } 114: } 115: if (@lond_port_curropen) { 116: unless (grep(/^\Q$port\E$/,@opened)) { 117: push(@opened,$port); 118: } 119: print "Port already open for ".scalar(@lond_port_curropen)." IP addresses\n"; 120: } 121: if (@lond_port_open) { 122: unless (grep(/^\Q$port\E$/,@opened)) { 123: push(@opened,$port); 124: } 125: print "Port opened for ".scalar(@lond_port_open)." IP addresses\n"; 126: } 127: if (@port_error) { 128: print "Error opening port for following IP addresses: ".join(', ',@port_error)."\n"; 129: } 130: if (keys(%command_error) > 0) { 131: foreach my $chain (sort(keys(%command_error))) { 132: if (ref($command_error{$chain}) eq 'ARRAY') { 133: if (@{$command_error{$chain}}) { 134: print "Bad command error opening port for following IP addresses: ". 135: join(', ',@{$command_error{$chain}})."\n". 136: 'Command was: "'."$iptables -I $chain -p tcp -s ".'$ip'." -d 0/0 --dport $port -j ACCEPT".'", where $ip is IP address'."\n"; 137: } 138: } 139: } 140: } 141: } else { 142: my (@port_errors,%command_errors); 143: foreach my $fw_chain (@okchains) { 144: my $firewall_command = 145: "$iptables -I $fw_chain -p tcp -d 0/0 --dport $port -j ACCEPT"; 146: system($firewall_command); 147: my $return_status = $?>>8; 148: if ($return_status == 1) { 149: push(@port_errors,$fw_chain); 150: } elsif ($return_status == 2) { 151: $command_errors{$fw_chain} = $firewall_command; 152: } elsif ($return_status == 0) { 153: push(@opened,$port); 154: last; 155: } 156: } 157: unless (grep(/^\Q$port\E$/,@opened)) { 158: if (@port_errors) { 159: print "Error opening port for chains: ". 160: join(', ',@port_errors).".\n"; 161: } 162: if (keys(%command_errors)) { 163: foreach my $fw_chain (sort(keys(%command_errors))) { 164: print "Bad command error opening port for chain: $fw_chain. Command was\n". 165: " ".$command_errors{$fw_chain}."\n"; 166: } 167: } 168: } 169: } 170: } 171: foreach my $port (@{$ports}) { 172: if (!grep(/^\Q$port\E$/,@opened)) { 173: return 'Required port not open: '.$port."\n"; 174: } 175: } 176: return 'ok'; 177: } 178: 179: sub firewall_is_port_open { 180: my ($iptables,$fw_chain,$port,$lond_port,$iphost,$curropen) = @_; 181: # for lond port returns number of source IPs for which firewall port is open 182: # for other ports returns 1 if the firewall port is open, 0 if not. 183: # 184: # check if firewall is active or installed 185: return if (! &firewall_is_active()); 186: my $count = 0; 187: if (open(PIPE,"$iptables -L $fw_chain -n |")) { 188: while(<PIPE>) { 189: if ($port eq $lond_port) { 190: if (ref($iphost) eq 'HASH') { 191: if (/^ACCEPT\s+tcp\s+\-{2}\s+(\S+)\s+\S+\s+tcp\s+dpt\:\Q$port\E/) { 192: my $ip = $1; 193: if ($iphost->{$ip}) { 194: $count ++; 195: if (ref($curropen) eq 'HASH') { 196: $curropen->{$ip} ++; 197: } 198: } 199: } 200: } 201: } else { 202: if (/tcp dpt\:\Q$port\E/) { 203: $count ++; 204: last; 205: } 206: } 207: } 208: close(PIPE); 209: } 210: return $count; 211: } 212: 213: sub firewall_is_active { 214: if (-e '/proc/net/ip_tables_names') { 215: return 1; 216: } else { 217: return 0; 218: } 219: } 220: 221: sub firewall_close_port { 222: my ($iptables,$fw_chains,$lond_port,$iphost,$ports) = @_; 223: return 'inactive firewall' if (!&firewall_is_active()); 224: return 'port number unknown' if !$lond_port; 225: return 'invalid firewall chain' unless (ref($fw_chains) eq 'ARRAY'); 226: my (@opened,@chains,@badchains,@okchains); 227: foreach my $chain (@{$fw_chains}) { 228: if ($chain =~ /^([\w\-]+)$/) { 229: push(@okchains,$1); 230: } else { 231: push(@badchains,$chain); 232: } 233: } 234: if (!@okchains) { 235: return 'None of the chain names has the expected format'."\n"; 236: } 237: if (ref($ports) ne 'ARRAY') { 238: return 'List of ports to close needed.'; 239: } 240: foreach my $portnum (@{$ports}) { 241: my $port = ''; 242: if ($portnum =~ /^(\d+)$/) { 243: $port = $1; 244: } else { 245: print "Skipped non-numeric port: $portnum\n"; 246: next; 247: } 248: print "Closing firewall access on port $port.\n"; 249: if (($port ne '') && ($port eq $lond_port)) { 250: my $output; 251: foreach my $fw_chain (@okchains) { 252: my (@port_error,@command_error,@lond_port_close); 253: my %to_close; 254: if (open(PIPE, "$iptables -n -L $fw_chain |")) { 255: while (<PIPE>) { 256: chomp(); 257: next unless (/dpt:\Q$port\E\s*$/); 258: if (/^ACCEPT\s+tcp\s+\-{2}\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+/) { 259: my $ip = $1; 260: my $keepopen = 0; 261: if (ref($iphost) eq 'HASH') { 262: if (exists($iphost->{$ip})) { 263: $keepopen = 1; 264: } 265: } 266: unless ($keepopen) { 267: $to_close{$ip} = $port; 268: } 269: } 270: } 271: close(PIPE); 272: } 273: if (keys(%to_close) > 0) { 274: foreach my $ip (keys(%to_close)) { 275: my $firewall_command = 276: "$iptables -D $fw_chain -p tcp -s $ip -d 0/0 --dport $port -j ACCEPT"; 277: system($firewall_command); 278: my $return_status = $?>>8; 279: if ($return_status == 1) { 280: push (@port_error,$ip); 281: } elsif ($return_status == 2) { 282: push(@command_error,$ip); 283: } elsif ($return_status == 0) { 284: push(@lond_port_close,$ip); 285: } 286: } 287: } 288: if (@lond_port_close) { 289: $output .= "Port closed for ".scalar(@lond_port_close)." IP addresses\n"; 290: } 291: if (@port_error) { 292: $output .= "Error closing port for following IP addresses: ".join(', ',@port_error)."\n"; 293: } 294: if (@command_error) { 295: $output .= "Bad command error opening port for following IP addresses: ". 296: join(', ',@command_error)."\n". 297: 'Command was: "'."$iptables -D $fw_chain -p tcp -s ".'$ip'." -d 0/0 --dport $port -j ACCEPT".'", where $ip is IP address'."\n"; 298: } 299: } 300: if ($output) { 301: print $output; 302: } else { 303: print "No IP addresses required discontinuation of access.\n"; 304: } 305: } else { 306: foreach my $fw_chain (@okchains) { 307: my (@port_error,@command_error,@lond_port_close); 308: my $to_close; 309: if (open(PIPE, "$iptables -n -L $fw_chain |")) { 310: while (<PIPE>) { 311: chomp(); 312: next unless (/dpt:\Q$port\E\s*$/); 313: $to_close = 1; 314: } 315: close(PIPE); 316: } 317: if ($to_close) { 318: my $firewall_command = 319: "$iptables -D $fw_chain -p tcp -d 0/0 --dport $port -j ACCEPT"; 320: system($firewall_command); 321: my $return_status = $?>>8; 322: if ($return_status == 1) { 323: # Error 324: print "Error closing port for chain: $fw_chain.\n"; 325: } elsif ($return_status == 2) { 326: # Bad command 327: print "Bad command error closing port. Command was\n". 328: " ".$firewall_command."\n"; 329: } else { 330: print "Port closed for chain $fw_chain.\n"; 331: } 332: } 333: } 334: } 335: } 336: return; 337: } 338: 339: sub firewall_close_anywhere { 340: my ($iptables,$fw_chain,$port) = @_; 341: if (open(PIPE, "$iptables --line-numbers -n -L $fw_chain |")) { 342: while (<PIPE>) { 343: next unless (/dpt:\Q$port\E/); 344: chomp(); 345: if (/^(\d+)\s+ACCEPT\s+tcp\s+\-{2}\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0/) { 346: my $firewall_command = "$iptables -D $fw_chain $1"; 347: system($firewall_command); 348: my $return_status = $?>>8; 349: if ($return_status == 1) { 350: print 'Error closing port '.$port.' for source "anywhere"'."\n"; 351: } elsif ($return_status == 2) { 352: print 'Bad command error closing port '.$port.' for source "anywhere". Command was'."\n". 353: ' '.$firewall_command."\n"; 354: } else { 355: print 'Port '.$port.' closed for source "anywhere"'."\n"; 356: } 357: } 358: } 359: close(PIPE); 360: } 361: } 362: 363: sub get_lond_port { 364: my $perlvarref=&LONCAPA::Configuration::read_conf(); 365: my $lond_port; 366: if (ref($perlvarref) eq 'HASH') { 367: if (defined($perlvarref->{'londPort'})) { 368: $lond_port = $perlvarref->{'londPort'}; 369: } 370: } 371: if (!$lond_port) { 372: print("Unable to determine lond port number from LON-CAPA configuration.\n"); 373: } 374: return $lond_port; 375: } 376: 377: sub get_fw_chains { 378: my ($iptables) = @_; 379: my $distro = &LONCAPA::distro(); 380: my @fw_chains; 381: my $suse_config = "/etc/sysconfig/SuSEfirewall2"; 382: my $ubuntu_config = "/etc/ufw/ufw.conf"; 383: if (-e $suse_config) { 384: push(@fw_chains,'input_ext'); 385: } else { 386: my @posschains; 387: if (-e $ubuntu_config) { 388: @posschains = ('ufw-user-input','INPUT'); 389: } else { 390: if ($distro =~ /^(debian|ubuntu|suse|sles)/) { 391: @posschains = ('INPUT'); 392: } else { 393: @posschains = ('RH-Firewall-1-INPUT','INPUT'); 394: } 395: if (!-e '/etc/sysconfig/iptables') { 396: if (!-e '/var/lib/iptables') { 397: unless ($distro =~ /^(debian|ubuntu)/) { 398: print("Unable to find iptables file containing static definitions\n"); 399: } 400: } 401: if ($distro =~ /^(fedora|rhes|centos|scientific)/) { 402: push(@fw_chains,'RH-Firewall-1-INPUT'); 403: } 404: } 405: } 406: if ($iptables eq '') { 407: $iptables = &get_pathto_iptables(); 408: } 409: my %counts; 410: if (open(PIPE,"$iptables -L -n |")) { 411: while(<PIPE>) { 412: foreach my $chain (@posschains) { 413: if (/(\Q$chain\E)/) { 414: $counts{$1} ++; 415: } 416: } 417: } 418: close(PIPE); 419: } 420: foreach my $fw_chain (@posschains) { 421: if ($counts{$fw_chain}) { 422: unless(grep(/^\Q$fw_chain\E$/,@fw_chains)) { 423: push(@fw_chains,$fw_chain); 424: } 425: } 426: } 427: } 428: return @fw_chains; 429: } 430: 431: sub get_pathto_iptables { 432: my $iptables; 433: if (-e '/sbin/iptables') { 434: $iptables = '/sbin/iptables'; 435: } elsif (-e '/usr/sbin/iptables') { 436: $iptables = '/usr/sbin/iptables'; 437: } else { 438: print("Unable to find iptables command\n"); 439: } 440: return $iptables; 441: } 442: 443: 1; 444: __END__ 445: 446: =pod 447: 448: =head1 NAME 449: 450: B<LONCAPA::Firewall> - dynamic opening/closing of firewall ports 451: 452: =head1 SYNOPSIS 453: 454: use lib '/home/httpd/lib/perl/'; 455: use LONCAPA::Firewall; 456: 457: LONCAPA::Firewall::firewall_open_port(); 458: LONCAPA::Firewall::firewall_close_port(); 459: LONCAPA::Firewall::firewall_is_port_open(); 460: LONCAPA::Firewall::firewall_is_active(); 461: LONCAPA::Firewall::firewall_close_anywhere(); 462: 463: =head1 DESCRIPTION 464: 465: The scripts: /etc/init.d/loncontrol, used to stop or start LON-CAPA services, 466: as well as the setuid script /home/httpd/perl/lciptables, called by loncron 467: for housekeeping tasks, make use of the methods provided by this module to 468: open and close firewall ports (currently the default port: 5663), used 469: for socket-based communication between LON-CAPA servers in the cluster 470: of networked servers to which the server belongs. 471: 472: The following methods are available: 473: 474: =over 4 475: 476: =item LONCAPA::Firewall::firewall_open_port( $iptables,$fw_chains,$lond_port,$iphost,$port ); 477: 478: =back 479: 480: =over 4 481: 482: =item LONCAPA::Firewall::firewall_close_port( $iptables,$fw_chains,$lond_port,$iphost,$ports ); 483: 484: =back 485: 486: =over 4 487: 488: =item LONCAPA::Firewall::firewall_is_port_open( $iptables,$fw_chain,$port,$lond_port,$iphost,$curropen ); 489: 490: =back 491: 492: =over 4 493: 494: =item LONCAPA::Firewall::firewall_is_active(); 495: 496: =back 497: 498: =over 4 499: 500: =item LONCAPA::Firewall::firewall_close_anywhere( $iptables,$fw_chain,$port ); 501: 502: =back 503: 504: =over 4 505: 506: =item LONCAPA::Firewall::get_lond_port(); 507: 508: =back 509: 510: =over 4 511: 512: =item LONCAPA::Firewall::get_fw_chains(); 513: 514: =back 515: 516: =over 4 517: 518: =item LONCAPA::Firewall::get_pathto_iptables(); 519: 520: 521: =head1 AUTHORS 522: 523: This library is free software; you can redistribute it and/or 524: modify it under the same terms as LON-CAPA itself. 525: 526: =cut 527: