#!/bin/sh
# $Id: ipacset,v 1.26 1999/08/06 10:53:24 moritz Exp $
#
# Set accounting rules as told by config file
# Copyright (C) 1997 - 1999 Moritz Both
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# The author can be reached via email: moritz@daneben.de, or by
# snail mail: Moritz Both, Im Moore 26, 30167 Hannover,
#             Germany. Phone: +49-511-1610129
#
# config file should be is $1
# =()<RUNFILE="@<RUNFILE>@">()=
RUNFILE="/var/run/ip-accounting-rules"
# =()<IPFWADM="@<IPFWADM>@">()=
IPFWADM="/sbin/ipfwadm"
# =()<IPCHAINS="@<IPCHAINS>@">()=
IPCHAINS=""
# =()<CONFIGFILE="@<RULESFILE>@">()=
CONFIGFILE="/etc/ipac.conf"
# =()<FETCHIPAC="@<INSTALLPATH>@/fetchipac">()=
FETCHIPAC="/usr/local/bin/fetchipac"
# =()<LOCKFILE="@<LOCKFILE>@">()=
LOCKFILE="/var/lock/ipac.lck"
# =()<CH_INNAME="@<CH_INNAME>@">()=
CH_INNAME="ipac_in"
# =()<CH_OUTNAME="@<CH_OUTNAME>@">()=
CH_OUTNAME="ipac_out"
# =()<CH_IONAME="@<CH_IONAME>@">()=
CH_IONAME="ipac_bth"
# =()<MKTEMP="@<MKTEMP>@">()=
MKTEMP="/bin/mktemp"
# =()<TMPDIR="@<TMPDIR>@">()=
TMPDIR="/tmp"
# =()<IPFWADM_PROC="@<IPFWADM_PROC>@">()=
IPFWADM_PROC="/proc/net/ip_acct"
# =()<IPCHAINS_PROC_N="@<IPCHAINS_PROC_N>@">()=
IPCHAINS_PROC_N="/proc/net/ip_fwnames"
# =()<IPCHAINS_PROC_C="@<IPCHAINS_PROC_C>@">()=
IPCHAINS_PROC_C="/proc/net/ip_fwchains"
# =()<AWK="@<AWK>@">()=
AWK="/usr/bin/awk"


PROC=/proc

# add a chain if it doesnt exist.
addchain() {
	grep "$1 " $IPCHAINS_PROC_N >/dev/null 2>&1 \
		|| $IPCHAINS --new-chain $1 || exit 1
}

# add a jump from a standard chain to an ipac chain if it doesnt exist
# insert it as first rule into the chain
addjump() {
	egrep "$1 .* $2" $IPCHAINS_PROC_C >/dev/null 2>&1 \
		|| $IPCHAINS --insert $1 1 --jump $2 || exit 1
}

# check which firewall code is in the kernel
# and set up some variables.
if [ -f $IPFWADM_PROC ]; then
	HAVE_IPCHAINS=no
	if [ -z "$IPFWADM" ]; then
		echo "$0: ipfwadm tool missing" >&2
		exit 1
	fi
	if [ ! -x $IPFWADM ]; then
		echo "$0: cant execute $IPFWADM" >&2
		exit 1
	fi
	if [ ! -r $IPFWADM_PROC ]; then
		echo "$0: cant read \"$IPFWADM_PROC\" - exit" >&2
		exit 1
	fi
	INTERFACEFLAG=-W
	PROC_LINES_FILE=$IPFWADM_PROC
elif [ -f $IPCHAINS_PROC_N ]; then
	HAVE_IPCHAINS=yes
	if [ -z "$IPCHAINS" ]; then
		echo "$0: ipchains tool missing" >&2
		exit 1
	fi
	if [ ! -x $IPCHAINS ]; then
		echo "$0: cant execute $IPCHAINS" >&2
		exit 1
	fi
	if [ ! -r $IPCHAINS_PROC_N ]; then
		echo "$0: cant read \"$IPCHAINS_PROC_N\" - exit" >&2
		exit 1
	fi
	INTERFACEFLAG=-i
	PROC_LINES_FILE=$IPCHAINS_PROC_C
