#!/bin/bash
#Copyright 2001 William Stearns <wstearns@pobox.com>
#GPL'd.

#This converts any input snort rules to iptables rules.

#asterisks added in any content rules referenced in the code to avoid tripping snort... :-)

#FIXME
#temporary fix for missing semicolon after content in:
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"WEB-MISC Netscape   
#Enterprise directory listing attempt"; content:"I*N*D*E*X " offset:0; depth:6;
#flags:A+; reference:cve,CAN-2001-0250; reference:bugtraq,2285; classtype
#:attempted-recon; sid:1048; rev:2;)



#FIXME - strongly suggest REJECT if blocking to avoid filling state table of attacked machine.

Debug () {
	echo "$*" >/dev/stderr
}

Failed () {
#Record fact that conversion failed because of a feature in snort missing in iptables.
	FailedReasons="$FailedReasons $1"
}


StripOptionName () {
	echo "$*" | sed -e 's/^[A-Za-z]*://' -e 's/^ //'
}


ToAscii () {
	Input="$1"
	Tail="$Input"
	Output=''
	Hex=''
	while [ -n "$Tail" ]; do
		if [ -z "$Hex" ]; then
			#Strip ascii character and glue onto output, or | to switch us to hex.
			Head=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'`
			Tail=`echo "$Tail" | sed -e 's/^.//'`
			if [ "$Head" = "|" ]; then
				Hex="y"
			else
				Output="$Output$Head"
			fi
		else
			#Strip first hex digit, space, or | to switch us back to ascii
			Head=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'`
			Tail=`echo "$Tail" | sed -e 's/^.//'`
			if [ "$Head" = "|" ]; then
				Hex=''
			elif [ "$Head" = " " ]; then
				:
			else
				#We already have the first hex digit, grab the second.
				Head2=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'`
				Tail=`echo "$Tail" | sed -e 's/^.//'`
				while [ "$Head2" = " " ]; do
					#Debug "X space second char in X$Input"
					Head2=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'`
					Tail=`echo "$Tail" | sed -e 's/^.//'`
				done

#$, `, ", \, and <newline> are treated specially in double quotes.
#
#The reason for the    \\\" is that we need to provide enough quoting that the string can be
#reprocessed by the shell into the final iptables command.
#  \\\"  is interpreted as \\ (a \) and \" (a "), resulting in a  \" in the
#iptables command line.  When that command line is processed, it will see the  \"
#and (hopefully) stuff  "  into the string to be matched in the kernel.
#   *fingers crossed*

				Head=$Head$Head2
				case $Head in
				00)	#Null
					Failed "0x00 in $Input"
					#Output="$Output"`echo -e "\x$Head"`	#No output at all.
					;;
				01)
					Failed "0x01 in $Input"
					#Output="$Output"`echo -e "\x$Head"`	#No output at all.
					;;
				0[2-9B-Fb-f]|1[0-9A-Fa-f])	#Low control characters
					Output="$Output"`echo -e "\x$Head"`
					;;
				0[Aa])	#Newline
					#Output="$Output"'\\'`echo -e "\x0A"`	#We get the \, but no lf.
					Failed "0x0A in $Input"
					;;
				2[0356A-Fa-f]|3[0-9A-Fa-f]|4[0-9A-Fa-f]|5[0-9ABDEFabdef]|6[1-9A-Fa-f]|7[0-9A-Ea-e])
					#Few if any problems in printing.
					Output="$Output"`echo -e "\x$Head"`
					;;
				21)	# !
					Failed "0x21 in $Input"
					#Output="$Output\\\!"	#Gives extra \
					#Output="$Output"'!'	#when iptables run, bash interprets the !
					;;
				22)	# "
					Output="$Output\\\""
					;;
				27)	# '
					#Output="$Output\\\'"	#Gives extra \
					#Output="$Output\'"	#Gives extra \
					Output="$Output'"	#Fine
					;;
				28)	# (
					#Output="$Output\\("	#Gives extra \
					Output="$Output("	#Fine
					;;
				29)	# )
					#Output="$Output\\\)"	#Gives extra \
					#Output="$Output\)"	#Gives extra \
					Output="$Output)"	#Fine
					;;
				24)	#$
					Output="$Output"'\$'
					;;
				5[Cc])	#\
					Output="$Output"'\\'
					;;
				60)	# `
					#Output="$Output"'\\\`'	#Gives extra \
					Output="$Output"'\`'	#Fine
					;;
				7[Ff])	#Del
					Failed "0x7f in $Input"
					#Output="$Output"`echo -e "\x$Head"`
					;;
				[89A-Fa-f][0-9A-Fa-f])	#High extended ascii (drawing, etc.) characters.
					Output="$Output"`echo -e "\x$Head"`
					;;
				*)
					Debug "Unhandled character $Head"
					#Debug "Conv$Head"
					;;
				esac
			fi
		fi
	done

	echo "$Output"
