#!/bin/bash

iptables_chain_policy()
{
	eval "$IPTABLES -P $@" || print_error "$IPTABLES -P $@"
}

iptables_create_chain()
{
	eval "$IPTABLES -N $@" || print_error "$IPTABLES -N $@"
}

iptables_delete_chain()
{
	eval "$IPTABLES -X $@" || print_error "$IPTABLES -X $@"
}

iptables_flush_chain()
{
	eval "$IPTABLES -F $@" || print_error "$IPTABLES -F $@"
}

iptables_zero_chain()
{
	eval "$IPTABLES -Z $@" || print_error "$IPTABLES -Z $@"
}

iptables_rename_chain()
{
	eval "$IPTABLES -E $@" || print_error "$IPTABLES -E $@"
}

iptables_list_chain()
{
	eval "$IPTABLES -L $@" || print_error "$IPTABLES -L $@"
}

iptables_push_rule()
{
	: ${@:?missing args to $FUNCNAME}
    case $1 in
	"-A"|"-I"|"-D")
	    eval "$IPTABLES $@" || print_error "$IPTABLES $@"
		;;
	*)
        case ${IPTABLES_RULE_EMBEDDING:APPEND} in
    		APPEND)
	   			    eval "$IPTABLES -A $@" || print_error "$IPTABLES -A $@"
					;;
			INSERT)
	    	    	eval "$IPTABLES -I $@" || print_error "$IPTABLES -I $@"
					;;
			*)
					print_error "Unknown type of rule embedding $IPTABLES_RULE_EMBEDDING"
					;;
	    esac
		;;
    esac
}

iptables_pop_rule()
{
	: ${@:?missing args to $FUNCNAME}
    case $1 in
	"-A")
	    shift
	    eval "$IPTABLES -D $@" || print_error "$IPTABLES -D $@"	    	    
		;;
	"-I")
	    PRE="$2"
		case $3 in
			[0-9]*)
				shift 3
				;;
			*)
				shift 2
				;;
		esac
	    eval "$IPTABLES -D $PRE $@" || print_error "$IPTABLES -D $PRE $@"
		;;	
	"-D")
	    shift
	    iptables_push_rule $@
		;;
	"-P")
		continue
		;;
	*)
        eval "$IPTABLES -D $@" || print_error "$IPTABLES -D $@"	    	        	
		;;
    esac
}

iptables_load_syntax()
{
	: ${1:?missing 1st arg to $FUNCNAME}
	[ ! -f "$1" ] || [ ! -s "$1" ] && \
	{
		export IPTABLES_SYNTAX=
		return 1
	}
	export IPTABLES_SYNTAX=$(< "$1")
}

iptables_create_sed_rules()
{
	: ${1:?missing 1st arg to $FUNCNAME}
	local SEARCH
	local REPLACE
    local REPLACES
    local OLDIFS
    export IPTABLES_SED_RULES=
    print_message "Loading iptables data"
    REPLACES="$1"
    OLDIFS=$IFS
    IFS=":"
    while read SEARCH REPLACE; do
        print_progress
        IPTABLES_SED_RULES+="s/\(^\|[^-]\b\)$SEARCH\(\b[^-]\|$\)/\1$REPLACE\2/g;"
    done < <(echo "$REPLACES"|egrep "^[^#]"|sed -e 's/\//\\\\\//g')
    IFS=$OLDIFS
    print_message
    export IPTABLES_SED_RULES
}

iptables_expand_string()
{
	: ${1:?missing 1st arg to $FUNCNAME}
	local RULE
	if is_yes "$IPTABLES_HUMAN_SYNTAX"; then
		[ -z "$IPTABLES_SED_RULES" ] && print_error "Human syntax is enabled but no syntax file is loaded"
		RULE=$(eval "printf '%s\n' $@"|sed  -e "$IPTABLES_SED_RULES;s/\ \ /\ /g;")
	else
		RULE=$(eval "printf '%s\n' $@")
	fi
	printf '%s\n' "$RULE"|egrep -q "^[^#]" && printf '%s\n' "$RULE" || echo ""
}

iptables_preload()
{
	is_yes "$IPTABLES_HUMAN_SYNTAX" && [ -z "$IPTABLES_SYNTAX" ] && { 
		iptables_load_syntax ${IPTABLES_SYNTAX_DIR:=/etc/net/ifaces/default/fw/iptables}/syntax
		iptables_create_sed_rules "$IPTABLES_SYNTAX"
		}			
}

