#!/bin/bash

## Copyright (C) 2012 - 2020 ENCRYPTED SUPPORT LP <adrelanos@riseup.net>
## 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/lib/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 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.
##
## <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
## outside of Whonix) listen on the external IP instead. See also:
##
## * https://github.com/Whonix/bindp
## * https://phabricator.whonix.org/T561
#### 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

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 /rw/config/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 command line instead by a wrapper. Otherwise it would
   ## interfere with the forwarded options to application. I.e. 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://phabricator.whonix.org/T317
   [ -n "$TORSOCKS_LOG_LEVEL" ] || TORSOCKS_LOG_LEVEL=1
   export TORSOCKS_LOG_LEVEL

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

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

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

   ## Remove the leading '.anondist' file extension. This is useful in case for
   ## example uwtwrapper_parent is called by running
   ## 'exec /usr/bin/apt-get.anondist', such as eatmydata is doing.
   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://phabricator.whonix.org/T797
   [ -n "$uwtwrapper_zeroarg" ] || uwtwrapper_zeroarg="$uwtwrapper_parent"
   export uwtwrapper_zeroarg

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

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

   true "Let us see if uwtwrapper_parent.anondist-orig $uwtwrapper_parent.anondist-orig is actually a symlink."
   ## (For example '/usr/bin/gpg2.anondist-orig' is actually a symlink to 'gpg'.)
   if test -h "$uwtwrapper_parent.anondist-orig" ; then
      true "Yes, it is a symlink."
      local readlink_result
      if readlink_result="$(readlink "$uwtwrapper_parent.anondist-orig")" ; then
         ## readlink_result example:
         ## gpg
         true "Therefore, execute readlink_result $readlink_result."
         exec "$readlink_result" "$@"
      fi
   else
      true "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##*/}"

      ## Lets see if /usr/bin/aptitude is actually a symlink to /etc/alternatives/aptitude.
      if [ -e "/etc/alternatives/$basename_uwtwrapper_parent" ]; then
         local readlink_result
         ## Lets see where for example /etc/alternatives/aptitude links to and use it.
         if readlink_result="$(readlink "/etc/alternatives/$basename_uwtwrapper_parent")" ; then
            ## Symlink could be read. Lets 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
      if ! command -v command-not-found >/dev/null 2>/dev/null ; then
         echo "$SCRIPTNAME uwt wrapper ERROR: $uwtwrapper_parent.anondist-orig does not exist."
         exit 1
      fi
      ## Example basename_uwtwrapper_parent:
      ## aptitude
      basename_uwtwrapper_parent="${uwtwrapper_parent##*/}"
      ## --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: $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://phabricator.whonix.org/T561
   [ -n "$bindp_so" ] || bindp_so="/usr/lib/libindp.so"
   [ -n "$bindp_interface" ] || bindp_interface="eth0"
   [ -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"
   ## Environment variable BIND_ADDR gets 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 "/tmp/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