#	echo >/dev/stderr
}


#ToAscii regression test
if /bin/false ; then
	error () {
		echo Test $1 failed
	}

	if [ ! "`ToAscii "Abc"`" =								"Abc"			]; then error ToAscii-1 ; fi
	if [ ! "`ToAscii "|20|"`" =								" "			]; then	error ToAscii-2 ; fi
	if [ ! "`ToAscii "Abc|20|Def"`" =							"Abc Def"		]; then	error ToAscii-3 ; fi
	if [ ! "`ToAscii "|20|Abc|20|"`" =							" Abc "			]; then	error ToAscii-4 ; fi
	if [ ! "`ToAscii "| 20   |Abc|    20 |"`" =						" Abc "			]; then	error ToAscii-5 ; fi
	if [ ! "`ToAscii "| 2 0  2  0  |"`" =							"  "			]; then	error ToAscii-6 ; fi
	if [ ! "`ToAscii "a||b||||c||||||d"`" =							"abcd"			]; then	error ToAscii-7 ; fi
	if [ ! "`ToAscii "a|||20|||b"`" =							"a b"			]; then error ToAscii-8 ; fi
	if [ ! "`ToAscii "|6E 61 6D 65 20 3D 22 70 69 63 73 34 79 6F 75 2E 65 78 65 22|"`" =	"name =\\\"pics4you.exe\\\"" ]; then error ToAscii-9 ; fi
	if [ ! "`ToAscii "|66 69 63 6B 65 6E|"`" =						"ficken"		]; then	error ToAscii-10 ; fi
	#if [ ! "`ToAscii ""`" =								""			]; then	error ToAscii-x ; fi
fi



ProcessPrimaryFields () {
	PrimaryParams=""
	#$1: alert, config, include, preprocessor, var
	case "$1" in
	"")
		:	#Ignore null line
		;;
	alert)
		#Debug Primary: "$*"
		#$2: tcp, udp, icmp, ip, *.rules
		case "$2" in 
		ip)
			if [ "$4" != "any" ] || [ "$7" != "any" ]; then
				Debug IP protocol with source port "$4" and dest port "$7"
			fi
			;;
		tcp|TCP)
			PrimaryParams="$PrimaryParams -p tcp"
			;;
		udp|UDP)
			PrimaryParams="$PrimaryParams -p udp"
			;;
		icmp|ICMP)
			if [ "$4" != "any" ] || [ "$7" != "any" ]; then
				Debug ICMP protocol with source port "$4" and dest port "$7"
			fi
			PrimaryParams="$PrimaryParams -p icmp"
			;;
		*)
			Debug unrecognized protocol "$2"
			;;
		esac

		#$5: ->  <-  <>  
		#calling routine handles '<>', this function handles '<-' or '->'
		case "$5" in
		-\>)
			LeftDir="s"
			RightDir="d"
			;;
		\<-)
			LeftDir="d"
			RightDir="s"
			;;
		*)
			Failed "Unhandled direction $5"
			;;
		esac

		#$3: netvars, any
		if [ "$3" != "any" ]; then
			PrimaryParams="$PrimaryParams -$LeftDir $3"
		fi

		#$4: ports, any (port ranges a:b , a: )	#iptables takes a:, :b quite well.
		if [ "$4" != "any" ]; then
			PrimaryParams="$PrimaryParams --${LeftDir}port $(echo "$4" | sed 's/!/! /')"
		fi

		#$6: netvars, ips, any
		if [ "$6" != "any" ]; then
			PrimaryParams="$PrimaryParams -$RightDir $6"
		fi

		#$7: port  a:b  a:  !a:b  :b  any 
		if [ "$7" != "any" ]; then
			PrimaryParams="$PrimaryParams --${RightDir}port $(echo "$7" | sed 's/!/! /')"
		fi
		;;
	config)
		:
		#Debug Skipping classification
		;;
	include)
		Debug 'Um, why did I get an include line?'
		;;
	preprocessor)
		Debug 'Um, why did I get a preprocessor line?'
		;;
	var)
		Debug 'Um, why did I get a var line?'
		;;
	*) 
		Failed "Unknown line starting with $*"
		;;
	esac
	#PrimaryParams is passed back as a global variable
}

