#!/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 and apps
#### workstation_only yes
#### description
## Generates systemd unit files in
## <code>/usr/lib/systemd/system/anon-ws-disable-stacked-tor_autogen_*</code> which
## listen on common local ports used by popular Tor applications such as Tor
## Browser.
##
## Redirect Whonix-Workstation port <code>9050</code> to Whonix-Gateway port
## <code>9050</code> and so forth.
##
## Create a unix domain socket files such as
## /run/anon-ws-disable-stacked-tor/127.0.0.1_9050.sock and forward those
## to $GATEWAY_IP:9150 etc. See also:
## https://forums.whonix.org/t/tbbs-use-of-sockssocket-will-break-whonixs-tor-browser-implementation/19207
##
## system Tor default SocksSocket is /run/tor/socks
## redirect Whonix-Workstation unix domain socket file /run/tor/socks to Whonix-Gateway port 9050
##
## Debian /usr/share/tor/tor-service-defaults-torrc uses '/run/tor/control' Tor ControlSocket
## Redirect Whonix-Workstation unix domain socket file /run/tor/control to Whonix-Gateway port 9051
#### meta end

########
# init #
########

set -e

MYDIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" && pwd )"

## Change to either system or package source code root folder.
cd -- "$MYDIR"
cd -- ..
cd -- ..
cd -- ..

#####################
# built-in defaults #
#####################

## Redirect Whonix-Workstation port 9050 to Whonix-Gateway port 9050 and so forth.
file_port_tuples+=" port#9050:9050 " ## system Tor default SocksPort
file_port_tuples+=" port#9150:9150 " ## Tor Browser Bundle default SocksPort
file_port_tuples+=" port#9051:9051 " ## Tor default ControlPort
file_port_tuples+=" port#9151:9051 " ## Tor Browser Bundle default ControlPort
file_port_tuples+=" port#9102:9102 " ## Thunderbird

file_port_tuples+=" port6#9050:9050 " ## system Tor default SocksPort
file_port_tuples+=" port6#9150:9150 " ## Tor Browser Bundle default SocksPort
file_port_tuples+=" port6#9051:9051 " ## Tor default ControlPort
file_port_tuples+=" port6#9151:9051 " ## Tor Browser Bundle default ControlPort
file_port_tuples+=" port6#9102:9102 " ## Thunderbird

## Create a unix domain socket files such as
## /run/anon-ws-disable-stacked-tor/127.0.0.1_9050.sock and forward those
## to $GATEWAY_IP:9150 etc. See also:
## https://forums.whonix.org/t/tbbs-use-of-sockssocket-will-break-whonixs-tor-browser-implementation/19207
file_port_tuples+=" file#/run/anon-ws-disable-stacked-tor/127.0.0.1_9050.sock:9050 "
file_port_tuples+=" file#/run/anon-ws-disable-stacked-tor/127.0.0.1_9150.sock:9150 "
file_port_tuples+=" file#/run/anon-ws-disable-stacked-tor/127.0.0.1_9051.sock:9051 "
file_port_tuples+=" file#/run/anon-ws-disable-stacked-tor/127.0.0.1_9151.sock:9051 "

file_port_tuples+=" file6#/run/anon-ws-disable-stacked-tor/::1_9050.sock:9050 "
file_port_tuples+=" file6#/run/anon-ws-disable-stacked-tor/::1_9150.sock:9150 "
file_port_tuples+=" file6#/run/anon-ws-disable-stacked-tor/::1_9051.sock:9051 "
file_port_tuples+=" file6#/run/anon-ws-disable-stacked-tor/::1_9151.sock:9051 "

## TODO: IPv6 - How to make IPv6 equivalents of these?
##       Do we even need IPv6 equivalents?
##       If we do, do we need to keep IPv4 variants around?
##         Probably, yes, as long as there is no argument to use IPv6 by default.
##         Keeping IPv4 the default might be more stable code paths.
##
## system Tor default SocksSocket is /run/tor/socks
## redirect Whonix-Workstation unix domain socket file /run/tor/socks to Whonix-Gateway port 9050
file_port_tuples+=" file#/run/tor/socks:9050 "

## Debian /usr/share/tor/tor-service-defaults-torrc uses '/run/tor/control' Tor ControlSocket
## Redirect Whonix-Workstation unix domain socket file /run/tor/control to Whonix-Gateway port 9051
file_port_tuples+=" file#/run/tor/control:9051 "

########################
# parse config folders #
########################

## Process configuration folders. Allows overwriting GATEWAY_IP and GATEWAY_IP6.

