#!/bin/sh
#-------------------------------------------------------------------------------
# charon_common
#-------------------------------------------------------------------------------
# Copyright (C) 2013-2022 STROMASYS.
# All rights reserved.
#
VERSION=1.36
for P in `ls /etc/profile.d/charon*`;do . $P;done

EXPERIMENTALMODE=/root/.charon.experimental
test -e ${EXPERIMENTALMODE} && LANG=en_US.UTF-8

CHARONDIR=/opt/charon
HASPERRCODES=${CHARONDIR}/utils/haspruntime.errorcodes
PREFFILE=/opt/charon/utils/charon.preferences
VENDOR_ID=68704

NCUBIN=/opt/charon/bin/ncu

#-------------------------------------------------------------------------
# get_lastused - get and clean last used files (latest VM selected)
#-------------------------------------------------------------------------
function get_lastused
{
  LASTUSED="/root/.charon.last.vmused.`ps -h -o ppid -p $$|tr -dc [:digit:]`"

  #--- Compatibility with previous method
  test -e /root/.charon.last.vmused && mv -f /root/.charon.last.vmused ${LASTUSED} 2>/dev/null

  #--- If LASTUSED does not exist, use the most recent one if any
  if test ! -e ${LASTUSED}
  then
    LASTFND=`ls -1tr /root/.charon.last.vmused.* 2>/dev/null | tail -1`
    if test -n "${LASTFND}"
    then
      cp -f ${LASTFND} ${LASTUSED} 2>/dev/null
    fi
  fi

  #--- Removing files that are no more attached to an existing PID
  ls /root/.charon.last.vmused.* 2>/dev/null | while read LVMU
  do
    LVMUPID=`echo ${LVMU} | cut -f5 -d'.'`
    test -e /proc/${LVMUPID} || rm -f ${LVMU} 2>/dev/null
  done
}

#-------------------------------------------------------------------------
# get_preferences - gets the user's preferences
#-------------------------------------------------------------------------
function get_preferences
{
  #--- Default values if preferences are not set
  CUIEDIT="none"
  GUIEDIT="none"
  SELEDIT="always_ask"
  LOGTAILST="true"
  LOGTAILCT="enabled"
  OOMPRVPIDKILL="disabled"
  SEMIGRAPH="enabled"
  LISTSEPAR="none"
  GLOBALLOGMON="enabled"
  SVCTIMOUTSTART=20
  SVCTIMOUTSTOP=20
  AUTOSTOPLOGTAIL=1
  ENABLESUDO=0
  CONSOLEMODE="always_ask"
  DEFAULTBLUE=0

  if test -e ${PREFFILE}
  then
    test -x ${PREFFILE} || chmod u+x ${PREFFILE} 2>/dev/null
    . ${PREFFILE}
  fi

  if test ${DEFAULTBLUE:-0} = 0
  then
    FGBLUE="[38;5;27m"
    FGBLUEBOLD="[38;5;32m"
  else
    FGBLUE="[34m"
    FGBLUEBOLD="[34m[1m"
  fi
 
}


