#!/bin/bash

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

check_qubes_network_interface() {
   if test -f /run/qubes/this-is-templatevm ; then
      return 0
   fi

   local qubesdb_read_qubes_ip_exit_code
   qubesdb_read_qubes_ip_exit_code="0"
   ## https://doc.qubes-os.org/en/latest/developer/debugging/vm-interface.html
   ## > /qubes-ip - IP address for this VM (only when VM has netvm set)
   qubes_ip="$(qubesdb-read /qubes-ip 2>&1)" || { qubesdb_read_qubes_ip_exit_code="$?" ; true; };
   qubes_ip="$(sanitize-string -- 255 "$qubes_ip")"

   if [ "$qubes_ip" = "Failed connect to local daemon" ]; then
      local if_you_know_what_you_are_doing_msg
      if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"
      local MSG="\
<p>Qubes qubes-db test Result: qubes-db read failed!
<br></br>
<br></br>Reading qubes-db failed for some reason. This could be due to a broken upgrade, race condition or other bug. Try restarting the VM to see if this error persists.
<br></br>
<br></br><code>qubesdb-read /qubes-ip</code> output:
<br></br><code>$qubes_ip</code>
<br></br>
<br></br>Check the systemd unit status of qubes-db.
<br></br>1. Open a terminal. ($start_menu_instructions_system_first_part Terminal)
<br></br>2. Run. <blockquote><code>sudo systemctl status qubes-db</code></blockquote>
$if_you_know_what_you_are_doing_msg</p>"
      $output_x ${output_opts[@]} --messagex --typex "error" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "error" --message "$MSG"
      EXIT_CODE="1"
      cleanup "1"
      return 0
   fi

   if ! [[ "$qubes_ip" =~ \
     ^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]]; then
      local if_you_know_what_you_are_doing_msg
      if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"
     local MSG="\
<p>Qubes qubes-db test Result: qubes-db read of /qubes-ip returned invalid data!
<br></br>
<br></br>Reading qubes-db /qubes-ip returned data that does not match an IPv4 address. This could be due to a software bug or VM compromise. Try restarting the VM to see if this error persists.
<br></br>
<br></br><code>qubesdb-read /qubes-ip</code> output (sanitized):
<br></br><code>$qubes_ip</code>
$if_you_know_what_you_are_doing_msg</p>"
      $output_x ${output_opts[@]} --messagex --typex "error" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "error" --message "$MSG"
      EXIT_CODE="1"
      ## TODO: review
      #cleanup "1"
      return 0
   fi

   local MSG="<p>Qubes qubes-db Test Result: Connection to local qubes-db daemon succeeded, ok.</p>"
   if [ "$verbose" -ge "1" ]; then
      $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
   fi

   if [ "$GATEWAY_IP" = "qubesdb_read_failed" ] || [ ! "$qubesdb_read_qubes_ip_exit_code" = "0" ] ; then
      local netvm_to_what
      if [ "$vm_lower_case_short" = "machine" ]; then
         if test -e /var/run/qubes/this-is-netvm; then
            true "INFO: This could be sys-net. qubesdb-read /qubes-ip outputs qubesdb_read_failed in case of netvm, ok."
            return 0
         elif test -e /var/run/qubes/this-is-proxyvm; then
            true "INFO: This could be sys-firewall. qubesdb-read /qubes-ip outputs qubesdb_read_failed in case of netvm, ok."
            netvm_to_what="sys-net"
         else
            netvm_to_what="sys-firewall"
         fi
      else
         if [ "$vm_lower_case_short" = "gateway" ]; then
            netvm_to_what="sys-firewall"
         else
            netvm_to_what="sys-whonix"
         fi
      fi
      local if_you_know_what_you_are_doing_msg
      if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"
      local MSG="\
<p>Qubes Settings Test Result: Networking will probably not work. Is the 'netvm' of this VM set to <code>none</code>?
<br></br>Usually you want to set the 'netvm' of this VM to '<code>$netvm_to_what</code>'.
<blockquote>1. Shutdown this VM.
<br></br>2. dom0 -> Qubes VM Manager -> right click '<code>$qubes_name_of_vm</code>' -> VM Settings -> NetVM
<br></br>3. Restart this VM.</blockquote>
Also refer to the installation instructions of Qubes-Whonix.
<br></br>(Debugging information:)
<br></br>(GATEWAY_IP: <code>$GATEWAY_IP</code>)
<br></br>(qubes_ip: <code>$qubes_ip</code>)
<br></br>(qubes_vm_type: <code>$qubes_vm_type</code>)
<br></br>
<br></br>$if_you_know_what_you_are_doing_msg</p>"
      $output_x ${output_opts[@]} --messagex --typex "warning" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "warning" --message "$MSG"
      EXIT_CODE="1"
      ## Do not run 'cleanup' here, the Qube may lack Internet access on purpose.
      return 0
   fi

   local MSG="<p>Qubes Settings Test Result: Ok. (GATEWAY_IP: <code>$GATEWAY_IP</code>)</p>"
   if [ "$verbose" -ge "1" ]; then
      $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
   fi
}

