#!/bin/ksh

#==================================================
# gennetconfs - generate network coniguration files
#
# $Id: gennetconfs,v 1.25 2025/01/01 00:58:54 kaw Exp $
#==================================================

# Copyright (c) 2018--2025
# Yoshihiro Kawamata
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#
#   * Neither the name of Yoshihiro Kawamata nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# == Global Variables ====================================================
#   basedir            directory where configurations reside
#   conf               name of configuration
#   confdir            directory where files to be configure reside
#   description        contents of confdir/description
#   etc_myname         contents of /etc/myname
#   etc_mygate         contents of /etc/mygate
#   etc_hosts          contents of /etc/hosts
#   etc_hostname_if    contents of /etc/hostname.if
#   etc_resolv_conf    contents of /etc/resolv.conf
#   etc_sysctl_conf    contents of /etc/sysctl.conf
#   ifconf4_manu       'yes' if IPv4 manually configured
#   ifconf6_manu       'yes' if IPv6 manually configured
#   ipv                enabled IP version such as '6 4'
#   eth_nics           detected physical network interfaces
#   confnic            a network interface to configure
# ========================================================================

# quiet output
#   print message if -v specified
#
quiet () {
    if [[ "$quiet" != 'yes' ]]; then
        echo "$@"
    fi
}

# display usage
#
usage () {
    cat <<EOT >&2

$PROGNAME: generate network configuration files
Usage: $PROGNAME [-b] [-q] [-d 'description'] [-h] [config_name]
    -b: surpress banner message
    -q: surpress additional explains
    -d description: brief description of this config
    -h: print this help
EOT
}

# addvar name [arg1 arg2 ...]
#
#   add trailing args 'arg1 arg2 ...'
#   to variable 'name' followed by EoL
#                      ~~~~~~~~~~~~~~~
addvar () {
    [[ -z "$1" ]] && return
    local _name="$1"; shift;
    eval "${_name}=\"\$${_name}\"\"$*\"\"\\n\""
}

