# Functions needed by dwall

### General variables
DWALLCONFIG="/etc/dwall/dwall.conf"
IPT_OPTIONS=":A:a:d:i:l:o:p:s:-:"

### Checks if string is address
function is_address {
	local str="$1";
	if [ "${str:0:1}" '>' "/" -a "${str:0:1}" '<' ":" ]; then
			return 0
	fi
	return 1
}

### Checks if string is interface
function is_interface {
	local str=$1;
	if [ -e /sys/class/net/$str ];
	then
	   return 0;
	else   
	  case "$str" in
		(eth?|tr?|tun?|ppp?) 
			return 0;;
		(*)
			return 1;;
	  esac
	fi  
}

### Expand alias into interface
function expand_ip {
	local alias="$1"
	local value="$(hash_get alias $alias)"
	if is_address $alias; then
		echo -n $alias " "
	elif is_address $value; then
		echo -n $value ""
	else
		die $FUNCNAME $LINENO "Alias $alias does not expand to IP address."
	fi
}

### Expand alias(es) into aliases (if possible)
function expand_alias {
	local aliases="$@"
	for alias in ${aliases//,/ }; do
		if hash_exists alias $alias; then
			for value in $(hash_get alias $alias); do
				if [ "$value" == "$(hash_get alias $alias)" ]; then
					echo -n $alias ""
				elif hash_exists alias $value; then
					echo -n $(expand_alias $value) ""
				else
					echo -n $value ""
				fi
			done
		elif is_address $alias; then
			echo -n $alias ""
		else
			error $FUNCNAME $LINENO "Alias $alias does not exist in alias.conf"
		fi
	done
}


### Verify installation
function dwall_verify {
	### Controle if Iptables has been installed
	IPTABLES=$(which iptables)
	if [ ! -x $IPTABLES ]; then
		die "Iptables not installed."
	fi
	
	### Controle Syntax
	for file in $LIBDIR/dwall-functions $CONFIGDIR/chains/*.chain $CONFIGDIR/services/* $CONFIGDIR/*.conf; do
		bash -n $file || error $FUNCNAME $LINENO "Syntax error in $file."
	done

	for chainfile in "$CONFIGDIR/chains/"*.chain; do
		local CHAIN=$(/bin/basename $chainfile)
		CHAIN=${CHAIN/.chain/}
	        local zsrc=${CHAIN/-*/} 
		local zdst=${CHAIN/*-/}
		if [ ${#CHAIN} -gt 31 ]; then
			error $FUNCNAME $LINENO "Chain $CHAIN is ${#CHAIN} and exceeds 31 characters."
		fi
	done

	if [ "$ERROR" ]; then
		die "Configuration problems, exiting prematurely."
	fi
}

function dwall_hash_import {
	local hash="$1"
	local file="$2"
	if [ -r "$file" ]; then
		while read arg1 args; do
			if [ "${arg1//\\#*/}" ]; then
				for arg2 in $args; do
					if ! hash_exists $hash $arg2; then
						echo "hash_put" $hash $arg2 $arg1
					fi
				done
			fi
		done <"$file" > "$CONFIGDIR/tmp/mapping-$hash-$(basename $file)"
		source "$CONFIGDIR/tmp/mapping-$hash-$(basename $file)"
	else
		error $FUNCNAME $LINENO "File $file cannot be read."
	fi
}

function dwall_alias_self {
	tail -n +3 /proc/net/dev | tr ':' ' ' | while read if a b c d e f g h i j k l m n o p; do
		case "$if" in
			(lo) ;;
			(*)
				/sbin/ip addr show primary dev $if up 2> /dev/null | tail -1 | while read x ip y bc z; do
					case "$x" in
						(inet) echo "hash_put alias self_$if" $(echo $ip | cut -d'/' -f1) ;;
					esac
				done
		esac
	done >"$CONFIGDIR/tmp/mapping-alias-if"
	source "$CONFIGDIR/tmp/mapping-alias-if"
}

### Enable packet forwarding
#	echo "1" > /proc/sys/net/ipv4/ip_forward
### Disable source routing of packets
#	for i in /proc/sys/net/ipv4/conf/*/accept_source_route:do
#		echo "0" > $i
#	done
### Log packets with impossible address to kernel log
#	if [ -e /proc/sys/net/ipv4/conf/all/log_martians ]; then
#		echo "0" > /proc/sys/net/ipv4/conf/all/log_martians
#	fi
### Enable route verification
#	for i in /proc/sys/net/ipv4/conf/*/rp_filter; do
#		echo "1" > $i
#	done
### Add Syncookie support
#	echo "1" > /proc/sys/net/ipv4/tcp_syncookies

