#!/bin/sh
#
# Author: Robert Allmeroth
#
# $Id: dhclient-script,v 1.2 2019/04/25 11:45:57 artur_sanasaryan Exp $
#
# extract from dhclient-script.8
#
# following reasons are defined:
#
# MEDIUM	: set interface media type
#			  $interface
#			  $medium
# PREINIT	: DHCP needs to send packets, so set interface up (with no IP address)
#			  $interface
# ARPCHECK	: feedback to DHCP to retrieve information about ARPSEND
# ARPSEND	: DHCP wants to know if someone else has this IP address
#			  $interface
#			  $check_...
# BOUND		: initial binding to a new address
#			  $interface
#			  $medium
#			  $new_ip_address
#			  $old_...
#			  any dhcp-options ( '-' -> '_' )
# RENEW		: binding has been renewed
#			  $new_...
#			  $old_...
# REBIND	: rebound to a new DHCP server (similar to RENEW except if IP changed)
# REBOOT	: successful reaquired on old address after reboot (similar to BOUND)
# EXPIRE	: failed to renew / aquire the ip address - clean up
# FAIL		: no DHCP server found - no IP address assigned
# TIMEOUT	: no DHCP server found - but using an old leased IP address
#
# be careful: this script rewrite does not care about ip address aliases
#

# source the current configuration of interfaces
. /etc/sysconfig/rc.conf
. /etc/sysconfig/network.conf

# source the current configuration of interfaces
. /var/run/interfaces

LASTVALIDADDR=/var/state/last_valid_ipaddress.dhcp

# source in the features
if [ -e "/etc/image_features" ] ; then
    . /etc/image_features
fi
if [ -e "/tmp/featurestatus.conf" ] ; then
    . /tmp/featurestatus.conf
fi

LOG=/tmp/logs/dhclient.log
#LOG=/dev/null

CON=/dev/ttyS0
LAST_DHCLIENT_EVENT=/tmp/lastDHClientEvent

if [ -r /var/run/system_is_ready ] ; then
# output messages only while booting.. to 'shorten' the time ;)
	CON="/dev/null"
fi

do_SERVICE_START ()
{
	echo "starting services" > $LOG

	/bin/addroute -b -s allWAN > /dev/null 2>&1
	/bin/addroute -b -p allWAN > /dev/null 2>&1

	echo -n " named:         " > $CON
	if [ "$START_NAMED" = "true" ] ; then
		echo /etc/init.d/named restart > $LOG
		/etc/init.d/named restart > /dev/null 2>&1 && echo "done"  > $CON || echo "failed"  > $CON
	else
		echo "skipped" > $CON
	fi

	if [ "$START_PPTP_UPLINK" = "true" ] ; then
		# in case of pptp we must not start services except named. the ppp will do that.
		# if the pptp server is in another network we need a static route so that the pptp is able
		# to reach it after the default route has been rewritten
		return
	fi

	echo -n " ntp:           " > $CON
	if [  "$START_NTP" = "true" ] ; then
		echo /etc/init.d/ntp restart > $LOG
		/etc/init.d/ntp restart > /dev/null 2>&1 &
		echo "async" > $CON
	else
		echo "skipped" > $CON
	fi

	echo -n " vpn-handler:  " > $CON
	if [ "$SEC_START_VPN" = "true" ] && [ "$FEATURE_VPN" = "1" ] ; then
	    if [ "$START_VPND" = "false" ] ; then
			echo "skipped" > $CON
	    else
			echo /etc/init.d/vpnd restart > $LOG
			/etc/init.d/vpnd restart > /dev/null
			echo "done" > $CON
	    fi
	else
	    echo "feature disabled"
	fi

	echo -n " ipsec daemon:  " > $CON
	if [ "$SEC_START_VPN" = "true" ] && [ "$FEATURE_VPN" = "1" ] ; then
	    if [ "$START_IPSECD" = "false" ] ; then
			echo "skipped" > $CON
	    else
			echo /etc/init.d/ipsecd restart > $LOG
			/etc/init.d/ipsecd restart > /dev/null
			echo "done" > $CON
	    fi
	else
	    echo "feature disabled"
	fi

	echo -n " ipsec:         " > $CON
	if [ "$SEC_START_VPN" = "true" ] && [ "$FEATURE_VPN" = "1" ] ; then
	    if [ "$START_IPSEC" = "true" ] ; then
			echo /etc/init.d/ipsec restart > $LOG
			/etc/init.d/ipsec restart > /dev/null 2>&1 &
			echo "async" > $CON
		else
			/bin/changepsk
			echo "skipped" > $CON
		fi
	else
	    echo "feature disabled" > $CON
	fi

	echo -n " dyndns:        " > $CON
	if [ "$START_DYNDNS" = "true" ] ; then
		if [ -f $LASTVALIDADDR ] ; then
			. $LASTVALIDADDR
		fi
		if [ "$LAST_IP_ADDRESS" != "$IPLOCAL" ] ; then
			echo /etc/init.d/dyndnsd restart > $LOG
			/etc/init.d/dyndnsd restart $PHYS_WAN $new_ip_address > /dev/null 2>&1 &
		fi
		echo "async" > $CON
	else
		echo "skipped" > $CON
	fi
}

