#!/usr/bin/bash

set -e

vm_name="$2"
if [ "$vm_name" = "" ]; then
    vm_name="sys-whonix"
fi

dom0_check_dependencies() {
   ## Packaging should make sure these packages are installed in dom0 before proceeding.
   #sudo qubes-dom0-update torsocks socat openssh-server x2goserver

   command -v torsocks >/dev/null || { echo "ERROR: missing torsocks" >&2 ; exit 101; };
   command -v socat >/dev/null || { echo "ERROR: missing socat" >&2 ; exit 102; };
   command -v sshd >/dev/null || { echo "ERROR: missing sshd" >&2 ; exit 103; };

   ## x2goserver is optional.
}

no_root_check() {
    if [ "$(id -u)" = "0" ]; then
        echo "ERROR: Do not run $0 as root / with sudo!" >&2
        exit 100
    fi
}

start_info() {
   echo "INFO: Starting Qubes Remote Support Receiver."
   echo "INFO: This tool is supposed to be run by those who wish to receive remote support."
   echo "INFO: Setting up... This will take a moment..."
}

dom0_copy_from_vm() {
    qvm-run --user root --pass-io "$1" "cat $2" > "$3"
}

preparation() {
    temp_dir="$(mktemp --directory)"
    mkdir -p "$temp_dir/keys"
    mkdir -p ~/.qubes-remote-support
}

colors() {
   if [ "$TERM" = "" ]; then
      return 0
   fi

   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/037
   ## Variables for terminal requests.
   [[ -t 2 ]] && {
       export alt=$(      tput smcup  || tput ti      ) # Start alt display
       export ealt=$(     tput rmcup  || tput te      ) # End   alt display
       export hide=$(     tput civis  || tput vi      ) # Hide cursor
       export show=$(     tput cnorm  || tput ve      ) # Show cursor
       export save=$(     tput sc                     ) # Save cursor
       export load=$(     tput rc                     ) # Load cursor
       export bold=$(     tput bold   || tput md      ) # Start bold
       export stout=$(    tput smso   || tput so      ) # Start stand-out
       export estout=$(   tput rmso   || tput se      ) # End stand-out
       export under=$(    tput smul   || tput us      ) # Start underline
       export eunder=$(   tput rmul   || tput ue      ) # End   underline
       export reset=$(    tput sgr0   || tput me      ) # Reset cursor
       export blink=$(    tput blink  || tput mb      ) # Start blinking
       export italic=$(   tput sitm   || tput ZH      ) # Start italic
       export eitalic=$(  tput ritm   || tput ZR      ) # End   italic
   [[ $TERM != *-m ]] && {
       export red=$(      tput setaf 1|| tput AF 1    )
       export green=$(    tput setaf 2|| tput AF 2    )
       export yellow=$(   tput setaf 3|| tput AF 3    )
       export blue=$(     tput setaf 4|| tput AF 4    )
       export magenta=$(  tput setaf 5|| tput AF 5    )
       export cyan=$(     tput setaf 6|| tput AF 6    )
   }
       export white=$(    tput setaf 7|| tput AF 7    )
       export default=$(  tput op                     )
       export eed=$(      tput ed     || tput cd      )   # Erase to end of display
       export eel=$(      tput el     || tput ce      )   # Erase to end of line
       export ebl=$(      tput el1    || tput cb      )   # Erase to beginning of line
       export ewl=$eel$ebl                                # Erase whole line
       export draw=$(     tput -S <<< '   enacs
                                   smacs
                                   acsc
                                   rmacs' || { \
                   tput eA; tput as;
                   tput ac; tput ae;         } )   # Drawing characters
       export back=$'\b'
   } 2>/dev/null ||:
}

dom0_exit_handler() {
   systemctl --user stop socat-9050 &>/dev/null || true
   systemctl --user reset-failed socat-9050 &>/dev/null || true
}

