#!/bin/sh

VERSION=1.2
CONFDIR=/etc
CONFIGFILE=$CONFDIR/network.conf
NL='
'

auto () {
  autoifaces=''

  while read -r line; do
    case "$line" in
      auto*)
        if [ -z "$autoifaces" ]; then
          autoifaces="${line#* }"
        else
          autoifaces="$autoifaces ${line#* }"
        fi
        ;;
    esac
  done << EOF
$1
EOF

  echo "$autoifaces"
  }

down () {
  iface=$1

  if [ -f /var/run/dhclient.$iface.pid ]; then
    [ -n "$verbose" ] && echo "Stopping dhclient for $iface"

    if [ -n "$noact" ]; then
      echo "dhclient -x -pf /var/run/dhclient.$iface.pid"
    else
      dhclient -x -pf /var/run/dhclient.$iface.pid
    fi
  fi

  case "$iface" in
    *:*)
      real=${iface%:*}
      address=$(getinet $iface)

      if [ -z "$address" ] || [ -n "$noact" ]; then
        return
      fi

      [ -n "$verbose" ] && echo "Removing label $iface from $real"

      if [ -n "$noact" ]; then
        echo "ip address del $address dev $real label $iface"
      else
        ip address del $address dev $real label $iface
      fi
      ;;
    *)
      [ -n "$verbose" ] && echo "Flushing interface $iface"

      if [ -n "$noact" ]; then
        echo "ip address flush dev $iface"
      else
        ip address flush dev $iface
      fi

      [ -n "$verbose" ] && echo "Bringing down $iface"

      if [ -n "$noact" ]; then
        echo "ip link set dev $iface down"
      else
        ip link set dev $iface down
      fi
      ;;
  esac
  }

getinet () {
  iface=$1

  while read -r line; do
    case "$line" in
      "inet "*$iface)
        address=${line#inet* }
        address=${address%% *}
        ;;
    esac
  done << EOF
$(ip addr show $iface)
EOF

  echo $address
  }

mask2cidr () {
  bits=0
  IFS=.

  for dec in $1; do
    case $dec in
      255) bits=$((bits+8));;
      254) bits=$((bits+7));;
      252) bits=$((bits+6));;
      248) bits=$((bits+5));;
      240) bits=$((bits+4));;
      224) bits=$((bits+3));;
      192) bits=$((bits+2));;
      128) bits=$((bits+1));;
      0);;
      *)
        echo "Error: '$dec': invalid octet"
        return
        ;;
    esac
  done

  echo "$bits"
  }

readconf () {
  while read -r line; do
    case "$line" in
      \#*)
        continue
        ;;
      "source-directory /"*)
        for file in ${line#* }/*.conf; do
          [ -f "$file" ] || continue;

          readconf $file
        done
        ;;
      source-directory*)
        for file in $CONFDIR/${line#* }/*.conf; do
          [ -f "$file" ] || continue;

          readconf $file
        done
        ;;
      *)
        if [ -n "$CONFIG" ]; then
          CONFIG="$CONFIG$NL"
        fi

        CONFIG="$CONFIG$line"
    esac
  done < $1
  }

reload () {
  down $1
  up $1 "$2"
  }

