#!/usr/bin/perl

#
# TurtleFw: Turtle Firewall
#
# Software per la configurazione di un firewall linux (iptables)
#
#   2001/11/23 13:25:00
#
#======================================================================
# Copyright (c) 2001-2002 Friweb snc, Andrea Frigido <andrea@friweb.com>
# You may distribute under the terms of either the GNU General Public
# License
#======================================================================
#
# Changelog:
# 05-12-2001 v.0.91
#            - Modificato il nome da fwconf in Turtle Firewall (turtlefw)
#
# 10-01-2002 v.0.92
#            - Inserite le regole di accesso da/verso interfaccia lo
#              che precedentemente impedivano l'accesso a se stesso.
#            - Impostati i diritti sul file sh generato per l'esecuzione.
#            - Corretta la definizione delle lan nei file di configurazione
#              di esempio (samples).
# 19-03-2002 v.0.93
#            - Aggiunto l'uso del modulo turtlefirewall.pm (/usr/lib)
#
# 22-03-2002 v.0.94
#            - Aggiunto supporto dell'attributo ACTIVE delle rule.
# 02-04-2002 v.0.95
#            - Aggiunto il file setup al tarball.
# 17-04-2002 v.0.96
#            - Add webmin module languages files for English and Italian.
#            - Fix Masquerade and NAT bug.
# 17-05-2002 v.0.97
#            - Add franch webmin language file.
#            - Fix bugs.
# 23-05-2002 v.0.98	Giampaolo Tomassoni <g.tomassoni@ar-tel.it>
#            - Do you need port-based natting? Here it is...
#            - Fixed the I-Wanna-Reply-To-Pings-But-It-Doesn't bug: when
#              the fw accepts pings on a <somewere> => FIREWALL base,
#              don't turn the /proc/sys/net/ipv4/icmp_echo_ignore_all
#              kernel flag on...
#            - Applied few ahestetic make-ups
# 14-06-2002 v.0.99
#            - Fix turtlefirewall privileges bug.
#            - Use iptables from PATH (iptables directory need to be in PATH env. var.)
#            - PreLoad modules for ftp connections and NAT.
#            - Add CVS, NNTP services.
# 20-06-2002 v.1.00
#            - Change SystemV service start/stop order from 00/99 to 08/92.
#            - Change TurtleFirewall package file name.
#            - Check if XML::Parser perl module is installed.
#            - Add Telnet service.
# 26-06-2002 v.1.10
#            - Add description field for rules and items.
#            - Add experimental H.323 service.
#            - Fix bugs.
# 08-07-2002 v.1.11
#            - Setup procedure into webmin module, now Turtle Firewall installation is very easy.
#            - Removed chkconfig command for setup, it isn't availabe in all GNU/Linux distributions.
#            - Fix bug in "Create Nat" web interface.
#            - Other minor changes.
# 09-07-2002 v.1.12
#            - Fix bug in XML::Parser module checking.
# 03-09-2002 v.1.13
#            - Add NAT from a zone interface to a real host (etc. modem interface ip to my pc host).
#            - Add Redirect module (For Transparent Proxy).
#            - Fix security hole with INVALID packets filter code by Mark Francis.
#            - Enhanced Log.
#            - Add firewall rules for IPsec VPN service.
#            - Add firewall rules for Webmin service.
# 10-09-2002 v.1.14
#            - The configurable options contains now the option to select the logfile (Karl Lovink)
#            - The dutch language has been added (Karl Lovink).
# 13-09-2002 v.1.15
#            - Fix "DROP INVALID unclean" bug.
# 26-09-2002 v.1.16
#            - Change webmin category from System to Networking.
#            - Fix a bug on tcp/udp Local Redirection (Soep).
# 16-10-2002 v.1.17
#            - Fix bug with "--log-level info" iptables option.
#            - Enhanced log report.
#            - Enhanced interface.
#            - Add afp-over-tcp service: AFP (Apple Filing Protocol) over TCP.
#                  (Alain Terriault)
#            - Add nfs (experimental)
# 13-11-2002 v.1.18
#            - Add Firewall Configuration Options.
#            - Now you can change firewall rules order (more readable).
#            - Add fwuserdefservices.xml file for userdefined services. With this file you can write your own
#              services filter without changing official fwservices.xml file.
#              The structure of this new file is identical of fwservices.xml file structure.
#              If you write a service with a name used by fwservices.xml, this new service definition overwrite
#              the original service definition so, if you want, you can rewrite all services.
#              IMPORTANT: I invite all to send me your userdefined service filter definitions, so I can add them into
#              the predefined services list (fwservices.xml) for all Turtle Firewall users.
# 26-11-2002 v.1.19
#            - Fix bug in Zone deletion.
#            - Fix a bug using aliased interfaces (signaled by Torsten)
#            - Add German translation (Jimmy Collins)
#            - Add mysql and kazaa services (Jimmy Collins)
#            - Add pptp (vpn) and rdp services (Joe MacDonald)
#            - Add PC-Anyware service (Chris Carter)
#            - Change setup script for Slackware Linux distribution (A.Frigido, Patrik)
# 15-01-2003 v.1.20
#            - Add optional MAC address field in host edit form.
#            - Add target field (ACCEPT/DROP/REJECT) in rule edit form.
#            - Fix bug in Log prefix string, it must be up to 29 chars length.
#            - Add x11: X Window System service.
#            - Use numerical notation for ports in fwservices.xml.
#            - Add Active flag to NAT, Masquerade and Redirect rules.
# 16-01-2003 v.1.21
#            - Fix a bug in Redirection.
# 12-02-2003 v.1.22
#            - Firewall and NAT rules with multiple services.
#            - Change LOG prefix from "TFW DROP" to "TFW".
#            - Add --start, --stop and --status options to turtlefirewall main script.
#            - Add stop button in the webmin turtlefirewall index page.
#            - Translate error messages (english and italian).
#            - Add icmp_all service for all messages (request+reply).
#            - Add all icmp messages in the special service "all".
# 18-02-2003 v.1.23
#            - Add proxy, ssh21, dhcp, snmptrap, socks and eDonkey services (Karl Lovink).
#            - Fix a bug into log viewer (Fredrik Tuomas).
#            - Add Configuration Backup/Restore.
# 31-03-2003 v.1.24
#            - Change Turtle Firewall stop process, ping will be reenabled.
#            - Add AIM/ICQ and Soulseek std services (Frank Frster).
#            - Add Oracle, VNC, VNC-http services.
#            - Add rip, syslog, icecast, icp, irc (Karl Lovink).
#            - Local Redirection Improved.
#            - Now you can rename all firewall items.
#            - More options.
# 02-04-2003 v.1.25
#            - Fix bugs.
# 07-05-2003 v.1.26
#            - Fix "de" language file (Frank Frster).
#            - NAT Improved, now you can change rules order.
#            - NAT rules bugfix.
#            - Configuration backup download bugfix.
# 14-05-2003 v.1.27
#            - Small Bug-fix.


