#!/bin/bash
# config-scripts/src/share/networkif.sh
# 
#  Copyright: ©2007–2011, Güralp Systems Ltd.
#  Author: Laurence Withers <lwithers@guralp.com>
#  Author: Bob Dunlop <rdunlop@guralp.com>
#  License: GPLv3
#

gcs_subitem="$3"

# Handle navbar without loading anything else
if [ "X$1" == "X--navbar" ]
then
    echo "networktop Networking"
    if [ "$gcs_subitem" == "vcreate" ]
    then
        echo "networkif/$gcs_subitem New VLAN"
    else
        echo "networkif/$gcs_subitem $gcs_subitem"
    fi
    exit 0
fi

# Load support functions
script_dir=$(dirname $0)
. $script_dir/functions.sh



gcs_var_o_or_d() {
    local name="$1"
    local file="$2"
    local def="$3"
    local val

    val="$(gcs_get_varf "${name}" "${file}")"
    if [ -n "${val}" ]
    then
        gcs_var "o_${name}" "${val}"
        eval ${name}=\"${val}\"
    else
        gcs_var "d_${name}" "${def}"
        eval ${name}=\"${val}\"
    fi
}


# Generate a list of interface we can attach VLANs to
gcs_iselect_eth() {
    local -i i=0
    local file

    for file in "${NETCONFDIR}/eth"*
    do
        if [ -f "${file}" ]
        then
            dev="$(gcs_get_varf device "${file}")"
            [ -z "${dev}" ] && continue

            desc="$(gcs_get_varf desc "${file}")"
            echo "select_vlanhost${i}=${dev} ${dev} - ${desc}"
            ((++i))
        fi
    done
}

# Create the bootproto choices
gcs_iselect_bootp() {
    local vlan_mode=$1

    echo "select_bootproto0=static   Static"
    echo "select_bootproto1=dhcp     DHCP (Dynamic Host Configuration Protocol)"
    echo "select_bootproto2=disabled Powered off"
    if ! gcs_truefalse "${vlan_mode:-false}"
    then
        echo "select_bootproto3=enabled  Unconfigured but powered up (possible VLAN trunk)"
    fi
}


do_read() {
    local -i i
    local alias_ip r_destn
    local force_speed media_lockdown

    if [ "${gcs_subitem}" = "vcreate" ]
    then
        gcs_var "vcreate_mode" "True"
        gcs_var "vlan_mode"    "True"
        gcs_iselect_bootp True
        gcs_iselect_eth
        CFGFILE="/dev/null"
        gcs_var_o_or_d desc "${CFGFILE}" "Newly created VLAN interface"

    else
        gcs_var "vcreate_mode" "False"
        if [ "${gcs_subitem:0:4}" == "vlan" ]
        then
            gcs_var "vlan_mode" "True"
            gcs_iselect_bootp True
            gcs_iselect_eth
        else
            gcs_var "vlan_mode" "False"
            gcs_iselect_bootp False
        fi

        CFGFILE="${NETCONFDIR}/${gcs_subitem}"
        if [ ! -r "${CFGFILE}" ]
        then
            gcs_err fatal "\"$gcs_subitem\" is not a valid network interface identifier"
            return
        fi

        gcs_var_o_or_d desc "${CFGFILE}" "Network interface ${gcs_subitem}"
        gcs_var_o_or_d device "${CFGFILE}" ""
        # New VLANs may not show until the system is rebooted, allow the user to
        # view and edit settings even if we can't get the MAC address.
        if ip -o link show ${gcs_subitem} > /dev/null 2>&1
        then
            gcs_var "mac_address" "$(ip -o link show ${gcs_subitem} | sed -e 's,^.*link/ether \([^ ]*\).*$,\1,')"
        else
            gcs_var "mac_address" "00:00:00:00:00:00"
        fi
    fi

    gcs_var_o_or_d vlanhost             "${CFGFILE}" ""
    gcs_var_o_or_d vlantag              "${CFGFILE}" ""
    gcs_var_o_or_d onboot               "${CFGFILE}" "true"
    gcs_var        enact_now            "False"
    gcs_var_o_or_d mtu                  "${CFGFILE}" ""
    gcs_var_o_or_d bootproto            "${CFGFILE}" "dhcp"
    gcs_var_o_or_d default_address_ip   "${CFGFILE}" ""
    gcs_var_o_or_d default_address_broadcast "${CFGFILE}" ""
    gcs_var_o_or_d default_nameserver0  "${CFGFILE}" ""
    gcs_var_o_or_d default_nameserver1  "${CFGFILE}" ""
    gcs_var_o_or_d default_route_via    "${CFGFILE}" ""

    # media type, including CMG-EAM lockdown support
    if [ -e "/etc/conf.local/ethernet" ]
    then
        force_speed="`gcs_get_varf \"FORCE_SPEED_${gcs_subitem}\" "/etc/conf.local/ethernet"`"
        case "${force_speed}" in
        100baseTx-FD)
            media_lockdown="100BASE-TX full duplex"
            ;;
        100baseTx-HD)
            media_lockdown="100BASE-TX half duplex"
            ;;
        10baseT-FD)
            media_lockdown="10BASE-T full duplex"
            ;;
        10baseT-HD)
            media_lockdown="10BASE-T half duplex"
            ;;
        esac
    fi
    if [ -z "${media_lockdown}" ]
    then
        gcs_var_o_or_d "media" "${CFGFILE}" "Auto"
    else
        gcs_var "o_media" "${force_speed}"
        gcs_var "media_lockdown" "${media_lockdown}"
    fi

    # fill IP alias table
    i=0
    while true
    do
        alias_ip="$(gcs_get_varf "alias_address_ip${i}" "${CFGFILE}")"
        [ -z "${alias_ip}" ] && break

        gcs_var "o_alias_address_ip${i}" "${alias_ip}"
        gcs_var "o_alias_address_broadcast${i}" "$(gcs_get_varf "alias_address_broadcast${i}" "${CFGFILE}")"
        gcs_var "o_alias_address_args${i}" "$(gcs_get_varf "alias_address_args${i}" "${CFGFILE}")"

        (( i++ ))
    done

    # fill route table
    i=0
    while true
    do
        r_destn="$(gcs_get_varf "route_destn${i}" "${CFGFILE}")"
        [ -z "${r_destn}" ] && break

        gcs_var "o_route_destn${i}" "${r_destn}"
        gcs_var "o_route_type${i}" "$(gcs_get_varf "route_type${i}" "${CFGFILE}")"
        gcs_var "o_route_via${i}" "$(gcs_get_varf "route_via${i}" "${CFGFILE}")"
        gcs_var "o_route_args${i}" "$(gcs_get_varf "route_args${i}" "${CFGFILE}")"

        (( i++ ))
    done
}



