#!/bin/bash

# Copyright: 2023 Virtuozzo International GmbH

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.



[ -n "${OSLIB_VERSION:-}" ] && return 0;
OSLIB_VERSION="0.1";

include math;
inherit tests output net;

__FALSE=1;
__TRUE=0;

__NEED_REBOOT=0;

$PROGRAM 'service';

# workaround variables inside the envinfo.lib {{{
SERVICE_CMD="${SERVICE}";
declare +r SERVICE;
# }}}

$PROGRAM 'ps';
$PROGRAM 'chkconfig';
$PROGRAM 'ip';
$PROGRAM 'grep';
$PROGRAM 'sed';

_FD_STDIN=0;
_FD_STDOUT=1;
_FD_STDERR=2;

__AUNITS=(b kb mb gb tb);


# $1 - array to encode
# example call:
#       funcname $(encodeArr arrayName);
function encodeArr() {
    echo "$(base64 -w 0 <<< $(declare -p $1))";
}

# $1 - base64 to decode
# example call:
#       eval $(decodeArr $1 varname);
function decodeArr() {
    echo "$(base64 -d -i <<< $1 | $SED -re "s/(^declare\s+-a\s)([^=]+)=(.*)/\1$2=\3/")";
}

# $1 value
# $2 from
# $3 to
function convertUnits() {
    local value=${1:-0} op ifrom=$(indexOf "${__AUNITS[*]}" $(${SED} -e 's/\(.*\)/\L\1/' <<< ${2})) ito=$(indexOf "${__AUNITS[*]}" $(${SED} -e 's/\(.*\)/\L\1/' <<< ${3}));
    local result=${value};

    [[ ${ifrom} < ${ito} ]] && { op='/'; } || {
        op='*';
        ifrom=$(( ${ifrom}-${ito} ));
        ito=$(( ${ito} + ${ifrom} ));
        ifrom=$(( ${ito} - ${ifrom} ));
    }

    while [ "${ifrom}" -lt "${ito}" ] ; do
        isFunction floatEval &&  result=$(floatEval "${result} ${op} 1024") || eval "result=$(( ${result} ${op} 1024 ))";
        ifrom=$((${ifrom} + 1));
    done
    echo ${result};
}

function enableService() {
    local name=${1} level=${2:-3} doStart=${3:-${__TRUE}};
    [[ ${doStart} == ${__TRUE} ]] && startService ${name};
    ${CHKCONFIG} --level ${level} ${name} on;
    return ${?};
}


# $1 - service name
function startService() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} start;
    result=$?;
    sleep 1;
    return ${result};
}

# $1 - service name
function startServiceSilent() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} start >>$ACTIONS_LOG 2>&1;
    result=$?;
    sleep 1;
    return ${result};
}


function stopService() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} stop;
    result=$?;
    sleep 1;
    return ${result};
};

# $1 - service name
function stopServiceSilent() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} stop >>$ACTIONS_LOG 2>&1;
    result=$?;
    sleep 1;
    return ${result};
}

function reloadService() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} reload;
    return ${?};
}

function reloadServiceSilent() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} reload >>$ACTIONS_LOG 2>&1;
    return ${?};
}

function restartServiceSilent() {
    local name=${1};
    ${SUDO_COMMAND} ${SERVICE_CMD} ${name} restart >>$ACTIONS_LOG 2>&1;
    return ${?};
}

# $1 - process name
function getPID() {
    local procname=${1};
    echo $(${PS} ax | ${GREP} ${procname} | ${GREP} -v grep | ${SED} -e 's/\([0-9]\+\).*/\1/');
}

function getMemory() {
    local memtype=${1:-MemTotal} units=${2:-kb} result;
    result=$(${SED} -ne "/${memtype}:/{s/${memtype}:\s\+\([0-9]\+\).*/\1/i;p};h" /proc/meminfo );
    echo $(convertUnits ${result} kb ${units} );
}

# listFDs()
#       print list of opened file discriptors
function listFDs() {
    local label=${1:-'Opened fds:'}
    local pid=${2:-$$}
    echo $(ls /proc/$$/fd | sort -n | awk 'BEGIN{printf("%s ", "'"${label}"'")} {printf("%s ", $1)}');
}

# closeFD()
#       param: 1 - file descriptor to close
function closeFD() {
    local fd=$1;
    eval "exec ${fd}>&-;exec ${fd}<&-";
}

function editConfigProperty() {
    local file=${1} propertyName=${2} propertyValue=${3:-""};
    [[ -a "${file}" ]] && ${GREP} -Pq '^${propertyName}\s*=.*' ${file} && { addProperty ${file} ${propertyName} ${propertyValue}; return $?; };
    ${SED} -rie "s/(^${propertyName}\s*=\"?)([^\"]+)(\"?)$/\1${propertyValue}\3/" ${file};
    return ${?};
}

