#! /bin/bash
#
# Copyright (c) 2002 SuSE Linux AG Nuernberg, Germany. All rights reserved.
# 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., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
#
# Author: Christian Zoz <zoz@suse.de>, 2002
#         Peter Poeml <poeml@suse.de>, 2002
#
# $Id: ifup-dhcp 1169 2005-03-07 04:54:17Z zoz $
#

usage () {
	echo $@
	echo "usage: if{up,down,status}-dhcp [<config>] <interface> [-o <options>]"
	echo 
	echo "options are: boot      : we are currently booting"
	echo "             hotplug   : we are handling a hotplug event"
	echo "             quiet     : supress normal output"
	echo "             debug     : be verbose"
	echo "             rc        : indicates that we are called from rcnetwork"
	echo "all other or wrong options are silently ignored"
	exit $R_USAGE
}

dhcpc_on_iface() {
	local i
	for i in `pidof $DHCLIENT`; do
		case $(cat /proc/$i/cmdline) in
		    *$INTERFACE*) return 0
		esac
	done
	return 1
}

######################################################################
# change the working direcory and source some common files
#
R_INTERNAL=1      # internal error, e.g. no config or missing scripts
cd /etc/sysconfig/network || exit $R_INTERNAL
test -f scripts/functions && . scripts/functions || exit $R_INTERNAL
test -f ./config && . ./config

######################################################################
# check arguments and how we are called (in case of links)
#
SCRIPTNAME=${0##*/}
debug $*

case "${SCRIPTNAME}" in
	ifup-*) ACTION=start ;;
	ifdown-*) ACTION=stop ;;
	ifstatus-*) ACTION=status ;;
	*) usage
esac

INTERFACE=$1
case "$INTERFACE" in ""|-h|*help*) usage; esac
shift
if [ -n "$1" -a "$1" != "-o" ] ; then
	CONFIG=$INTERFACE
	INTERFACE=$1
fi
shift
test "$1" = "-o" && shift
OPTIONS="$@"
MODE=manual
while [ $# -gt 0 ]; do
	case $1 in
		boot|onboot) MODE=onboot ;;
		hotplug)     MODE=hotplug ;;
		quiet)       BE_QUIET=yes ;;
		debug)       BE_QUIET=no
		             DEBUG=yes ;;
		rc)          RUN_FROM_RC=yes ;;
		*)           debug unknown option $1 ;;
	esac
	shift
done

######################################################################
# check presence of configuration files
#
test -f ./dhcp && . ./dhcp
test -f ./ifcfg-$CONFIG && . ./ifcfg-$CONFIG

######################################################################
# get the interface and check if it is up
#
if [ -z "$INTERFACE" ] ; then
	usage "No interface given"
fi
if ! is_iface_available $INTERFACE ; then
	# When a hotplug remove event occurs the interface has already gone.
	# Nevertheless we have to clean up.
	if [ \( "$ACTION" != stop -o "$MODE" != hotplug \) -a "$INTERFACE" != all ] ; then
		logerror "interface ${INTERFACE} is not available"
		exit $R_NODEV
	fi
fi

######################################################################
# determine type of dhcp client
#
# This script is supposed to work with both dhcpcd and the ISC DHCP client,
# because there's a lot of common stuff.
# If neccesary you can preselect a dhcp client; just set DHCLIENT_BIN in one of
# the used config files. If not we prefer dhcpcd if available.
: ${DHCLIENT_BIN:=dhcpcd}
if [ -x $DHCLIENT_BIN ]; then 
	:
elif [ -x /sbin/$DHCLIENT_BIN ]; then 
	DHCLIENT_BIN=/sbin/$DHCLIENT_BIN; 
elif [ -x /sbin/dhclient ]; then
	DHCLIENT_BIN=/sbin/dhclient
else
	if [ "$INTERFACE" = all ]; then 
		# 'rcnetwork stop' also calls us with "ifdown-dhcp all" in the end to make
		# sure that all DHCP clients are stopped.  But if there is none installed,
		# there is nothing to do and we can silently quit
		exit $R_SUCCESS
	else
		logerror "DHCP client $DHCLIENT_BIN is not available"
		logerror "please install package 'dhcpcd' or 'dhcp-client'"
		exit $R_ERROR
	fi