do_SERVICE_STOP ()
{
	echo "stopping services" > $LOG

	if [ "$START_PPTP_UPLINK" != "true" ] ; then
		# in case of pptp we must not stop services except named. the ppp will do that

		if [ "$START_DYNDNS" = "true" ] ; then
			echo /etc/init.d/dyndnsd stop > $LOG
			/etc/init.d/dyndnsd stop
		fi
	
		## stop VPN features
		if [ "$SEC_START_VPN" = "true" ] && [ "$FEATURE_VPN" = "1" ] ; then
		    if [ "$START_IPSECD" = "true" ] ; then
				echo /etc/init.d/ipsecd stop > $LOG
				/etc/init.d/ipsecd stop
		    fi

		    if [ "$START_IPSEC" = "true" ] ; then
				echo /etc/init.d/ipsec stop > $LOG
				/etc/init.d/ipsec stop
		    fi

		    if [ "$START_VPND" = "true" ] ; then
				echo /etc/init.d/vpnd stop > $LOG
				/etc/init.d/vpnd stop
		    fi
		fi
	
		if [  "$START_NTP" = "true" ] ; then
			echo /etc/init.d/ntp stop > $LOG
			/etc/init.d/ntp stop
		fi
	
		if [ -f /var/run/dynforward.conf.dhcp ] ; then
			/bin/rm -f /var/run/dynforward.conf.dhcp
		fi
	fi

	if [ "$START_NAMED" = "true" ] ; then
		echo /etc/init.d/named restart > $LOG
		/etc/init.d/named restart
	fi

	/bin/addroute -b -S allWAN > /dev/null 2>&1
	/bin/addroute -b -P allWAN > /dev/null 2>&1
}


do_MEDIUM ()
{
	# do nothing.. we dont have different media types
	return
}

do_PREINIT ()
{
	echo " preinit - flushing interface" > $CON

	echo "/bin/ip -4 address flush dev $interface" > $LOG
	/bin/ip -4 address flush dev $interface > $LOG 2>&1

	echo "/bin/ip link set up dev $interface" > $LOG
	/bin/ip link set up dev $interface > $LOG 2>&1
}

do_ARPCHECK ()
{
	if [ -f /tmp/dhcp_arp_check_failed_"$check_ip_address"_"$check_subnet_mask" ] ; then
		echo "ARPCHECK failed for $check_ip_address/$check_subnet_mask - exit with 1" > $LOG
		/bin/rm -f /tmp/dhcp_arp_check_failed_"$check_ip_address"_"$check_subnet_mask"
		/bin/sleep 5
		exit 1
	fi
	return
}