function addConfigProperty() {
    local file=${1} propertyName=${2} propertyValue=${3:-""};
    [[ -a "${file}" ]] && ${GREP} -Pq '^${propertyName}\s*=.*' ${file} && { editProperty ${file} ${propertyName} ${propertyValue}; return $?; };
    echo "${propertyName}=\"${propertyValue}\"" >> ${file};
    return ${?};
}

function getConfigProperty() {
    local file=${1} propertyName=${2};
    echo $(${SED} -rne "/^${propertyName}\s*=/{s/^${propertyName}\s*=\"?([^\"]+)\"?/\1/;p};" ${file});
}

function disableSelinux() {
    local file='/etc/sysconfig/selinux';
    local state=$(getConfigProperty ${file} 'SELINUX');
    [[ ${state} != 'disabled' ]] && editConfigProperty ${file} 'SELINUX' 'disabled' && editConfigProperty ${file} 'SELINUXTYPE' 'targeted' && __NEED_REBOOT=${__TRUE}
    return ${?};
}

function enableSelinux() {
    local file='/etc/sysconfig/selinux';
    local state=$(getConfigProperty ${file} 'SELINUX');
    [[ ${state} == 'disabled' ]] && editConfigProperty ${file} 'SELINUX' 'permissive' && editConfigProperty ${file} 'SELINUXTYPE' 'targeted' && __NEED_REBOOT=${__TRUE};
    return ${?};
}

function needReboot() {
    return ${__NEED_REBOOT};
}

function __getAddr() {
    : ${1:?"Missing param: interface"};
    : ${2:?"Missing param: variable name"};
    : ${3:-"-4"};
    local dev=$1 name=$2 af=$3 alist;
    alist=$($IP $af addr show dev $dev 2>/dev/null | $SED -rne '/inet/{s/.*inet6* //;s/\/.*//;s/peer.*//;p}');
    [ -z "$alist" ] && {
        eval "$name=''";
        return 1;
    }
    eval "${name}_list=\"$alist\"";
    return $?;
}

function __getNet() {
    local dev=$1 name=$2 af=$3 alist;
    alist=$($IP route list dev $dev | $SED 's/ .*//;/default/d');
    [ -z "$alist" ] && {
        eval "$name=''"
        return $?;
    }
    eval "${name}_list=\"$alist\"";
    return $?;
}

function getIfaceWild() {
    : ${1:?"Missing param: wildcard"};
    local mask=$1;
    echo $($IP link show | $SED -rne "/^[0-9]+:\s+${mask}:/{s/.*:\s+(\S+):.*/\1/g;p}");
    return $?
}

function getAddr() {
    __getAddr $1 $2 "-4";
}

function getAddr6() {
    __getAddr $1 $2 "-6";
}

function getNet() {
    __getNet $1 $2 "-4";
}

function getNet6() {
    __getNet $1 $2 "-6";
}

#function getNSAddr() {
#    echo $($SED -rne 's/nameserver\s+(.*)/\1/;/(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/p' /etc/resolv.conf);
#    return 0;
#}

#function getLocalNSAddr() {
#    local NS=($(getNSAddr)) locNS=();
#    for i in ${NS[@]} ; do
#        isLANIP $i && locNS=(${locNS[@]} $i);
#    done;
#    [ -n "${locNS}" ] && { echo "${locNS[@]}" | $SED -re 's/\s+/\n/g' | uniq ; return 0; };
#    return 1;
#}

function getHostIP() {
    : ${1:?"Missing param: hostname"};
    echo $(host ${1} | $SED '/alias for/d' | $SED -re '/NXDOMAIN/d;s/.+\s+(([0-9]{1,3}\.){3}[0-9]{1,3})/\1/' | head -n 1);
}