# setaddr_4
#
#   set configurration for IPv4 address and routing
#
setaddr_4 () {
    local testif
    local addr_mask
    local addr mask gw

    echo
    echo  'IPv4 - address and routing:'
    echo  '  Enter "auto" or "IPv4_address[/mask] [default_gateway]"'
    quiet '  "auto" is an automatic setting by DHCP.'
    quiet '  The "/mask" part can be specified in either format,'
    quiet '  such as "/255.255.255.0" or "/24".'
    quiet '  If there is no default gateway,'
    quiet '  set the second field to "none" or leave it blank.'

    # randomly choose unused virtual ethernet device
    # for test address and mask
    #
    while :; do
        testif=vether$(jot -r 1 1000 1999)  # choice with random number
        ifconfig $testif >/dev/null 2>&1 || break  # unused ... choose this
        sleep 1  # testif already exists ... retry after 1sec
    done
    
    # read value and test it
    #
    while :; do
        echo -n '[auto] -> '; read addr_mask gw
        case "$addr_mask" in
            [Aa][Uu][Tt][Oo]*|'')  # autoconf
                addvar etc_hostname_if 'inet autoconf'
                break ;;
            *.*.*.*/[1-9]|*.*.*.*/[123][0-9])
                # masklen
                set -- $(echo "$addr_mask" | tr / ' ')
                addr="$1"
                mask="$2"
                if ifconfig $testif inet "$addr/$mask" >/dev/null 2>&1; then
                    addvar etc_hostname_if "inet $addr/$mask"
                    ifconf4_manu=yes
                    break
                else
                    echo "bad value - $addr_mask"
                fi
                ;;
            *.*.*.*/*.*.*.*)
                # maskaddr
                set -- $(echo "$addr_mask" | tr / ' ')
                addr="$1"
                mask="$2"
                if ifconfig $testif inet "$addr" netmask "$mask" >/dev/null 2>&1; then
                    addvar etc_hostname_if "inet $addr $mask"
                    ifconf4_manu=yes
                    break
                else
                    echo "bad value - $addr"
                fi
                ;;
            *.*.*.*)
                # no mask specified
                if ifconfig $testif inet "$addr_mask" >/dev/null 2>&1; then
                    set -- $(ifconfig $testif | grep 'inet .* netmask .*')
                    addr="$2"
                    mask="$4"
                    addvar etc_hostname_if "inet $addr $mask"
                    ifconf4_manu=yes
                    break
                else
                    echo "bad value - $addr_mask"
                fi
                ;;
            *)
                # illegal form
                echo "illegal address format - $addr_mask"
                ;;
        esac
    done

    # set addr to hosts
    #
    [[ -n "$addr" ]] && addvar etc_hosts $addr $(echo -n $etc_myname)  # to omit etc_myname's EOL

    # dispose the test device
    #
    ifconfig $testif destroy >/dev/null 2>&1

    # set default gateway
    #
    if [[ -n "$gw" && "$gw" = [1-9]*.[1-9]*.[1-9]*.[1-9]* ]]; then
        addvar etc_mygate "$gw"
    fi
}

# setaddr_6
#
#   set configurration for IPv6 address and routing
#
setaddr_6 () {
    local testif
    local addr_plen
    local addr plen gw

    echo
    echo  'IPv6 - address and routing:'
    echo  '  Enter "auto" or "IPv6_address[/prefixlen] [default_gateway]"'
    quiet '  "auto" is an automatic setting by SLAAC.'
    quiet '  The "/prefixlen" part can be an integer between 0 and 128.'
    quiet '  If there is no default gateway,'
    quiet '  set the second field to "none" or leave it blank.'

    # randomly choose unused virtual ethernet device
    # for test address and mask
    #
    while :; do
        testif=vether$(jot -r 1 1000 1999)  # choice with random number
        ifconfig $testif >/dev/null 2>&1 || break
        sleep 1  # testif already exists ... retry after 1sec
    done
    
    # read value and test it
    #
    while :; do
        echo -n '[auto] -> '; read addr_plen gw
        case "$addr_plen" in
            [Aa][Uu][Tt][Oo]*|'')  # autoconf
                addvar etc_hostname_if 'inet6 autoconf'
                break ;;
            [:0-9A-Fa-f]*/[0-9]|[:0-9A-Fa-f]*/[1-9][0-9]|[:0-9A-Fa-f]*/1[0-9][0-9])
                # prefixlen
                set -- $(echo "$addr_plen" | tr / ' ')
                addr="$1"
                plen="$2"
                if ifconfig $testif inet6 "$addr" prefixlen "$plen" >/dev/null 2>&1; then
                    addvar etc_hostname_if "inet6 $addr $plen"
                    ifconf6_manu=yes
                    break
                else
                    echo "bad value - $addr"
                fi
                ;;
            [:0-9A-Fa-f]*[:0-9A-Fa-f])
                # no prefixlen specified
                if ifconfig $testif inet6 "$addr_plen" >/dev/null 2>&1; then
                    set -- $(ifconfig $testif | grep 'inet6 .* prefixlen .*' | grep -v scopeid)
                    addr="$2"
                    plen="$4"
                    addvar etc_hostname_if "inet6 $addr $plen"
                    ifconf6_manu=yes
                    break
                else
                    echo "bad value - $addr_plen"
                fi
                ;;
            *)
                # illegal form
                echo "illegal address format - $addr_plen"
                ;;
        esac
    done

    # set addr to hosts
    #
    [[ -n "$addr" ]] && addvar etc_hosts $addr $(echo -n $etc_myname)  # to omit etc_myname's EOL

    # dispose the test device
    ifconfig $testif destroy >/dev/null 2>&1

    if [[ -n "$gw" && "$gw" = [:0-9A-Fa-f]*[:0-9A-Fa-f] ]]; then
        addvar etc_mygate "$gw"
    fi
}

# outif text outfile
#   output text to outfile unless text is null
#   subfunction for emit_config
#
outif () {
    if [[ -n "$1" && -n "$2" ]]; then
        quiet "  ${confdir}/$2"
        echo -n "$1" > $2
    fi
}

# write all setting values to files
#
emit_config () {
    quiet ''
    quiet 'writing configured values to:'
    outif "$description" description
    outif "$etc_myname" myname
    outif "$etc_mygate" mygate
    outif "$etc_hosts" hosts
    if [[ -n "$confnic" ]]; then
        outif "$etc_hostname_if" hostname.${confnic}
        if [[ -e hostname.${confnic} ]]; then
            chown root:wheel hostname.${confnic}
            chmod 0640 hostname.${confnic}
        fi
    fi
    outif "$etc_resolv_conf" resolv.conf
    outif "$etc_sysctl_conf" sysctl.conf
}


#=====================
# main runs from here
#=====================

PROGNAME="${0##*/}"

# must be root
#
if [ ! `id -un` = root ]; then
    echo "$PROGNAME: must be run as a root."
    exit 1
fi

# parsing command line options
#
while getopts bqd:h OPT; do
    case "$OPT" in
        b) builtin_rc=yes  # surpress banner
           ;;
        q) quiet=yes  # no additional messages
           ;;
        d) addvar description "$OPTARG"
           ;;
        h) usage; exit;;
        *) usage; exit 1;;
    esac
done
shift $((OPTIND - 1))

# base directory for configurations
#
basedir=/etc/fuguita/netconfs

# set and cd to configuration directory
# unless specified, run on current working directory
#
if [[ -n "$1" ]]; then
    conf="$1"
    if [[ "$conf" = 'templ' ]]; then
	echo "$PROGNAME: '$conf' is the special name."
	echo "    You can't configure $conf. Use other name."
        exit 1
    fi

    confdir="$basedir/$conf"
    mkdir -p "$confdir"  # even if $confdir already exists
    cd "$confdir" || exit 1
else
    confdir=$(pwd)
fi

if [[ "$builtin_rc" != 'yes' ]]; then
    cat <<EOT
===================================================
= gennetconfs: generate network configuration files
===================================================
EOT
fi

# Description
#
if [[ -z "$description" ]]; then
    echo
    echo 'Enter brief description of this configuration.'
    echo -n '-> '; read ln
    addvar description "$ln"
fi

# Host name
#
echo
echo 'Hostname with domain part (FQDN):'
quiet 'only host name without domain part is also OK.'
while :; do
    echo -n '-> '; read ln
    if echo "$ln" | grep -q '[^0-9A-Za-z.-]'; then
        echo "illgal character in $ln"
        continue
    elif [[ -z "$ln" ]]; then
        continue
    else
        addvar etc_myname "$ln"
        break
    fi
done

# Detect physical network devices
#
for nic in $(ifconfig -a|grep '^[a-z][0-9a-z][0-9a-z]*: '|cut -d: -f1); do
    case "$(ifconfig $nic)" in
        *'media: Ethernet '*)
            eth_nics=${eth_nics:+"$eth_nics "}$nic
        ;;
        *'media: IEEE802.11 '*)
            wifi_nics=${wifi_nics:+"$wifi_nics "}$nic
        ;;
    esac
done

# No NICs found ... standalone settings
#
if [[ -z "${eth_nics}${wifi_nics}" ]]; then
    echo
    echo 'No physical network devices found - setup as standalone...'
    addvar etc_resolv_conf 'lookup file'
    emit_config
    exit
fi

# IP versions
#
echo
echo 'IP protocol version(s) to be enabled: 4, 6, 46, 64 or "none"'
quiet '  4: enable only IPv4'
quiet '  6: enable only IPv6'
quiet '  46: give priority to IPv4 name resolution'
quiet '  64: give priority to IPv6 name resolution'
quiet '  none: operate as standalone'
while :; do
    echo -n '[64] -> '; read ipver
    case "$ipver" in
        46)
            ipv='4 6'
            addvar etc_resolv_conf 'lookup file bind'
            addvar etc_resolv_conf 'family inet4 inet6'
            ;;
        64|'')
            ipv='6 4'
            addvar etc_resolv_conf 'lookup file bind'
            addvar etc_resolv_conf 'family inet6 inet4'
            ;;
        4)
            ipv='4'
            addvar etc_resolv_conf 'lookup file bind'
            addvar etc_resolv_conf 'family inet4'
            ;;
        6)
            ipv='6'
            addvar etc_resolv_conf 'lookup file bind'
            addvar etc_resolv_conf 'family inet6'
            ;;
        [Nn]*)
            echo 'No IP protocols specified - setup as standalone...'
            addvar etc_resolv_conf 'lookup file'
            emit_config
            exit
            ;;
        *) continue
           ;;
    esac
    break
