#!/usr/bin/env bash # # sms-free.sh - send SMS to Free Mobile. # # (C) Bruno Raoult ("br"), 2022 # Licensed under the GNU General Public License v3.0 or later. # Some rights reserved. See COPYING. # # You should have received a copy of the GNU General Public License along with this # program. If not, see . # # SPDX-License-Identifier: GPL-3.0-or-later # #%MAN_BEGIN% # NAME # sms-free.sh - Send SMS to Free Mobile account. # # SYNOPSIS # sms-free.sh [OPTIONS] [-k KEYFILE] USER [MESSAGE] # sms-free.sh [OPTIONS] [-l LOGIN:PASSWORD] [MESSAGE] # # DESCRIPTION # Send a SMS to a Free Mobile (french mobile operator). This script will # only work for phones numbers for which you have the "SMS key" (see FREE # MOBILE SMS SETUP below). Therefore yourself, close relatives, and other # people who trust you). # MESSAGE is the text to be sent. If missing, it will be read from standard # input. # # OPTIONS # -d, --dry-run # Will not send the SMS. # # -h, --help # Display usage and exit. # # -l, --login=ACCOUNT:SMSKEY # Do not use a KEYFILE, and provide directly the Free Mobile ACCOUNT # and SMSKEY. # # -k, --keyfile=KEYFILE # Use KEYFILE instead of default ~/data/private/free-sms-keys.txt. # # -m, --man # Print a man-like help and exit. # # -v, --verbose # Print messages on what is being done. # # FREE MOBILE SMS SETUP # You should first connect on https://mobile.free.fr/account/, and # activate the option "Mes options / Notifications par SMS". You will be # given a key. # # KEY FILE SYNTAX # The key file contains lines of the form: # id:login:password # id # A mnemonic for the user (firstname, etc...), it should be unique. # login # A valid Free Mobile account number (usually 8 digits). # key # The SMS key associated with the Free Mobile login (usually a 14 # alphanumeric string). # # Example: # bruno:01234567:abcdeABCDE1234 # bodiccea:76543210:xyztXYZT123456 # # AUTHOR # Bruno Raoult. #%MAN_END% # # PERSONAL NOTES/TODO # In example above, "%20" can be replaced by "+" # See: https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 # utf8 characters look supported (tested on French accentuated characters, # Japanese kana and kanji, and Chinese) # # FREE MOBILE DOCUMENTATION # # L'envoi du SMS se fait en appelant le lien suivant : # # https://smsapi.free-mobile.fr/sendmsg # avec les paramètres suivants : # # user : votre login # pass : votre clé d'identification générée automatiquement par notre # service # msg : le contenu du SMS encodé sous forme d'url (Percent-encoding) # # Exemple : Envoyer le message "Hello World !" sur votre mobile : # # https://smsapi.free-mobile.fr/sendmsg?user=12345678&pass=abcABC12345678&msg=Hello%20World%20! # # Vous pouvez également, si vous le préférez, envoyer les paramètres en POST. # Dans ce cas, le contenu du message n'a pas besoin d'être encodé. # Le code de retour HTTP indique le succès ou non de l'opération : # # 200 : Le SMS a été envoyé sur votre mobile. # 400 : Un des paramètres obligatoires est manquant. # 402 : Trop de SMS ont été envoyés en trop peu de temps. # 403 : Le service n'est pas activé sur l'espace abonné, ou login / clé # incorrect. # 500 : Erreur côté serveur. Veuillez réessayer ultérieurement. #set -x script="$0" # full path to script cmdname=${0##*/} # script name export LC_CTYPE="C.UTF-8" # to handle non ascii chars declare sms_keyfile=~/data/private/free-sms-keys.txt declare sms_verbose="" declare sms_credentials="" declare sms_dryrun="" declare sms_message="" declare sms_url="https://smsapi.free-mobile.fr/sendmsg" declare -A sms_status=( [-]="Unknown error" [200]="OK" [400]="Missing parameter" [402]="Too many SMS sent in short time" [403]="Service non activated or incorrect credentials" [500]="Server error" ) usage() { printf "usage: %s [-hmv] [-k KEYFILE] USER [MESSAGE]\n" "$cmdname" printf " %s [-hmv] [-l LOGIN:PASSWORD] [MESSAGE]\n" "$cmdname" printf "Use '%s --man' for more help\n" "$cmdname" return 0 } man() { sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$script" | more } # log() - log messages on stderr # parameters: # -l, -s: long, or short prefix (default: none). Last one is used. # -t: timestamp # -n: no newline # This function accepts either a string, either a format string followed # by arguments : # log -s "%s" "foo" # log -s "foo" log() { local timestr="" prefix="" newline=y todo OPTIND [[ -z $sms_verbose ]] && return 0 while getopts lsnt todo; do case $todo in l) prefix=$(printf "*%.s" {1..30}) ;; s) prefix=$(printf "*%.s" {1..5}) ;; n) newline=n ;; t) timestr=$(date "+%F %T%z ") ;; *) ;; esac done shift $((OPTIND - 1)) [[ $prefix != "" ]] && printf "%s " "$prefix" >&2 [[ $timestr != "" ]] && printf "%s" "$timestr" >&2 # shellcheck disable=SC2059 printf "$@" >&2 [[ $newline = y ]] && printf "\n" >&2 return 0 } # echorun() - logs and run (maybe) a command. # $1: reference of variable which will get the output of command # $2..$n: command to log and run echorun() { local -n _out="$1" shift [[ -n $sms_dryrun ]] && log -n "dry-run: " log "%s" "$*" [[ -z $sms_dryrun ]] && _out=$("$@") return $? } # get_credentials() - get credentials from keyfile # $1: reference of variable which will contain credentials # $2: keyfile # $3: user to find # # @return: 0 on success # @return: 1 on file not present or not readable # @return: 2 if user not found get_credentials() { local -n _cred=$1 local _keyfile="$2" _user="$3" _name="" log "get_credentials: ref=%s user=%s keyfile=%s" "$!_cred" "$_user" "$_keyfile" if [[ ! -r "$_keyfile" ]]; then printf "%s: cannot read keyfile %s\n" "$cmdname" "$_keyfile" return 2 fi while IFS=: read -r _name _cred; do log -n "key: name=[%s] creds=[%s]... " "$_name" "$_cred" [[ $_name = "$_user" ]] && log "match." && return 0 log "skipping." done < "$_keyfile" printf "%s: cannot find credentials for user '%s'\n" "$cmdname" "$_user" return 2 } parse_opts() { # short and long options local sopts="dhk:l:mv" local lopts="dry-run,help,keyfile:,login:,man,verbose" if ! tmp=$(getopt -o "$sopts" -l "$lopts" -n "$cmdname" -- "$@"); then log "Use '%s --help' or '%s --man' for help." "$cmdname" "$cmdname" exit 1 fi eval set -- "$tmp" while true; do case "$1" in '-d'|'--dry-run') sms_dryrun=y ;; '-h'|'--help') usage exit 0 ;; '-k'|'--keyfile') sms_keyfile="$2" shift ;; '-l'|'--login') sms_credentials="$2" log "sms_creds=%s" "$sms_credentials" shift ;; '-m'|'--man') man exit 0 ;; '-v'|'--verbose') sms_verbose=y ;; '--') shift break ;; *) usage log 'Internal error!' exit 1 ;; esac shift done # parse remaining arguments case "$#" in 0) # no user, no message: we need sms_credentials if [[ -z $sms_credentials ]]; then printf "%s: Missing credentials.\n" "$cmdname" exit 1 fi ;; 1|2) # get credentials from KEYFILE if [[ -z $sms_credentials ]]; then get_credentials sms_credentials "$sms_keyfile" "$1" || exit 1 shift else # cannot have user and sms_credentials (( $# == 2 )) && usage && exit 1 fi if [[ $# == 1 ]]; then sms_message="$1" else readarray sms_message printf -v sms_message "%s" "${sms_message[@]}" sms_message=${sms_message%$'\n'} # remove trailing '\n' fi ;; *) usage ;; esac log "credentials=%s" "$sms_credentials" log "message=[%s]" "$sms_message" } # send-sms() - send SMS (GET method) send_sms() { local _login=${sms_credentials%:*} _pass=${sms_credentials#*:} _res="" _str="" log "send_sms(): login=%s pass=%s" "$_login" "$_pass" echorun _res curl --silent --get --write-out '%{http_code}' \ --data "user=$_login" \ --data "pass=$_pass" \ --data-urlencode "msg=$sms_message" \ "$sms_url" [[ -n $sms_dryrun ]] && _res=200 _str="${sms_status[$_res]:-${sms_status[-]}}" log "send_sms(): curl status=%s (%s)" "$_res" "$_str" if [[ $_res != 200 ]]; then printf "%s: %s\n" "$cmdname" "$_str" fi } parse_opts "$@" send_sms exit 0 # Indent style for emacs # Local Variables: # sh-basic-offset: 4 # sh-indentation: 4 # indent-tabs-mode: nil # comment-column: 32 # End: