#!/bin/bash

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

#### meta start
#### project Whonix
#### category networking
#### description
## curl tor wrapper
#### meta end

## NOTE: curl is incompatible with torsocks; it will not resolve .onion
## addresses if used with torsocks, and DNS will break completely if using IPv6
## with torsocks, thus we instead instruct curl to connect to a Tor proxy
## itself. We randomize the proxy username and password for each invocation so
## Tor will provide stream isolation.

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

true "$0: START"

set -o errexit
set -o nounset
set -o errtrace
set -o pipefail

if test -o xtrace; then
   uwtwrapper_verbose=1
   export uwtwrapper_verbose
fi

export uwtwrapper_parent="${BASH_SOURCE[0]}"

[[ -v UWT_DEV_PASSTHROUGH ]] || UWT_DEV_PASSTHROUGH=""

if [ "$UWT_DEV_PASSTHROUGH" = 1 ]; then
  ## UWT_DEV_PASSTHROUGH=1 is already set. Obey it.
  ## For example, this is useful to prevent 'qubes-whonix-torified-updates-proxy-check.service'
  ## from picking up output by this script such as the following.
  ## > NOTE: Detected loopback address. Stream isolation will be disabled.
  exec /usr/libexec/uwt/uwtwrapper "${@}"
fi

## Prevent 'uwtwrapper' from prepending 'torsocks'.
## This script injects 'curl' with the '--proxy' option, so torsocks should not be used.
export UWT_DEV_PASSTHROUGH=1

curl_args=( "$@" )
curl_binary="$(command -v curl.anondist-orig)" || curl_binary=""
disable_stream_isolation='false'
found_ipv6_arg='false'

for curl_arg in "${curl_args[@]}"; do
  ## Unfortunately curl's argument parser makes it impossible to determine
  ## what is an option, what is an argument to an option, and what is a URL,
  ## without knowing all the options it supports and parsing them accordingly.
  ## This is because rather than having short options like `-oarg` and long
  ## options like `--option=arg`, curl uses `-o arg` and `--option arg`. We
  ## can make a good guess that if a proxy-related argument is present in the
  ## command line, the user is probably managing proxying manually, but this
  ## will break if someone passes `-U` or `-x` as an argument to some other
  ## option (which could happen if the user is attempting to reference a file
  ## named `-U` or `-x`). Beyond that, the user might have their own proxy
  ## setup stored in a config file that they pass using the `-K` or `--config`
  ## argument, meaning we either have to parse curl config files too, or we
  ## have to live with an incomplete solution and tell people to call the
  ## original curl binary if things don't quite work.
  ##
  ## Using a simple but incomplete solution for now.
  if [ "${curl_arg}" = '--proxy' ] \
    || [ "${curl_arg}" = '-x' ]; then
    printf '%s\n' "WARNING: Detected '--proxy' or '-x' argument. Stream isolation will be disabled." 1>&2
    disable_stream_isolation='true'
  fi
  if [ "${curl_arg}" = '--proxy-user' ] \
    || [ "${curl_arg}" = '-U' ]; then
    printf '%s\n' "WARNING: Detected '--proxy-user' or '-U' argument. Stream isolation will be disabled." 1>&2
    disable_stream_isolation='true'
  fi
  if [[ "${curl_arg}" =~ ^[^-].*127\.0\.0\.1 ]] \
    || [[ "${curl_arg}" =~ ^[^-].*\[::1\] ]]; then
    printf '%s\n' "NOTE: Detected loopback address. Stream isolation will be disabled." 1>&2
    disable_stream_isolation='true'
  fi
  if [ "${curl_arg}" = '--ipv6' ] \
    || [ "${curl_arg}" = '-6' ]; then
    found_ipv6_arg='true'
  fi
  if [ "${curl_arg}" = '--config' ] \
    || [ "${curl_arg}" = '-K' ]; then
    if [ -n "${curl_binary}" ]; then
      printf '%s\n' "ERROR: Detected '--config' or '-K' argument. The uwt curl wrapper cannot handle this. Please use '${curl_binary}' instead." 1>&2
    else
      printf '%s\n' "ERROR: Detected '--config' or '-K' argument. The uwt curl wrapper cannot handle this." 1>&2
    fi
    exit 1
  fi
done

if [ "${disable_stream_isolation}" = 'true' ]; then
  exec /usr/libexec/uwt/uwtwrapper "${curl_args[@]}"
fi

curl_proxy_addr=""
if [ "${found_ipv6_arg}" = 'true' ]; then
   printf '%s\n' "NOTICE: Detected '--ipv6' or '-6' argument, will attempt to use IPv6 Tor proxy." 1>&2
   curl_proxy_addr="[::1]:9050"
else
   curl_proxy_addr="127.0.0.1:9050"
fi

if [ -n "${curl_binary}" ]; then
  if ! "${curl_binary}" "http://${curl_proxy_addr}/" --output /dev/null 2>/dev/null; then
     printf '%s\n' "WARNING: Tor proxy doesn't seem to be functional. Stream isolation will be disabled." 1>&2
     disable_stream_isolation='true'
  fi
fi

if [ "${disable_stream_isolation}" = 'true' ]; then
  exec /usr/libexec/uwt/uwtwrapper "${curl_args[@]}"
fi

## NOTE: Duplicated in package 'helper-scripts' file 'strings.sh'.
random_alpha_numeric() {
  local n="${1:-43}"
  LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/random | head --bytes="$n" || true
  printf '%s\n' ""
}

## https://spec.torproject.org/socks-extensions.html
random_socks_password="$(random_alpha_numeric 43)"
extra_curl_args=(
  '--proxy' "socks5h://${curl_proxy_addr}/"
  '--proxy-user' "<torS0X>0:curl-wrapper_${random_socks_password}"
)

curl_args=( "${extra_curl_args[@]}" "${curl_args[@]}" )

exec /usr/libexec/uwt/uwtwrapper "${curl_args[@]}"
