#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## Exit codes of this script get interpreted by sdwdate.
## exit 0
## exit 1: wait, retry and error icon
## exit 2: wait, retry and busy icon

set -o pipefail
set -e errtrace

output_cmd() {
   msg="$@"
   printf '%s\n' "__ $msg"
}

error_handler() {
   exit_code="$?"
   printf '%s\n' "\
BASH_COMMAND: $BASH_COMMAND
exit_code: $exit_code"
}

trap "error_handler" ERR

exit_handler() {
   if [ "$exit_code" = "" ]; then
      "$0: No exit code set yet. Setting to 1."
   fi
   if [ "$exit_code" = "0" ]; then
      meaning="success"
   elif [ "$exit_code" = "1" ]; then
      meaning="wait, show error icon and retry."
   elif [ "$exit_code" = "2" ]; then
      meaning="wait, show busy icon and retry."
   else
      meaning="Unknown."
   fi
   output_cmd "### END: ### Exiting with exit_code '$exit_code' indicating '$meaning'."
   exit "$exit_code"
}

trap "exit_handler" EXIT

source /usr/libexec/helper-scripts/tor_enabled_check

## Not actually used anymore. Might cause:
## Feb 14 02:36:29 host sdwdate[909]: 2025-02-14 02:36:29 - sdwdate - INFO - /usr/libexec/helper-scripts/onion-time-pre-script: WARNING: privleapd is not running. Folder '/proc/681' does not exist. Cannot use privleap.
#source /usr/libexec/helper-scripts/pkg_manager_running_check

## provides check_tor_bootstrap_status
source /usr/libexec/helper-scripts/tor_bootstrap_check.bsh

detect_vm_type() {
   if [ -f "/usr/share/anon-gw-base-files/gateway" ]; then
      VM="Gateway"
   elif [ -f "/usr/share/anon-ws-base-files/workstation" ]; then
      VM="Workstation"
   else
      VM="Other"
   fi
}

anondate_folder() {
   true "user: $USER"

   if [ "$USER" = "sdwdate" ]; then
      anondate_state_folder=/run/sdwdate
   elif [ "$(id -u)" = "0" ]; then
      anondate_state_folder=/run/anondate
   else
      output_cmd "ERROR: running under user '$USER' is not yet implemented. Run:
sudo -u sdwdate $0"

      anondate_state_folder=/run/anondate

      exit_code=1
      exit "$exit_code"
   fi

   true "anondate_state_folder: $anondate_state_folder"
}

onion_time_script_status() {
   if [ -f "$anondate_state_folder/onion-time-script-after-boot" ]; then
      output_cmd "Status: Subsequent run after boot."
      onion_time_script_status_boot=true
   else
      output_cmd "Status: First run after boot. (Creating file '$anondate_state_folder/onion-time-script-after-boot'.)"
      onion_time_script_status_boot=false
      touch "$anondate_state_folder/onion-time-script-after-boot"
   fi
}

timesanitycheck_static() {
   output_cmd "Static Time Sanity Check: $(timesanitycheck)" || true
}

exit_if_tor_not_yet_enabled() {
   ## Sets: TOR_ENABLED
   check_tor_enabled_do

   if [ "$TOR_ENABLED" = "1" ]; then
      true "Tor enabled check: Tor already enabled, ok."
   else
      if [ -f /usr/share/whonix/marker ]; then
         output_cmd "Tor enabled check: Tor is disabled. Please enable Tor using Anon Connection Wizard or setup-dist. \
Start Menu -> System -> Anon Connection Wizard or in Terminal: sudo setup-dist"
      else
         output_cmd "Tor enabled check: Tor is disabled. Please enable Tor in the Tor config."
      fi

      exit_code=1
      exit "$exit_code"
   fi
}