up () {
  local address brports cidr dhcp exists gateway loopback manual \
    routes stanza static netmask

  iface=$1

  while read -r line; do
    case "$line" in
      "iface $iface "*)
        exists=1
        stanza=1

        case "$line" in
          "iface $iface inet dhcp")
            dhcp=1
            ;;
          "iface $iface inet loopback")
            loopback=1
            ;;
          "iface $iface inet manual")
            manual=1
            ;;
          "iface $iface inet static")
            static=1
            ;;
          *)
            exists=0
            stanza=0
            ;;
        esac
    esac

    if [ -n "$stanza" ]; then
      case "$line" in
        address*)
          address=${line#* }

          case "$address" in
            */*)
              cidr=${address#*/}
              address=${address%/*}
              ;;
          esac
          ;;
        netmask*)
          netmask=${line#* }

          if [ -z "$cidr" ]; then
            cidr=$(mask2cidr $netmask)
          fi
          ;;
        gateway*)
          gateway=${line#* }
          ;;
        route*)
          if [ -n "$routes" ]; then
            routes="$routes$NL${line#* }"
          else
            routes="${line#* }"
          fi
          ;;
        bridge_ports*)
          brports=${line#* }
          ;;
        '')
          break
          ;;
      esac
    fi
  done << EOF
$2
EOF

  if [ -z "$exists" ]; then
    echo "$iface not defined"

    return 1
  fi

  if [ -n "$brports" ]; then
    [ -n "$verbose" ] && echo "Creating bridge interface $iface"

    if [ -n "$noact" ]; then
      echo "ip link add $iface type bridge"
    else
      ip link add $iface type bridge
    fi

    for brport in "$brports"; do
      [ -n "$verbose" ] && echo "Configuring $brport as slave to $iface"
      [ -n "$verbose" ] && echo "Flushing interface $brport"
  
      if [ -n "$noact" ]; then
        echo "ip address flush dev $brport"
      else
        ip address flush dev $brport
      fi

      [ -n "$verbose" ] && echo "Bringing up interface $brport"
  
      if [ -n "$noact" ]; then
        echo "ip link set dev $brport up"
      else
        ip link set dev $brport up
      fi

      if [ -n "$noact" ]; then
        echo "ip link set dev $brport master $iface"
      else
        ip link set dev $brport master $iface
      fi
    done
  fi

  if [ -n "$dhcp" ]; then
    [ -n "$verbose" ] && echo "Flushing interface $iface"

    if [ -n "$noact" ]; then
      echo "ip address flush dev $iface"
    else
      ip address flush dev $iface
    fi

    [ -n "$verbose" ] && echo "Bringing up interface $iface"

    if [ -n "$noact" ]; then
      echo "ip link set dev $iface up"
    else
      ip link set dev $iface up
    fi

    [ -n "$verbose" ] && echo "Starting dhclient for $iface"

    if [ -f /var/run/dhclient.$iface.pid ]; then
      if [ -n "$noact" ]; then
        echo "dhclient -x -pf /var/run/dhclient.$iface.pid"
      else
        dhclient -x -pf /var/run/dhclient.$iface.pid
      fi
    fi

    if [ -n "$noact" ]; then
      echo "dhclient -nw -pf /var/run/dhclient.$iface.pid $iface"
    else
      dhclient -nw -pf /var/run/dhclient.$iface.pid $iface
    fi
  elif [ -n "$loopback" ]; then
    [ -n "$verbose" ] && echo "Flushing interface $iface"

    if [ -n "$noact" ]; then
      echo "ip address flush dev $iface"
    else
      ip address flush dev $iface
    fi

    [ -n "$verbose" ] && echo "Bringing up loopback interface $iface"

    if [ -n "$noact" ]; then
      echo "ip link set dev $iface up"
    else
      ip link set dev $iface up
    fi
  elif [ -n "$static" ]; then
    case "$iface" in
      *:*)
        real=${iface%:*}

        [ -n "$verbose" ] && echo "Configuring $iface with $address/$cidr"

        if [ -n "$noact" ]; then
          echo "ip address add $address/$cidr dev $real label $iface"
        else
          ip address add $address/$cidr dev $real label $iface
        fi
        ;;
      *)
        [ -n "$verbose" ] && echo "Flushing interface $iface"

        if [ -n "$noact" ]; then
          echo "ip address flush dev $iface"
        else
          ip address flush dev $iface
        fi

        [ -n "$verbose" ] && echo "Bringing up interface $iface"

        if [ -n "$noact" ]; then
          echo "ip link set dev $iface up"
        else
          ip link set dev $iface up
        fi

        [ -n "$verbose" ] && echo "Configuring $iface with $address/$cidr"

        if [ -n "$noact" ]; then
          echo "ip address add $address/$cidr dev $iface"
        else
          ip address add $address/$cidr dev $iface
        fi
        ;;
    esac
  fi

  if [ -n "$gateway" ]; then
    [ -n "$verbose" ] && echo "Setting default route via $gateway"

    if [ -n "$noact" ]; then
      echo "ip route add default via $gateway"
    else
      ip route add default via $gateway
    fi
  fi

  if [ -n "$routes" ]; then
    [ -n "$verbose" ] && echo "Adding static routes"

    for i in 1 2 3 4 5; do
      sleep .5
      chkaddr=$(getinet ${iface%:*})

      [ -n "$chkaddr" ] && break;
    done

    IFS=$NL

    for route in $routes; do
      [ -n "$verbose" ] && echo "Routing $route"
      IFS=$' \t\n'

      if [ -n "$noact" ]; then
        echo "ip route add $route dev $iface src $address"
      else
        ip route add $route dev "$iface" src "$address"
      fi
    done
  fi
  }

usage () {
  echo "Usage: $self [OPTIONS] [INTERFACES]"
  echo "Enable and configure network interfaces as defined in /etc/network.conf"
  echo
  echo "Either -a or one or more INTERFACES must be provided as arguments"
  echo "  -a                  act on 'auto' interfaces"
  echo "  -h                  print usage information"
  echo "  -n                  display actions without performing them"
  echo "  -v                  enable verbose output"
  echo "  -V                  display version information"
  }

self=${0#*/}
action=${self#*if}

if [ "$#" -eq 0 ]; then
  usage

  exit 1;
fi

while getopts ahnvV args; do
  case $args in
    (a) all=1;;
    (h) usage && exit;;
    (n) noact=1;;
    (v) verbose=1;;
    (V) echo "$self v$VERSION" && exit;;
    (*) usage && exit 1;;
  esac
done

shift "$((OPTIND - 1))"

readconf "$CONFIGFILE"

if [ -n "$all" ]; then
  ifaces=$(auto "$CONFIG")
else
  ifaces="$@";
fi

for iface in $ifaces; do
  $action "$iface" "$CONFIG"
done
