Monday, March 24, 2008

Doing Network Wide Updates Using Expect

Hey there,

It's been a while since we took a look at Expect on this blog, so I'm throwing a monster out there today ;)

Today's script is a bit long-in-the-tooth, and may be complicated looking, but it's basically a simple script to enable you to do updates across your network all at once; only having to login once (or twice, but all at the same time ;)

I put a small section in the beginning that checks whether or not a user invoking the script is allowed to use it (although this script doesn't allow anyone to do anything that they don't have the privilege or access and knowledge to do already). It's a simple check against an exec of the logname command, to make sure that the user's logname is among the list of lognames allowed to use the script. This whole section can be removed without affecting the script's functionality. Simply delete the section with the header: "Weed out the undesirables pronto" and it'll no longer be an issue.

I also added separate login functions for Solaris, Linux, HP-UX and SCO Unix for variety (Note that you may need to tweak these Expect routines, in case the regular expression matching doesn't suit your flavor). You may not need all of these functions, and you may only need one. In any case, you can either call the script specifically to use it the way in which you desire, or edit it so that it will only run the way you desire. If you call it with the "-h" flag it should give you more help than you could ever possibly want ;)

Ex:
host # ./update.exp -h


Hope you enjoy this and find it useful. Good Times! :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/local/bin/expect

#########################################################################
# update.exp - do mass updates across network
# without lifing a finger
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
# Usage: update.exp <-h> <-d>
# [ +host1 +hostn | (+sco &| +sun &| +hp &| +linux) | +all ]
# [ login ] [ passwd | <command> <args> ]
#########################################################################

#########################################################################
# Weed out the undesirables pronto
#########################################################################

set gauntlet [exec logname]
if { [string compare $gauntlet "user1"] != 0 && [string compare $gauntlet "user2"] != 0 && [string compare $gauntlet "user3"] != 0 } {
puts "Sorry, $gauntlet. You may not use update.exp."
exit
}

#########################################################################
# Go for the help short-circuit
#########################################################################

foreach member $argv {
if { $member == "-h" } {
puts "#############################################################################"
puts "USAGE:"
puts " update.exp <-h> <-d>"
puts " \[ +host1 +hostn | (+sco &| +sun &| +hp &| +linux) | +all \]"
puts " \[ login \] \[ passwd | <command> <args> \]"
puts "#############################################################################"
puts "SCO Hosts:"
puts " sco1 sco2 sco3"
puts "SUN Hosts:"
puts " sun1 sun2 sun3"
puts "HP Hosts:"
puts " hp1 hp2 hp3"
puts "LINUX Hosts:"
puts " linux1 linux2 linux3"
puts "#############################################################################"
puts "FLAGS:"
puts " -h: Show this message."
puts " -d: Force extremely verbose output during operation."
puts " +host\[1-n\]: The plus symbol followed by any vaild hostname."
puts " +sco: Process all sco hosts."
puts " +sun: Process all sun hosts."
puts " +hp: Process all hp hosts."
puts " +linux: Process all linux hosts."
puts " +all: Process all hosts."
puts " login: Your own."
puts " passwd: Change password on specified machines."
puts " command: Any architecture-supported command, with optional arguments."
puts "#############################################################################"
puts "A FEW VALID COMMAND LINE EXPRESSIONS:"
puts " update.exp -h"
puts " \(The -h command invalidates all others\)"
puts " update.exp -d *****"
puts " \(The -d command may be used in conjunction with any other\)"
puts " update.exp +host1 +host2 login command args"
puts " \(Multiple hosts may be named on command line\)"
puts " update.exp +host1 +sun login command args"
puts " \(Specific host may be coupled with groups\)"
puts " update.exp +sun +hp login command"
puts " \(Groups may be combined. Args to command are trivial\)"
puts " update.exp +all login passwd"
puts " \(+all obviates any other hosts named, but may be combined for"
puts " a specific purpose\)"
puts "#############################################################################"
exit
}
}

#########################################################################
# Hide the gory details unless they want blood
#########################################################################

if { [llength $argv] == 0 } {
set debug 0
} else {
foreach entrail $argv {
if { $entrail == "-d" } {
set debug 1
break
} else {
set debug 0
}
}
}

if { $debug } {
log_user 1
} else {
log_user 0
}

#########################################################################
# Generic login procs divided by architecture
#########################################################################

proc sun_login {name tprompt login lpass} {
send_user "$name\n"

set timeout 3
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass

expect "ogin: " {send "$qlogin\r"}
expect "word: " {send "$qpass\r"}
}

proc linux_login {name tprompt login lpass} {
send_user "$name\n"

set timeout 3
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass

expect "ogin: " {send "$qlogin\r"}
expect "word: " {send "$qpass\r"}
}

proc hp_login {name tprompt login lpass} {
send_user "$name\n"

set timeout 12
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass

expect "ogin: " {send "$qlogin\r"}
expect "word:" {send "$qpass\r"}
}

proc sco_login {name tprompt login lpass} {
send_user "$name\n"

set timeout 6
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass

expect "ogin: " {send "$qlogin\r"}
expect "word:" {send "$qpass\r"}
expect -re $tprompt {send "\r"}
}

#########################################################################
# Command and user passwd procs by architecture
#########################################################################

proc sunhp_q {command arglist tprompt debug login lpass} {
set timeout 5
set log $login
set opass $lpass
set argvec $arglist
set commvec $command
set prompt $tprompt
set db $debug

if { $command == "passwd" } {
set username $log
set oldpass $opass
set newpass $argvec

expect -re $prompt {send "$commvec $username\r"}
expect -re "word: ?$" {send "$oldpass\r"}
expect -re "New password: ?$" {send "$newpass\r"}
expect {
-re "New password: ?$" {
send "\r"
send_user "Password not accepted!\n"
expect -re "New password: ?$" {send "\r"}
expect -re "New password: ?$" {send "\r"}
}
-re "word: ?$" {send "$newpass\r"}
}
expect -re $prompt {send "exit\r"}
} else {
if { $argvec != 0 } {
expect -re $tprompt {send "$commvec $argvec\r"}
} else {
expect -re $tprompt {send "$commvec\r"}
}
log_user 1
expect -re $tprompt {
if { $db } {
log_user 1
} else {
log_user 0
}
send "exit\r"
}
}
}

proc linux_q {command arglist tprompt debug login lpass} {
set timeout 5
set log $login
set opass $lpass
set argvec $arglist
set commvec $command
set prompt $tprompt
set db $debug

if { $command == "passwd" } {
set username $log
set oldpass $opass
set newpass $argvec

expect -re $prompt {send "$commvec\r"}
expect -re "word: ?$" {send "$oldpass\r"}
expect -re "assword: ?$" {send "$newpass\r"}
expect {
-re "BAD.*$" {
send "\r"
send_user "Password not accepted!\n"
expect -re "BAD.*$" {send "\r"}
expect -re "BAD.*$" {send "\r"}
}
-re "word: ?$" {send "$newpass\r"}
}
expect -re $prompt {send "exit\r"}
} else {
if { $argvec != 0 } {
expect -re $tprompt {send "$commvec $argvec\r"}
} else {
expect -re $tprompt {send "$commvec\r"}
}
log_user 1
expect -re $tprompt {
if { $db } {
log_user 1
} else {
log_user 0
}
send "exit\r"
}
}
}

proc sco_q {command arglist tprompt debug login lpass} {
set timeout 5
set log $login
set opass $lpass
set argvec $arglist
set commvec $command
set prompt $tprompt
set db $debug

if { $db } {
log_user 1
} else {
log_user 0
}
if { $command == "passwd" } {
set username $log
set oldpass $opass
set newpass $argvec

expect -re $prompt {send "$commvec $username\r"}
expect -re "word: ?$" {send "$oldpass\r"}
expect -re "1\\\): ?$" {send "\r"}
expect -re "New password: ?$" {send "$newpass\r"}
expect {
-re "New password: ?$" {
send "$newpass\r"
expect -re "New password: ?$" {send "$newpass\r"}
send_user "Password not accepted!\n"
}
-re "word: ?$" {send "$newpass\r"}
}
expect -re $prompt {send "exit\r"}
} else {
expect -re $tprompt {send "\r"}
expect -re $tprompt {
if { $argvec != 0 } {
send "$commvec $argvec\r"
} else {
send "$commvec\r"
}
log_user 1
}
expect -re $tprompt {
if { $db } {
log_user 1
} else {
log_user 0
}
send "exit\r"
}
}
}

#########################################################################
# The main process loop
#########################################################################

#########################################################################
# Check command line args
#########################################################################

if { [llength $argv] < 3 } {
puts "Usage: $argv0 <-h> <-d> \\ "
puts "\t\[+host1 +hostn | \(+sun &| +sco &| +hp &| +linux\) | +all\] \\ "
puts "\t\[login\] \[rpasswd | passwd | <command> <args>\]"
exit
}

set gotserv 0
set gotlog 0

foreach name $argv {
if { [string match "+*" $name ] } {
set gotserv 1
} elseif { [string match "+*" $name ] && $gotserv } {
continue
} elseif { $gotserv } {
set gotlog 1
}
}

if { [expr $gotlog+$gotserv] != 2 } {
puts "Usage: $argv0 <-h> <-d> \\ "
puts "\t\[+host1 +hostn | \(+sun &| +sco &| +hp &| +linux\) | +all\] \\ "
puts "\t\[login\] \[rpasswd | passwd| <command> <args>\]"
exit
}

#########################################################################
# Set list of servers, passwords and other globals
#########################################################################

set sun [list sun1 sun2 sun3]
set sco [list sco1 sco2 sco3]
set hp [list hp1 hp2 hp3]
set linux [list linux1 linux2 linux3]

list servers
list arglist
set allservers 0
set sunservers 0
set linuxservers 0
set scoservers 0
set hpservers 0
set needargs 0
set gotargs 0
set verify 0
set tprompt "(%|#|\\\/|\\\$|\\\/|\\\/\\\/#|user1|user2|user3|\\\)|\\\>|\\\}) ?$"

#########################################################################
# Parse arg vector and delegate procs
#########################################################################

foreach name $argv {
if { [string match "+*" $name] == 1 } {
if { [string match "+all" $name] == 1 } {
set allservers 1
} elseif { [string match "+sun" $name] == 1 } {
set sunservers 1
} elseif { [string match "+linux" $name] == 1 } {
set linuxservers 1
} elseif { [string match "+sco" $name] == 1 } {
set scoservers 1
} elseif { [string match "+hp" $name] == 1 } {
set hpservers 1
} else {
lappend servers [string range $name 1 [expr [string length $name]-1]]
}
} else {
if { $needargs == 1 } {
lappend arglist $name
set gotargs 1
} elseif { $verify == 1 } {
set command $name
set needargs 1
} elseif { $name == "-d" } {
continue
} else {
set login $name
incr verify
}
}
}

set ssshservers [expr $sunservers + $linuxservers + $scoservers + $hpservers]

if { $gotargs == 0 } {
set arglist 0
}

#########################################################################
# Grab user password and new password if necessary.
#########################################################################

send_user "Enter your login password : "
stty -echo
expect -re "(.*)\n"
stty echo
set lpass $expect_out(1,string)

send_user "\n"

if { $command == "passwd" } {
send_user "Enter your new password : "
stty -echo
expect -re "(.*)\n"
stty echo
set arglist $expect_out(1,string)
}

send_user "\n"

#########################################################################
# Loop until all conditions are satisfied
#########################################################################

for {} {1} {} {
if {$allservers} {
foreach host $sun {
spawn telnet $host
sun_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
foreach host $linux {
spawn telnet $host
linux_login $host $tprompt $login $lpass
linux_q $command $arglist $tprompt $debug $login $lpass
}
foreach host $hp {
spawn telnet $host
hp_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
foreach host $sco {
spawn telnet $host
sco_login $host $tprompt $login $lpass
sco_q $command $arglist $tprompt $debug $login $lpass
}
break
} elseif {$sunservers} {
foreach host $sun {
spawn telnet $host
sun_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set sunservers 0
incr ssshservers -1
continue
}
break
} elseif {$linuxservers} {
foreach host $linux {
spawn telnet $host
linux_login $host $tprompt $login $lpass
linux_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set linuxservers 0
incr ssshservers -1
continue
}
break
} elseif {$scoservers} {
foreach host $sco {
spawn telnet $host
sco_login $host $tprompt $login $lpass
sco_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set scoservers 0
incr ssshservers -1
continue
}
break
} elseif {$hpservers} {
foreach host $hp {
spawn telnet $host
hp_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set hpservers 0
incr ssshservers -1
continue
}
break
} else {
foreach host $servers {
if { [lsearch $sun $host] >= 0 } {
spawn telnet $host
sun_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
} elseif { [lsearch $hp $host] >= 0 } {
spawn telnet $host
hp_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
} elseif { [lsearch $linux $host] >= 0 } {
spawn telnet $host
linux_login $host $tprompt $login $lpass
linux_q $command $arglist $tprompt $debug $login $lpass
} elseif { [lsearch $sco $host] >= 0 } {
spawn telnet $host
sco_login $host $tprompt $login $lpass
sco_q $command $arglist $tprompt $debug $login $lpass
}
}
if { $ssshservers > 1 } {
incr ssshservers -1
continue
}
break
}
}

exit


, Mike