do_ARPSEND ()
{
	local result_str
	local test
	local result_val

	echo -n " arpsend - testing $check_ip_address/$check_subnet_mask: " > $CON

	result_str=$(/bin/addr -r $check_ip_address/$check_subnet_mask)
	result_val=$?

	if [ "$result_val" != 0 ] ; then
		text=$(/bin/translate 'Error: IP address conflict. DHCP wants to set $check_ip_address but that ip address is used by this device')
		/bin/raise_event -a DHCPClient -n Error -t "$text"
		/bin/touch /tmp/dhcp_arp_check_failed_"$check_ip_address"_"$check_subnet_mask"
		echo "conflict" > $LAST_DHCLIENT_EVENT
		echo " failed - local conflict" >> $CON
		exit 1
	fi

	if /bin/ping -q -c 1 $check_ip_address ; then
		# whoa.. the ip address is reachable.. cannot be.. IP misconfiguration
		text=$(/bin/translate 'Error: IP address conflict. DHCP wants to set $check_ip_address but that host does already exist in your network')
		/bin/raise_event -a DHCPClient -n Error -t "$text"
		/bin/touch /tmp/dhcp_arp_check_failed_"$check_ip_address"_"$check_subnet_mask"
		echo "conflict" > $LAST_DHCLIENT_EVENT
		echo " failed - address exists in network" > $CON
		exit 1
	fi

	if ! /bin/arping -D -c 1 -I $interface $check_ip_address ; then
		# whoa.. the ip address is reachable.. DHCP misconfiguration
		text=$(/bin/translate 'Error: IP address conflict. DHCP wants to set $check_ip_address but that host does already exist')
		/bin/raise_event -a DHCPClient -n Error -t "$text"
		/bin/touch /tmp/dhcp_arp_check_failed_"$check_ip_address"_"$check_subnet_mask"
		echo "conflict" > $LAST_DHCLIENT_EVENT
		echo " failed - address exists in network" > $CON
		exit 1
	fi

	echo " success" > $CON
}

