#!/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
    echo "$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"