dom0_run_socat() {
   ## Running this function early to allow socat to be launched into the background.

   ## By Qubes default, dom0 is non-networked.
   ## Use socat in combination with torsocks to allow running wormhole directly
   ## from Qubes dom0. This is to make VMs untrusted. I.e. a compromised VM
   ## should not get access to dom0 SSH server keys.
   ## In other words, allow running "torsocks wormhole" in dom0.
   ## (As executed by qubes-remote-support-receiver-wormhole-helper.)
   ##
   ## Listen with socat on port 9050.
   ## (9050 is the default Tor listening port. I.e. if Tor was installed, it would
   ## listen on that port. This is being simulated.)
   ## torsocks with its default configuration will use port 9050.
   ##
   ## We could use any dom0 port other than 9050.
   ## For that we would have to use something like this:
   ## TORSOCKS_CONF_FILE=/path/to/qubes-remote-support-torsocks-config torsocks
   ## But since nothing should be listening on port 9050 in dom0 the choice of
   ## this port is appropriate at a small expense of breakage if Tor was
   ## installed in dom0 which really should not be the case in dom0 and
   ## certainty is not the case in default configuration.
   ##
   ## Port number 9122 is an arbitrarily chosen unused Tor SocksPort inside Whonix-Gateway.
   ##
   ## Using bash with 'program-name &' followed by 'pid=$!' and writing that to a pid file
   ## is prone to race conditions. To avoid having to manually maintain a state, a pid file,
   ## systemd-run is being used.

   ## Terminate possibly running leftover processes from a previous run in case a previous
   ## run of this script was terminated with sigkill thorugh a kernel OOM due to an unrelated
   ## issue. This is to avoid race conditions.
   systemctl --user stop socat-9050 &>/dev/null || true
   systemctl --user reset-failed socat-9050 &>/dev/null || true

   ## Suppress output "Running as unit: socat-9050.service" to avoid confusing Qubes remote support gui.
   systemd-run --user --unit socat-9050 socat TCP-LISTEN:9050,reuseaddr,fork "EXEC:qrexec-client -d $vm_name DEFAULT\\:\\'\\''/etc/qubes-rpc/qubes.ConnectTCP 9122\\'\\''" &>/dev/null

   systemctl --user --no-pager --no-block status socat-9050 >/dev/null

   ## socat TCP-LISTEN:9050,reuseaddr,fork 'EXEC:qrexec-client -d sys-whonix user\:\'\''/etc/qubes-rpc/qubes.ConnectTCP 9122\'\'''

   ## Give socat sufficient time to set up listener.
   ## Hardcoded wait.A non-hardcoded, event based a protocol where socat signals readiness similar to
   ## systemd-notify would be overly complex.
   sleep 2

   ## Symptom, shell output if socat is already listening:
   ## 2020/09/30 06:50:40 socat[4168765] E bind(5, {AF=2 0.0.0.0:9050}, 16): Address already in use
}

dom0_rpc_policy_setup() {
    local append_string

    if ! test -d /etc/qubes-rpc/policy ; then
        sudo --non-interactive mkdir -p /etc/qubes-rpc/policy
    fi

    if ! test -f /etc/qubes-rpc/policy/qubes.ConnectTCP+22 ; then
        sudo --non-interactive touch /etc/qubes-rpc/policy/qubes.ConnectTCP+22
    fi

    test -r /etc/qubes-rpc/policy/qubes.ConnectTCP+22

    append_string="$vm_name dom0 allow,target=dom0"
    if grep --quiet "$append_string" /etc/qubes-rpc/policy/qubes.ConnectTCP+22 ; then
        true "INFO: /etc/qubes-rpc/policy/qubes.ConnectTCP+22 added modified."
    else
        echo "$append_string" | sudo --non-interactive tee --append /etc/qubes-rpc/policy/qubes.ConnectTCP+22 >/dev/null
    fi

    ## Debugging.
    #cat /etc/qubes-rpc/policy/qubes.ConnectTCP+22
}

dom0_sshd_hardening() {
    local old new file_name
    if ! test -e /etc/ssh/sshd_config ; then
        echo "ERROR: file /etc/ssh/sshd_config does not exist!" >&2
        exit 1
    fi
    ## dom0 minimal sshd configuration hardening
    old="PasswordAuthentication yes"
    new="PasswordAuthentication no"
    file_name="/etc/ssh/sshd_config"
    sudo --non-interactive sed -i "s/$old/$new/g" "$file_name"
}

check_vm_installed() {
    qvm-check "$vm_name" 2>/dev/null
}

start_vm() {
    if qvm-check --running "$vm_name" 2>/dev/null ; then
        true "INFO: already running, ok."
    else
        ## Would exit non-zero if already running.
        true "INFO: Not yet running, starting..."
        qvm-start "$vm_name" 2>/dev/null
    fi
}

vm_setup() {
    ## --pass-io is optional but useful for gathering debug output.

    qvm-run --user root --pass-io "$vm_name" "systemctl start tor" >/dev/null

    qvm-run --user root --pass-io "$vm_name" "systemctl restart qubes-whonix-remote-support.service" >/dev/null

    qvm-run --user root --pass-io "$vm_name" "systemctl --no-pager --no-block status qubes-whonix-remote-support.service" >/dev/null

    qvm-run --user root --pass-io "$vm_name" "virtport=22 hsport=22 hsname=remote_support client=1 anon-auth-autogen" >/dev/null
}

dom0_get_tor_auth_private() {
    dom0_copy_from_vm "$vm_name" /var/lib/tor_autogen/remote_support/1.auth_private "$temp_dir/keys/1.auth_private"

    ## Debugging.
    #cat "$temp_dir/keys/1.auth_private"

    dom0_copy_from_vm "$vm_name" /var/lib/tor/remote_support/hostname "$temp_dir/keys/hostname"

    ## Debugging.
    #cat "$temp_dir/keys/hostname"
}