do_BOUND ()
{
	# take care about IP configuration

	# write all variables to config file so that others can see them
	set | grep "new_" > /tmp/dhclient-config-variables

	# import some important functions
	. /bin/network-functions

	echo " bound   - using $new_ip_address. Configuring interface" > $CON

	CURRENT_IP_ADDR=$(/bin/addr -i $interface)

	if [ -f $LASTVALIDADDR ] ; then
		. $LASTVALIDADDR
	fi

	if [ -n "$LAST_IP_ADDRESS" ] ; then
		if [ "$LAST_IP_ADDRESS" != "$new_ip_address" ] || [ "$CURRENT_IP_ADDR" != "$new_ip_address" ] ; then

			do_SERVICE_STOP

			echo "/bin/ip -4 addr flush dev $interface" > $LOG
			/bin/ip -4 addr flush dev $interface > $LOG 2>&1
		fi
	fi

	if [ "$reason" = "BOUND" ] || [ "$reason" = "REBOOT" ] ; then
		# initial assignment of IP address

		if [ -n "$interface_mtu" ] ; then
			echo /bin/ip link dev $interface mtu $interface_mtu > $LOG
			/bin/ip link dev $interface mtu $interface_mtu > $LOG 2>&1
		fi

		if [ -n "$ip_forwarding" ] && [ "$ip_forwarding" = "0" ] ; then
			text=$(/bin/translate 'WARNING: The provider does not want this device working as router! (ip-forwarding=$ip_forwarding)')
			/bin/raise_event -a DHCPClient -n Error -t "$text"
		fi

		NETMASK=$new_subnet_mask

		ABCMaskLen
		pfxlen=$?
		pfx="$new_ip_address/$pfxlen"

		if [ "$FEATURE_FIREWALL" = "1" ] ; then
			echo "/bin/fw_ci --add changeip --ip $new_ip_address" > $LOG
			/bin/fw_ci --add changeip --ip $new_ip_address > $LOG 2>&1
		fi

		# configure interface
		echo /bin/ip address add $pfx dev $interface broadcast $new_broadcast_address scope global > $LOG
		/bin/ip address add $pfx dev $interface broadcast $new_broadcast_address scope global > $LOG 2>&1

		echo 1 > /proc/sys/net/ipv4/conf/$interface/hidden

		if [ "$ARP_ANTIDOTE" = "true" ] ; then
			echo 1 > /proc/sys/net/ipv4/neigh/$interface/arp_antidote
		fi

		# configure default route
		# in the pptp case the default route will be overwritten by PPPD
		set $new_routers
		echo /bin/ip route add default dev $interface via $1 > $LOG
		/bin/ip route add default dev $interface via $1 > $LOG 2>&1
	fi

	# take care about name resolution
	rm -f /var/run/dynforward.conf.dhcp
	for nameserver in $new_domain_name_servers ; do
		echo "nameserver $nameserver" >> /var/run/dynforward.conf.dhcp
	done

	if [ -n "$LAST_IP_ADDRESS" ] ; then
		if [ "$LAST_IP_ADDRESS" != "$new_ip_address" ] || [ "$CURRENT_IP_ADDR" != "$new_ip_address" ] ; then
			# only if IP addresses have been changed, the whole startup sermon is necessary

			if [ "$SWAP_INTERFACES" = "true" ] ; then
				text=$(/bin/translate 'New IP address assigned to LAN interface. ($new_ip_address)')
			else
				text=$(/bin/translate 'New IP address assigned to WAN interface. ($new_ip_address)')
			fi

			/bin/raise_event -a DHCPClient -n "Got Lease" -t "$text"

			if [ "$START_PBX_ONLY_MODE" = "true" ] ; then
				/bin/ipeaddrchange
			fi

			do_SERVICE_START
		fi
	else
		# no old IP address.. so set the new one !

		if [ "$SWAP_INTERFACES" = "true" ] ; then
			text=$(/bin/translate 'New IP address assigned to LAN interface. ($new_ip_address)')
		else
			text=$(/bin/translate 'New IP address assigned to WAN interface. ($new_ip_address)')
		fi

		/bin/raise_event -a DHCPClient -n "Got Lease" -t "$text"

		if [ "$START_PBX_ONLY_MODE" = "true" ] ; then
			/bin/ipeaddrchange
		fi

		do_SERVICE_START
	fi

	echo "LAST_IP_ADDRESS=$new_ip_address" > $LASTVALIDADDR
	echo "new" > $LAST_DHCLIENT_EVENT

}

do_RENEW ()
{
	do_BOUND
}

do_REBIND ()
{
	do_BOUND
}

do_REBOOT ()
{
	do_BOUND
}

do_EXPIRE ()
{
	echo " expired - flushing interface" > $CON

	# shut down the interface

	if [ "$lastEvent" != "expired" ] ; then
	
		if [ "$SWAP_INTERFACES" = "true" ] ; then
			text=$(/bin/translate 'DHCP lease expired. IP configuration of LAN interface cleared.')
		else
			text=$(/bin/translate 'DHCP lease expired. IP configuration of WAN interface cleared.')
		fi
		
		/bin/raise_event -a DHCPClient -n Error -t "$text"
		echo "expired" > $LAST_DHCLIENT_EVENT
	fi

	do_SERVICE_STOP

	echo "/bin/ip -4 address flush dev $interface" > $LOG
	/bin/ip -4 address flush dev $interface > $LOG 2>&1

	if [ "$FEATURE_FIREWALL" = "1" ] ; then
		echo "/bin/fw_ci --remove changeip" > $LOG
		/bin/fw_ci --remove changeip > $LOG 2>&1
	fi

	echo "/bin/ip -4 neighbour flush dev $interface" > $LOG
	/bin/ip -4 neighbour flush dev $interface > $LOG 2>&1
}

