diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-09-16 10:43:00 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-09-16 17:10:01 +0300 |
commit | 72335d68e35f99835a649ce01bc86dc05d9b6893 (patch) | |
tree | 466600964de53464c2a53f5b5d73f1c174df8ce3 | |
parent | 1b1357c01336bbf06175ebbace176f8ce8756e82 (diff) | |
download | rex-72335d68e35f99835a649ce01bc86dc05d9b6893.tar.gz rex-72335d68e35f99835a649ce01bc86dc05d9b6893.tar.bz2 |
Improve error handling and reporting
Use StrictHostKeyChecking=yes to avoid problems with eventual
interactive prompts from ssh/scp. Use LogLevel=VERBOSE to
correctly determine whether we succeeded in authenticating to
the server. Catch error output from ssh/scp in INIT state.
If any appears, reflect it on the standard error and mark
error for that host.
* README: Reword intro
* rex (max_scp_retry_count): Remove config setting.
(terror): Take optional argument -nosave.
(warning): Pass -nosave in call to terror
(hostpass): Throw error if no password defined. All uses
updated.
(runcp,runcmd): Force StrictHostKeyChecking=yes and
LogLevel=VERBOSE.
Catch state changes.
Reflect on stderr whatever received in INIT state. Raise error
state if anything was.
(runcprev): Rewrite using state machine similar to runcp.
(rex_login): Rewrite using state machine similar to runcmd.
Catch errors. Honor -Z option and earlycmd settings.
(closeproc): Log state transition.
(transition): Log state transition.
Call hook if defined.
* rex.man8: Update.
-rw-r--r-- | README | 15 | ||||
-rwxr-xr-x | rex | 662 | ||||
-rw-r--r-- | rex.man8 | 12 |
3 files changed, 457 insertions, 232 deletions
@@ -3,12 +3,23 @@ README for Rex * Introduction Rex is a remote execution utility that runs a supplied command -on several remote hosts in succession or in parallel. It can also be -used to distribute a file or files to several hosts. +on a set of remote hosts. Arbitrary data can be supplied to the +command in the form of local files, which will be transferred to each +remote host prior to running the command and removed afterward. Both +parallel and sequential execution is supported. + +Special mode exists to distribute a file or files to several hosts. Rex is written in TCL and provides extensive scripting facilities. It provides a convenient way to administrate multiple servers. +The program is designed to operate in a minimalistic environment. It +is self-contained and does not need any external libraries. The only +requirements are: + +** Expect v. 5.44.1.13 or newer +** ssh and scp + * Installing Change to the toplevel source directory and type @@ -27,7 +27,6 @@ array set config { mode command sudo "" prompt "(%|#|\\$) $" - max_scp_retry_count 5 debug 0 option,jobs 1 option,resolve 1 @@ -39,6 +38,8 @@ if {[info exists env(EXPECT_DEBUG)]} { exp_internal -f $env(EXPECT_DEBUG) 1 } +set ssh_options {-oStrictHostKeyChecking=yes -oLogLevel=VERBOSE} + # config_option KEY [VAR] # Return the value of the configuration option KEY, or "" if not # defined. @@ -912,19 +913,33 @@ proc debug {args} { } proc terror {args} { - global argv0 errors - - if {![info exist errors]} { - set errors {} + global argv0 + set save 1 + while {[llength $args] > 0} { + switch -- [lindex $args 0] { + -nosave { + set args [lreplace $args 0 0] + set save 0 + } + -- { + set args [lreplace $args 0 0] + break + } + default { break } + } } + set msg [join $args] - set errors [linsert $errors end $msg] + if {$save} { + global errors + lappend errors $msg + } send_error "$argv0: $msg\n" } proc warning {args} { if {[config_option verbose]} { - terror "warning:" {*}$args + terror -nosave "warning:" {*}$args } } @@ -1368,7 +1383,7 @@ proc hostpass {host} { if {$x == ""} { set x [getans -echo "Default password:"] if { $x == "" } { - exit 0 + return -code error "No password for $host" } rexdbput pass [passenc $x] return $x @@ -1381,8 +1396,7 @@ proc hostpass {host} { if {[info exist config(pass)]} { return [passdec $config(pass)] } - # FIXME: Throw an error? - return "" + return -code error "No password for $host" } proc add_host_key_list {host key varname} { @@ -1469,8 +1483,8 @@ proc argcvshift {} { # Copying to and from remote hosts and running remote commands # ####################################################################### proc runcp {args} { - global config argv0 - set password_sent 0 + global config argv0 ssh_options + set hosts [lindex $args 0] set retry_count 0 set cmd_stub [lrange $args 1 end-1] @@ -1482,43 +1496,63 @@ proc runcp {args} { foreach host $hosts { set cmd $cmd_stub lappend cmd [hostuser $host]@$host:[lindex $args end] - spawn -noecho scp {*}$cmd + spawn -noecho scp {*}$ssh_options {*}$cmd lappend sidlist $spawn_id set sidinfo($spawn_id,host) $host set sidinfo($spawn_id,cmd) $cmd - set sidinfo($spawn_id,state) INIT + transition $spawn_id INIT } while {[llength $sidlist] > 0} { expect { -i $sidlist - "ermission denied" { + -re {^Authenticated to} { set sid $expect_out(spawn_id) - set host $sidinfo($sid,host) - terror "[hostuser $host]@[hostname $host]: $expect_out(buffer)" - closeproc $sid - continue + transition $sid AUTHENTICATED } - - denied { + + -re {assword:|assword for .*:} { set sid $expect_out(spawn_id) set host $sidinfo($sid,host) - terror "bad password for user [hostuser $host] on [hostname $host]" - closeproc $sid - exp_continue + debug 3 "$sid: $host prompted for password in state $sidinfo($sid,state)" + switch -- $sidinfo($sid,state) { + {INIT} { + if {[catch {hostpass $host} result] == 0} { + send -i $sid "$result\r" + transition $sid PASS_SENT + } else { + terror "$host: requires password, but none is given" + closeproc $sid + } + } + {PASS_SENT} { + terror "bad password for user [hostuser $host] on [hostname $host]" + closeproc $sid + } + default { + terror "unexpected password prompt from $host in state $sidinfo($sid,state)" + closeproc $sid + } + } + } + + -re {^[^\n]*\n} { + set sid $expect_out(spawn_id) + set text [regsub -all {[\r\n]} $expect_out(0,string) {}] + if {$sidinfo($sid,state) == "INIT"} { + terror -nosave "$sidinfo($sid,host): $text" + } elseif {[regexp {^scp: (.*)$} $text x msg]} { + terror "$sidinfo($sid,host): $msg" + } } - "*assword:" { + denied { set sid $expect_out(spawn_id) set host $sidinfo($sid,host) - if {$sidinfo($sid,state) == PASS_SENT} { - terror "bad password for user [hostuser $host] on [hostname $host]" - closeproc $sid - } else { - send -i $sidlist "[hostpass $host]\r" - set sidinfo($sid,state) PASS_SENT - } + terror "[hostuser $host]@[hostname $host]: $expect_out(buffer)" + closeproc $sid + continue } timeout { @@ -1532,17 +1566,25 @@ proc runcp {args} { eof { set sid $expect_out(spawn_id) + set host $sidinfo($sid,host) + set last_state $sidinfo($sid,state) closeproc $sid - wait -i $sid - # FIXME: analyze res + set res [wait -i $sid] + if {[lindex $res 2] == -1} { + terror "$host: operating system error: [lindex $res 3]" + } elseif {[lindex $res 3] != 0} { + terror "$host: copy failed: cp exit status [lindex $res 3]" + } elseif {$last_state != "AUTHENTICATED"} { + terror "$host: copy failed (in state $last_state)" + } } } } } proc runcprev {args} { - global config argv0 - set password_sent 0 + global config argv0 ssh_options + set host [lindex $args 0] set source [lindex $args 1] if {[llength $args] == 3} { @@ -1550,60 +1592,80 @@ proc runcprev {args} { } else { set dest $source } - set retry_count 0 - for {set retry 1} {$retry} {} { - set retry 0 - spawn -noecho scp [hostuser $host]@$host:$source $dest - expect { - denied { - terror "bad password for [hostuser $host] on [hostname $host]" - catch { close } - return 1 - } - "*assword:" { - if {$password_sent} { - terror "bad password for [hostuser $host] on [hostname $host]" - catch { close } - return 1 - } else { - send "[hostpass $host]\r" - incr password_sent - exp_continue - } + + log_user [config_option log] + + spawn -noecho scp {*}$ssh_options [hostuser $host]@$host:$source $dest + lappend sidlist $spawn_id + set sidinfo($spawn_id,host) $host + transition $spawn_id INIT + + while {[llength $sidlist] > 0} { + expect { + -i $sidlist + + -re {^Authenticated to} { + transition $spawn_id AUTHENTICATED } - -re "$source.*\[0-9\]+%" { - exp_continue + + -re {assword:|assword for .*:} { + debug 3 "$spawn_id: $host prompted for password in state $sidinfo($spawn_id,state)" + switch -- $sidinfo($spawn_id,state) { + {INIT} { + if {[catch {hostpass $host} result] == 0} { + send "$result\r" + transition $spawn_id PASS_SENT + } else { + terror "$host: requires password, but none is given" + closeproc $spawn_id + } + } + {PASS_SENT} { + terror "bad password for user [hostuser $host] on [hostname $host]" + closeproc $spawn_id + } + default { + terror "unexpected password prompt from $host in state $sidinfo($spawn_,state)" + closeproc $spawn_id + } + } } - "Are you sure you want to continue connecting (yes/no)?" { - send "yes\r" - exp_continue + + -re {^[^\n]*\n} { + set text [regsub -all {[\r\n]} $expect_out(0,string) {}] + if {$sidinfo($spawn_id,state) == "INIT"} { + terror -nosave "$sidinfo($spawn_id,host): $text" + } elseif {[regexp {^scp: (.*)$} $text x msg]} { + terror "$sidinfo($spawn_id,host): $msg" + } + } + + denied { + terror "[hostuser $host]@[hostname $host]: $expect_out(buffer)" + closeproc $spawn_id } + timeout { - terror "connection to [hostname $host] timed out" - catch { close } - return 1 + terror "connection to [hostname $host] timed out" + closeproc $spawn_id } - eof - } - set res [wait] - if {[lindex $res 2] == 0} { - if {[lindex $res 3] != 0} { - incr retry_count - if {$retry_count > $config(max_scp_retry_count)} { - terror "failed to copy file from [hostname $host]" - catch { close } - return 1 + + eof { + set last_state $sidinfo($spawn_id,state) + closeproc $spawn_id + set res [wait -i $spawn_id] + if {[lindex $res 2] == -1} { + terror "$host: operating system error: [lindex $res 3]" + return 1 + } elseif {[lindex $res 3] != 0} { + terror "$host: copy failed: cp exit status [lindex $res 3]" + return 1 + } elseif {$last_state != "AUTHENTICATED"} { + terror "$host: copy failed (in state $last_state)" + return 1 } - send_error "$argv0: Retrying connection to [hostname $host]\n" - set retry 1 - set password_sent 0 - } - } else { - terror "operating system error while copying script file to [hostname $host]" - catch { close } - return 1 - } - catch { close } + } + } } return 0 } @@ -1613,8 +1675,10 @@ proc closeproc {sid} { close -i $sid } upvar sidlist sidlist + upvar sidinfo sidinfo set idx [lsearch $sidlist $sid] set sidlist [lreplace $sidlist $idx $idx] + debug 2 "$sid transition $sidinfo($sid,state) => CLOSED" set sidinfo($sid,state) CLOSED return [expr [llength $sidlist] > 0] } @@ -1628,19 +1692,27 @@ proc lshift listVar { proc transition {sid state} { upvar sidinfo sidinfo + if {[info exist sidinfo($sid,state)]} { + debug 2 "$sid transition $sidinfo($sid,state) => $state" + } else { + debug 2 "$sid setting state $state" + } set sidinfo($sid,state) $state - ::hostproc::transition $sidinfo($sid,host) $state + upvar transition_hook hook + if {[info exist hook]} { + eval $hook $sidinfo($sid,host) $state + } } - + proc runcmd {hosts command} { - global config + global config ssh_options debug 2 "running $command on $hosts" log_user [config_option log] - + set transition_hook ::hostproc::transition foreach host $hosts { - spawn -noecho ssh [hostuser $host]@$host + spawn -noecho ssh {*}$ssh_options [hostuser $host]@$host lappend sidlist $spawn_id set sidinfo($spawn_id,host) $host debug 3 "$host on $spawn_id" @@ -1666,108 +1738,135 @@ proc runcmd {hosts command} { expect { -i $sidlist - "Are you sure you want to continue connecting (yes/no)?" { - send -i $expect_out(spawn_id) "yes\r" + -re {^Authenticated to} { + set sid $expect_out(spawn_id) + transition $sid AUTHENTICATED + if {[info exists sidinfo($sid,earlycmd)]} { + foreach cmd $sidinfo($sid,earlycmd) { + debug 3 "$sid: sending early $cmd" + send -i $sid "$cmd\r" + } + unset sidinfo($sid,earlycmd) + } } - - "*assword:" { + -re {assword:|assword for .*:} { set sid $expect_out(spawn_id) set host $sidinfo($sid,host) - if {$sidinfo($sid,state) == "INIT"} { - send -i $sid "[hostpass $host]\r" - transition $sid PASS_SENT - } elseif {$sidinfo($sid,state) == "COMMAND"} { - if {$config(sudo) != ""} { - send -i $sid "[hostpass $host]\r" - transition $sid SUDO - exp_continue - } else { + debug 3 "$sid: $host prompted for password in state $sidinfo($sid,state)" + switch -- $sidinfo($sid,state) { + {INIT} { + if {[catch {hostpass $host} result] == 0} { + send -i $sid "$result\r" + transition $sid PASS_SENT + } else { + terror "$host: requires password, but none is given" + closeproc $sid + } + } + {COMMAND} { + if {$config(sudo) != ""} { + if {[catch {hostpass $host} result] == 0} { + send -i $sid "$result\r" + transition $sid SUDO + } else { + terror "$host: sudo requires password, but none is given" + closeproc $sid + } + } else { + # FIXME + closeproc $sid + } + } + {SUDO2} { + if {[catch {hostpass $host} result] == 0} { + send -i $sid "$result\r" + transition $sid CLEANUP + exp_continue + } else { + terror "$host: requires password for cleanup, but none is given" + closeproc $sid + } + } + default { # FIXME + terror "unexpected password prompt from $sidinfo($sid,host) in state $sidinfo($sid,state)" closeproc $sid } - } elseif {$sidinfo($sid,state) == "SUDO2"} { - send -i $sid "[hostpass $host]\r" - transition $sid CLEANUP - exp_continue - } else { - # FIXME - terror "unexpected password prompt from $sidinfo($sid,host) in state $sidinfo($sid,state)" - closeproc $sid } } -re {^[^\n]*\n} { set sid $expect_out(spawn_id) - if {$sidinfo($sid,state) == "INIT"} { - if {[info exists sidinfo($sid,earlycmd)]} { - foreach cmd $sidinfo($sid,earlycmd) { - debug 3 "$sid: sending $cmd" - send -i $sid "$cmd\r" - } - unset sidinfo($sid,earlycmd) - } else { - continue + switch -- $sidinfo($sid,state) { + {INIT} { + set text [regsub -all {[\r\n]} $expect_out(0,string) {}] + terror -nosave "$sidinfo($sid,host): $text" } - } elseif {$sidinfo($sid,state) == "COMMAND" - || $sidinfo($sid,state) == "SUDO"} { - - set host $sidinfo($sid,host) - set text [regsub -all {\r} $expect_out(0,string) {}] + {COMMAND} - + {SUDO} { + set host $sidinfo($sid,host) + set text [regsub -all {\r} $expect_out(0,string) {}] - if {[config_option buffer-output]} { - lappend output($host) $text - } + if {[config_option buffer-output]} { + lappend output($host) $text + } - ::hostproc::getline $host $text - } elseif {$sidinfo($sid,state) == "STTY"} { - regexp -- {REXDIR=(.+)\r} $expect_out(0,string) x sidinfo($sid,wd) - continue - } else { - continue + ::hostproc::getline $host $text + } + {STTY} { + regexp -- {REXDIR=(.+)\r} $expect_out(0,string) x sidinfo($sid,wd) + } } } -re $config(prompt) { set sid $expect_out(spawn_id) - debug 3 "STATE $sid $sidinfo($sid,state)" - if {$sidinfo($sid,state) == "INIT" - || $sidinfo($sid,state) == "PASS_SENT"} { - set cmd [lshift sidinfo($sid,cmdlist)] - debug 3 "$sid: sending $cmd" - send -i $sid "$cmd\r" - transition $sid STTY - } elseif {$sidinfo($sid,state) == "STTY"} { - if {[llength $sidinfo($sid,cmdlist)]} { + debug 3 "$sid: prompt seen in state $sidinfo($sid,state)" + switch -- $sidinfo($sid,state) { + {AUTHENTICATED} { set cmd [lshift sidinfo($sid,cmdlist)] debug 3 "$sid: sending $cmd" send -i $sid "$cmd\r" - } elseif {[info exists config(data)]} { - set host $sidinfo($sid,host) - set cmd $config(data) - lappend cmd "[hostuser $host]@$host:$sidinfo($sid,wd)" - debug 3 "$sid: starting scp $cmd" - spawn -noecho scp {*}$cmd - lappend sidlist $spawn_id - set sidinfo($spawn_id,state) COPY - set sidinfo($spawn_id,host) $host - set sidinfo($spawn_id,cmd) "scp $cmd" - set sidinfo($spawn_id,master) $sid - } else { - debug 3 "$sid: sending $command" - send -i $sid "$command\r" - transition $sid COMMAND + transition $sid STTY } - } else { - if {[info exists sidinfo($sid,wd)] && $sidinfo($sid,state) == "COMMAND"} { - set host $sidinfo($sid,host) - debug 3 "$sid: removing $host:$sidinfo($sid,wd)" - send -i $sid "$config(sudo)rm -f -r $sidinfo($sid,wd)\r" - if {$config(sudo) != ""} { - transition $sid SUDO2 + {STTY} { + if {[llength $sidinfo($sid,cmdlist)]} { + set cmd [lshift sidinfo($sid,cmdlist)] + debug 3 "$sid: sending $cmd" + send -i $sid "$cmd\r" + } elseif {[info exists config(data)]} { + set host $sidinfo($sid,host) + set cmd $config(data) + lappend cmd "[hostuser $host]@$host:$sidinfo($sid,wd)" + debug 3 "$sid: starting scp $cmd" + spawn -noecho scp {*}$cmd + lappend sidlist $spawn_id + set sidinfo($spawn_id,state) COPY + set sidinfo($spawn_id,host) $host + set sidinfo($spawn_id,cmd) "scp $cmd" + set sidinfo($spawn_id,master) $sid } else { - transition $sid CLEANUP + debug 3 "$sid: sending $command" + send -i $sid "$command\r" + transition $sid COMMAND } - } else { + } + {COMMAND} { + if {[info exists sidinfo($sid,wd)]} { + set host $sidinfo($sid,host) + debug 3 "$sid: removing $host:$sidinfo($sid,wd)" + send -i $sid "$config(sudo)rm -f -r $sidinfo($sid,wd)\r" + if {$config(sudo) != ""} { + transition $sid SUDO2 + } else { + transition $sid CLEANUP + } + } else { + send -i $sid "exit\r" + transition $sid LOGOUT + } + } + default { send -i $sid "exit\r" transition $sid LOGOUT } @@ -1801,24 +1900,29 @@ proc runcmd {hosts command} { eof { set sid $expect_out(spawn_id) - if {$sidinfo($sid,state) == "LOGOUT"} { - set host $sidinfo($sid,host) - debug 2 "EOF from $host, waiting for $sid" - closeproc $sid - wait -i $sid - - ::hostproc::finish $host output($host) - } elseif {$sidinfo($sid,state) == "COPY"} { - debug 3 "$sidinfo($sid,cmd) finished" - debug 3 "sending $command to $sidinfo($sid,master)" - send -i $sidinfo($sid,master) "$command\r" - transition $sidinfo($sid,master) COMMAND - - closeproc $sid - wait -i $sid - } else { - terror "connection to $sidinfo($sid,host) failed: $expect_out(buffer) $sidinfo($sid,state)" - closeproc $sid + switch -- $sidinfo($sid,state) { + {LOGOUT} { + set host $sidinfo($sid,host) + debug 2 "EOF from $host, waiting for $sid" + closeproc $sid + wait -i $sid + + ::hostproc::finish $host output($host) + } + {COPY} { + debug 3 "$sidinfo($sid,cmd) finished" + debug 3 "sending $command to $sidinfo($sid,master)" + send -i $sidinfo($sid,master) "$command\r" + transition $sidinfo($sid,master) COMMAND + + closeproc $sid + wait -i $sid + } + + default { + terror "connection to $sidinfo($sid,host) failed: $expect_out(buffer) $sidinfo($sid,state)" + closeproc $sid + } } } } @@ -2467,6 +2571,7 @@ proc rex_copy_from {} { debug 2 "copy-from mode" } + common_config_setup exit [runcprev {*}$argv] } @@ -2556,6 +2661,7 @@ proc rex_login {} { global argv global argv0 global config + global ssh_options ::getopt::parse \ -usage {rex login [OPTIONS] HOST} \ @@ -2573,6 +2679,10 @@ proc rex_login {} { user,u=NAME {log in as user NAME} { set config(user) $optarg } + + zsh-quirk,Z + {try to cope with hosts running zsh} + { set config(option,zsh-quirk) 1 } sudo {run PROGRAM via sudo} @@ -2590,51 +2700,150 @@ proc rex_login {} { common_config_setup + log_user [config_option log] + debug 2 "logging in to $host" - set password_sent 0 - spawn -noecho ssh [hostuser $host]@$host - expect { - denied { - terror "bad password for [hostuser $host]@[hostname $host]" - exit 1 - } - "*assword:" { - if {$password_sent} { - terror "bad password for [hostuser $host]@[hostname $host]" - exit 1 - } else { - send "[hostpass $host]\r" - incr password_sent - exp_continue + + spawn -noecho ssh {*}$ssh_options [hostuser $host]@$host + + lappend sidlist $spawn_id + set sidinfo($spawn_id,host) $host + transition $spawn_id INIT + set sidinfo($spawn_id,zsh-quirk) [config_option zsh-quirk] + add_host_key_list $host earlycmd sidinfo($spawn_id,earlycmd) + set sidinfo($spawn_id,cmdlist) [list "stty -echo"] + + while {[llength $sidlist] > 0} { + expect { + -i $sidlist + + -re {^Authenticated to} { + set sid $expect_out(spawn_id) + transition $sid AUTHENTICATED + if {[info exists sidinfo($sid,earlycmd)]} { + foreach cmd $sidinfo($sid,earlycmd) { + debug 3 "$sid: sending early $cmd" + send -i $sid "$cmd\r" + } + unset sidinfo($sid,earlycmd) + } } - } - timeout { - terror "connection to [hostname $host] timed out" - exit 1 - } - eof { - terror "connection to [hostname $host] failed: $expect_out(buffer)" - exit 1 - } - "Are you sure you want to continue connecting (yes/no)?" { - send "yes\r" - exp_continue - } - -re $config(prompt) { - if {$config(sudo)!=""} { - send "stty -echo\r" - expect -re ".*$config(prompt)" - send "$config(sudo) -s\r" - expect { - "*assword*:" { send "[hostpass $host]\r" - expect "\n" } - -re $config(prompt) { } + + -re {assword:|assword for .*:} { + set sid $expect_out(spawn_id) + set host $sidinfo($sid,host) + debug 3 "$sid: $host prompted for password in state $sidinfo($sid,state)" + switch -- $sidinfo($sid,state) { + {INIT} { + if {[catch {hostpass $host} result] == 0} { + send -i $sid "$result\r" + transition $sid PASS_SENT + } else { + terror "$host: requires password, but none is given" + closeproc $sid + } + } + {COMMAND} { + if {[catch {hostpass $host} result] == 0} { + send -i $sid "$result\r" + transition $sid SUDO + } else { + terror "$host: sudo requires password, but none is given" + closeproc $sid + } + } + {PASS_SENT} { + set sid $expect_out(spawn_id) + set host $sidinfo($sid,host) + terror "bad password for [hostuser $host] on [hostname $host]" + closeproc $sid + } + default { + # FIXME + terror "unexpected password prompt from $sidinfo($sid,host) in state $sidinfo($sid,state)" + closeproc $sid + } + } + } + + -re {^[^\n]*\n} { + set sid $expect_out(spawn_id) + switch -- $sidinfo($sid,state) { + {INIT} { + set text [regsub -all {[\r\n]} $expect_out(0,string) {}] + terror -nosave "$sidinfo($sid,host): $text" + } + } + } + + -re $config(prompt) { + set sid $expect_out(spawn_id) + debug 3 "$sid: prompt seen in state $sidinfo($sid,state)" + switch -- $sidinfo($sid,state) { + {AUTHENTICATED} { + set cmd [lshift sidinfo($sid,cmdlist)] + debug 3 "$sid: sending $cmd" + send -i $sid "$cmd\r" + transition $sid STTY + } + {STTY} { + if {[llength $sidinfo($sid,cmdlist)]} { + set cmd [lshift sidinfo($sid,cmdlist)] + debug 3 "$sid: sending $cmd" + send -i $sid "$cmd\r" + } elseif {$config(sudo) != ""} { + send -i $sid "sudo -s\r" + transition $sid COMMAND + } else { + transition $sid COMMAND + send -i $sid "stty echo\r" + interact { + \004 { send -i $sid "exit\r" } + } + closeproc $sid + } + } + {COMMAND} - + {SUDO} { + send -i $sid "stty echo\r" + interact { + \004 { send -i $sid "exit\r" } + } + closeproc $sid + } + } + } + + timeout { + set sid $expect_out(spawn_id) + if {$sidinfo($sid,state) == "AUTHENTICATED"} { + if {$sidinfo($sid,zsh-quirk)} { + send -i $sid "unsetopt ZLE\r" + send -i $sid "PS1='$ '\r" + set sidinfo($sid,zsh-quirk) 0 + continue + } else { + terror "timed out waiting for prompt from $sidinfo($sid,host)" + } + } else { + terror "connection to $sidinfo($sid,host) timed out" } - send "stty echo\r" + closeproc $sid + } + + denied { + set sid $expect_out(spawn_id) + set host $sidinfo($sid,host) + terror "bad password for [hostuser $host] on [hostname $host]" + closeproc $sid + } + + eof { + set sid $expect_out(spawn_id) + closeproc $sid } - interact } - } + } debug 2 "quitting" } @@ -2740,14 +2949,17 @@ trap { switch -- $config(mode) { run - command rex_command + cp - copy rex_copy_to + copy-from - rcp rex_copy_from + login rex_login list rex_list edit rex_editdb - encrypt - + default { terror "unknown mode: $config(mode)" exit 1 @@ -13,7 +13,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with Rex. If not, see <http://www.gnu.org/licenses/>. -.TH REX 8 "September 13, 2016" "REX" "Rex User Reference" +.TH REX 8 "September 16, 2016" "REX" "Rex User Reference" .ds ET /etc .SH NAME rex \- remote execution utility @@ -726,6 +726,12 @@ Set default password (unsafe!) \fB\-u\fR, \fB\-\-user=\fINAME\fR Set default user name. .TP +\fB\-Z\fR, \fB\-\-zsh\-quirk\fR +Try to cope with hosts running +.BR zsh (1). +See the description of this option in subsection +.BR "REX RUN OPTIONS" . +.TP \fB\-h\fR, \fB\-\-help\fR Display short help summary. .SH REX LIST @@ -803,10 +809,6 @@ files. .B hosts List of hosts to run command on. .TP -.B max_scp_retry_count -(integer) Retry the failed \fBscp\fR command this number of times -before giving up. The default is 5. -.TP .B mode Operation mode. The value is one of: .BR command , |