check_qubes_vm_type() {
   if [ "$vm_lower_case_short" = "gateway" ]; then
      if [ -f /run/qubes/this-is-netvm ] || [ -f /run/qubes/this-is-proxyvm ] || [ -f /run/qubes/this-is-templatevm ] ; then
         local MSG="<p>Qubes Settings Test Result: Ok, qubes_vm_type is <code>$qubes_vm_type</code>.</p>"
         if [ "$verbose" -ge "1" ]; then
            $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
            $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
         fi
      else
         local if_you_know_what_you_are_doing_msg
         if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"
         local MSG="\
<p>Qubes Settings Test Result: Qubes-Whonix-Gateway Qubes VM Type is <code>$qubes_vm_type</code>, but NetVM, ProxyVM or TemplateVM is expected.
<br></br>You most likely want to re-create this VM. Make sure to choose ProxyVM.
<br></br>Also refer to the installation instructions of Qubes-Whonix.</p>
<br></br>
<br></br>$if_you_know_what_you_are_doing_msg</p>"
         $output_x ${output_opts[@]} --messagex --typex "warning" --message "$MSG"
         $output_cli ${output_opts[@]} --messagecli --typecli "warning" --message "$MSG"
         EXIT_CODE="1"
         cleanup "1"
         return 0
      fi
   fi

   if [ "$vm_lower_case_short" = "workstation" ]; then
      if [ -f /run/qubes/this-is-appvm ] || [ -f /run/qubes/this-is-templatevm ] ; then
         local MSG="<p>Qubes Settings Test Result: Ok, qubes_vm_type is <code>$qubes_vm_type</code>.</p>"
         if [ "$verbose" -ge "1" ]; then
            $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
            $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
         fi
      else
         local if_you_know_what_you_are_doing_msg
         if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"
         local MSG="\
<p>Qubes Settings Test Result: Qubes-Whonix-Workstation Qubes VM Type is <code>$qubes_vm_type</code>, but AppVM or TemplateVM is expected.
<br></br>You most likely want to re-create this VM. Make sure to choose AppVM.
<br></br>Also refer to the installation instructions of Qubes-Whonix.</p>
<br></br>
<br></br>$if_you_know_what_you_are_doing_msg</p>"
         $output_x ${output_opts[@]} --messagex --typex "warning" --message "$MSG"
         $output_cli ${output_opts[@]} --messagecli --typecli "warning" --message "$MSG"
         EXIT_CODE="1"
         cleanup "1"
         return 0
      fi
   fi
}

check_qubes_persistent_mounts() {
   local should_be_ephemeral_list should_be_ephemeral_path bind_mount_list \
      bind_mount_path MSG if_you_know_what_you_are_doing_msg

   if ! [ -f /run/qubes/persistent-rw-only ]; then
      ## Skip, only relevant in AppVMs
      return 0
   fi

   ## TODO: What other directories should be listed here? Maybe '/opt'?
   should_be_ephemeral_list=( '/etc' '/usr' '/var' )
   if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"

   for should_be_ephemeral_path in "${should_be_ephemeral_list[@]}"; do
      ## 'findmnt' outputs information about bind mounts using the format
      ## /dev/disk[/bind/path]. The sed command extracts only the part between
      ## the brackets.
      readarray -t bind_mount_list < <(findmnt --noheadings --output=SOURCE \
         -- "${should_be_ephemeral_path}" \
         | sed 's/.*\[\([^]]*\)\].*/\1/' || true)
      for bind_mount_item in "${bind_mount_list[@]}"; do
         printf '%s\n' "${bind_mount_item}" >&2
         if [ -d "/rw/${bind_mount_item}" ]; then
            MSG="\
<p>Qubes Persistent Mount Test Result: Directory <code>$should_be_ephemeral_path</code> should be ephemeral, but is bind-mounted from <code>/rw</code>!
<br></br>This directory's files will be modified by template software updates. This <code>$qubes_vm_type</code> will not receive those changes due to this configuration.
<br></br>Remove the directory from Qubes' bind-dirs configuration.
<br></br>See: https://doc.qubes-os.org/en/latest/user/advanced-topics/bind-dirs.html
<br></br>
<br></br>$if_you_know_what_you_are_doing_msg</p>"
            $output_x ${output_opts[@]} --messagex --typex "warning" --message "$MSG"
            $output_cli ${output_opts[@]} --messagecli --typecli "warning" --message "$MSG"
            EXIT_CODE="1"
            break
         fi
      done
   done
}

