#!/bin/bash

# dhclient-script for Linux. Dan Halbert, March, 1997.
# Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
# Modified for Debian.  Matt Zimmerman and Eloy Paris, December 2003
# Modified to remove useless tests for antiquated kernel versions that
# this doesn't even work with anyway, and introduces a dependency on /usr
# being mounted, which causes cosmetic errors on hosts that NFS mount /usr
# Andrew Pollock, February 2005
# Modified to work on point-to-point links. Andrew Pollock, June 2005
# Modified to support passing the parameters called with to the hooks. Andrew Pollock, November 2005

# 'ip' just looks too weird.  /sbin/ip looks less weird.
ip=/sbin/ip


# modified make_resolv_conf () for Vyatta system below
make_resolv_conf() {
    if [ -n "$new_domain_name" ]; then
        /usr/bin/vyos-hostsd-client --set-host-name --search-domain $new_domain_name
    fi

    if [ -n "$new_dhcp6_domain_search" ]; then
        /usr/bin/vyos-hostsd-client --set-host-name --search-domain $new_dhcp6_domain_search
    fi

    if [ -n "$new_domain_name_servers" ] && ! cli-shell-api existsEffective system disable-dhcp-nameservers; then
        vyos-hostsd-client --delete-name-servers --tag dhcp-$interface
        NEW_SERVERS=""
        for nameserver in $new_domain_name_servers; do
            NEW_SERVERS="$NEW_SERVERS --name-server $nameserver"
        done
        /usr/bin/vyos-hostsd-client --add-name-servers $NEW_SERVERS --tag dhcp-$interface
    fi

    if [ -n "$new_dhcp6_name_servers" ] && ! cli-shell-api existsEffective system disable-dhcp-nameservers; then
        vyos-hostsd-client --delete-name-servers --tag dhcpv6-$interface
        NEW_SERVERS=""
        for nameserver in $new_dhcp6_name_servers; do
            NEW_SERVERS="$NEW_SERVERS --name-server $nameserver"
        done
        /usr/bin/vyos-hostsd-client --add-name-servers $NEW_SERVERS --tag dhcpv6-$interface
    fi

    if cli-shell-api existsEffective service dns forwarding; then
       /usr/libexec/vyos/conf_mode/dns_forwarding.py --dhclient
    fi
}

run_hook() {
    local script="$1"
    local exit_status
    shift	# discard the first argument, then the rest are the script's

    if [ -f $script ]; then
        . $script "$@"
    fi


    if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then
        logger -p daemon.err "$script returned non-zero exit status $exit_status"
        save_exit_status=$exit_status
    fi

    return $exit_status
}

run_hookdir() {
    local dir="$1"
    local exit_status
    shift	# See run_hook

    if [ -d "$dir" ]; then
        for script in $(run-parts --list $dir); do
            run_hook $script "$@" || true
            exit_status=$?
        done
    fi

    return $exit_status
}

# Must be used on exit.   Invokes the local dhcp client exit hooks, if any.
exit_with_hooks() {
    exit_status=$1

    # Source the documented exit-hook script, if it exists
    if ! run_hook /etc/dhcp/dhclient-exit-hooks "$@"; then
        exit_status=$?
    fi

    # Now run scripts in the Debian-specific directory.
    if ! run_hookdir /etc/dhcp/dhclient-exit-hooks.d "$@"; then
        exit_status=$?
    fi

    exit $exit_status
}

get_prefix() {
    # use existing perl module to compute prefix
    # one line don't bother with script
    perl -MNetAddr::IP \
	-e '$ip = new NetAddr::IP(@ARGV); print $ip->masklen(), "\n"' $*
}

set_hostname() {
    local current_hostname=$(hostname)
    if [ -z "$current_hostname" -o "$current_hostname" = "(none)" ]; then
        hostname "$new_host_name"
    fi
}

set_address() {
    if [ -n "$old_ip_address" ] &&
	[ "$old_ip_address" != "$new_ip_address" -o "$old_prefix" != "$new_prefix" ] ; then
	# Clear out route cache and ARP tables and all addresses and routes
	ip -family inet addr flush dev $interface
    fi

    if [ "$reason" = "BOUND" ] || [ "$reason" = "REBOOT" ] ||
       [ -z "$old_ip_address" -o "$old_ip_address" != "$new_ip_address" ] ||
       [ -z "$old_prefix" -o  "$old_prefix" != "$new_prefix" ] ; then
        ip -family inet addr add $new_ip_address/$new_prefix \
	    broadcast $new_broadcast_address dev $interface
    fi

    if [ -n "$new_interface_mtu" ] &&
       [ ! "$old_interface_mtu" = "$new_interface_mtu" ]; then
        ip link set $interface mtu $new_interface_mtu
    fi
}

# Administrative for DHCP routes. Should be configurable
ZEBRA_ROUTE_DHCP=210