ProcessSecondaryFields () {
	SecondaryParams=""
	Comment=""
	LogString=""
	DashMStringLoaded=""
	#Glue "itype: mmm; icode: nnn;" together into itype: mmm/nnn;
	AllSecondary=`echo "$*" | sed -e 's@\(itype:[ ]*[0-9]*\)[ ]*;[ ]*icode:[ ]*\([0-9]*;\)@\1/\2@'`
	HeadField=`echo "$AllSecondary" | sed -e 's/;.*//' -e 's/^ //'`
	AllSecondary=`echo "$AllSecondary"  | sed -e 's/[^;]*;//'`
	while [ -n "$HeadField" ]; do
		HeadValue=$(StripOptionName $HeadField)
		case "$HeadField" in
		\))	#FIXME
			:
			Failed 'Stray right parentheses.'
			;;
		ack:*)
			#ack: 0
			#ack: 101058054
			#ack:0
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ack
			Failed "$HeadField"
			;;
		classtype:*)
			#classtype: attempted-admin
			#classtype: attempted-dos
			#classtype: successful-admin
			#classtype:attempted-admin
			#classtype:attempted-dos
			#classtype:attempted-recon
			#classtype:attempted-user
			#classtype:bad-unknown
			#classtype:not-suspicious
			#classtype:unknown
			#classtype:unsuccessful-user
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/classtype
			Comment="$Comment $HeadField"
			;;
		content:*)
#FIXME
#alert tcp any 110 -> any any (msg:"Virus - Possible PrettyPark Trojan"; content:"\\C*o*o*l*P*r*o*g*s\\";offset:300;depth:750; reference:MCAFEE,10175; sid:772; rev:1;)
#gives
#iptables -A SnortRules  -p tcp --sport 110  -m string --string CoolProgs"' -j DROP
#Try `iptables -h' or 'iptables --help' for more information.
#Bad argument `-'

#FIXME
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"WEB-MISC http directory traversal"; flags: A+; content: ".*.*\*\";reference:arachnids,298; classtype:attempted-recon; sid:1112; rev:1;)
#gives
#iptables -A SnortRules  -p tcp -s 0/0 -d 0/0 --dport 80  --tcp-flags ACK ACK -m string --string .."' -j DROP
#Try `iptables -h' or 'iptables --help' for more information.
#Bad argument `http'