tor_dormant_status() {
   ## sets: tor_dormant_status
   check_tor_dormant_status ## tor_bootstrap_check.bsh

   if [ "$tor_dormant_status" = "0" ]; then
      output_cmd "Tor Dormant Result: No, not dormant, good. (tor_dormant_type: '$tor_dormant_type')"
   elif [ "$tor_dormant_status" = "1" ]; then
      output_cmd "Tor Dormant Result: Yes, dormant. (tor_dormant_type: '$tor_dormant_type') Sending 'SIGNAL active' and 'SIGNAL newnym' to wake up Tor..."
      ## https://stackoverflow.com/questions/37135213/check-tor-connection-is-established-before-running-scrapy
      run_signal_newnym
   else
      output_cmd "Tor Dormant Result: Unknown."
   fi
}

check_tor_circuit_established_yet() {
   ## sets: check_bootstrap_helper_script
   ## sets: lastpid
   ## sets: tor_bootstrap_percent
   ## sets: tor_bootstrap_status
   check_tor_circuit_established ## tor_bootstrap_check.bsh

   ## $tor_circuit_established_check_exit_code on timeout returns:
   ## - 124 if sigterm was sufficient
   ## - 137 if needed to use kill.

   for invalid_exit_code in "124" "137" "254" ; do
      if [ "$tor_circuit_established_check_exit_code" = "$invalid_exit_code" ]; then
         output_cmd "Tor Bootstrap Result:
check_bootstrap_helper_script: $check_bootstrap_helper_script
tor_bootstrap_timeout_type: $tor_bootstrap_timeout_type
tor_circuit_established_check_exit_code: $tor_circuit_established_check_exit_code"
         exit_code=1
         exit "$exit_code"
      fi
   done

   if [ "$tor_circuit_established_check_exit_code" = "255" ]; then
      if [ "$VM" = "Gateway" ]; then
         output_cmd "Tor Bootstrap Result: \
Tor's Control Port could not be reached."
      elif [ "$VM" = "Workstation" ]; then
         if [ -f /usr/share/whonix/marker ]; then
            output_cmd "Tor Bootstrap Result: \
Tor's Control Port could not be reached.\
Did you start Gateway beforehand? \
Please run systemcheck on Gateway."
         else
            output_cmd "Tor Bootstrap Result: \
Tor's Control Port could not be reached.\
Did you start Gateway beforehand?"
         fi
      else
         if [ -f /usr/share/whonix/marker ]; then
            output_cmd "Tor Bootstrap Result: \
Tor's Control Port could not be reached.\
Did you start Gateway beforehand? \
Please run systemcheck on Gateway.
$FUNCNAME: This is neither a gateway nor a workstation. Please report this bug!"
         else
            output_cmd "Tor Bootstrap Result: \
Tor's Control Port could not be reached."
         fi
      fi

      exit_code=1
      exit "$exit_code"
   fi
}

tor_bootstrap_check() {
   if [ "$VM" = "Workstation" ]; then
      true "$FUNCNAME: INFO: skipping check_tor_bootstrap_status because workstation has no access to it."
   else
      check_tor_bootstrap_status
   fi

   ## TODO:
   ## When using an old Tor consensus which might be the case when no Tor
   ## circuit has been established yet, there is no point to check Tor
   ## consensus time as it might be outdated leading to concluding that the
   ## clock is fast.

   ## Could be 100% bootstrap process but still no Tor circuit established yet.

   if [ "$VM" = "Workstation" ]; then
      output_cmd "Tor circuit: $tor_circuit_established_word"
   else
      output_cmd "Tor reports: $tor_bootstrap_status"
      output_cmd "Tor circuit: $tor_circuit_established_word"

      if printf '%s\n' "$tor_bootstrap_status" | grep -- "REASON=TIMEOUT" &>/dev/null; then
         output_cmd "Tor reports: REASON=TIMEOUT"
      fi
   fi
}