shopt -s nullglob
for i in etc/anon-ws-disable-stacked-tor.d/*.conf rw/anon-ws-disable-stacked-tor.d/*.conf /usr/local/etc/anon-ws-disable-stacked-tor.d/*.conf; do
   bash_n_exit_code="0"
   bash_n_output="$(bash -n "$i" 2>&1)" || { bash_n_exit_code="$?" ; true; };
   if [ ! "$bash_n_exit_code" = "0" ]; then
      printf '%s\n' "Invalid config file: $i
bash_n_exit_code: $bash_n_exit_code
bash_n_output:
$bash_n_output" >&2
      exit 1
   fi
   source "$i"
done

#############
# variables #
#############

if [ "$BASH_SOURCE" = "/usr/libexec/anon-ws-disable-stacked-tor/systemd-unit-files-generator" ]; then
   [ -n "$system_run" ] || system_run="true"
   [ -n "$generated_comment" ] || generated_comment="This file was auto generated by $BASH_SOURCE at system runtime."
   if [ "$GATEWAY_IP" = "" ] || [ "$GATEWAY_IP6" = "" ]; then
      ## Provides variable $GATEWAY_IP and $GATEWAY_IP6 so it can be used by `source`d config snippets.
      eval $(/usr/libexec/helper-scripts/settings_echo)
      GATEWAY_IP6="$GATEWAY_IP6"
   fi
else
   [ -n "$system_run" ] || system_run="false"
   script_name_without_leading_dot="$(printf '%s\n' "$BASH_SOURCE" | str_replace "./" "/")"
   [ -n "$generated_comment" ] || generated_comment="This file was auto generated by $script_name_without_leading_dot at package build time."
   if [ "$GATEWAY_IP" = "" ]; then
      ## IP HARDCODED. Set GATEWAY_IP through a drop-in configuration snippet in /etc/anon-ws-disable-stacked-tor.d configuration folder instead.
      GATEWAY_IP="10.152.152.10"
   fi
   if [ "$GATEWAY_IP6" = "" ]; then
      ## IP HARDCODED. Set GATEWAY_IP6 through a drop-in configuration snippet in /etc/anon-ws-disable-stacked-tor.d configuration folder instead.
      GATEWAY_IP6="fd19:c33d:88bc::10"
   fi
fi

#############
# generator #
#############

## Delete previously automatically generated systemd unit files by this script.
safe-rm -f -- usr/lib/systemd/system/anon-ws-disable-stacked-tor_autogen_*

for item in $file_port_tuples ; do
   type_item="${item%#*}"
   port_tuples="${item##*#}"

   source_port="${port_tuples%:*}"
   destination_port="${port_tuples##*:}"

   add_ipv6_condition='false'
   ipv6_condition_maybe=''

   if [ "$type_item" = "port" ]; then
      listen_where="127.0.0.1:$source_port"
      destination_where="${GATEWAY_IP}:${destination_port}"
      file_name="port_${source_port}"

   elif [ "$type_item" = "port6" ]; then
      listen_where="[::1]:$source_port"
      destination_where="${GATEWAY_IP6}:${destination_port}"
      file_name="port6_${source_port}"
      add_ipv6_condition='true'

   elif [ "$type_item" = "file" ]; then
      listen_where="$source_port"
      destination_where="${GATEWAY_IP}:${destination_port}"
      file_name=${source_port//\//\_}

   elif [ "$type_item" = "file6" ]; then
      listen_where="$source_port"
      destination_where="${GATEWAY_IP6}:${destination_port}"
      file_name=${source_port//\//\_}
      add_ipv6_condition='true'

   else
      printf '%s\n' "ERROR: Unrecognized type '$type_item'!"
      continue
   fi

   if [ "${add_ipv6_condition}" = 'true' ]; then
     ipv6_condition_maybe='ConditionPathExists=/run/helper-scripts/ipv6-is-enabled'
   fi

   printf '%s\n' "\
## Copyright (C) 2018 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## $generated_comment

[Unit]
Description=redirect $listen_where to Whonix-Gateway port ${destination_port}
Documentation=https://www.whonix.org/wiki/Dev/anon-ws-disable-stacked-tor
ConditionPathExists=!/run/qubes/this-is-templatevm
After=detect-ipv6-enabled-in-kernel.service
${ipv6_condition_maybe}

[Socket]
ListenStream=$listen_where
SocketUser=debian-tor
SocketMode=666
DirectoryMode=2755

[Install]
WantedBy=sockets.target" > "./usr/lib/systemd/system/anon-ws-disable-stacked-tor_autogen_${file_name}.socket"

   printf '%s\n' "\
## Copyright (C) 2018 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## $generated_comment

[Unit]
## IP HARDCODED. See next comment below.
Description=redirect $listen_where to Whonix-Gateway port ${destination_port}
Documentation=https://www.whonix.org/wiki/Dev/anon-ws-disable-stacked-tor
ConditionPathExists=!/run/qubes/this-is-templatevm
After=detect-ipv6-enabled-in-kernel.service
${ipv6_condition_maybe}
## IP HARDCODED. See next comment below.
After=anon-ws-disable-stacked-tor_autogen_${file_name}.socket
Requires=anon-ws-disable-stacked-tor_autogen_${file_name}.socket

[Service]
## IP HARDCODED. If you want to change IP, set variable GATEWAY_IP / GATEWAY_IP6
## through a drop-in configuration snippet in /etc/anon-ws-disable-stacked-tor.d
## configuration folder instead.
## Then run the systemd-socket-proxyd systemd-unit-files-generator.
#sudo /usr/libexec/anon-ws-disable-stacked-tor/systemd-unit-files-generator
## See Documentation:
## https://www.whonix.org/wiki/Redirect_Whonix-Workstation_Ports_or_Unix_Domain_Socket_Files_to_Whonix-Gateway
ExecStart=/usr/lib/systemd/systemd-socket-proxyd $destination_where
PrivateTmp=yes" > "./usr/lib/systemd/system/anon-ws-disable-stacked-tor_autogen_${file_name}.service"

   if [ "$system_run" = "true" ]; then
      systemctl --system daemon-reload
      systemctl enable "anon-ws-disable-stacked-tor_autogen_${file_name}.socket"

      ## 'restart' rather than 'start' required to support running this script
      ## during runtime due to the following.
      ##
      ## Jun 06 05:38:49 host systemd[1]: anon-ws-disable-stacked-tor_autogen__var_run_tor_socks.socket: Socket unit configuration has changed while unit has been running, no open socket file descriptor left. The socket unit is not functional until restarted.
      systemctl restart "anon-ws-disable-stacked-tor_autogen_${file_name}.socket"
   fi

done
