#!/bin/bash

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

## $uwtwrapper_parent uwt wrapper.

#### meta start
#### project Whonix
#### category networking
#### description
## When running uwt-wrapped applications (such as <code>apt</code>,
## <code>wget</code>, <code>curl</code>, <code>onionshare</code>, or others)
## automatically prepend <code>torsocks</code> or <code>bindp</code>, i.e.
##
## When, for example, <code>apt</code> or <code>curl</code> is executed, what
## really happens is running <code>torsocks apt</code> or
## <code>torsocks curl</code>.
##
## uwtwrappers and <code>/usr/libexec/uwt/uwtwrapper</code> are hacks to socksify applications
## that do
## not support native SOCKS proxy settings. Used to implement Stream Isolation.
## https://www.whonix.org/wiki/Stream_Isolation
##
## In essence, uwtwrappers are installed so users can type commands like
## apt-get normally while transparently injecting torsocks, thereby stream
## isolating them.
##
## To better understand how uwt wrappers function, you could, for example, open
## /usr/bin/apt-get.anondist in an editor.
##
## Also useful to run:
## ls -la /usr/bin/apt-get*
##
## You will see that /usr/bin/apt-get has been replaced with a symlink to
## /usr/bin/apt-get.anondist. (This was done using config-package-dev.)
##
## /usr/bin/apt-get.anondist is a uwt wrapper.
##
## /usr/bin/apt-get.anondist-orig is the original apt-get binary.
##
## <code>bindp</code> is used to make applications which listen on the internal
## IP by default, such as <code>onionshare</code> (which is the right thing to
## do outside of Whonix), listen on the external IP instead. See also:
##
## * https://github.com/Whonix/bindp
## * https://forums.whonix.org/t/find-way-to-have-tor-ephermal-hidden-service-using-applications-in-whonix-workstation-bind-on-all-interfaces/18846
#### meta end

## If you want to enable/disable uwt and/or privacy
## globally or for certain applications, see /etc/uwt.d/.

## Technical comment:
## - 'time_privacy' is called 'time_privacy' because this script may get added
##   upstream to the Debian package 'faketime'. This is to avoid a conflict when
##   the new file 'timeprivacy' gets installed by 'faketime'.

if [ ! "$uwtwrapper_verbose" = "" ]; then
   if [ "$uwtwrapper_verbose" -ge 1 ]; then
      set -x
   fi
fi

true "$0: START"

set -e

error_handler() {
   echo "
## uwtwrapper BUG.
## SCRIPTNAME: '$SCRIPTNAME'
## uwtwrapper_parent: '$uwtwrapper_parent'
## BASH_COMMAND: '$BASH_COMMAND'
## Please report this BUG!
"
   exit 1
}

trap "error_handler" ERR

set -o pipefail
set -o errtrace

SCRIPTNAME="$(basename -- "$BASH_SOURCE")"

preparation() {
   declare -A -g timeprivacy
   declare -A -g uwtwrapper
   declare -A -g bindp_use

   ## Keep for backwards compatibility with legacy config files.
   declare -A -g uwtport
}

