#!/bin/bash

## Copyright (C) Amnesia <amnesia at boum dot org>
## Copyright (C) 2014 - 2023 ENCRYPTED SUPPORT LP <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

#set -x
set -e
set -o pipefail

variables() {
   [ -n "$TOR_RC" ] || TOR_RC="/etc/tor/torrc"

   if test -r /run/tor/log ; then
      [ -n "$TOR_LOG" ] || TOR_LOG="cat /run/tor/log"
   fi
   ## Does not include certificate lifetime.
   [ -n "$TOR_LOG" ] || TOR_LOG="journalctl --boot --output cat --no-pager -u tor@default"

   [ -n "$TOR_DIR" ] || TOR_DIR="/var/lib/tor"
   [ -n "$TOR_DESCRIPTORS" ] || TOR_DESCRIPTORS="${TOR_DIR}/cached-microdescs"
   [ -n "$NEW_TOR_DESCRIPTORS" ] || NEW_TOR_DESCRIPTORS="${TOR_DESCRIPTORS}.new"
   [ -n "$TOR_CONSENSUS" ] || TOR_CONSENSUS="${TOR_DIR}/cached-microdesc-consensus"
   [ -n "$TOR_UNVERIFIED_CONSENSUS" ] || TOR_UNVERIFIED_CONSENSUS="${TOR_DIR}/unverified-microdesc-consensus"
   [ -n "$DATE_RE" ] || DATE_RE='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]'
   [ -n "$LC_TIME" ] || export LC_TIME=C
   [ -n "$TZ" ] || export TZ=UTC
}

parse_cmd_options() {
   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   while :
   do
       case $1 in
           --verbose)
               echo "$SCRIPTNAME verbose output..."
               echo "Script running as $(whoami)"
               set -x
               true "$0: $@"
               shift
               ;;
           --has-consensus)
               has_consensus_="true"
               shift
               ;;
           --current-time-in-valid-range)
               current_time_in_valid_range_="true"
               shift
               ;;
           --show-valid-after)
               show_valid_after_="true"
               shift
               ;;
           --show-valid-until)
               show_valid_until_="true"
               shift
               ;;
           --show-middle-range)
               show_middle_range_="true"
               shift
               ;;
           --tor-cert-lifetime-valid)
               tor_cert_lifetime_valid_="true"
               shift
               ;;
           --tor-cert-valid-after)
               tor_cert_valid_after_="true"
               shift
               ;;
           --verified-only)
              verified_only_="true"
              shift
              ;;
           --prefer-verified)
              prefer_verified_="true"
              shift
              ;;
           --unverified-only)
              unverified_only_="true"
              shift
              ;;
           --user-permission)
              user_permission_="true"
              shift
              ;;
           --group-permission)
              group_permission_="true"
              shift
              ;;
           --)
               shift
               break
               ;;
           -*)
               echo "$SCRIPTNAME unknown option: $1" >&2
               exit 111
               ;;
           *)
               break
               ;;
       esac
   done

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

   if [ "$verified_only_" = "true" ]; then
      consensus="$TOR_CONSENSUS"
   elif [ "$prefer_verified_" = "true" ]; then
      if [ -e "${TOR_CONSENSUS}" ]; then
         consensus="$TOR_CONSENSUS"
      else
         ## make unverified consensus ISOTime accessible through Tor's ControlPort
         ## https://gitlab.torproject.org/tpo/core/tor/-/issues/16845
         consensus="$TOR_UNVERIFIED_CONSENSUS"
      fi
   elif [ "$unverified_only_" = "true" ]; then
      consensus="$TOR_UNVERIFIED_CONSENSUS"
   else
      consensus="$TOR_CONSENSUS"
   fi

   if [ "$has_consensus_" = "true" ]; then
      has_consensus
      exit "$?"
   fi
   if [ "$current_time_in_valid_range_" = "true" ]; then
      current_time_is_in_valid_range
      exit "$?"
   fi
   if [ "$show_valid_after_" = "true" ]; then
      show-valid-after
      exit "$?"
   fi
   if [ "$show_valid_until_" = "true" ]; then
      show-valid-until
      exit "$?"
   fi
   if [ "$show_middle_range_" = "true" ]; then
      show-middle-range
      exit "$?"
   fi
   if [ "$tor_cert_lifetime_valid_" = "true" ]; then
      tor_cert_lifetime_valid
      exit "$?"
   fi
   if [ "$tor_cert_valid_after_" = "true" ]; then
      tor_cert_valid_after
      exit "$?"
   fi
   if [ "$user_permission_" = "true" ]; then
      user_permission
      exit "$?"
   fi
   if [ "$group_permission_" = "true" ]; then
      group_permission
      exit "$?"
   fi

   echo "No option chosen." 2>&1
   exit 1
}