### Initialise firewall code
function dwall_fw_init {
	echo "#!/bin/bash"
	echo
	echo "### This script is compiled using Dwall v$DWALL_VERSION"
	echo "### It was compiled on $(uname -n) by $(logname)"
	echo "### on $(date)"
	echo
	echo "### Compiled with Bash v$BASH_VERSION"
	echo
	echo "trap \"\" 1 2 3 15"
	echo
	echo "iptables -F"
	echo "iptables -t nat -F"
#	echo "iptables -t mangle -F"
	echo "iptables -X"
	echo "iptables -t nat -X"
#	echo "iptables -t mangle -X"

	echo "iptables -P INPUT DROP"
	echo "iptables -P FORWARD DROP"
	echo "iptables -P OUTPUT DROP"

#	echo "iptables -t nat -P PREROUTING DROP"
#	echo "iptables -t nat -P POSTROUTING DROP"
#	echo "iptables -t nat -P OUTPUT DROP"

#	echo "iptables -t mangle -P PREROUTING DROP"
#	echo "iptables -t mangle -P OUTPUT DROP"

# Allowed chain : allowed chain for TCP Connections used in FORWARD
#	echo "### Stateful firewalling chain"
#	echo "iptables -N allowed"
#	echo "iptables -A allowed --syn -j ACCEPT"
#	echo "iptables -A allowed -m state --state ESTABLISHED,RELATED -j ACCEPT"
#	echo "iptables -A allowed -j DROP"

	echo
	echo "### Importing scripts/pre.sh"
	source "$CONFIGDIR/scripts/pre.sh"
	echo
}

