#!/bin/bash -u #% flexdial -- automatic alternate numbers dialer VER='$Revision: 1.268 $'; #% Copyright (c) 2003 Richard Hawes under GPL v. 2 #-This program is free software; you can redistribute it and/or modify #-it under the terms of the GNU General Public License as published by #-the Free Software Foundation; either version 2, or (at your option) #-any later version. #-This program is distributed in the hope that it will be useful, #-but WITHOUT ANY WARRANTY; without even the implied warranty of #-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #-GNU General Public License for more details. #-You should have received a copy of the GNU General Public License #-along with this program; if not, write to the Free Software #-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #_ #_ FEATURES #_* automatically dial alternate numbers #_* uses a pipe to send chat verbose messages to diald and #_ to system logs at debug priority #_* uses a pipe to report connect speed to system log at info priority #_* messages to system log can be reduced by setting priority level #_ while letting all messages go to diald fifo #_ #_ AVAILABILITY #_Available at www.dma.org/~rhawes/programs/flexdial #_GPL Available at www.dma.org/~rhawes/site/COPYING #_ #_ SECURITY #_This program exports password variables to chat #_instead of passing passwords on the command line. #_Command lines can be seen in /proc and with ps -fC chat. #_Run as another user: #_connect "/bin/su -pc '/etc/flexdial/flexdial -n \"522-002 412-449\"' dialout" #_ #_ PREFIXES #_All names begin with an underscore (_) or a single letter and underscore. #_User defined variables and functions should use a different #_prefix to avoid conflicts with future versions. #_ #_ COMMENTS #_ To remove comments: #_perl -e 'while(<>){s/\s*#[-_=].*$//;print if length > 1;}' flexdial #_ To view documentation: #_grep '#[%_=]' < flexdial | sed -e 's/#[%_]//' -e 's/#[=]/(default)/' #_ #_ SYNOPSIS #_Usage: flexdial -d (print commands) -f `configuration_file' #_ -n `list of phone numbers' -o `eval string' #_ #_any variable can be changed with -o 'variable=value' #_Directory for configuration files. _CFG_DIR=/etc/flexdial; #= #_configuration file suffix _CFG_SFX="sh"; #= #_ #_ REQUIREMENTS #_This script requires the use of chat-1.9 or greater for full #_functionality. It should work with older versions of chat, #_but it will not be able to report the reason for a connection failure. #_This was tested on Bash version 2.05a.0(1)-release #_ #_ ERROR CODES #_The error code indicates either a configuration error, #_the number of the last successfully completed phase, #_or terminating on a signal readonly E_CFG=2 E_SIG=4; #_ UNCONNECTED PHASES #_The modem is in command mode. readonly P_ON_HK=10 P_OFF_HK=11 P_WAIT=12; #_Phase numbers 32-63 RESERVED for user defined phases. #_ CONNECTED PHASES (>=64) #_The modem is in online mode. #_It can know when to escape to command mode with escape sequence before #_giving the hangup command. (Remote system will not be sent `AT'.) readonly P_CONN=64 P_LOGIN=65 P_CALLBK=66; #_Phase numbers 96-127 RESERVED for user defined phases. #_ #_ #_*= ^ ^ ^ ^ ^ ^ =* #_ CONFIGURATION FILE #_Has a default suffix of .sh for file browsers #_*= v v v v v v =* #_ #_set to a non empty string to remove fifo's _CLEAN_UP=""; #= #_ #_date format for messages sent to diald fifo _DATE="-Iseconds"; #= #_ #_Set Dial Prefix to T for touch tone or P for Pulse dialing _DIAL=T; #= #_Use it to specify other commands such as dialing an outside line. #_For example, to dial 9, wait for dial tone(`W') use: #__DIAL=T9W #_ #_escape modem to command mode #_modem registers: #_S2=escape character #_S12=guard time _ESCAPE='\d\d\d\d\d+++\c'; #= #_ #_shell eval strings #_Use this to let PPP authentify you: _EVAL_AFTER=':'; #= #_ #_Use this to login: #_pwd=xyz; #__EVAL_AFTER='_log_in 60 name pwd' #_ #_An example of using callback: #__EVAL_AFTER='_log_in 60 name pwd&&_call_back 20&& #__phase_if 96 "start ppp" _chat 15 "annex:" ppp&& #__phase_if 97 ack _chat 15 PPP "\c"' #_ #_commands before dialing _EVAL_BEFORE=':'; #= _EVAL_BEGIN=':'; #= #_to redirect error path to debug pipe use: #__EVAL_BEGIN='exec 2>&$D_debug;'; _EVAL_END=':'; #= _EVAL_ERROR=':'; #= #_ #_If diald fifo is set up, it is used to send all messages #_to a diald monitoring program such as dctrl. #_Set to "" to avoid warnings if diald is not used. _FIFO_DIALD=/var/run/diald.fifo; #= #_ #_set suffix to null to use files instead of fifos. _FIFO_SUFF=.fifo; #= #_ #_commands to hash _HASH="chat date logger mkdir mknod rm"; #= #_ #_logger facility (see man 8 syslog-facility) _LOG_FACILITY=user; #= #_logger tag _LOG_TAG="flexdial[$$]"; #= #_ #_The initialization string for your modem _INITIAL='AT&FV1W2E0S12=249'; #= #_ #_space separated list of phone numbers _PHONE_NOS=""; #= #_ #_signals to trap _SIGNALS='HUP INT TERM'; #= #_ #_How long to wait for modem to connect _TIMEOUT=90; #= #_ #_prefix for temporary files or fifos _TMP_PREF=flexdial; #= #_ #_working directory and fifo location _TMP_DIR=/var/tmp/flexdial; #= #_ #_ #_*= ^ ^ ^ ^ ^ ^ =* #_ FUNCTIONS #_To use these functions, #_put shell command list in the variable `_EVAL_AFTER' #_*= v v v v v v =* #_ #_ _call_back `optional timeout in seconds' _call_back () { _hang_up $P_WAIT; _phase_if $P_CALLBK "call back" _modem ${1:-90} RING ATA; } #_ #_ _chat `timeout in seconds' chat script _chat () { r_chat $_CHAT '' "$@";} #_ #_ _do command list #_do command and print error message if it fails _do () { { "$@" >&$D_debug 2>&1;}||_err "$* failed!";} #_ #_ _err "message string" #_sends err priority messages _err () { e_exit w $p_val "$@";} #_ #_ _hang_up `optional phase #' _hang_up () { #-put modem in comand mode #-then hang up [ $p_val -ge $P_CONN ]&&m_esc||:; m_at||m_esc||:; m_h&&_phase ${1:-$P_ON_HK} "modem hung up"|| _warn "could not hang up modem"; } #_ #_ _info "message" _info () { _write info "$@";} #_ #_ _log_in `timeout in seconds' `user name' `name of pwd var' #_Log in to provider. #_Password passed thru environment instead of command line (more secure). #_The chat sequence to recognize that the remote system #_is asking for your user name. _SEQ_LOGIN='ogin--ogin'; #= #_The chat sequence to recognize that the remote system #_is asking for your password. _SEQ_PWD=assword; #= _log_in () { _phase_if $P_LOGIN login \ _secret "$3" $1 \ "$_SEQ_LOGIN" "$2" \ "$_SEQ_PWD" "\q\$$3" ; } #_ #_ _modem `timeout in seconds' chat script #_sends commands to modem _modem () { local M_TO=$1;shift; r_chat r_debug '' $M_TO ABORT ERROR "$@"||{ set -- $?; case $1 in 4) return 0;; *) return $1;; esac } } #_ #_ _open `fd #' `file' _open () { [ "$2" ]&&{ eval exec "$1>$2"||_warn "cannot open fd $1 to $2";} } #_ #_ _priority `priority level' #_ priority levels are the same key words as syslog priority #_ * err : error conditions #_ * warning : warning conditions #_ * notice : normal, but significant, condition #_ * info : informational message #_ * debug : debug-level message #_ #_ _phase `phase #' ["optional message"] _phase () { p_val=$1;shift; [ $# -lt 1 ]||_write notice "$@"; } #_ #_ _phase_if `phase #' `phase msg' command list #_if command succeeds, set next phase _phase_if () { local P_NO=$1 P_MG=$2;shift 2; { "$@";}&&_phase $P_NO "$P_MG succeeded!"|| _warn "$P_MG failed!"; } _priority () { set +u eval V_LVL=\$X_$1; set -u; case "$V_LVL" in [$X_err-$X_debug]);; *) V_LVL=$X_debug; e_cfg "no such priority level '$1' ";; esac } #_ #_ _report [info|debug|off] _report () { case "$1" in info|debug|off) _CHAT="r_$1";; *) e_cfg "$1 invalid report mode";; esac } #_ #_ _secret 'export list' `timeout in seconds' chat script _secret () { r_chat r_debug "$@";} #_ #_ _write priority "message string" < messages #_Pass a message on to diald and the system logs. _write () { local _PRI="$1" _COM=":" _MSG; eval [ $V_LVL -lt "\$X_$1" ]||_COM=logger; shift; _MSG="$*"; { while true; do [ -z "$_MSG" ]||{ echo -E "message `date $_DATE` $_LOG_TAG: $_MSG" >&$D_DIALD; echo -E "$_MSG"; } read -r _MSG||break; done }|$D_C $_COM -p "$_LOG_FACILITY.$_PRI" -t "$_LOG_TAG"; } #_ #_ _verbose [y|n] #_select chat verbose mode _verbose () { case "$1" in y*) _VERBOSE="-vsS";; n*) _VERBOSE="";; *) e_cfg "$1 is invalid _verbose mode";; esac } #_ #_ _warn "warning message" and fail and or list _warn () { _write warning "$@"; return $p_val;} D_C=""; d_check () { echo "flexdial $2" D_C="echo -E"; eval exec "$D_DIALD>&4"; } e_cfg () { e_exit w $E_CFG "$@";} E_EXIT=0; #-prevent endless error loops e_exit () { local _W=$1 _E=$2;shift 2; [ $E_EXIT -eq 0 ]&&{ E_EXIT=$_E; _write err "$@"; _hang_up; e_eval _EVAL_ERROR; }||:; f_exit $_W $E_EXIT; } e_eval () { { eval eval \"\$${1}\";}||_err "eval string $1 failed";} e_sig () { e_exit s $E_SIG "terminating on $1";} e_trap () { while [ $# -gt 0 ]; do trap "e_sig $1" $1;shift;done;} e_v () { e_cfg "flexdial $2";} A_Finfo=""; A_Fdebug=""; f_log () { local _FIL; eval _FIL=\"\$A_F$1\"; [ -f "$_FIL" ]&&{ _write $1 < "$_FIL"; } } f_exit () { set +x; f_close; e_eval _EVAL_END; [ "${_FIFO_SUFF}" ]||{ f_log info;f_log debug;} [ -z "$_CLEAN_UP" ]||{ [ "$1" = w ]&&wait; _do rm -f "$A_Finfo" "$A_Fdebug"; } exit $2; } f_fifo () { { [ -p "$2" ]||_do mknod --mode=0660 "$2" p; }&&[ -w "$2" ]&&{ (set +x;_priority debug;_write $1;) <$2 & }||_warn "cannot open fifo '$2' for logging"; } f_file () { local _PIP; _PIP="${_TMP_PREF}.${_LOG_FACILITY}.${1}${_FIFO_SUFF}"; eval A_F$1=\"$_PIP\"; { [ -z "${_FIFO_SUFF}" ]||f_fifo $1 $_PIP;}&& eval _open \$D_$1 $_PIP; } f_close () { eval exec ">&4 2>&4 $D_DIALD>&5 $D_info>&4 $D_debug>&4";} m_at () { _modem 5 '' '' '' 'AT' 'OK-AT-OK' '\c';} m_dial () { _phase $P_OFF_HK "Dialing $1"; _chat $_TIMEOUT \ REPORT CONNECT \ ABORT ERROR \ ABORT BUSY \ ABORT "NO ANSWER" \ ABORT VOICE \ ABORT "NO DIALTONE" \ "" "ATD$_DIAL$1" \ CONNECT '\c' ; } m_esc () { _chat 15 '' "$_ESCAPE" OK '\c';} m_h () { _modem 5 '' ATH OK '\c';} m_init () { m_at||_err "Failed to attention the modem"; _modem 5 '' "$_INITIAL" OK '\c'\ ||_err "Failed to initialize the modem"; } r_chat () { local F_CH="$1" L_EXP="$2" M_TO="$3" C_ST=0;shift 3; [ -z "$L_EXP" ]||export $L_EXP; $F_CH $M_TO ABORT "\r\nNO CARRIER\r\n" "$@"||C_ST=$?; [ -z "$L_EXP" ]||export -n $L_EXP; case $C_ST in 0) ;; 1) e_cfg "Chat Error";; 2) e_cfg "Chat Script Error";; 3) _info "Chat Timeout";; 4) _info "No Carrier";; #- provided abort strings are in this order 5) e_cfg "bad modem command";; 6) _info "Busy";; 7) _info "No Answer";; 8) _info "Voice";; 9) _err "No Dial Tone";; *) _err "chat error: $C_ST";; esac return $C_ST; } r_debug () { $D_C chat $_VERBOSE -Et "$@" <&3 >&3 2>&$D_debug;} r_info () { $D_C chat -r "$A_Finfo" $_VERBOSE -Et "$@" <&3 >&3 2>&$D_debug;} r_off () { $D_C chat -r /dev/null $_VERBOSE -Et "$@" <&3 >&3 2>&$D_debug;} #-priority levels readonly X_err=3 X_warning=4 X_notice=5 X_info=6 X_debug=7; #- MAIN PROGRAM _CFG=""; readonly D_DIALD=6 D_info=7 D_debug=8; exec 3<&0 3>&1 4>&2 5<>/dev/null 0<&5; f_close; _phase $P_ON_HK; _priority info; _report info; _verbose no; e_trap ERR; while getopts :df:n:o: _OPT;do case $_OPT in d) d_check $VER;; f) _CFG="$OPTARG";; n) _PHONE_NOS="$OPTARG";; o) e_eval OPTARG;; :) e_cfg "option '-$OPTARG' missing an argument";; \?) e_cfg "invalid option: -$OPTARG";; esac;done if [ "$_CFG" ];then shift $(($OPTIND - 1)); OPTIND=1; _do cd "$_CFG_DIR"; F_CF="${_CFG}.${_CFG_SFX}"; source "./$F_CF" "$@"||e_cfg "sourcing $_CFG_DIR/$F_CF failed" fi hash $_HASH||e_cfg "hash error, PATH=$PATH"; $D_C _open $D_DIALD $_FIFO_DIALD||:; [ "$_PHONE_NOS" ]||e_cfg "no phone numbers"; [ -d "$_TMP_DIR" ]||$D_C _do mkdir --mode=0770 "$_TMP_DIR"; $D_C _do cd "$_TMP_DIR"; e_trap $_SIGNALS; $D_C f_file info||:; $D_C f_file debug||:; e_eval _EVAL_BEGIN; for N_PHONE in $_PHONE_NOS;do m_init; e_eval _EVAL_BEFORE; ! m_dial $N_PHONE||{ _phase $P_CONN&&eval "$_EVAL_AFTER&&f_exit w 0||:"; } [ $E_EXIT -eq 0 ]||exit $E_EXIT; _hang_up||break; done _err "all numbers failed";