#FIXME
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"WEB-MISC ads.cgi command execution attempt"; flags:A+; uricontent:"/a*d*s*.*c*g*i"; nocase; conten
#t:"f*i*l*e*="; nocase; content:"../../"; content:"\*|"; reference:cve,CAN-2001-0025; reference:bugtraq,2103; classtype:web-application-attack; sid:1053
#; rev:3;)
#gives
#--string "
#and
#Unhandled character "

			#content: "..."
			#content:"..."
			# | toggles to and from hex.
			#Ignore embedded spaces in hex strings.
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/content
			if [ -z "$DashMStringLoaded" ]; then
				SecondaryParams="$SecondaryParams -m string"
				DashMStringLoaded="Yes"
			fi
			if `echo $HeadValue | grep -q '|'`; then
				#Failed "$HeadField"
				SecondaryParams="$SecondaryParams --string "`ToAscii "$HeadValue"`
			else
				#Ce n'est pas un pipe... :-)
				SecondaryParams="$SecondaryParams --string $HeadValue"
			fi
			;;
		depth:*)
			#depth: nnn
			#depth:nnn
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/depth
			:
			#We won't care about depth on the assumption that this is an optimization.
			;;
		dsize:*)
			#dsize: nnn or > nnn or < nnn
			#dsize:nnn or > nnn or < nnn
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/dsize
			Failed "$HeadField"	#Doesn't appear to be an iptables equivalent
			;;
		flags:*)
			#flags: A
			#flags: A+
			#flags: AP
			#flags: F
			#flags: PA12
			#flags: S
			#flags: SA
			#flags: SF
			#flags: SF12
			#flags: SFP
			#flags: SFU12
			#flags: U+
			#flags:0
			#flags:A
			#flags:A+
			#flags:FPU
			#flags:PA
			#flags:S
			#flags:SA
			#flags:SF
			#flags:SFPU
			#flags:SRAFPU
			#flags:a+
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/flags
			FlagA='' ; FlagF='' ; FlagP='' ; FlagS='' ; FlagR='' ; FlagU='' ; Flag1='' ; Flag2='' ; FlagPlus=''
			HeadByte=`echo "$HeadValue" | sed -e 's/^\(.\).*/\1/'`
			HeadValue=`echo "$HeadValue" | sed -e 's/^.//'`
			while [ -n "$HeadByte" ]; do
				case "$HeadByte" in
				0)
					FlagA='' ; FlagF='' ; FlagP='' ; FlagS='' ; FlagR='' ; FlagU='' ; Flag1='' ; Flag2='' ; FlagPlus=''
					;;
				A|a)
					FlagA="ACK"
					;;
				F|f)
					FlagF="FIN"
					;;
				P|p)
					FlagP="PSH"
					;;
				S|s)
					FlagS="SYN"
					;;
				R|r)
					FlagR="RST"
					;;
				U|u)
					FlagU="URG"
					;;
				1)
					Failed Flag1
					#Flag1="ZZ"
					;;
				2)
					Failed Flag2
					#Flag2="ZZ"
					;;
				+)
					FlagPlus="Yes"
					;;
				*)
					Debug Invalid HeadByte in flags: "$HeadByte"
					;;
				esac
				HeadByte=`echo "$HeadValue" | sed -e 's/^\(.\).*/\1/'`
				HeadValue=`echo "$HeadValue" | sed -e 's/^.//'`
			done
			#$FlagA $FlagF $FlagP $FlagS $FlagR $FlagU $Flag1 $Flag2
			if [ -n "$FlagA$FlagF$FlagP$FlagS$FlagR$FlagU$Flag1$Flag2" ]; then
				AllFlags=""
				for OneFlag in $FlagA $FlagF $FlagP $FlagS $FlagR $FlagU $Flag1 $Flag2 ; do
					if [ -z "$AllFlags" ]; then
						AllFlags="$OneFlag"
					else
						AllFlags="$AllFlags,$OneFlag"
					fi
				done
			else
				AllFlags="NONE"
			fi

			if [ "$FlagPlus" = "Yes" ]; then
				SuperSet="$AllFlags"
			else
				SuperSet="ALL"
			fi

			if [ "$AllFlags" != "NONE" ] || [ "$SuperSet" != "NONE" ]; then
				SecondaryParams="$SecondaryParams --tcp-flags $SuperSet $AllFlags"
			fi
			;;
		fragbits:*)
			#fragbits: M
			#fragbits: M+
			#fragbits:M
			#fragbits:R
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/fragbits
			Failed "$HeadField"
			;;
		icmp_id:*)
			#icmp_id: 0
			#icmp_id: 1000
			#icmp_id: 123
			#icmp_id: 456
			#icmp_id: 51201
			#icmp_id: 666
			#icmp_id: 666 
			#icmp_id: 667
			#icmp_id: 668
			#icmp_id: 669
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/icmp_id
			Failed "$HeadField"	#There does not seem to be a netfilter equivalent.
			;;
		icmp_seq:*)
			#icmp_seq: 0
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/icmp_seq
			Failed "$HeadField"
			;;
		icode:*)
			#As iptables wants these glued together in a single field, the 
			#sed at the top of this function should have glued
			#"itype: mmm; icode: nnn;" together into itype: mmm/nnn;
			#icode: 0
			#icode: 1
			#icode:0
			#icode:1
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/icode
			Failed "$HeadField"
			;;
		id:*)
			#id: 39426
			#id: 413
			#id: 666
			#id: 678
			#id:13170
			#id:242
			#id:3868
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/id
			Failed "$HeadField"
			;;
		ip_proto:*)
			#ip_proto: 2
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ip_proto
			SecondaryParams="$SecondaryParams --proto $HeadValue"
			;;
		ipopts:*)
			#ipopts: rr
			#ipopts: ssrr 
			#ipopts:lsrr
			#ipopts:lsrre
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ipopts
			case "$HeadValue" in
			rr)
				SecondaryParams="$SecondaryParams -m ipv4options --rr"
				;;
			ssrr)
				SecondaryParams="$SecondaryParams -m ipv4options --ssrr"
				;;
			lsrr)
				SecondaryParams="$SecondaryParams -m ipv4options --lsrr"
				;;
			lsrre)
				#What is this?
				Failed "$HeadField"
				;;
			esac
			;;
		itype:*)
			#itype:[ ]0-39
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/itype
			SecondaryParams="$SecondaryParams -m icmp --icmp-type $HeadValue"
			;;
		msg:*)
			#msg:[ ]"..." text description
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/msg
			Comment="$Comment $HeadValue"
			;;
		nocase)
			#nocase (verbatim)
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/nocase
#FIXME - handle more gracefully
			if true ; then
				Comment="$Comment nocase-ignored"
			else
				Failed "$HeadField"
			fi
			;;
		offset:*)
			#offset:[ ]0-300
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/offset
			:
			#We won't care about offset on the assumption that this is an optimization.
			;;
		reference:*)
			#reference:[ ]ascii, no quotes
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/reference
			case "$HeadValue" in
			cve*)
				if [ -z "$LogString" ]; then
					LogString=`echo "$HeadValue" | sed -e 's/cve, /cve-/' -e 's/cve,/cve-/'`
				else
					LogString="$LogString,"`echo "$HeadValue" | sed -e 's/cve, /cve-/' -e 's/cve,/cve-/'`
				fi
				;;
			*)
				Comment="$Comment $HeadValue"
				;;
			esac
			;;
		rev:*)
			#rev:[ ]1-4
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/rev
			#Don't really care about the revision number.
			:
			;;
		rpc:*)
			#rpc: 100000,*,*
			#rpc:100249,*,*
			#rpc:391029,*,*
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/rpc
			Failed "$HeadField"
			;;
		sameip)
			#sameip (verbatim)
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/sameip
			Failed "$HeadField"
			;;
		seq:*)
			#seq: 101058054
			#seq: 1958810375
			#seq: 3868
			#seq: 6060842
			#seq: 674711609
			#seq:0
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/seq
			Failed "$HeadField"
			;;
		sid:*)
			#sid:[ ]nnnn
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/sid
			Comment="$Comment $HeadField"
			;;
		ttl:*)