if( -f 'TurtleFirewall.pm' ) {
	require 'TurtleFirewall.pm';
} else {
	require '/usr/lib/TurtleFirewall.pm';
}

$firewall = new TurtleFirewall();

print "\nTurtle Firewall ".$firewall->Version()."\n";
print "Copyright 2001-2003 FriWeb snc - www.friweb.com (GPL)\n\n";


# parsing dei parametri
my $outputFile;
my $fwFile;
my $servicesFile;
my $userdefServicesFile;
foreach my $arg (@ARGV) {
	if( $arg =~ /^(-o|--output)\=(.*)/ ) {
		$outputFile = $2;
	} elsif( $arg =~ /^(-f|--firewall)\=(.*)/ ) {
		$fwFile = $2;
	} elsif( $arg =~ /^(-s|--services)\=(.*)/ ) {
		$servicesFile = $2;
	} elsif( $arg =~ /^--userdefservices\=(.*)/ ) {
		$userdefServicesFile = $2;
	} elsif( $arg eq '--start' ) {
		# Start Firewall (optional)
	} elsif( $arg eq '--stop' ) {
		# Stop Firewall
		firewallStop();
	} elsif( $arg eq '--status' ) {
		# Status
		firewallStatus();
	} else {
		if( $arg ne '--help' ) {
			print "Wrong parameters...\n";
		}

		print "Use: turtlefirewall [--start|--stop|--status] [--output=outputfile] [--firewall=firewallfile] [--services=servicesfile] [--userdefservices=file]\n";
		print "or turtlefirewall [--start|--stop|--status] [-o=outputfile] [-f=firewallfile] [-s=servicesfile]\n";
		print "Example: turtlefirewall --start -o=fw.sh -f=fw.xml -s=services.xml\n";
		exit(1);
	}
}

if( $fwFile eq '' )	  { $fwFile = "/etc/turtlefirewall/fw.xml"; }
if( $servicesFile eq '' ) { $servicesFile = "/etc/turtlefirewall/fwservices.xml"; }
if( $userdefServicesFile eq '' ) { $userdefServicesFile = "/etc/turtlefirewall/fwuserdefservices.xml"; }

#===================================
# Carico le regole del firewall

$firewall->LoadFirewall( $fwFile );
$firewall->LoadServices( $servicesFile, $userdefServicesFile );

%fw = %{$firewall->{fw}};
%fwItems = %{$firewall->{fwItems}};
%services = %{$firewall->{services}};




# Apro il file script
if( $outputFile ne '' ) {
	open( SCRIPTFILE, ">$outputFile" ) or die("Errore generando lo script file $outputFile!");
}

# Log limit parameters (default)
$log_limit=60;
$log_limit_burst=5;

# inizializzazione dello script
initScript();

# Creo le catene delle ZONE
comment( "#======================================" );
comment( "# Definizione delle catene delle zone." );
comment( "#" );
@zone = $firewall->GetZoneList();
for($i=0; $i<=$#zone; $i++ ) {
	my $z1 = $zone[$i];
	my %zone1 = $firewall->GetZone($z1);
	for($j=0; $j<=$#zone; $j++ ) {
		my $z2 = $zone[$j];
		my %zone2 = $firewall->GetZone($z2);
		if( $z1 eq 'FIREWALL' || $z2 eq 'FIREWALL' ) {
			# Definisco le catene per i pacchetti che hanno come destinazione od
			# origine lo stesso firewall.
			# Notare che escludo la coppia FIREWALL -> FIREWALL
			if( $z1 eq 'FIREWALL' && $z2 ne 'FIREWALL' ) {
				comment( "# Definizione catena $z1 -> $z2" );
				command( "iptables -N $z1-$z2" );
				command( "iptables -A OUTPUT -o \"".$zone2{'IF'}."\" -j $z1-$z2\n" );
			}
			if( $z1 ne 'FIREWALL' && $z2 eq 'FIREWALL' ) {
				comment( "# Definizione catena $z1 -> $z2" );
				command( "iptables -N $z1-$z2" );
				command( "iptables -A INPUT -i ".$zone1{'IF'}." -j $z1-$z2\n" );
			}
		} else {
			comment( "# Definizione catena $z1 -> $z2" );
			command( "iptables -N $z1-$z2" );
			command( "iptables -A FORWARD -i ".$zone1{'IF'}." -o ".$zone2{'IF'}." -j $z1-$z2\n" );
		}
	}
}