source_config_folder() {
   shopt -s nullglob
   for i in /etc/uwt.d/*.conf /usr/local/etc/uwt.d/*.conf; do
      bash -n "$i"
      source "$i"
   done
}

show_help() {
   echo "\
$0 help.

$0 is not supposed to be used manually by users.
Manual usage is difficult. Use uwt instead. See:
man uwt

If you are a very advanced user or developer, who wants to learn more about
uwt wrappers, read ahead.

uwtwrappers and $0 are hacks to socksify applications that do
not support native socks proxy settings. Used to implement Stream Isolation.
https://www.whonix.org/wiki/Stream_Isolation

In essence, uwtwrappers are installed so users can type commands like
apt-get normally while transparently injecting torsocks, thereby stream
isolating them.

To understand better how uwt wrappers function, you could for example open
/usr/bin/apt-get.anondist in an editor.

Also useful to run:
ls -la /usr/bin/apt-get*

You will see, that /usr/bin/apt-get has been replaced with a symlink to
/usr/bin/apt-get.anondist. (This was done using config-package-dev.)

/usr/bin/apt-get.anondist is a uwt wrapper.

/usr/bin/apt-get.anondist-orig is the original apt-get binary.

If you want to add additional uwtwrappers you need the files and symlink in
place as well as uwt settings. See also the /etc/uwt.d configuration folder.

source code:
https://github.com/Whonix/uwt"
}

parse_cmd() {
   ## Only use the command line parser if $uwtwrapper_parent is empty, i.e., when
   ## manually called from the command line instead of by a wrapper. Otherwise, it would
   ## interfere with the forwarded options to the application. For example,
   ## 'apt-get --help' would break.
   if [ ! "$uwtwrapper_parent" = "" ]; then
      return 0
   fi

   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   while true;
   do
      case $1 in
         -h|-\?|--help)
            show_help
            exit 0
            ;;
        --)
            shift
            break
            ;;
        *)
            break
      esac
      shift
   done

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

nested_protection() {
   [ -n "$uwtwrapper_max" ] || uwtwrapper_max=10
   [ -n "$uwtwrapper_counter" ] || uwtwrapper_counter=0
   uwtwrapper_counter=$(( uwtwrapper_counter + 1 ))
   export uwtwrapper_counter
   if [ "$uwtwrapper_counter" -ge "$uwtwrapper_max" ]; then
      echo "$SCRIPTNAME uwt wrapper ERROR: More than uwtwrapper_counter '$uwtwrapper_counter' nested executions (uwtwrapper_max: '$uwtwrapper_max')."
      exit 255
   fi
}

variables() {
   ## Disable torsocks warning spam such as:
   ## [May 20 11:45:27] WARNING torsocks[2645]: [syscall] Unsupported syscall number 224. Denying the call (in tsocks_syscall() at syscall.c:165)
   ## https://forums.whonix.org/t/disable-torsocks-warning-spam/19084
   [ -n "$TORSOCKS_LOG_LEVEL" ] || TORSOCKS_LOG_LEVEL=1
   export TORSOCKS_LOG_LEVEL

   [ -n "$torsocks_bin" ] || torsocks_bin=torsocks
   [ -n "$uwtexec_bin" ] || uwtexec_bin=/usr/libexec/uwt/uwtexec

   if [ "$UWT_DEV_PASSTHROUGH" = "1" ]; then
      torsocks_bin=""
   fi

   ## uwtwrapper_parent examples:
   ## /usr/bin/gpg2
   ## /usr/bin/gpg2.anondist

   ## Remove the trailing '.anondist' file extension. This is useful if, for example,
   ## uwtwrapper_parent is called by running
   ## 'exec /usr/bin/apt-get.anondist', as eatmydata does.
   uwtwrapper_parent="${uwtwrapper_parent%".anondist"}"

   ## Export the zeroth argument for use in the uwtexec script.
   ## This allows uwtexec to supply the original zeroth argument.
   ## https://forums.whonix.org/t/issue-with-symbolic-links/18637
   [ -n "$uwtwrapper_zeroarg" ] || uwtwrapper_zeroarg="$uwtwrapper_parent"
   export uwtwrapper_zeroarg

   ## uwtwrapper_parent example:
   ## /usr/bin/gpg2

   true "$0: Checking whether uwtwrapper_parent $uwtwrapper_parent is a symlink."
   ## (For example, '/usr/bin/git-receive-pack' is actually a symlink to 'git'.)
   if test -h "$uwtwrapper_parent" ; then
      true "$0: Yes, it is a symlink."
      local readlink_result
      if readlink_result="$(readlink "$uwtwrapper_parent")" ; then
         ## readlink_result example:
         ## git
         true "$0: Checking whether $uwtwrapper_parent.anondist has a uwt wrapper script."
         if [ -e "$uwtwrapper_parent.anondist" ]; then
            true "$0: Yes, uwtwrapper_parent $uwtwrapper_parent has a uwt wrapper script."
         else
            true "$0: No, uwtwrapper_parent $uwtwrapper_parent does not have a uwt wrapper script."
            true "$0: Therefore, execute the target of the symlink for uwtwrapper_parent $uwtwrapper_parent."
            exec "$readlink_result" "$@"
         fi
      fi
   else
      true "$0: No, it is not a symlink."
   fi

   true "$0: Checking whether uwtwrapper_parent.anondist-orig $uwtwrapper_parent.anondist-orig is a symlink."
   ## (For example, '/usr/bin/gpg2.anondist-orig' is actually a symlink to 'gpg'.)
   if test -h "$uwtwrapper_parent.anondist-orig" ; then
      true "$0: Yes, it is a symlink."
      local readlink_result
      if readlink_result="$(readlink "$uwtwrapper_parent.anondist-orig")" ; then
         ## readlink_result example:
         ## gpg
         true "$0: Therefore, execute readlink_result: $readlink_result."
         exec "$readlink_result" "$@"
      fi
   else
      true "$0: No, it is not a symlink."
   fi

   [ -n "$timeprivacy_global" ] || timeprivacy_global="0"
   [ -n "$uwtwrapper_global" ] || uwtwrapper_global="1"

   [ -n "${bindp_use["/usr/bin/onionshare"]}" ] || bindp_use["/usr/bin/onionshare"]="true"
   [ -n "${bindp_use["/usr/bin/onionshare-gui"]}" ] || bindp_use["/usr/bin/onionshare-gui"]="true"
   [ -n "${bindp_use["/usr/bin/ricochet"]}" ] || bindp_use["/usr/bin/ricochet"]="true"

   for bindp_use_item in "${!bindp_use[@]}" ; do
     if [ "$uwtwrapper_parent" = "$bindp_use_item" ]; then
        if [ "${bindp_use[$bindp_use_item]}" = "true" ]; then
           bindp_dispatch="true"
           break
        fi
     fi
   done
}

alternatives_support() {
   if [ ! -e "$uwtwrapper_parent.anondist-orig" ]; then
      ## For example, /usr/bin/aptitude.anondist-orig does not exist.

      ## Example basename_uwtwrapper_parent:
      ## aptitude
      basename_uwtwrapper_parent="${uwtwrapper_parent##*/}"

      ## Let's see if /usr/bin/aptitude is actually a symlink to /etc/alternatives/aptitude.
      if [ -e "/etc/alternatives/$basename_uwtwrapper_parent" ]; then
         local readlink_result
         ## Let's see where, for example, /etc/alternatives/aptitude links to and use it.
         if readlink_result="$(readlink "/etc/alternatives/$basename_uwtwrapper_parent")" ; then
            ## The symlink could be read. Let's use it.
            uwtwrapper_parent="$readlink_result"
         fi
      fi
   fi
}