do_STOP ()
{
	do_SERVICE_STOP

	echo "/bin/ip -4 address flush dev $interface" > $LOG
	/bin/ip -4 address flush dev $interface > $LOG 2>&1

	if [ "$FEATURE_FIREWALL" = "1" ] ; then
		echo "/bin/fw_ci --remove changeip" > $LOG
		/bin/fw_ci --remove changeip > $LOG 2>&1
	fi

	echo "/bin/ip -4 neighbour flush dev $interface" > $LOG
	/bin/ip -4 neighbour flush dev $interface > $LOG 2>&1
}

do_FAIL ()
{
	echo " failed" > $CON

	# check first if there was an configured interface. otherwise we have nothing to do here
	if addr -i $interface > /dev/null 2>&1 ; then
		do_EXPIRE
	else
		if [ "$SWAP_INTERFACES" = "true" ] ; then
			echo "do_FAIL - LAN interface not configured.. do nothing" > $LOG
		else
			echo "do_FAIL - WAN interface not configured.. do nothing" > $LOG
		fi
	fi
}

do_TIMEOUT ()
{
	echo " timeout - try to use old lease" > $CON

	# import some important functions
	. /bin/network-functions

	echo "new_subnet_mask=$new_subnet_mask" > $LOG
	NETMASK=$new_subnet_mask

	ABCMaskLen
	pfxlen=$?
	pfx="$new_ip_address/$pfxlen"

	if [ "$FEATURE_FIREWALL" = "1" ] ; then
		echo "/bin/fw_ci --add changeip --ip $new_ip_address" > $LOG
		/bin/fw_ci --add changeip --ip $new_ip_address > $LOG 2>&1
	fi

	# configure interface
	echo /bin/ip address add $pfx dev $interface broadcast $new_broadcast_address scope global > $LOG
	/bin/ip address add $pfx dev $interface broadcast $new_broadcast_address scope global > $LOG 2>&1

	set $new_routers
	echo /bin/ping -q -c 3 $1  > $LOG
	if /bin/ping -q -c 3 $1 > $LOG 2>&1 ; then
		# we got an answer from the router.. so lease could still be valid

		if [ "$START_PPTP_UPLINK" != "true" ] ; then
			# configure default route
			echo /bin/ip route add default dev $interface via $1 > $LOG
			/bin/ip route add default dev $interface via $1 > $LOG 2>&1

			# take care about name resolution

			rm -f /var/run/dynforward.conf.dhcp
			for nameserver in $new_domain_name_servers ; do
				echo "nameserver $nameserver" >> /var/run/dynforward.conf.dhcp
			done
		fi

		if [ "$lastEvent" != "old" ] ; then
			text=$(/bin/translate 'Could not reach the DHCP Server. Using old address $new_ip_address')
			/bin/raise_event -a DHCPClient -n Error -t "$text"
			echo "old" > $LAST_DHCLIENT_EVENT
		fi

		do_SERVICE_START

	else
		do_EXPIRE
		exit 1
	fi

}

echo > $LOG
echo "$0 date: $(date) call reason=$reason" > $LOG

if [ -f $LAST_DHCLIENT_EVENT ] ; then
	lastEvent=$(/bin/cat $LAST_DHCLIENT_EVENT)
else
	lastEvent=""
fi

case "$reason" in

	MEDIUM)
		do_MEDIUM
		;;
	PREINIT)
		do_PREINIT
		;;
	ARPCHECK)
		do_ARPCHECK
		;;
	ARPSEND)
		do_ARPSEND
		;;
	BOUND)
		do_BOUND
		;;
	RENEW)
		do_RENEW
		;;
	REBIND)
		do_REBIND
		;;
	REBOOT)
		do_REBOOT
		;;
	EXPIRE)
		do_EXPIRE
		;;
	FAIL)
		do_FAIL
		;;
	TIMEOUT)
		do_TIMEOUT
		;;
	STOP)
		do_STOP
		;;