#FIXME
#iptables -A SnortRules -p icmp -s 0/0 -d 0/0 -m ttl --ttl-eq 1 -m icmp --icmp-type 8 -j DROP
#iptables v1.2.3: Can't specify TTL option twice

			#ttl: >220
			#ttl:1
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ttl
			case "$HeadValue" in
			\>*)
				SecondaryParams="$SecondaryParams -m ttl --ttl-gt $(echo "$HeadValue" | sed -e 's/>//')"
				;;
			\<*)
				SecondaryParams="$SecondaryParams -m ttl --ttl-lt $(echo "$HeadValue" | sed -e 's/<//')"
				;;
			*)
				SecondaryParams="$SecondaryParams -m ttl --ttl-eq $HeadValue"
				;;
			esac
			;;
		uricontent*)
			#uricontent:[ ]"..."
			#echo "$HeadField" >>/usr/src/snort2iptables-work/fields/uricontent
			if [ -z "$DashMStringLoaded" ]; then
				SecondaryParams="$SecondaryParams -m string"
				DashMStringLoaded="Yes"
			fi
			if `echo $HeadValue | grep -q '|'`; then
				#Failed "$HeadField"
				SecondaryParams="$SecondaryParams --string "`ToAscii "$HeadValue"`
			else
				#Ce n'est pas un pipe... :-)
				SecondaryParams="$SecondaryParams --string $HeadValue"
			fi



			;;
		*)
			echo "$HeadField" >>/usr/src/snort2iptables-work/fields/newfields
			Failed "$HeadField"
			;;
		esac
		HeadField=`echo "$AllSecondary" | sed -e 's/;.*//' -e 's/^ //'`
		AllSecondary=`echo "$AllSecondary" | sed -e 's/[^;]*;//'`
	done
	#SecondaryParams is passed back as a global variable
}