done

# List physical network devices
#
echo
echo "Network Interfaces: Choose one"
echo
echo '  NIC    type      Name'
echo '-------- ----- ------------'
dmesg="$(cat /var/run/dmesg.boot 2>/dev/null; dmesg)"  # get device names dmesg
for nic in $eth_nics $wifi_nics; do
    if expr "$eth_nics" : ".*$nic" >/dev/null 2>&1 ; then
        type=ether
    elif expr "$wifi_nics" : ".*$nic" >/dev/null 2>&1 ; then
        type=wifi
    else
        type='???'
    fi
    prod=$(echo "$dmesg" | sed "/^$nic at .*\".*\"/!d; s/^[^\"]*\"//; s/\".*//" | tail -n 1)
    printf "%8s %-5s %s\n" "$nic" "$type" "${prod:-unknown}"
done

# Spcifying a device
#
set -- $eth_nics $wifi_nics
defnic="$1"  # simply, default is the first one found
echo -n "[$defnic] -> "; read nic
confnic=${nic:-$defnic}

# Non exsitent now
#   proceed settings since the device may be created
#   in the future (i.e. vether, USB dongle ...)
#
if ! expr "$eth_nics $wifi_nics" : ".*$confnic" >/dev/null; then
   echo
   echo "Warning: $confnic hasn't been detected yet."
   echo "         anyway, continue setting..."
fi

# Wi-Fi settings
#
while :; do  # this loop runs once
    wlan=''
    expr "$wifi_nics" : ".*$confnic" >/dev/null || break  # It's not Wi-Fi.

    echo
    echo 'Wi-Fi settings:'
    echo -n '  SSID -> '; read wifi_ssid
    wlan="${wifi_ssid:+nwid $wifi_ssid}"

    echo -n '  WPA Key -> '; read wifi_wpakey
    wlan="$wlan${wifi_wpakey:+ wpakey $wifi_wpakey}"
    [[ -n "$wifi_wpakey" ]] && break  # finish setting here

    echo -n '  WEP Key -> '; read wifi_wepkey
    wlan="$wlan${wifi_wepkey:+ nwkey $wifi_wepkey}"
    break
done
#
# store the values to var
#
if [[ -n "$wlan" ]]; then
    addvar etc_hostname_if "$wlan"
    addvar etc_hostname_if "up"
fi

# IP address and routing
#   configure in name resolution order
#
for ipver in $ipv; do
    case "$ipver" in
        4) setaddr_4 ;;
        6) setaddr_6 ;;
    esac
done

# DNS settings
#
if [[ "${ifconf4_manu}${ifconf6_manu}" != '' ]]; then
    # manual config at least one
    echo
    echo 'DNS servers: up to 3 IP addresses, separated by spaces'
    echo -n '-> '; read dns
    set -- $(echo "$dns" | tr -sc '0-9.A-Fa-z:%' ' ')
    [[ -n "$1" ]] && addvar etc_resolv_conf "nameserver $1"
    [[ -n "$2" ]] && addvar etc_resolv_conf "nameserver $2"
    [[ -n "$3" ]] && addvar etc_resolv_conf "nameserver $3"
fi

# write to configuration files
#
emit_config

if [[ "$builtin_rc" != 'yes' ]]; then
    cat <<EOT

======================================================
= end of gennetconfs:
= Use chnetconf utility to activate this configuration
======================================================
EOT
fi