#	*)
#		exit 1
#		;;
esac

###
### DHCPv6 Handlers
###

ip=$(command -v ip)
RESOLV6_CONF=/var/run/dynforward.conf.dhcp6

make_resolv_conf() {
  if [ x"$new_domain_name_servers" != x ]; then
    cat /dev/null > $RESOLV6_CONF
    chmod 644 $RESOLV6_CONF
    if [ x"$new_domain_search" != x ]; then
      echo search $new_domain_search >> $RESOLV6_CONF
    elif [ x"$new_domain_name" != x ]; then
      # Note that the DHCP 'Domain Name Option' is really just a domain
      # name, and that this practice of using the domain name option as
      # a search path is both nonstandard and deprecated.
      echo search $new_domain_name >> $RESOLV6_CONF
    fi
    for nameserver in $new_domain_name_servers; do
      echo nameserver $nameserver >> $RESOLV6_CONF
    done

    #mv $RESOLV6_CONF /etc/resolv.conf
	rm -f $RESOLV6_CONF
	
  elif [ "x${new_dhcp6_name_servers}" != x ] ; then
    cat /dev/null > $RESOLV6_CONF
    chmod 644 $RESOLV6_CONF

    if [ "x${new_dhcp6_domain_search}" != x ] ; then
      echo search ${new_dhcp6_domain_search} >> $RESOLV6_CONF
    fi

    for nameserver in ${new_dhcp6_name_servers} ; do
      # If the nameserver has a link-local address
      # add a <zone_id> (interface name) to it.
	  ns=$(echo "$nameserver" | awk '{print tolower($0)}')
	  if [[ $ns == fe80::* ]]
      then
		zone_id="%$interface"
      else
		zone_id=
      fi
      echo nameserver ${nameserver}$zone_id >> $RESOLV6_CONF
    done

    #mv $RESOLV6_CONF /etc/resolv.conf
	rm -f $RESOLV6_CONF
  fi
}


if [ x$reason = xPREINIT6 ] ; then
  # Ensure interface is up.
  ${ip} link set ${interface} up

  # Remove any stale addresses from aborted clients.
  ${ip} -f inet6 addr flush dev ${interface} scope global permanent

  exit 0
fi

if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
    echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}

    exit 0
fi

if [ x$reason = xBOUND6 ] ; then	
	echo "$reason: New IPv6:"${new_ip6_address}/${new_ip6_prefixlen} > $LOG
  if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
    exit 2;
  fi

  ${ip} -f inet6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
	dev ${interface} scope global

  # Check for nameserver options.
  make_resolv_conf

  exit 0
fi

if [ x$reason = xRENEW6 ] || [ x$reason = xREBIND6 ] ; then
  if [ x${new_ip6_address} != x ] && [ x${new_ip6_prefixlen} != x ] ; then
    ${ip} -f inet6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
	dev ${interface} scope global
  fi

  # Make sure nothing has moved around on us.

  # Nameservers/domains/etc.
  if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
     [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
	 echo "$reason: New dhcp6 name servers:"${new_dhcp6_name_servers} > $LOG
	 echo "$reason: New dhcp6 domain search:"${new_dhcp6_domain_search} >> $LOG
    make_resolv_conf
  fi

  exit 0
fi

if [ x$reason = xDEPREF6 ] ; then
  if [ x${new_ip6_prefixlen} = x ] ; then
    exit 2;
  fi

  ${ip} -f inet6 addr change ${new_ip6_address}/${new_ip6_prefixlen} \
       dev ${interface} scope global preferred_lft 0

  exit 0
fi

if [ x$reason = xEXPIRE6 -o x$reason = xRELEASE6 -o x$reason = xSTOP6 ] ; then
  if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
    exit 2;
  fi

  ${ip} -f inet6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
	dev ${interface}

  exit 0
fi


exit 0
