multiple ssh to cisco routers using expect script - bash

I am not a hardcore script guy,but trying to learn.I just started with expect scripts to automate tasks on cisco router.Please be gentle and nudge me in the right direction.I will do research accordingly thereafter.
Requirement: to spawn 2 ssh sessions to 2 different cisco routers and run unique commands on each of them in a single expect script.
Current Method : I call this expect script using a regular bash script.I can achieve the requirement using two expect scripts,But I want to do this using one expect script.
Example:
# Set variables
set router1 [lindex $argv 0]
set router2 [lindex $argv 1]
set username [lindex $argv 2]
set password [lindex $argv 3]
spawn ssh -o StrictHostKeyChecking=no $username\#$router1
expect "*assword"
send "$enablepassword\n"
expect "#"
send "command on router1"
expect "#"
close
#i want to close this ssh session and spawn ssh process to router2
spawn ssh -o StrictHostKeyChecking=no $username\#$router2
#i tried this simply in the same script and it doesn't work,mostly because #it is not correct
expect "*assword"
send "$enablepassword\n"
expect "#"
send "command on router2"
expect "#"

I think you should use spawn_id global variable, it helps to interact with multiple ssh or telnet sessions.
Your code should look something like this:
spawn ssh -o StrictHostKeyChecking=no $username\#$router1
set R1 $spawn_id
expect -i $R1 "*assword"
send -i $R1 "$enablepassword\n"
expect -i $R1 "#"
send -i $R1 "command on router1"
expect -i $R1 "#"
send -i $R11 "exit\r"
spawn ssh -o StrictHostKeyChecking=no $username\#$router2
set R2 $spawn_id
expect -i $R2 "*assword"
send -i $R2 "$enablepassword\n"
expect -i $R2 "#"
send -i $R2 "command on router2"
expect "#"

Related

Loop through file and send commands over expect