check_qubes_settings() {
   if [ ! "$qubes_detected" = "true" ]; then
      local MSG="<p>Qubes Settings Test Result: Skipped, because Qubes not detected.</p>"
      if [ "$verbose" -ge "1" ]; then
         $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
         $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      fi
      return 0
   fi

   systemcheck_run_function check_qubes_network_interface
   systemcheck_run_function check_qubes_vm_type
   systemcheck_run_function check_qubes_persistent_mounts
}

check_qubes_update_proxy_status_files_check() {
   if ! test -f /usr/share/whonix/marker ; then
      true "INFO: Qubes UpdatesProxy Status Files Check: Skip, because not running in Whonix, ok."
      return 0
   fi

   if ! test -f /run/qubes/this-is-templatevm ; then
      true "INFO: Qubes UpdatesProxy Status Files Check: Skip, because not running in Template, ok."
      return 0
   fi

   local exit_code unit_name max_wait_seconds counter
   unit_name="qubes-whonix-torified-updates-proxy-check.service"
   max_wait_seconds=5
   counter=0

   while (( counter < max_wait_seconds )); do
      exit_code="$(systemctl show -p ExecMainStatus --value "$unit_name")"

      if ! is_whole_number "$exit_code"; then
         true
      elif [ "$exit_code" = 0 ]; then
         local MSG="<p>Qubes UpdatesProxy Status Files Check: Ok.</p>"
         if [ "$verbose" -ge "1" ]; then
            $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
            $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
         fi
         return 0
      else
         break
      fi

      light_sleep 1
      counter=$(( counter + 1 ))
   done

   local MSG="<p>Qubes UpdatesProxy Status Files Check: Failed!

Debugging information:
$unit_name did not complete after $max_wait_seconds seconds or failed.

<blockquote>sudo journalctl --boot --output cat -u $unit_name</blockquote>
</p>"

   $output_x ${output_opts[@]} --messagex --typex "error" --message "$MSG"
   $output_cli ${output_opts[@]} --messagecli --typecli "error" --message "$MSG"
}

check_qubes_update_proxy_daemon() {
   if test -f /run/qubes/this-is-templatevm ; then
      true "INFO: Qubes Updates Proxy Daemon Check: Skip, because running inside Template."
      return 0
   fi

   if [ ! "$VM" = "Whonix-Gateway" ]; then
      true "INFO: Qubes Updates Proxy Daemon Check: Skip, because not running inside Whonix-Gateway."
      return 0
   fi

   if leaprun qubes-updates-proxy-status >/dev/null 2>/dev/null ; then
      local MSG="<p>Qubes Updates Proxy Daemon Check Result: qubes-updates-proxy.service running, ok.</p>"
      if [ "$verbose" -ge "1" ]; then
         $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
         $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      fi
      return 0
   fi
   local if_you_know_what_you_are_doing_msg
   if_you_know_what_you_are_doing_msg="$(if_you_know_what_you_are_doing_funct "$FUNCNAME")"
   local MSG="\
<p>Qubes Updates Proxy Daemon Check Result: Qubes torified UpdatesProxy not running. Updating Templates using this Qubes-Whonix-Gateway will fail.
<br></br>To see this for yourself, run:
<blockquote>leaprun qubes-updates-proxy-status</blockquote>
<br></br>$if_you_know_what_you_are_doing_msg</p>"
   $output_x ${output_opts[@]} --messagex --typex "error" --message "$MSG"
   $output_cli ${output_opts[@]} --messagecli --typecli "error" --message "$MSG"
   EXIT_CODE="1"
}