# NAT
comment( "#=====================================" );
comment( "# NAT" );
for( $i=1; $i <= $firewall->GetNatsCount(); $i++ ) {
	applyNat( $firewall->GetNat($i) );
}

# MASQUERADE (sempre dopo il NAT)
comment( "#=====================================" );
comment( "# Masquerading" );
for( $i=1; $i <= $firewall->GetMasqueradesCount(); $i++ ) {
	applyMasquerade( $firewall->GetMasquerade($i) );
}

# REDIRECT
comment( "#=====================================" );
comment( "# REDIRECT" );
my $redirectCount = $firewall->GetRedirectCount();
if( $redirectCount > 0 ) {
	# Add REDIR chain
	command( "iptables -t nat -N REDIR" );
	command( "iptables -t nat -A PREROUTING -j REDIR" );
	for( $i=1; $i <= $firewall->GetRedirectCount(); $i++ ) {
		applyRedirect( $firewall->GetRedirect($i) );
	}
	# close the REDIR chain with a RETURN to the PREROUTING parent chain
	command( "iptables -t nat -A REDIR -j RETURN" );
}

# Applicazione delle RULEs
comment( "#=====================================" );
comment( "# Regole di forwarding." );
for( $i=1; $i <= $firewall->GetRulesCount(); $i++ ) {
	applyRule( $firewall->GetRule($i) );
}

# chiudo le catene delle zone
comment( "" );
comment( "#=====================================" );
comment( "# Chiusura di tutte le catene con relativo log\n" );
for($i=0; $i<=$#zone; $i++ ) {
	$z1 = $zone[$i];
	for($j=0; $j<=$#zone; $j++ ) {
		$z2 = $zone[$j];
		if( $z1 ne 'FIREWALL' || $z2 ne 'FIREWALL' ) {
			my $logprefix = "TFW $z1-$z2";
			if( length($logprefix) > 28 ) {
				# iptables need log-prefix strings up to 29 chars length (with char ":")
				$logprefix = substr( $logprefix, 0, 28 );
			}
			comment( "# Chiusura catena $z1 -> $z2" );
			command( "iptables -A $z1-$z2 -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"$logprefix:\"" );
			command( "iptables -A $z1-$z2 -j DROP\n" );
		}
        }
}

if( $outputFile ne '' ) {
	close( SCRIPTFILE );
	# set privileges
	chmod 0700, $outputFile;
	print "\nFile $outputFile generated.\n\n";
}

print "DENY any other connections\n\n";

# FINE :)




sub firewallStop {
	#
	# Stop the firewall, allow all connections.
	#
	command(
		"iptables -F\n".
		"iptables -X\n".
		"iptables -t nat -F\n".
		"iptables -t nat -X\n".
		"iptables -P INPUT ACCEPT\n".
		"iptables -P OUTPUT ACCEPT\n".
		"iptables -P FORWARD ACCEPT\n" );
	# enable ping
	command( "echo \"0\" >/proc/sys/net/ipv4/icmp_echo_ignore_all\n" );
	print "Firewall Stopped.\n\n";
	exit( 0 );
}

sub firewallStatus {
	#
	# Print "Firewall ON" and return 0 or "Fireall OFF" and return 1
	#
	if( $firewall->GetStatus() ) {
		print "Firewall ON\n\n";
		exit( 0 );
	} else {
		print "Firewall OFF\n\n";
		exit( 1 );
	}
}

