#!/bin/bash
#
# dhclient-script for Linux.
#
#		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.
#
# Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
#
# Probably, I did not understand, what this funny feature as "alias"
# means exactly. For now I suppose, that it is a static address, which
# we should install and preserve.
#

exec >> /var/log/DHS.log 2>&1

echo dhc-script $* reason=$reason
set | grep "^\(old_\|new_\|check_\)"

LOG () {
    echo LOG $* ;
}

# convert 8bit mask to length
# arg: $1 = mask
#
Mask8ToLen() {
	local l=0;

	while [ $l -le 7 ]; do
		if [ $[ ( 1 << $l ) + $1 ] -eq 256 ]; then
			return	$[ 8 - $l ]
		fi
		l=$[ $l + 1 ]
	done
	return 0;
}

# convert inet dotted quad mask to length
# arg: $1 = dotquad mask
#
MaskToLen() {
 local masklen=0
 local mask8=$1

 case $1 in
 0.0.0.0)
	return 0;
	;;
 255.*.0.0)
	masklen=8
	mask8=${mask8#255.}
	mask8=${mask8%.0.0}
	;;
 255.255.*.0)
	masklen=16
	mask8=${mask8#255.255.}
	mask8=${mask8%.0}
	;;
 255.255.255.*)
	masklen=24
	mask8=${mask8#255.255.255.}
	;;
 *)
	return 255
	;;
 esac
 Mask8ToLen $mask8
 return $[ $? + $masklen ]
}

# calculate ABC "natural" mask
# arg: $1 = dotquad address
#
ABCMask () {
 local class;

 class=${1%%.*}

 if [ "$1" = "255.255.255.255" ]; then
    echo $1
 elif [ "$1" = "0.0.0.0" ]; then
    echo $1
 elif [ $class -ge 224 ]; then
    echo 240.0.0.0
 elif [ $class -ge 192 ]; then
    echo 255.255.255.0
 elif [ $class -ge 128 ]; then
    echo 255.255.0.0
 else
    echo 255.0.0.0
 fi
}

# calculate ABC "natural" mask length
# arg: $1 = dotquad address
#
ABCMaskLen () {
 local class;

 class=${1%%.*}

 if [ "$1" = "255.255.255.255" ]; then
    return 32
 elif [ "$1" = "0.0.0.0" ]; then
    return 0
 elif [ $class -ge 224 ]; then
    return 4;
 elif [ $class -ge 192 ]; then
    return 24;
 elif [ $class -ge 128 ]; then
    return 16;
 else
    return 8;
 fi
}

# Delete IP address
# args: $1 = interface
#       $2 = address
#       $3 = mask
#       $4 = broadcast
#       $5 = label
#
DelINETAddr () {
  local masklen=32
  local addrid=$1

  LOG DelINETAddr $*

  if [ "$5" ]; then
    addrid=$addrid:$5
  fi
  LOG ifconfig $addrid down
  ifconfig $addrid down
}

# Add IP address
# args: $1 = interface
#       $2 = address
#       $3 = mask
#       $4 = broadcast
#       $5 = label
#
AddINETAddr () {
  local mask_arg
  local brd_arg
  local addrid=$1

  LOG AddINETAddr $*

  if [ "$5" ]; then
    addrid=$addrid:$5
  fi
  if [ "$3" ]; then
    mask_arg="netmask $3"
  fi
  if [ "$4" ]; then
    brd_arg="broadcast $4"
  fi

  LOG ifconfig $addrid $2 $mask_arg $brd_arg up
  ifconfig $addrid $2 $mask_arg $brd_arg up
}

# Add default routes
# args: $1 = routers list
#
AddDefaultRoutes() {
    local router

    if [ "$1" ]; then
      LOG AddDefaultRoutes $*
      for router in $1; do
        LOG route add default gw $router
        route add default gw $router
      done ;
    fi
}