root_check() {
   if [ "$(whoami)" = "sdwdate" ]; then
      true
   elif [ "$(id -u)" != "0" ]; then
      echo "$0: ERROR: Must run as root." >&2
      exit 112
   fi
}

has_consensus() {
   if ! test -r "$TOR_DIR" ; then
      echo "$0: ERROR: Cannot read TOR_DIR: '$TOR_DIR'" >&2
      exit 4
   fi
   if [ ! -r "$consensus" ]; then
      echo "$0: ERROR: Cannot read consensus file: '$consensus'" >&2
      exit 4
   fi
   local grep_exit_code="0"
   grep -qs "^valid-until ${DATE_RE}"'$' "$consensus" || { grep_exit_code="$?" ; true; };
   if [ "$grep_exit_code" = "0" ]; then
      return 0
   else
      return 1
   fi
}

show-valid-after() {
   vstart="$(/usr/libexec/helper-scripts/tor_consensus_valid-after.py)"
   #vstart="$(sed -n "/^valid-after \(${DATE_RE}\)"'$/s//\1/p; t q; b; :q q' ${consensus})" || exit 1
   if [ "$vstart" = "" ]; then
      exit 1
   fi
   if [ "$show_valid_after_" = "true" ]; then
      echo "$vstart"
   fi
}

show-valid-until() {
   vend="$(/usr/libexec/helper-scripts/tor_consensus_valid-until.py)"
   #vend="$(sed -n "/^valid-until \(${DATE_RE}\)"'$/s//\1/p; t q; b; :q q' ${consensus})" || exit 1
   if [ "$vend" = "" ]; then
      exit 1
   fi
   if [ "$show_valid_until_" = "true" ]; then
      echo "$vend"
   fi
}

show-middle-range() {
   show-valid-after
   show-valid-until
   vmid="$(date -ud "${vstart} -0130" +'%F %T')" || exit 1
   if [ "$show_middle_range_" = "true" ]; then
      echo "$vmid"
   fi
}

current_time_is_in_valid_range() {
   show-middle-range

   ## {{ Sanity Test
   ## Debugging.
   #vend="2099-09-03 09:41:29"
   vendchk="$(date -ud "${vstart} -0300" +'%F %T')"
   if [ ! "${vend}" = "${vendchk}" ]; then
      echo "ERROR: Unexpected valid-until: [${vend}] is not [${vstart} + 3h]"
      exit 1
   fi
   ## }} Sanity Test

   curdate="$(date -u +'%F %T')"
   ## Debugging.
   #curdate="2099-09-03 09:41:29"

   vendcons="$(date -ud "${vstart} -0230" +'%F %T')"
   order="${vstart}
${curdate}
${vendcons}"
   ordersrt="$(echo "${order}" | sort)"

   if [ "${order}" = "${ordersrt}" ]; then
      exit 0
   else
      exit 1
   fi
}

tor_cert_lifetime_valid() {
   ## TODO:
   ## To be sure that we only grep relevant information, we
   ## should delete the log when Tor is started, which we do
   ## TODO:
   ## in 10-tor.sh.

   ## Example Tor log:
   ## Sep 03 10:32:59.000 [warn] Certificate already expired. Either their clock is set wrong, or your clock is wrong.
   ## Sep 03 10:32:59.000 [warn] (certificate lifetime runs from Aug 16 00:00:00 2014 GMT through Jul 29 23:59:59 2015 GMT. Your time is Sep 03 10:32:59 2015 UTC.)

   ## Certificate not yet valid. Either their clock is set wrong, or your clock is wrong.
   ## (certificate lifetime runs from Sep 13 00:00:00 2020 GMT through Sep 13 00:00:00 2021 GMT. Your time is Jan 05 14:31:46 2000 UTC.)

   ## The log severity will be "warn" if bootstrapping with
   ## authorities and "info" with bridges.

   ## For some reason 'grep -q' does not work here.
   if $TOR_LOG | grep "Certificate \(not yet valid\|already expired\)\." >/dev/null ; then
      return 1
   else
      return 0
   fi
}

tor_cert_valid_after() {
   ## make certificate lifetime accessible through Tor's ControlPort
   ## https://gitlab.torproject.org/tpo/core/tor/-/issues/16822

   ## Only print the last = freshest match
   $TOR_LOG | sed -n 's/^.*certificate lifetime runs from \(.*\) through.*$/\1/p' | tail -n 1

   ## Example output:
   ## Jun 16 00:00:00 2014 GMT
   ## sudo: timestamp too far in the future: Jun 16 00:00:00 2014 GMT
}

user_permission() {
   stat -c "%U" "$consensus"
}

group_permission() {
   stat -c "%G" "$consensus"
}

root_check
variables
parse_cmd_options "$@"