# Inizializzazione del file script, genera le catene BACK e ICMP-ACC
sub initScript {
	comment(
		"#!/bin/sh\n\n".
		"##################################################\n".
		"# Firewall Script\n".
		"# Generato da turtlefirewall v.".$firewall->Version()." (".localtime().")\n".
		"##################################################\n"
	);

	# PreLoad modules for ftp connections and NAT
	command("modprobe ip_tables >& /dev/null");
	command("modprobe ip_conntrack >& /dev/null");
	command("modprobe ip_conntrack_ftp >& /dev/null");
	command("modprobe ip_nat_ftp >& /dev/null");

	command("# pongo la politica di filtring per il forward a DROP\n".
		"iptables -P FORWARD DROP\n".
		"iptables -P INPUT DROP\n".
		"iptables -P OUTPUT DROP\n\n".
		"# cancello tutte le regole ed eventuali catene\n".
		"iptables -F\n".
		"iptables -X\n\n".
		"iptables -t nat -F\n".
		"iptables -t nat -X\n\n".
		"# abilito l'accesso da/verso l'interfaccia lo\n".
		"iptables -A INPUT -i lo -j ACCEPT\n".
		"iptables -A OUTPUT -o lo -j ACCEPT\n\n".
		"# Abilitiamo l'IP forwarding\n".
		"echo \"1\" >/proc/sys/net/ipv4/ip_forward\n" );

	comment("# per respingere pacchetti che arrivano da interfacce impreviste\n");
	if( $firewall->{fw}{OPTION}{rp_filter} eq 'unchange' ) {
		print "rp_filter: unchange\n";
	} else {
		my $flag;
		if( $firewall->{fw}{OPTION}{rp_filter} eq 'off' ) {
			print "rp_filter: off\n";
			$flag = 0;
		} else {
			print "rp_filter: on\n";
			$flag = 1;
		}
		command("for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo $flag > ".'$f'."; done\n\n" );
	}

	if( $firewall->{fw}{OPTION}{log_martians} eq 'unchange' ) {
		print "log_martians: unchange\n";
	} else {
		my $flag;
		if( $firewall->{fw}{OPTION}{log_martians} eq 'off' ) {
			print "log_martians: off\n";
			$flag = 0;
		} else {
			print "log_martians: on\n";
			$flag = 1;
		}
		command( "# Log spoofed packets, source routed packets, redirect packets.\n".
			"for f in /proc/sys/net/ipv4/conf/*/log_martians; do echo $flag > ".'$f'."; done\n" );
	}

# Begin GT-20020525
# Following had changed: pings are ignored iff no ping acceptance rule is defined
# for the 'FIREWALL' destination.
#	command( "# Disable response to ping.\n".
#		"/bin/echo \"1\" > /proc/sys/net/ipv4/icmp_echo_ignore_all\n" );

	# I really miss the point: why should we turn the icmp-echo engine off?
	# If one wants maximum protection against malformed pings, not defining
	# any ping acceptance rule isn't enough?
	# However,the following code scans the rules tree searching for an
	# icmp-echo-reply acceptance rule having FIREWALL as a source. Please
	# note that we enable the icmp-echo engine even if these rules are
	# disabled, since I prefer not to have any surprising behaviour when,
	# under attach, I decide to disable my ping rules: if I'm looking what's
	# going on from inside my router, I could get a wrong idea seeing no
	# replies from my interfaces...
	#  GT-20020525

	if( doWeNeedEchoReplies() ) {
		command(
			"# Disable broadcast icmp-replies.\n".
			"# You don't want yourself becoming a Smurf amplifier.\n".
			"echo \"1\" >/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts\n".
			"\n".
			"# Enables icmp-reply engine (rules ask for it)\n".
			"echo \"0\" >/proc/sys/net/ipv4/icmp_echo_ignore_all\n"
		);
	} else {
		command(
			"# Disables icmp-reply engine\n".
			"echo \"1\" >/proc/sys/net/ipv4/icmp_echo_ignore_all\n"
		);
	}
	# End GT-20020525

	command( "# Don't accept source routed packets. Attackers can use source routing to generate\n".
		"# traffic pretending to be from inside your network, but which is routed back along\n".
		"# the path from which it came, namely outside, so attackers can compromise your\n".
		"# network. Source routing is rarely used for legitimate purposes.\n".
		"for f in /proc/sys/net/ipv4/conf/*/accept_source_route; do echo 0 > ".'$f'."; done\n" );

	command( "# Disable ICMP redirect acceptance. ICMP redirects can be used to alter your routing\n".
		"# tables, possibly to a bad end.\n".
		"for f in /proc/sys/net/ipv4/conf/*/accept_redirects; do echo 0 > ".'$f'."; done\n" );

	command( "# Enable bad error message protection.\n".
		"echo 1 > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses\n" );

	############################################
	# INVALID Packets filter by Mark Francis
	# Drop INVALID packets for INPUT, OUTPUT and FORWARD chains.
	command( "### Log invalid packets then drop packets\n#\n" );
	command("iptables -N INVALID" );
	command("iptables -N CHECK_INVALID");

	if( $firewall->{fw}{OPTION}{drop_unclean} eq 'on' ) {
		# 13-09-2002 It doesn't work, wait stable version of unclean module (Andrea Frigido)
		command("# This next rule is marked experimental but does not appear to block legitimite traffic\n".
			"iptables -A CHECK_INVALID -m unclean -j INVALID");
		command("iptables -A INVALID -m unclean ".
			" -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID unclean:\"" );
		print "drop_unclean: on\n";
	} else {
		print "drop_unclean: off\n";
	}

	print "drop_invalid_state: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_state} ne 'off' ) {
		command("iptables -A CHECK_INVALID -m state --state INVALID            -j INVALID");
		command("iptables -A INVALID -m state --state INVALID ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID STATE:\"");
		print "on\n";
	} else {
		print "off\n";
	}

	print "drop_invalid_all: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_all} ne 'off' ) {
		command("iptables -A CHECK_INVALID -p tcp --tcp-flags ALL ALL          -j INVALID");
		command("iptables -A INVALID -p tcp --tcp-flags ALL ALL ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID ALL:\"");
		print "on\n";
	} else {
		print "off\n";
	}

	print "drop_invalid_none: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_none} ne 'off' ) {
		command("iptables -A CHECK_INVALID -p tcp --tcp-flags ALL NONE         -j INVALID");
		command("iptables -A INVALID -p tcp --tcp-flags ALL NONE ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID NONE:\"");
		print "on\n";
	} else {
		print "off\n";
	}

	print "drop_invalid_fin_notack: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_fin_notack} ne 'off' ) {
		command("iptables -A CHECK_INVALID -p tcp --tcp-flags FIN,ACK FIN      -j INVALID");
		command("iptables -A INVALID -p tcp --tcp-flags FIN,ACK FIN ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID FIN,!ACK:\"");
		print "on\n";
	} else {
		print "off\n";
	}

	print "drop_invalid_sys_fin: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_syn_fin} ne 'off' ) {
		command("iptables -A CHECK_INVALID -p tcp --tcp-flags SYN,FIN SYN,FIN  -j INVALID");
		command("iptables -A INVALID -p tcp --tcp-flags SYN,FIN SYN,FIN ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID SYN,FIN:\"");
		print "on\n";
	} else {
		print "off\n";
	}

	print "drop_invalid_syn_rst: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_syn_rst} ne 'off' ) {
		command("iptables -A CHECK_INVALID -p tcp --tcp-flags SYN,RST SYN,RST  -j INVALID");
		command("iptables -A INVALID -p tcp --tcp-flags SYN,RST SYN,RST ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID SYN,RST:\"");
		print "on\n";
	} else {
		print "off\n";
	}

	print "drop_invalid_fragment: ";
	if( $firewall->{fw}{OPTION}{drop_invalid_fragment} ne 'off' ) {
		command("iptables -A CHECK_INVALID -f                                  -j INVALID");
		command("iptables -A INVALID -f ".
			"    -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID fragment:\"" );
		print "on\n";
	} else {
		print "off\n";
	}

	command("iptables -A CHECK_INVALID -j RETURN\n" );
	command("# Log all invalid then drop\n".
		"iptables -A INVALID -m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"TFW INVALID PACKET:\"\n".
		"iptables -A INVALID -j DROP"
		);

	command( "iptables -A INPUT -j CHECK_INVALID" );
	command( "iptables -A OUTPUT -j CHECK_INVALID" );
	command( "iptables -A FORWARD -j CHECK_INVALID" );
	# END of INVALID Packets filter by Mark Francis
	############################################

	####
	# Other options
	if( $firewall->{fw}{OPTION}{ip_conntrack_max} > 0 ) {
		open( FILE, ">/proc/sys/net/ipv4/ip_conntrack_max" );
		print FILE $firewall->{fw}{OPTION}{ip_conntrack_max};
		close FILE;
		print "ip_conntrack_max: ",$firewall->{fw}{OPTION}{ip_conntrack_max},"\n";
	}

	if( $firewall->{fw}{OPTION}{log_limit} > 0 ) {
		$log_limit = $firewall->{fw}{OPTION}{log_limit};
		print "log_limit: $log_limit\n";
	}
	if( $firewall->{fw}{OPTION}{log_limit_burst} > 0 ) {
		$log_limit_burst = $firewall->{fw}{OPTION}{log_limit_burst};
		print "log_limit_burst: $log_limit_burst\n";
	}


	# Definizione della catena di ritorno
	command( "# Chain dei pacchetti di ritorno (NO nuove connessioni)\n".
		"iptables -N BACK\n".
		"iptables -A BACK -m state --state ESTABLISHED,RELATED -j ACCEPT\n".
		"iptables -A BACK -j RETURN\n" );

	# Definizione della catena ICMP-ACC
	command( "# Chain per la gestione degli errori standard ICMP\n".
		"iptables -N ICMP-ACC\n".
		"iptables -A ICMP-ACC -p icmp --icmp-type destination-unreachable -j ACCEPT\n".
		"iptables -A ICMP-ACC -p icmp --icmp-type source-quench -j ACCEPT\n".
		"iptables -A ICMP-ACC -p icmp --icmp-type time-exceeded -j ACCEPT\n".
		"iptables -A ICMP-ACC -p icmp --icmp-type parameter-problem -j ACCEPT\n".
		"iptables -A ICMP-ACC -j RETURN\n" );
}