### Jump to different targets
function dwall_fw_targets {
	echo "### Create chains and define jumps."
	for chainfile in "$CONFIGDIR/chains/"*.chain; do
        	local CHAIN=$(/bin/basename $chainfile)
        	CHAIN=${CHAIN/.chain/}
        	local zsrc=${CHAIN/-*/}
 	        local zdst=${CHAIN/*-/}

		if [ "$zsrc" '>' "$zdst" -a -f "$CONFIGDIR/chains/$zdst-$zsrc.chain" ]; then
			continue
		fi

		local ifsrc=$(hash_get zone $zsrc);
		if [ -z "$ifsrc" ]; then
			error $FUNCNAME $LINENO "Zone $zsrc is not defined in zonemap."
			continue
		fi

		local ifdst=$(hash_get zone $zdst);
		if [ -z "$ifdst" ]; then
			error $FUNCNAME $LINENO "Zone $zdst is not defined in zonemap."
			continue
		fi

		echo "### Chains and jumps for $zsrc <-> $zdst"
		echo "iptables -N $zsrc-$zdst"
		echo "iptables -N $zdst-$zsrc"

		if [ "$zdst" == "self" -o "$zdst" == "$HOSTNAME" ]; then
			hash_delete alias self $HOSTNAME
			hash_put alias self "$(hash_get alias self_$ifsrc)"
			hash_put alias $HOSTNAME "$(hash_get alias self_$ifsrc)"
			zdst="self"
		elif [ "$zsrc" == "self" -o "$zsrc" == "$HOSTNAME" ]; then
			hash_delete alias self $HOSTNAME
			hash_put alias self "$(hash_get alias self_$ifdst)"
			hash_put alias $HOSTNAME "$(hash_get alias self_$ifdst)"
			zsrc="self"
		fi

### REMOVED IP-ADRESSES FOR NOW, SHOULD BE POSSIBLE TO DIVIDE A ZONE IN RANGES
### FOR NOW ONLY DEPEND ON ROUTING INFORMATION

#		for ipsrc in $(hash_get alias $zsrc); do
#			is_address $ipsrc
#			if [ $? -ne 0 ]; then continue; fi
#			for ipdst in $(hash_get alias $zdst); do
#				is_address $ipdst
#				if [ $? -ne 0 ]; then continue; fi
				if [ "$zdst" == "self" ]; then
#					echo "iptables -A INPUT -p udp -i $ifsrc -d 255.255.255.255/32 -j $zsrc-$zdst"
#					echo "iptables -A INPUT -i $ifsrc -s $ipsrc -d $ipdst -j $zsrc-$zdst"
#					echo "iptables -A OUTPUT -p udp -o $ifsrc -s $(hash_get alias self_$ifsrc) -d 255.255.255.255/32 -j $zdst-$zsrc"
#					echo "iptables -A OUTPUT -o $ifsrc -s $ipdst -d $ipsrc -j $zdst-$zsrc"
					echo "iptables -A INPUT -i $ifsrc -j $zsrc-$zdst"
					echo "iptables -A OUTPUT -o $ifsrc -j $zdst-$zsrc"
				elif [ "$zsrc" == "self" ]; then
#					echo "iptables -A OUTPUT -p udp -o $ifdst -s $(hash_get alias self_$ifdst) -d 255.255.255.255/32 -j $zsrc-$zdst"
#					echo "iptables -A OUTPUT -o $ifdst -s $ipsrc -d $ipdst -j $zsrc-$zdst"
#					echo "iptables -A INPUT -p udp -i $ifdst -d 255.255.255.255/32 -j $zdst-$zsrc"
#					echo "iptables -A INPUT -i $ifdst -s $ipdst -d $ipsrc -j $zdst-$zsrc"
					echo "iptables -A OUTPUT -o $ifdst -j $zsrc-$zdst"
					echo "iptables -A INPUT -i $ifdst -j $zdst-$zsrc"
				else
#					echo "iptables -A FORWARD -i $ifsrc -o $ifdst -s $ipsrc -d $ipdst -j $zsrc-$zdst"
#					echo "iptables -A FORWARD -i $ifdst -o $ifsrc -s $ipdst -d $ipsrc -j $zdst-$zsrc"
					echo "iptables -A FORWARD -i $ifsrc -o $ifdst -j $zsrc-$zdst"
					echo "iptables -A FORWARD -i $ifdst -o $ifsrc -j $zdst-$zsrc"
				fi
#			done
#		done
		echo
	done

	echo "iptables -A INPUT -i lo -j ACCEPT"
	echo "iptables -A OUTPUT -o lo -j ACCEPT"
	echo; echo
}

function dwall_fw_chains {
	local PATH="/bin"
	local nr=0
	for chainfile in "$CONFIGDIR/chains/"*.chain; do
		local CHAIN="$(/bin/basename $chainfile)"
		CHAIN="${CHAIN/.chain/}" # cut '.chain' from filename
		local zsrc="${CHAIN/-*/}" 
		local zdst="${CHAIN/*-/}"
		echo "### Rules for $zsrc -> $zdst"
		source "$chainfile"
		echo
		nr=$((nr+1))
	done
	echo " and $nr chains loaded." >&2
}

function dwall_fw_exit {
	echo "### Importing scripts/post.sh"
	source $CONFIGDIR/scripts/post.sh
	echo
	
	echo "### Allow packets from all related or established connections."
	echo "iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT"
	echo "iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT"
	echo "iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT"
	echo

	#	for chainfile in "$CONFIGDIR/chains/"*; do
	#		str=$(/bin/basename $chainfile)
	#		CHAIN=${str/.chain/} # cut '.chain' from filename
	#		cat <<EOF
	#iptables -A $CHAIN -j LOG --log-prefix "+DROPPED+ "  --log-level info #-m limit --limit 5/minute
	#iptables -A $CHAIN -j DROP
	#EOF
	#	done

	if [ "$LOG_ALL" == "yes" -o "$LOG_DROPPED" == "yes" -o "$LOG_UNMATCHED" == "yes" ]; then
		echo "### Log dropped or rejected unmatched packets"
		cat <<EOF
iptables -A INPUT -j LOG --log-level debug --log-prefix "+UNMATCHED_DROP+ " #-m limit --limit 5/minute
iptables -A INPUT -j DROP
iptables -A FORWARD -j LOG --log-level debug --log-prefix "+UNMATCHED_DROP+ " #-m limit --limit 5/minute
iptables -A FORWARD -j DROP
iptables -A OUTPUT -j LOG --log-level debug --log-prefix "+UNMATCHED_REJECT+ " #-m limit --limit 5/minute
iptables -A OUTPUT -j REJECT
EOF
	fi
}

