#!/bin/bash

###########################################################################
# Connects to a remote server and offers it a local shell.
# Usage: epoptes [server] [port]
#
# Copyright (C) 2010, 2011 Alkis Georgopoulos <alkisg@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# On Debian GNU/Linux systems, the complete text of the GNU General
# Public License can be found in `/usr/share/common-licenses/GPL'.
###########################################################################

# epoptes-client may be called either as root, to control the client, or as a
# user, to control the user session.
# As root, epoptes-client starts from if-up.d on standalone clients.
# Unfortunately, thin and fat clients don't get if-up.d events, so just for
# this case we're using a helper sysvinit script.
# As a user, epoptes-client runs from /etc/xdg/autostart.
# Users can cancel that from their System > Preferences > Services gnome menu.

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_function. 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
}

# Get $USER, $UID and $TYPE of the client, and the default $SERVER and $PORT.
basic_info() {
    test -n "$USER" || export USER=$(whoami)
    test -n "$UID" || export UID=$(id -u)
    test -n "$HOME" || export HOME=$(getent passwd "$UID" | cut -d: -f6)

    # TODO: fix upstream "$LTSP_FATCLIENT" to be present in user sessions
    if my_boolean_is_true "$LTSP_FATCLIENT" && [ -f /etc/ltsp_fat_chroot ]; then
        TYPE="fat"
    elif [ -n "$LTSP_CLIENT" ] || [ -f /etc/ltsp_chroot ]; then
        TYPE="thin"
    elif [ -n "$(dpkg-query -W -f '${Version}' epoptes 2>/dev/null)" ]; then
        TYPE="server"
    else
        TYPE="standalone"
    fi

    if [ "$UID" -eq 0 ] || [ "$TYPE" != "thin" ]; then
        SERVER=server
    else
        SERVER=localhost
    fi
    PORT=789
}

epoptes_info() {
    local def_iface

    # TODO: use `ip route get server-ip` instead
    while true; do
        def_iface=$(route -n | sed -n "/^0.0.0.0/s/.* //p")
        def_iface=${def_iface:-$(route -n | awk '$2=="0.0.0.0" { print $8; exit }')}
        # If we got it, continue.
        test -n "$def_iface" && break
        # On dual NIC standalone workstartions, if the "non-default-route"
        # interface is brought up first, exit, we'll be called again later.
        test $UID -eq 0 && die "Empty def_iface, probably 2-NIC workstation, we'll get called again for the second NIC.."
        # At this point, it's probably a user epoptes-client that doesn't have
        # a network connection up yet. Retry until it's ready.
        sleep 10
    done

    HOSTNAME=$(hostname)
    test -n "$HOSTNAME" || die "Empty hostname"
    IP=$(ip -oneline -family inet addr show dev "$def_iface" | sed "s/.* \([0-9.]*\)\/.*/\\1/")
    test -n "$IP" || die "Empty IP"
    MAC=$(ip -oneline -family inet link show dev "$def_iface" | sed "s/.*ether \([^ ]*\).*/\\1/")
    MAC=$(echo "$MAC" | sed 'y/abcdef-/ABCDEF:/;s/[^A-F0-9:]//g')
    test -n "$MAC" || die "Empty MAC"
    CPU=$(cat /proc/cpuinfo | grep "^model name" | head -1 | sed "s/.*: //")
    RAM=$(free -m | grep "^Mem" | awk '{print $2}')
    VGA=$(lspci -nn -m | sed -n -e '/"VGA/s/[^"]* "[^"]*" "[^"]*" "\([^"]*\)" .*/\1/p')

    # If epoptes-client is ran on a thin client from a user account (meaning
    # that it actually runs on the server), then use $LTSP_CLIENT_HOSTNAME,
    # $LTSP_CLIENT and $LTSP_CLIENT_MAC instead of $HOSTNAME, $IP and $MAC.
    # CPU, RAM and VGA are not available in the environment, so we're leaving
    # the ones of the server.
    if [ "$TYPE" = "thin" ] && [ "$UID" -ne 0 ]; then
        test -n "$LTSP_CLIENT" && IP="$LTSP_CLIENT"
        test -n "$LTSP_CLIENT_HOSTNAME" && HOSTNAME="$LTSP_CLIENT_HOSTNAME"
        test -n "$LTSP_CLIENT_MAC" && MAC="$LTSP_CLIENT_MAC"
    fi

    export HOSTNAME IP MAC TYPE USER UID CPU RAM VGA 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
}


# Main.

# When called from /etc/xdg/autostart, /sbin is not in the system path.
PATH="$PATH:/sbin:/usr/sbin"

# Ensure that the LTSP defaults are there (for $LTSP_FATCLIENT etc)
if [ -f /usr/share/ltsp/ltsp_config ]; then
    . /usr/share/ltsp/ltsp_config
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
if [ "$1" = "-c" ]; then
    need_certificate=true
    shift
fi
SERVER=${1:-$SERVER}
PORT=${2:-$PORT}

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

# We don't want epoptes-client root service running on the LTSP server
if [ $UID -eq 0 ] && [ $TYPE = "server" ]; 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

epoptes_info

# Source the lsb init functions, for log_begin_msg / log_end_msg.
# Unfortunately it seems that Centos and Fedora don't have that file.
if [ -f /lib/lsb/init-functions ]; then
    . /lib/lsb/init-functions
else
    alias log_begin_msg=echo
    alias log_warning_msg=echo
    alias log_end_msg=echo
fi

# Epoptes-client is called two times and runs in three phases:
# In the first phase, some initialization is done, and socat is exec'ed.
# In the second phase, socat has successfully connected, and calls
# epoptes-client again to resume execution. Epoptes-client has to go through
# some initialization again, and it finally exec's a plain /bin/sh with
# the stdio descriptors that were inherited from socat.
# That's the third phase, a plain shell. But bash'es `exec -a` is used, so
# that "epoptes-client" is displayed in `ps`, instead of "/bin/sh".
if [ -z "$EPOPTES_CLIENT_PHASE" ]; then
    export EPOPTES_CLIENT_PHASE=1

    # Kill all ghost instances of epoptes-client of the same user.
    # That may happen if network connectivity is lost for a while.
    # Standalone workstations don't hang if the network is down, and nbd might cope
    # with that for LTSP clients, but epoptes kills disconnected epoptes-clients.
    # Exclude the current epoptes-client.
    pkill -U $UID -f '^epoptes-client \+m'

    log_begin_msg "Epoptes-client connecting to $SERVER:$PORT..."

    # 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

    # Connect to the server, or keep retrying until the server gets online
    # (for standalone workstations booted before the server).
    export EPOPTES_CLIENT_PHASE=2
    if [ -s /etc/epoptes/server.crt ]; then
        exec socat EXEC:"$0 $*" \
            openssl-connect:$SERVER:$PORT,cafile=/etc/epoptes/server.crt,forever
    elif [ -f /etc/epoptes/server.crt ]; then
       exec socat tcp:$SERVER:$PORT,forever EXEC:"$0 $*",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
elif [ "$EPOPTES_CLIENT_PHASE" = "2" ]; then
    log_end_msg 0 >&5
    # Finally, exec sh instead of keeping bash, to save memory.
    # But use "epoptes-client" as the zeroth argument, to make it easier for `ps`.
    # +m = disable job control.
    export EPOPTES_CLIENT_PHASE=3
    exec -a epoptes-client /bin/sh +m
fi