fi
DHCLIENT=${DHCLIENT_BIN##*/}


case "$ACTION" in
	start)
		# avoid that the temporary resolv.conf will persist after the client stops
		# just because there hasn't been any resolv.conf to back up
		test -e /etc/resolv.conf || touch /etc/resolv.conf

		# if we were called at boot time, increase the time startproc waits,
		# because the next scripts might rely on a configured network
		# test "$MODE" = "boot" && STARTPROC_TIMEOUT=5

		# a previous dhcpcd daemon could have been killed leaving its pidfile, 
		# and we need to remove it before we can start a new one.
		if [ $DHCLIENT = dhcpcd ]; then 
			if [ -s /var/run/dhcpcd-$INTERFACE.pid ]; then
				checkproc -k -p /var/run/dhcpcd-$INTERFACE.pid $DHCLIENT_BIN
				ret=$? 
				if [ $ret = 1 -o $ret = 7 ]; then 
					message removing stale pid file /var/run/dhcpcd-$INTERFACE.pid
					rm -f /var/run/dhcpcd-$INTERFACE.pid
				fi
			fi
		fi

		if checkproc $DHCLIENT_BIN || [ "$DHCLIENT_PRIMARY_DEVICE" = no ]; then
			
			# let's first test if the running dhcpcd actually runs on this
			# interface, because then we don't want to start another one:
			already_running=false
			for i in `pidof $DHCLIENT`; do
				case $(cat /proc/$i/cmdline) in
				    *$INTERFACE*) already_running=true;;
				esac
			done
			if $already_running; then
				if test "$RUN_FROM_RC" = yes; then
					while read a b c d e f g h i; do
						message "`printf "    %-9s IP address: %s (DHCP was already running)" $i $d`"
						R_DHCP_BG=$R_SUCCESS
					done < <(ip -o -4 address show $INTERFACE)
					if [ "$R_DHCP_BG" != "$R_SUCCESS" ] ; then
						message "`printf "    %-9s DHCP already running" $INTERFACE`"
					fi
					exit $R_DHCP_BG
				else
					message    "DHCP client is already running on $INTERFACE"
				fi
				exit $R_SUCCESS
			fi
		
			# there is already a dhcpcd running (or scheduled). We probably don't
			# want to set another default gateway and so on. Let's clear some
			# variables and source the ifcfg-$CONFIG again -- unless
			# DHCLIENT_PRIMARY_DEVICE is set to "yes" in ifcfg-$CONFIG.

			if [ "$DHCLIENT_PRIMARY_DEVICE" != yes ]; then 

				unset DHCLIENT_SET_HOSTNAME \
				      DHCLIENT_SET_DOMAINNAME \
				      DHCLIENT_KEEP_SEARCHLIST \
				      DHCLIENT_MODIFY_RESOLV_CONF \
				      DHCLIENT_SET_DEFAULT_ROUTE \
				      DHCLIENT_MODIFY_NTP_CONF \
				      DHCLIENT_MODIFY_NIS_CONF 

				test -f ./ifcfg-$CONFIG && . ./ifcfg-$CONFIG
			fi
		fi

		debug "Starting service dhcp client on $INTERFACE"
		ip link set $INTERFACE up ${MTU:+mtu $MTU}
		for a in 1 2 3 4 5; do is_iface_up $INTERFACE && break; sleep 1; done

		# (optionally) wait until a device is really configured
		sleep ${DHCLIENT_SLEEP:-0}

		if [ "$DHCLIENT" = "dhcpcd" ] ; then 
			# The following DHCLIENT_ARGS are specific to dhcpcd. 
			# (the ISC DHCP client can be finetuned via /etc/dhclient.conf 
			# and so-called hooks (see man 8 dhclient-script)
			test "$DHCLIENT_DEBUG"               = "yes" && args="-d"
			test "$DHCLIENT_UDP_CHECKSUM"        = "yes" && args="$args -C"
			test "$DHCLIENT_SET_HOSTNAME"        = "yes" && args="$args -H"
			test "$DHCLIENT_SET_DOMAINNAME"      = "yes" && args="$args -D"
			test "$DHCLIENT_KEEP_SEARCHLIST"     = "yes" && args="$args -K"
			test "$DHCLIENT_MODIFY_RESOLV_CONF" != "yes" && args="$args -R"
			test "$DHCLIENT_SET_DEFAULT_ROUTE"  != "yes" && args="$args -G"
			test "$DHCLIENT_MODIFY_NTP_CONF"    != "yes" && args="$args -N"
			test "$DHCLIENT_MODIFY_NIS_CONF"    != "yes" && args="$args -Y"
			test -n "$DHCLIENT_TIMEOUT" \
				&& args="$args -t $DHCLIENT_TIMEOUT"
			test -n "$DHCLIENT_REBOOT_TIMEOUT" \
				&& args="$args -z $DHCLIENT_REBOOT_TIMEOUT"
			test -n "$DHCLIENT_CLIENT_ID" \
				&& args="$args -I $DHCLIENT_CLIENT_ID"
			test -n "$DHCLIENT_VENDOR_CLASS_ID" \
				&& args="$args -i $DHCLIENT_VENDOR_CLASS_ID"
			test -n "$DHCLIENT_LEASE_TIME" \
				&& args="$args -l $DHCLIENT_LEASE_TIME"
			test -n "$DHCLIENT_ADDITIONAL_OPTIONS" \
				&& args="$args $DHCLIENT_ADDITIONAL_OPTIONS"

			# send hostname for DDNS?
			case "$DHCLIENT_HOSTNAME_OPTION" in AUTO|auto)
				unset DHCLIENT_HOSTNAME_OPTION
				test -s /etc/HOSTNAME && read -t 1 FQHOSTNAME < /etc/HOSTNAME
				FQHOSTNAME=${FQHOSTNAME%%.*} # strip domain
				FQHOSTNAME=${FQHOSTNAME%% *} # must not contain spaces, just in case
				DHCLIENT_HOSTNAME_OPTION=$FQHOSTNAME
				;;
			esac
			test -n "$DHCLIENT_HOSTNAME_OPTION" \
				&& args="$args -h $DHCLIENT_HOSTNAME_OPTION"

			DHCLIENT_ARGS=$args

			# just in case /var is not mounted
			test -d /var/lib/dhcpcd || mkdir -p /var/lib/dhcpcd
			timestamp=/var/lib/dhcpcd/dhcpcd-$INTERFACE.timestamp; touch $timestamp
			info=/var/lib/dhcpcd/dhcpcd-$INTERFACE.info

			if test "$RUN_FROM_RC" = yes; then 
				message_n "`printf "    %-9s (DHCP) " $INTERFACE`"
				# message_n "(DHCP) " 
			else
				message_n "Starting DHCP Client Daemon on $INTERFACE... "
			fi

			# now start dhcpcd
			$DHCLIENT_BIN $DHCLIENT_ARGS $INTERFACE </dev/null &>/dev/null &

			# wait some more time until dhcpcd has done its job.
			# it would be easier to wait for the pidfile, but it is written later
			for ((i=0; i<${DHCLIENT_WAIT_AT_BOOT:-15}; i++)); do 
				if [ -e $info -a ! $info -ot $timestamp ]; then continue
				else message_n .\ ; sleep 1
				fi
			done
			if [ -e $info -a ! $info -ot $timestamp ]; then 
				while read line; do
					case "$line" in 
						IPADDR*) message_n ${line/IPADDR=/IP/Netmask: };; 
						NETMASK*) message_n " / ${line/NETMASK=/}";; 
						HOSTNAME*) message_n " (${line/HOSTNAME=/})";; 
					esac
				done < $info
				message " "
				R_DHCP_BG=$R_SUCCESS
			else
				message "no IP address yet... backgrounding. "
			fi
			exit $R_DHCP_BG


		else
			# if we are here, then $DHCLIENT is the ISC dhclient

			# don't write copyright info
			DHCLIENT_ARGS="-q" 
			# If ISC dhclient should be used for more than one interfaces, we have
			# to stop it and restart it with all interfaces when adding an
			# interface.  !!! Currently, we do not provide that.
			if checkproc $DHCLIENT_BIN; then
				logerror "$DHCLIENT is already running for some interface." \
				         "Stop it first.\nIf you want to run dhcp on several " \
				         "interfaces, please use dhcpcd."
			else
				startproc -f -t ${STARTPROC_TIMEOUT:-1} -q $DHCLIENT_BIN \
				          $DHCLIENT_ARGS $INTERFACE
			fi
		fi

		;;
	stop)
		debug "Shutting down service dhcp client on $INTERFACE"

		case $DHCLIENT in 
		dhcpcd) 
			# let's first test if any dhcpcd processes are running on this
			# interface.  Unfortunately, the pid file is not there when it starts
			# up, so we can't rely on that.  Normally, only one process is running
			# per interface. but we have made bad experiences with ...hotplug 
			for i in `pidof $DHCLIENT`; do
				case $(cat /proc/$i/cmdline) in
					*$INTERFACE*) dhcpcd_on_device="$dhcpcd_on_device $i"
				esac
			done

			test "$DHCLIENT_RELEASE_BEFORE_QUIT" = "yes" \
			  && USE_SIGNAL="-HUP" || USE_SIGNAL="-TERM"

			for i in $dhcpcd_on_device; do
				kill $USE_SIGNAL $i &>/dev/null
			done

                        # wait until the processes are gone
			for i in $dhcpcd_on_device; do
				for ((wait=0; wait<30; wait++)); do
					if test -d /proc/$i; then usleep 300000
					else continue 2
					fi
					if [ $wait = 20 ]; then
						echo process $i still not dead, sending SIGKILL
						kill -KILL $i &>/dev/null
					fi
				done
			done

			if [ -z "$dhcpcd_on_device" ]; then 
				# dhcpcd may not be running (it quits if it gets an 
				# infinite lease)
				info=/var/lib/dhcpcd/dhcpcd-$INTERFACE.info
				if test -s $info && grep -q LEASETIME=4294967295 $info; then
					debug "dhcpcd is no longer running on $INTERFACE, since it got"
					debug "an infinite lease. Manually shutting down the interface."
					ip address flush dev $INTERFACE &>/dev/null
					ip link set dev $INTERFACE down &>/dev/null
				fi
			fi

			;;

		dhclient)

			# If ISC dhclient is used for more than one interfaces, we have to stop
			# it and restart it without this interface when removing one interface,
			# or we have to use the OMAPI interface
			# !!! Currently, we do not provide that.
			killproc $PIDFILE $DHCLIENT_BIN &>/dev/null 
			;;
		esac

		test "$INTERFACE" = all && killall $DHCLIENT_BIN &>/dev/null

		# This is useful for pcmcia interfaces... before the modules can be
		# unloaded, the device must be unused.
		if [ "$DHCLIENT_SET_DOWN_LINK" = yes ] ; then
			ip link set down dev $INTERFACE
		fi
		;;
	status)
		if test -z "`ip -o -f inet addr show $INTERFACE`"; then
			# interface has currently no ipv4 address assigned
			if dhcpc_on_iface ; then
				message "`printf "    %-9s %s is still waiting for data" $INTERFACE $DHCLIENT`"
				exit $R_DHCP_BG
			else
				message "`printf "    %-9s DHCP client NOT running" $INTERFACE`"
				exit $R_NOTRUNNING
			fi
		else 
			# interface has at least one ipv4 address
			if dhcpc_on_iface ; then
				message "`printf "    %-9s DHCP client (%s) is running" $INTERFACE $DHCLIENT`"
				# Show more info if not run from initscript
				if [ "$RUN_FROM_RC" != yes ] ; then
					case $DHCLIENT in 
						dhcpcd)
							if test -e /var/lib/dhcpcd/dhcpcd-$INTERFACE.info; then
								# message "              current lease for $INTERFACE:" 
								while read line; do
									case "$line" in 
										IPADDR*|NETMASK*|GATEWAY*|HOSTNAME*|DOMAIN*)
											message "              "$line ;;
										DNS*|DHCPCHADDR*|DHCPSIADDR*|REBINDTIME*)
											message "              "$line ;;
										*) ;;
									esac
								done < /var/lib/dhcpcd/dhcpcd-$INTERFACE.info
							else 
								message /var/lib/dhcpcd/dhcpcd-$INTERFACE.info file \
								        not available
							fi
							;;
						dhclient)
							if test -e /var/lib/dhcp/dhclient.leases; then 
								message last lease in /var/lib/dhcp/dhclient.leases:
								awk 'BEGIN {RS="lease[[:blank:]]*{"; FS="}"} {lease=$1} END {print "{" lease "} "}' \
								    /var/lib/dhcp/dhclient.leases
							else
								message /var/lib/dhcp/dhclient.leases file not available
							fi
							;;
					esac
				fi
			else
				message "`printf "    %-9s DHCP client NOT running" $INTERFACE`"
				if [ "$RUN_FROM_RC" = yes ] ; then
					while read a b c d e f g h i; do
						message "`printf "    %-9s IP address: %s (DHCP)" ${i:-$b} $d`"
					done < <(ip -o -4 address show $INTERFACE)
				else
					message "$(ip addr show $INTERFACE)"
				fi
				exit $R_NOTRUNNING
			fi


		fi
esac
