#!/bin/bash
# info: add firewall ipset
# options: NAME [SOURCE] [IPVERSION] [AUTOUPDATE] [FORCE]
#
# The function adds new ipset to system firewall

#----------------------------------------------------------#
#                    Variable&Function                     #
#----------------------------------------------------------#

ip_name=${1}
data_source=${2}
ip_version=${3:-v4}
autoupdate=${4:-yes}
force=${5:-no}

# Includes
source $HESTIA/func/main.sh
source $HESTIA/conf/hestia.conf


#----------------------------------------------------------#
#                    Verifications                         #
#----------------------------------------------------------#

check_args '1' "$#" 'NAME [SOURCE] [IPVERSION] [AUTOUPDATE] [FORCE]'
is_format_valid 'ip_name'
is_boolean_format_valid "$autoupdate" 'bool (yes/no)'
is_boolean_format_valid "$force" 'bool (yes/no)'
is_system_enabled "$FIREWALL_SYSTEM" 'FIREWALL_SYSTEM'

ipset_hstobject='../../data/firewall/ipset'

IPSET_BIN="$(which ipset)"
IPSET_PATH="$HESTIA/data/firewall/ipset"

if [ -z "$data_source" ]; then
    if [ ! -f "${IPSET_PATH}.conf" ] || [[ ! $(grep "LISTNAME='$ip_name'" "${IPSET_PATH}.conf") ]]; then
        check_args '2' "$#" 'NAME SOURCE [IPVERSION] [AUTOUPDATE] [FORCE]'
    fi

    data_source="$(get_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$SOURCE')"
    ip_version="$(get_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$IP_VERSION')"
else
    is_object_new "$ipset_hstobject" 'LISTNAME' "$ip_name"
fi

if [ "$ip_version" != "v4" ] && [ "$ip_version" != "v6" ]; then
    check_result $E_INVALID "invalid ip version, valid: (v4|v6)"
fi

if ! echo "$data_source" | egrep -q '^(https?|script|file):'; then
    check_result $E_INVALID "invalid ipset source, valid: (http[s]://|script:|file:)"
fi


IPSET_FILE="${ip_name}.${ip_version}"
IPSET_MIN_SIZE=10

# Perform verification if read-only mode is enabled
check_hestia_demo_mode

# Install ipset package if missing
if [ -z "$IPSET_BIN" ]; then
    apt-get --quiet --yes install ipset > /dev/null
    check_result $? "Installing ipset package"

    IPSET_BIN="$(which ipset)"
    check_result $? "ipset binary not found"
fi


#----------------------------------------------------------#
#                       Action                             #
#----------------------------------------------------------#

mkdir -p "$IPSET_PATH"

# Generate ip lists file if missing or when forced
if [ ! -f "${IPSET_PATH}/${IPSET_FILE}.iplist" ] || [ "$force" = "yes" ]; then

    iplist_tempfile=$(mktemp)

    if [[ "$data_source" =~ ^https?:// ]]; then

        wget --tries=3 --timeout=15 --read-timeout=15 --waitretry=3 --no-dns-cache --quiet "$data_source" -O "$iplist_tempfile"
        check_result $? "Downloading ip list"

        # Advanced: execute script with the same basename for aditional pre-processing
        # ex: 
        if [ -x "${IPSET_PATH}/${IPSET_FILE}.sh" ]; then 
            setpriv --clear-groups --reuid nobody --regid nogroup -- ${IPSET_PATH}/${IPSET_FILE}.sh "$ip_name" "$iplist_tempfile"
        fi

    elif [[ "$data_source" =~ ^script:/ ]]; then

        # Generate the ip list file trough a external script
        # ex: compiling a ip list from multiple sources on demand

        if [ -x "${data_source#script:}" ]; then

            setpriv --clear-groups --reuid nobody --regid nogroup -- ${data_source#script:} "$ip_name" > "$iplist_tempfile"
            check_result $? "Running custom ip list update script"

        fi

    elif [[ "$data_source" =~ ^file:/ ]]; then

        # Use a external ip-list file managed by other apps
        # ex: Using a ip list that is continously updated

        [ -f "${data_source#file:}" ] && cp -f "${data_source#file:}" "$iplist_tempfile"

    fi

    # Validate iplist file size
    iplist_size=$(sed -r -e '/^#|^$/d' "$iplist_tempfile" | wc -l)
    [[ "$iplist_size" -le $IPSET_MIN_SIZE ]] && check_result $E_INVALID "iplist file too small (<${IPSET_MIN_SIZE}), ignoring"
    mv -f "$iplist_tempfile" "${IPSET_PATH}/${IPSET_FILE}.iplist"

fi

# Load ipset in kernel
inet_ver="inet"
[ "$ip_version" == "v6" ] && inet_ver="inet6"

$IPSET_BIN -quiet create -exist "$ip_name" hash:net family $inet_ver
$IPSET_BIN -quiet destroy "${ip_name}-tmp"
$IPSET_BIN create "${ip_name}-tmp" -exist hash:net family $inet_ver maxelem 1048576
$IPSET_BIN flush "${ip_name}-tmp"

sed -rn -e '/^#|^$/d'  -e "s/^(.*)/add ${ip_name}-tmp \\1/p" "${IPSET_PATH}/${IPSET_FILE}.iplist" | $IPSET_BIN -quiet restore
check_result $? "Populating ipset table"

$IPSET_BIN swap "${ip_name}-tmp" "${ip_name}"
$IPSET_BIN -quiet destroy "${ip_name}-tmp"


# Generating timestamp
time_n_date=$(date +'%T %F')
time=$(echo "$time_n_date" |cut -f 1 -d \ )
date=$(echo "$time_n_date" |cut -f 2 -d \ )

if [ ! -f "${IPSET_PATH}.conf" ] || [ -z "$(get_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$LISTNAME')" ]; then

    # Concatenating rule
    str="LISTNAME='$ip_name' IP_VERSION='$ip_version' SOURCE='$data_source'"
    str="$str AUTOUPDATE='$autoupdate' SUSPENDED='no'"
    str="$str TIME='$time' DATE='$date'"
    echo "$str" >> $HESTIA/data/firewall/ipset.conf

elif [ "$force" = "yes" ]; then

    # update iplist last regen time
    update_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$TIME' "$time"
    update_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$DATE' "$date"

fi

# Changing permissions
chmod 660 $HESTIA/data/firewall/ipset.conf
chmod 660 "${IPSET_PATH}/${IPSET_FILE}.iplist"

# Install ipset daily cron updater
if ! grep --silent --no-messages "v-update-firewall-ipset" $HESTIA/data/queue/daily.pipe; then
    cmd="$BIN/v-update-firewall-ipset yes"
    echo "$cmd" >> $HESTIA/data/queue/daily.pipe
fi


#----------------------------------------------------------#
#                       Hestia                             #
#----------------------------------------------------------#

# Logging
log_event "$OK" "$ARGUMENTS"

exit
