# firewall-lib.pl
# Functions for iptables firewall configuration
#  - edits firewall settings in a single file
#  - each distro uses a different file for the settings
#  - may need to create init script to load firewall
#  - allow selection of firewall type, from os-specific list using os-specific
#    code (ie. redhat's low/medium/high security)
# XXX support special module option
# XXX support special module targets
# XXX what does no -j option mean?
# XXX have to load modules with -m when needed
# XXX where can ! be used?

do '../web-lib.pl';
&init_config();
do "$gconfig{'os_type'}-lib.pl";

if (!$firewall_rules_file) {
	# Use a webmin file for storing rules
	$firewall_rules_file = "$module_config_directory/firewall.rules";
	}

# list_rules()
# Returns a list of iptables rules from the firewall script
sub list_rules
{
@list_rules_cache = &parse_rules_file($firewall_rules_file)
	if (!defined(@list_rules_cache));
return @list_rules_cache;
}

# parse_rules_file(file)
sub parse_rules_file
{
local @rv;
local $lnum = 0;
open(FILE, $_[0]);
while(<FILE>) {
	s/\r|\n//g;
	s/#.*$//g;
	local $slnum = $lnum;
	while(/\\$/) {
		local $nl = <FILE>;
		s/\\$//;
		$nl =~ s/^\s+//;
		$_ .= $nl;
		$lnum++;
		}
	if (/^\s*(\S*iptables)(\s+.*)$/) {
		# Found an iptables rule
		local $rule = { 'file' => $_[0],
				'line' => $slnum,
				'eline' => $lnum,
				'index' => scalar(@rv),
				'type' => 0,
				'command' => $1,
				'args' => $2 };

		# Parse action args
		if ($rule->{'args'} =~ s/\s+-t\s+(\S+)//) {
			$rule->{'table'} = $1;
			}
		if ($rule->{'args'} =~ s/\s+(-A|-D|-N|--append|--delete|--new-chain)\s+(\S+)//) {
			$rule->{'chain'} = $2;
			$rule->{'action'} = $1 =~ /^-+(.)/ ? uc($1) : undef;
			}
		elsif ($rule->{'args'} =~ s/\s+(-R|-I|--replace|--insert)\s+(\S+)\s+(\S+)//) {
			$rule->{'chain'} = $2;
			$rule->{'num'} = $3;
			$rule->{'action'} = $1 =~ /^-+(.)/ ? uc($1) : undef;
			}
		elsif ($rule->{'args'} =~ s/\s+(-L|-F|-Z|-X|--list|--flush|--zero|--delete-chain)(\s+([^\-\s]\S*))?//) {
			$rule->{'chain'} = $3;
			$rule->{'action'} = $1 =~ /^-+(.)/ ? uc($1) : undef;
			}
		elsif ($rule->{'args'} =~ s/\s+(-P|--policy)\s+(\S+)\s+(\S+)//) {
			$rule->{'chain'} = $2;
			$rule->{'target'} = $3;
			$rule->{'action'} = $1 =~ /^-+(.)/ ? uc($1) : undef;
			}
		# XXX support -E option

		# Parse parameter args
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(-p|--protocol)\s+(\!?)\s*(\S+)//) {
			$rule->{'protocol'} = [ $1 || $3, uc($4) ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(-s|--source)\s+(\!?)\s*(\S+)//) {
			$rule->{'source'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(-d|--destination)\s+(\!?)\s*(\S+)//) {
			$rule->{'dest'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(-j|--jump)\s+(\S+)//) {
			$rule->{'jump'} = [ undef, $2 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(-i|--in-interface)\s+(\!?)\s*(\S+)//) {
			$rule->{'in'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(-o|--out-interface)\s+(\!?)\s*(\S+)//) {
			$rule->{'out'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(-f|--fragment)//) {
			$rule->{'fragment'} = [ $1 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--source-port)\s+(\!?)\s*(\S+)//) {
			$rule->{'sourceport'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s*(\!?)\s*(--destination-port)\s+(\!?)\s*(\S+)//) {
			$rule->{'destport'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--tcp-flags)\s+(\!?)\s*(\S+)\s+(\S+)//) {
			$rule->{'tcpflags'} = [ $1 || $3, $4, $5 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--syn)//) {
			$rule->{'syn'} = [ $1 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--tcp-option)\s+(\!?)\s*(\S+)//) {
			$rule->{'tcpoption'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--icmp-type)\s+(\!?)\s*(\S+)//) {
			$rule->{'icmptype'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--mac-source)\s+(\!?)\s*(\S+)//) {
			$rule->{'mac'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--limit)\s+(\!?)\s*(\S+)//) {
			$rule->{'limit'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--limit-burst)\s+(\!?)\s*(\S+)//) {
			$rule->{'limitburst'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--port)\s+(\!?)\s*(\S+)//) {
			$rule->{'port'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--uid-owner)\s+(\!?)\s*(\S+)//) {
			$rule->{'uidowner'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--gid-owner)\s+(\!?)\s*(\S+)//) {
			$rule->{'gidowner'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--sid-owner)\s+(\!?)\s*(\S+)//) {
			$rule->{'sidowner'} = [ $1 || $3, $4 ];
			}
		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--pid-owner)\s+(\!?)\s*(\S+)//) {
			$rule->{'pidowner'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--state)\s+(\!?)\s*(\S+)//) {
			$rule->{'state'} = [ $1 || $3, $4 ];
			}

		if ($rule->{'args'} =~ s/\s+(\!?)\s*(--tos)\s+(\!?)\s*(\S+)//) {
			$rule->{'tos'} = [ $1 || $3, $4 ];
			}

		push(@rv, $rule);
		}
	elsif (/^\s*if\s+/) {
		# Found an if block
		local $block = { 'file' => $_[0],
				 'line' => $slnum,
				 'eline' => $lnum,
				 'index' => scalar(@rv),
				 'type' => 2,
				 'text' => $_ };
		local $nest = 1;
		while(<FILE>) {
			s/\r|\n//g;
			$block->{'text'} .= "\n$_";
			if (/^\s*if\s+/) {
				$nest++;
				}
			elsif (/^\s*fi(\s|$)/ && !--$nest) {
				last;
				}
			}
		push(@rv, $block);
		}
	elsif (/^\s*(for|while)\s+/) {
		# Found a for block
		local $block = { 'file' => $_[0],
				 'line' => $slnum,
				 'eline' => $lnum,
				 'index' => scalar(@rv),
				 'type' => 2,
				 'text' => $_ };
		local $nest = 1;
		while(<FILE>) {
			s/\r|\n//g;
			$block->{'text'} .= "\n$_";
			if (/^\s*(for|while)\s+/) {
				$nest++;
				}
			elsif (/^\s*done(\s|$)/ && !--$nest) {
				last;
				}
			}
		push(@rv, $block);
		}
	elsif (/^\s*([^\s=]+)\s*=\s*"(.*)"$/ ||
	       /^\s*([^\s=]+)\s*=\s*'(.*)'$/ ||
	       /^\s*([^\s=]+)\s*=\s*(.*)$/) {
		# Found a variable assignment
		push(@rv, { 'file' => $_[0],
			    'line' => $slnum,
			    'eline' => $lnum,
			    'index' => scalar(@rv),
			    'type' => 1,
			    'name' => $1,
			    'value' => $2 });
		}
	elsif (/\S/) {
		# Some other unknown script line
		push(@rv, { 'file' => $_[0],
			    'line' => $slnum,
			    'eline' => $lnum,
			    'index' => scalar(@rv),
			    'type' => 2,
			    'text' => $_ });
		}
	$lnum++;
	}
close(FILE);
return @rv;
}

# describe_rule(&rule)
sub describe_rule
{
local $a = $_[0]->{'action'};
if ($a eq 'P') {
	return &text('desc_policy', $_[0]->{'target'});
	}
elsif ($a eq 'F') {
	return $text{'desc_flush'};
	}
elsif ($a eq 'L') {
	return $text{'desc_list'};
	}
elsif ($a eq 'Z') {
	return $text{'desc_zero'};
	}
elsif ($a eq 'N') {
	return $text{'desc_new'};
	}
elsif ($a eq 'X') {
	return $_[0]->{'chain'} ? $text{'desc_dchain'} : $text{'desc_dchains'};
	}
else {
	# Entry has a rule specification .. describe it
	local @c;
	foreach $d ('protocol', 'source', 'dest', 'in', 'out', 'fragment',
		    'sourceport', 'destport', 'tcpflags', 'syn', 'tcpoption',
		    'icmptype', 'mac', 'limit', 'limitburst', 'port',
		    'uidowner', 'gidowner', 'pidowner', 'sidowner',
		    'state', 'tos') {
		if ($_[0]->{$d}) {
			local ($n, @v) = @{$_[0]->{$d}};
			push(@c, &text("desc_$d$n", @v));
			}
		}
	local $desc;
	if (@c) {
		$desc = &text('desc_conds', join(" $text{'desc_and'} ", @c));
		}
	else {
		$desc = $text{'desc_always'};
		}
	if ($_[0]->{'jump'}) {
		$desc .= " ".($text{'desc_jump_'.lc($_[0]->{'jump'}->[1])} ||
		      &text('desc_jump', "<tt>$_[0]->{'jump'}->[1]</tt>"));
		}
	else {
		$desc .= " ".$text{'desc_nojump'};
		}
	return $desc;
	}
}


1;