sanity_tests_general() {
   if [ "$uwtwrapper_parent" = "" ]; then
      echo "ERROR: You are not supposed to run '$0' manually from the command line."
      echo "See:" >&2
      echo "$0 --help" >&2
      exit 255
   fi
   if [ ! -e "$uwtwrapper_parent.anondist-orig" ]; then
      ## Example basename_uwtwrapper_parent:
      ## aptitude
      basename_uwtwrapper_parent="${uwtwrapper_parent##*/}"
      if ! command -v command-not-found >/dev/null 2>/dev/null ; then
         echo "$SCRIPTNAME uwt wrapper ERROR: file '$uwtwrapper_parent.anondist-orig' does not exist.

Is the package that includes file '$uwtwrapper_parent' installed?

Just a guess: Perhaps install the '$basename_uwtwrapper_parent' package?
sudo apt update
sudo apt install $basename_uwtwrapper_parent

'command-not-found' is a tool that might be able to suggest the name of the missing package. But it is not installed either. The user could consider to install 'command-not-found' as per documentation:
https://www.whonix.org/wiki/command-not-found"
         exit 1
      fi
      ## --ignore-installed is used, because the uwt wrapper (for example
      ## /usr/bin/aptitude) will already be installed.
      exec command-not-found --ignore-installed "$basename_uwtwrapper_parent"
   fi
   if [ ! -x "$uwtwrapper_parent.anondist-orig" ]; then
      echo "$SCRIPTNAME uwt wrapper ERROR: file '$uwtwrapper_parent.anondist-orig' is not executable."
      exit 1
   fi
}

preparation "$@"
source_config_folder "$@"
parse_cmd "$@"
nested_protection
variables "$@"
alternatives_support "$@"
sanity_tests_general "$@"

if [ "$bindp_dispatch" = "true" ]; then
   ## https://github.com/Whonix/bindp
   ## https://forums.whonix.org/t/find-way-to-have-tor-ephermal-hidden-service-using-applications-in-whonix-workstation-bind-on-all-interfaces/18846
   [ -n "$bindp_so" ] || bindp_so="/usr/lib/libindp.so"
   [ -n "$bindp_interface" ] || bindp_interface="eth0"
   ## TODO: IPv6
   [ -n "$BIND_ADDR" ] || BIND_ADDR="$(/sbin/ifconfig "$bindp_interface" | grep -oP 'inet \K\S+')" || true
   [ -n "$BIND_ADDR" ] || BIND_ADDR="127.0.0.1"
   LD_PRELOAD+=" $bindp_so"
   ## The environment variable BIND_ADDR is used by bindp.
   export BIND_ADDR
   export LD_PRELOAD
   exec $uwtexec_bin "$@"
fi

if [ "${timeprivacy["$uwtwrapper_parent"]}" = "1" ]; then
   fake_time="faketime"
   privacy_time="$("time_privacy" -f ~"/.timeprivacy/$SCRIPTNAME_CLEANED")"
else
   if [ "$timeprivacy_global" = "1" ]; then
      fake_time="faketime"
      privacy_time="$("time_privacy")"
   else
      fake_time=""
      privacy_time=""
   fi
fi

if [ "$uwtwrapper_global" = "0" ]; then
   if [ "$fake_time" = "faketime" ]; then
      exec "$fake_time" "$privacy_time" $uwtexec_bin "$@"
   else
      exec $uwtexec_bin "$@"
   fi
fi

if [ "${uwtwrapper["$uwtwrapper_parent"]}" = "0" ]; then
   if [ "$fake_time" = "faketime" ]; then
      exec "$fake_time" "$privacy_time" $uwtexec_bin "$@"
   else
      exec $uwtexec_bin "$@"
   fi
fi

if [ "$fake_time" = "faketime" ]; then
   exec "$fake_time" "$privacy_time" $torsocks_bin $uwtexec_bin "$@"
else
   exec $torsocks_bin $uwtexec_bin "$@"
fi