I am trying to send commands from a file to a device over expect. I tried sending them one at a time from my local machine but all of my file paths were relative to local, not relative to the remote device. My solution was to try to upload the file to the device and load the commands from there. When I try to load the file I keep getting a permissions issue even though if I cat the file from the device I don't have a problem reading it. The file has 1 command per line.
devicepath=rsync://root#localhost:$PORT_RSYNC/root/var/root/file.txt
/usr/bin/rsync -Pavr $1 $devicepath
expect <<- expect_feed
set send_slow {1 .001}
spawn ssh -o NoHostAuthenticationForLocalhost=yes -p $PORT_SSH root#localhost
expect -re "password:"
send -s "password\r"
expect -re $PROMPT_ROOT
send -s "chmod 777 /var/root/file.txt\r"
expect -re $PROMPT_ROOT
set f [cat /var/root/file.txt]
set cmds [split [read $f] "\n"]
close $f
foreach line $cmds {
send -s "$line\r"
expect -re $PROMPT_ROOT
expect_feed
This yields:
root# cat: /var/root/file.txt: Permission denied
I also tried
set f [open /var/root/file.txt]
...but it gave the same error.
If the file you send over contains shell commands, treat it as such and simply source it on the remote host
devicepath=rsync://root#localhost:$PORT_RSYNC/root/var/root/file.txt
/usr/bin/rsync -Pavr "$1" "$devicepath"
export PROMPT_ROOT PORT_SSH
expect << 'EXPECT_FEED'
set send_slow {1 .001}
spawn ssh -o NoHostAuthenticationForLocalhost=yes -p $env(PORT_SSH) root#localhost
expect -re "password:"
send -s "password\r"
expect -re $env(PROMPT_ROOT)
send -s ". /var/root/file.txt\r" ;# <<<<
expect -re $env(PROMPT_ROOT)
send "exit\r"
expect eof
EXPECT_FEED
I prefer to use quoted heredocs: shell variables can be passed to expect via the environment.
I'm assuming root's shell is a POSIX-type shell where . is the "source" command.
Thanks for the great suggestions. It is working like this.
It would probably work just as well without send slow now that it is sending one line at a time and waiting for a response.
The last command in the file is 'quit' in order to return to the root prompt, I suppose that could have been hard coded
cmdpath=$1
export cmdpath
expect << 'EXPECT_FEED'
set send_slow {1 .001}
set commandfile [open $env(cmdpath)]
spawn ssh -o NoHostAuthenticationForLocalhost=yes -p $env(PORT_SSH) root#localhost
expect -re "password:"
send -s "password\r"
expect -re $env(PROMPT_ROOT)
send -s ">the name of the process accepting commands<\r"
while { [gets $commandfile line] != -1 } {
expect -re $env(PROMPT)
send -s "$line\r" }
expect -re $env(PROMPT_ROOT)
send "exit\r"
expect eof
EXPECT_FEED

Bash script with expect that executes commands locally and in sftp [duplicate]

I'm trying to use expect in a Bash script to provide the SSH password. Providing the password works, but I don't end up in the SSH session as I should. It goes back strait to Bash.
My script:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com'
expect "password"
send "$PWD\n"
EOD
echo "you're out"
The output of my script:
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
usr#$myhost.example.com's password: you're out
I would like to have my SSH session and, only when I exit it, to go back to my Bash script.
The reason why I am using Bash before expect is because I have to use a menu. I can choose which unit/device to connect to.
To those who want to reply that I should use SSH keys, please abstain.
Mixing Bash and Expect is not a good way to achieve the desired effect. I'd try to use only Expect:
#!/usr/bin/expect
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
# Use the correct prompt
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
send "my_password\r"
interact -o -nobuffer -re $prompt return
send "my_command1\r"
interact -o -nobuffer -re $prompt return
send "my_command2\r"
interact
Sample solution for bash could be:
#!/bin/bash
/usr/bin/expect -c 'expect "\n" { eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com; interact }'
This will wait for Enter and then return to (for a moment) the interactive session.
The easiest way is to use sshpass. This is available in Ubuntu/Debian repositories and you don't have to deal with integrating expect with Bash.
An example:
sshpass -p<password> ssh <arguments>
sshpass -ptest1324 ssh user#192.168.1.200 ls -l /tmp
The above command can be easily integrated with a Bash script.
Note: Please read the Security Considerations section in man sshpass for a full understanding of the security implications.
Add the 'interact' Expect command just before your EOD:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
expect "password"
send -- "$PWD\r"
interact
EOD
echo "you're out"
This should let you interact with the remote machine until you log out. Then you'll be back in Bash.
After looking for an answer for the question for months, I finally find a really best solution: writing a simple script.
#!/usr/bin/expect
set timeout 20
set cmd [lrange $argv 1 end]
set password [lindex $argv 0]
eval spawn $cmd
expect "assword:" # matches both 'Password' and 'password'
send -- "$password\r"; # -- for passwords starting with -, see https://stackoverflow.com/a/21280372/4575793
interact
Put it to /usr/bin/exp, then you can use:
exp <password> ssh <anything>
exp <password> scp <anysrc> <anydst>
Done!
A simple Expect script:
File Remotelogin.exp
#!/usr/bin/expect
set user [lindex $argv 1]
set ip [lindex $argv 0]
set password [lindex $argv 2]
spawn ssh $user#$ip
expect "password"
send "$password\r"
interact
Example:
./Remotelogin.exp <ip> <user name> <password>
Also make sure to use
send -- "$PWD\r"
instead, as passwords starting with a dash (-) will fail otherwise.
The above won't interpret a string starting with a dash as an option to the send command.
Use the helper tool fd0ssh (from hxtools, source for ubuntu, source for openSUSE, not pmt). It works without having to expect a particular prompt from the ssh program.
It is also "much safer than passing the password on the command line as sshpass does" ( - comment by Charles Duffy).
Another way that I found useful to use a small Expect script from a Bash script is as follows.
...
Bash script start
Bash commands
...
expect - <<EOF
spawn your-command-here
expect "some-pattern"
send "some-command"
...
...
EOF
...
More Bash commands
...
This works because ...If the string "-" is supplied as a filename, standard input is read instead...
sshpass is broken if you try to use it inside a Sublime Text build target, inside a Makefile. Instead of sshpass, you can use passh
With sshpass you would do:
sshpass -p pa$$word ssh user#host
With passh you would do:
passh -p pa$$word ssh user#host
Note: Do not forget to use -o StrictHostKeyChecking=no. Otherwise, the connection will hang on the first time you use it. For example:
passh -p pa$$word ssh -o StrictHostKeyChecking=no user#host
References:
Send command for password doesn't work using Expect script in SSH connection
How can I disable strict host key checking in ssh?
How to disable SSH host key checking
scp without known_hosts check
pam_mount and sshfs with password authentication

Why does my Expect script only echo the command not running?

I'm trying to automate some ssh process. I have my Expect code. But my Expect code only echos/prints out the command. It doesn't actually run the command.
#!/usr/bin/expect -f
set timeout 10
set usrnm "aaaaaa"
set pwd "pppppp"
set addr1 "xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
set addr2 "xxx.xxxx.xxxx.com"
spawn ssh $usrnm#$addr1
expect {
"(yes/no)?" {send "yes\r";exp_continue}
"password: " {send "$pwd\r"}
}
expect "*#"
send "ssh $usrnm#$addr2\r"
expect {
"(yes/no)?" {send "yes\r";exp_continue}
"password:" {send "$pwd\r"}
}
expect "*#"
send "cd /tmp/myself/folder\r"
expect "*#"
send "./run_engine.sh test.py\r"
expect eof
#interact
So if I do
expect my_expect.exp
it just prints the command:
spawn ssh aaaaaa#xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
(10s later)
ssh aaaaa#xxx.xxxx.xxxx.com
(10s later)
cd /tmp/amz337/COAFALV
(10s later)
./run_engine.sh test.py
(exit)
What's wrong with my script?
Because Tcl (and thus Expect) does not change the word boundaries when variables get substituted. You are trying to log into the host named exactly:
xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
spaces and all.
Logically, it does not make sense to put ssh options into a variable that holds the address. May I suggest:
set addr1 "xxx.cloud.xxx.com"
set addr2 "xxx.xxxx.xxxx.com"
set ssh_opts($addr1) {-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no}
set ssh_opts($addr2) {}
Then
spawn ssh {*}$ssh_opts($addr1) $usrnm#$addr1
The {*} syntax is Tcl's "splat" operator that splits a word with spaces into the individual words. See https://tcl.tk/man/tcl8.6/TclCmd/Tcl.htm rule #5.
Later, when you connect to the second machine, you're interpolating into a string, so the splat is not necessary:
send "ssh $ssh_opts($addr2) $usrnm#$addr2\r"
You might want to catch timeout events and abort the script:
expect {
timeout {error "timed-out connecting to $addr1"}
"(yes/no)?" {send "yes\r"; exp_continue}
"password: " {send "$pwd\r"}
}
At the end of your script, after the run_engine script completes, you're still connected to addr2, so expect eof will not actually detect EOF on the spawned process. You'll timeout after 10 seconds and the Expect process will exit. For tidiness, you should:
send "./run_engine.sh test.py\r"
expect "*#"
send "exit\r"
# This prompt is from addr1
expect "*#"
send "exit\r"
# _Now_ the spawned ssh process will end
expect eof
If you think the run_engine script will take longer than 10 seconds, you should adjust the timeout variable before sending that command.
Also, while developing an Expect script, you should turn on debugging:
exp_internal 1
That will show you what's going on behind the scenes, especially when it comes to seeing if your patterns are matching.

How to write expect snippet in bash script to run remote command?

Currently I am running this script to print directories on a remote box but I am not sure this code is working.
#!/bin/bash
PWD="test123"
ip="10.9.8.38"
user=$1
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$ip df -h
expect "*assword:"
send "$PWD\n"
interact
EOD
expect spawns a new sub-shell, hence your local bash variables lose their scope, one way to achieve this is to export your variables to make it available for the sub-shell. Use the Tcl env built-in to import such variables in your script.
#!/bin/bash
export pwdir="test123"
export ip="10.9.8.38"
export user=$1
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no "$env(user)"#"$env(ip)" df -h
expect "*assword:"
send "$env(pwdir)\n"
interact
EOD
(Or) If you are not interested in using an expect script directly with a #!/usr/bin/expect she-bang, you can do something like
#!/usr/bin/expect
set pwdir [lindex $argv 0];
set ip [lindex $argv 1];
set user [lindex $argv 2];
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$ip df -h
expect "*assword:"
send "$pwdir\n"
interact
and run the script as
./script.exp "test123" "10.9.8.38" "johndoe"

calling expect script inside a while loop of shell script

I am trying to automate password-less ssh setup from master account to other slave accounts.I have a script named AddSSH.ksh which does this setup.When this script is run manually,it asks for same password same times,it basically copied keys using scp. All the slave accounts are saved in a file env.txt.So Now, I have a shell script(run.ksh) which reads the accounts from this file(env.txt) one by one and then uses expect script auto_ssh.ksh to handle the interaction and it enters the password accordingly.
env.txt
account1#machine1
account2#machine2
account3#machine3
account4#machine4
run.ksh:
#!/usr/bin/ksh
while read env
do
username=`echo $env | cut -d"#" -f1`;
hostname=`echo $env | cut -d"#" -f2`;
password='Unix_11'
ssh -n -o PasswordAuthentication=no ${env} ' ' 2>/dev/null
if [ $? -eq 0 ]; then
printf "\nConnection OK for : $env \n"
else
expect auto_ssh.ksh $username $hostname $password
fi
done<env.txt
auto_ssh.ksh:
#!expect
set timeout 6
set user [lindex $argv 0]
set machine [lindex $argv 1]
set password [lindex $argv 2]
spawn AddSSH.ksh $user $machine
expect "password:"
send "$password\r";
expect "password:"
send "$password\r";
interact
If a run the script auto_ssh.ksh like
./auto_ssh.ksh account1 machine1 password
It runs fine but when I call it inside shell script,this expect script exits at the second password.when I ran the shell script in debug mode, I see that instead of sending the password the second time it moves to reading the next env from env.txt and exits.
This is the line of the output in debug mode where it fails.
account1#machine1's password: + read env
Add exp_internal 1 to the expect script for additional debugging. I suspect you might need to refine what you expect: expect -re {password:\s*$}
If you don't need to actually interact with addSSH.ksh, change interact to expect eof
Why does your expect script have a ".ksh" extension?

Resources