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