#!/bin/bash
# console-config/src/lib-scripts/serial-tcpserial.sh
#
#  Copyright: ©2014, Güralp Systems Ltd.
#  Author: Kelly Dunlop <kdunlop@guralp.com>
#  License: GPLv3
#
# Setup the parameters for a serial port configured for tcpserial.



# display_tcpserial_mode
# mode (default simple_server)
# $1 is the title text to display
# $2 is the current value
# $3 is the default value
# values are simple_server or simple_client
display_tcpserial_mode() {
    if [ -z "$2" ]
    then
        default="$3"
    else
        default="$2"
    fi

    MENU=( "simple_server" "TCP client can connect to the server" \
           "simple_client" "converter connects to a remote server" )

    display_menu "$1" "Operation mode" 0 0 "$default"
}



# Separate the bind variable out in to it's component parts
# Returns output in two arrays named HOSTS and SERVICES
# $1 is the current value of the bind variable from the config file
# Returns a counter of how many items found
# Updates the HOSTS and SERVICES arrays with the separated out hostname
# and port numbers set in the bind parameter.  The hostnames may be ""
read_bind_values() {
    bind="$1"
    count=0

    if [ -z "${bind}" ]
    then
        return 0
    fi

    for item in ${bind}
    do
        if [ "${item}" = "${item/,*}" ]
        then
            HOSTS+=("")
            SERVICES+=("${item}")
        else
            HOSTS+=("${item/,*}")
            SERVICES+=("${item/*,}")
        fi
        ((count++))
    done

    return ${count}
}



# Recreate the bind variable from the two arrays.  If nothing has changed then
# just use the original value.
# $1 is the changes variable (1 means need to reassemble 0 leave alone)
# $2 is the number of items in the arrays
reassemble_bind() {
    if [ $1 -eq 0 ]
    then
        NEW_TCP_BIND="NOCHANGE"
    else
        count=0
        NEW_TCP_BIND=""
        while [ $count -lt $2 ]
        do
            if [ -z "${HOSTS[${count}]}" ]
            then
                # If both are empty then we do nothing (effectively delete the entry)
                if [ ! -z "${SERVICES[${count}]}" ]
                then
                    NEW_TCP_BIND="${NEW_TCP_BIND} ${SERVICES[${count}]}"
                fi
            else
                NEW_TCP_BIND="${NEW_TCP_BIND} ${HOSTS[${count}]},${SERVICES[${count}]}"
            fi
            ((count++))
        done
    fi
}



# display_tcpserial_ipfilter
# ipfilter (default "")
# $1 is the title text to display
# $2 is a true/false value to indicate accept or reject (may be "")
# $3 is the current value (may be "")
# $2 and $3 will be "" for new entries
display_tcpserial_ipfilter() {
    local type

    if [ -z "$2" ]
    then
        type="accept/reject"
    else 
        if [ "$2" = "true" ]
        then
            type="reject"
        else
            type="accept"
        fi
    fi

    display_inputbox "$1" "Hostname to ${type} incoming connections from" 0 0 "$3"
}



