#!/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

  # Normal cursor.
  tput cnorm

  # Running tasks before the end of the script.
  _after_init

  _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" && ! -L "$_log" ]] ; then
    mkdir -p "$_log" ; 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;1;33"

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

    s_char="•"
    s_trgb="1;0;32"

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

    s_char="•"
    s_trgb="1;0;32"

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

    s_char="!"
    s_trgb="0;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" ]] && [[ -n "$_s_info" ]] ; then

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

      # local _sc='░▒▓█'
      local _sc='-\|/'

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

      # Invisible cursor.
      tput civis

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

        _n=$(( ( _n + 1 ) % 4 ))

        # shellcheck disable=SC2086
        printf '\r » \e['${trgb_3811}'m%s\e[m' "${_sc:_n:1}" ; tput sgr0

        sleep 0.1

        _tmp_id=$(ps a | awk '{print $1}' | grep -w "$_pid")

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

          printf "\\r »  " ; tput sgr0

        fi

      done

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

    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

            trgb_3811="1;39"

            printf '[\e['${s_trgb}'m%s\e[m] \e['${trgb_3811}'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

            trgb_3811="1;43"

            printf '[\e['${s_trgb}'m%s\e[m] \e['${trgb_3811}'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

            trgb_3811="1;41"

            printf '[\e['${s_trgb}'m%s\e[m] \e['${trgb_3811}'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

  # shellcheck disable=SC2034,SC2154
  if [[ "$mixed_content_state" -eq 1 ]] && [[ -s "$_scan_output" ]] ; then

    _fd_empty="1"

  fi

  tput sgr0

  # shellcheck disable=SC2154
  sed 's/^/         › /' "$_scan_output" | \
  sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | \
  sed 's/\r//' | grep "› ." | fmt -w "$MAX_WIDTH" -s

  # shellcheck disable=SC2154
  : >"$_scan_output"

  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() {

  local _FUNCTION_ID="_init_cmd"
  local _STATE="0"

  local _cmd="$1"

  # If the scanning command returns an error, we try again.
  # shellcheck disable=SC2086,SC2154
  for i in $(seq 1 $_scounter) ; do

    _logger "info" \
      "${_FUNCTION_ID}()" \
      "init: $_cmd (counter: $i)"

    # Execute command and exit save to file.
    # shellcheck disable=SC2154
    $_cmd >"$_scan_output" 2>&1 &

    # We keep pid of the last command.
    _pid="$!"

    # When the '(command) &' command is performed.
    _sprintf "spin" "$_pid"

    # Very important line:
    # We define the state of the output job from the background.
    wait "$_pid" &>/dev/null && _state="0" || _state="1"

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

      _logger "info" \
        "${_FUNCTION_ID}()" \
        "result: pass"

      break

    else

      _logger "info" \
        "${_FUNCTION_ID}()" \
        "result: fail"

    fi

    _STATE="$_state"

  done

  return "$_STATE"

}

# ``````````````````````````````````````````````````````````````````````````````
# Function name: _init_function()
#
# Description:
#   It deals with launching functions:
#     - creates a simple environment for the functions you perform
#     - operates on message to display and command to execute
#
# Usage:
#   _init_function function_name params
#
# Examples:
#   _init_function "CheckConn 172.20.20.50 22"
#

