#!/bin/bash
# Copyright (C) 2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

. /lib/lsb/init-functions

: ${vyatta_env:=/etc/default/vyatta}
source $vyatta_env

declare progname=${0##*/}
declare action=$1; shift

declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot

# If vyos-config= boot option is present, use that file instead
for x in $(cat /proc/cmdline); do
    [[ $x = vyos-config=* ]] || continue
    VYOS_CONFIG="${x#vyos-config=}"
done

if [ ! -z "$VYOS_CONFIG" ]; then
    if [ -r "$VYOS_CONFIG" ]; then
        echo "Config selected manually: $VYOS_CONFIG"
        declare -x BOOTFILE="$VYOS_CONFIG"
    else
        echo "WARNING: Could not read selected config file, using default!"
    fi
fi

declare -a subinit
declare -a all_subinits=( firewall )

if [ $# -gt 0 ] ; then
    for s in $@ ; do
        [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
    done
else
    for s in ${all_subinits[@]} ; do
        [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
    done
fi

GROUP=vyattacfg

# easy way to make empty file without any command
empty()
{
    >$1
}

# check if bootup of this portion is disabled
disabled () {
    grep -q -w no-vyos-$1 /proc/cmdline
}

# if necessary, provide initial config
init_bootfile () {
    if [ ! -r $BOOTFILE ] ; then
        if [ -f $vyatta_sysconfdir/config.boot.default ]; then
            cp $vyatta_sysconfdir/config.boot.default $BOOTFILE
        else
            $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE
        fi
        chgrp ${GROUP} $BOOTFILE
        chmod 660 $BOOTFILE
    fi
}

# if necessary, migrate initial config
migrate_bootfile ()
{
    if [ -x $vyos_libexec_dir/run-config-migration.py ]; then
        log_progress_msg migrate
        sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE"
    fi
}

# load the initial config
load_bootfile ()
{
    log_progress_msg configure
    (
        if [ -f /etc/default/vyatta-load-boot ]; then
            # build-specific environment for boot-time config loading
            source /etc/default/vyatta-load-boot
        fi
        if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then
            sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE"
        fi
    )
}

# restore if missing pre-config script
restore_if_missing_preconfig_script ()
{
    if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then
        mkdir -p ${vyatta_sysconfdir}/config/scripts
        chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
        chmod 775 ${vyatta_sysconfdir}/config/scripts
        cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
        chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
        chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
    fi
}

# execute the pre-config script
run_preconfig_script ()
{
    if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then
        $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script
    fi
}

# restore if missing post-config script
restore_if_missing_postconfig_script ()
{
    if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then
        mkdir -p ${vyatta_sysconfdir}/config/scripts
        chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
        chmod 775 ${vyatta_sysconfdir}/config/scripts
        cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
        chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
        chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
    fi
}

# execute the post-config scripts
run_postconfig_scripts ()
{
    if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then
        $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script
    fi
    if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then
        $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script
    fi
}

run_postupgrade_script ()
{
    if [ -f $vyatta_sysconfdir/config/.upgraded ]; then
        # Run the system script
        /usr/libexec/vyos/system/post-upgrade

        # Run user scripts
        if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then
            run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d
        fi
        rm -f $vyatta_sysconfdir/config/.upgraded
    fi
}

#
# On image booted machines, we need to mount /boot from the image-specific
# boot directory so that kernel package installation will put the
# files in the right place.  We also have to mount /boot/grub from the
# system-wide grub directory so that tools that edit the grub.cfg
# file will find it in the expected location.
#
bind_mount_boot ()
{
    persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
    if [ $? == 0 ]; then
        if [ -e $persist_path/boot ]; then
            image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')

            if [ -n "$image_name" ]; then
                mount --bind $persist_path/boot/$image_name /boot
                if [ $? -ne 0 ]; then
                    echo "Couldn't bind mount /boot"
                fi

                if [ ! -d /boot/grub ]; then
                    mkdir /boot/grub
                fi

                mount --bind $persist_path/boot/grub /boot/grub
                if [ $? -ne 0 ]; then
                    echo "Couldn't bind mount /boot/grub"
                fi
            fi
        fi
    fi
}

clear_or_override_config_files ()
{
    for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \
        keepalived/keepalived.conf cron.d/vyos-crontab \
        ipvsadm.rules default/ipvsadm resolv.conf
    do
    if [ -s /etc/$conf ] ; then
        empty /etc/$conf
        chmod 0644 /etc/$conf
    fi
    done
}

update_interface_config ()
{
    if [ -d /run/udev/vyos ]; then
        $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE
    fi
}

cleanup_post_commit_hooks () {
    # Remove links from the post-commit hooks directory.
    # note that this approach only supports hooks that are "configured",
    # i.e., it does not support hooks that need to always be present.
    cpostdir=$(cli-shell-api getPostCommitHookDir)
    # exclude commits hooks from vyatta-cfg
    excluded="10vyatta-log-commit.pl 99vyos-user-postcommit-hooks"
    if [ -d "$cpostdir" ]; then
	    for f in $cpostdir/*; do
	        if [[ ! $excluded =~ $(basename $f) ]]; then
		        rm -f $cpostdir/$(basename $f)
	        fi
	    done
    fi
}

# These are all the default security setting which are later
# overridden when configuration is read. These are the values the
# system defaults.
security_reset ()
{

    # restore NSS cofniguration back to sane system defaults
    # will be overwritten later when configuration is loaded
    cat <<EOF >/etc/nsswitch.conf
passwd:         files
group:          files
shadow:         files
gshadow:        files

# Per T2678, commenting out myhostname
hosts:          files dns #myhostname
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

netgroup:       nis
EOF

    # restore PAM back to virgin state (no radius/tacacs services)
    pam-auth-update --package --remove radius
    rm -f /etc/pam_radius_auth.conf
    pam-auth-update --package --remove tacplus
    rm -f /etc/tacplus_nss.conf /etc/tacplus_servers

    # Certain configuration files are re-generated by the configuration
    # subsystem and must reside under /etc and can not easily be moved to /run.
    # So on every boot we simply delete any remaining files and let the CLI
    # regenearte them.

    # PPPoE
    rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm*

    # IPSec
    rm -rf /etc/ipsec.conf /etc/ipsec.secrets
    find /etc/swanctl -type f | xargs rm -f

    # limit cleanup
    rm -f /etc/security/limits.d/10-vyos.conf

    # iproute2 cleanup
    rm -f /etc/iproute2/rt_tables.d/vyos-*.conf

    # Container
    rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf
    # Clean all networks and re-create them from our CLI
    rm -f /etc/containers/networks/*

    # System Options (SSH/cURL)
    rm -f /etc/ssh/ssh_config.d/*vyos*.conf
    rm -f /etc/curlrc
}

# XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based)
gen_duid ()
{
    DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid"
    UUID_FILE="/sys/class/dmi/id/product_uuid"
    UUID_FILE_ALT="/sys/class/dmi/id/product_serial"
    if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then
        return 1
    fi

    # DUID is based on the BIOS/EFI UUID. We omit additional - characters
    if [ -f ${UUID_FILE} ]; then
        UUID=$(cat ${UUID_FILE} | tr -d -)
    fi
    if [ -z ${UUID} ]; then
        UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -)
    fi
    # Add DUID type4 (UUID) information
    DUID_TYPE="0004"

    # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian
    # format - beware when porting to ARM64. The length field consists out of the
    # UUID (128 bit + 16 bits DUID type) resulting in hex 12.
    DUID_LEN="0012"
    if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then
        # true on little-endian (x86) systems
        DUID_LEN="1200"
    fi

    for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do
        echo -ne "\x$i"
    done > ${DUID_FILE}
}

start ()
{
    # reset and clean config files
    security_reset || log_failure_msg "security reset failed"

    # some legacy directories migrated over from old rl-system.init
    mkdir -p /var/run/vyatta /var/log/vyatta
    chgrp vyattacfg /var/run/vyatta /var/log/vyatta
    chmod 775 /var/run/vyatta /var/log/vyatta

    log_daemon_msg "Waiting for NICs to settle down"
    # On boot time udev migth take a long time to reorder nic's, this will ensure that
    # all udev activity is completed and all nics presented at boot-time will have their
    # final name before continuing with vyos-router initialization.
    SECONDS=0
    udevadm settle
    STATUS=$?
    log_progress_msg "settled in ${SECONDS}sec."
    log_end_msg ${STATUS}

    # mountpoint for bpf maps required by xdp
    mount -t bpf none /sys/fs/bpf

    # Clear out Debian APT source config file
    empty /etc/apt/sources.list

    # Generate DHCPv6 DUID
    gen_duid || log_failure_msg "could not generate DUID"

    # Mount a temporary filesystem for container networks.
    # Configuration should be loaded from VyOS cli.
    cni_dir="/etc/cni/net.d"
    [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir}
    mount -t tmpfs none ${cni_dir}

    # Init firewall
    nfct helper add rpc inet tcp
    nfct helper add rpc inet udp
    nfct helper add tns inet tcp
    nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"

    # As VyOS does not execute commands that are not present in the CLI we call
    # the script by hand to have a single source for the login banner and MOTD
    ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console"
    ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files"
    ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files"
    ${vyos_conf_scripts_dir}/system-ip.py || log_failure_msg "could not reset system IPv4 options"
    ${vyos_conf_scripts_dir}/system-ipv6.py || log_failure_msg "could not reset system IPv6 options"
    ${vyos_conf_scripts_dir}/conntrack.py || log_failure_msg "could not reset conntrack subsystem"
    ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem"

    clear_or_override_config_files || log_failure_msg "could not reset config files"

    # enable some debugging before loading the configuration
    if grep -q vyos-debug /proc/cmdline; then
        log_action_begin_msg "Enable runtime debugging options"
        touch /tmp/vyos.container.debug
        touch /tmp/vyos.ifconfig.debug
        touch /tmp/vyos.frr.debug
        touch /tmp/vyos.container.debug
    fi

    log_action_begin_msg "Mounting VyOS Config"
    # ensure the vyatta_configdir supports a large number of inodes since
    # the config hierarchy is often inode-bound (instead of size).
    # impose a minimum and then scale up dynamically with the actual size
    # of the system memory.
    local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo)
    local tpages
    local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes
    mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \
      && chgrp ${GROUP} ${vyatta_configdir}
    log_action_end_msg $?

    # T5239: early read of system hostname as this value is read-only once during
    # FRR initialisation
    tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name")
    hostnamectl set-hostname --static "$tmp"

    ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config"
    # If for any reason FRR was not started by system_frr.py - start it anyways.
    # This is a safety net!
    systemctl start frr.service

    disabled bootfile || init_bootfile

    cleanup_post_commit_hooks

    log_daemon_msg "Starting VyOS router"
    disabled migrate || migrate_bootfile

    restore_if_missing_preconfig_script

    run_preconfig_script

    run_postupgrade_script

    update_interface_config

    for s in ${subinit[@]} ; do
    if ! disabled $s; then
        log_progress_msg $s
        if ! ${vyatta_sbindir}/${s}.init start
        then log_failure_msg
         exit 1
        fi
    fi
    done

    bind_mount_boot

    disabled configure || load_bootfile
    log_end_msg $?

    telinit q
    chmod g-w,o-w /

    restore_if_missing_postconfig_script

    run_postconfig_scripts
}

stop()
{
    local -i status=0
    log_daemon_msg "Stopping VyOS router"
    for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do
    s=${subinit[$i]}
    log_progress_msg $s
    ${vyatta_sbindir}/${s}.init stop
    let status\|=$?
    done
    log_end_msg $status
    log_action_begin_msg "Un-mounting VyOS Config"
    umount ${vyatta_configdir}
    log_action_end_msg $?

    systemctl stop frr.service
}

case "$action" in
    start) start ;;
    stop)  stop ;;
    restart|force-reload) stop && start ;;
    *)  log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ;
    false ;;
esac

exit $?

# Local Variables:
# mode: shell-script
# sh-indentation: 4
# End: