#!/bin/bash

## Copyright (C) 2018 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## Copyright (C) 2018 Iry Koon <iry@riseup.net>
## See the file COPYING for copying conditions.

#### meta start
#### project Whonix
#### category tor and usability
#### gateway_only yes
#### description
## Tor configuration check command line utility.
#### meta end

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

SCRIPTNAME="$(basename -- "${BASH_SOURCE[0]}")"

## CODE DUPLICATION: Adapted from systemcheck's preparation.bsh.
if has qubesdb-read; then
   ## Qubes OS detected.
   qubes_name_of_vm="$(qubesdb-read /name)" || true
   qubes_name_of_vm="$(sanitize-string -- 255 "$qubes_name_of_vm")"

   ## '/qubes-vm-type' is either 'AppVM', 'ProxyVM', 'NetVM' or 'TemplateVM'
   qubes_vm_type="$(qubesdb-read /qubes-vm-type)" || true
   qubes_vm_type="$(sanitize-string -- 255 "$qubes_vm_type")"

   qubes_starter_first_part="ServiceVM"
   if [ "$qubes_type_of_vm" = "AppVM" ]; then
      qubes_starter_first_part="Domain"
   fi
   if [ "$qubes_type_of_vm" = "TemplateVM" ]; then
      qubes_starter_first_part="Template"
   fi

   ## '/qubes-vm-persistence' outputs:
   ## - 'full' (TemplateVM or Standalone),
   ## - 'rw-only' (TemplateBasedVM), or
   ## - 'none' (DispVM).
   qubes_vm_persistence="$(qubesdb-read /qubes-vm-persistence)" || true

   if test -f /run/qubes/this-is-templatevm ; then
      start_menu_instructions_system_first_part="dom0 - Start Menu - Template: <code>$qubes_name_of_vm</code> -"
   else
      if [ "$qubes_vm_persistence" = "full" ]; then
         start_menu_instructions_system_first_part="dom0 - Start Menu - Domain: <code>$qubes_name_of_vm</code> -"
      elif [ "$qubes_vm_persistence" = "rw-only" ]; then
         start_menu_instructions_system_first_part="dom0 - Start Menu - $qubes_starter_first_part: <code>$qubes_name_of_vm</code> -"
      elif [ "$qubes_vm_persistence" = "none" ]; then
         start_menu_instructions_system_first_part="dom0 - Start Menu - $qubes_starter_first_part: <code>$qubes_name_of_vm</code> -"
      else
         start_menu_instructions_system_first_part="dom0 - Start Menu - $qubes_starter_first_part: <code>$qubes_name_of_vm</code> -"
      fi
   fi
else
   ## Non-Qubes OS detected.
   start_menu_instructions_system_first_part="Start Menu - System -"
fi

## Read by the two '=~' tests that extract --defaults-torrc and -f from it, but
## the get_tor_start_command() that assigned it -- and its call site -- are both
## COMMENTED OUT further down. So it has always been empty here, both regexes
## have always failed, and the torrc paths they would have set fall back to
## their defaults. Kept empty rather than reviving the commented-out function,
## which would change which torrc files anon-verify reports on.
tor_start_command=""

shopt -s nullglob

## parse_cmd_options is based on code in repository-dist by Patrick
parse_cmd_options() {
   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   ## Initialize all the option variables.
   ## This ensures we are not contaminated by variables from the environment.
   VERBOSE=
   VERY_VERBOSE=

   local HELP_MSG="See:
   man ${SCRIPTNAME}"

   while :
   do
      case "${1:-}" in
         -h | --help | -\?)
            printf '%s\n' "${HELP_MSG}"
            exit 0
         ;;
            -v | --verbose )
            printf '%s\n' "${SCRIPTNAME} verbose output..."
            VERBOSE="1"
            shift
         ;;
            -vv | --very-verbose)
            printf '%s\n' "${SCRIPTNAME} very verbose output..."
            VERBOSE="1"
            VERY_VERBOSE="1"
            shift
         ;;
            --)
            shift
            break
         ;;
            -*)
            printf '%s\n' "${SCRIPTNAME} unknown option: $1" >&2
            exit 1
         ;;
            *)
            break
         ;;
      esac
   done

   ## If there are input files (for example) that follow the options, they
   ## will remain in the "$@" positional parameters.
}

## get_tor_start_command() will set the $tor_start_command to the command
## used in Whonix to start Tor. If it did not successfully extract the
## command, it will exit with exit code 1. If Whonix get rid of
## systemd for Tor control in the future, we simply change the content
## in the tor_start_command.
##
## This is incompatible with privleap, thus out-commented to allow
## Whonix-Gateway to operate sudoless.
#function get_tor_start_command() {
#   ## Tor use systemd to start
#   tor_start_command=$(grep -i "ExecStart=" /lib/systemd/system/tor@default.service | sed s/ExecStart=//g)
#
#   if [ -z "$tor_start_command" ]; then
#      echo "[error] Failed to extra commands used to start Tor." >&2
#      return 1
#   fi
#
#   ## Tor is run as root, we need to simulate this, too.
#   tor_start_command="sudo $tor_start_command"
#}