# iptables_load_rules_from_file <table> <chain>
iptables_load_rules_from_file()
{
	local RULE
	local RULES
	local CHAIN
	local TABLE
	: ${1:?missing 1st arg to $FUNCNAME}
	: ${2:?missing 2nd arg to $FUNCNAME}
	TABLE="$1"
	CHAIN="$2"
	if [ -f "$TABLE/$CHAIN" ] && [ -s "$TABLE/$CHAIN" ]; then
		print_message "Loading rules for chain $CHAIN in table $TABLE"
		RULES=$(< "$TABLE/$CHAIN")
		while read RULE; do
				RULE=`iptables_expand_string "$RULE"`
				[ -z "$RULE" ] || {
				    read FIRST SECOND REST < <(echo $RULE)
    					case $FIRST in
					    "-A"|"-D")
    	    					iptables_push_rule $FIRST $CHAIN -t $TABLE $SECOND $REST
								;;
					    "-I")
								case $SECOND in
							    	[0-9]*)
			        						iptables_push_rule $FIRST $CHAIN $SECOND -t $TABLE $REST
									    	;;
							   		 *)
		        							iptables_push_rule $FIRST $CHAIN -t $TABLE $SECOND $REST
									    	;;
								esac
								;;
					    *)
    	    					iptables_push_rule $CHAIN -t $TABLE $FIRST $SECOND $REST
						    	;;
						esac
				}
				print_progress
		done < <(echo "$RULES"|egrep "^[^#]")
		print_message
	fi
}

# iptables_flush_rules_from_file <table> <chain> [flush]
iptables_flush_rules_from_file()
{
	local RULE
	local RULES
	local CHAIN
	local TABLE
	: ${1:?missing 1st arg to $FUNCNAME}
	: ${2:?missing 2nd arg to $FUNCNAME}
	TABLE="$1"
	CHAIN="$2"
	if [ -f "$TABLE/$CHAIN" ] && [ -s "$TABLE/$CHAIN" ]; then
		if [ "$NAME" != "default" ] && [ "$3" != "flush" ]; then
			print_message "Unloading rules for chain $CHAIN in table $TABLE"
			RULES=$(tac "$TABLE/$CHAIN")
			while read RULE; do
				RULE=`iptables_expand_string "$RULE"`
				[ -z "$RULE" ] || {
				    read  FIRST SECOND REST < <(echo $RULE)
					case $FIRST in
					    "-A"|"-D")
        						iptables_pop_rule $FIRST $CHAIN -t $TABLE $SECOND $REST
								;;
					    "-I")
								case $SECOND in
								    [0-9]*)
				        					iptables_pop_rule $FIRST $CHAIN $SECOND -t $TABLE $REST
										    ;;
								    *)
	        								iptables_pop_rule $FIRST $CHAIN -t $TABLE $SECOND $REST
										    ;;
								esac
								;;
					    *)
        						iptables_pop_rule $CHAIN -t $TABLE $FIRST $SECOND $REST
							    ;;
						esac
				}				
				print_progress
			done < <(echo "$RULES"|egrep "^[^#]")
			print_message
		else
			# Flush rules only when stop networking
			print_message "Flushing chain $CHAIN in table $TABLE"
			iptables_flush_chain $CHAIN -t $TABLE
		fi
	fi
}