# Delete default routes
# args: $1 = routers list
#
DelDefaultRoutes() {
    local router

    if [ "$1" ]; then
      LOG DelDefaultRoutes $*

      for router in $1; do
        LOG route del default gw $router
        route del default gw $router
      done
    fi
}

# ping a host
# args: $1 = dotquad address of the host
#
PingNode() {
    LOG PingNode $*
    if ping -q -c 1 -w 2 $1 ; then
	return 0;
    fi
    return 1;
}

# Check (and add route, if alive) default routers
# args: $1 = routers list
# returns: 0 if at least one router is alive.
#
CheckRouterList() {
    local router
    local succeed=1

    LOG CheckRouterList $*

    for router in $1; do
      if PingNode $router ; then
	succeed=0
        route add default gw $router
      fi
    done
    return $succeed
}

# Delete/create static routes.
# args: $1 = operation (del/add)
#       $2 = routes list in format "dst1 nexthop1 dst2 ..."
#
# BEWARE: this feature of DHCP is obsolete, because does not
#         support subnetting.
#
X-StaticRouteList() {
    local op=$1
    local lst="$2"
    local masklen

    LOG X-StaticRouteList $*

    if [ "$lst" ]; then
      set $lst
      while [ $# -gt 1 ]; do
	route $op -net $1 netmask `ABCMask "$1"` gw $2
	shift; shift;
      done
   fi
}

# Create static routes.
# arg: $1 = routes list in format "dst1 nexthop1 dst2 ..."
#
AddStaticRouteList() {
    LOG AddStaticRouteList $*
    X-StaticRouteList add "$1"
}

# Delete static routes.
# arg: $1 = routes list in format "dst1 nexthop1 dst2 ..."
#
DelStaticRouteList() {
    LOG DelStaticRouteList $*
    X-StaticRouteList del "$1"
}

# Broadcast unsolicited ARP to update neighbours' caches.
# args: $1 = interface
#       $2 = address
#
UnsolicitedARP() {
    if [ -f /sbin/arping ]; then
	/sbin/arping -A -c 1 -I "$1" "$2" &
	(sleep 2 ; /sbin/arping -U -c 1 -I "$1" "$2" ) &
    fi
}

# Duplicate address detection.
# args: $1 = interface
#       $2 = test address
# returns: 0, if DAD succeeded.
DAD() {
  if [ -f /sbin/arping ]; then
	/sbin/arping -c 2 -w 3 -D -I "$1" "$2"
	return $?
  fi
  return 0
}


# Setup resolver.
# args: NO
#       domain and nameserver list are passed in global variables.
#
# NOTE: we try to be careful and not to break user supplied resolv.conf.
#       The script mangles it, only if it has dhcp magic signature.
#
UpdateDNS() {
    local nameserver
    local idstring="#### Generated by DHCPCD"

    LOG UpdateDNS $*

    if [ "$new_domain_name" = "" -a "$new_domain_name_servers" = "" ]; then
	return 0;
    fi

    echo $idstring > /etc/resolv.conf.dhcp
    if [ "$new_domain_name" ]; then
	echo search $new_domain_name >> /etc/resolv.conf.dhcp
    fi
    echo options ndots:1 >> /etc/resolv.conf.dhcp

    if [ "$new_domain_name_servers" ]; then
	for nameserver in $new_domain_name_servers; do
	    echo nameserver $nameserver >> /etc/resolv.conf.dhcp
	done
    else
	echo nameserver 127.0.0.1 >> /etc/resolv.conf.dhcp
    fi

    if [ -f /etc/resolv.conf ]; then
	if [ "`head -1 /etc/resolv.conf`" != "$idstring" ]; then
	    return 0
	fi
	if [ "$old_domain_name" = "$new_domain_name" -a
	     "$new_domain_name_servers" = "$old_domain_name_servers" ]; then
	     return 0
	fi
    fi
    mv /etc/resolv.conf.dhcp /etc/resolv.conf
}

case $reason in
NBI)
  exit 1
  ;;

MEDIUM)
  exit 0
  ;;