function do_service {
	local service=$1
	local asrc=$2
	local adst=$3
	shift 3
	
	if [ -z "$use" ]; then
		use="$service"
	fi
	
	### Checking interface
	local ifsrc=$(hash_get zone $zsrc);	 
	if [ -z "$ifsrc" ]; then
		error $FUNCNAME $LINENO "Zone $zsrc is not defined in zonemap."
		return 0;
	fi
	local ifdst=$(hash_get zone $zdst);	 
	if [ -z "$ifdst" ]; then
		error $FUNCNAME $LINENO "Zone $zdst is not defined in zonemap."
		return 0;
	fi

	if [ "$zdst" == "self" ]; then
		hash_delete alias self $HOSTNAME
		hash_put alias self "$(hash_get alias self_$ifsrc)"
		hash_put alias $HOSTNAME "$(hash_get alias self_$ifsrc)"
		local where="input"
	elif [ "$zsrc" == "self" ]; then
		hash_delete alias self $HOSTNAME
		hash_put alias self "$(hash_get alias self_$ifdst)"
		hash_put alias $HOSTNAME "$(hash_get alias self_$ifdst)"
		local where="output"
	else
		local where="forward"
	fi

	if [ ! -r "$CONFIGDIR/services/$use" ]; then
		die "Service \"$use\" not found or permission denied."
	elif [ "$(type -t ${use}_${where})" == "function" -a "$port" ]; then
		echo "### Start ${use}_${where} [ $service $asrc $adst / port $port $log]"
	elif [ "$(type -t ${use}_${where})" == "function" ]; then
		echo "### Start ${use}_${where} [ $service $asrc $adst $log]"
	elif [ "$(type -t ${use}_forward)" == "function" ]; then
		echo "### Start ${use}_${where} [ $service $asrc $adst / port $port $log] using ${use}_forward."
		where="forward"
	else
		die "Function \"${use}_${where}\" not declare, please check service \"$service\" or \"$use\"."
	fi

	for alias in $(expand_alias $asrc); do
		local ipsrc="$(expand_ip $alias)"
		if hash_exists mac $alias; then
			local args="-a $(hash_get mac $alias)"
		fi
		for alias in $(expand_alias $adst); do
			local ipdst="$(expand_ip $alias)"
			${use}_${where} $args $@
		done
	done
	echo
	unset use
}

