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