PREINIT)
  ifconfig $interface:dhcp down
  ifconfig $interface:dhcp1 down
  if [ -d /proc/sys/net/ipv4/conf/$interface ]; then
    ifconfig $interface:dhcp 10.10.10.10 netmask 255.255.255.255
    ifconfig $interface:dhcp down
    if [ -d /proc/sys/net/ipv4/conf/$interface ]; then
	LOG The interface $interface already configured.
    fi
  fi
  ifconfig $interface:dhcp up
  exit 0
  ;;

ARPSEND)
  exit 0
  ;;

ARPCHECK)
  if DAD "$interface" "$check_ip_address" ; then
    exit 0
  fi
  exit 1
  ;;

BOUND|RENEW|REBIND|REBOOT)
  if [ "$old_ip_address" -a "$alias_ip_address" -a \
	"$alias_ip_address" != "$old_ip_address" ]; then
    DelINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
  fi
  if [ "$old_ip_address" -a "$old_ip_address" != "$new_ip_address" ]; then
    DelINETAddr "$interface" "$old_ip_address" "$old_subnet_mask" "$old_broadcast_address" dhcp
    DelDefaultRoutes "$old_routers"
    DelStaticRouteList "$old_static_routes"
  fi
  if [ "$old_ip_address" = "" -o "$old_ip_address" != "$new_ip_address" -o \
       "$reason" = "BOUND" -o "$reason" = "REBOOT" ]; then
    AddINETAddr "$interface" "$new_ip_address" "$new_subnet_mask" "$new_broadcast_address" dhcp
    AddStaticRouteList "$new_static_routes"
    AddDefaultRoutes "$new_routers"
    UnsolicitedARP "$interface" "$new_ip_address"
  fi
  if [ "$new_ip_address" != "$alias_ip_address" -a "$alias_ip_address" ]; then
    AddINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
  fi
  UpdateDNS
  exit 0
  ;;

EXPIRE|FAIL)
  if [ "$alias_ip_address" ]; then
    DelINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
  fi
  if [ "$old_ip_address" ]; then
    DelINETAddr "$interface" "$old_ip_address" "$old_subnet_mask" "$old_broadcast_address" dhcp
    DelDefaultRoutes "$old_routers"
    DelStaticRouteList "$old_static_routes"
  fi
  if [ "$alias_ip_address" ]; then
    AddINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
  fi
  exit 0
  ;;

TIMEOUT)
  if [ "$alias_ip_address" ]; then
    DelINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
  fi
# Seems, <null address> means, that no more old leases found.
# Or does it mean bug in dhcpcd? 8) Fail for now.
  if [ "$new_ip_address" = "<null address>" ]; then
    if [ "$old_ip_address" ]; then
	DelINETAddr "$interface" "$old_ip_address" "$old_subnet_mask" "$old_broadcast_address" dhcp
    fi
    if [ "$alias_ip_address" ]; then
        AddINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
    fi
    exit 1
  fi
  if DAD "$interface" "$new_ip_address" ; then
    AddINETAddr "$interface" "$new_ip_address" "$new_subnet_mask" "$new_broadcast_address" dhcp
    UnsolicitedARP "$interface" "$new_ip_address"
    if [ "$alias_ip_address" -a "$alias_ip_address" != "$new_ip_address" ]; then
      AddINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
      UnsolicitedARP "$interface" "$alias_ip_address"
    fi
    if CheckRouterList "$new_routers" ; then
	AddStaticRouteList "$new_static_routes"
	UpdateDNS
	exit 0
    fi
  fi
  DelINETAddr "$interface" "$new_ip_address" "$new_subnet_mask" "$new_broadcast_address" dhcp
  DelDefaultRoutes "$old_routers"
  DelStaticRouteList "$old_static_routes"
  if [ "$alias_ip_address" ]; then
    AddINETAddr "$interface" "$alias_ip_address" "$alias_subnet_mask" "$alias_broadcast_address" dhcp1
  fi
  exit 1
  ;;
esac

exit 0
