#!/bin/bash
#Copyright 2003 William Stearns <wstearns@pobox.com>
#Released under the GPL.

#Many thanks to the denizens of the Sans handlers list for their input
#on this module, especially Chris Brenton, Gary Kessler, George Bakos,
#Kevin Timm, Robert Wagner, Remko Lodder, Johannes Ullrich, and Vasily
#Tomilin.  Thanks too to Don Cohen for his input on u32 issues.


Me='plength'
MyVersion='0.3.3'
#FIXME - check this module carefully, then make DefaultActions='DROP'
DefaultActions='NONE'

[ -r /etc/firebricks/firebricks.conf ] &&			. /etc/firebricks/firebricks.conf
[ -r /etc/firebricks/$Me.conf ] &&				. /etc/firebricks/$Me.conf
[ -r ${FBLibDir:-'/usr/lib/firebricks/'}/firebrickslib ] &&	. ${FBLibDir:-'/usr/lib/firebricks/'}/firebrickslib
if [ -z "$FBLibVer" ]; then
	echo 'It looks like firebrickslib was not loaded, why?  Exiting' >&2
	exit 1
fi

for OneTask in $Tasks ; do
	case "$OneTask" in
	link)
		$IptablesBin -N $Me >/dev/null 2>&1
		$IptablesBin $AppIn INPUT -i \! lo -m length --length 0:512				-j $Me
		$IptablesBin $AppIn FORWARD -m length --length 0:512					-j $Me
		$IptablesBin $AppIn OUTPUT -m length --length 0:512					-j $Me
		;;
	unlink)
		$IptablesBin -D INPUT -i \! lo -m length --length 0:512					-j $Me
		$IptablesBin -D FORWARD -m length --length 0:512					-j $Me
		$IptablesBin -D OUTPUT -m length --length 0:512						-j $Me
		$IptablesBin -X $Me >/dev/null 2>&1
		;;
	create)
		echo "Starting $Me" >&2
		FlushOrNewChain $Me

		#Drop fragmented syn or syn/ack packets:
		LogAs='FragmentedSyn'	$Ipt -A $Me -p tcp --tcp-flags SYN SYN -m u32 --u32 '"4&0x3FFF=1:0x3FFF"'	$Tail

		#Check for payload on a syn/syn+ack.  To do so, we use u32
		#to jump over the ip and tcp headers, and simply test the first
		#payload byte for 0:255.  If there is any payload at all, this
		#will return true; if there is no payload, u32 will realize it's
		#being asked to test a non-existant byte and return false.
		#Marvelously elegant; thanks to Don Cohen for this.
		LogAs='PayloadOnSyn'	$Ipt -A $Me  -p tcp --tcp-flags SYN SYN -m u32 --u32 '"0>>22&0x3C@12>>26&0x3C@-3&0xFF=0:255"'	$Tail

		#rfc791:
		#Every internet module must be able to forward a datagram of 68 octets
		#without further fragmentation.  This is because an internet header may
		#be up to 60 octets, and the minimum fragment is 8 octets.
		#So, any fragment except the last that is less than 68 bytes was
		#artificially fragmented.
		#We can't have MF set and have a packet smaller than 68 bytes.
		LogAs='TooSmallNonTerminalFrag'	$Ipt -A $Me -m u32 --u32 '"3&0x20>>5=1"' -m length --length 0:67	$Tail
		#MF set, 68-511 bytes, technically legal, but _way_ unlikely.
		LogAs='SmallNonTerminalFrag'	$Ipt -A $Me -m u32 --u32 '"3&0x20>>5=1"' -m length --length 68:511	$Tail

		#Last fragment (MF clear, non-zero frag offset) must be >=21 bytes
		LogAs='TooSmallTerminalFrag'	$Ipt -A $Me -m u32 --u32 '"3&0x20>>5=0 && 4&0x1FFF=1:65535"' -m length --length 0:20	$Tail

		#OK, we're done testing fragments, so we go back if MF is set
		#$IptablesBin -A $Me -m u32 --u32 '3&0x20>>5=1'								-j RETURN
		#...or the Frag offset is >0
		#$IptablesBin -A $Me -m u32 --u32 '4&0x1FFF=1:65535'							-j RETURN
		#Actually, check for set MF or non-zero offset all at once:
		$IptablesBin -A $Me -m u32 --u32 '4&0x3FFF=1:65535'							-j RETURN

		#Now we're left with unfragmented packets
		#Complete UDP >=28 bytes
		LogAs='Toosmalludp'	$Ipt -A $Me -p udp -m length --length 0:27			$Tail
		#Complete TCP >=40 bytes
		LogAs='Toosmalltcp'	$Ipt -A $Me -p tcp -m length --length 0:39			$Tail
		#Complete ICMP; 28 bytes for no payload ping or chargen request; smaller ones?
		LogAs='Toosmallicmp'	$Ipt -A $Me -p icmp -m length --length 0:27			$Tail
		#protocol 30, should be >=32 bytes
		LogAs='Toosmallproto30'	$Ipt -A $Me -p 30 -m length --length 0:31			$Tail
		#protocol 47 GRE, >=40
		LogAs='Toosmallproto47'	$Ipt -A $Me -p 47 -m length --length 0:39			$Tail
		#protocol 50 ESP, >=50
		LogAs='Toosmallproto50'	$Ipt -A $Me -p 50 -m length --length 0:49			$Tail
		#protocol 51 AH, >=36
		LogAs='Toosmallproto51'	$Ipt -A $Me -p 51 -m length --length 0:35			$Tail
		#IP packets, >=20
		LogAs='ToosmallIP'	$Ipt -A $Me -m length --length 0:19				$Tail
		;;
	destroy)
		echo "Stopping $Me" >&2
		DestroyChain $Me
		;;
	renamechain)
		TempChain="$Me-$RANDOM"
		echo "Replacing existing rules in $Me with new rules" >&2
		$IptablesBin -E $Me $TempChain
		;;
	replacelinks)
		if [ -z "$TempChain" ]; then
			echo "No temporary chain to relink in $Me replacelinks, replace operation incomplete." >&2
		elif ! $IptablesBin -L $Me -n >/dev/null 2>&1 ; then
			echo "No $Me chain in $Me, replace operation incomplete." >&2
		elif ! $IptablesBin -L $TempChain -n >/dev/null 2>&1 ; then
			echo "No $TempChain chain in $Me, replace operation incomplete." >&2
		elif [ "`$IptablesBin -L INPUT -n --line-numbers | grep $TempChain | wc -l`" -ne 1 ]; then
			echo "Too few/many references to $TempChain in INPUT in $Me replacelinks, replace operation incomplete." >&2
		elif [ "`$IptablesBin -L FORWARD -n --line-numbers | grep $TempChain | wc -l`" -ne 1 ]; then
			echo "Too few/many references to $TempChain in FORWARD in $Me replacelinks, replace operation incomplete." >&2
		elif [ "`$IptablesBin -L OUTPUT -n --line-numbers | grep $TempChain | wc -l`" -ne 1 ]; then
			echo "Too few/many references to $TempChain in OUTPUT in $Me replacelinks, replace operation incomplete." >&2
		else
			$IptablesBin -R INPUT `$IptablesBin -L INPUT -n --line-numbers | grep $TempChain | awk '{print $1}'` -i \! lo -m length --length 0:512	-j $Me
			$IptablesBin -R FORWARD `$IptablesBin -L FORWARD -n --line-numbers | grep $TempChain | awk '{print $1}'` -m length --length 0:512	-j $Me
			$IptablesBin -R OUTPUT `$IptablesBin -L OUTPUT -n --line-numbers | grep $TempChain | awk '{print $1}'` -m length --length 0:512		-j $Me
			DestroyChain $TempChain
			unset TempChain
		fi
		;;
	status)
		if $IptablesBin -L $Me -n >/dev/null 2>&1 ; then
			echo "$Me created" >&2
		else
			echo "$Me destroyed" >&2
		fi
		;;
	version)
		echo "$Me $MyVersion, firebrickslib $FBLibVer" >&2
		;;
	help)
		DefaultHelp
		cat <<EOTEXT >&2
	The $Me module takes a closer look at the lengths of certain
types of packets.  Certain types of packets should be _at least_ N bytes
long.  For example, since normal fragmentation produces non-terminal
fragments that are at least the MTU large, we should never see a
non-terminal packet smaller than 68 bytes (the rfc791 minimum MTU) or
even less than 512 bytes (the Internet practical minimum MTU).  See the
notes in this file for more details about the packet types and reasons
for their minimums lengths.
	We\'ve put enough thought into this module that it _should_ be
safe to use, but it could really use a few days on an unprotected ISP
router to be sure I haven\'t missed some subtle issue.  It might be
worth running this with an action of NONE for a few hours before putting
it into production use.
EOTEXT
		;;
	*)
		echo "Unknown action $Action in $Me, no action taken." >&2
		;;
	esac
done