else
	echo "$0: no ip firewall / accounting code in the kernel" >&2
	exit 1
fi

TRYAGAIN=""
FAIL=0
FIX_CHAINS=""
ALL_OPTIONS="$*"

# parse command line.
while [ "$1" != "" ]; do
	case "$1" in
	-D)	
		# switch to debugging mode
		IPAC_DEBUG=y
		TESTECHO=echo
		;;
	--fix-chains)
		FIX_CHAINS=1
		;;
	--tryagain)
		TRYAGAIN="y"
		;;
	*)
		CONFIGFILE="$1"
		;;
	esac
	shift
done

if [ x$CONFIGFILE = x ]; then
	echo "Usage: $0 configfile" >&2
	echo "See ipacset(8) documentation for details." >&2
	exit 1
fi

if [ ! -r $CONFIGFILE ]; then
	echo "$0: cant read config file \"$CONFIGFILE\"" >&2
	exit 1
fi

# ipchains:
# create my chains if they dont exist. Do this before the lock is
# checked for option --fix-chains
if [ $HAVE_IPCHAINS = yes ]; then
	addchain $CH_INNAME
	addchain $CH_OUTNAME
	addchain $CH_IONAME

	# add jump rules to the standard chains if they dont exist.
	addjump input $CH_INNAME
	addjump output $CH_OUTNAME
	addjump input $CH_IONAME
	addjump output $CH_IONAME
fi

# --fix-chains: We did now, so exit.
test "$FIX_CHAINS" != "" && exit 0


# fetch now before resetting everything
# (if theres something to fetch)
test -r $RUNFILE && $FETCHIPAC

# prevent fetchipac from fetching now and myself from
# running twice
echo $$ >$LOCKFILE.$$ || exit 1
trap "rm -f $LOCKFILE.$$; exit 0" 1 2 3 15
if ln $LOCKFILE.$$ $LOCKFILE 2>/dev/null; then
	trap "rm -f $LOCKFILE.$$ $LOCKFILE \$TMPFILES; exit" 0 1 2 3 15
else
	if kill -0 `cat $LOCKFILE` 2>/dev/null; then
		rm -f $LOCKFILE.$$
		echo "$0: cant run twice at once, exiting"
		exit 1
	else
		rm -f $LOCKFILE
		if [ x$TRYAGAIN = xy ]; then
			echo "$0: Something weired is going on"
			exit 1
		fi
		echo "$0: removed old lock file, trying again"
		exec $0 --tryagain $ALL_OPTIONS
	fi
fi

sleep 5

$TESTECHO touch $RUNFILE || exit 1
>$RUNFILE

OFS=$IFS
IFS=\|

# delete existing accounting info
if [ $HAVE_IPCHAINS = yes ]; then
	# flush my chains so no rules are in there anymore.
	$IPCHAINS --flush $CH_INNAME
	$IPCHAINS --flush $CH_OUTNAME
	$IPCHAINS --flush $CH_IONAME
	TMPFILE=`$MKTEMP $TMPDIR/ipacset.XXXXXXXX`
	TMPFILES="$TMPFILES $TMPFILE" || exit 1
else
	# ipfwadm:
	$TESTECHO $IPFWADM -Af

fi

# remember the line count in $PROC_LINES_FILE when empty
PROC_LINES=`wc -l < $PROC_LINES_FILE`

