#!/bin/bash

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

## usage:
## sudo virtport=80 hsport=80 hsname=hidden_service client=1 anon-auth-autogen

#set -x

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

# shellcheck source=../../../../kicksecure/helper-scripts/usr/libexec/helper-scripts/has.sh
## Sibling repo: absent in an isolated CI checkout, so shellcheck cannot
## follow it there. The source= path above still documents where it lives.
##
## TODO: Don't run shellcheck unless in a derivative-maker source tree.
# shellcheck disable=SC1091
source /usr/libexec/helper-scripts/has.sh

# shellcheck source=../../../../kicksecure/helper-scripts/usr/libexec/helper-scripts/strings.bsh
source /usr/libexec/helper-scripts/strings.bsh

error_handler() {
   local exit_code="$?"
   printf '%s\n' "ERROR: exit_code: ${exit_code} | BASH_COMMAND: ${BASH_COMMAND}"
   exit 1
}

trap error_handler ERR

SCRIPTNAME="$(basename "${BASH_SOURCE[0]}")"

if [ "$(id -u)" != "0" ]; then
    printf '%s\n' "ERROR: This must be run as root (sudo)!"
    printf '%s\n' "INFO: You can start ${SCRIPTNAME} by entering..."
    printf '%s\n' "      sudo ${SCRIPTNAME}"
    exit 1
fi

default_if_empty hsname "hidden_service"

default_if_empty hsport "80"

default_if_empty virtport "80"

default_if_empty hsversion "3"

default_if_empty tor_autogen_root_folder "/var/lib/tor_autogen"
default_if_empty tor_autogen_hs_folder "/var/lib/tor_autogen/${hsname}"

default_if_empty hsdir "/var/lib/tor/${hsname}"
default_if_empty onion_url_file "${hsdir}/hostname"
default_if_empty authorized_clients_folder "${hsdir}/authorized_clients"

default_if_empty torconfdir "/usr/local/etc/torrc.d"

default_if_empty torconffile "${torconfdir}/43_${hsname}_hs_autogen.conf"

default_if_empty torunit "tor@default"

default_if_empty unitaction "reload"
default_if_empty unitruntest "is-active"

default_if_empty unittool "systemctl"

default_if_empty unitcmd "${unittool} ${unitaction} ${torunit}"

default_if_empty sleep_seconds_after_reload "5"

default_if_empty unitruntestcmd "${unittool} ${unitruntest} ${torunit}"

default_if_empty tor_user "debian-tor"
default_if_empty tor_group "debian-tor"

default_if_empty client "1"

default_if_empty private_key_file "${tor_autogen_hs_folder}/${client}_private_key.pem"
default_if_empty public_key_file "${tor_autogen_hs_folder}/${client}_public_key.pem"

default_if_empty home_folder_auth_private_file "/home/user/${client}.auth_private"

default_if_empty auth_private_file "${tor_autogen_hs_folder}/${client}.auth_private"

default_if_empty base_32_private "${tor_autogen_hs_folder}/${client}_private_key.base32"
default_if_empty base_32_public "${tor_autogen_hs_folder}/${client}_public_key.base32"

default_if_empty client_authorization_file_name "${client}.auth"
default_if_empty client_authorization_full_path "${tor_autogen_hs_folder}/${client_authorization_file_name}"

default_if_empty tor_user_sudo "sudo --non-interactive -u ${tor_user}"

default_if_empty ip ""

if [ "${ip}" = "" ]; then
   if has qubesdb-read ; then
      ip="$(qubesdb-read /qubes-ip)"
   else
      ip=10.152.152.11
   fi
fi

has basez tail tr cat grep tee openssl mkdir chmod chown cp sudo id groups "${unittool}" sleep

id "${tor_user}" >/dev/null
groups "${tor_group}" >/dev/null

if ! ${unitruntestcmd} &>/dev/null ; then
   printf '%s\n' "ERROR: Tor is not running. Start Tor first."
   exit 1
fi

test -d "${torconfdir}"
safe-rm -f -- "${torconffile}"
touch "${torconffile}"

printf '%s\n' "\
# This file is generated by: $0
# User configuration should go to /usr/local/etc/torrc.d/50_user.conf, not here.
# However, deleting this file will be fine since a new plain file will be generated the next time you run ${SCRIPTNAME}