### Accept all unknown options, just handle the ACCEPT specific ones
function allow {
	local services=$1
	local from=$2
	local to=$3
	shift 3
	local target="ACCEPT"
	local options
	local port
	if [ "$*" ]; then
		OPTIND=1
		while getopts "$IPT_OPTIONS" c; do
			case "$c" in
				(p)	port="$OPTARG";;
				(-)	c=${OPTARG/#-/}; OPTARG="$(echo $* | cut -d' ' -f$OPTIND)"
					case "$c" in
						(port) port="$OPTARG"; OPTIND=$((OPTIND+1));;
						(*) options="$options --$c $OPTARG"; OPTIND=$((OPTIND+1));;
					esac;;
				('?')	error $FUNCNAME $LINENO "Option \"-$c\" not allowed" >&2;; 
				(*)	options="$options -$c $OPTARG";;
			esac
		done
		shift $((OPTIND-1))
	fi
	if [ "$*" ]; then
		die "allow: Argument(s) \"$*\" not parsed in \"$rule\"."
	fi

	for service in ${services//,/ }; do
		if [ -r "$CONFIGDIR/services/$service" ]; then
			${service} $from $to $options $@
			unset port
		else
			die "allow: Service \"$service\" not found."
		fi
	done
	unset target
}

function drop {
	local services=$1
	local from=$2
	local to=$3
	shift 3
	local target="DROP"
	local options
	if [ "$*" ]; then
		OPTIND=1
		while getopts "$IPT_OPTIONS" c; do
			case "$c" in
				(p)	port="$OPTARG";;
				(-)	c=${OPTARG/#-/}; OPTARG="$(echo $* | cut -d' ' -f$OPTIND)"
					case "$c" in
						(port) port="$OPTARG"; OPTIND=$((OPTIND+1));;
						(*) options="$options --$c $OPTARG"; OPTIND=$((OPTIND+1));;
					esac;;
				('?')	error $FUNCNAME $LINENO "Option \"-$c\" not allowed" >&2;; 
				(*)	options="$options -$c $OPTARG";;
			esac
		done
		shift $((OPTIND-1))
	fi
	if [ "$*" ]; then
		die "drop: Argument(s) \"$*\" not parsed in \"$rule\"."
	fi

	for service in ${services//,/ }; do
		if [ -r "$CONFIGDIR/services/$service" ]; then
			${service} $from $to $options $@
			unset port
		else
			die "drop: Service \"$service\" not found."
		fi
	done
	unset target
}

function reject {
	local services=$1
	local from=$2
	local to=$3
	shift 3
	local target="REJECT"
	local options
	if [ "$*" ]; then
		OPTIND=1
		while getopts "${IPT_OPTIONS}r:" c; do
			case "$c" in
				(p)	port="$OPTARG";;
				(r)	options="$options --reject-with $OPTARG";;
				(-)	c=${OPTARG/#-/}; OPTARG="$(echo $* | cut -d' ' -f$OPTIND)"
					case "$c" in
						(port)	port="$OPTARG"; OPTIND=$((OPTIND+1));;
						(*)	options="$options --$c $OPTARG"; OPTIND=$((OPTIND+1));;
					esac;;
				('?')	error $FUNCNAME $LINENO "Option \"-$c\" not allowed" >&2;; 
				(*)	options="$options -$c $OPTARG";;
			esac
		done
		shift $((OPTIND-1))
	fi
	if [ "$*" ]; then
		die "reject: Argument(s) \"$*\" not parsed in \"$rule\"."
	fi

	for service in ${services//,/ }; do
		if [ -r "$CONFIGDIR/services/$service" ]; then
			${service} $from $to $options $@
			unset port
		else
			die "reject: Service \"$service\" not found."
		fi
	done
	unset target
}

function ipt {
	local rule=$@
	local options
	local logoptions="--log-level debug --log-prefix"
	local logprefix
	local proto
	local init
	OPTIND=1
	while getopts "$IPT_OPTIONS" c; do
		case "$c" in
			(A)	local zsrc="${OPTARG/-*/}"; local zdst="${OPTARG/*-/}"; options="$options -$c $zsrc-$zdst";;
			(a)	options="$options -m mac --mac-source $OPTARG";;
			(l)	logprefix="$OPTARG";;
			(i)	if [ "$zsrc" != "self" ]; then options="$options -$c $OPTARG"; fi;;
			(o)	if [ "$zdst" != "self" ]; then options="$options -$c $OPTARG"; fi;;
			(p)  	options="$options -$c $OPTARG"; proto="$OPTARG";;
			(-)	c=${OPTARG/#-/}; OPTARG="$(echo $* | cut -d' ' -f$OPTIND)"
				case "$c" in
					(dport|sport|icmp-type|reject-with) options="$options --$c $OPTARG"; OPTIND=$((OPTIND+1));;
					(init) init="yes";;
					(*) error $FUNCNAME $LINENO "No valid option \"--$c\""
				esac;;
			('?')	error $FUNCNAME $LINENO "Option \"-$c\" not allowed" >&2;; 
			(*)	options="$options -$c $OPTARG";;
		esac
	done
	shift $((OPTIND-1))
	if [ "$*" ]; then
		die "ipt: Argument(s) \"$*\" not parsed in \"$rule\"."
	fi

	### Log
	if  [ "$logprefix" ]; then
		echo iptables $options -j LOG $logoptions "+$logprefix+ "
	elif  [ "$init" -a "$LOG_ALL" == "yes" -o "$init" -a "$LOG_INITIAL" == "yes" ]; then
		if [ "$proto" == "tcp" ]; then
			echo iptables $options --syn -j LOG $logoptions "+INITIAL_TCP_$target+ "
		else
			echo iptables $options -j LOG $logoptions "+INITIAL_$target+ "
		fi
	elif  [ "$LOG_DROPPED" == "yes" -a "$target" == "DROP" -o "$target" == "REJECT" ]; then
		echo iptables $options -j LOG $logoptions "+${target}+ "
	elif  [ "$LOG_ALL" == "yes" ]; then
		echo iptables $options -j LOG $logoptions "+${target}+ "
	fi

	if [ "$proto" == "tcp" ]; then
		if [ "$init" ]; then
			echo iptables $options --syn -j $target
		fi
	else
		if [ "$init" ]; then
			echo iptables $options -j $target
		fi
	fi
}