function getOSVersion() {
    : ${1:?"Missing param: issue_string"}
    local issue_string=$1
    local OS_version=$($GREP -h VERSION_ID <<< "$issue_string" 2>/dev/null | $SED -nre 's/.*=\"?([0-9.]*).*/\1/;p;n' | head -n 1)
    if [ -z "$OS_version" ] ; then
        OS_version=$($GREP -h DISTRIB_RELEASE <<< "$issue_string" 2>/dev/null | $SED -nre 's/.*=\s?\"?([0-9.]*).*/\1/;p;n' | head -n 1)
        if [ -z "$OS_version" ] ; then
            if $GREP -hqi "jessie" <<< "$issue_string" 2>/dev/null ; then
                OS_version="8"
            elif $GREP -hqi "stretch" <<< "$issue_string" 2>/dev/null ; then
                OS_version="9"
            elif $GREP -hqi "buster" <<< "$issue_string" 2>/dev/null ; then
                OS_version="10"
            fi

            if [ -z "$OS_version" ] ; then
                OS_version=$($GREP -m 1 -h "CentOS release" <<< "$issue_string" 2>/dev/null | $SED -nre 's/[a-zA-Z ]*\s([0-9.]*).*/\1/;p;n')
            fi

            if [ -z "$OS_version" ] ; then
                OS_version=$($GREP -m 1 -hP "Ubuntu|Debian" <<< "$issue_string" 2>/dev/null | $SED -nre 's/[a-zA-Z\/ ]*\s([0-9.]*).*/\1/;p;n')
            fi

            if [ -z "$OS_version" ] ; then
                OS_version=$($GREP -m 1 -h "Alpine" <<< "$issue_string" 2>/dev/null | $SED -nre 's/[a-zA-Z\/ ]*\s([0-9]*).*/\1/;p;n')
                $GREP -q "Alpine" <<< "$issue_string" && OS_version=3
            fi

            if [ -z "$OS_version" ] ; then
                #Oracle Linux Server release 6.6
                OS_version=$($GREP -m 1 -h "release" <<< "$issue_string" 2>/dev/null | $SED -nre 's/[a-zA-Z ]*\s([0-9.]*).*/\1/;p;n')
            fi
        fi
    fi
    echo $OS_version
}

function detectOS() {
    : ${1:?"Missing param: issue_string"};
    local issue_string=$1

    [ -z "$_OS" ] && $GREP -iq "almalinux" <<< $issue_string && _OS="almalinux"
    [ -z "$_OS" ] && $GREP -iq "vzlinux\|virtuozzo" <<< $issue_string && _OS="vzlinux"
    [ -z "$_OS" ] && $GREP -iq "kali" <<< $issue_string && _OS="kali"
    [ -z "$_OS" ] && $GREP -iq "ubuntu\|mint" <<< $issue_string && _OS="ubuntu"
    [ -z "$_OS" ] && $GREP -iq "debian" <<< $issue_string && _OS="debian"
    [ -z "$_OS" ] && $GREP -iq "centos\|rhel" <<< $issue_string && _OS="centos"
    [ -z "$_OS" ] && $GREP -iq "Oracle Linux Server" <<< $issue_string && _OS="centos"
    [ -z "$_OS" ] && $GREP -iq "fedora" <<< $issue_string && _OS="fedora"
    [ -z "$_OS" ] && $GREP -iq "redhat" <<< $issue_string && _OS="centos"
    [ -z "$_OS" ] && $GREP -iq "suse" <<< $issue_string && _OS="suse"
    [ -z "$_OS" ] && $GREP -iq "alpine" <<< $issue_string && _OS="alpine"
    echo $_OS
}

function installPackageCmd() {
    : ${1:?"Missing param: OS"};
    local OS=$1; shift
    local pmaction=$1; shift
    local isCertified=""
    if [ ! -z "${1##*[!0-9]*}" ]; then
        isCertified=$1; shift
    fi
    local _apk_wait_timeout=${APK_WAIT_TIMEOUT:="10"}
    local proxy_env="export http_proxy=\"$(printenv http_proxy)\"; export https_proxy=\"$(printenv https_proxy)\"; export ftp_proxy=\"$(printenv ftp_proxy)\"; export no_proxy=\"$(printenv no_proxy)\"";
    for arg do PKGS="$arg $PKGS"; done
    case $OS in
        suse)
                PM="$proxy_env; zypper --quiet --non-interactive"
            ;;
        debian|ubuntu|kali)
                PM="$proxy_env; DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated -o Dpkg::Options::=\"--force-confold\""
            ;;
        centos|redhat|fedora|vzlinux)
                PM="$proxy_env; yum -y"
            ;;
        almalinux)
                if [[ "$isCertified" -gt 0 ]]; then
                    PM="$proxy_env; microdnf -y"
                else
                    PM="$proxy_env; yum -y"
                fi
            ;;
        alpine)
                PM="$proxy_env; apk --wait $_apk_wait_timeout"
                case $pmaction in
                    install)
                        pmaction="add -f"
                        ;;
                    remove)
                        pmaction="del -f"
                esac
            ;;
    esac
    echo "$PM $pmaction $PKGS"
}

function checkPackageCmd() {
    : ${1:?"Missing param: OS"};
    local OS=$1; shift
    local _apk_wait_timeout=${APK_WAIT_TIMEOUT:="10"}
    local res=""

    case $OS in
        debian|ubuntu|kali)
            ltpl='dpkg -s %s >/dev/null 2>&1 || echo "%s" ;'
            ;;
        centos|redhat|fedora|suse|almalinux)
            ltpl='rpm -q %s >/dev/null 2>&1 || echo "%s" ;'
            ;;
        alpine)
            ltpl='p="%s";if apk | grep -q list ; then [ -n "$(apk --wait '$_apk_wait_timeout' list -I $p)" ] || echo $p ; else [ -z "$(apk --wait '$_apk_wait_timeout' version -q $p)" ] && echo %s; fi ;'
            ;;
    esac
    for arg do
        printf -v cmd "$ltpl" "$arg" "$arg"
        res="$res $cmd"
    done
    echo $res
}

