#!/bin/sh

RCSID='$Id: dhcpclient,v 1.14 2002/08/02 22:56:29 csutton Exp $'

USAGE=$"Revision: $RCSID
Usage:
  dhcpclient { get <dhcpserver> <clientid> <parpoolfile> <timeout> <dhcpClientBin> [dhcpClientIf] }
  dhcpclient { cleanup }
"

######################################################################
# TROUBLESHOOTING
#
# socket: Address family not supported by protocol - make sure
# CONFIG_PACKET (Packet socket) and CONFIG_FILTER
# (Socket Filtering) are enabled in your kernel configuration!
######################################################################

######################################################################
# FIREWALL RULES 
# 
# If you are running the DHCP server or client on a computer that's also acting 
# as a firewall, you must be sure to allow DHCP packets through the firewall. In 
# particular, your firewall rules _must_ allow packets from IP address 0.0.0.0 
# to IP address 255.255.255.255 from UDP port 68 to UDP port 67 through. They 
# must also allow packets from your local firewall's IP address and UDP port 67 
# through to any address your DHCP server might serve on UDP port 68. Finally, 
# packets from relay agents on port 67 to the DHCP server on port 67, and vice 
# versa, must be permitted. 
#
# We have noticed that on some systems where we are using a packet filter, if 
# you set up a firewall that blocks UDP port 67 and 68 entirely, packets sent 
# through the packet filter will not be blocked. However, unicast packets will 
# be blocked. This can result in strange behaviour, particularly on DHCP 
# clients, where the initial packet exchange is broadcast, but renewals are 
# unicast - the client will appear to be unable to renew until it starts 
# broadcasting its renewals, and then suddenly it'll work. The fix is to fix the 
# firewall rules as described above. 
######################################################################

if [ -f "./vars" ]; then
	echo "NOTE: using local defs."
	. vars
else
	QUIET=yes
	CLEANUP=yes
	WORKINGDIR=/tmp
	DEFAULT_TIMEOUT=2
fi

DHCPCLIENTSYSLOG=authpriv.debug
#DHCPCLIENTSYSLOGTAG=`basename $0`
DHCPCLIENTSYSLOGTAG=dhcpagent	# compliments agent process

######################################################################

# convert Unix time to string
ctime() {
	perl -e '
		require "ctime\.pl";
		print &ctime(@ARGV);
	' $1
}

logit()
{
	if [ "$QUIET" != "yes" ]; then
		logger -s -p $DHCPCLIENTSYSLOG -t $DHCPCLIENTSYSLOGTAG "($$) $@"	# log to stderr too
	else
		logger -p $DHCPCLIENTSYSLOG -t $DHCPCLIENTSYSLOGTAG "($$) $@" # just log to syslog
	fi
}

######################################################################

remove_entry()
{
	if [ -f "$PARPOOLFILE" -a -n "$1" ]; then
		while [ -n "`cat $PARPOOLFILE | grep -e "\[${1}\]"`" ]; do
			cat $PARPOOLFILE | sed "/^\[${1}\]/,/^expiry*./d" > $PARPOOLFILE
		done
		logit "Removed existing entries for client: \"$1\" from cache."
	fi
}

stop_client()
{
	if [ -s "$TMPWRK/dhclient.pid" ]; then
		logit "Stopping DHCP client process ..."
		kill -10 `cat $TMPWRK/dhclient.pid` 1>/dev/null 2>&1
	else
		logit "Stopping ALL DHCP client processes ..."
		killall -10 `basename $DHCLIENT` 1>/dev/null 2>&1
	fi
}

kill_client()
{
	if [ -s "$TMPWRK/dhclient.pid" ]; then
		logit "Killing DHCP client process ..."
		kill -9 `cat $TMPWRK/dhclient.pid` 1>/dev/null 2>&1
	else
		logit "Killing ALL DHCP client processes ..."
		killall -9 `basename $DHCLIENT` 1>/dev/null 2>&1
	fi
}

killall_cleanup()
{
	kill_client	# shut down ALL clients with SIGKILL
	[ "$CLEANUP" = "yes" ] && rm -fr $WORKINGDIR/dhcpclient* 1>/dev/null 2>&1
}

do_client()
{
	touch $PARPOOLFILE

	if [ ! -f "$PARPOOLFILE" ]; then
		logit "ERROR: cache file: $PARPOOLFILE does not exist."
		RC=1
		return
	fi

	# make sure that cached DHCP leases' absolute
	# lifetimes are not reused
	remove_entry "$CLIENTID"

	if [ ! -x "$DHCLIENT" ]; then
		logit "ERROR: DHCP client binary: $DHCLIENT does not exist."
		RC=1
		return
	fi

	# hold on to PAR pool filename so this script can
	# get the filename when dhclient calls it back
	echo "$PARPOOLFILE" > $TMPWRK/par.tmp

	# hold on to phase 1 identity so this script can
	# save it in pool entry when dhclient calls it back
	echo "$CLIENTID" > $TMPWRK/clientid.tmp

	if [ -z "$DISCOVER_TIMEOUT" -o $DISCOVER_TIMEOUT -lt 6 ]; then
		DISCOVER_TIMEOUT=$DEFAULT_TIMEOUT
	else
		# adjust for agent timeout to ensure that this script finishes first
		DISCOVER_TIMEOUT=$(($DISCOVER_TIMEOUT - 5))
	fi

	logit "Starting DHCP Client; cache file: $PARPOOLFILE, timeout: $DISCOVER_TIMEOUT sec."

	# copy myself into working dir and set as client callback script
	cp -af $0 $TMPWRK/callback.sh
	chmod 755 $TMPWRK/callback.sh

	cat > $TMPWRK/dhclient.conf <<EOF
pseudo "iked-pseudo-interface" "$PHYSIF" {
	send dhcp-client-identifier "$CLIENTID";
	send user-class "PAR-ipsec-client";
	request subnet-mask, domain-name-servers, netbios-name-servers;
	require subnet-mask;
	timeout $DISCOVER_TIMEOUT;
	retry 0;
	reboot 0;
	initial-interval 2;
	script "$TMPWRK/callback.sh";
}
EOF

	cat > $TMPWRK/dhclient.leases <<EOF
$DEFAULT_LEASE
EOF

	$DHCLIENT $OPTIONS -q -d -1 -s $DHCPSERVER -pf $TMPWRK/dhclient.pid -lf $TMPWRK/dhclient.leases -cf $TMPWRK/dhclient.conf $PHYSIF 1>/dev/null 2>&1
	RC=$?
	[ "$RC" -eq 138 ] && RC=0 # client shut down with SIGUSR1 by callback
}

######################################################################

case "$1" in

  -h|--h|-help|--help)

	echo "$USAGE"
	exit 0

	;;

  cleanup|--cleanup)

	killall_cleanup
	exit 0

	;;

  get|--get)

	if [ -z "$2" -o -z "$3" -o -z "$4" -o -z "$5" -o -z "$6" -o -z "$7" ]; then
		echo "$USAGE"
		exit 0
	fi

	TMPWRK="$WORKINGDIR/dhcpclient-$$"
	mkdir -p $TMPWRK

	DHCPSERVER=$2
	CLIENTID=$3
	PARPOOLFILE=$4
	DISCOVER_TIMEOUT=$5
	DHCLIENT=$6
	PHYSIF=$7
	unset IPADDR

	# ALWAYS broadcast client DHCPDISCOVER and DHCPREQUEST
	DHCPSERVER=255.255.255.255

	if [ -n "$8" ]; then
		# REBOOT
		IPADDR=$8
		DEFAULT_LEASE="\
		lease {
		  interface \"$PHYSIF\";
		  fixed-address $IPADDR;
		  expire 0 2020/1/1 00:00:01;
		}"
	fi

	OPTIONS="$DHCLIENT_OPTIONS $GADDR"

	logit "GET: client: \"$CLIENTID\"."

	unset RC
	do_client

	# remove temp files
	[ "$CLEANUP" = "yes" ] && rm -fr $TMPWRK 1>/dev/null 2>&1

	logit "Returning status: $RC."
	exit $RC

	;;