tor_consensus_time_sanity_check() {
   ## If the static timestamp based time sanity check failed, there is no
   ## need to run the Tor consensus based time sanity check. Avoiding
   ## duplicate output.
   if [ "$timesanitycheck_static_timestamp_based_failed" = "true" ]; then
      return 0
   fi

   ## sets: tor_consensus_valid_after_exit_code
   ## sets: tor_consensus_valid_after_output
   ## sets: tor_consensus_valid_after_unixtime
   tor_consensus_valid-after

   ## sets: tor_consensus_valid_until_exit_code
   ## sets: tor_consensus_valid_until_output
   ## sets: tor_consensus_valid_until_unixtime
   tor_consensus_valid-until

   current_unixtime="$(date --utc +"%s")"

   if [ ! "$tor_consensus_valid_after_unixtime" = "" ] && [ ! "$tor_consensus_valid_until_unixtime" = "" ]; then
      clock_tor_consensus_check_result="ok"
      if [ "$current_unixtime" -ge "$tor_consensus_valid_after_unixtime" ]; then
         true
      else
         clock_tor_consensus_check_result="slow"
         clock_tor_consensus_check_msg="The clock might be too slow. Clock is slower than consensus/valid-after $tor_consensus_valid_after_output."
      fi
      if [ "$current_unixtime" -ge "$tor_consensus_valid_until_unixtime" ]; then
         clock_tor_consensus_check_result="fast"
         if [ "$onion_time_script_status_boot" = "true" ]; then
            clock_tor_consensus_check_msg="The clock might be too fast. Clock is faster than consensus/valid-until $tor_consensus_valid_until_output. \
extra guess: Tor consensus might be outdated and download might still be in progress since this is the first run after boot."
         else
            clock_tor_consensus_check_msg="The clock might be too fast. Clock is faster than consensus/valid-until $tor_consensus_valid_until_output. extra guess: None."
         fi
      else
         true
      fi
   elif [ "$tor_consensus_valid_after_exit_code" = "277" ] && [ "$tor_consensus_valid_until_exit_code" = "277" ]; then
      clock_tor_consensus_check_result="noneyet"
      clock_tor_consensus_check_msg="Might not have downloaded a Tor consensus yet."
   else
      clock_tor_consensus_check_result="error"
      clock_tor_consensus_check_msg="Consensus time sanity check failed."
   fi

   if [ "$clock_tor_consensus_check_result" = "ok" ]; then
      clock_tor_consensus_check_result="ok"
      clock_tor_consensus_check_msg="Clock within consensus parameters consensus/valid-after '$tor_consensus_valid_after_output' and consensus/valid-until '$tor_consensus_valid_until_output'."
   fi

   if [ "$clock_tor_consensus_check_result" = "ok" ]; then
      output_cmd "Tor Consensus Time Sanity Check: $clock_tor_consensus_check_msg"
   else
      output_cmd "Tor Consensus Time Sanity Check: $clock_tor_consensus_check_msg"
   fi

   ## TODO
   ## Would have to parse tor_bootstrap_status.
   ## In case Tor cannot fetch Tor consensus $tor_consensus_valid_after_exit_code /
   ## $tor_consensus_valid_until_exit_code may be zero but $tor_consensus_valid_until_output
   ## may be empty.
   #if [ ! "$clock_tor_consensus_check_result" = "ok" ]; then
   #   if [ "$VM" = "Gateway" ]; then
   #      exit_code=1
   #      exit "$exit_code"
   #   fi
   #fi
}

connectivity_test() {
    ## TODO: optional connectivity test
    #/usr/bin/url_to_unixtime 127.0.0.1 9050 http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion 80 true 2>&1
    true "connectivity_test: Not yet implemented."
}

exit_success_if_tor_circuit_already_established() {
   if [ "$tor_circuit_established" = "1" ]; then
      output_cmd "Conclusion: Tor already reports circuit established."
      exit_code=0
      exit "$exit_code"
   fi

   ## https://stackoverflow.com/questions/37135213/check-tor-connection-is-established-before-running-scrapy

   if [ "$tor_bootstrap_percent" = "100" ]; then
      output_cmd "Conclusion:  No Tor circuit established yet, but Tor bootstrap progress (tor_bootstrap_percent) is 100."

      exit_code=0
      exit "$exit_code"
   fi

   ## Happened that Tor bootstrap was 100% done but not circuit established.
   ## Example Tor log:
   ## Bootstrapped 100% (done): Done
   ## Our directory information is no longer up-to-date enough to build circuits: We're missing descriptors for 1/2 of our primary entry guards (total microdescriptors: 6967/6984). That's ok. We will try to fetch missing descriptors soon.
   output_cmd "Conclusion: No Tor circuit established yet."
}