check_route() {
    local r_type="$1"
    local r_via="$2"
    local err_var="fatal" # TODO: more specific?

    case "${r_type}" in
    unicast)
        [ -z "${r_via}" ] && gcs_err "${err_var}" "A unicast route requires a 'via' (gateway) address"
        ;;
    unreachable | blackhole | prohibit | local | broadcast)
        [ -n "${r_via}" ] && gcs_err "${err_var}" "A route of type '${r_via}' cannot have a 'via' (gateway) address"
        ;;
    esac
}

do_check() {
    local -i i

    gcs_read_vars

    if gcs_truefalse "${new_vcreate_mode:-false}"
    then
        new_device=vlan$new_vlantag
        CFGFILE="${NETCONFDIR}/$new_device"
        if [ -r "${CFGFILE}" ]
        then
            gcs_err msg "Device \"$new_device\" already exists. Choose another"
            return
        fi
        gcs_var device $new_device
    else
        if [ "X$new_device" != "X$gcs_subitem" ]
        then
            gcs_err fatal "\"$new_device\" != \"$gcs_subitem\" ?"
            return
        else
            CFGFILE="${NETCONFDIR}/$gcs_subitem"

            if [ ! -r "${CFGFILE}" ]
            then
                gcs_err fatal "\"$gcs_subitem\" is not a valid network interface identifier"
                return
            fi
        fi
    fi


    # TODO: we should probably check that each router is on a local subnet

    # for each route, we check its type and its via
    [ -n "${new_default_route_via}" ] && check_route "unicast" "${new_default_route_via}"
    
    for (( i = 0 ; i < $new_route_rows ; i++ ))
    do
        r_destn="new_route_destn${i}"
        [ -z "${!r_destn}" ] && continue

        r_type="new_route_type${i}"
        r_via="new_route_via${i}"
        check_route "${!r_type}" "${!r_via}"
    done
}



