commit 7db958bd5466ff7abe24ddc79ab5a9d65dc4674c
parent dfd42adf827f942b8d1faf5e772a0355638ecaac
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Wed, 22 Oct 2025 15:18:31 +0200
add sms scripts with fallback (#10295)
Diffstat:
3 files changed, 321 insertions(+), 21 deletions(-)
diff --git a/contrib/libeufin-tan-sms-clicksend.sh b/contrib/libeufin-tan-sms-clicksend.sh
@@ -0,0 +1,140 @@
+#!/bin/bash
+# This file is in the public domain.
+# Send an SMS using ClickSend API
+
+set -eu
+
+# Check shared secrets
+if [ -x "$CLICKSEND_USERNAME" ]
+then
+ echo "CLICKSEND_USERNAME not set in environment"
+ exit 1
+fi
+if [ -x "$CLICKSEND_API_KEY" ]
+then
+ echo "CLICKSEND_API_KEY not set in environment"
+ exit 1
+fi
+
+if [ $# -ne 1 ]
+then
+ echo "Usage: $0 <phone_number>" 1>&2
+ exit 1
+fi
+
+PHONE_NUMBER="$1"
+MESSAGE=$(cat -)
+
+TMPFILE=$(mktemp /tmp/clicksend-sms-logging-XXXXXX)
+
+RESPONSE=$(curl --silent --show-error --fail \
+ --url https://rest.clicksend.com/v3/sms/send \
+ --request POST \
+ --header 'Content-Type: application/json' \
+ --user "$CLICKSEND_USERNAME:$CLICKSEND_API_KEY" \
+ --data "{
+ \"messages\": [{
+ \"source\": \"bash-script\",
+ \"to\": \"$PHONE_NUMBER\",
+ \"body\": \"$MESSAGE\"
+ }]
+ }")
+
+echo "$RESPONSE" > "$TMPFILE"
+
+RESPONSE_CODE=$(echo "$RESPONSE" | jq -r '.response_code')
+
+if [ "$RESPONSE_CODE" != "SUCCESS" ];
+then
+ echo "Failed to send message, got response code $RESPONSE_CODE." 1>&2
+ exit 1
+fi
+
+MESSAGE_ID=$(echo "$RESPONSE" | jq -r '.data.messages[0].message_id')
+
+if [ "$MESSAGE_ID" == "null" ];
+then
+ echo "Failed to retrieve message ID." 1>&2
+ exit 1
+fi
+
+MESSAGE_STATUS=$(echo "$RESPONSE" | jq -r '.data.messages[0].status')
+
+if [ "$MESSAGE_STATUS" == "SUCCESS" ];
+then
+ echo "Message delivered successfully." 1>&2
+ exit 0
+fi
+
+MAX_ITERATIONS=12
+
+# Poll message status
+echo "Polling message status (message_id: $MESSAGE_ID)..." 1>&2
+for N in $(seq 1 "$MAX_ITERATIONS")
+do
+ STATUS_RESPONSE=$(curl --silent --show-error --fail \
+ --url "https://rest.clicksend.com/v3/sms/receipts/$MESSAGE_ID" \
+ --user "$CLICKSEND_USERNAME:$CLICKSEND_API_KEY")
+
+ echo "$STATUS_RESPONSE" >> "$TMPFILE"
+
+ RESPONSE_CODE=$(echo "$RESPONSE" | jq -r '.response_code')
+
+ if [ "$RESPONSE_CODE" != "SUCCESS" ];
+ then
+ echo "Failed to get message status, assuming failure." 1>&2
+ exit 1
+ fi
+
+ STATUS_CODE=$(echo "$STATUS_RESPONSE" | jq -r '.data.status_code')
+ STATUS_TEXT=$(echo "$STATUS_RESPONSE" | jq -r '.data.status_text')
+ STATUS=$(echo "$STATUS_TEXT" | awk --field-separator ':' '{print $1}')
+
+ case "$STATUS_CODE" in
+ "200")
+ case "$STATUS" in
+ "Success"|"Sent")
+ # Message sent to the network for delivery, wait a bit
+ sleep 1
+ ;;
+ "Queued"|"Scheduled")
+ # queued for delivery, sleep a bit longer
+ sleep 10
+ ;;
+ "WaitApproval")
+ # Human in the loop (strange), sleep even longer
+ sleep 120
+ ;;
+ *)
+ # Unexpected status, keep trying
+ sleep 5
+ ;;
+ esac
+ ;;
+ "201")
+ # Message delivered to the handset
+ echo "Message delivered successfully." 1>&2
+ exit 0
+ ;;
+ "300")
+ # Temporary network error, clicksend will retry automatically, sleep a bit
+ sleep 20
+ ;;
+ "301")
+ # Delivery failed
+ echo "Message delivery failed: $DESCRIPTION" 1>&2
+ exit 1
+ ;;
+ "FAILED"|"INVALID_RECIPIENT")
+ exit 1
+ ;;
+ *)
+ sleep 5
+ ;;
+ esac
+done
+
+echo "Unclear message delivery status $STATUS_CODE ($DESCRIPTION) after $MAX_ITERATIONS iterations. Assuming failure." 1>&2
+exit 1
+
+
diff --git a/contrib/libeufin-tan-sms-telesign.sh b/contrib/libeufin-tan-sms-telesign.sh
@@ -0,0 +1,154 @@
+#!/bin/bash
+# This file is in the public domain.
+# Send an SMS using Telesign API
+set -eu
+
+# Check shared secrets
+if [ -x "$TELESIGN_AUTH_TOKEN" ]
+then
+ echo "TELESIGN_AUTH_TOKEN not set in environment"
+ exit 1
+fi
+
+if [ $# -ne 1 ]; then
+ echo "Usage: $0 <phone_number>" 1>&2
+ exit 1
+fi
+
+PHONE_NUMBER="$1"
+MESSAGE=$(cat -)
+
+TMPFILE=$(mktemp /tmp/telesign-sms-logging-XXXXXX)
+
+RESPONSE=$(curl --silent --show-error --fail \
+ --url https://rest-api.telesign.com/v1/messaging \
+ --request POST \
+ --header "Authorization: Basic $TELESIGN_AUTH_TOKEN" \
+ --header "Content-Type: application/x-www-form-urlencoded" \
+ --data account_livecycle_event=transact \
+ --data "phone_number=$PHONE_NUMBER" \
+ --data-urlencode "message=$MESSAGE" \
+ --data "message_type=OTP")
+
+echo "$RESPONSE" > "$TMPFILE"
+REFERENCE_ID=$(jq -r '.reference_id' "$TMPFILE")
+
+if [ "$REFERENCE_ID" == "null" ];
+then
+ echo "Failed to retrieve reference ID." 1>&2
+ exit 1
+fi
+
+STATUS_CODE=$(echo "$RESPONSE" | jq -r '.status.code')
+
+case "$STATUS_CODE" in
+ "200")
+ # Delivered to headset. Should basically never happen here.
+ exit 0
+ ;;
+ "203"|"292"|"295")
+ # Delivered to gateway
+ sleep 2
+ ;;
+ "207"|"211"|"220"|"221"|"222"|"231"|"237"|"238")
+ # Failure to deliver (hard)
+ echo "Could not deliver" 1>&2
+ exit 1
+ ;;
+ "210")
+ # Temporary phone error
+ ;;
+ "250")
+ # Final status unknown
+ echo "Final status unknown, assuming success" 1>&2
+ exit 0
+ ;;
+ "290")
+ # Message in progress, go into loop below
+ sleep 2
+ ;;
+ "502"|"503"|"504"|"505"|"506"|"507"|"508"|"509"|"510"|"511"|"512"|"513"|"514"|"515"|"517"|"520"|"521")
+ echo "Carrier problem ($STATUS_CODE)" 1>&2
+ exit 1
+ ;;
+ "10000")
+ # Internal error at telesign...
+ echo "Telesign internal error" 1>&2
+ exit 1
+ ;;
+ "10019"|"10020")
+ # Rate limit exceeded. Treating as hard failure for now.
+ echo "Rate limit exceeded" 1>&2
+ exit 1
+ ;;
+ *)
+ # Many possible status codes for failure...
+ echo "Message delivery failed: $STATUS_CODE" 1>&2
+ exit 1
+ ;;
+esac
+
+MAX_ITERATIONS=12
+
+# Poll for message status
+echo "Polling message status (reference_id: $REFERENCE_ID)..." 1>&2
+for N in $(seq 1 "$MAX_ITERATIONS")
+do
+ STATUS_RESPONSE=$(curl --silent --show-error --fail \
+ --url "https://rest-api.telesign.com/v1/messaging/$REFERENCE_ID" \
+ --header "Authorization: Basic $TELESIGN_AUTH_TOKEN")
+
+ echo "$STATUS_RESPONSE" >> "$TMPFILE"
+
+ STATUS_CODE=$(echo "$STATUS_RESPONSE" | jq -r '.status.code')
+ DESCRIPTION=$(echo "$STATUS_RESPONSE" | jq -r '.status.description')
+
+ case "$STATUS_CODE" in
+ "200")
+ # Delivered to headset. Great!
+ echo "Delivered to headset" 1>&2
+ exit 0
+ ;;
+ "203"|"290"|"292"|"295")
+ # Delivered to gateway, wait a bit for an update
+ sleep 2
+ ;;
+ "210")
+ # Temporary phone error
+ sleep 15
+ ;;
+ "207"|"211"|"220"|"221"|"222"|"231"|"237"|"238")
+ # Failure to deliver (hard)
+ echo "Could not deliver" 1>&2
+ exit 1
+ ;;
+ "250")
+ # Final status unknown
+ echo "Final status unknown, assuming success" 1>&2
+ exit 0
+ ;;
+ "502"|"503"|"504"|"505"|"506"|"507"|"508"|"509"|"510"|"511"|"512"|"513"|"514"|"515"|"517"|"520"|"521")
+ echo "Carrier problem ($STATUS_CODE)" 1>&2
+ exit 1
+ ;;
+ "10000")
+ # Internal error at telesign...
+ echo "Telesign internal error" 1>&2
+ exit 1
+ ;;
+ "10019"|"10020")
+ # Rate limit exceeded. Treating as hard failure for now.
+ echo "Rate limit exceeded" 1>&2
+ exit 1
+ ;;
+ *)
+ # Many possible status codes for failure...
+ echo "Message delivery failed: $STATUS_CODE" 1>&2
+ exit 1
+ ;;
+ esac
+done
+
+echo "Unclear message delivery status $STATUS_CODE ($DESCRIPTION) after $MAX_ITERATIONS iterations. Assuming failure." 1>&2
+exit 1
+
diff --git a/contrib/libeufin-tan-sms.sh b/contrib/libeufin-tan-sms.sh
@@ -1,31 +1,37 @@
#!/bin/bash
# This file is in the public domain.
+# Send an SMS
set -eu
-# Check shared secrets
-if [ -x "$TELESIGN_AUTH_TOKEN" ]
+if [ $# -ne 1 ]
then
- echo "TELESIGN_AUTH_TOKEN not sent in environment"
+ echo "Usage: $0 <phone_number>" 1>&2
exit 1
fi
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SCRIPT_NAME=$(basename "$0")
+BASE="${SCRIPT_NAME%.sh}"
+
+PHONE_NUMBER="$1"
MESSAGE=$(cat -)
-TMPFILE=$(mktemp /tmp/sms-loggingXXXXXX)
-STATUS=$(curl --request POST \
- --url https://rest-api.telesign.com/v1/messaging \
- --header 'authorization: Basic $TELESIGN_AUTH_TOKEN' \
- --header 'content-type: application/x-www-form-urlencoded' \
- --data account_livecycle_event=transact \
- --data "message=$MESSAGE" \
- --data message_type=OTP \
- --data "phone_number=$1" \
- -w "%{http_code}" -s -o $TMPFILE)
-case $STATUS in
- 200|203|250|290|291|295)
- exit 0;
- ;;
- *)
- exit 1;
- ;;
-esac
+
+# List of sub-scripts to try.
+PROVIDERS="telesign clicksend"
+
+for PROVIDER in $PROVIDERS
+do
+ SCRIPT_PATH="$SCRIPT_DIR/${BASE}-${PROVIDER}.sh"
+ if [ -x "$SCRIPT" ]
+ then
+ if echo "$MESSAGE" | "$SCRIPT_PATH" "$PHONE_NUMBER"
+ then
+ exit 0
+ else
+ echo "$PROVIDER failed." 1>&2
+ fi
+ fi
+done
+
+echo "All SMS providers failed." 1>&2
exit 1