#!/bin/bash

# Copyright (c) 2012 Peter Palfrader <peter@palfrader.org>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

set -e
set -u

usage() {
	echo "Usage: $0 [-v [-v]] [-d <datadir>] <torserver-fpr> <targethost> [<targetport>]"
	echo "   If torserver-fpr is -, a list of fingerprints is read from stdin"
}

verbose=0
datadir=""
while getopts "vhd:" OPTION
do
	case "$OPTION" in
		v)
			verbose=$((verbose + 1))
			;;
		h)
			usage
			exit 0
			;;
		d)
			datadir="$OPTARG"
			;;
		*)
			usage >&2
			exit 1
	esac
done
shift $(($OPTIND - 1))

if [ "${1:-}" = "--help" ]; then
	usage
	exit 0
elif [ "$#" -lt 2 ]; then
	usage >&2
	exit 1
fi


torserver="$1"; shift
hostname="$1"; shift
port="${1:-443}"

socksport=$((RANDOM % 40000 + 20000))
mapaddr="192.0.2.1"


logpid=""
torpid=""
tmpdir=""

cleanup() {
	[ -z "$torpid" ] || kill "$torpid" || true
	[ -z "$logpid" ] || kill "$logpid" || true
	[ -z "$tmpdir" ] || rm -rf "$tmpdir" || true
}

tmpdir=$(mktemp -d "/tmp/cert-check-XXXXXX")
trap 'cleanup' EXIT

pidfile="$tmpdir/pid"
torlog="$tmpdir/log"
if [ "$verbose" -gt 0 ]; then
	tail -F "$torlog" &
	logpid=$!
fi

if command -v tor > /dev/null; then
	tor="tor"
elif [ -x /usr/sbin/tor ]; then
	tor="/usr/sbin/tor"
else
	echo >&2 "Cannot find tor executable"
	exit 1
fi

if [ "$verbose" -gt 1 ]; then
	loglevel="info"
else
	loglevel="notice"
fi

datadir=${datadir:-$tmpdir/tor}
cat > "$tmpdir/torrc" << EOF
DataDirectory $datadir
RunAsDaemon 1
SocksPort $socksport
PidFile $pidfile
Log $loglevel file $torlog
SafeLogging 0
# MapAddress $mapaddr $hostname.\$$torserver.exit
ControlSocket $tmpdir/sock
StrictNodes 1
EOF

cat > "$tmpdir/torsocks.conf" << EOF
server = 127.0.0.1
server_port = $socksport
EOF

mkdir -p -m 0700 "$datadir"
if [ "$verbose" -gt 0 ]; then hush=""; else hush="--hush"; fi
"$tor" $hush -f "$tmpdir/torrc"
torpid="$(cat $pidfile)"


eatdata() {
	local fn="$1"; shift
	if [ "$verbose" -gt 0 ]; then
		tee "$fn"
	else
		cat > "$fn"
	fi
}
expect_ok() {
	local line
	read code line <&${COPROC[0]}
	line=$(echo "$line" | tr -d '\r')
	if ! [ "$code" = "250" ]; then
		echo >&2 "Tor said '$code $line' - no idea what that means"
		exit 1
	fi
}

[ "$verbose" = 0 ] || echo "Directly:"
openssl s_client -no_ticket -showcerts -connect "$hostname":"$port" < /dev/null 2>&1 | eatdata "$tmpdir/cert-direct"
egrep -v '(Session-ID|Master-Key|Start Time):' < "$tmpdir/cert-direct" > "$tmpdir/cert-direct.filtered"
[ "$verbose" = 0 ] || echo "===="


coproc socat UNIX-CONNECT:"$tmpdir/sock" -
echo 'AUTHENTICATE' >&${COPROC[1]}
expect_ok

errors=0
while : ; do
	if [ "$torserver" = "-" ]; then
		read server
		[ -n "$server" ] || break
	else
		server="$torserver"
	fi

	[ "$verbose" = 0 ] || echo "Setting ExitNodes $server"
	echo "RESETCONF ExitNodes" >&${COPROC[1]}
	expect_ok
	echo "SETCONF ExitNodes=\$$server" >&${COPROC[1]}
	expect_ok

	rm -f "$tmpdir/cert-tor" "$tmpdir/cert-tor.filtered"

	[ "$verbose" = 0 ] || echo "Via $server:"
	TORSOCKS_CONF_FILE="$tmpdir/torsocks.conf" torify openssl s_client -no_ticket -showcerts -connect "$hostname":"$port" < /dev/null 2>&1 | eatdata "$tmpdir/cert-tor"

	egrep -v '(Session-ID|Master-Key|Start Time):' < "$tmpdir/cert-tor" > "$tmpdir/cert-tor.filtered"

	if diff "$tmpdir/cert-tor.filtered" "$tmpdir/cert-direct.filtered" > /dev/null; then
		echo "RESULT: $server: No real differences."
		[ "$verbose" = 0 ] || diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true
	elif egrep '^connect:errno=' "$tmpdir/cert-tor" > /dev/null; then
		[ "$verbose" -lt 1 ] || diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true
		echo "RESULT: $server: Connect failed"
		errors=1
	else
		echo "RESULT: $server: differences!"
		[ "$verbose" = 0 ] || echo "===="
		[ "$verbose" = 0 ] || echo "Diff:"
		diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true
		errors=1
	fi

	[ "$torserver" = "-" ] || break
done
exit "$errors"