Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/sh
- # Usage: ussd [-n|--] '<request>' [<tty-device>]
- # Send USSD request <request> to cellular network via modem port
- # <tty-device> and print the response. Then, if a session is opened,
- # read stdin for answers and send its.
- # Alexander B. Waldner, 2012-2019
- UItty=/dev/ttyUSB2 # Default UserInterface tty device.
- NRTO=15 # Network Response TimeOut (sec).
- MTO=2 # Modem TimeOut (sec).
- LCKDir=/var/lock # uucp-style LoCKfile Directoty.
- T='' # Transmission mode.
- chat () { /usr/sbin/chat "${@}" ; } # Location of chat program.
- Err () { # <message> # Always "unsuccess".
- printf '%s: %s\n' "${0##*/}" "${1}" >&2 ; ! :
- } # Err
- EGSMToGSMPDU () {
- # Convert text (stdin) from 8-bit encoded Extended GSM Default Alphabet
- # to hexadecimal representation of packed 7-bit encoding and put it.
- # (GSM 03.38 6.1.2.3).
- od -v -A n -t x1 | tr '\n' ' ' | # Get hexadecimal representation.
- sed '
- y/ABCDEF/abcdef/ ; # Overcaution.
- s/ [89a-f]/ 1b&/ ; # Unescape, if > 127.
- s/[[:blank:]]//g ; s/\(.\)\(.\)/\2\1/g ; # Swap quadruples.
- s/0/0000/g ; s/1/1000/g ; s/2/0100/g ; s/3/1100/g
- s/4/0010/g ; s/5/1010/g ; s/6/0110/g ; s/7/1110/g ; # from LSB to MSB,
- s/8/0001/g ; s/9/1001/g ; s/a/0101/g ; s/b/1101/g ; # left to right.
- s/c/0011/g ; s/d/1011/g ; s/e/0111/g ; s/f/1111/g
- s/\(.\{7\}\)./\1/g ; # Remove each 8-th bit.
- s/.\{8\}/& /g ; # Split octets.
- s/.1011000 $/& 10110000 / ; # Add CR if need.
- s/ .$/&1011000 / ; # Pad with CR
- s/ ..$/&000000 / ; s/ ...$/&00000 / ; s/ ....$/&0000 / ; # Pad with
- s/ .....$/&000 / ; s/ ......$/&00 / ; s/ .......$/&0 / ; # zero bites.
- s/^.\{7\}$/&0 / ; # Special case - one character.
- s/\(....\)\(....\) /\2 \1 /g ; # Split and swap quadruples.
- s/0000/0/g ; s/1000/1/g ; s/0100/2/g ; s/1100/3/g
- s/0010/4/g ; s/1010/5/g ; s/0110/6/g ; s/1110/7/g
- s/0001/8/g ; s/1001/9/g ; s/0101/A/g ; s/1101/B/g
- s/0011/C/g ; s/1011/D/g ; s/0111/E/g ; s/1111/F/g
- s/ //g
- '
- } # EGSMToGSMPDU
- GSMPDUToEGSM () { # <text>
- # Convert the string ($1) from hexadecimal representation of packed
- # 7-bit encoding of GSM Default Alphabet to 8-bit encoded Extended GSM DA
- # and put it. (GSM 03.38 6.2).
- # If the string is not a valid code, then put none.
- printf '%s\n' "${1}" | # '\n' is workaround for GNU sed.
- sed '
- /[^0-9A-Fa-f]/ d ; /^\(..\)*.$/ d ; /^$/ d ; # Validity check.
- y/abcdef/ABCDEF/ ; # Overcaution.
- s/\(.\)\(.\)/\2\1/g ; # Swap quadruples.
- s/0/0000/g ; s/1/1000/g ; s/2/0100/g ; s/3/1100/g
- s/4/0010/g ; s/5/1010/g ; s/6/0110/g ; s/7/1110/g ; # from LSB to MSB,
- s/8/0001/g ; s/9/1001/g ; s/A/0101/g ; s/B/1101/g ; # left to right.
- s/C/0011/g ; s/D/1011/g ; s/E/0111/g ; s/F/1111/g
- s/.\{7\}/&00 /g ; # Complete to 9 bit and split nonets.
- s/101100000 $// ; # Remove CR on octet boundary.
- s/[^ ]*$// ; # Remove redundant bits.
- s/110110000 \(.\{7\}\)00/\110/g ; # Escape to Extended table.
- s/\(...\)\(...\)\(...\) /\3 \2 \1 /g ; # Split and swap triplets.
- s/000/0/g ; s/100/1/g ; s/010/2/g ; s/110/3/g
- s/001/4/g ; s/101/5/g ; s/011/6/g ; s/111/7/g
- s/\(.\) \(.\) \(.\) /\1\2\3 /g ; # To octal representation.
- y/ /\n/ ; s/.$// ; # Split up to one byte per line.
- ' |
- while read -r S ; do printf '%b' "\\0${S}" ; done
- } # GSMPDUToEGSM
- IRAToEGSM () { # <text>
- # Recode text ($1) from IRA to Extended GSM Default Alphabet
- # (GSM 03.38 6.2.1). All non-GSM DA and non-IRA characters are ignored.
- printf '%s' "${1}" |
- LC_ALL=C tr -d '\000-\011\013-\014\016-\037\140\177-\377' |
- LC_ALL=C tr '\044\100\133-\137\173-\176' \
- '\002\000\274\257\276\224\021\250\300\251\275'
- } # IRAToEGSM
- EGSMToIRA () {
- # Recode text (stdin) from Extended GSM Default Alphabet to IRA
- # (GSM 03.38 6.2.1). All non-IRA characters are ignored, but characters
- # with diacritics are recoded in their corresponding ordinary.
- LC_ALL=C tr -d "$( printf '%s' \
- '\001\020\022-\036\100\137-\140\200-\223' \
- '\225-\247\252-\256\260-\273\277\301-\377' )" |
- LC_ALL=C tr "$( printf '%s' \
- '\000\002-\014\016\017\021\037\133-\136' \
- '\173-\177\224\250\251\257\274-\276\300' )" \
- "$( printf '%s' \
- '\100\044\131\145\145\165\151\157\143\012\117' \
- '\157\101\141\137\105\101\117\116\125\141\157' \
- '\156\165\141\136\173\175\134\133\176\135\174' )"
- } # EGSMToIRA
- UCS2PDUToUCS2 () { # <text>
- # Convert the string ($1) from hexadecimal representation of UCS2
- # encoding to UCS2-BE encoding and put it.
- # If the string is not a valid code, then put none.
- printf '%s\n' "${1}" | # '\n' is workaround for GNU sed.
- sed '
- /[^0-9A-Fa-f]/ d ; /^\(....\)*.\{1,3\}$/ d ; /^$/ d ; # Validity check.
- s/\(..\)/0x\1 /g ; y/ /\n/ ; s/.$// ; # Split up to one byte per line.
- ' |
- ( while read -r S ; do printf '%b' "\\0$( printf '%o' "${S}" )" ; done )
- } # UCS2PDUToUCS2
- GetEnc () { # <DCS> # For use in subshell only!
- # Extract Encoding type from Data Coding Scheme (GSM 03.38 5).
- D=$( printf '%u' "${1}" ) &&
- if { [ "${D}" -ne 17 ] && [ "${D}" -lt 68 ] ; } ||
- { [ "${D}" -ge 80 ] && [ "${D}" -lt 84 ] ; } ||
- { [ "${D}" -ge 128 ] && [ "${D}" -lt 244 ] ; } ; then echo 'GSM'
- elif { [ "${D}" -ge 72 ] && [ "${D}" -lt 76 ] ; } ||
- { [ "${D}" -ge 88 ] && [ "${D}" -lt 92 ] ; } ; then echo 'UCS2'
- else ! :
- fi
- } # GetEnc
- Chat () { # <script-string>...
- # Global constants: MTO - modem answers timeout ;
- # &3 - modem UI r/w file descriptor.
- # Exit code: 1 - script or parameter error, 2 I/O error or signal
- # caught, 3 - timeout, 4 - CME Error, 5 - Error reported, 6 - No Carrier.
- chat -s -S -t "${MTO}" \
- REPORT '+CME ERROR' ABORT '+CME ERROR' \
- ABORT ERROR ABORT 'NO CARRIER' "${@}" <&3 >&3
- } # Chat
- MkLock () { # <lockfilename> <retries> # For use in subshell only!
- # Create а uucp-style lockfile. If the file exist and contains PID
- # of live process, then put these PID on stdout and return nonzero.
- # Subshell is significant.
- F=${1} R=0 P='' C=$( printf '%d' "${2}" )
- set -o noclobber
- while [ ${R} -eq 0 ] && ! printf '%10u\n' ${$} 2>/dev/null > "${F}" ; do
- read -r P < "${F}" && [ "$( printf '%u' "${P}" )" = "${P}" ] &&
- { ! ps -p "${P}" || [ "${P}" = ${$} ] ; } && [ "${C}" -gt 0 ] &&
- rm -f "${F}" ; R=${?} C=$(( C - 1 ))
- done >/dev/null
- [ ${R} -ne 0 ] && printf '%s' "${P}"
- return ${R}
- } # MkLock
- TTYName () { # <file name> # For use in subshell only!
- # Put canonical absolute pathname of <file name> ($1) if it is
- # a read/write allowed terminal device, otherwise return non-zero code
- # and output is undefined.
- : < "${1}" && tty 0<> "${1}" # '0<>' for ksh93.
- # ': <' prevent create file by sh, subshell is significant.
- } # TTYName
- Stty () { stty -g && stty raw -echo ; } # Stty
- Rtty () { [ "${2}" ] && stty "${2}" < "${1}" ; } # Rtty
- CME_L () {
- # Get maximal CME Error verbosity level (GSM 07.07 9.1).
- Chat REPORT '+CMEE:' '' 'AT+CMEE=?' OK 2>&1 |
- sed 's/^.*\([0-9]\)[^0-9]*$/\1/'
- } # CME_L
- MInit () {
- Chat '' 'ATE0V1' OK && Chat '' "AT+CMEE=$( CME_L )" OK
- } # MInit
- SendReq () { # <request>
- # Global constants:
- # MTO - modem answers timeout ; NRTO - USSD answers timeout.
- Chat '' 'AT+CUSD=1,"'"$( EncodeReq "${1}" )"'",15' ECHO ON \
- TIMEOUT "${NRTO}" '+CUSD:' AT TIMEOUT "${MTO}" OK 2>&1
- # Set 'ECHO ON' owing to 'REPORT' string < 255 char's,
- # and last 'AT' for echo be on time.
- } # SendReq
- USSDSess () {
- # Main session loop.
- # Globals:
- # Req - ussd Request to send, then ussd answer.
- # ATp - Answer Type: 0 - done, 1 - continue. | Really this is local
- # DCS - Data Coding Sheme, then encoding. | vars of this function.
- # Msg - for Err messages, auxiliary. |
- ATp=2 # For initial send.
- while
- case ${ATp} in
- 1 ) echo '---(^D for session close)---' ; read -r Req ;;
- 0 ) Chat '' AT+CUSD=2 OK 2>/dev/null ; ! : ;; # Close session, if any.
- esac
- do
- if ! Req=$( SendReq "${Req}" ) ; then Err "${Req}"
- elif
- Req=${Req##*+CUSD: } ; ATp=$( echo "${Req%%,\"*}" | head -n 1 )
- [ "${ATp#[01]}" ]
- then
- case ${ATp} in
- 2 ) Err 'terminated by network' ;;
- 4 ) Err 'operation not supported' ;;
- 5 ) Err 'network timeout' ;;
- * ) Err "unrecognized answer type: ${ATp}" ;;
- esac
- elif
- DCS=$( echo "${Req##*\",}" | head -n 1 )
- Msg="unsupported DCS: '${DCS}'" ; ! DCS=$( GetEnc "${DCS}" )
- then Err "${Msg}"
- elif
- Req=${Req#?,\"} ; Req=${Req%\",*} ; ! ValidCode "${Req}" "${DCS}"
- then Err "wrong PDU code: ${Req}"
- else
- case ${DCS} in
- GSM ) DecodeGSM "${Req}" ;; UCS2 ) DecodeUCS2 "${Req}" ;;
- esac
- echo
- fi || ! ATp=0
- done
- return
- } # USSDSess
- case ${1} in
- -- ) shift ;; -n ) T='n' ; shift ;;
- -* ) while [ ${#} -ne 0 ] ; do shift ; done ;; # wrong parameter
- esac
- if [ "${T}" ] ; then # Text (coding, non-transparent) mode.
- EncodeReq () { printf '%s' "${1}" ; } # EncodeReq
- DecodeGSM () { printf '%s' "${1}" ; } # DecodeGSM
- DecodeUCS2 () { printf '%s' "${1}" ; } # DecodeUCS2
- ValidCode () { : ; } # ValidCode
- else # Transparent (PDU) mode.
- EncodeReq () { # <text>
- # Convert the string ($1) from IRA to hexadecimal representation
- # of packed 7-bit encoding of GSM Default Alphabet and put it.
- # (GSM 03.38 6.2.1, 6.1.2.3).
- IRAToEGSM "${1}" | EGSMToGSMPDU
- } # EncodeReq
- ValidCode () { # <text> <encoding>
- [ "1${1}" = "1${1#*[!0-9a-fA-F]}" ] &&
- [ $(( ${#1} % $( [ 'UCS2' = "${2}" ] && echo 4 || echo 2 ) )) -eq 0 ]
- } # ValidCode
- DecodeGSM () { # <text>
- # Convert the string ($1) from hexadecimal representation of packed
- # 7-bit encoding of GSM Default Alphabet to IRA and put it.
- # (GSM 03.38 6.2). If the string is not a valid code, then put none.
- GSMPDUToEGSM "${1}" | EGSMToIRA
- } # DecodeGSM
- DecodeUCS2 () { # <text>
- # Convert the string ($1) from hexadecimal representation of UCS2
- # to current locale's encoding and put it.
- UCS2PDUToUCS2 "${1}" | iconv -c -f UCS-2BE
- } # DecodeUCS2
- fi
- Req=${1} UItty=${2:-${UItty}}
- # ussd Request to send, User Interface tty device.
- ATp=2 # Answer Type. | Really this are
- DCS='' # Data Coding Sheme, then encoding. | local vars for USSDSess.
- LF='' SU='' # Lock File, Saved UI terminal settings.
- Msg='Usage: ussd [-n|--] <ussd-request> [<tty-device>]'
- # For Err messages, auxiliary.
- if [ ${#} -eq 0 ] || [ ${#} -ge 3 ] ; then Err "${Msg}"
- elif ! UItty=$( TTYName "${UItty}" 2>&1 ) ; then Err "${UItty}"
- elif
- LF=${LCKDir}/LCK..${UItty##*/}
- ! Msg="lockfile '${LF}' is owned by '$( MkLock "${LF}" 10 )'"
- then Err "${Msg}"
- else
- trap ' rm -f "${LF}" ; Rtty "${UItty}" "${SU}" ' EXIT
- { SU=$( Stty <&3 ) && { Msg=$( MInit 2>&1 ) || Err "${Msg}" ; } &&
- USSDSess
- } 3<> "${UItty}"
- fi
- exit # ussd
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement