#!/bin/sh
# This file is part of Epoptes, https://epoptes.org
# Copyright 2010-2018 the Epoptes team, see AUTHORS.
# SPDX-License-Identifier: GPL-3.0-or-later

# epoptes-client is called either from systemd as root, to control the client,
# or from /etc/xdg/autostart as a user, to control the user session.
# Users can cancel that from their System > Preferences > Services gnome menu.

usage() {
    printf "Usage: $0 [-c|-h|-v] [SERVER] [PORT]\n%s" \
'
Connect to a remote server and offer it a local shell.
'
}

version() {
    export VERSION="1.0"  # Automatically updated while packaging
}

die() {
    echo "epoptes-client ERROR: $@" >&2
    exit 1
}

# The "boolean_is_true" name is used as a sentinel that prevents ltsp_config
# from sourcing ltsp_common_functions. So we're using a different name.
my_boolean_is_true() {
    case "$1" in
       # match all cases of true|y|yes
       [Tt][Rr][Uu][Ee]|[Yy]|[Yy][Ee][Ss]) return 0 ;;
       *) return 1 ;;
    esac
}

# Return true if we're in a chroot.
chrooted() {
    # The result is cached in a variable with the same name as the function :P
    test -n "$chrooted" && return "$chrooted"
    test -n "$UID" || UID=$(id -u)
    if [ "$UID" -gt 0 ]; then
        chrooted=1
    elif [ "$(stat -c %d/%i /)" = "$(stat -Lc %d/%i /proc/1/root 2>/dev/null)" ]
    then
        # the devicenumber/inode pair of / is the same as that of /sbin/init's
        # root, so we're *not* in a chroot and hence return false.
        chrooted=1
    else
        chrooted=0
    fi
    return "$chrooted"
}

# Get $UID and $TYPE of the client, and the default $SERVER and $PORT.
basic_info() {
    test -n "$UID" || UID=$(id -u)

    # We temporarily need LTSP_CLIENT and LTSP_FATCLIENT to decide TYPE.
    # Unfortunately, when epoptes-client is ran as a system service, they're
    # not in our environment, and we need to source ltsp_config.
    # But we don't want to pollute the environment with any of its other vars.
    if [ "$UID" -eq 0 ] && [ -f /usr/share/ltsp/ltsp_config ] && ! chrooted &&
        egrep -qs 'ltsp|nfs|nbd' /proc/cmdline
    then
        export $(
            . /usr/share/ltsp/ltsp_config >/dev/null
            echo "LTSP_CLIENT=$LTSP_CLIENT"
            echo "LTSP_FATCLIENT=$LTSP_FATCLIENT"
            echo "EPOPTES_CLIENT_VERIFY_CERTIFICATE=$EPOPTES_CLIENT_VERIFY_CERTIFICATE")
        # LTSP_CLIENT may not be available in system sesssions, if so fake it
        LTSP_CLIENT=${LTSP_CLIENT:-127.0.0.1}
    fi

    # LTSP_FATCLIENT may not be available in user sessions, autodetect it
    if [ -n "$LTSP_CLIENT" ] && [ -z "$LTSP_FATCLIENT" ] &&
        [ "$UID" -gt 0 ] && [ -x /usr/bin/getltscfg ] &&
        egrep -qs 'ltsp|nfs|nbd' /proc/cmdline
    then
        LTSP_FATCLIENT=True
    fi

    if my_boolean_is_true "$LTSP_FATCLIENT"; then
        TYPE="fat"
    elif [ -n "$LTSP_CLIENT" ]; then
        TYPE="thin"
    else
        TYPE="standalone"
    fi

    if ( [ "$TYPE" = "thin" ] && [ "$UID" -gt 0 ] ) || chrooted; then
        SERVER=localhost
    else
        SERVER=server
    fi
    PORT=789

    export UID TYPE SERVER PORT
}

fetch_certificate()
{
    test "$UID" -eq 0 || die "Need to be root to fetch the certificate"
    mkdir -p /etc/epoptes
    openssl s_client -connect $SERVER:$PORT < /dev/null \
        | sed '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/!d' \
        > /etc/epoptes/server.crt
    if [ -s /etc/epoptes/server.crt ]; then
        echo "Successfully fetched certificate from $SERVER:$PORT"
        exit 0
    else
        die "Failed to fetch certificate from $SERVER:$PORT"
    fi
}

wait_for_dns() {
    # Don't wait for DNS if SERVER is an IP
    if [ -z "$(echo "$SERVER" | tr -d '[0-9.]')" ]; then
        server_ip=$SERVER
        return
    fi
    delay=60
    while true; do
        server_ip=$(getent hosts "$SERVER" | awk '{ print $1; exit }')
        test -n "$server_ip" && break
        # If DNS is not up yet, retry, otherwise abort
        server_ip=$(getent hosts "ntp.org" | awk '{ print $1; exit }')
        if [ -z "$server_ip" ]; then
            echo "Cannot resolve $SERVER, postponing for $delay seconds..."
            sleep $delay
            # Add a minute for each failure to minimize log spamming
            delay=$(($delay+60))
        else
            echo "Cannot resolve $SERVER while DNS appears to work, aborting."
            exit 1
        fi
    done
}

