#!/bin/bash
# This file is in the public domain.
# Transmit a Challenger TAN by SMS, trying each provider in turn.
#
# Usage:
#   echo "MESSAGE" | $0 '{"CONTACT_PHONE":"+41791234567"}'
#
# The TAN is part of the message read from stdin and never appears in argv.
#
# Exit codes (see challenger-send-sms(1)).  Success is 0 or the 200-210 band;
# every other value is a failure.  The success band is up at 200 and not at 1
# because libgnunetutil reports a failed exec() of the helper as exit code 1,
# which must not be mistaken for a delivery.
#
#    0  delivered, confirmed on the handset
#  201  accepted by the provider, delivery not confirmed
#  202  suppressed as a duplicate; identical message already accepted
#
#   10  address malformed
#   11  number too short
#   12  number too long
#   13  not a mobile subscription
#   14  unallocated / unknown subscriber
#   15  blocked, barred or blocklisted
#
#   20  handset offline / out of coverage
#   21  delivery expired
#   22  carrier / mobile operator failure
#
#   30  provider outage or internal error
#   31  provider rejected the request
#   32  rate limit exceeded
#   33  no verdict: submission outcome unknown
#
#   40  local configuration or invocation error
#   41  invalid authorization
#   42  insufficient balance
#
#   50  unclassified failure

set -eu

if [ $# -ne 1 ]; then
  echo "Usage: $0 '{\"CONTACT_PHONE\":\"+41791234567\"}'" 1>&2
  exit 40
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE=$(basename "$0")

PHONE_NUMBER=$(echo "$1" | jq -r '.CONTACT_PHONE // empty')
MESSAGE=$(cat -)

if [ -z "$PHONE_NUMBER" ]; then
  echo "No CONTACT_PHONE field in the address object" 1>&2
  exit 10
fi

# Generic E.164 check, done once here so that every provider benefits and no
# API call is spent on hopeless input.  This can only enforce the ITU global
# bounds: per-country lengths would need libphonenumber, which this package
# deliberately does not depend on.  A number that is merely one digit short or
# long, or that has a transposed digit, therefore passes and is classified as
# 14 by whichever provider reports on it first.
if [[ ! $PHONE_NUMBER =~ ^\+[0-9]+$ ]]; then
  echo "Phone number '$PHONE_NUMBER' is not of the form +<digits>" 1>&2
  exit 10
fi

DIGITS=${PHONE_NUMBER#+}
if [ ${#DIGITS} -lt 7 ]; then
  echo "Phone number '$PHONE_NUMBER' is too short" 1>&2
  exit 11
fi
if [ ${#DIGITS} -gt 15 ]; then
  echo "Phone number '$PHONE_NUMBER' is too long" 1>&2
  exit 12
fi

# Providers are resolved by name convention; adding one means creating
# ${BASE}-<name>, making it executable, listing it in src/meson.build and
# adding it here.
PROVIDERS="telesign clicksend aspsms"

# Precedence used when every provider failed: report the most actionable
# code seen.  Lower rank wins.
rank() {
  case "$1" in
  1[0-5]) echo 1 ;;
  2[0-2]) echo 2 ;;
  4[0-2]) echo 3 ;;
  3[0-3]) echo 4 ;;
  *) echo 5 ;;
  esac
}

BEST=0

for PROVIDER in $PROVIDERS; do
  SCRIPT_PATH="$SCRIPT_DIR/${BASE}-${PROVIDER}"
  if [ ! -x "$SCRIPT_PATH" ]; then
    continue
  fi
  STATUS=0
  printf '%s\n' "$MESSAGE" | "$SCRIPT_PATH" "$PHONE_NUMBER" || STATUS=$?
  case "$STATUS" in
  0 | 20[0-9] | 210)
    exit "$STATUS"
    ;;
  1[0-5] | 2[01])
    # The address is at fault, or the handset itself is unreachable.
    # Another provider would reach the same number on the same handset,
    # so trying one only costs time and another submission.
    echo "$PROVIDER: address or recipient error ($STATUS), not trying further providers" 1>&2
    exit "$STATUS"
    ;;
  *)
    # Carrier, provider and configuration failures are specific to the
    # route or the account, so another provider may well succeed.
    echo "$PROVIDER failed to send to $PHONE_NUMBER with status $STATUS" 1>&2
    if [ 0 -eq "$BEST" ] || [ "$(rank "$STATUS")" -lt "$(rank "$BEST")" ]; then
      BEST=$STATUS
    fi
    ;;
  esac
done

if [ 0 -eq "$BEST" ]; then
  echo "No SMS provider helper is installed next to $0" 1>&2
  exit 40
fi

echo "All SMS providers failed to send to $PHONE_NUMBER, reporting status $BEST" 1>&2
exit "$BEST"