sub doWeNeedEchoReplies {
	foreach my $r (@{$firewall->{fw}{RULE}}) {
		if( $r->{DST} eq 'FIREWALL' ) {
			# Check if service defines icmp-echo-repply on the 'back' way.
			foreach my $f (@{$services{$r->{SERVICE}}{FILTERS}}) {
				if(
					$f->{DIRECTION} eq 'back'	&&
					$f->{P} eq 'icmp'		&&
					$f->{ICMPTYPE} eq 'echo-reply'
				) {
					return(1);
				}
			}
		}
	}

	return(0);
}

# Applica una regola di Natting
sub applyNat {
	my %nat		= @_;

	if( $nat{ACTIVE} eq 'NO' ) {
		return;
	}

	my $virtual	= $nat{VIRTUAL};
	my $real	= $nat{REAL};
	my $nmService	= $nat{SERVICE};
	my $port	= $nat{PORT};			# Optional port identifier
	my $virtual_ip='';
	my $virtual_if='';
	my $real_ip='';

	# service is a list of services?
	if( $nmService =~ /,/ ) {
		my @services = split( /,/, $nmService );
		my %newnat = %nat;
		foreach my $serv (@services) {
			$newnat{SERVICE} = $serv;
			applyNat( %newnat );
		}
		return;
	}

	if( $virtual eq '' ) {
		print STDERR "Error: VIRTUAL attribute missing in NAT rule definition.";
		return;
	}
	if( $real eq '' ) {
		print STDERR "Error: REAL attribute missing in NAT rule definition.";
		return;
	}

	if( $fwItems{$virtual} ne 'HOST' && $fwItems{$virtual} ne 'ZONE' ) {
		print STDERR "Error: in a NAT rule definition, VIRTUAL attribute is not a valid host or zone name.";
		return;
	}
	if( $fwItems{$virtual} eq 'HOST' ) {
		$virtual_ip = $fw{HOST}{$virtual}{IP};
	}
	if( $fwItems{$virtual} eq 'ZONE' ) {
		$virtual_if = $fw{ZONE}{$virtual}{IF};
	}

	if( $fwItems{$real} ne 'HOST' ) {
		print STDERR "Error: in a NAT rule definition, REAL attribute is not a valid host name.";
		return;
	}
	$real_ip = $fw{HOST}{$real}{IP};

	if( $nmService eq '' || $nmService eq 'all' ) {
		# Interface-wide nat. This was the only way natting was used to be.
		if( $virtual_ip ne '' ) {
			# Virtual HOST to Real HOST nat
			print "NAT virtual( $virtual ) --> real( $real )\n";
			command( "#NAT virtual( $virtual ) -to-> real( $real )" );
			command( "iptables -t nat -A PREROUTING -d $virtual_ip -j DNAT --to-destination $real_ip" );
			command( "iptables -t nat -A POSTROUTING -s $real_ip -j SNAT --to-source $virtual_ip\n" );
		} else {
			# ZONE interface to Real HOST nat
			print "NAT from zone( $virtual ) --> real( $real )\n";
			command( "#NAT from zone ( $virtual ) -to-> real( $real )" );
			command( "iptables -t nat -A PREROUTING -i $virtual_if -j DNAT --to-destination $real_ip" );
			command( "iptables -t nat -A POSTROUTING -s $real_ip -o $virtual_if -j MASQUERADE\n" );
		}
	} else {
		# Service-wide nat. This was introduced with v0.98.
		# On the 'go' way of the specified service we do a DNAT from $virtual_ip:$dport
		# to $real_ip:$dport, while on the 'back' way we do a SNAT from $real_ip:$sport
		# to $virtual_ip:$sport. $state conditions and $jump tags are added to the iptable
		# entries as well.
		print "NAT virtual( $virtual ) --> real( $real ) on service( $nmService".
				($port ne '' ? "($port)" : '')." )\n";
		comment( "#NAT virtual( $virtual ) -to-> real( $real ) on service( $nmService($port) )" );

		# Outputs a nat roule for each defined service channel
		foreach my $filter (@{$services{$nmService}{FILTERS}}) {
			my $direction	= $filter->{DIRECTION};
			my $proto	= $filter->{P};
			my $icmptype	= $filter->{ICMPTYPE};
			my $sport	= $filter->{SPORT};
			my $dport	= $filter->{DPORT};
			my $state	= $filter->{STATE};

			# Fetches
			if( $sport eq 'PORT' ) { $sport = $port; }
			if( $dport eq 'PORT' ) { $dport = $port; }

			# Basic command skeleton
			my $cmd = 'iptables ';
			$cmd .=	'-t nat ';
			$cmd .= (
				$direction eq 'go' ?
					( $virtual_ip ne '' ?
						"-A PREROUTING -d $virtual_ip "
					:
						"-A PREROUTING -i $virtual_if "
					)
				:
					"-A POSTROUTING -s $real_ip "
			);

			# Add protocol filter if the service defines it
			if( $proto eq 'tcp' || $proto eq 'udp' ) {
				$cmd .= "-p $proto ";
				#$cmd .= ( $direction eq 'go' ? "--dport $dport " : "--sport $sport " );
				if( $dport ne '' ) { $cmd .= "--dport $dport "; }
				if( $sport ne '' ) { $cmd .= "--sport $sport "; }
			} elsif( $proto ne 'icmp' ) {
				# Well, I'm coding this... But what purpouse is supposed
				# to have an icmp nat? Mmmmm...
				$cmd .= "-p $proto ";

				if( $icmptype ne '' ) {
					$cmd .= "--icmp-type $icmptype ";
				}
			} elsif( $proto ne '' ) {
				print "  a nat on protocol \"$proto\" had been disregarded.\n";
				next;
			}

			# Add state-related rule
			if( $state ne '' ) {
				$cmd .= "-m state --state $state ";
			}

			# Destination/source mangling
			$cmd .= (
				$direction eq 'go' ?
					"-j DNAT --to-destination $real_ip"
				:
					( $virtual_ip ne '' ?
						"-j SNAT --to-source $virtual_ip"
					:
						"-o $virtual_if -j MASQUERADE"
					)
			);

			# Finally, executes the command
			command( $cmd );
		}
	}
}