ProcessLine () {
	#Debug "$*"
	FailedReasons=''
	PrimaryFields=`echo "$*" | sed -e 's/ (.*//'`
	SecondaryFields=`echo "$*" | sed -e 's/[^(]* (//' -e 's/)$//'`
	PrimaryParams=''
	SecondaryParams=''
	ProcessPrimaryFields $PrimaryFields
	if [ -n "$PrimaryParams" ]; then
		ProcessSecondaryFields $SecondaryFields
	fi
}


OutputRule () {
	if [ -n "$1$2" ]; then
		if [ -n "$FailedReasons" ]; then
			if [ "$DoLog" = "yes" ]; then
				if [ -n "$LogString" ]; then
					echo "#iptables -A SnortRules$1$2 -j LOG --log-prefix \" $LogString \"	#Cannot convert:$FailedReasons	$Comment"
				else
					echo "#iptables -A SnortRules$1$2 -j LOG	#Cannot convert:$FailedReasons	$Comment"
				fi
			fi
			if [ -n "$DoAction" ]; then
				echo "#iptables -A SnortRules$1$2 -j $DoAction	#Cannot convert:$FailedReasons	$Comment"
			fi
		else
			if [ "$DoLog" = "yes" ]; then
				if [ -n "$LogString" ]; then
					echo "iptables -A SnortRules$1$2 -j LOG --log-prefix \" $LogString \"	#$Comment"
				else
					echo "iptables -A SnortRules$1$2 -j LOG	#$Comment"
				fi
			fi
			if [ -n "$DoAction" ]; then
				echo "iptables -A SnortRules$1$2 -j $DoAction	#$Comment"
			fi
		fi
	fi
}