apply_wol() {
    if [ "$UID" -eq 0 ] && [ -n "$WOL" ] && [ -x /sbin/ethtool ] &&
        [ "$EPOPTES_CLIENT_APPLIED_WOL" != True ]
    then
        export EPOPTES_CLIENT_APPLIED_WOL=True
        # Copied from client-functions, these map from server_ip to def_iface
        read def_iface IP <<EOF
$(ip route get "$server_ip" | sed -n 's/.*dev *\([^ ]*\).*src *\([^ ]*\).*/\1 \2/p')
EOF
        test "${def_iface:-lo}" != "lo" || read def_iface IP <<EOF
$(ip route get 192.168.67.0 | sed -n 's/.*dev *\([^ ]*\).*src *\([^ ]*\).*/\1 \2/p')
EOF
        if [ "${def_iface:-lo}" != "lo" ]; then
            # Only set WOL if def_iface is both != '' and != 'lo'.
            echo "Setting WOL=$WOL for $def_iface"
            ethtool -s "$def_iface" wol $WOL
        fi
    fi
}


# Main.
version

# Check the first parameter as it may turn out we don't need to run at all
case "$1" in
    -v|--version)
        echo "$VERSION"
        exit
        ;;
    -h|--help)
        if [ -x /usr/bin/man ]; then
            exec man epoptes-client
        else
            usage
            exit
        fi
        ;;
    -c|--certificate)
        need_certificate=true
        shift
        ;;
esac

# Set a reasonable PATH to execute commands or to relaunch epoptes-client.
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"

# When launched as a service, LANG might not be set.
if [ -z "$LANG" ] && [ -r /etc/default/locale ]; then
    . /etc/default/locale
    export LANG
fi

basic_info
# The configuration file overrides the default values
if [ -f /etc/default/epoptes-client ]; then
    . /etc/default/epoptes-client
fi
# And the command line parameters override the configuration file
export SERVER=${1:-$SERVER}
export PORT=${2:-$PORT}

# Provide an easy way to fetch the server certificate
test -n "$need_certificate" && fetch_certificate

# We don't want the epoptes-client system service running on the epoptes server
if ( [ $UID -eq 0 ] && [ $TYPE = "standalone" ] && [ -x /usr/bin/epoptes ] ) ||
    chrooted
then
    exit 0
fi

# Go to the scripts directory, so that we can run them with ./xxx
cd $(dirname "$0")
if [ -d ../epoptes-client ]; then
    cd ../epoptes-client
else
    cd /usr/share/epoptes-client
fi

wait_for_dns
apply_wol
printf "Epoptes-client connecting to $SERVER:$PORT..."

# Call chain:
#  * systemd executes /usr/sbin/epoptes-client
#  * then socat is called
#  * after a successful connection, socat exec's /bin/sh
#  * and the daemon sends /usr/share/epoptes/client-functions to that shell

# Kill all ghost instances of epoptes-client of the same user.
# The current epoptes-client is excluded because it starts with /bin/sh.
pkill -QUIT -U "$UID" -f '^epoptes-client$'

# Remember the stdout descriptor to use it in the second phase.
# stdio will be redirected to the server, but stderr will be kept in the
# local console, to avoid possible noise from applications started in the
# background.
# If the callee needs to grab stderr, it can use `cmd 2>&1`.
exec 5>&1

# Bash supports launching a program with a different zeroth argument,
# this makes pgrep'ing for epoptes-client easier.
cmdline='bash -c \"exec -a epoptes-client sh\"'

# Offer an lts.conf (or environment) variable to disable cert verification.
if my_boolean_is_true "${EPOPTES_CLIENT_VERIFY_CERTIFICATE:-True}"; then
    cert_param="cafile=/etc/epoptes/server.crt"
    # Check if socat provides the commonname option and use it in order to
    # support certificates which don't have a commonname set
    if socat -hhh | grep -q openssl-commonname; then
        cert_param="$cert_param,commonname=\"\""
    fi
else
    cert_param="verify=0"
fi

# Connect to the server, or keep retrying until the server gets online
# (for standalone workstations booted before the server).
if [ -s /etc/epoptes/server.crt ] || [ "$cert_param" = "verify=0" ]; then
    exec socat -T 60 openssl-connect:$SERVER:$PORT,$cert_param,interval=60,forever EXEC:"$cmdline"
elif [ -f /etc/epoptes/server.crt ]; then
   exec socat tcp:$SERVER:$PORT,interval=60,forever EXEC:"$cmdline",nofork
else
    die "
The epoptes certificate file, /etc/epoptes/server.crt, doesn't exist.
You can fetch the server certificate by running:
$0 -c"
fi