#-------------------------------------------------------------------------
# get_logfile - gets log file name from configuration file
#-------------------------------------------------------------------------
function get_logfile
{
  #--- $1 = configuration file or -renext (rename extension)
  #--- $2 = Either OPA0 for console log or not specified for VM log
  LOGF=""
  LOGW=""
  ROTA=0
  LOGK=${2:-session}
  if test "$1" != "-renext"
  then
    GETLOG=`grep "^set[ \t]\+${LOGK}[ \t]\+log" $1|grep -v ^#|tr "	" " "|sed "s/  //g"|sed "s: = :=:g"|sed "s: =:=:g"|sed "s:= :=:g"`
    for T in ${GETLOG}
    do
      if test "`echo ${T} | cut -c1-4`" = "log="
      then
        LOGW=`echo ${T} | sed "s:log=::g" | tr -d '"' | tr -d "'"`
        # Starting with V4.7 B17101
        BLD=`rpm -q charon-base charon-axp-base charon-axp charon-vax | grep -v 'not installed' | head -1 | sed "s=\(^.*-\)\([0-9][0-9][0-9][0-9][0-9]\)\(.*$\)=\2=g"`
        if test ${BLD:-0} -ge 17100 -a -d ${LOGW}
        then
          ROTA=1
          GETCFGNAM=`grep "^set[ \t]\+session[ \t]\+configuration_name" $1|grep -v ^#|cut -f2 -d'='|sed "s=\s==g"|tr -d "'"|tr -d '"'`
          if test -n "${GETCFGNAM}"
          then
            test -z "$2" && LOGW="${LOGW}/${GETCFGNAM}.log" || LOGW="${LOGW}/${GETCFGNAM}-OPA0.log"
          else
            GETHWMOD=`grep "^set[ \t]\+session[ \t]\+hw_model" $1|grep -v ^#|cut -f2 -d'='|sed "s=\s==g"|tr -d "'"|tr -d '"'`
            test -z "$2" && LOGW="${LOGW}/${GETHWMOD}.log" || LOGW="${LOGW}/${GETHWMOD}-OPA0.log"
          fi
        fi
      fi
    done
    if test -z "$2"
    then
      LOGF=${LOGW}
    else
      LOGCONS=${LOGW}
    fi
  fi
  LOGFRENEXT="upto"`date +%F-%H%M%S`

}

#-------------------------------------------------------------------------
# ask_editor - asks for the editor to use depending on installed ones
#-------------------------------------------------------------------------
function ask_editor
{
  get_preferences
  if test "${CUIEDIT}" = "none"
  then
    echo 
    echo -n "[44m[37mAvailable editors[0m"

    EDILIST=""
    if test -n "${DISPLAY}"
    then
      if test "${SEMIGRAPH}" = "enabled"
      then
        tput smacs
        echo "${FGBLUE}qqqqqqqqqqqqqqCUIq qGUIqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
        tput rmacs
      else
        echo "${FGBLUE}--------------CUI- -GUI----------------------------------------"
      fi
      tput sgr0
    else
      echo
    fi

    if test -x /bin/nano
    then
      printf "%-36s" "n - nano (basic)"
      EDILIST="${EDILIST} =n="
    fi
    if test -x /usr/bin/gedit -a -n "${DISPLAY}"
    then
      echo "g - gedit (windows notepad like)"
      EDILIST="${EDILIST} =g="
    else
      echo
    fi

    if test -x /bin/vi
    then
      printf "%-36s" "v - vi   (advanced users)"
      EDILIST="${EDILIST} =v="
    fi
    if test -x /usr/bin/emacs -a -n "${DISPLAY}"
    then
      echo "e - emacs (windows notepad like, advanced)"
      EDILIST="${EDILIST} =e="
    else
      echo
    fi

    if test -x /usr/bin/vim
    then
      printf "%-36s" "m - vim  (advanced users)"
      EDILIST="${EDILIST} =m="
    fi
    if test "$1" != "nogvim"
    then
      if test -x /usr/bin/gvim -a -n "${DISPLAY}"
      then
        echo "M - gvim  (GUI version)"
        EDILIST="${EDILIST} =M="
      else
        echo
      fi
    else
      echo
    fi

    #test -z "${DISPLAY}"  && echo
    test "$1" != "noquit" && echo "q - quit"

    while test 1
    do
      echo -n "${FGBLUE}Select the editor you want to use:[0m "
      read ANS
      case "${ANS}"
      in
        g)
          if test -n "`echo ${EDILIST} | grep =${ANS}=`"
          then
            EDI="/usr/bin/gedit"
            break
          fi
          ;;
        e)
          if test -n "`echo ${EDILIST} | grep =${ANS}=`"
          then
            EDI="/usr/bin/emacs"
            break
          fi
          ;;
        n)
          if test -n "`echo ${EDILIST} | grep =${ANS}=`"
          then
            EDI="/bin/nano"
            break
          fi
          ;;
        v)
          if test -n "`echo ${EDILIST} | grep =${ANS}=`"
          then
            EDI="/bin/vi"
            break
          fi
          ;;
        m)
          if test -n "`echo ${EDILIST} | grep =${ANS}=`"
          then
            EDI="/usr/bin/vim"
            break
          fi
          ;;
        M)
          if test -n "`echo ${EDILIST} | grep =${ANS}=`"
          then
            EDI="/usr/bin/gvim -f"
            break
          fi
          ;;
        q)
          if test "$1" = "noquit"
          then
            tput cuu1;tput el
          else 
            EDI=""
            break
          fi
          ;;
        *)
          tput cuu1;tput el
          ;;
      esac
    done
  else
    if test "${SELEDIT}" = "auto"
    then
      if test -z "${DISPLAY}"
      then
        EDI=${CUIEDIT}
      else
        if test "${GUIEDIT}" = "none"
        then
          EDI=${CUIEDIT}
        else
          EDI=${GUIEDIT}
        fi
      fi
    else
      if test "${GUIEDIT}" = "none"
      then
        EDI=${CUIEDIT}
      else
        echo
        echo    "[44m[37mYou have defined the following editors as the default ones[0m"
        echo    "- `basename ${CUIEDIT}` for the Character user interface (c)"
        echo    "- `basename ${GUIEDIT}` for the Graphical user interface (g)"
        while test 1
        do
          echo -n "${FGBLUE}Select the editor you want to use (c/g"
          test "$1" = "noquit" || echo -n "/q"
          echo -n "):[0m "
          read ANS
          case "${ANS}"
          in
            c|C)
              EDI=${CUIEDIT}
              break
              ;;
            g|G)
              EDI=${GUIEDIT}
              break
              ;;
            q)
              if test "$1" = "noquit"
              then
                tput cuu1;tput el
              else 
                EDI=""
                break
              fi
              ;;
            *)
              tput cuu1;tput el
              ;;
          esac 
        done
      fi
    fi
    test "${EDI}" = "/usr/bin/gvim" && EDI="/usr/bin/gvim -f"
  fi
}