function getExportedDirList()
{
    [ ! -f "$1" ] && return 1
    OIFS=$IFS;IFS=$'\n'
    adir=("${adir[@]}" $(sed -rne 's/^[ \t]*//' -e '/^#/d' -e '/^"/d' -e 's/([^ ]+)\s+.*/\1/' -e '/^$/d' -e p "$1"))
    adir=("${adir[@]}" $(sed -rne 's/^[ \t]*//' -e '/^#/d' -e '/^[^"]/d' -e 's/"(.*)"\s+.*/"\1"/' -e '/^$/d' -e p "$1"))
    IFS=$OIFS
    for a in "${adir[@]}"; do
        b="${a/#\"/}"
        b=${b/%\"/}
        echo "$b"
    done
    return 0
}

function isShareExistsOnDisk() {
    [ -d "$1" ]
}

# $1 - share name
# $2 - exports file
function LookupShare() {
    [ ! -f "$2" ] && return 1
    [[ "x${1:$((${#1}-1)):1}" == "x/" ]] && shareName=${1/%\//} || shareName=$1
    dpath="^\"?($(sed -re 's/\//\\\//g' <<< $(sed -re 's/\^/\\^/g' -e 's/\$/\\$/g' -e 's/(\+\{|\}|\[|\]|\(|\))/\\\1/g'  -e 's/\s/(\\\\040|\\s)/g' <<< "$shareName")))\/?\"?\s+";
    [[ "$dpath" != "${dpath/\\\\040/}" ]] && brf="\3" || brf="\2"
    opts=()
    while read l ; do
        eval "$l";
        opts=("${opts[@]}" $soptions)
    done <<< $(sed -re '/'$dpath'/!d' -e 's/'$dpath'(.*)/sname="\1";soptions="'$brf'"/' "$2")
    echo ${opts[*]}
    [[ ${#opts} -gt 0 ]] && return 0;
}

# $1 - share name
# $2 - exports file
# $3 - share prefix
function isValidShare()
{
    [ ! -f "$2" ] && return 1
    if isContainerRunning ${CTID};  then
        vzctl exec2 ${CTID} "[ -d \"$1\" ]" || return 1;
    else
        isShareExistsOnDisk $3$1 || return 1;
    fi
    local opts=$(LookupShare "$1" "$2")
    [[ -z "$opts" ]] && return 1
    return 0
}

getRealExternalIp(){
    local extip
    local services=( 'http://ifconfig.co' 'http://ifconfig.io' 'http://ipecho.net/plain' 'http://ipinfo.io/ip' )
    for i in ${services[@]}; do
        extip=$(curl -4 -m 3 --connect-timeout 2 -Lkq --silent "$i")
        [[ ! -z "$extip" ]] && isValidIP "$extip" && { echo "$extip"; return 0; }
    done
    which dig &>/dev/null || yum -y install bind-utils &>>${JEM_CALLS_LOG}
    extip=$(dig TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d \")
    [[ ! -z "$extip" ]] && isValidIP "$extip" && { echo "$extip"; return 0; }
    { writeJSONResponseErr "result=>4212" "message=>Failed to get real external IP" ; return 99; }
}

getOutgoingIp(){
    local EXTERNAL_IP=$(ip route get 1 | sed -nre 's/.*src\s(.*)\s/\1/p')
    [[ ! -z "$EXTERNAL_IP" ]] && isValidIP "$EXTERNAL_IP" && { echo "$EXTERNAL_IP"; return 0; }
    { writeJSONResponseErr "result=>4212" "message=>Failed to get external IP" ; return 99; }
}

detectFirewallBackend() {
    if [ -z "$DISTRIBUTION_NAME" ] ; then
        DISTRIBUTION_NAME=$(detectOS "$1")
    fi

    if [ -z "$DISTRIBUTION_VERSION" ] ; then
        DISTRIBUTION_VERSION=$(getOSVersion "$1")
    fi
    DISTRIBUTION_VERSION=${DISTRIBUTION_VERSION%%.*}

    if [ "x${DISTRIBUTION_NAME,,}" == "xalmalinux" -a "$DISTRIBUTION_VERSION" -ge 9 ] ; then
        echo "nftables"
        return 0
    fi

    vzexecRun "which nft"
    if [ ! -z "$(vzexecGetLastStdOut)" ] ; then
        vzexecRun "nft list ruleset"
        if [ ! -z "$(vzexecGetLastStdOut)" ] ; then
            echo "nftables"
            return 0
        fi
    fi

    echo "iptables"
    return 0
}