anondate_use() {
   if [ "$VM" = "Workstation" ]; then
      output_cmd "anondate_use: Skipping 'anondate-set' since this is a workstation, ok."
      return 0
   fi

   ## https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
   ## https://www.whonix.org/wiki/Dev/TimeSync#Fixing_Time_based_on_Tor_Consensus
   ## /usr/libexec/sdwdate/sdwdate-start-anondate-set-file-watcher
   output_cmd "anondate_use: Running 'anondate-set' (by creating file '$anondate_state_folder/request_anondate-set')..."
   touch "$anondate_state_folder/request_anondate-set"

   exit_code=2
   exit "$exit_code"
}

te_pe_tb_check() {
   ## For better look inside sdwdate log viewer.
   printf '\n'

   output_cmd "### START: ### $0"

   anondate_folder

   detect_vm_type

   onion_time_script_status

   timesanitycheck_static

   exit_if_tor_not_yet_enabled

   tor_dormant_status

   check_tor_circuit_established_yet

   tor_bootstrap_check

   tor_consensus_time_sanity_check

   connectivity_test

   ## Cannot use this at this point. While seeing the following log:
   ##
   ## 2021-02-26 14:21:15 - sdwdate - INFO - PREPARATION:
   ## 2021-02-26 14:21:15 - sdwdate - INFO - /usr/libexec/helper-scripts/onion-time-pre-script: Start.
   ## Within minimum time Sun Jan 17 00:00:00 UTC 2021 and expiration timestamp Tue May 17 10:00:00 UTC 2033.
   ## Tor reports: NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"
   ## Tor circuit: established.
   ## Tor Consensus Time Sanity Check: The clock might be too fast. Clock is faster than consensus/valid-until 2021-02-26 01:00:00.
   ## Tor already reports circuit established.
   ## /usr/libexec/helper-scripts/onion-time-pre-script: END: Exiting with exit_code '0' indicating 'success'.
   ## 2021-02-26 14:21:15 - sdwdate - INFO - PREPARATION RESULT: SUCCESS.
   ##
   ## Tor reporting "circuit established" by itself is not a reliable determination,
   ## if onion connections can be established. Some Tor onion connections where still timing out.
   ## This happens when turning off Whonix-Gateway for example for one day.
   ## Tor still uses the old Tor consensus until it downloads the new Tor consensus.
   ## Therefore Tor Consensus Time Sanity Check will show in clock being too fast,
   ## which resulted in testing in onion time sources to be mistakenly considered
   ## too fast, while these where actually correct.
   #exit_success_if_tor_circuit_already_established

   if [ "$timesanitycheck_static_timestamp_based_failed" = "true" ]; then
      anondate_use
      ## Time incorrect by a lot. Indicate error.
      ## [1] Since anondate_use has it's own `exit`, no need to use `exit` here.
      ## [2] Do not use `anondate-set` on Whonix-Workstation.
      ##     Since sdwdate can establish onion connections irrespective of Whonix-Workstation system clock,
      ##     as long as Whonix-Gateway Tor is functional.
      #exit_code=1
      #exit "$exit_code"
   fi

   if [ ! "$clock_tor_consensus_check_result" = "ok" ]; then
      anondate_use
      ## Time incorrect but not by giant amounts.
      ## Probably non-fatal issue. Indicate wait.
      ## [1]
      ## [2]
      #exit_code=2
      #exit "$exit_code"
   fi

   exit_success_if_tor_circuit_already_established

   exit_code=2
   exit "$exit_code"
}

te_pe_tb_check "$@"