function _init_function() {

  local _FUNCTION_ID="_init_function"
  local _STATE="0"

  _kstate=0

  local _args=("$@")

  _logger "info" \
    "${_FUNCTION_ID}()" \
    "init function: '${_args[*]}'"

  # In these two variables we store the message to display
  # and the command to execute.
  export _msg_args=()
  export _cmd_args=()

  eval "${_args[@]}" ; if [ ! $? ] ; then _exit_ 255 ; fi

  _num="${#_msg_args[@]}"
  for (( _xa="0" ; _xa<=((_num - 1)) ; _xa++ )) ; do

    _msg="${_msg_args[$_xa]}"
    _full_command="${_cmd_args[$_xa]}"

    # _sprintf "info" "$_msg"
    _msg_x=$(echo "$_msg" | cut -d ":" -f1)
    _msg_y=$(echo "$_msg" | cut -d ":" -f2-)

    # shellcheck disable=SC2086,SC2154
    printf '    \e['${trgb_3811}'m%s\e[m \e['${trgb_3722}'m(%s)\e[m\n\n' \
           "$_msg_x" "$_msg_y"

    _logger "info" \
      "${_FUNCTION_ID}()" \
      "$_msg"

    # shellcheck disable=SC2154
    if [[ "$_scan_type" == "active" ]] ; then

      # shellcheck disable=SC2086,SC2154
      printf '       - scan-type: \e['${trgb_3100}'m%s\e[m\n\n' "$_scan_type"

    elif [[ "$_scan_type" == "passive" ]] ; then

      # shellcheck disable=SC2086,SC2154
      printf '       - scan-type: \e['${trgb_3200}'m%s\e[m\n\n' "$_scan_type"

    else

      # shellcheck disable=SC2086,SC2154
      printf '       - scan-type: \e['${trgb_3300}'m%s\e[m\n\n' "$_scan_type"

    fi

    # shellcheck disable=SC2154
    printf '     ▸ %s\n\n' "$_destination_host"

    _init_cmd "$_full_command"

    # shellcheck disable=SC2034
    if [ ! "$?" ] ; then _kstate=255 ; fi

  done

  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 [[ -n "$_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

  # shellcheck disable=SC2154
  printf "%s" "
    $_init_name $_version

  Usage:

    $_init_name <option|long-option> [value]

  Examples:

    $_init_name -u https://example.com -s -h -b
    $_init_name -u https://example.com --all-scans

  Options:

        --help                                show this message
        --version                             show script version
        --examples                            show script examples

    Standard:

        -u|--url <value>                      set target url with http/https protocol
        -s|--ssl                              show basic ssl server/connection parameters
        -h|--headers                          show response headers
        -b|--body                             show response body
        -M|--req-method <value>               set request method (default: GET)
        -H|--req-header <value>               set request header(s)
        -p|--proxy <value>                    set proxy server (not for external tools)
        -r|--resolve <value>                  resolve the host+port to this address
        -i|--iface <value>                    set network interface (or address)
        -t|--timers                           turn on curl timers
        -a|--all-scans                        use all external security tools

    Security tools:

        --testssl                             test ssl protocols and ciphers (testssl.sh)
        --observatory                         analyze website headers (mozilla observatory)
        --ssllabs                             deep analysis of the ssl web server (ssllabs)
        --mixed-content                       scan website for non-secure resources (mixed-content-scan)
        --nse                                 scan website and domain with nse library (nmap)
        --waf                                 detect and bypass web application firewalls (wafw00f)
        --dns                                 enumerate subdomains (subfinder) and perform zone transfer
        --http2                               test HTTP/2 (nghttp2)

    Extended:

        --conn-debug                          debug curl connection
        --ssl-bin <path>                      set path to the openssl bin
        --ssl-debug                           debug ssl connection
        --cache-bypass <value>                try (proxy) cache bypass
        --user-agent <value>                  set 'User-Agent' header
        --referer <value>                     set 'Referer' header
        --auth <value>                        set authentication method
        --httpv <value>                       set http version
        --tlsv <value>                        set tls version
        --ciph <value>                        set of cryptographic algorithm
        --max-redirects <num>                 set max redirects (default: 10)
        --timeout <num>                       set max timeout (default: 15)
        --hide-src-ip                         hide source ip from output


  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: _examples_()
#
# Description:
#   Show htrace.sh examples.
#
# Usage:
#   _examples_
#
# Examples:
#   _examples_
#

function _examples_() {

  local _FUNCTION_ID="_examples_"
  local _STATE=0

  # shellcheck disable=SC2154
  printf "%s" "
  Original: https://github.com/trimstray/htrace.sh/wiki/One-liners

  Look also at {PATH_TO_HTRACE.SH}/config.

  Check redirects
    › htrace.sh -u https://example.com

  Show basic SSL configuration and response headers
    › htrace.sh -u https://example.com -s -h

  Show basic SSL configuration and enable SSL debug mode
    › htrace.sh -u https://example.com -s --ssl-debug

  Show response body
    › htrace.sh -u https://example.com -b

  Set POST request method
    › htrace.sh -u https://example.com -h -M \"POST:'{\"username\":\"xyz\",\"password\":\"xyz\"}'\"
                                               ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                                /                       \\
                                               /                         \\
                                          POST method              POST method value

  Pin a single request to a specified IP address
    › htrace.sh -u https://example.com -h --resolve \"xxx.xxx.xxx.xxx\"
    › htrace.sh -u https://example.com -h --resolve \"xxx.xxx.xxx.xxx:443\"

  Scan SSL domain configuration
    › htrace.sh -u https://example.com --ssllabs --testssl

  Show and analyse HTTP response headers
    › htrace.sh -u https://example.com -h --observatory

  Send request headers
   # One request header
    › htrace.sh -u https://example.com -h -H 'Host: example.com'
   # Multiple request headers
    › htrace.sh -u https://example.com -h -H 'Host: example.com ; x-debug-mode: true'

  Enumerate subdomains and perform zone transfer
    › htrace.sh -u https://example.com --dns

  Set credentials to access a URL
   # Standard authentication
    › htrace.sh -u https://example.com -h --auth \"basic:foo:bar\"
   # Proxy authentication
    › htrace.sh -u https://example.com -h --auth \"proxy:foo:bar\"
   # Cookie authentication
    › htrace.sh -u https://example.com -h --auth \"cookie:/tmp/example.com.cookie\"

  Set specific HTTP version
    › htrace.sh -u https://example.com --httpv 1.1

  Set specific TLS version and Ciphersuite
    › htrace.sh -u https://example.com -s --tlsv 1.1 --ciph \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA\"

  Set path to the OpenSSL binary
    › htrace.sh -u https://example.com -s --ssl-bin /usr/bin/openssl-v1.1.1b

"

  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() {

  local _FUNCTION_ID="_before_init"
  local _STATE=0

  mkdir -p "$_log"

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

  # shellcheck disable=SC2154
  : >"$_log_stdout"

  # shellcheck disable=SC2066,SC2154
  for i in "$_tmp" ; do

    if [[ -d "$i" ]] ; then

      rm -fr "$i" && \
      mkdir -p "$i"

    else

      mkdir -p "$i"

    fi

  done

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

    export HTRACE_COLORS="on"

  elif [[ "$HTRACE_COLORS" != "off" ]] ; then

    export HTRACE_COLORS="on"

  fi

  # Path to ssl certificates (CA).
  if [[ -z "$HTRACE_SSL_CERT_PATH" ]] ; then

    # Tasks for specific system version.
    if [[ "$OSTYPE" == "linux-gnu" ]] ; then

      command -v yum > /dev/null 2>&1      && _DIST_VERSION="rhel"
      command -v apt-get > /dev/null 2>&1  && _DIST_VERSION="debian"

    elif [[ "$OSTYPE" == *"bsd"* ]] ; then

      command -v pkg > /dev/null 2>&1      && _DIST_VERSION="bsd"

    fi

    if [[ "$_DIST_VERSION" == "rhel" ]] ; then

      # shellcheck disable=SC2034
      export _ssl_ca_path="/etc/pki/tls/certs/ca-bundle.crt"
      export SSL_CERT_FILE="/etc/pki/tls/certs/ca-bundle.crt"

    elif [[ "$_DIST_VERSION" == "debian" ]] ; then

      # shellcheck disable=SC2034
      export _ssl_ca_path="/etc/ssl/certs/ca-certificates.crt"
      export SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"

    elif [[ "$_DIST_VERSION" == "bsd" ]] ; then

      # shellcheck disable=SC2034
      export _ssl_ca_path="/usr/local/share/certs"
      export SSL_CERT_DIR="/usr/local/share/certs"

    else

      # shellcheck disable=SC2034
      export _ssl_ca_path="/etc/ssl/certs"
      export SSL_CERT_DIR="/etc/ssl/certs"

    fi

  else

    _ca_fd=$(basename "${HTRACE_SSL_CERT_PATH}")
    _ca_dir=$(dirname "${HTRACE_SSL_CERT_PATH}")

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

      # shellcheck disable=SC2034
      export _ssl_ca_path="$_ca_dir"
      export SSL_CERT_DIR="$_ca_dir"

    else

      export SSL_CERT_FILE="$_ca_fd"

    fi


  fi

  _max_width_current=$(tput cols)

  if [[ -z "$MAX_WIDTH" ]] || \
     [[ "$MAX_WIDTH" == "auto" ]] ; then

    if [[ "$_max_width_current" -gt 150 ]] ; then

      export MAX_WIDTH="150"

    else

      export MAX_WIDTH=$((_max_width_current - 5))

    fi

  fi

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

    export HIDE_SRC_IP="off"

  elif [[ "$HIDE_SRC_IP" != "on" ]] ; then

    export HIDE_SRC_IP="off"

  fi

  # Invisible cursor.
  # tput civis

  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 "$_init_directory" || \
  _logger "stop" \
    "${_FUNCTION_ID}()" \
    "directory change error: '$_init_directory'"

  # shellcheck disable=SC2066,SC2154
  if [[ -d "$_log" ]] ; then

    : >"$_scan_output"

  fi

  # shellcheck disable=SC2066,SC2154
  for i in "$_tmp" ; do

    if [[ -d "$i" ]] ; then

      rm -fr "$i"

    fi

  done

  return $_STATE

}