HiddenServiceDir ${hsdir}
HiddenServicePort ${virtport} ${ip}:${hsport}
HiddenServiceVersion ${hsversion}
" | tee "${torconffile}" >/dev/null

printf '%s\n' "INFO: Created torconffile '${torconffile}'."
printf '%s\n' "INFO: Reloading Tor."

## Reload Tor to so Tor will create onion_url_file.
## by default:
## systemctl reload tor@default
${unitcmd}

printf '%s\n' "INFO: Giving Tor ${sleep_seconds_after_reload} seconds to create hidden service file."

sleep "${sleep_seconds_after_reload}"

onion_url="$(cat "${onion_url_file}")"

onionname="$(printf '%s\n' "${onion_url}" | str_replace ".onion" "")"

mkdir -p "${tor_autogen_root_folder}"
chown "${tor_user}:${tor_group}" "${tor_autogen_root_folder}"

${tor_user_sudo} mkdir -p "${tor_autogen_hs_folder}"

## Lock down permissions on existing private key files.
shopt -s nullglob
for existing_private_key_file in \
   "${tor_autogen_hs_folder}"/*_private_key.pem \
   "${tor_autogen_hs_folder}"/*_private_key.base32 \
   "${tor_autogen_hs_folder}"/*.auth_private ; do
   [ -e "${existing_private_key_file}" ] || continue
   chmod 0600 -- "${existing_private_key_file}"
done
shopt -u nullglob

pushd "${tor_autogen_hs_folder}" >/dev/null

## Based on:
## https://tor.stackexchange.com/questions/19221/how-to-setup-client-authorization-for-v3-onion-services

## Using OpenSSL 1.1 or later, generate a new X25519 private key.
## This will produce a PEM-encoded private key file, private-key.pem
${tor_user_sudo} openssl genpkey -algorithm x25519 -out "${private_key_file}"
chmod 0600 -- "${private_key_file}"

## Using the newly generated private key file, generate a corresponding public key file, public-key.pem:
${tor_user_sudo} openssl pkey -in "${private_key_file}" -pubout -outform PEM -out "${public_key_file}"

## Now that you have both the private and public parts of your keypair,
## first convert the private part from its PEM-encoded format into a Base32
## encoded string for use in your Tor client's .auth_private file:
grep -v " PRIVATE KEY" < "${private_key_file}" | \
   basez --base64pem --decode | \
   tail --bytes 32 | \
   basez --base32 | \
   tr -d '=' | \
   ${tor_user_sudo} tee "${base_32_private}" >/dev/null
chmod 0600 -- "${base_32_private}"

## Visitors need to be provided with.
printf '%s' "${onionname}:descriptor:x25519:" | \
   cat - "${base_32_private}" | \
   ${tor_user_sudo} tee "${auth_private_file}" >/dev/null
chmod 0600 -- "${auth_private_file}"

grep -v " PUBLIC KEY" < "${public_key_file}" | \
   basez --base64pem --decode | \
   tail --bytes 32 | \
   basez --base32 | \
   tr -d '=' | \
   ${tor_user_sudo} tee "${base_32_public}" >/dev/null

printf '%s' "descriptor:x25519:" | \
   cat - "${base_32_public}" | \
   ${tor_user_sudo} tee "${client_authorization_full_path}" >/dev/null

${tor_user_sudo} mkdir -p "${authorized_clients_folder}"

${tor_user_sudo} cp "${client_authorization_full_path}" "${authorized_clients_folder}/"

printf '%s\n' "INFO: Installed \".auth\" file (public key) '${client_authorization_full_path}' to '${client_authorization_full_path}' to allow client '${client}' to access hsname '${hsname}' onion_url '${onion_url}'."

printf '%s\n' "INFO: Reloading Tor again to activate \".auth\" (public key) file for client '${client}'."

## Reload Tor to so Tor will load client_authorization_full_path.
## by default:
## systemctl reload tor@default
${unitcmd}

printf '%s\n' "INFO: You need to provide client '${client}' with \".auth_private\" file (private key) '${auth_private_file}'."

printf '%s\n' "INFO: Visitors that use Whonix could store '${auth_private_file}' in '${home_folder_auth_private_file}' and then run 'sudo sourcefile=${home_folder_auth_private_file} anon-server-to-client-install'."
