#!/bin/bash

# Copyright (c) 2013 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 [<options>] <target> [<target> ...]"
  echo "Options:"
  echo "  -a <auth>  use this authority [$AUTH]"
  echo "  -l <dir>   log dir [$LOGDIR]"
  echo "  -m         send mail when a certificate mismatch is found"
  echo "  -v         be a bit verbose"
  echo "  -h         print this help"
}


TORPID=""
CHECKDIR=""

cleanup() {
	[ -z "$TORPID" ] || kill "$TORPID" || true
	[ -z "$CHECKDIR" ] || rm -rf "$CHECKDIR" || true
}



AUTH=tor.noreply.org
PER_RUN=60
DEFAULT_CHECKHOSTS="www.torproject.org"
LOGDIR="tor-exit-ssl-check-many.log"
CACHEDIR="tor-exit-ssl-check-many.cache"
MAIL=0
CHECKDIR=$(mktemp -d "/tmp/cert-check-many-XXXXXX")
DUMPFILE="$CHECKDIR/dump"
trap 'cleanup' EXIT

mkdir -vp "$LOGDIR"
mkdir -vp "$CACHEDIR"

LOG="$LOGDIR/log"
CTR=0
HOSTNAME=$(hostname)
VERBOSE=0
while getopts "vha:l:m" OPTION
do
  case "$OPTION" in
    v)
      VERBOSE=$((VERBOSE + 1))
      ;;
    h)
      usage
      exit 0
      ;;
    a)
      AUTH="$OPTARG"
      ;;
    l)
      LOGDIR="$OPTARG"
      ;;
    m)
      MAIL=1
      ;;
    *)
      usage >&2
      exit 1
  esac
done
shift $(($OPTIND - 1))

if [ "${1:-}" = "--help" ]; then
  usage
  exit 0
fi

declare -a CHECKHOSTS=("$@")
if [ "${#CHECKHOSTS[@]}" = 0 ]; then
  for i in $DEFAULT_CHECKHOSTS; do
    CHECKHOSTS[${#CHECKHOSTS[@]}]="$i"
  done
fi

run_some_checks() {
  local idx=$(( RANDOM % ${#CHECKHOSTS[@]} ))
  local host="${CHECKHOSTS[$idx]}"
  wget -q -O - "http://$AUTH"/tor/status-vote/current/consensus | \
    grep -v BadExit | grep '^s .*Exit' -B1 |\
    awk '$1 == "r" {print $3}' |\
    sort -R | \
    head -n "$PER_RUN" | \
    while read fpr; do
      [ "$VERBOSE" = 0 ] || echo -n "[$(TZ=UTC date +%Y-%m-%dT%H:%M:%SZ)] Checking $host at $fpr..."
      if timeout 600 tor-exit-ssl-check -c "$CACHEDIR" -C "$CONTROLSOCKET" "$fpr" $host > "$DUMPFILE" 2>&1; then
        ecode=0
      else
        ecode="$?"
      fi
      [ "$VERBOSE" = 0 ] || echo "$ecode"
      prefix="[$(TZ=UTC date +%Y-%m-%dT%H:%M:%SZ)][$fpr][EC=$ecode]"
      case "$ecode" in
        0) echo "$prefix OK" >> "$LOG";;
        2) echo "$prefix connect failed" >> "$LOG";;
        4) echo "$prefix handshake failed" >> "$LOG";;
        8) lf="$LOGDIR/$(date +%s).$HOSTNAME.$$.$CTR"
           echo "$prefix differences - logged as $lf" >> "$LOG"
           CTR=$((CTR + 1))
           echo "$fpr" > "$lf"
           echo >> "$lf"
           cat "$DUMPFILE" >> "$lf"
           echo >> "$lf"
           wget -q -O - http://"$AUTH"/tor/server/fp/$(echo "$fpr" | perl -MMIME::Base64 -e "print unpack(\"H*\", decode_base64(<>)),\"\n\"") >> "$lf"
           if [ "$MAIL" -gt 0 ]; then
             (echo "Log file at $lf:"; echo; cat "$lf") | mail -s "certificate mismatch found for relay $fpr" "$USER"
           fi
           ;;
        124) echo "$prefix timeout!" >> "$LOG";;
        *)
           echo "$prefix unknown exit code" >> "$LOG"
           echo "$prefix unknown exit code" >&2
           cp $DUMPFILE "$LOGDIR/failed-output"
           exit 1
      esac
    done
}


start_tor() {
	local pidfile="$CHECKDIR/pid"

	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

	local datadir="$CHECKDIR/tor"
	CONTROLSOCKET="$CHECKDIR/sock"
	cat > "$CHECKDIR/torrc" << EOF
DataDirectory $datadir
RunAsDaemon 1
SocksPort auto
PidFile $pidfile
SafeLogging 0
ControlSocket $CONTROLSOCKET
StrictNodes 1
EOF

	mkdir -p -m 0700 "$datadir"
	"$tor" --hush -f "$CHECKDIR/torrc"
	TORPID="$(cat $pidfile)"
}

start_tor
while : ; do
  run_some_checks
  sleep 60
done