# Applica una regola di mascheramento
sub applyMasquerade {
	%masq = @_;

	if( $masq{ACTIVE} eq 'NO' ) {
		return;
	}

	my $zone = $masq{ZONE};

	if( $zone eq '' ) {
		print STDERR "Error: ZONE attribute missing in MASQUERADE rule.";
		return
	}

	if( $fwItems{$zone} ne 'ZONE' ) {
		print STDERR "Error: invalid ZONE attribute missing in MASQUERADE rule.";
		return
	}

	my $if = $fw{ZONE}{$zone}{IF};

	print "MASQUERADE to zone $zone\n";
	command( "# MASQUERADE to zone $zone" );
	command( "iptables -t nat -A POSTROUTING -o $if -j MASQUERADE\n" );
}

# Apply Redirect rule
sub applyRedirect {
	my %redirect = @_;

	if( $redirect{ACTIVE} eq 'NO' ) {
		return;
	}

	# Redirect or don't redirect?
	my $is_redirect = $redirect{REDIRECT} ne 'NO';

	my $src = $redirect{SRC};
	my $dst = $redirect{DST};

	# Vedo se come sorgente ho un group
	if( $fwItems{$src} eq 'GROUP' ) {
		my %newredirect = %redirect;
		foreach my $item ( @{$fw{GROUP}{$src}{ITEMS}} ) {
			if( $item ne 'FIREWALL' ) {
				$newredirect{SRC} = $item;
				applyRedirect( %newredirect );
			}
		}
		return;
	}

	# Vedo se come destinazione ho un group
	if( $fwItems{$dst} eq 'GROUP' ) {
		my %newredirect = %redirect;
		foreach my $item ( @{$fw{GROUP}{$dst}{ITEMS}} ) {
			# Ignore ZONE items (PREROUTING don't accept -o option
			if( $item ne 'FIREWALL' && $fw{ZONE}{$item}{IF} eq '' ) {
				$newredirect{DST} = $item;
				applyRedirect( %newredirect );
			}
		}
		return;
	}

	# Definisco il SERVICE
	my $service = $redirect{SERVICE};
	my $port = $redirect{PORT};
	my $toport = $redirect{TOPORT};

	my ($src_zone, $src_peer, $src_mac) = expand_item( $src );
	my %src_zone_attr = $firewall->GetZone( $src_zone );
	my $src_if = $src_zone_attr{IF};

	my $dst_zone;
	my $dst_peer;
	my $dst_if;
	if( $dst eq '*' ) {
		$dst_zone = '*';
		$dst_peer = '0.0.0.0/0';
		$dst_if = '';
	} else {
		($dst_zone, $dst_peer) = expand_item( $dst );
		my %dst_zone_attr = $firewall->GetZone( $dst_zone );
		$dst_if = $dst_zone_attr{IF};
	}

	print $is_redirect ? '' : 'NOT ',"REDIRECT ( service $service";
	if( $service eq 'tcp' || $service eq 'udp' ) { print "($port)"; }
	print " $src";
	if( $src_mac ne '' ) { print "(mac:$src_mac)"; }
	print " --> $dst )";
	if( $is_redirect ) {
		 print " TO LOCAL PORT $toport";
	}
	print "\n";

	#comment( "" );
	#comment( "# redirect $service: $src --> $dst  ($src_peer -> $dst_peer) [$src_zone -> $dst_zone]" );

	# Creo le 2 catene di andata e ritorno.
	applyServiceRedirect( $service, $src_if, $src_peer, $src_mac, $dst_if, $dst_peer, $port, $toport, $is_redirect);
}