#-------------------------------------------------------------------------
# select_mailto - select email recipient
#-------------------------------------------------------------------------
function select_mailto
{
  LAST_RECIP=`cat /root/.charon.lastrecipient 2>/dev/null`
  echo
  while test 1
  do
    printf "Enter the email recipient (q to quit) [${LAST_RECIP}]: "
    read RECIP
    case "${RECIP}"
    in
      q)
        >/root/.charon.recipient
        break
        ;;
      "")
        echo ${LAST_RECIP} >/root/.charon.recipient
        break
        ;;
      *@*.*)
        echo ${RECIP} >/root/.charon.recipient
        echo ${RECIP} >/root/.charon.lastrecipient
        while test 1
        do
          printf "Confirm (y/n) : "
          read CH
          case "${CH}"
          in
            y) break 2;;
            n) break;;
            *) tput cuu1;tput el;;
          esac
        done
        ;;
      *)
        echo "This does not seem to be a valid email adress. Please retry."
        ;;
    esac
  done
}

#-------------------------------------------------------------------------
# display_header - display common header and title
#-------------------------------------------------------------------------
function display_header
{
  get_preferences
  tput clear
  DISPV=`grep ^VERSION $0| cut -f2 -d'='`
  if test "${SEMIGRAPH}" = "enabled"
  then
    tput smacs
    echo -n "lq"
    tput rmacs
    echo -n "[44m[37mSTROMASYS - Legacy server emulation[0m"
    tput smacs
    echo "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk"
    tput cup 0 71
    if test -n "${DISPV}"
    then
      tput rmacs
      echo "${FGBLUE}V${DISPV}[0m"
      tput smacs
    fi
    DISPHEAD=`echo "$1                                                                               " | cut -c1-76`
    echo -n "x "
    tput rmacs
    echo -n "${DISPHEAD}"
    tput smacs
    echo " x"
    echo "mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj"
    tput rmacs
  else
    echo -n "+-"
    echo -n "[44m[37mSTROMASYS - Legacy server emulation[0m"
    echo "------------------------------------------+"
    tput cup 0 71
    test -n "${DISPV}" && echo "${FGBLUE}V${DISPV}[0m"
    DISPHEAD=`echo "$1                                                                               " | cut -c1-76`
    echo "| ${DISPHEAD} |"
    echo "+------------------------------------------------------------------------------+"
  fi
}