# Separate the ipfilter variable out in to it's component parts
# Returns output in two arrays named ADDRESS and REJECT
# $1 is the current value of the reject variable from the config file
# Returns a counter of how many items found
# Updates the ADDRESS and REJECT arrays with the separated out hostname
# and a true/false value set in the reject parameter.
read_ipfilter_values() {
    local filter="$1"
    local count=0
    local item len

    if [ -z "${filter}" ]
    then
        return 0
    fi

    for item in ${filter}
    do
        len=$((${#item}-7))
        if [ "${item:0:6}" = "accept" ]
        then
            REJECT+=("false")
            if [ "${item:((${#item}-1)):1}" = "," ]
            then
                len=$((len-2))
            else
                len=$((len-1))
            fi
            ADDRESS+=("${item:7:$len}")
        else
            REJECT+=("true")
            if [ "${item:((${#item}-1)):1}" = "," ]
            then
                len=$((len-2))
            else
                len=$((len-1))
            fi
            ADDRESS+=("${item:7:$len}")
        fi
        ((count++))
    done

    return ${count}
}



# Recreate the ipfilter variable from the two arrays
# $1 is the changes variable (1 means need to reassemble 0 leave alone)
# $2 is the number of items in the arrays
reassemble_ipfilter() {
    local count

    if [ $1 -eq 0 ]
    then
        NEW_TCP_IPFILTER="NOCHANGE"
    else
        count=0
        NEW_TCP_IPFILTER=""
        while [ $count -lt $2 ]
        do
            # If address is empty then we do nothing (effectively delete the entry)
            if [ -n "${ADDRESS[${count}]}" ]
            then
                if [ "${REJECT[${count}]}" = "false" ]
                then
                    if [ -z "${NEW_TCP_IPFILTER}" ]
                    then
                        NEW_TCP_IPFILTER="accept(${ADDRESS[${count}]})"
                    else
                        NEW_TCP_IPFILTER="${NEW_TCP_IPFILTER}, accept(${ADDRESS[${count}]})"
                    fi
                else
                    if [ -z "${NEW_TCP_IPFILTER}" ]
                    then
                        NEW_TCP_IPFILTER="reject(${ADDRESS[${count}]})"
                    else
                        NEW_TCP_IPFILTER="${NEW_TCP_IPFILTER}, reject(${ADDRESS[${count}]})"
                    fi
                fi
            fi
            ((count++))
        done
    fi
}



# Separate the server variable out in to it's component parts - it is
# just a hostname/addr and port nubmer separated by a comma.
# Returns output in two global variables named TCP_CLIENT_ADDR and TCP_CLIENT_PORT
# $1 is the current value of the server variable from the config file
read_server_values() {
    if [ -z "$1" ]
    then
        return 0
    fi

    TCP_CLIENT_ADDR="${1/,*}"
    TCP_CLIENT_PORT="${1/*,}"
}



# update config file
# $1 is the config filename
# $2 is the NEW_TCP_MODE value
# $3 is the NEW_TCP_BIND value
# $4 is the NEW_TCP_IPFILTER value
# $5 is the NEW_TCP_SERVER value
update_config_file() {

    cfset $1 mode="$2"
    cfset $1 bind="$3"
    cfset $1 ipfilter="$4"
    cfset $1 server="$5"

    # Now we've finished making changes reset the permission of the config file
    cconf_set_perms_and_group ${TCP_CFG_FILE} 664 "conf"
}



# Check that the latest host/port combination is not already there.
# $1 is the index of the latest added entry.
tcpserial_check_repeat_server() {
    local latest=$1
    local count=0

    while [ $count -lt $latest ]
    do
        # host the same
        if [ "${HOSTS[${count}]}" = "${HOSTS[${latest}]}" ]
        then
            if [ "${SERVICES[${count}]}" = "${SERVICES[${latest}]}" ]
            then
                clear
                echo "Repeated host/service combination ${HOSTS[${count}]}:${SERVICES[${count}]}"
                exit 1
            fi
        fi

        ((count++))
    done
}



# display_tcpserial_server_options - Display the server options and update them
# $1 is the title text to use
# $2 is the number of bind hosts currently set
# $3 is the number of accept/reject hosts currently set
# There are two arrays for the bind setup named HOSTS and SERVICES
# There are two arrays for the accept/reject setup named ADDRESS and REJECT
display_tcpserial_server_options() {
    local count=0
    local bind_changes=0
    local ipfilter_changes=0
    local titletext="$1"
    local num_bind=$2
    local num_reject=$3
    local portno default

    # In server mode the client options will be unchanged so just pass the
    # current setting through to the new setting.
    NEW_TCP_SERVER="${TCP_SERVER}"

    while [ $count -lt $num_bind ]
    do
        if [ -z "${SERVICES[${count}]}" ]
        then
            portno="${DEF_TCP_BIND_SVC}"
        else
            portno="${SERVICES[${count}]}"
        fi

        dialog --title "${titletext} - Simple server - current settings" \
            --form "Edit hostname/port number to listen for incoming connections" \
            0 0 0 \
            "Hostname (blank = all)" 1 0 "${HOSTS[${count}]}" 2 4 54 100 \
            "Port number" 3 0 "${portno}" 4 4 54 100 \
            2> "${DIALOG_RESULTFILE}"
        if [ $? -ne 0 ]
        then
            clear
            echo Cancelled
            exit 1
        fi

        {
            read -r NEW_TCP_BIND_HOST
            read -r NEW_TCP_BIND_SVC
        } < "${DIALOG_RESULTFILE}"

        if [ "${NEW_TCP_BIND_HOST}" != "${HOSTS[${count}]}" ]
        then
            bind_changes=1
            HOSTS[${count}]="${NEW_TCP_BIND_HOST}"
        fi

        if [ "${NEW_TCP_BIND_SVC}" != "${SERVICES[${count}]}" ]
        then
            bind_changes=1
            SERVICES[${count}]="${NEW_TCP_BIND_SVC}"
        fi

        # Need to make sure there are no repeats because it causes tcpserial to error if there
        # are.  The exit occurs within the function so if it returns there are no repeats.
        tcpserial_check_repeat_server $count

        ((count++))
    done

    while true
    do
        # We need to make sure one address is set otherwise tcpserial fails to run.
        if [ $count -eq 0 ]
        then
            default=""
        else
            default="--defaultno"
        fi

        # Now see if the user wants to add anything, count is the next array element to fill
        # and it will be 0 if there where no host/service values already set
        display_yesno "${titletext} - Simple server host and service setup" \
            "Add addresses to listen for incoming connections." 0 0 ${default}
        if [ $? -eq 0 ]
        then
            bind_changes=1

            # This is the easiest way to work out if this is the first address/port in the list.
            # If it is we need to set the default port value to DEF_TCP_BIND_SVC.
            if [ $count -eq 0 ]
            then
                portno=${DEF_TCP_BIND_SVC}
            else
                portno=""
            fi

            dialog --title "${titletext} - Simple server - new values" \
                --form "Enter hostname/port number to listen for incoming connections" \
                0 0 0 \
                "Hostname (blank = all)" 1 0 "" 2 4 54 100 \
                "Port number" 3 0 "${portno}" 4 4 54 100 \
                2> "${DIALOG_RESULTFILE}"
            if [ $? -ne 0 ]
            then
                clear
                echo Cancelled
                exit 1
            fi

            {
                read -r NEW_TCP_BIND_HOST
                read -r NEW_TCP_BIND_SVC
            } < "${DIALOG_RESULTFILE}"

           if [ "${NEW_TCP_BIND_HOST}" != "${HOSTS[${count}]}" ]
           then
               bind_changes=1
               HOSTS[${count}]="${NEW_TCP_BIND_HOST}"
           fi

           if [ "${NEW_TCP_BIND_SVC}" != "${SERVICES[${count}]}" ]
           then
               bind_changes=1
               SERVICES[${count}]="${NEW_TCP_BIND_SVC}"
           fi

            # Need to make sure there are no repeats because it causes tcpserial to error if there
            # are.  The exit occurs within the function so if it returns there are no repeats.
            tcpserial_check_repeat_server $count

           ((count++))
        else
            if [ $count -eq 0 ]
            then
                dialog --title "${titletext} - ERRORS FOUND" \
                    --msgbox "At least one port to bind to must be specified in simple server mode" \
                    0 0 2> "${DIALOG_RESULTFILE}"
            else
                break
            fi
        fi
    done

    # Now we've finished the Bind variables we need to put things back together
    # into a single bind variable. This is set in NEW_TCP_BIND
    if [ $bind_changes -ne 0 ]
    then
        reassemble_bind $bind_changes $count
    else
        NEW_TCP_BIND="${TCP_BIND}"
    fi

    # Now we need to display any ipfilter values currently set
    count=0
    while [ $count -lt $num_reject ]
    do
        display_tcpserial_ipfilter "${titletext} - Simple server filter - current settings" \
            "${REJECT[${count}]}" "${ADDRESS[${count}]}"

        NEW_TCP_FILTER_ADDR="`cat ${DIALOG_RESULTFILE}`"
        if [ "${NEW_TCP_FILTER_ADDR}" != "${ADDRESS[${count}]}" ]
        then
            ipfilter_changes=1
            ADDRESS[${count}]="${NEW_TCP_FILTER_ADDR}"
        fi

        if [ "${REJECT[count]}" = "false" ]
        then
            default=""
        else
            default="--defaultno"
        fi

        # If this has been cleared then we don't need to continue on with this pass.
        if [ -z "${NEW_TCP_FILTER_ADDR}" ]
        then
            ((count++))
            continue
        fi

        display_yesno "${titletext} - Simple server filter options" \
            "Accept calls from this address ${NEW_TCP_FILTER_ADDR} ? (Yes - accept, No - reject)" \
            0 0 $default
        if [ $? -eq 0 ]
        then
            if [ "${REJECT[count]}" = "true" ]
            then
                ipfilter_changes=1
            fi
            REJECT[${count}]="false"
        else
            if [ "${REJECT[count]}" = "false" ]
            then
                ipfilter_changes=1
            fi
            REJECT[${count}]="true"
        fi
        ((count++))
    done

    while true
    do
        # Now we need to set the addresses to accept or reject connections from.
        # These may just be blank.
        display_yesno "${titletext} - Simple server filter options" \
            "Add addresses to accept or reject connections from.  \
There are currently $count defined." 0 0 --defaultno
        if [ $? -eq 0 ]
        then
            ipfilter_changes=1

            display_tcpserial_ipfilter "${titletext} - Simple server filter - new values" "" ""
            NEW_TCP_FILTER_ADDR="`cat ${DIALOG_RESULTFILE}`"
            ADDRESS[${count}]="${NEW_TCP_FILTER_ADDR}"

            display_yesno "${titletext} - Simple server filter options" \
                "Accept calls from this address ${NEW_TCP_FILTER_ADDR} ? (Yes for accept, No for reject)"
            if [ $? -eq 0 ]
            then
                REJECT[${count}]="false"
            else
                REJECT[${count}]="true"
            fi
        else
            break
        fi
        ((count++))
    done

    # Now we've finished the Filter variables we need to put things back together
    # into a single IPFILTER variable. This is set in NEW_TCP_IPFILTER
    if [ $ipfilter_changes -ne 0 ]
    then
        reassemble_ipfilter $ipfilter_changes $count
    else
        NEW_TCP_IPFILTER="${TCP_IPFILTER}"
    fi
}



# display_tcpserial_client_options - Display the client options and update them
# $1 is the titletext to display
display_tcpserial_client_options() {
    local titletext="$1"

    # In client mode the server options will be unchanged so just pass through
    # the current settings to the new ones.
    NEW_TCP_BIND="${TCP_BIND}"
    NEW_TCP_IPFILTER="${TCP_IPFILTER}"

    while true
    do
        dialog --title "${titletext} Simple client address options" \
            --form "Enter hostname and port number to connect to (cannot be blank)" \
            0 0 0 \
            "Hostname" 1 0 "${TCP_CLIENT_ADDR}" 2 4 54 100 \
            "Port number" 3 0 "${TCP_CLIENT_PORT}" 4 4 54 100 \
            2> "${DIALOG_RESULTFILE}"
        if [ $? -ne 0 ]
        then
            clear
            echo Cancelled
            exit 1
        fi

        {
            read -r NEW_TCP_CLIENT_ADDR
            read -r NEW_TCP_CLIENT_PORT
        } < "${DIALOG_RESULTFILE}"

        if [ -n "${NEW_TCP_CLIENT_ADDR}" ]
        then
            if [ "${NEW_TCP_CLIENT_ADDR}" = "${TCP_CLIENT_ADDR}" ]
            then
                NEW_TCP_CLIENT_ADDR="NOCHANGE"
            fi
        else
            continue
        fi

        if [ -n "${NEW_TCP_CLIENT_PORT}" ]
        then
            if [ "${NEW_TCP_CLIENT_PORT}" = "${TCP_CLIENT_PORT}" ]
            then
                NEW_TCP_CLIENT_PORT="NOCHANGE"
            fi
        else
            continue
        fi
        break
    done

    # if both are unchanged
    if [ "${NEW_TCP_CLIENT_ADDR}" = "NOCHANGE" -a "${NEW_TCP_CLIENT_PORT}" = "NOCHANGE" ]
    then
        NEW_TCP_SERVER="${TCP_SERVER}"
    else
        if [ "${NEW_TCP_CLIENT_ADDR}" = "NOCHANGE" ]
        then
            NEW_TCP_SERVER="${TCP_CLIENT_ADDR},${NEW_TCP_CLIENT_PORT}"
        else
            if [ "${NEW_TCP_CLIENT_PORT}" = "NOCHANGE" ]
            then
                NEW_TCP_SERVER="${NEW_TCP_CLIENT_ADDR},${TCP_CLIENT_PORT}"
            else
                NEW_TCP_SERVER="${NEW_TCP_CLIENT_ADDR},${NEW_TCP_CLIENT_PORT}"
            fi
        fi
    fi
}



# Go through the list of things that the user can change in the tcpserial config
# file.
#   $1 - portname
# If something is Cancelled or there is an error then this can exit.
serial_tcpserial_parameters() {
    local port=$1
    local titletext

    HOSTS=()
    SERVICES=()
    ADDRESS=()
    REJECT=()

    TCP_CFG_FILE="${SERIAL_CFDIR}/tcpserial/${port}"

    # Set default values
    DEF_TCP_MODE="simple_server"
    case "${port}" in
    DataOut|PortDO|gsl-tty-00)      DEF_TCP_BIND_SVC=10000     ;;
    PortA|gsl-tty-01)               DEF_TCP_BIND_SVC=10001     ;;
    PortB|gsl-tty-02)               DEF_TCP_BIND_SVC=10002     ;;
    PortC|gsl-tty-03)               DEF_TCP_BIND_SVC=10003     ;;
    PortD|gsl-tty-04)               DEF_TCP_BIND_SVC=10004     ;;
    PortE|gsl-tty-05)               DEF_TCP_BIND_SVC=10005     ;;
    PortF|gsl-tty-06)               DEF_TCP_BIND_SVC=10006     ;;
    PortG|gsl-tty-07)               DEF_TCP_BIND_SVC=10007     ;;
    gsl-tty-08)                     DEF_TCP_BIND_SVC=10008     ;;
    gsl-tty-09)                     DEF_TCP_BIND_SVC=10009     ;;
    gsl-tty-10)                     DEF_TCP_BIND_SVC=10010     ;;
    gsl-tty-11)                     DEF_TCP_BIND_SVC=10011     ;;
    gsl-tty-12)                     DEF_TCP_BIND_SVC=10012     ;;
    gsl-tty-13)                     DEF_TCP_BIND_SVC=10013     ;;
    gsl-tty-14)                     DEF_TCP_BIND_SVC=10014     ;;
    gsl-tty-15)                     DEF_TCP_BIND_SVC=10015     ;;
    gsl-tty-16)                     DEF_TCP_BIND_SVC=10016     ;;
    gsl-tty-17)                     DEF_TCP_BIND_SVC=10017     ;;
    gsl-tty-18)                     DEF_TCP_BIND_SVC=10018     ;;
    gsl-tty-19)                     DEF_TCP_BIND_SVC=10019     ;;
    gsl-tty-20)                     DEF_TCP_BIND_SVC=10020     ;;
    gsl-tty-21)                     DEF_TCP_BIND_SVC=10021     ;;
    gsl-tty-22)                     DEF_TCP_BIND_SVC=10022     ;;
    gsl-tty-23)                     DEF_TCP_BIND_SVC=10023     ;;
    PortSA1)                        DEF_TCP_BIND_SVC=10091     ;;
    PortSA2)                        DEF_TCP_BIND_SVC=10092     ;;
    Console|SerialConsole)          DEF_TCP_BIND_SVC=10099     ;;
    esac

    DEF_TCP_IPFILTER=""
    DEF_TCP_SERVER=""

    # Get current values or set defaults if file doesn't already exist
    if [ -r $TCP_CFG_FILE ]
    then
        # get the current values for each item
        TCP_MODE="`cfget ${TCP_CFG_FILE} mode`"
        TCP_BIND="`cfget ${TCP_CFG_FILE} bind`"
        TCP_IPFILTER="`cfget ${TCP_CFG_FILE} ipfilter`"
        TCP_SERVER="`cfget ${TCP_CFG_FILE} server`"
    else
        # set empty values for each item
        TCP_MODE=""
        TCP_BIND=""
        TCP_IPFILTER=""
        TCP_SERVER=""
    fi

    # Now go through the values giving user a choice
    titletext="${port} TCP converter config"

    # mode (default simple_server)
    display_tcpserial_mode "${titletext}" "${TCP_MODE}" "${DEF_TCP_MODE}"
    NEW_TCP_MODE="`cat ${DIALOG_RESULTFILE}`"

    # Separate the bind variable out in to it's component parts
    # Sets up two arrays named HOSTS and SERVICES
    read_bind_values "${TCP_BIND}"
    num_bind_items=$?

    # Separate the ipfilter variable out in to it's component parts
    # Sets up two arrays named ADDRESS and REJECT
    read_ipfilter_values "${TCP_IPFILTER}"
    num_ipfilter_items=$?

    # Separate the server variable out into it's component parts
    # Sets up two global variables TCP_CLIENT_ADDR and TCP_CLIENT_PORT
    read_server_values "${TCP_SERVER}"

    # What we do now is dependent on the value just set for the mode
    # If we've just changed it to simple_server or it was already set
    # to that and it hasn't changed
    if [ "${NEW_TCP_MODE}" = "simple_server" ]
    then
        display_tcpserial_server_options "${titletext}" ${num_bind_items} ${num_ipfilter_items}
    fi

    # Now check for simple_client being selected
    if [ "${NEW_TCP_MODE}" = "simple_client" ]
    then
        display_tcpserial_client_options "${titletext}"
    fi

    cconf_create_config_file $TCP_CFG_FILE

    # Now we need to make the updates that have been done here before returning
    update_config_file ${TCP_CFG_FILE} ${NEW_TCP_MODE} \
        "${NEW_TCP_BIND}" "${NEW_TCP_IPFILTER}" "${NEW_TCP_SERVER}"
}



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