ProcessFile () {
#FIXME - recursion: local variables and file descriptor (case passed incrementing parameter)
#Don't use includes for the moment.
#Params:
#- RecursionLevel (0 for top level file, 1 for file included from 0, 2 for file included in 1, etc.)
#- Filename to process
	local RecursionLevel="$1"
	shift

	local OneLine
	local Params
	
	if [ -z "$1" ]; then
		Debug Missing filename in ProcessFile
		return 1
	fi
	if [ ! -r "$1" ]; then
		Debug File "$1" is not readable
		return 1
	fi
	
	#Read kludge:
	#Redirect stdin to work around an f^@&ing annoying limitation in the read command.
	case "$RecursionLevel" in
	0)
		exec 5<&0 < "$1"
		;;
	1)
		exec 6<&0 < "$1"
		;;
	2)
		exec 7<&0 < "$1"
		;;
	3)
		exec 8<&0 < "$1"
		;;
	*)
		echo Recursion level too deep in ProcessFile, exiting. >/dev/stderr
		exit 1
		;;
	esac
	while read OneLine ; do
		case "$OneLine" in
		\#*)
			:	#Ignore comment lines
			;;
		include*)
			Debug Processing `echo "$OneLine" | sed -e 's/include //'`
			ProcessFile $[ $RecursionLevel + 1 ] `echo "$OneLine" | sed -e 's/include //'`
			;;
		preprocessor*)
			:
			#FIXME
			#Debug Skipping preprocessor
			;;
		var*)
			echo "$OneLine" | sed -e 's/var //' -e 's/ *$//' -e 's/ /=/' -e 's@any@0/0@'
			;;
		alert\ ip\ *\ any\ -\>\ *\ :1023\ *)
			#Debug Special processing ip with port: "alert ip $EXTERNAL_NET any -> $HOME_NET :1023 ..."
			#call twice, once with tcp ....
			OneLine=`echo "$OneLine" | sed -e 's/ ip / tcp /'`
			ProcessLine "$OneLine"
			OutputRule "$PrimaryParams" "$SecondaryParams"

			#...and once with udp
			OneLine=`echo "$OneLine" | sed -e 's/ tcp / udp /'`
			ProcessLine "$OneLine"
			OutputRule "$PrimaryParams" "$SecondaryParams"
			;;
		*\<\>*)
			#call twice, once with -> ....
			OneLine=`echo "$OneLine" | sed -e 's/<>/->/'`
			ProcessLine "$OneLine"
			OutputRule "$PrimaryParams" "$SecondaryParams"

			#...and once with <-
			OneLine=`echo "$OneLine" | sed -e 's/->/<-/'`
			ProcessLine "$OneLine"
			OutputRule "$PrimaryParams" "$SecondaryParams"
			;;
		*)
			#-\>|\<-)
			ProcessLine "$OneLine"
			OutputRule "$PrimaryParams" "$SecondaryParams"
			;;
		esac
	done
	case "$RecursionLevel" in
	0)
		exec 0<&5 5<&-
		;;
	1)
		exec 0<&6 6<&-
		;;
	2)
		exec 0<&7 7<&-
		;;
	3)
		exec 0<&8 8<&-
		;;
	*)
		echo Invalid recursion level in ProcessFile, exiting. >/dev/stderr
		exit 1
		;;
	esac
}

usage () {
	echo This script is designed to convert a snort ruleset to iptables rules. >/dev/stderr
	echo Since it can recurse into included files, simply give it the top-level >/dev/stderr
	echo ruleset, such as /etc/snort/snort.conf . >/dev/stderr
	echo >/dev/stderr
	echo Usage: >/dev/stderr
	echo -e '\t' "$0" ' [--log] [--drop|--reject] SnortRuleFile [SnortRuleFile...]' >/dev/stderr
	echo Example: >/dev/stderr
	echo -e '\t' "$0" ' --log /etc/snort/snort.conf' >/dev/stderr
	echo You should pick at least one of log, drop, or reject to get any output. >/dev/stderr
	echo Log can be mixed with drop or reject. >/dev/stderr
}


if [ -z "$1" ]; then
	usage
	exit 1
fi

DoLog=''
DoAction=''

while [ -n "$1" ]; do
	case "$1" in
	--[Ll][Oo][Gg])
		DoLog="yes"
		;;
	--[Dd][Rr][Oo][Pp])
		DoAction="DROP"
		;;
	--[Rr][Ee][Jj][Ee][CC][TT])
		DoAction="REJECT"
		;;
	*)
		if [ -r "$1" ]; then
			ProcessFile 0 "$1"
		else
			echo Unable to read "$1", skipping. >/dev/stderr
		fi
		;;
	esac
	shift
done





