#!/bin/sh
# ipmikvm (part of ossobv/vcutil) // wdoekes/2019-2020 // Public Domain
#
# A wrapper to call the IPMIView KVM console without starting IPMIView
# (and avoiding a bug that attempts to connect to port 0).
#
# Requirements:
# - ipmiview installed in /opt/ipmiview
# - curl (for 2-step user/pass)
# - socat (for TLS-tunnel)
#
# Usage:
#
#   $ ipmikvm
#   Usage: ./ipmikvm [-u ADMIN] [-P ADMIN] [-p 5900] IP.ADD.RE.SS
#   Options: [-k (nossl)] [-2 (2-step user/pass fetch)]
#
#   $ ipmikvm 10.11.12.13 -P otherpassword
#   (connects KVM console on IPMI device at 10.11.12.13)
#
# This has been tested with IPMIView 2.16.0+build190528 (and
# specifically, the tw.com.aten.ikvm.KVMMain 1.69+r14 (or 1.69.20)).
#
# See also: ipmikvm-tls2020
#
set -eu
ipmipath=/opt/ipmiview

host=
user=ADMIN
pass=ADMIN
port=5900
step2=
tls=1
vm=0  # ???

# Use getopt(1) to reorder arguments
eval set --"$(getopt -- 'hk2p:u:P:' "$@")"

usage() {
    test ${1:-1} -ne 0 && exec >&2  # non-zero? write to stderr
    echo "Usage: $0 [-u ADMIN] [-P ADMIN] [-p 5900] IP.ADD.RE.SS"
    echo "Options: [-k (nossl)] [-2 (2-step user/pass fetch)]"
    exit ${1:-1}
}

while getopts 'hk2p:u:P:' OPTION; do
    case "$OPTION" in
    h) usage 0;;
    k) tls=0;;
    p) port=$OPTARG;;
    u) user=$OPTARG;;
    P) pass=$OPTARG;;
    2) step2=1;;
    ?) usage 1;;
    esac
done
shift $((OPTIND - 1))

test $# -ne 1 && usage
host=${1:-}; shift
test -z "$host" && usage

# Try ping first
if ! ping -q -c1 -w2 "$host" >/dev/null; then
    echo "No ping response from $host" >&2
    exit 1
fi

# SuperMicro 2-step auth request (get user+pass from jnlp download)
if test -n "$step2"; then
    set +e
    fail=1
    url="https://$host"
    temp=$(mktemp)
    b64_user=$(echo -n "$user" | base64 -w0 | sed -e 's/=/%3D/g;s/+/%2B/g')
    b64_pass=$(echo -n "$pass" | base64 -w0 | sed -e 's/=/%3D/g;s/+/%2B/g')
    if curl --fail -sk --cookie-jar "$temp" -XPOST "$url/cgi/login.cgi" \
          --data "name=$b64_user&pwd=$b64_pass&check=00" -o/dev/null; then
        out=$(curl --fail -sk --cookie "$temp" \
            "$url/cgi/url_redirect.cgi?url_name=man_ikvm&url_type=jwsk")
        if test $? -eq 0; then
            user_pass=$(
              echo "$out" | grep -FA2 "<argument>$host</argument>" |
                sed -e '1d;s/^[[:blank:]]*<argument>//;s/<\/argument>.*//' |
                tr '\n' ' ' | sed -e 's/ *$//')
            user=${user_pass% *}
            pass=${user_pass#* }
            fail=
        fi
    fi
    rm "$temp"
    if test -n "$fail" || test -z "$user" || test -z "$pass"; then
        echo "Problem getting password from web interface" >&2
        exit 1
    fi
    set -e
fi

# Check whether it speaks TLS, and auto-disable if not
if test $tls -ne 0 &&
        ! openssl s_client -connect "$host:$port" -quiet -state \
          </dev/null 2>&1 | grep -q ^SSL.*read; then
    echo "No TLS/SSL detected. Trying without instead" >&2
    tls=0
fi

# TLS wrapping
if test $tls -ne 0; then
    remotehost=$host
    remoteport=$port
    host=127.0.0.1
    port=$((30000 + ($$ % 10000)))  # use this-pid as "random"
    echo "Spawning TLS/SSL socat $host:$port -> $remotehost:$remoteport..." >&2
    # spawn socat
    crtpath="$ipmipath/BMCSecurity" # "/opt/ipmiview/BMCSecurity/client.crt"
    crtopt="commonname=IPMI,cafile=$crtpath/server.crt"
    crtopt="$crtopt,cert=$crtpath/client.crt,key=$crtpath/client.key"
    socat "TCP4-LISTEN:$port" "OPENSSL:$remotehost:$remoteport,$crtopt" &
    tlspid=$!
    # socat doesn't connect until we connect, so we don't need to sleep
fi

# Yes. Passing usernames and passwords on the command line is insecure.
# You can pass them through a file if you want:
# printf 'ID: ADMIN\nPassword: ADMIN\nPort: 5900\nVm: 0\n' >IP.ADD.RE.SS.temp
# But I don't think your desktop/laptop is going to be insecure enough
# to warrant that. (And in that case, this script needs to get the user/pass
# through a different means as well :P)

echo "Attempting iKVM connection to $host:$port..." >&2
ret=0
"$ipmipath/jre/bin/java" -Djava.library.path="$ipmipath" \
    -jar "$ipmipath/iKVM.jar" "$host" "$user" "$pass" "$vm" "$port" || ret=$?
echo "(iKVM exit code $ret)" >&2

# TLS, clean up socat
if test $tls -ne 0; then
    if kill $tlspid 2>/dev/null; then
        sleep 1
        kill -0 $tlspid 2>/dev/null && kill -9 $tlspid
    fi
fi

exit $ret