## generate_report will generate a user-friendly report based on
## $tor_verify_config_output and $tor_verify_config_exit_code
function generate_report() {
   ## generate concise report
   printf '%s\n' "/===================================================================\\"
   printf '%s\n' "|                      Report Summary                               |"
   printf '%s\n' "\\===================================================================/"
   if [ "${tor_verify_config_exit_code}" = "0" ]; then
      printf '%s\n' "No error detected in your Tor configuration."
      printf '%s\n' "Tor verify exit code: ${tor_verify_config_exit_code}"
      tor_verify_config_output_concise="Tor configuration was valid."
   else
      printf '%s\n' "Your Tor config files contain at least one error."
      printf '%s\n' "Tor verify exit code: ${tor_verify_config_exit_code}"
      ## concise report which only contains complains with [warn] or [err] tags
      printf '%s\n' "/===================================================================\\"
      printf '%s\n' "|                    Tor Concise Report                             |"
      printf '%s\n' "\\===================================================================/"
      printf '%s\n' "Below warns and errors must be fixed before you can use Tor:"
      grep -E -w -h -i --color '\[warn\]|\[err\]' <<< "${tor_verify_config_output}" || true
      tor_verify_config_output_concise="$(grep -E -w -h -i --color '\[warn\]|\[err\]' <<< "${tor_verify_config_output}" 2>&1)" || true
   fi

   printf '%s\n' "/===================================================================\\"
   printf '%s\n' "|                      Tor Full Report                              |"
   printf '%s\n' "\\===================================================================/"
   printf '%s\n' "${tor_verify_config_output}"
   ## TODO: $anon_verify_report_html is used by systemcheck
   ## we need to find a way to merge it with $anon_verify_report
   tor_verify_config_output_html="$(/usr/libexec/msgcollector/br_add.py "${tor_verify_config_output}")"
   ## Assigned for systemcheck to consume; never read in this file, which is
   ## all shellcheck can see.
   # shellcheck disable=SC2034
   anon_verify_report_html="<p>Tor Config Check Result:
<br></br><b>Your Tor config files contain at least one error.</b></p>

<p>(Tor exit code: ${tor_verify_config_exit_code})</p>

<p><u><b>Tor concise reports (below warns and errors must be fixed before you can use Tor)</b></u>:
<br></br><code>${tor_verify_config_output_concise}</code></p>

<p><u>Tor full reports</u>:
<br></br><code>${tor_verify_config_output_html}</code></p>

Try to look at this report again by running:

${start_menu_instructions_system_first_part} Terminal

<blockquote>anon-verify</blockquote>

<p>To try to fix this, please open your Tor config file.

<blockquote>    ${start_menu_instructions_system_first_part} Torrc</blockquote>
    <br></br>or in Terminal: <code>sudo nano /usr/local/etc/torrc.d/50_user.conf</code></p>

<p>Please restart Tor after fixing this error.

<blockquote>    ${start_menu_instructions_system_first_part} Restart Tor</blockquote>
    <br></br>or in Terminal: <code>sudo systemctl restart tor@default</code></p>"

   ## torrc-parser will grep the invalid option for user and specify
   ## which file is corrupted for better fixing suggestion
   bash -n /usr/libexec/anon-gw-anonymizer-config/torrc-parser
   source /usr/libexec/anon-gw-anonymizer-config/torrc-parser

   ## extract default_torrc file and -f torrc file
   default_torrc=""
   f_torrc=""
   if [[ "${tor_start_command}" =~ (--defaults-torrc )([^ ]*) ]]; then
      default_torrc="${BASH_REMATCH[2]}"
   fi

   if [[ "${tor_start_command}" =~ (-f )([^ ]*) ]]; then
      f_torrc="${BASH_REMATCH[2]}"
   fi

   ## call parser() to parse torrc files, arguments order matters
   parser "${default_torrc}" "${f_torrc}"

   printf '%s\n' "/===================================================================\\"
   printf '%s\n' "|                 Used Tor Configuration Files                      |"
   printf '%s\n' "\\===================================================================/"
   used_torrc_files

   extraneous_torrc_files

   ## TODO: More suggestions to different errors can be added in the
   ## future

   ## extract the unknown option complained by Tor
   if [[ "${tor_verify_config_output}" =~ (Unknown option \')([^\']*) ]]; then
      printf '%s\n' "/===================================================================\\"
      printf '%s\n' "|                 Detailed Suggested Solution                       |"
      printf '%s\n' "\\===================================================================/"
      unknown_option_specifier "${BASH_REMATCH[2]}"
   fi

   ## parsing_order_verbose creates a very long output which is not
   ## user-friendly. Therefore, we do not enable it by default and
   ## only use it with anon-verify -v and anon-verify -vv
   if [ "${VERBOSE}" = "1" ]; then
      printf '%s\n' "/===================================================================\\"
      printf '%s\n' "|                Verbose Tor Configuration Parsing                  |"
      printf '%s\n' "\\===================================================================/"
      parsing_order_verbose
   fi

   printf '%s\n' "====================================================================="
}

parse_cmd_options "$@"

#get_tor_start_command

## since we should use exactly the same arguments to examine if Tor
## can start, we simply append the --verify-config arguments behind it
## (and run as root)
#tor_verify="$tor_start_command --verify-config"

## execute the verification
tor_verify_config_exit_code="0"
tor_verify_config_output="$(leaprun anonymizer-tor-verify-config)" || { tor_verify_config_exit_code="$?" ; true; };

## generate the report
generate_report