iptables_start()
{
	local NAME
	local MODULE
	local TABLE
	local CHAIN
	: ${1:?missing 1st arg to $FUNCNAME}
	NAME="$1"
	print_message "Starting iptables for $NAME"
	# Set defaut policy
	if [ "$NAME" = "default" ]; then
		[ -z "$IPTABLES_INPUT_POLICY" ] || {
			print_message "Set $IPTABLES_INPUT_POLICY policy for INPUT chain"
			iptables_chain_policy INPUT "$IPTABLES_INPUT_POLICY"
		}
		[ -z "$IPTABLES_FORWARD_POLICY" ] || {
			print_message "Set $IPTABLES_FORWARD_POLICY policy for FORWARD chain"
			iptables_chain_policy FORWARD "$IPTABLES_FORWARD_POLICY"
		}
		[ -z "$IPTABLES_OUTPUT_POLICY" ] || {
			print_message "Set $IPTABLES_OUTPUT_POLICY policy for OUTPUT chain"
			iptables_chain_policy OUTPUT "$IPTABLES_OUTPUT_POLICY"
		}
	fi
	cd "$MYIFACEDIR/fw/$FW_TYPE"
	# Load modules
	if [ -f "modules" ] && [ -s "modules" ]; then
		while read MODULE; do
			print_message "Loading module $MODULE"
			$MODPROBE "$MODULE" || print_error "Can't load module $MODULE"
		done < <(egrep "^[^#]" modules)
	fi
	# Create user chains
	for TABLE in *; do
		[ ! -d "$TABLE" ] && continue
		for CHAIN in "$TABLE"/*; do
			CHAIN=`basename $CHAIN`
			[ "$TABLE/$CHAIN" = "$TABLE/loadorder" ] || [ ! -f "$TABLE/$CHAIN" ] && continue
			egrep -q "([^-]\b|^)$CHAIN(\b[^-]|$)" < <(echo "$IPTABLES_SYSTEM_CHAINS") ||
			{
				print_message "Creating chain $CHAIN in table $TABLE"
				iptables_create_chain $CHAIN -t $TABLE
			}
		done
	done
	# FIXME  Double code
	# Load rules after creating _all_ chains
	if [ -f "loadorder" ] && [ -s "loadorder" ]; then
		while read TABLE; do
			[ ! -d "$TABLE" ] && continue
			if [ -f "$TABLE/loadorder" ] && [ -s "$TABLE/loadorder" ]; then
				while read CHAIN; do
					iptables_load_rules_from_file "$TABLE" "$CHAIN"
				done < <(egrep "^[^#]" "$TABLE/loadorder")
			else
				for CHAIN in "$TABLE"/*; do
					CHAIN=`basename $CHAIN`
					iptables_load_rules_from_file "$TABLE" "$CHAIN"
				done
			fi
		done < <(egrep "^[^#]" loadorder)
	else
		for TABLE in *; do
			[ ! -d "$TABLE" ] && continue
			if [ -f "$TABLE/loadorder" ] && [ -s "$TABLE/loadorder" ]; then
				while read CHAIN; do
					iptables_load_rules_from_file "$TABLE" "$CHAIN"
				done < <(egrep "^[^#]" "$TABLE/loadorder")
			else
				for CHAIN in "$TABLE"/*; do
					CHAIN=`basename $CHAIN`
					iptables_load_rules_from_file "$TABLE" "$CHAIN"
				done
			fi
		done
	fi
}

iptables_stop()
{
	local NAME
	local MODULE
	local TABLE
	local CHAIN
	: ${1:?missing 1st arg to $FUNCNAME}
	NAME="$1"
	print_message "Stopping iptables for $NAME"
	cd "$MYIFACEDIR/fw/$FW_TYPE"
	# FIXME  Double code
	# Flush rules
	if [ -f "loadorder" ] && [ -s "loadorder" ]; then
		while read TABLE; do
			[ ! -d "$TABLE" ] && continue
			if [ -f "$TABLE/loadorder" ] && [ -s "$TABLE/loadorder" ]; then
				while read CHAIN; do
					iptables_flush_rules_from_file "$TABLE" "$CHAIN"
				done < <(tac "$TABLE/loadorder"|egrep "^[^#]")
			else
				for CHAIN in "$TABLE"/*; do
					CHAIN=`basename $CHAIN`
					iptables_flush_rules_from_file "$TABLE" "$CHAIN"
				done
			fi
		done < <(tac loadorder|egrep "^[^#]")
	else
		for TABLE in *; do
			[ ! -d "$TABLE" ] && continue
			if [ -f "$TABLE/loadorder" ] && [ -s "$TABLE/loadorder" ]; then
				while read CHAIN; do
					iptables_flush_rules_from_file "$TABLE" "$CHAIN"
				done < <(tac "$TABLE/loadorder"|egrep "^[^#]")
			else
				for CHAIN in "$TABLE"/*; do
					CHAIN=`basename $CHAIN`
					iptables_flush_rules_from_file "$TABLE" "$CHAIN"
				done
			fi
		done
	fi

	# Delete user chains
	for TABLE in *; do
		[ ! -d "$TABLE" ] && continue
		for CHAIN in "$TABLE"/*; do
			CHAIN=`basename $CHAIN`
			[ "$TABLE/$CHAIN" = "$TABLE/loadorder" ] || [ ! -f "$TABLE/$CHAIN" ] && continue
			egrep -q "([^-]\b|^)$CHAIN(\b[^-]|$)" < <(echo "$IPTABLES_SYSTEM_CHAINS") ||
			{
				print_message "Deleting chain $CHAIN from table $TABLE"
				iptables_delete_chain $CHAIN -t $TABLE
			}
		done
	done

	# Unload modules
	if [ -f "modules" ] && [ -s "modules" ]; then
		while read MODULE; do
			print_message "Unloading module $MODULE"
			$MODPROBE -r "$MODULE" || print_error "Can't unload module $MODULE"
		done < <(tac modules|egrep "^[^#]")
	fi
	# Set ACCEPT policy
	if [ "$NAME" = "default" ]; then
		print_message "Set ACCEPT policy for INPUT chain"
		iptables_chain_policy INPUT ACCEPT
		print_message "Set ACCEPT policy for FORWARD chain"
		iptables_chain_policy FORWARD ACCEPT
		print_message "Set ACCEPT policy for OUTPUT chain"
		iptables_chain_policy OUTPUT ACCEPT
	fi
}