# read the config file
exec <$CONFIGFILE
while read name direction interface protocol src destination; do
	test `expr "x$name" : "x#"` != 0 && continue
	# ignore empty lines [FL]
	if [ -z "$name" ]; then
		continue
	fi
	# some syntax checking [FL]
	if [ -z "$name" -o -z "$direction" ]; then
		echo "$0: incomplete line in config file: "\
		     "\"$name|$direction|$interface|$protocol|$src|$destination\"" >&2
		FAIL=1
		continue
	fi

	if [ "$direction" = "io" ]; then
		direction="both"
	fi

	case x$direction in
	xin)	;;
	xout)	;;
	xboth)	;;
	*)	echo "$0: Invalid direction \"$direction\""
		FAIL=1
		;;
	esac
	
	if [ "x$interface" != "x" ]; then
		if [ `expr "$interface" : [0-9]` != 0 ]; then
			if [ $HAVE_IPCHAINS = yes ]; then
				REAL_IF=`ifconfig | $AWK -v ADDR=$interface '
					/^[a-z0-9A-Z]/ { IFNAME=$1 } 
					$0 ~ "^[^A-Za-z0-9:]*inet addr:" ADDR " " {
						print IFNAME
						exit(0)
				}'`
				if [ -z "$REAL_IF" ]; then
					echo "ERROR: cant find interface name for address \"$interface\""
					echo " IP numbers in the interface field are depriciated anyway. Better use"
					echo " the interface name now (this is a new feature[tm] of Linux 2.2)"
					REAL_IF="unknown"
					FAIL=1
				fi
				interface="$INTERFACEFLAG $REAL_IF"

			else
				interface="-V $interface"
			fi
		else
			interface="$INTERFACEFLAG $interface"
		fi
	fi

	if [ "x$protocol" = "x" ]; then
		protocol="all"
	fi

	if [ "x$src" = "x" ]; then
		src="0.0.0.0/0"
	fi

	if [ "x$destination" = "x" ]; then
		destination="0.0.0.0/0"
	fi

	#
	if [ $HAVE_IPCHAINS = yes ]; then
		case $direction in
		in)	CHAIN=$CH_INNAME
			;;
		out)	CHAIN=$CH_OUTNAME
			;;
		both)	CHAIN=$CH_IONAME 
			;;
		esac

		# emulate old ipfwadm behavour concerning port numbers
		# (multiple port numbers in one line)
		awk -v src="$src" -v dest="$destination" '
		BEGIN {
			sn=split(src, s, / +/)
			if (sn == 1)
				s[++sn] = ""
			dn=split(dest, d, / +/)
			if (dn == 1)
				d[++dn] = ""
			for (is=2; is <= sn; is++) {
				if (is>2 && s[is]=="")
					continue
				for (id=2; id <= dn; id++) {
					if (id>2 && d[id]=="")
						continue
					print s[1] " " s[is] "|" d[1] " " d[id]
				}
			}
			exit(0)
		}' >$TMPFILE

		EXCODE=0
		while read S D; do
			eval $TESTECHO $IPCHAINS --append $CHAIN $interface \
				-p $protocol \
				-s $S -d $D
			SUB_EXCODE=$?
			if [ $SUB_EXCODE. != 0. ]; then
				EXCODE=$SUB_EXCODE
			fi
		done < $TMPFILE

		CHAIN_RUNFILE="$CHAIN "
	else
		eval $TESTECHO $IPFWADM -A $direction -a $interface \
			-P $protocol \
			-S $src -D $destination
		EXCODE=$?
	fi
	if [ $EXCODE. != 0. -o x$FAIL = x1 ]; then
		echo "$0: Failed to add rule \"$name\"" >&2
		FAIL=1
	fi

	# figure out how many actual kernel rules ipfwadm added -
	# this is important when analysing the output of ipfwadm -Al
	# add that many lines to $RUNFILE
	N="$PROC_LINES"
	PROC_LINES=`wc -l < $PROC_LINES_FILE`
	while [ $N -lt $PROC_LINES ]; do
		echo "${CHAIN_RUNFILE}$name" >>$RUNFILE
		N=`eval expr $N + 1`
	done
done

exit $FAIL