update_routers() {
    if [ -n "$old_routers" ]; then
	# No change just renewing
	if [ "$reason" = "RENEW" -a "$old_routers" = "$new_routers" ]; then
	    return 0
	fi

	for router in $old_routers; do
	    if [ "$old_subnet_mask" == "255.255.255.255" ]; then
		vtysh -c "conf t" -c "no ip route $router/32 $interface $ZEBRA_ROUTE_DHCP" >/dev/null 2>&1
	    fi
	    vtysh -c "conf t" -c "no ip route 0.0.0.0/0 $router $ZEBRA_ROUTE_DHCP" >/dev/null 2>&1
	done
    fi

    for router in $new_routers; do
	# point to point
	if [ "$new_subnet_mask" == "255.255.255.255" ]; then
            if vtysh -c exit; then
	        vtysh -c "conf t" -c "ip route $router/32 $interface $ZEBRA_ROUTE_DHCP" >/dev/null 2>&1
            else
                ip -4 route add ${router}/32 dev $interface >/dev/null 2>&1
            fi
	fi

	# Note: this should really be $router and $interface but zebra does
	# not have way to specify this (yet).
        if vtysh -c exit; then
	    vtysh -c "conf t" -c "ip route 0.0.0.0/0 $router $ZEBRA_ROUTE_DHCP" >/dev/null 2>&1
        else
            ip -4 route add default via ${router} dev $interface >/dev/null 2>&1
        fi
    done
}

if [ -n "$new_subnet_mask" ]; then
    new_prefix=$(get_prefix $new_ip_address $new_subnet_mask)
fi
if [ -n "$old_subnet_mask" ]; then
    old_prefix=$(get_prefix $old_ip_address $old_subnet_mask)
fi
if [ -n "$new_interface_mtu" ]; then
    # Vyatta configuration overrides response from server to allow user
    # to work around broken ISP's
    mtu_path=$(/opt/vyatta/sbin/vyatta-interfaces.pl --dev=$interface)
    if [ -r $mtu_path ]; then
	read new_interface_mtu < $mtu_path
    fi

    # The 576 MTU is only used for X.25 and dialup connections
    # where the admin wants low latency.  Such a low MTU can cause
    # problems with UDP traffic, among other things.  As such,
    # disallow MTUs from 576 and below by default, so that broken
    # MTUs are ignored, but higher stuff is allowed (1492, 1500, etc).
    if [ $new_interface_mtu -le 576 ]; then
	new_interface_mtu=
    fi
fi

# The action starts here

# Invoke the local dhcp client enter hooks, if they exist.
run_hook /etc/dhcp/dhclient-enter-hooks
run_hookdir /etc/dhcp/dhclient-enter-hooks.d


# Remove static default kernel route if exists.
ip -4 route del default >/dev/null 2>&1

# Execute the operation
case "$reason" in
    MEDIUM)
        # Do nothing
        ;;

    PREINIT)
        ip -family inet addr flush dev $interface
        ip link set $interface up

        if [ -n "$DHCLIENT_DELAY" ] && [ $DHCLIENT_DELAY -gt 0 ]; then
            sleep $DHCLIENT_DELAY
        fi
        ;;

    ARPCHECK|ARPSEND)
        if [ -z "$new_ip_address" ] || [ -z "$interface" ] ||
           arping -q -f -c 2 -w 3 -D -I $interface $new_ip_address; then
            exit_with_hooks 0
        else
            exit_with_hooks 1
        fi
        ;;

    BOUND|RENEW|REBIND|REBOOT)
	# Set the address before everything so that other scripts
	# can use it
	# make_resolv_conf will wait until commit complete so that it
	# can include name servers from the VyOS config in resolv.conf
	set_address
	set_hostname
	make_resolv_conf
	update_routers

        # If FRR is not running make sure dhclient daemon does not keep running.
        if ! vtysh -c exit; then
            killall dhclient
        fi

        exit_with_hooks 0
        ;;

    EXPIRE|FAIL|RELEASE|STOP)
	new_routers=""; update_routers

        if [ -n "$old_ip_address" ]; then
            ip -family inet addr flush dev ${interface}
        fi
	if [ "$reason" = "STOP" ]; then
            ip link set ${interface} down
	fi

	vyos-hostsd-client --delete-name-servers --tag dhcp-${interface}
        ;;

    TIMEOUT)
        if [ -n "$new_routers" ]; then
            ip -family inet addr add $new_ip_address/$new_prefix \
		broadcast $new_broadcast_address dev $interface

            set -- $new_routers
            first_router="$1"

            if ping -q -c 1 -I $interface $first_router ; then
		# Again, set address first, the rest wait for commit
		set_address
		make_resolv_conf
		update_routers

		exit_with_hooks 0
	    fi
            ip -family inet addr flush dev $interface
	    # Note: this exits with interface still up
	    # see Debian bug #144666
        fi
        exit_with_hooks 2 "$@"
        ;;

    PREINIT6)
        # 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_with_hooks 0
	;;

    BOUND6)
        if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
	    exit_with_hooks 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_with_hooks 0
	;;

    RENEW6|REBIND6)
	# 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
	    make_resolv_conf
	fi

	exit_with_hooks 0
	;;

    DEPREF6)
        if [ x${new_ip6_prefixlen} = x ] ; then
	    exit_with_hooks 2;
	fi

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

	exit_with_hooks 0
	;;

    EXPIRE6|RELEASE6|STOP6)
        if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
	    exit_with_hooks 2;
	fi

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

	vyos-hostsd-client --delete-name-servers --tag dhcpv6-${interface}
	exit_with_hooks 0
	;;

esac

exit_with_hooks 0