sub applyServiceRedirect {
	my ($serviceName, $src_if, $src_peer, $src_mac, $dst_if, $dst_peer, $port, $toport, $is_redirect) = @_;

	my %service = %{$services{$serviceName}};

	# ciclo sulle regole di filering
	for( my $i = 0; $i <= $#{$service{FILTERS}}; $i++ ) {

		my %filter = %{$service{FILTERS}[$i]};

		my $direction = $filter{DIRECTION};
		my $p = $filter{P};
		my $icmptype = $filter{ICMPTYPE};
		my $sport = $filter{SPORT};
		my $dport = $filter{DPORT};
		my $state = $filter{STATE};
		my $jump = $filter{JUMP};

		# I only use the first tcp/udp filter rule
		if( $direction eq 'go' && ($p eq 'tcp' || $p eq 'udp' || $p eq '') &&
		    ($filter{JUMP} eq '' || $filter{JUMP} eq 'ACCEPT') ) {

			if( $dport eq 'PORT' ) {
				$dport = $port;
			}

			my $cmd = "iptables -t nat -A REDIR ";
			if( $src_if ne '' ) { $cmd .= "-i $src_if "; }
			if( $src_peer ne '0.0.0.0/0' ) { $cmd .= "-s $src_peer "; }
			if( $src_mac =~ /^[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}$/ ) {
				$cmd .= "-m mac --source-mac $src_mac ";
			}

			# iptables prerouting chain don't accept -o option.
			#if( $dst_if ne '' ) { $cmd .= "-o $dst_if "; }
			if( $dst_peer ne '0.0.0.0/0' ) { $cmd .= "-d $dst_peer "; }

			if( $p ne '' ) {
				$cmd .= "-p $p ";
			} else {
				$cmd .= "-p * ";
			}

			#if( $icmptype ne '' ) { $cmd .= "--icmp-type $icmptype "; }
			if( $sport ne '' ) { $cmd .= "--sport $sport "; }
			if( $dport ne '' ) { $cmd .= "--dport $dport "; }
			if( $state ne '' ) { $cmd .= "-m state --state $state "; }

			if( $is_redirect ) {
				if( $toport eq '' ) {
					$cmd .= "-j REDIRECT";
				} else {
					$cmd .= "-j REDIRECT --to-port $toport";
				}
			} else {
				# Don't redirect and return to parent chain
				$cmd .= "-j RETURN";
			}

			if( $p ne '' ) {
				command( $cmd );
			} else {
				# I must explode '-p *' in -p tcp e -p udp
				$cmd =~ s/ \-p \*/ -p tcp/;
				command( $cmd );
				$cmd =~ s/ \-p tcp/ -p udp/;
				command( $cmd );
			}
		}
	}
}


# Applica una regola di filtro del firewall
sub applyRule {
	my %rule = @_;

	if( $rule{ACTIVE} eq 'NO' ) {
		return;
	}

	my $src = $rule{SRC};
	my $dst = $rule{DST};
	my $target = $rule{TARGET};


	#print "Source=$src, Destination=$dst\n";

	# Vedo se come sorgente ho un group
	if( $fwItems{$src} eq 'GROUP' ) {
		my %newrule = %rule;
		foreach my $item ( @{$fw{GROUP}{$src}{ITEMS}} ) {
			$newrule{SRC} = $item;
			applyRule( %newrule );
		}
		return;
	}

	# Vedo se come destinazione ho un group
	if( $fwItems{$dst} eq 'GROUP' ) {
		my %newrule = %rule;
		foreach my $item ( @{$fw{GROUP}{$dst}{ITEMS}} ) {
			$newrule{DST} = $item;
			applyRule( %newrule );
		}
		return;
	}

	# Definisco il SERVICE
	my $service = $rule{SERVICE};
	my $port = $rule{PORT};

	# service is a list of services?
	if( $service =~ /,/ ) {
		my @services = split( /,/, $service );
		my %newrule = %rule;
		foreach my $serv (@services) {
			$newrule{SERVICE} = $serv;
			applyRule( %newrule );
		}
		return;
	}

	my ($src_zone, $src_peer, $src_mac) = expand_item( $src );
	my ($dst_zone, $dst_peer) = expand_item( $dst );

	if( $target=~ /DROP|REJECT/ ) {
		print "$target $service";
	} else {
		print "ALLOW $service";
	}
	if( $service eq 'tcp' || $service eq 'udp' ) { print "($port)"; }
	print " $src";
	if( $src_mac ne '' ) { print "(mac:$src_mac)"; }
	print " --> $dst\n";

	#command( "" );
	#comment( "# service $service: $src --> $dst  ($src_peer -> $dst_peer) [$src_zone -> $dst_zone]" );

	# Creo le 2 catene di andata e ritorno.
	my $andata = "$src_zone-$dst_zone";
	my $ritorno = "$dst_zone-$src_zone";
	applyService( $service, $andata, $ritorno, $src_peer, $src_mac, $dst_peer, $port, $target);
}

