#!/home/dob/bin/expect -- # ############################################################################ # # A semi-faithful reimplementation of the popular chat(1) program used # by many Unix folks (along with pppd(1)) to dial out a modem and connect # to their ISP. Chat(1) was originally written in the C language by # # Karl Fox # Morning Star Technologies, Inc. # 1760 Zollinger Road # Columbus, OH 43221 # (614)451-1883 # # This version written in Expect (Don Libes) by # # Daniel M. O'Brien # 6252 Beaver Run Rd # Pataskala, OH 43062 # # Why you might ask? 'cause I could! Nah, I have my reasons... :-) # # Several features are missing like HANGUP and BREAK support, along with # the pause features \d and \p, plus \q is missing. (Read the chat(1) man # page for details.) # # As it is written in the chat(1) man page.... # # COPYRIGHT # The chat program is in public domain. This is not the GNU # public license. If it breaks then you get to keep both # pieces. # # ############################################################################ # useful GLOBALs set argv0 [file tail $argv0] set LOG /var/tmp/chat.log.[pid] # quick check for usage... if {($argv == "-?") || ($argv == "-h")} { puts stderr "Usage: $argv0 \[-e] \[-v] \[-V] \[-t timeout] \[-r report-file] \{\[-f chat-file] | \[chat-script]\}" puts stderr "" puts stderr "\t-e\tStart with the echo option turned on. Echoing may" puts stderr "\t\talso be turned on or off at specific points in the" puts stderr "\t\tchat script by using the ECHO keyword. When echoing" puts stderr "\t\tis enabled, all output from the modem is echoed to" puts stderr "\t\tstderr." puts stderr "" puts stderr "\t-v\tRequest that the chat script be executed in a ver-" puts stderr "\t\tbose mode. The chat program will then log all text" puts stderr "\t\treceived from the modem and the output strings" puts stderr "\t\twhich it sends to syslogd(8)." puts stderr "" puts stderr "\t-V\tRequest that the chat script be executed in a" puts stderr "\t\tstderr verbose mode. The chat program will then log" puts stderr "\t\tall text received from the modem and the output" puts stderr "\t\tstrings which it sends to the stderr device." puts stderr "" puts stderr "\t-t\tSet the timeout for the expected string to be" puts stderr "\t\treceived. If the string is not received within the" puts stderr "\t\ttime limit then the reply string is not sent." puts stderr "\t\tDefault is 45 seconds." puts stderr "" puts stderr "\t-r\tSet the file for output of the report strings. If" puts stderr "\t\tyou use the keyword REPORT, the resulting strings" puts stderr "\t\tare written to this file. If this option is not" puts stderr "\t\tused and you still use REPORT keywords, the stderr" puts stderr "\t\tfile is used for the report strings." puts stderr "" puts stderr "\t-f\tRead the chat script from chat file. The use of" puts stderr "\t\tthis option is mutually exclusive with the chat" puts stderr "\t\tscript parameters." puts stderr "" puts stderr "\tscript\tIf the script is not specified in a file with the" puts stderr "\t\t-f option then the script is included as parameters" puts stderr "\t\tto the chat program." exit 1 } # getopt -- # # general purpose getopt routine similar to getopt(3) or getopt(1) # # arguments: # optString input parameter defines expected arguments # "ab:c" says expect -a -b arg -c # argvName name of "argument" string such as argv, matching # arguments are stripped off and named variable is # massaged down to resulting string without supplied # arguments # flagName name of output array where values of input args # are set, missing "flags" are set to 0, missing # argument values are set to "", # otherwise supplied flags are set to 1 and values # are set accordingly. # # example: # set test "-a -b bvalue -c one two three" # getopt "ab:cd:" test Flags # results: # $test == "one two three" # $Flags(a) == 1 # $Flags(b) == "bvalue" # $Flags(c) == 1 # $Flags(d) == "" proc getopt {optString argvName flagName} { global argv0 set return 0 upvar $argvName argv upvar $flagName FLAGS set optArgs {} set prevFlag "" foreach flag [split $optString {}] { if {$flag == ":"} { set FLAGS($prevFlag) "" append optArgs $prevFlag } else { set FLAGS($flag) 0 } set prevFlag $flag } regsub -all ":" $optString "" optString while {[regexp {^ *-([a-zA-Z]+)([^a-zA-Z]*.*)$} $argv "" flags argv]} { foreach flag [split $flags {}] { if {[regexp $flag $optString]} { if {[regexp $flag $optArgs]} { set FLAGS($flag) [lindex $argv 0] set argv [lrange $argv 1 end] } else { set FLAGS($flag) 1 } } else { puts stderr "$argv0: bad flag: $flag" set return 1 } } } set argv [string trim $argv] regsub "^-- *" $argv "" argv return $return } # syslog -- # # log $msg to system log as would other chat(1) # # assumes logger command available to communicate with syslogd(8) proc syslog {msg} { global argv0 catch {exec logger -i -p local2.info -t $argv0 $msg} return 0 } # verbose -- # # check echo or verbosity flags, log message accordingly proc verbose {msg} { global FLAGS if {$FLAGS(v)} { syslog $msg } if {$FLAGS(V) || $FLAGS(e)} { puts stderr $msg } return 0 } # gleanLog -- # # open Expect debug log and find last expect: line showing what was # input from modem! # # results: # return string found or NULL string proc gleanLog {log} { set f [open $log r] set msg "" foreach line [split [read $f] "\n"] { regexp "^expect: does \"(\[^\"]+)\"" $line "" msg } close $f return $msg } # report -- # # per chat(1), if input string matched one of the REPORT strings # write it to report file proc report {} { global reportList expect_out FLAGS if {[lsearch -exact $reportList $expect_out(0,string)] != -1} { puts $FLAGS(rfile) $expect_out(0,string) } return 0 } # abort -- # # abort condition was encountered, timeout or matching ABORT string, # close out log and notify everyone (report, etc), and exit # # argument: # code exit code to shell proc abort {code} { global expect_out LOG exp_internal 0 verbose [gleanLog $LOG] file delete $LOG report verbose "ABORTING on ($expect_out(0,string))" exit $code } # cnvChar -- # # massage input string to remove escapes and create binary data as # requested # # arguments: # s input string # # results: # returns resulting modified data # more work needs to go here to process more \options... proc cnvChar {s} { set o {} for {set i 0} {$i < [string length $s]} {incr i} { set c [string range $s $i $i] if {$c == "^"} { incr i scan [string range $s $i $i] "%c" c incr c -64 append o [format "%c" $c] } else { append o $c } } while {[regexp {(.*)\\([01][0-7][0-7])(.*)} $o {} f c l]} { scan $c %o c set o "$f[format "%c" $c]$l" } return $o } # prepOutput -- # # preprocess output string, cleaning up META characters, etc. # # arguments: # out output string to be cleaned up # # results: # cleaned up string is returned proc prepOutput {out} { if {$out == "EOT"} { set out "^D" } elseif {$out == "BREAK"} { puts stderr "UNABLE TO SEND BREAK, SENDING return INSTEAD" set out "\r" } elseif {![regexp {(.*)\\c$} $out "" out]} { append out "\r" } # regsub -all {\\N} $out "^@" out return [cnvChar $out] } # doExpectSend -- # # heart of interaction with modem, waits for pattern and then sends # resulting action string # # arguments: # pat string to watch for from modem # act "action" string to send to modem when pat is found proc doExpectSend {pat act} { global LOG expect_out verbose "expect ($pat)" catch {file delete $LOG} exp_internal -f $LOG 0 expect -ex $pat { exp_internal 0 verbose [gleanLog $LOG] file delete $LOG verbose "$expect_out(0,string) -- got it" report send [prepOutput $act] verbose "send ($act)" return 0 } timeout { set expect_out(0,string) \ "TIMEOUT expecting($pat)" return 1 } return 1 } # main program getopt "f:t:r:vVe" argv FLAGS if {$FLAGS(t) != ""} { set timeout $FLAGS(t) verbose "timeout set to $timeout seconds" } else { set timeout 45 } if {$FLAGS(f) == ""} { set script $argv } elseif {[catch {open $FLAGS(f) r} f]} { puts stderr "$argv0: $f" exit 1 } else { set TOK "\['\"](\[^'\"]*)\['\"]" set WS "\[ \t]+" set script {} foreach line [split [read $f] "\n"] { if {[regexp "^#" $line]} { continue } set line [string trim $line] if {[regexp "${TOK}${WS}${TOK}" $line {} pat act]} { append script " {$pat} {$act}" } elseif {[regexp "${TOK}${WS}(.*)" $line {} pat act]} { append script " {$pat} $act" } elseif {[regexp "(.*)${WS}${TOK}" $line {} pat act]} { append script " $pat {$act}" } else { append script " $line" } } close $f set script [string trimleft $script] } if {$FLAGS(r) != ""} { if {[catch {open $FLAGS(r) w} f]} { puts stderr "$argv0: $f" exit 1 } else { set FLAGS(rfile) $f } } else { set FLAGS(rfile) stderr } # preprocess input script a little set expList {} for {set i 0} {$i < [llength $script]} {incr i} { set pat [lindex $script $i] incr i set act [lindex $script $i] switch -- $pat { "CLR_REPORT" { lappend expList [list $pat "DUMMY"] incr i -1 } "CLR_ABORT" { lappend expList [list $pat "DUMMY"] incr i -1 } "HANGUP" { puts stderr "$argv0: $pat not supported, yet" } default { lappend expList [list $pat $act] } } } log_user 0 #stty raw -echo spawn -open stdin set reportList {} set abortList {} set abortCnt 3 foreach pair $expList { set pat [lindex $pair 0] set act [lindex $pair 1] switch -- $pat { "REPORT" { lappend reportList $act verbose "report ($act)" } "CLR_REPORT" { set reportList {} } "ABORT" { incr abortCnt append abortList " \"$act\" {abort $abortCnt}" verbose "abort on ($act)" } "CLR_ABORT" { set abortList {} set abortCnt 3 } "TIMEOUT" { set timeout $act verbose "timeout set to $timeout seconds" } "ECHO" { if {$act == "ON"} { set $FLAGS(e) 1 } elseif {$act == "OFF"} { set $FLAGS(e) 0 } else { verbose "unknown ECHO option $act" } } "SAY" { puts stderr $act } "" { send [prepOutput $act] verbose "send ($act)" } default { if {[llength $abortList]} { eval expect_before $abortList } # check for subexpect/subsend string if {[regexp "(\[^-]+)-(\[^-]+)-(\[^-])" $pat "" pat1 subsnd pat2]} { if {[doExpectSend $pat1 $act]} { send [prepOutput $subsnd] verbose "send ($subsnd)" if {[doExpectSend $pat2 $act]} { abort 3; # timeout! } } } else { if {[doExpectSend $pat $act]} { abort 3; # timeout! } } } } } exit 0