#!/usr/bin/env bash

# shellcheck shell=bash

################################################################################
####################### Definitions of global functions ########################
################################################################################

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _exit_()
#
# Description:
#   Covers the default exit command.
#
# Usage:
#   _exit_ value
#
# Examples:
#   _exit_ 0
#

function _exit_() {

  local _FUNCTION_ID="_exit_"
  local _STATE="0"

  _STATUS="$1"

  # Remember that for it a trap is executed that intercepts
  # the exit command (at the end of this function).
  if [[ "$_STATUS" -eq 0 ]] ; then

    # Add tasks when exiting the code is equal 0.
    true

  else

    # Add tasks when exiting the code is non equal 0.
    false

  fi

  exit "$_STATUS"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _get_trap_SIG()
#
# Description:
#   Ensuring they always perform necessary cleanup operations,
#   even when something unexpected goes wrong. It can handle
#   all output signals.
#
# Usage:
#   trap _get_trap_SIG SIGNAL
#
# Examples:
#   trap _get_trap_SIG EXIT
#   trap "_get_trap_SIG SIGS" SIGHUP SIGTERM
#

function _get_trap_SIG() {

  local _FUNCTION_ID="_get_trap_SIG"
  local _STATE="${_STATUS:-}"

  local _SIG_type="$1"

  # Remember not to duplicate tasks in the _exit_() and _get_trap_SIG()
  # functions. Tasks for the _exit_() function only work within it
  # and refer to the exit mechanism. Tasks in the _get_trap_SIG() function
  # can refer to specific signal or all signals.

  if [ -z "$_STATE" ] ; then _STATE=254

  # Performs specific actions for the EXIT signal.
  elif [[ "$_SIG_type" == "EXIT" ]] ; then

    # Unset variables (e.g. global):
    #   - local _to_unset=("$IFS_ORIG" "$IFS_HACK" "$IFS" "$PATH")
    local _to_unset=("$PATH")

    # Running tasks before the end of the script.
    _after_init

    # shellcheck disable=SC2034
    for i in "${_to_unset[@]}" ; do unset i ; done

    # You can cover the code supplied from the _exit_() function
    # (in this case) or set a new one.
    _STATE="${_STATUS:-}"

  # Performs specific actions fot the other signals.
  # In this example, using the SIGS string, we mark several output signals
  # (see the second example in the description of the function).
  elif [[ "$_SIG_type" == "SIGS" ]] ; then

    # You can cover the code supplied from the function
    # or set a new one.
    _STATE="${_STATUS:-}"

  else

    # In this block the kill command was originally used,
    # however, it suspended the operation of dracnmap.
    # The lack of this command terminates the process
    # and does not cause the above problems.
    _STATE="255"

  fi

  _logger "exit" \
    "$_FUNCTION_ID > ${_SIG_type} (${_STATE})"

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _logger()
#
# Description:
#   Saving selected operation states to a log file
#   and allows you to terminate the script with 'stop' signal.
#
#   Four states of message type:
#     info - normal information
#     head - normal information (header)
#     warn - warning information
#     stop - interrupts script execution
#
# Usage:
#   _logger "type" "message"
#
# Examples:
#   _logger "info" "load config file properly"
#   _logger "stop" "not connected"
#

function _logger() {

  local _FUNCTION_ID="_logger"
  local _STATE="0"

  local _type="$1"
  local _to_log=""
  local _conv_type=""

  _to_log=$(shift ; echo "$@")
  _conv_type=$(echo "$_type" | tr '[:lower:]' '[:upper:]')

  # shellcheck disable=SC2154
  if [[ ! -d "$_log_directory" && ! -L "$_log_directory" ]] ; then
    mkdir -p "$_log_directory" ; fi

  # Normal debug mode (output the same as the contents of the log file).
  # shellcheck disable=SC2154
  if [[ "$stdout_mode" == "debug" ]] ; then

    printf "%s  %s:  [%s] %s\\n" \
           "$(date +"%d/%m/%y %X")" \
           "$_init_name" \
           "$_conv_type" \
           "$_to_log" \
           | tee -a "$_log_path"

  # The decision whether an INFO is to be only log to a file
  # or to a file and to standard output.
  else

    printf "%s  %s:  [%s] %s\\n" \
           "$(date +"%d/%m/%y %X")" \
           "$_init_name" \
           "$_conv_type" \
           "$_to_log" \
           >>"$_log_path"

  fi

  # By means of this construction, we can terminate the operation
  # of the script with the action of logging into the log file.
  # This do not have to remember to place the _exit_ <value> function
  # in 'exit' script points. If you prefer to have more control,
  # do not use the _logger function with the 'stop' parameter.
  if [[ "$_type" == "stop" ]] ; then _exit_ 255 ; fi

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _sprintf()
#
# Description:
#   Function designed to output to the screen in a clear format.
#
# Usage:
#   _sprintf "type" "message"
#
# Examples:
#   _sprintf "head" "correct certificate: $_ssl_cert_file"
#

function _sprintf() {

  local _FUNCTION_ID="_sprintf"
  local _STATE="0"

  local _s_type="$1"
  local _s_info="$2"

  # Determine the type of character and color for each type
  # of output information.
  if [[ "$_s_type" == "head" ]] ; then

    s_char="+"
    s_trgb="1;32"

  elif [[ "$_s_type" == "info" ]] ; then

    s_char="-"
    s_trgb="0;33"

  elif [[ "$_s_type" == "warn" ]] ; then

    s_char="!"
    s_trgb="1;37"

  elif [[ "$_s_type" == "stop" ]] ; then

    s_char="!"
    s_trgb="1;31"

  else

    s_char="-"
    s_trgb="0;37"

  fi

  # If you run the tool in verbose mode do not display output using _sprintf.
  if [[ "$stdout_mode" != "debug" ]] ; then

    if [[ "$_s_type" == "spin" ]] && [[ ! -z "$_s_info" ]] ; then

      # Process id of the previous running command.
      local _pid="$_s_info"

      local _sc='-\|/'

      # Verify that the process is still running.
      local _n="0"

      # shellcheck disable=SC2143
      while [[ $(ps a | awk '{print $1}' | grep -w "$_pid") ]] ; do

        _n=$(( ( _n + 1 ) % 4 ))
        printf "\\r[%s]" "${_sc:_n:1}"
        sleep 0.1

      done

      # If the end, we clean.
      printf "\\r"

    else

      # Normal execution if:
      # - spinner has not been called
      # - spinner completed

      # If verbose mode is enabled, display info message.
      # shellcheck disable=SC2154
      if [[ "$printf_mode" == "verbose" ]] && [[ "$_s_type" == "info" ]] ; then

        printf '[\e['${s_trgb}'m%s\e[m] %s\n' "$s_char" "$_s_info"

      else

        # If not, just display only the head, warn or stop string.
        # shellcheck disable=SC2154
        if [[ "$_s_type" == "head" ]] ; then

          if [[ "$s_color" == "true" ]] ; then

            c_trgb="1;39"

            printf '[\e['${s_trgb}'m%s\e[m] \e['${c_trgb}'m%s\e[m\n' "$s_char" "$_s_info"

          else

            printf '[\e['${s_trgb}'m%s\e[m] %s\n' "$s_char" "$_s_info"

          fi

        elif [[ "$_s_type" == "warn" ]] ; then

          if [[ "$s_color" == "true" ]] ; then

            c_trgb="1;43"

            printf '[\e['${s_trgb}'m%s\e[m] \e['${c_trgb}'m%s\e[m\n' "$s_char" "$_s_info"

          else

            printf '[\e['${s_trgb}'m%s\e[m] %s\n' "$s_char" "$_s_info"

          fi

        elif [[ "$_s_type" == "stop" ]] ; then

          if [[ "$s_color" == "true" ]] ; then

            c_trgb="1;41"

            printf '[\e['${s_trgb}'m%s\e[m] \e['${c_trgb}'m%s\e[m\n' "$s_char" "$_s_info"

          else

            printf '[\e['${s_trgb}'m%s\e[m] %s\n' "$s_char" "$_s_info"

          fi

        fi

      fi

    fi

  fi

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _init_cmd()
#
# Description:
#   Function executing given as a command parameter.
#
# Usage:
#   _init_cmd "parameter"
#
# Examples:
#   _init_cmd "eval cd /etc/init.d && ls"
#

function _init_cmd() {

  # shellcheck disable=SC2034
  local _FUNCTION_ID="_init_cmd"
  local _STATE=0

  local _cmd=("$@")
  local _full_command=()

  # shellcheck disable=SC2154
  for _svar in "${_variables_stack[@]}" ; do

    # shellcheck disable=SC2034
    _key_description=$(echo "$_svar" | awk -v FS="(;|;)" '{print $1}')
    # shellcheck disable=SC2034
    _key_values=$(echo "$_svar" | awk -v FS="(;|;)" '{print $2}')
    # shellcheck disable=SC2034
    _key_id=$(echo "$_svar" | awk -v FS="(;|;)" '{print $3}')
    # shellcheck disable=SC2034
    _key_var=$(echo "$_svar" | awk -v FS="(;|;)" '{print $4}')

    if [[ -z "$_key_var" ]] ; then

      _key_var=""

    fi

    if [[ "$_key_id" == "dest" ]] ; then

      # shellcheck disable=SC2034
      local _destination="$_key_var"

    elif [[ "$_key_id" == "params" ]] ; then

      # shellcheck disable=SC2034
      local _parameters="$_key_var"

    elif [[ "$_key_id" == "report" ]] ; then

      # shellcheck disable=SC2034
      local _output_report="$_key_var"

    elif [[ "$_key_id" == "tor" ]] ; then

      # shellcheck disable=SC2034
      local _tor_connection="$_key_var"

    elif [[ "$_key_id" == "terminal" ]] ; then

      # shellcheck disable=SC2034
      local _output_terminal="$_key_var"

    fi

  done

  # shellcheck disable=SC2154
  if [[ "$_script_args_state" == "true" ]] ; then

    _script_args_full=()
    _script_args_tmp_full=()
    _script_args_tmp_full_x=()

    #shellcheck disable=SC2034
    _IFS="$IFS"

    # shellcheck disable=SC2034,SC2154
    if [[ "${#_script_args_tmp[@]}" -ne 0 ]] ; then

      _count=0

      for i in "${_script_args_tmp[@]}" ; do

        if [[ -z "${_script_value_tmp[$_count]}" ]] ; then

          _x_value=""

        else

          _x_value="${_script_value_tmp[$_count]}"

        fi

        # shellcheck disable=SC2034
        printf "%s (\\e[1;33m%s\\e[m): " "${i}" "${_x_value}"
        read -r _val

        if [[ ! -z "$_val" ]] ; then

          _x_val=$(echo "${_val}" | tr " " "???")
          _script_args_tmp_full+=("${i}=\"${_x_val}\"")

        fi

        _count=$((_count +1))

      done

      _script_args_tmp=()

    else

      _script_args_full=()
      _script_args_tmp=()

    fi

    # shellcheck disable=SC2034
    IFS=" " read -r -a _script_args_tmp_full_x <<< "$(echo "${_script_args_tmp_full[*]}" | tr " " "," | tr "???" " ")"

    _script_args_full+=("${_script_args_tmp_full_x[@]}")

    IFS="$_IFS"

  else

    _script_args_full=()
    _script_args_tmp=()

  fi

  # shellcheck disable=SC2154
  if [[ "$_tor_connection" == "true" ]] ; then

    _full_command+=("proxychains")

  fi

  # shellcheck disable=SC2154
  if [[ "$_output_terminal" == "external" ]] ; then

    printf "\\n\\e[1;39m%s\\e[m:\\n" "terminal"
    printf "\\e[1;39m%c\\e[m \\e[1;30m%s\\e[m\\n" ">" "$_output_terminal"
    _full_command+=("${_xterm}")

  else

    printf "\\n\\e[1;39m%s\\e[m:\\n" "terminal"
    printf "\\e[1;39m%c\\e[m \\e[1;30m%s\\e[m\\n" ">" "internal"

  fi

  # shellcheck disable=SC2154
  if [[ "${input_array[0]}" == "nmap" ]] ; then

    _full_command+=("nmap" "${_cmd[@]}")

  else

    if [[ -z "$_parameters" ]] ; then

      if [[ "$_script_args_state" == "true" ]] ; then

        if [[ "${#_script_args_full[@]}" -ne 0 ]] ; then

          _full_command+=("nmap" "${_cmd[@]}" "--script-args ${_script_args_full[@]}" "${_destination}")

        else

          _full_command+=("nmap" "${_cmd[@]}" "${_destination}")

        fi

      else

        _full_command+=("nmap" "${_cmd[@]}" "${_destination}")

      fi

    else

      if [[ "$_script_args_state" == "true" ]] ; then

        if [[ "${#_script_args_full[@]}" -ne 0 ]] ; then

          _full_command+=("nmap" "${_cmd[@]}" "${_parameters}" "--script-args ${_script_args_full[@]}" "${_destination}")

        else

          _full_command+=("nmap" "${_cmd[@]}" "${_parameters}" "${_destination}")

        fi

      else

        _full_command+=("nmap" "${_cmd[@]}" "${_parameters}" "${_destination}")

      fi

    fi

  fi

  # shellcheck disable=SC2154
  if [[ ! -z "$_output_report" ]] ; then

    case $_output_report in

      standard)

        _output_opt="-oN"
        # shellcheck disable=SC2154
        _output_path="${_out}/scan.$(date +%s).nmap"
        _output_targ="${_output_opt} ${_output_path}" ;;

      xml)

        _output_opt="-oX"
        _output_path="${_out}/scan.$(date +%s).xml"
        _output_targ="${_output_opt} ${_output_path}" ;;

      grep)

        _output_opt="-oG"
        _output_path="${_out}/scan.$(date +%s).grep"
        _output_targ="${_output_opt} ${_output_path}" ;;

      *) true ;;

    esac

  fi

  if [[ ! -z "$_output_path" ]] ; then

    _full_command+=("$_output_targ")

  fi

  if [[ "$_output_terminal" == "external" ]] ; then

    printf "\\n\\e[1;39m%s\\e[m:\\n" "command"
    printf "\\e[1;39m%c\\e[m \\e[1;30m%s\\e[m\\n\\n\\n" ">" "${_full_command[*]}"

  else

    printf "\\n\\e[1;39m%s\\e[m:\\n" "command"
    printf "\\e[1;39m%c\\e[m \\e[1;30m%s\\e[m\\n\\n" ">" "${_full_command[*]}"

  fi

  # Execute command and exit.
  # For shellcheck code:
  #   - after closing the array with double quotes, some commands are not executed properly
  if [[ "$_output_terminal" == "external" ]] ; then

    # shellcheck disable=SC2068
    ${_full_command[@]} &

  else

    # shellcheck disable=SC2068
    ${_full_command[@]} ; _state="$?"

    if [[ "$_state" -eq 0 ]] ; then

      printf "\\n\\e[1;39m%s\\e[m: \\e[0;32m%s\\e[m\\n\\n" "Result" "pass"

    else

      printf "\\n\\e[1;39m%s\\e[m: \\e[0;31m%s\\e[m\\n\\n" "Result" "fail"

    fi

  fi

  # Flush temporary variables after scanning.
  for i in _destination \
           _parameters \
           _output_report \
           _tor_connection \
           _output_terminal \
           _output_opt \
           _output_path \
           _output_targ \
           _full_command ; do

    unset "$i"

  done

  _script_args_full=()
  _script_args_tmp_full=()
  _script_args_tmp_full_x=()

  _STATE=$_state

  return $_STATE

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _load()
#
# Description:
#   Responsible for loading the configuration file, $config variable
#   parameter is defined in the script call.
#
# Usage:
#   _load "type" "path_to_config_file"
#
# Examples:
#   _load "info" "$config"
#   _load "head" "/tmp/file.cfg"
#

function _load() {

  local _FUNCTION_ID="_load"
  local _STATE="0"

  local _type="$1"
  local _filename="$2"

  if [[ ! -z "$_filename" ]] && [[ -e "$_filename" ]] ; then

    # If we do not want to inform that the file is loaded,
    # the value is 'null', otherwise:
    if [[ "$_type" == "head" ]] ; then

      _sprintf "head" "load configuration"
      _sprintf "info" "file: '$_filename'"

    elif [[ "$_type" == "info" ]] ; then

      _sprintf "info" "load configuration: '$_filename'"

    fi

    # shellcheck disable=SC1090
    # If the file exists is loaded.
    . "$_filename" && \
    _logger "info" \
      "${_FUNCTION_ID}()" \
      "configuration file: '$_filename'"

  elif [ -z "$_filename" ] ; then

    _sprintf "stop" "incorrectly loaded '$_filename' file (incorrect filename)"

    _logger "stop" \
      "${_FUNCTION_ID}()" \
      "incorrectly loaded '$_filename' file (incorrect filename)"

  else

    _sprintf "stop" "incorrectly loaded '$_filename' file (does not exist?)"

    _logger "stop" \
      "${_FUNCTION_ID}()" \
      "incorrectly loaded '$_filename' file (does not exist?)"

  fi

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _help_()
#
# Description:
#   Help message. Should be consistent with the contents of the file README.md.
#
# Usage:
#   _help_
#
# Examples:
#   _help_
#

function _help_() {

  local _FUNCTION_ID="_help_"
  local _STATE="0"

  printf "%s" "
  Usage:
    $_init_name <option|long-option>

  Examples:
    $_init_name --help

  Options:
        --help                      show this message


  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it
  under certain conditions; for more details please see
  <http://www.gnu.org/licenses/>.

"

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _before_init()
#
# Description:
#   INFOs performed before calling the __main__ function, e.g.
#   attaching files, cleaning logs (if you need a function that will deal
#   with it, put it here).
#
# Usage:
#   _before_init
#
# Examples:
#   _before_init
#

function _before_init() {

  # shellcheck disable=SC2154
  local _FUNCTION_ID="_before_init"
  local _STATE="0"

  # shellcheck disable=SC2154
  cd "$_rel" || \
  _logger "stop" \
    "${_FUNCTION_ID}()" \
    "directory change error: '$_rel'"

  # shellcheck disable=SC2154
  : >"$_log_stdout"

  if [[ ! -d "$_out" ]] ; then

    mkdir -p "$_out"

  fi

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _after_init()
#
# Description:
#   INFOs performed after calling the __main__ function, e.g.
#   cleaning logs (if you need a function that will deal with it,
#   put it here).
#
# Usage:
#   _after_init
#
# Examples:
#   _after_init
#

function _after_init() {

  local _FUNCTION_ID="_after_init"
  local _STATE="0"

  cd "$_rel" || \
  _logger "stop" \
    "${_FUNCTION_ID}()" \
    "directory change error: '$_rel'"

  return "$_STATE"

}