######################################################################

  rel|--rel)

	if [ -z "$2" -o -z "$3" -o -z "$4" -o -z "$5" -o -z "$6" -o -z "$7" -o -z "$8" ]; then
		echo "$USAGE"
		exit 0
	fi

	TMPWRK="$WORKINGDIR/dhcpclient-$$"
	mkdir -p $TMPWRK

	DHCPSERVER=$2
	CLIENTID=$3
	PARPOOLFILE=$4
	DISCOVER_TIMEOUT=$5
	DHCLIENT=$6
	PHYSIF=$7
	IPADDR=$8

	DEFAULT_LEASE="\
	lease {
	  interface \"$PHYSIF\";
	  fixed-address $IPADDR;
	}"

	OPTIONS="-r $DHCLIENT_OPTIONS"

	logit "RELEASE: client: \"$CLIENTID\", address: $IPADDR."

	unset RC
	do_client

	# remove temp files
	[ "$CLEANUP" = "yes" ] && rm -fr $TMPWRK 1>/dev/null 2>&1

	logit "Returning status: $RC."
	exit $RC

	;;

######################################################################

  *)
	TMPWRK=`dirname $0`

	logit "CALLBACK: reason: $reason."

	if [ "$reason" = "RELEASE" ]; then
		exit 0	# client will shut down on its own
	fi

	if [ "$reason" = "PREINIT" -o \
			"$reason" = "MEDIUM" -o \
			"$reason" = "EXPIRE" ]; then
		exit 0	# don't shut down yet, dhclient is not done
	fi

	if [ "$reason" = "FAIL" -o \
			"$reason" = "TIMEOUT" -o \
			-z "$reason" ]; then
		kill_client	# shut down client with SIGKILL
		exit 1
	fi

	[ -f "$TMPWRK/par.tmp" ] && PARPOOLFILE="`cat $TMPWRK/par.tmp`"
	[ -f "$TMPWRK/clientid.tmp" ] && CLIENTID="`cat $TMPWRK/clientid.tmp`"

	if [ "$reason" = "BOUND" -o \
         "$reason" = "RENEW" -o \
         "$reason" = "REBIND" -o \
         "$reason" = "REBOOT" ]; then

#		logit "   Server:           $new_dhcp_server_identifier"
#		logit "   Client:           $client"
#		logit "   Interface:        $interface"
#		logit "   New hostname:     $new_host_name"
#		logit "   New IP address:   $new_ip_address"
#		logit "   New subnet:       $new_subnet_mask"
#		logit "   New broadcast:    $new_broadcast_address"
#		logit "   New network:      $new_network_number"
#		logit "   New domain:       $new_domain_name"
#		logit "   New DNS:          $new_domain_name_servers"
#		logit "   New WINS:         $new_netbios_name_servers"
#		logit "   DHCP msg type:    $new_dhcp_message_type"
#		logit "   Lease time:       $new_dhcp_lease_time"
#		logit "   Rebinding time:   $new_dhcp_rebinding_time"
#		logit "   Renewal time:     $new_dhcp_renewal_time"
#		logit "   Expiry time_t:    $new_expiry"

#		EXPIRYCTIME=`ctime $new_expiry`
#		EXPIRY=`date -d "$EXPIRYCTIME" +'%m-%d-%Y %H:%M:%S'`

		TIMESTAMPUNIX=`date +'%s'`
		EXPIRY=$(($new_expiry - $TIMESTAMPUNIX))

		if [ -z "$CLIENTID" ]; then
			logit "ERROR: no client ID specified."
			kill_client	# shut down client with SIGKILL
			exit 1
		fi

		CLIENTIDSEC="[$CLIENTID]"
		ADDRESSKEYSTR="address=\"$new_ip_address"
		NETWORKKEYSTR="network=\"$new_network_number"
		SUBNETMASKKEYSTR="subnetmask=\"$new_subnet_mask"
		DNSKEYSTR="dns=\"$new_domain_name_servers"
		WINSKEYSTR="wins=\"$new_netbios_name_servers"
		EXPIRYKEYSTR="expiry=#$EXPIRY"

		if [ -z "$PARPOOLFILE" ]; then
			logit "ERROR: no PAR pool file specified."
			kill_client	# shut down client with SIGKILL
			exit 1
		fi

		# eliminate duplicates
		remove_entry "$CLIENTID"

		cat >> $PARPOOLFILE <<EOF
$CLIENTIDSEC
$ADDRESSKEYSTR
$NETWORKKEYSTR
$SUBNETMASKKEYSTR
$DNSKEYSTR
$WINSKEYSTR
$EXPIRYKEYSTR
EOF

		logit "Created PAR pool cache entry for client: \"$CLIENTID\"; address: $new_ip_address."
		stop_client	# shut down client with SIGUSR1
		exit 0
	fi

	;;

esac

# should never get here
exit 3
