#!/usr/bin/env sh

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

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
}

exit_handler() {
  trap "" EXIT
  if test -d "${temp_dir}" ; then
    safe-rm -r -f -- "${temp_dir}"
  fi
  exit 0
}

list_streams(){
  ## https://stackoverflow.com/a/22644006 and https://stackoverflow.com/a/53714583
  trap "" INT

  listen_stream="$(cat -- "${temp_dir}/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 -- "${temp_dir}/stream.hosts" 2>/dev/null) ${stream_target_orig%:*}=${stream_target%:*}")"
          printf '%s\n' "${stream_target_cache}" | tee -- "${temp_dir}/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 "${temp_dir}/stream.loop" && {
            touch -- "${temp_dir}/stream.loop"
            printf "%s\n" "ID Purpose CircID Purpose Target"
            printf "%s\n" "------------------------------------------------------------------------------------------------------"
          }
          ## Stream purpose is unknown 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\n" "${stream_id} ${stream_purpose:="UNKNOWN"} ${circuit_id}  ${circuit_purpose} ${stream_target}" | 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\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"

temp_dir="$(mktemp --directory)"

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_handler" QUIT TERM EXIT

## only to get which socket it successfully 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 -- "${temp_dir}/stream.tmp" | cut -d " " -f3-6
