Advertisement
abw

ussd.sh

abw
Jan 12th, 2020
469
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 11.63 KB | None | 0 0
  1. #!/bin/sh
  2.  
  3.   # Usage: ussd [-n|--] '<request>' [<tty-device>]
  4.   #   Send USSD request <request> to cellular network via modem port
  5.   # <tty-device> and print the response.  Then, if a session is opened,
  6.   # read stdin for answers and send its.
  7.   #   Alexander B. Waldner, 2012-2019
  8.  
  9.   UItty=/dev/ttyUSB2 # Default UserInterface tty device.
  10.   NRTO=15            # Network Response TimeOut (sec).
  11.   MTO=2              # Modem TimeOut (sec).
  12.   LCKDir=/var/lock   # uucp-style LoCKfile Directoty.
  13.   T=''               # Transmission mode.
  14.  
  15.   chat () { /usr/sbin/chat "${@}" ; } # Location of chat program.
  16.  
  17.   Err () { # <message>  # Always "unsuccess".
  18.     printf '%s: %s\n' "${0##*/}" "${1}" >&2 ; ! :
  19.   } # Err
  20.  
  21.   EGSMToGSMPDU () {
  22.     #   Convert text (stdin) from 8-bit encoded Extended GSM Default Alphabet
  23.     # to hexadecimal representation of packed 7-bit encoding and put it.
  24.     # (GSM 03.38 6.1.2.3).
  25.     od -v -A n -t x1 | tr '\n' ' ' | # Get hexadecimal representation.
  26.     sed '
  27.      y/ABCDEF/abcdef/ ; # Overcaution.
  28.      s/ [89a-f]/ 1b&/ ; # Unescape, if > 127.
  29.      s/[[:blank:]]//g ; s/\(.\)\(.\)/\2\1/g ; # Swap quadruples.
  30.      s/0/0000/g ; s/1/1000/g ; s/2/0100/g ; s/3/1100/g
  31.      s/4/0010/g ; s/5/1010/g ; s/6/0110/g ; s/7/1110/g ; # from LSB to MSB,
  32.      s/8/0001/g ; s/9/1001/g ; s/a/0101/g ; s/b/1101/g ; # left to right.
  33.      s/c/0011/g ; s/d/1011/g ; s/e/0111/g ; s/f/1111/g
  34.      s/\(.\{7\}\)./\1/g ; # Remove each 8-th bit.
  35.      s/.\{8\}/& /g ; # Split octets.
  36.      s/.1011000 $/& 10110000 / ; # Add CR if need.
  37.      s/ .$/&1011000 / ; # Pad with CR
  38.      s/ ..$/&000000 / ; s/ ...$/&00000 / ; s/ ....$/&0000 / ; #   Pad with
  39.      s/ .....$/&000 / ; s/ ......$/&00 / ; s/ .......$/&0 / ; # zero bites.
  40.      s/^.\{7\}$/&0 / ; # Special case - one character.
  41.      s/\(....\)\(....\) /\2 \1 /g ; # Split and swap quadruples.
  42.      s/0000/0/g ; s/1000/1/g ; s/0100/2/g ; s/1100/3/g
  43.      s/0010/4/g ; s/1010/5/g ; s/0110/6/g ; s/1110/7/g
  44.      s/0001/8/g ; s/1001/9/g ; s/0101/A/g ; s/1101/B/g
  45.      s/0011/C/g ; s/1011/D/g ; s/0111/E/g ; s/1111/F/g
  46.      s/ //g
  47.    '
  48.   } # EGSMToGSMPDU
  49.  
  50.   GSMPDUToEGSM () { # <text>
  51.     #   Convert the string ($1) from hexadecimal representation of packed
  52.     # 7-bit encoding of GSM Default Alphabet to 8-bit encoded Extended GSM DA
  53.     # and put it. (GSM 03.38 6.2).
  54.     #   If the string is not a valid code, then put none.
  55.     printf '%s\n' "${1}" | # '\n' is workaround for GNU sed.
  56.     sed '
  57.      /[^0-9A-Fa-f]/ d ; /^\(..\)*.$/ d ; /^$/ d ; # Validity check.
  58.      y/abcdef/ABCDEF/ ; # Overcaution.
  59.      s/\(.\)\(.\)/\2\1/g ; # Swap quadruples.
  60.      s/0/0000/g ; s/1/1000/g ; s/2/0100/g ; s/3/1100/g
  61.      s/4/0010/g ; s/5/1010/g ; s/6/0110/g ; s/7/1110/g ; # from LSB to MSB,
  62.      s/8/0001/g ; s/9/1001/g ; s/A/0101/g ; s/B/1101/g ; # left to right.
  63.      s/C/0011/g ; s/D/1011/g ; s/E/0111/g ; s/F/1111/g
  64.      s/.\{7\}/&00 /g ; # Complete to 9 bit and split nonets.
  65.      s/101100000 $// ; # Remove CR on octet boundary.
  66.      s/[^ ]*$//      ; # Remove redundant bits.
  67.      s/110110000 \(.\{7\}\)00/\110/g      ; # Escape to Extended table.
  68.      s/\(...\)\(...\)\(...\) /\3 \2 \1 /g ; # Split and swap triplets.
  69.      s/000/0/g ; s/100/1/g ; s/010/2/g ; s/110/3/g
  70.      s/001/4/g ; s/101/5/g ; s/011/6/g ; s/111/7/g
  71.      s/\(.\) \(.\) \(.\) /\1\2\3 /g  ; # To octal representation.
  72.      y/ /\n/ ; s/.$// ; # Split up to one byte per line.
  73.    ' |
  74.     while read -r S ; do printf '%b' "\\0${S}" ; done
  75.   } # GSMPDUToEGSM
  76.  
  77.   IRAToEGSM () { # <text>
  78.     #   Recode text ($1) from IRA to Extended GSM Default Alphabet
  79.     # (GSM 03.38 6.2.1).  All non-GSM DA and non-IRA characters are ignored.
  80.     printf '%s' "${1}" |
  81.     LC_ALL=C tr -d '\000-\011\013-\014\016-\037\140\177-\377' |
  82.     LC_ALL=C tr '\044\100\133-\137\173-\176' \
  83.                 '\002\000\274\257\276\224\021\250\300\251\275'
  84.   } # IRAToEGSM
  85.  
  86.   EGSMToIRA () {
  87.     #   Recode text (stdin) from Extended GSM Default Alphabet to IRA
  88.     # (GSM 03.38 6.2.1).  All non-IRA characters are ignored, but characters
  89.     # with diacritics are recoded in their corresponding ordinary.
  90.     LC_ALL=C tr -d "$( printf '%s' \
  91.                         '\001\020\022-\036\100\137-\140\200-\223' \
  92.                         '\225-\247\252-\256\260-\273\277\301-\377' )" |
  93.     LC_ALL=C tr "$( printf '%s' \
  94.                      '\000\002-\014\016\017\021\037\133-\136' \
  95.                      '\173-\177\224\250\251\257\274-\276\300' )" \
  96.                 "$( printf '%s' \
  97.                      '\100\044\131\145\145\165\151\157\143\012\117' \
  98.                      '\157\101\141\137\105\101\117\116\125\141\157' \
  99.                      '\156\165\141\136\173\175\134\133\176\135\174' )"
  100.   } # EGSMToIRA
  101.  
  102.   UCS2PDUToUCS2 () { # <text>
  103.     #   Convert the string ($1) from hexadecimal representation of UCS2
  104.     # encoding to UCS2-BE encoding and put it.
  105.     #   If the string is not a valid code, then put none.
  106.     printf '%s\n' "${1}" | # '\n' is workaround for GNU sed.
  107.     sed '
  108.      /[^0-9A-Fa-f]/ d ; /^\(....\)*.\{1,3\}$/ d ; /^$/ d ; # Validity check.
  109.      s/\(..\)/0x\1 /g ; y/ /\n/ ; s/.$// ; # Split up to one byte per line.
  110.    ' |
  111.     ( while read -r S ; do printf '%b' "\\0$( printf '%o' "${S}" )" ; done )
  112.   } # UCS2PDUToUCS2
  113.  
  114.   GetEnc () { # <DCS>    # For use in subshell only!
  115.     # Extract Encoding type from Data Coding Scheme (GSM 03.38 5).
  116.     D=$( printf '%u' "${1}" ) &&
  117.     if   { [ "${D}" -ne  17 ] && [ "${D}" -lt  68 ] ; } ||
  118.          { [ "${D}" -ge  80 ] && [ "${D}" -lt  84 ] ; } ||
  119.          { [ "${D}" -ge 128 ] && [ "${D}" -lt 244 ] ; } ; then echo 'GSM'
  120.     elif { [ "${D}" -ge  72 ] && [ "${D}" -lt  76 ] ; } ||
  121.          { [ "${D}" -ge  88 ] && [ "${D}" -lt  92 ] ; } ; then echo 'UCS2'
  122.     else ! :
  123.     fi
  124.   } # GetEnc
  125.  
  126.   Chat () { # <script-string>...
  127.     #   Global constants: MTO - modem answers timeout ;
  128.     # &3 - modem UI r/w file descriptor.
  129.     #   Exit code: 1 - script or parameter error, 2 I/O error or signal
  130.     # caught, 3 - timeout, 4 - CME Error, 5 - Error reported, 6 - No Carrier.
  131.     chat -s -S -t "${MTO}" \
  132.       REPORT '+CME ERROR' ABORT '+CME ERROR' \
  133.       ABORT ERROR ABORT 'NO CARRIER' "${@}" <&3 >&3
  134.   } # Chat
  135.  
  136.   MkLock () { # <lockfilename> <retries>    # For use in subshell only!
  137.     #   Create а uucp-style lockfile.  If the file exist and contains PID
  138.     # of live process, then put these PID on stdout and return nonzero.
  139.     #   Subshell is significant.
  140.     F=${1} R=0 P='' C=$( printf '%d' "${2}" )
  141.     set -o noclobber
  142.     while [ ${R} -eq 0 ] && ! printf '%10u\n' ${$} 2>/dev/null > "${F}" ; do
  143.       read -r P < "${F}" && [ "$( printf  '%u' "${P}" )" = "${P}" ] &&
  144.       { ! ps -p "${P}" || [ "${P}" = ${$} ] ; } && [ "${C}" -gt 0 ] &&
  145.       rm  -f "${F}" ; R=${?} C=$(( C - 1 ))
  146.     done >/dev/null
  147.     [ ${R} -ne 0 ] && printf '%s' "${P}"
  148.     return ${R}
  149.   } # MkLock
  150.  
  151.   TTYName () { # <file name>    # For use in subshell only!
  152.     #   Put canonical absolute pathname of <file name> ($1) if it is
  153.     # a read/write allowed terminal device, otherwise return non-zero code
  154.     # and output is undefined.
  155.     : < "${1}" && tty 0<> "${1}" # '0<>' for ksh93.
  156.       # ': <' prevent create file by sh, subshell is significant.
  157.   } # TTYName
  158.  
  159.   Stty () { stty -g && stty raw -echo ; } # Stty
  160.   Rtty () { [ "${2}" ] && stty "${2}" < "${1}" ; } # Rtty
  161.  
  162.   CME_L () {
  163.     # Get maximal CME Error verbosity level (GSM 07.07 9.1).
  164.     Chat REPORT '+CMEE:' '' 'AT+CMEE=?' OK 2>&1 |
  165.     sed 's/^.*\([0-9]\)[^0-9]*$/\1/'
  166.   } # CME_L
  167.  
  168.   MInit () {
  169.     Chat '' 'ATE0V1' OK && Chat '' "AT+CMEE=$( CME_L )" OK
  170.   } # MInit
  171.  
  172.   SendReq () { # <request>
  173.     #   Global constants:
  174.     # MTO - modem answers timeout ; NRTO - USSD answers timeout.
  175.     Chat '' 'AT+CUSD=1,"'"$( EncodeReq "${1}" )"'",15' ECHO ON \
  176.          TIMEOUT "${NRTO}" '+CUSD:' AT TIMEOUT "${MTO}" OK 2>&1
  177.      #   Set 'ECHO ON' owing to 'REPORT' string < 255 char's,
  178.      # and last 'AT' for echo be on time.
  179.   } # SendReq
  180.  
  181.   USSDSess () {
  182.     #   Main session loop.
  183.     # Globals:
  184.     #   Req - ussd Request to send, then ussd answer.
  185.     #   ATp - Answer Type: 0 - done, 1 - continue. |   Really this is local
  186.     #   DCS - Data Coding Sheme, then encoding.    | vars of this function.
  187.     #   Msg - for Err messages, auxiliary.         |
  188.  
  189.     ATp=2 # For initial send.
  190.  
  191.     while
  192.       case ${ATp} in
  193.         1 ) echo '---(^D for session close)---' ; read -r Req ;;
  194.         0 ) Chat '' AT+CUSD=2 OK 2>/dev/null ; ! : ;; # Close session, if any.
  195.       esac
  196.     do
  197.       if ! Req=$( SendReq "${Req}" ) ; then Err "${Req}"
  198.       elif
  199.         Req=${Req##*+CUSD: } ; ATp=$( echo "${Req%%,\"*}" | head -n 1 )
  200.         [ "${ATp#[01]}" ]
  201.       then
  202.         case ${ATp} in
  203.           2 ) Err 'terminated by network'   ;;
  204.           4 ) Err 'operation not supported' ;;
  205.           5 ) Err 'network timeout'         ;;
  206.           * ) Err "unrecognized answer type: ${ATp}" ;;
  207.         esac
  208.       elif
  209.         DCS=$( echo "${Req##*\",}" | head -n 1 )
  210.         Msg="unsupported DCS: '${DCS}'" ; ! DCS=$( GetEnc "${DCS}" )
  211.       then Err "${Msg}"
  212.       elif
  213.         Req=${Req#?,\"} ; Req=${Req%\",*} ; ! ValidCode "${Req}" "${DCS}"
  214.       then Err "wrong PDU code: ${Req}"
  215.       else
  216.         case ${DCS} in
  217.           GSM ) DecodeGSM "${Req}" ;; UCS2 ) DecodeUCS2 "${Req}" ;;
  218.         esac
  219.         echo
  220.       fi || ! ATp=0
  221.     done
  222.     return
  223.   } # USSDSess
  224.  
  225.   case ${1} in
  226.     -- ) shift ;; -n ) T='n' ; shift ;;
  227.     -* ) while [ ${#} -ne 0 ] ; do shift ; done ;; # wrong parameter
  228.   esac
  229.  
  230.   if [ "${T}" ] ; then # Text (coding, non-transparent) mode.
  231.  
  232.     EncodeReq  () { printf '%s' "${1}" ; } # EncodeReq
  233.     DecodeGSM  () { printf '%s' "${1}" ; } # DecodeGSM
  234.     DecodeUCS2 () { printf '%s' "${1}" ; } # DecodeUCS2
  235.     ValidCode  () { : ; } # ValidCode
  236.  
  237.   else # Transparent (PDU) mode.
  238.  
  239.     EncodeReq () { # <text>
  240.       #   Convert the string ($1) from IRA to hexadecimal representation
  241.       # of packed 7-bit encoding of GSM Default Alphabet and put it.
  242.       # (GSM 03.38 6.2.1, 6.1.2.3).
  243.       IRAToEGSM "${1}" | EGSMToGSMPDU
  244.     } # EncodeReq
  245.  
  246.     ValidCode () { # <text> <encoding>
  247.       [ "1${1}" = "1${1#*[!0-9a-fA-F]}" ] &&
  248.       [ $(( ${#1} % $( [ 'UCS2' = "${2}" ] && echo 4 || echo 2 ) )) -eq 0 ]
  249.     } # ValidCode
  250.  
  251.     DecodeGSM () { # <text>
  252.       #   Convert the string ($1) from hexadecimal representation of packed
  253.       # 7-bit encoding of GSM Default Alphabet to IRA and put it.
  254.       # (GSM 03.38 6.2).  If the string is not a valid code, then put none.
  255.       GSMPDUToEGSM "${1}" | EGSMToIRA
  256.     } # DecodeGSM
  257.  
  258.     DecodeUCS2 () { # <text>
  259.       #   Convert the string ($1) from hexadecimal representation of UCS2
  260.       # to current locale's encoding and put it.
  261.       UCS2PDUToUCS2 "${1}" | iconv -c -f UCS-2BE
  262.     } # DecodeUCS2
  263.  
  264.   fi
  265.  
  266.   Req=${1} UItty=${2:-${UItty}}
  267.     # ussd Request to send, User Interface tty device.
  268.   ATp=2  # Answer Type.                      |   Really this are
  269.   DCS='' # Data Coding Sheme, then encoding. | local vars for USSDSess.
  270.   LF='' SU='' # Lock File, Saved UI terminal settings.
  271.   Msg='Usage: ussd [-n|--] <ussd-request> [<tty-device>]'
  272.     # For Err messages, auxiliary.
  273.  
  274.   if [ ${#} -eq 0 ] || [ ${#} -ge 3 ] ; then Err "${Msg}"
  275.   elif ! UItty=$( TTYName "${UItty}" 2>&1 ) ; then Err "${UItty}"
  276.   elif
  277.     LF=${LCKDir}/LCK..${UItty##*/}
  278.     ! Msg="lockfile '${LF}' is owned by '$( MkLock "${LF}" 10 )'"
  279.   then Err "${Msg}"
  280.   else
  281.     trap ' rm -f "${LF}" ; Rtty "${UItty}" "${SU}" ' EXIT
  282.     { SU=$( Stty <&3 ) && { Msg=$( MInit 2>&1 ) || Err "${Msg}" ; } &&
  283.       USSDSess
  284.     } 3<> "${UItty}"
  285.   fi
  286.  
  287. exit # ussd
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement