Using if/else in expect script - linux

I recently updated and reformatted my /etc/hosts file and would like to run through every system (roughly 1000) to refresh my known_hosts file. I'm looking at using an expect script to send "yes" to the RSA fingerprint question. Simple enough. However, I know some of these systems are completely new to me and my password has not been set. This creates two possibilities:
"yes" is sent to the RSA fingerprint question and I'm logged into
the server. I'll then need to send an exit to close the connection
before moving onto the next host. Or...
"yes" is sent to the RSA fingerprint question and I'm presented with
the prompts to update my password starting with the current and
followed by the new password entered twice. The connection will
automatically close after the password is updated moving onto the
next host.
I think I have a basic grasp of the concept of "if/else" in expect, but I don't fully understand how to nest them, if there is a better way, or if I'm completely off-base to begin with.
This is what I have right now:
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/.pw1.txt]
set pw2 [exec cat /home/user/.pw2.txt]
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting"{
send "yes\r"
expect {
"current" {
send $pw2\r
} "New password" {
send $pw1\r
} "Retype new password" {
send $pw1\r
}
}
expect "msnyder"
send "exit\r"
}
interact
}
The file1 variable is the list of hosts to run the script against.
I know it isn't accurate because it errors on line 22. But, I have no idea what needs to be fixed.

Two errors I spotted:
missing close brace, probably for the "continue connecting" block
missing space before the open brace of "continue connecting". Tcl (hence Expect) is very sensitive to whitespace as it is parsed into words before the commands are evaluated. For the very few gory details, see the 12 syntax rules of Tcl.
Your code might look like:
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
exp_continue
}
"New password" {
send -- $pw1\r
exp_continue
}
"Retype new password" {
send -- $pw1\r
exp_continue
}
msnyder
}
send "exit\r"
}
}
interact
}
Notes:
exp_continue is used to "loop" back up to the expect statement: in this case, you will expect to see all of "current", "new" and "retype", so you don't want to bail out until you see your prompt.
get into the habit of typing send -- something. Without the double dash, you'll be surprised the day someone types in a password with a leading dash.

You can probably avoid having the script run like a human and just spawn an expected to fail ssh connection which will automatically accept the host RSA key and not bother with prompting for a password; you can do that later for new systems where you need to initiate a password.
Temporarily try adding this to your ~/.ssh/config file until your script is finished:
Host *
Protocol 2
PasswordAuthentication 0
StrictHostKeyChecking 'no'
CheckHostIP 'no'
UserKnownHostsFile ~/.ssh/known_hosts_new
Then when the new know_hosts_new file is loaded up, you can replace the default ~/.ssh/known_hosts and remove the UserKnownHostsFile line from your config.

Related

Looping over lines from a file in expect

New at scripting, beware...
I'm attempting to use this code that i put together from reading a couple blogs. The idea is for this script to read the IPs that i have saved on ips.txt and then run the code to ssh to the read IP using the credentials given, perform a couple send commands as described below, exit the ssh session, and repeat with the second line in the ips.txt file, which is a different IP, until it finishes the IPs list.
Note: the ips.txt file is a simple list of IP addresses as follows (no spaces between the IPs):
192.168.0.2
192.168.0.3
192.168.0.4
...
The spawn, expect, and send commands work fine. It also loops fine back up to the beginning of the code but it will NOT read the second IP in the ips.txt file; it will just read the first one again and perform the same steps over and over.
Please assist...
#!/usr/bin/expect
set timeout 180
set username admin
set password Changeme1
set fildes [open "ips.txt" r]
set ip [gets $fildes]
while {[string length $ip] != 1} {
spawn ssh $username#$ip
expect "password:"
send "$password\r"
expect ".mi"
send "show sw\r"
expect ".mi"
send "exit\r"
set ip [gets $fildes]
}
close $fildes
Let's try to boil the code down to something that minimally reproduces the problem. You say that the loop repeats with the same ip value, right? So let's remove the code interacting with the remote system:
#!/usr/bin/expect
set fildes [open "ips.txt" r]
set ip [gets $fildes]
while {[string length $ip] != 1} {
puts $ip
set ip [gets $fildes]
}
close $fildes
What happens when you run this?
I expect that the while condition will never be satisfied: you'll read the file, printing each line, then you print an inifinite number of blank lines. When you read past the last line of a file, you get the empty string as a result, not a string with length 1.
You most likely want
#!/usr/bin/expect
set timeout 180
set username admin
set password Changeme1
set fildes [open "ips.txt" r]
# the 2-argument form of `gets` returns -1 when it can't read another line
while {[gets $filedes ip] != -1} {
spawn ssh $username#$ip
expect "password:"
send "$password\r"
expect ".mi"
send "show sw\r"
expect ".mi"
send "exit\r"
expect eof ;# wait for the connection to close
}
close $fildes

Expect script to handle RSA finger print keys

I am trying to make my expect script more robust and to be able to handle more situations to be more automated. Currently my script works fine, however, there are times where I will be asked to add the RSA keys to known_hosts, I want this to default to yes all the time. My server doesn't always ask for the keys, once added then after awhile it wont ask until you delete the keys or switch gatways. After looking online i have tried to add this (commented code) in my working code and after added that it stalls at password input screen if RSA key had been added already.
So my question is, is there a way to handle this situation lets say if RSA has been added already, it will just skip to the password line?
[user#gateway my_direcotry]$ cat loadItTest
#!/usr/bin/expect -f
set timeout 600
set user root
set host 1.1.1.1
set pass pass
spawn ssh $user#$host
#expect {
# -re "RSA key fingerprint" {send "yes\r"}
#}
expect "assword:"
send "$pass\r"
expect "#"
Sample output:
[root#gateway my_direcotry]# loadItTest
spawn ssh root#1.1.1.1
root#1.1.1.1's password:
This is where the exp_continue command comes into play, to essentially create a "loop" within the expect command:
spawn ssh $user#$host
expect {
"RSA key fingerprint" {send "yes\r"; exp_continue}
"assword:" {send "$pass\r"}
}
expect "#"
If you see the "RSA key" pattern, answer "yes" but then keep waiting for the "assword" pattern. If you don't see "assword" before "RSA key", that's OK.
The body for the "assword" pattern does not contain exp_continue. After you send the password, the enclosing expect command will return, and the next command is to expect your prompt.
Note I removed the -re option: there are no regex-special characters in that pattern.

How to make a string as an optional to wait for it in an expect script?

The below script works in the case, where all three questions are being asked, but sometimes the Do you trust the above certificate [y|N] --> question is not asked by asadmin ... and so my expect fails.
Question
Is it possible to make the Do you trust the above certificate [y|N] --> question optional, so the expect script doesn't fails when this question in not asked?
#!/usr/bin/expect
set password [lindex $argv 0]
spawn asadmin --user admin change-admin-password
expect "password"
send "\n"
expect "password"
send "$password\n"
expect "password"
send "$password\n"
expect "Do you trust the above certificate \[y\|N\] -->"
send "y\n"
expect eof
exit
set password [lindex $argv 0]
spawn asadmin --user admin change-admin-password
# First time, when we see the password, we are simply typing 'return' key
expect "password"
send "\n"
expect {
"password" { send "$password\n"; exp_continue }
-ex "Do you trust the above certificate \[y|N] -->" {send "y\n";exp_continue}
timeout { puts "Timeout happened." }
eof { exit }
}
As you can see, exp_continue will help us in getting what you need.
If expect sees password, it will send the password value. Notice the use of exp_continue in there.
It will cause the expect to run again. So, the expect will see the password twice and if suppose, expect sees the question, it will send 'y\n'. If it sees eof before, then script will exit.
Please note that I have kept the first expect statement with password separately outside. The reason being is nothing but the value we are sending is different for the first time alone.
Also note the use of the -ex flag in the expect statement as below.
-ex "Do you trust the above certificate \[y|N] -->" {send "y\n";exp_continue}
It will make the expect to prevent any sort of special pattern matching. It is sufficient to escape the first square bracket alone.

parse information from expect command

I need to parse resulting data from a telnet/ssh command and act on the data.
As an example, I want to interact with a spawn session (ssh here), list files in current dir and collect only file of a certain extension to later execute a command on those files only.
What I've got so far:
#!/usr/bin/expect
set timeout 3
match_max 10000
set prompt {$ }
spawn ssh $user#$host
expect "password: "
send $pw\r
expect $prompt
# here's the command I need to parse resulting data
send "ls -1\r"
expect -re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
That script opens a ssh session, sends the command and displays all the files in current dir (log and others) but I need to process each file matching a given pattern, how can I do this?
script output:
$ DATA:
0_system.log
1_system.log
2_system.log
3_system.log
a.log
a.sh
blah
b.sh
data.csv
First of all... wouldn't ls -1 *.log be easier, and do the trick?
That said, I have found that usually you have to be very careful when using (.*), it can have at times unexpected results.
I would go with "any alphanumeric character plus underscore" (if that works for your filenames) - see my suggested expect block below. Also, keep in mind that by using $expect_out(1,string) you are saving only the filename, without the extension - not sure if that is what you want. If you want the whole thing, $expect_out(0,string) is the way to go in this case.
This will do it:
expect -re "(\[a-zA-Z0-9_\]*)\.log" {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
Hope that helps!
James
Corrected Expect probably should be:
expect {
-re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue;
}
}

expect telnet script : can't handle "----More---" string

I am new with expect scripting. I'm trying to backup Huawei router configuration with automated telnet script. I'm stuck in handling one string "---More---", it's like press any key to continue where I would like to send "\n"
My router output is like:
--------------------------------
-----------------------
voice-vlan mac-address 00d0-1e00-0000 mask ffff-ff00-0000 description Pingtel phone
voice-vlan mac-address 00e0-7500-0000 mask ffff-ff00-0000 description Polycom phone
voice-vlan mac-address 00e0-bb00-0000 mask ffff-ff00-0000 description 3com phone
#
---- More ----
And My Script is :
#!/usr/bin/expect
spawn telnet 192.168.xx.xx
expect "Username:"
send "username\n"
expect "Password:"
send "password\n"
expect ">"
# 'dis cur' is like cisco's 'show run'
send "dis cur\n"
expect "---- More ----"
send "\n"
interact
While running the script terminal throwing me this error:
bad flag "---- More ----": must be -glob, -regexp, -exact, -notransfer, -nocase, -i, -indices, -iread, -timestamp, -timeout, -nobrace, or --
while executing
"expect "---- More ----""
Can anyone help fixing this?... Thanks in advance :)
Quick answer: put -ex in front of the string that starts with a - so that the expect code can know for sure that it isn't an option.
However, aside from the quick answer you should do a somewhat more sophisticated version:
expect {
">" {
# Found prompt
}
-ex "---- More ----" {
send "\n" ;# Or maybe \r?
exp_continue ;# Keep on expecting please
}
}
interact
This has the advantage of allowing zero, one or many occurrences of the pager output.

Resources