check_qubes_update_proxy_reachability() {
   local MSG="<p>Qubes UpdatesProxy Reachability Test: Trying to reach local Qubes UpdatesProxy... PROXY_SERVER: $PROXY_SERVER</p>"
   $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"

   local curl_exit_code
   curl_exit_code="0"

   ## Do not use --fail, otherwise curl will return exit code 22 and skip tinyproxy output.
   UWT_DEV_PASSTHROUGH="1" \
      curl \
      $CURL_VERBOSE \
      --connect-timeout 3 \
      --output "$TEMP_DIR/qubes_update_proxy_reachability_file" \
      "$PROXY_SERVER" \
      &

   lastpid="$!"
   wait "$lastpid" || { curl_exit_code="$?" ; true; };

   local curl_status_message
   curl_status_message="$(/usr/libexec/helper-scripts/curl_exit_codes "$curl_exit_code")"

   qubes_update_proxy_test_result_torified="none"

   if [ "$curl_exit_code" = "7" ]; then
      if test -f /run/qubes/this-is-templatevm ; then
         local MSG="<p>Qubes UpdatesProxy Reachability Result: Failed. (UpdatesProxy not reachable.) \
   (curl exit code: $curl_exit_code | curl status message: $curl_status_message)</p>"
         $output_x ${output_opts[@]} --messagex --typex "error" --message "$MSG"
         $output_cli ${output_opts[@]} --messagecli --typecli "error" --message "$MSG"
         return 0
      fi
      local MSG="<p>Qubes UpdatesProxy Reachability Result: Ok, UpdatesProxy was not reachable.
This is expected because not running in Template.</p>"
      #$output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      return 0
   fi

   if [ ! "$curl_exit_code" = "0" ]; then
      local MSG="<p>Qubes UpdatesProxy Reachability Result: Failed. (Unknown issue.) \
(curl exit code: $curl_exit_code | curl status message: $curl_status_message)</p>"
      $output_x ${output_opts[@]} --messagex --typex "error" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "error" --message "$MSG"
      return 0
   fi

   local curl_output
   curl_output="$(stcat "$TEMP_DIR/qubes_update_proxy_reachability_file")" || true

   if printf '%s\n' "$curl_output" | grep -q -- "${PROXY_META}" ; then
      qubes_update_proxy_test_result_torified="true"
      local MSG="<p>Qubes UpdatesProxy Reachability Result: Ok, torified update proxy reachable.</p>"
      #$output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      return 0
   fi

   qubes_update_proxy_test_result_torified="false"
   local MSG="<p>Qubes UpdatesProxy Reachability Result: Ok, <u>non</u>-torified update proxy reachable.
curl_status_message: <blockquote>$curl_status_message</blockquote>
PROXY_META: <blockquote>$(sanitize-string -- 255 "${PROXY_META}")</blockquote>
curl_output: <blockquote>$(sanitize-string -- 255 "$curl_output")</blockquote></p>"
   $output_x ${output_opts[@]} --messagex --typex "warning" --message "$MSG"
   $output_cli ${output_opts[@]} --messagecli --typecli "warning" --message "$MSG"
}

check_qubes_update_proxy_connectivity() {
   if [ ! "$leak_tests" = "true" ]; then
      local MSG="<p>Qubes UpdatesProxy Connectivity Test: Skipped, because not using --leak-tests (--show-ip), ok.</p>"
      #$output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      return 0
   fi

   if [ ! "$qubes_update_proxy_test_result_torified" = "true" ]; then
      local MSG="<p>Qubes UpdatesProxy Connectivity Test: Skipped.</p>"
      $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      return 0
   fi

   local MSG="<p>Qubes UpdatesProxy Connectivity Test: Trying to use Qubes update proxy... PROXY_SERVER: $PROXY_SERVER</p>"
   $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"

   local curl_exit_code
   curl_exit_code="0"

   ## UWT_DEV_PASSTHROUGH=1 curl --connect-timeout 3 http://127.0.0.1:8082/
   UWT_DEV_PASSTHROUGH="1" \
      curl \
         --fail \
         --proxy "$PROXY_SERVER" \
         $CURL_VERBOSE \
         --retry-connrefused \
         --retry 3 \
         --retry-delay 3 \
         --retry-max-time 120 \
         --max-time 180 \
         --output "$TEMP_DIR/qubes_update_proxy_connectivity_file" \
         https://deb.qubes-os.org \
         &

   lastpid="$!"
   wait "$lastpid" || { curl_exit_code="$?" ; true; };

   local curl_status_message
   curl_status_message="$(/usr/libexec/helper-scripts/curl_exit_codes "$curl_exit_code")"

   if [ "$curl_exit_code" = "22" ]; then
      local MSG="<p>Qubes UpdatesProxy Connectivity Result: Failed. (Filtered.) \
(curl exit code: curl_exit_code | curl status message: $curl_status_message)</p>"
      $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      return 0
   fi

   if [ ! "$curl_exit_code" = "0" ]; then
      local MSG="<p>Qubes UpdatesProxy Connectivity Result: Unknown issue. \
(curl exit code: $curl_exit_code | curl status message: $curl_status_message)</p>"
      $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
      $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
      return 0
   fi

   local MSG="<p>Qubes UpdatesProxy Connectivity Result: Ok, functional.</p>"
   $output_x ${output_opts[@]} --messagex --typex "info" --message "$MSG"
   $output_cli ${output_opts[@]} --messagecli --typecli "info" --message "$MSG"
}

check_qubes_update_proxy() {
   if [ ! "$qubes_detected" = "true" ]; then
      local MSG="<p>Qubes UpdatesProxy Test Result: Skipped, because Qubes not detected.</p>"
      return 0
   fi

   check_qubes_update_proxy_status_files_check
   check_qubes_update_proxy_daemon
   check_qubes_update_proxy_reachability
   check_qubes_update_proxy_connectivity
}