#-------------------------------------------------------------------------
# check_network_settings - check some parameters in network configuration
#-------------------------------------------------------------------------
function check_network_settings
{
  #--- $1 = config file
  CFG=$1
  #--- $2 = alert mode: "display" or "alert"
  ALT=$2
  if test "$2" = "display"
  then
    echo "[44m[37m Checking network interfaces... [0m"
  fi

  GUEST=`basename ${CFG}|sed "s=\.cfg$==g"`
  CFGISSUE=0
  ETHDISP=0
  for LOADPKT in `grep -w ^load $1 | grep -w -e packet_port -e tap_port | grep -w interface | sed "s:=: :g" | awk '{print $3,$5}' | tr -d "'\"" | tr " " "/"`
  do
    PKP=`echo ${LOADPKT} | cut -f1 -d"/"`
    ETH=`echo ${LOADPKT} | cut -f2 -d"/"`
    test -z "${ETH}" && ETH="UNKNOWN"
    # if test -z "`ifconfig ${ETH} 2>/dev/null`"
    if test -z "`ip a | grep -w ${ETH}: 2>/dev/null`"
    then
      if test "$2" = "display"
      then
        if test ${ETHDISP} = 0
        then
          test "${ETH}" != "(disabled)" && ETHDISP=1
        fi
        if test "${ETH}" = "(disabled)"
        then
          echo "[30m[46mWarning/dis[0m: Found disabled interface (${PKP})"
        else
          if test "`echo ${ETH}|cut -c1-11`" = "connection:"
          then
            echo "[30m[41mError/synt[0m: The syntax '${ETH}' is specific to Charon"
            echo "            on Windows, not Linux. Please update (${PKP})."
          else
            echo "[30m[41mError/inex[0m: Interface '${ETH}' does not exist (${PKP})"
          fi
        fi
      else
        if test "${ETH}" != "(disabled)"
        then
          echo "[ERROR] Interface ${ETH} does not exist (${PKP})"
          if test -x ${CHARONDIR}/utils/charon_check.alertcmd
          then
            echo ${CHARONDIR}/utils/charon_check.alertcmd IFCFGUNKN "${GUEST}" "${ETH}" | at now >/dev/null 2>&1
          fi
        fi
      fi
    else
      # IFADDR=`ifconfig ${ETH} | grep -w inet | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -1`
      IFADDR=`ip -o a show ${ETH} | grep -w inet | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -1`
      if test -n "${IFADDR}"
      then
        if test "$2" = "display"
        then
          test ${ETHDISP} = 0 && ETHDISP=1
          echo "[30m[41mError/used[0m: Interface '${ETH}' (${PKP}) has an assigned IP address (${IFADDR}). Please check."
        else
          echo "[WARN ] Interface ${ETH} (${PKP}) has an assigned IP address (${IFADDR}). It has been automatically disabled to make it work with Charon."
          NMCLI_VER=`nmcli --ver | sed -r -e 's/^[^[:digit:]]*([[:digit:].]+)([^[:digit:]^\.]|$).*/\1/'`
          if [[ $NMCLI_VER < "0.9.9.0" ]] 
          then
            nmcli dev disconnect iface ${ETH}
          else
            nmcli dev disconnect ${ETH}
          fi
          if test -x ${CHARONDIR}/utils/charon_check.alertcmd
          then
            echo ${CHARONDIR}/utils/charon_check.alertcmd IFCFGADDR "${GUEST}" "${ETH}" "${IFADDR}" | at now >/dev/null 2>&1
          fi
        fi
      else
        if test ! -e ${NCUBIN}
        then
          for PARCHK in "NM_CONTROLLED;no;WARNING" "ONBOOT;no;CRITICAL"
          do
            PARNAM=`echo ${PARCHK} | cut -f1 -d';'`
            VALEXP=`echo ${PARCHK} | cut -f2 -d';'`
            ALTMSG=`echo ${PARCHK} | cut -f3 -d';'`
  
            VALGET=`grep ^${PARNAM}= /etc/sysconfig/network-scripts/ifcfg-${ETH} 2>/dev/null | cut -f2 -d= | tr -d '"' | tr -d "'"`
            if test "${VALGET}" != "${VALEXP}"
            then
              if test "$2" = "display"
              then
                test ${ETHDISP} = 0 && ETHDISP=1
                COLORC=4;test "${ALTMSG}" = "CRITICAL" && COLORC=1
                echo "[30m[46mWarning/ifcfg[0m: ${ETH}/${PKP} Parameter: ${FGBLUE}${PARNAM}[0m, current value: [1m[3${COLORC}m${VALGET:-undefined}[0m, expected value: [1m[3${COLORC}m${VALEXP}[0m (${ALTMSG})"
                CFGISSUE=1
              else
                test "${ALTMSG}" = "CRITICAL" && echo -n "[ERROR]" || echo -n "[WARN ]"
                echo " Configuration issue found in ifcfg-${ETH} file: Parameter ${PARNAM}, current value: ${VALGET:-undefined}, expected value: ${VALEXP}"
                if test -x ${CHARONDIR}/utils/charon_check.alertcmd
                then
                  echo ${CHARONDIR}/utils/charon_check.alertcmd IFCFG`echo ${ALTMSG}|cut -c1-4` "${GUEST}" "${ETH}" "${PARNAM}" "${VALEXP}" "${VALGET}" | at now >/dev/null 2>&1
                fi
              fi
            fi
          done
        else
          NCUDIR=`dirname ${NCUBIN}`
          eval `grep ^NCU_CFG= ${NCUDIR}/* | cut -f2 -d':' | sort -u | head -1`

          if test -z "`grep -w ${ETH} ${NCU_CFG} 2>/dev/null`"
          then
            if test "$2" = "display"
            then
              echo "[30m[46mWarning/ncu[0m: Interface '${ETH}' is not managed by 'ncu' (${PKP})"
              CFGISSUE=1
            else
              echo "[WARN ] Interface ${ETH} is not managed by 'ncu' (${PKP})"
              if test -x ${CHARONDIR}/utils/charon_check.alertcmd
              then
                echo ${CHARONDIR}/utils/charon_check.alertcmd NCUWARN "${GUEST}" "${ETH}" | at now >/dev/null 2>&1
              fi
            fi
          fi
        fi
        if test "$2" != "display"
        then
          #--- Switching off all offload parameters before guest start
          echo "[INFO ] Switching off all offload parameters for interface ${ETH}"
          for FEATURE in rx tx sg tso ufo gso gro lro rxvlan txvlan
          do
            ethtool -K ${ETH} ${FEATURE} off >/dev/null 2>&1
          done
        fi
      fi
    fi

    #--- Check if interface is not already used
    NICCFD=/tmp/charon_chknic_dupl.tmp.$$
    rm -f ${NICCFD} 2>/dev/null
    for CFLINE in `grep -v ^# ${BOOTLIST}`
    do
      CF_CFG=`echo ${CFLINE} | cut -f2 -d';'`
      for LOADPKT in `grep -w ^load ${CF_CFG} | grep -w -e packet_port -e tap_port | grep -w interface | sed "s:=: :g" | awk '{print $3,$5}' | tr -d "'\"" | tr " " "/"`
      do
        CFPKP=`echo ${LOADPKT} | cut -f1 -d"/"`
        CFETH=`echo ${LOADPKT} | cut -f2 -d"/"`
        test -z "${CFETH}" && CFETH="UNKNOWN"
        if test "${CFETH}" = "${ETH}" -a "${ETH}" != "(disabled)"
        then
          if test "${CF_CFG}" = "$1"
          then
            echo "${CFETH} is used as ${CFPKP} in this configuration file." >>${NICCFD}
          else
            echo "${CFETH} is used as ${CFPKP} in ${CF_CFG} file." >>${NICCFD}
          fi
        fi
      done
    done
    if test `cat ${NICCFD} 2>/dev/null | wc -l` -gt 1
    then
      ETHDISP=1
      CFGISSUE=1
      if test "$2" = "display"
      then
        cat ${NICCFD} | sed "s=^=[30m[46mWarning/duplicate[0m: =g"
      else
        cat ${NICCFD} | sed "s=^=[WARN ] =g"
      fi
    fi
    rm -f ${NICCFD} 2>/dev/null
  done

  if test "$2" = "display"
  then
    if test "${ETHDISP}" = "1"
    then
      echo
      echo "`tput smul`Note`tput rmul`: if you update the ifcfg file, the network service must be restarted"
      echo "      and the guest(s) must be powered off/on (a Linux server reboot is"
      echo "      recommended)"
      echo
    else
      test ${CFGISSUE} = 0 && echo "Done."
    fi
  fi

  return ${ETHDISP}
}

