#!/usr/bin/env sh

## This is the script you tell people to run and avoid leaking private information

nocolor="\033[0m"
bold="\033[1m"

me="${0##*/}"

usage(){
  printf '%s\n' "usage: ${me} [-mh] [-s socket] [-p password]

  -s [socket]    tor's control socket
                 notice: tcp: [addr:]port: 9051, 127.0.0.1:9051
                 notice: unix: [unix:]path: /run/tor/control,
                         unix:/run/tor/control
                 default: 9051

  -p [pwd]       use password [pwd] instead of Tor's control_auth_cookie
                 default: not used
"
  exit 1
}


list_streams(){
  ##https://stackoverflow.com/a/22644006 and https://stackoverflow.com/a/53714583
  # shellcheck disable=SC2154
  trap "rm -f stream.tmp; exit" INT QUIT TERM
  trap "rm -f stream.hosts stream.tmp stream.loop; kill 0" EXIT

  listen_stream="$(cat stream.tmp)"
  for stream_ordered in $(printf '%s\n' "${listen_stream}" | grep "^650 STREAM" | cut -d " " -f3 | sort -u | grep -v "^$" | grep -v "^250" ); do
    printf '%s\n' "${listen_stream}" | grep "^650 STREAM ${stream_ordered}" | while IFS="$(printf '\n')" read -r stream_line; do
      IFS=" " read -r _ _ stream_id stream_status circuit_id stream_target _ <<-EOF
        $(printf '%s\n' "${stream_line}")
EOF
      case "${stream_status}" in
        NEW|NEWRESOLVE)
          stream_target_orig="${stream_target}"
          stream_purpose="$(printf '%s\n' "${stream_line}" | tr " " "\n" | grep "PURPOSE=" | sed "s/PURPOSE=//")"
        ;;
        REMAP)
          stream_target_cache="$(printf '%s\n' "$(cat "${tmpdir}"/stream.hosts 2>/dev/null) ${stream_target_orig%:*}=${stream_target%:*}")"
          printf '%s\n' "${stream_target_cache}" | tee "${tmpdir}"/stream.hosts >/dev/null
          stream_target_hostname="$(printf '%s\n' "${stream_target_cache}" | tr " " "\n" | grep -F "=${stream_target%:*}" | head -n 1 | sed "s|=.*||")"
          stream_target_clean="$(printf '%s\n' "${stream_target_hostname}~(${stream_target})")"
        ;;
        CLOSED|SUCCEEDED)
          [ -n "${stream_target_clean}" ] && stream_target="${stream_target_clean}"
          # shellcheck disable=SC2086
          circuit_all="$(tor-ctrl -c "GETINFO circuit-status" ${cli_args})"
          circuit="$(printf '%s\n' "${circuit_all}" | grep "^${circuit_id} " | sed "/250 OK/d;/250+circuit-status=/d;/250 closing connection/d")"
          circuit_status="$(printf '%s\n' "${circuit}" | cut -d " " -f2)"
          circuit_purpose="$(printf '%s\n' "${circuit}" | cut -d " " -f5 | sed "s/PURPOSE=//")"
          [ -z "${circuit_status}" ] && break
          ! test -f stream.loop && {
            touch stream.loop
            printf %s"${bold}"
            printf "\nID Purpose CircID Purpose Target\n"
            printf %s"------------------------------------------------------------------------------------------------------${nocolor}\n"
          }
          ## Stream purpose is unkown for tor-ctrl if it was an end of stream that we didn't catch the creation
          ## unknown us being specified for proper field separation
          printf %s"${stream_id} ${stream_purpose:="UNKNOWN"} ${circuit_id}  ${circuit_purpose} ${stream_target}\n" | tr -s " " | grep -v " DIR_FETCH " | grep -F -v ".exit:"
          ## return to avoid duplicates (happens when there is CLOSED and SUCCEEDED)
          return
        ;;
      esac
    done
  done
  printf %s"------------------------------------------------------------------------------------------------------${nocolor}\n"
}


command -v tor-ctrl >/dev/null || error_msg "Install tor-ctrl"

while getopts ":s:p:h" Option; do
  case ${Option} in
    s) tor_control_socket="${OPTARG}";;
    p) tor_password="${OPTARG}";;
    h|*) usage;;
  esac
done

cli_args=""
[ -n "${tor_control_socket}" ] && cli_args="${cli_args} -s ${tor_control_socket}"
[ -n "${tor_password}" ] && cli_args="${cli_args} -p ${tor_password}"
[ -n "${machine_mode}" ] && cli_args="${cli_args} -m"

: "${TMPDIR:="/tmp"}"
mkdir -p "${TMPDIR}/tor-ctrl-observer" || error_msg "Failed to create '${TMPDIR}/tor-ctrl-observer'"
tmpdir="$(mktemp -d ${TMPDIR}/tor-ctrl-observer/XXXXXX)"

printf '%s\n' "${me}: [info]: subscribed to Tor stream events, as soon as streams are created, output will be shown below."

## it will print the streams table after receiving an INT signal
## other signals such as QUIT, TERM and EXIT should kill the process tree and exit
trap "list_streams" INT
trap "exit" QUIT TERM
trap "kill 0" EXIT

## only to get which socket it succesfully connects
tor-ctrl -c "GETINFO circuit-status" ${cli_args} | grep "connecting to socket with command"
printf '%s\n' "${me}: [info]: to stop listening and show a summary, please Ctrl+C"

tor-ctrl -w -m -c "SETEVENTS STREAM" ${cli_args} | sed "/DIR_FETCH/d;/DIR_UPLOAD/d;/DIRPORT_TEST/d;/\.exit\:/d;/^$/d" | \
tee "${tmpdir}"/stream.tmp | cut -d " " -f3-6


