aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2016-09-16 10:43:00 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2016-09-16 17:10:01 +0300
commit72335d68e35f99835a649ce01bc86dc05d9b6893 (patch)
tree466600964de53464c2a53f5b5d73f1c174df8ce3
parent1b1357c01336bbf06175ebbace176f8ce8756e82 (diff)
downloadrex-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--README15
-rwxr-xr-xrex662
-rw-r--r--rex.man812
3 files changed, 457 insertions, 232 deletions
diff --git a/README b/README
index 5c090ac..9728f22 100644
--- a/README
+++ b/README
@@ -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
diff --git a/rex b/rex
index 24be0bc..d186dc2 100755
--- a/rex
+++ b/rex
@@ -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
diff --git a/rex.man8 b/rex.man8
index 7ec4652..5b69afa 100644
--- a/rex.man8
+++ b/rex.man8
@@ -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 ,

Return to:

Send suggestions and report system problems to the System administrator.