#-------------------------------------------------------------------------
# check_vdisk_files - check if vdisk and iso files in configuration file
#                     are present
#-------------------------------------------------------------------------
function check_vdisk_files
{
  #--- $1 = config file
  CFG=$1
  #--- $2 = alert mode: "display" (interactive), "alert" (service)
  ALT=$2
  if test "$2" = "display"
  then
    echo "[44m[37m Checking virtual disks and iso files... [0m"
  fi

  VDKERR=0
  for VDK in `grep -e \.vdisk -e \.[iI][sS][oO] $1 | grep -v ^# | cut -f2 -d= | tr -d "'\" "`
  do
    if test ! -s ${VDK}
    then
      VDKERR=1
      if test "$2" = "display"
      then
        if test -d `dirname ${VDK}`
        then
          echo "- ${VDK}: [31mfile does not exist or is empty[0m"
        else
          echo "- ${VDK}: [31mfolder does not exist[0m"
        fi
      else
        if test -d `dirname ${VDK}`
        then
          echo "[ERROR] ${VDK}: file does not exist or is empty"
        else
          echo "[ERROR] ${VDK}: folder does not exist"
        fi
      fi
    fi
  done
  test "$2" = "display" && echo "Done."
  # (i) No need to send alert, log monitor will report the missing files if any / ERROR level
  return ${VDKDISP}
}

#-------------------------------------------------------------------------
# kill_emulator - kill's emulator process ($1)
# -> Returns 0 if successful, 1 if failed, 2 if process does not exist
#-------------------------------------------------------------------------
function kill_emulator
{
  ps -P $1 >/dev/null 2>&1
  if test $? -eq 0
  then
    typeset -i killtries=0
    while test ${killtries} -lt 5
    do
      killtries=killtries+1
      echo "[INFO ] Emulator stop with SIGTERM signal, pid $1, try#${killtries}"
      kill -15 $1
      sleep 2
      ps -P $1 >/dev/null 2>&1
      test $? -gt 0 && break
      sleep 8
    done
    ps -P $1 >/dev/null 2>&1
    if test $? -eq 0
    then
      #--- Force SIGKILL if SIGTERM is not successful
      echo "[WARN ] Emulator stop with SIGKILL signal, pid $1"
      kill -9 $1
      sleep 3
      ps -P $1 >/dev/null 2>&1
      if test $? -eq 0
      then
        return 1
      else
        return 0
      fi
    else
      return 0
    fi
  else
    #--- Process does not exist
    return 2
  fi
}