do_write() {
    declare -i i j

    do_check
    if (( gcs_errors > 0 ))
    then
        return
    fi

    [ "${gcs_subitem}" == "vcreate" ] && gcs_subitem="${new_device}"

    [ -z "$CFGFILE" ] && CFGFILE="${NETCONFDIR}/${gcs_subitem}"

    # Locate the VLAN helper script if needed
    vhelper=""
    if [ "${gcs_subitem:0:4}" == "vlan" ]
    then
        vhelper="$(dirname $0)/networkif_vlan_helper.sh"
    fi

    if gcs_truefalse "${new_delete:-false}"
    then
        # Delete the VLAN
        svc "net_${gcs_subitem}" stop > /dev/null 2>&1
        [ -n "$vhelper" ] && suexec "$vhelper" delete ${gcs_subitem}
        rm -f $CFGFILE
        return
    fi

    # Make a copy of the existing config minus the ipalias and routing tables
    TMPFILE="$(mktemp "${NETCONFDIR}/.gcs_netif.XXXXXX" 2>/dev/null)"
    if [ -z "${TMPFILE}" ]
    then
        gcs_err fatal "Cannot create new configuration file."
        return
    fi

    if [ -r "${CFGFILE}" ]
    then
        sed -e '/^alias_/d' -e '/^route_/d' < "${CFGFILE}" > "${TMPFILE}"
    fi

    # Set the new values
    gcs_set_varf "NETCONFIG_VERSION"            "${TMPFILE}" "1"
    gcs_set_varf "device"                       "${TMPFILE}" "${new_device}"
    gcs_set_varf "desc"                         "${TMPFILE}" "${new_desc}"
    gcs_set_varf "enable"                       "${TMPFILE}" "${new_onboot}" # compat option
    gcs_set_varf "onboot"                       "${TMPFILE}" "${new_onboot}"
    gcs_set_varf "media"                        "${TMPFILE}" "${new_media}"
    gcs_set_varf "mtu"                          "${TMPFILE}" "${new_mtu}"
    gcs_set_varf "bootproto"                    "${TMPFILE}" "${new_bootproto}"
    gcs_set_varf "default_address_ip"           "${TMPFILE}" "${new_default_address_ip}"
    gcs_set_varf "default_address_broadcast"    "${TMPFILE}" "${new_default_address_broadcast}"
    gcs_set_varf "default_nameserver0"          "${TMPFILE}" "${new_default_nameserver0}"
    gcs_set_varf "default_nameserver1"          "${TMPFILE}" "${new_default_nameserver1}"
    gcs_set_varf "default_route_via"            "${TMPFILE}" "${new_default_route_via}"

    gcs_set_varf "vlanhost"                     "${TMPFILE}" "${new_vlanhost}"
    gcs_set_varf "vlantag"                      "${TMPFILE}" "${new_vlantag}"

    j=0
    for (( i = 0 ; i < $new_alias_address_rows ; i++ ))
    do
        alias_ip="new_alias_address_ip${i}"
        [ -z "${!alias_ip}" ] && continue

        alias_bcast="new_alias_address_broadcast${i}"
        alias_args="new_alias_address_args${i}"
        gcs_set_varf "alias_address_ip${j}"     "${TMPFILE}" "${!alias_ip}"
        gcs_set_varf "alias_address_bcast${j}"  "${TMPFILE}" "${!alias_bcast}"
        gcs_set_varf "alias_address_args${j}"   "${TMPFILE}" "${!alias_args}"

        (( j++ ))
    done

    j=0
    for (( i = 0 ; i < $new_route_rows ; i++ ))
    do
        r_destn="new_route_destn${i}"
        [ -z "${!r_destn}" ] && continue

        r_type="new_route_type${i}"
        r_via="new_route_via${i}"
        r_args="new_route_args${i}"
        gcs_set_varf "route_destn${j}"          "${TMPFILE}" "${!r_destn}"
        gcs_set_varf "route_type${j}"           "${TMPFILE}" "${!r_type}"
        gcs_set_varf "route_via${j}"            "${TMPFILE}" "${!r_via}"
        gcs_set_varf "route_args${j}"           "${TMPFILE}" "${!r_args}"

        (( j++ ))
    done

    # Copy the new configuration back
    if diff "${TMPFILE}" "${CFGFILE}" > /dev/null 2>&1
    then
        # New file is the same as the original so just delete the temp
        rm -f "${TMPFILE}"
    else
        chgrp "netconf" "${TMPFILE}"
        chmod 0664 "${TMPFILE}"
        mv "${TMPFILE}" "${CFGFILE}"
    fi

    if gcs_truefalse "${new_enact_now:-false}"
    then
        [ -n "$vhelper" ] && suexec "$vhelper" add ${gcs_subitem}
        svc "net_${gcs_subitem}" restart > /dev/null 2>&1
    fi
}


case "X$1" in
X--check)       do_check        ;;
X--write)       do_write        ;;
X--read)        do_read         ;;
*)              exit 1          ;;
esac

gcs_cleanup
exit 0

# vim: ts=4:sw=4:expandtab