# Applica un servizio
sub applyService {
	my( $serviceName, $goChain, $backChain, $src, $src_mac, $dst, $port, $target ) = @_;

	%service = %{$services{$serviceName}};

	# commento del servizio
	comment( "# $serviceName: ".$service{DESCRIPTION} );

	# ciclo sulle regole di filering
	my $i;
	for( $i = 0; $i <= $#{$service{FILTERS}}; $i++ ) {

		%filter = %{$service{FILTERS}[$i]};

		my $direction = $filter{DIRECTION};
		my $p = $filter{P};
		my $icmptype = $filter{ICMPTYPE};
		my $sport = $filter{SPORT};
		my $dport = $filter{DPORT};
		my $state = $filter{STATE};
		my $jump = $filter{JUMP};

		if( $target =~ /DROP|REJECT/ && $direction ne 'go' ) {
			# Don't process Back filters
			next;
		}

		# porta impostata dalla regola del firewall
		if( $sport eq 'PORT' ) {
			$sport = $port;
		}
		if( $dport eq 'PORT' ) {
			$dport = $port;
		}

		my $cmd;
		if( $direction eq 'go' ) {
			$cmd = "iptables -A $goChain ";
			if( $src ne '0.0.0.0/0' ) { $cmd .= "-s $src "; }
			# MAC address
			if( $src_mac =~ /^[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}\:[0-9a-fA-F]{2}$/ ) {
				$cmd .= "-m mac --mac-source $src_mac ";
			}
			if( $dst ne '0.0.0.0/0' ) { $cmd .= "-d $dst "; }
		} else {
			$cmd = "iptables -A $backChain ";
			if( $dst ne '0.0.0.0/0' ) { $cmd .= "-s $dst "; }
			if( $src ne '0.0.0.0/0' ) { $cmd .= "-d $src "; }
		}

		if( $p ne '' ) { $cmd .= "-p $p "; }
		if( $icmptype ne '' ) { $cmd .= "--icmp-type $icmptype "; }
		if( $sport ne '' ) { $cmd .= "--sport $sport "; }
		if( $dport ne '' ) { $cmd .= "--dport $dport "; }
		if( $state ne '' ) { $cmd .= "-m state --state $state "; }

		# If target=DROP|REJECT then LOG before block
		if( $target =~ /DROP|REJECT/ ) {
			my $cmdlog = $cmd;
			my $logprefix = "TFW $goChain(".substr($target,0,3).")";
			if( length($logprefix) > 28 ) {
				# iptables need log-prefix strings up to 29 chars length
				$logprefix = substr( $logprefix, 0, 28 );
			}
			$cmdlog .= "-m limit --limit $log_limit/hour --limit-burst $log_limit_burst -j LOG --log-prefix \"$logprefix:\"";
			command( $cmdlog );
			$jump = $target;
		}

		# Se e' in andata accetto il passaggio del pacchetto se e' in ritorno lo invio
		# alla catena BACK che si occupa di verificare che sia realmente un pacchetto di
		# una connessione gia' aperta.
		if( $jump eq '' ) {
			$cmd .= "-j ".( $direction eq 'go' ? 'ACCEPT' : 'BACK' );
		} else {
			$cmd .= "-j $jump";
		}

		#print "\n$cmd\n";

		command( $cmd );
	}
}



# dato il nome dell'item ritorna la zona e l'ip + netmask
sub expand_item {
	$item = shift;
	$itemType = $fwItems{$item};

	my $zone = '';
	my $ip = '';
	my $mac = '';

	if( $itemType eq 'ZONE' ) {
		$zone = $item;
		$ip = '0.0.0.0/0';
	}
	if( $itemType eq 'NET' ) {
		$zone = $fw{NET}{$item}{ZONE};
		$ip = $fw{NET}{$item}{IP}.'/'.$fw{NET}{$item}{NETMASK};
	}
	if( $itemType eq 'HOST' ) {
		$zone = $fw{HOST}{$item}{ZONE};
		$ip = $fw{HOST}{$item}{IP}.'/32';
		$mac = $fw{HOST}{$item}{MAC};
	}
	return ($zone, $ip, $mac);
}

# scrive un commento sul file di script in generazione
sub comment {
	$msg = shift;
	#print "$msg\n";
	if( $outputFile ne '' ) {
		print SCRIPTFILE "$msg\n";
	}
	#command( "$msg\n" );
}

# scrive un comando sul file di script in generazione
sub command {
	$cmd = shift;

	#print "$cmd\n";
	if( $outputFile eq '' ) {
		print qx{ $cmd };
	} else {
		print SCRIPTFILE "$cmd\n";
	}
}

# controlla che la stringa non superi i 29 caratteri (per l'opzione --prefix di iptables)
sub upto29 {
	my $s = shift;
	return $s;
}