dom0_sshd_setup() {
    local append_string
    local one two three
    local ssh_keyscan_output ssh_algorithm ssh_fingerprint

    ## Generate SSH private key in dom0 of remote-support-receiver.
    ## untrusted sys-whonix: Goal is sys-whonix to never have access to SSH private key.
    ## - '-N ""' key without passphrase.
    ## Creates files:
    ## - $temp_dir/keys/id_ed25519
    ## - $temp_dir/keys/id_ed25519.pub
    ssh-keygen -t ed25519 -f "$temp_dir/keys/id_ed25519" -N "" -C "qubes-remote-support-receiver-auto-generated" >/dev/null

    mkdir -p ~/.ssh
    sudo --non-interactive chmod 700 ~/.ssh
    touch ~/.ssh/authorized_keys
    sudo --non-interactive chmod 600 ~/.ssh/*

    cat < "$temp_dir/keys/id_ed25519.pub" >> ~/.ssh/authorized_keys

    ## systemctl enable not required.

    if sudo --non-interactive systemctl --no-pager --no-block status sshd.service >/dev/null ; then
        true "INFO: sshd is already running."
        ## Adding a key to ~/.ssh/authorized_keys does not require a reload or
        ## even restart of the sshd service.
        touch ~/.qubes-remote-support/sshd_was_already_running.status
    else
        true "INFO: sshd was not yet started. Starting sshd..."
        rm -f ~/.qubes-remote-support/sshd_was_already_running.status
        sudo --non-interactive systemctl restart sshd.service >/dev/null
        true "INFO: Started sshd."
    fi

    sudo --non-interactive systemctl --no-pager --no-block status sshd.service >/dev/null
}

dom0_sshd_fingerprint() {
    ## 2>/dev/null to hide stderr "# 127.0.0.1:22 SSH-2.0-OpenSSH_7.4"
    ssh_keyscan_output="$(ssh-keyscan -t ed25519 -H 127.0.0.1 2>/dev/null)"
    ## example output stderr:
    ## # 127.0.0.1:22 SSH-2.0-OpenSSH_7.4
    ## example output stdout:
    ## |1|5kanhyz0x+i5x+8OE2uEEOFNhi4=|ewEL3Cc3nu9XzMfzb3knbgwxp7Q= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJgOxh9EHKZc7JPKSkkoRjrfqBCWRXZQeeP3mll8yZw1
    ##
    ## the format is
    ## hashed-hostname algorithm fingerprint
    ##
    ## We cannot use the hashed hostname as dom0 is "unaware" of its onion hostname.
    ## dom0 cannot reach the onion hostname using ssh ssh-keyscan.
    ## And if that was possible, it should not. Fingerprint should be securely obtained from localhost.
    read -r one two three <<< "$ssh_keyscan_output" || { echo "ERROR: parsing error in ssh_keyscan_output: '$ssh_keyscan_output'" >&2 ; true ;};

    ssh_algorithm="$two"
    ssh_fingerprint="$three"

    echo "$ssh_algorithm"  > "$temp_dir/keys/ssh_algorithm"
    echo "$ssh_fingerprint" > "$temp_dir/keys/ssh_fingerprint"

    ## output of ssh-keygen not suitable to be added to ~/.ssh/known_hosts
    ## on remote-support-provider.
    #ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub
    ## Example output:
    ## 256 SHA256:ope57TCAoeTn4+ORmL21Hq50Uy9244M9VK2LEK0nJQ0 no comment (ECDSA)
    ##
    ## Could not figure out to use this with known_hosts file.

    ## Debugging.
    #cat "$temp_dir/keys/ssh_algorithm"
    #cat "$temp_dir/keys/ssh_fingerprint"
}

dom0_x2go_setup() {
    sudo --non-interactive x2godbadmin --createdb >/dev/null
    ## systemctl enable not required.
    #sudo --non-interactive systemctl enable x2gocleansessions.service >/dev/null
    sudo --non-interactive systemctl restart x2gocleansessions.service >/dev/null
    sudo --non-interactive systemctl --no-pager --no-block status x2gocleansessions.service >/dev/null
}

dom0_create_key_files_archive() {
    tar -zcf "$temp_dir/remote-support-keys.tar.gz" -C "$temp_dir" keys
}

wormhole_send_wrapper() {
    test -r "$temp_dir/remote-support-keys.tar.gz"

    echo "INFO: Remote support archive file '$temp_dir/remote-support-keys.tar.gz' was successfully created."
    echo "INFO: (That file allows a Qubes Remote Support Provider to connect to this machine.)"
    echo "INFO: (No need to do anything with that file.)"
    echo "INFO: Starting the file transfer tool magic-wormhole... This will take a moment..."

    local found
    found=no

    while read -r line ; do
        true "INFO: line: $line"
        if [ "$line" = "" ]; then
            ## Shorter xtrace.
            continue
        fi
        read -r first second third _ <<< "$line" || { echo "ERROR: parsing error in line: '$line'" >&2 ; true ;};
        true "first: $first"
        true "second: $second"
        true "third: $third"
        if [ "$first" = "wormhole" ]; then
            if [ "$second" = "receive" ]; then
                echo "INFO: File transfer too magic-wormhole successfully started."
                echo "INFO: The next line will show a wormhole code phrase. Send the code to the Qubes Remote Support Provider. Once the the Qubes Remote Support Provider acknowledged receipt, just wait."
                true "${green}${bold}INFO: next line is wormhole code.${reset}"
                echo "wormhole_code: $third"
                found=yes
            fi
        fi

    ## wormhole writes to stderr, hence we must use 2>&1 to redirect to stdout,
    ## so bash's "while read" can access it.
    done < <( qubes-remote-support-receiver-wormhole-helper "$temp_dir/remote-support-keys.tar.gz" 2>&1 )

    if [ "$found" = "no" ]; then
        echo "${red}${bold}ERROR: wormhole issue.${reset}" >&2
        return 0
    fi

    echo "INFO: The Qubes Remote Support Provider successfully received the remote support archive file and is now able to establish remote support."
    echo "INFO: This can take up to 10 minutes."
    echo ""
    echo "INFO: Should you wish to stop this Qubes Remote Support Session, please run:"
    echo "qubes-remote-support-receiver-stop"
    echo ""
    echo "INFO: This tool will exit now and there is nothing else to do besides waiting."
    echo "INFO: Success."
}

no_root_check
start_info
dom0_check_dependencies
preparation
colors
trap dom0_exit_handler EXIT
dom0_run_socat
dom0_rpc_policy_setup
dom0_sshd_hardening
check_vm_installed
start_vm
vm_setup
dom0_get_tor_auth_private
dom0_sshd_setup
dom0_sshd_fingerprint
dom0_x2go_setup
dom0_create_key_files_archive
wormhole_send_wrapper

true "INFO: Success."
