SSH connection requests password before password can be sent - bash

I have an odd problem I am trying to ssh connect to a remote device inside of a script so I can send commands to it but weirdly it's asking for the password to the ssh connection before I can even call an expect command. My snippet is this:
#!/usr/bin/expect
set PASS="password"
echo {$PASS}
ssh admin#10.3.0.1 -p 4118 #Firewall Device IP
expect {
"Password: "
{send '$PASS\r'}
}
and the output I get is this:
password
Password:
I have no idea why it behaving this way.
Edit:
I've applied the changes suggested by #glenn jackman, the new code being:
#!/usr/bin/expect
set PASS "peak1234"
puts "$PASS"
ssh admin#10.3.0.1 -p 4118 #Firewall Device IP
expect "Password :"
send "$PASS\r"
with these changes I get the error:
set: Variable name must begin with a letter.

You're confusing expect with sh
#!/usr/bin/expect
set PASS "password" ; ## no "="
puts $PASS ; ## no "echo", braces wrong
ssh admin#10.3.0.1 -p 4118
expect "Password: "
send "$PASS\r" ; ## single quotes have no meaning in expect/Tcl

Related

Bash scripting with expect. Set parameters to multiple test boards

I'm working on a small project for school. I'm using 15 or so tuners to emulate a Cell network. I'm by no means well versed in scripting yet. I'm an EE who usually googles until I have some frankencode capable of my purposes.
The goal is the set up all the modules quickly so I thought to automate the process with a script. This requires ssh, and so far I have to manually type in the password each time. This morning I set up a basic test with both Expect and sshpass. In either case I can correctly log in, but not give instructions to the remote machine.
I was reading that sshpass has difficulty with sending remote instruction, correct me if I'm wrong.
/usr/bin/expect << EOF
spawn ssh root#<IP>
expect "(yes/no)?" #Are you sure you want to connect nonsense
send "yes\r"
expect "password"
send "$pass\r"
I tried a few things here to get the device to receive instruction
interact
cat /pathto/config.txt
#or
send "cat /pathto/config.txt
#the real goal is to send this instruction
sqlite3 /database.db "update table set param=X"
EOF
You might as well make it an expect script, not a shell script
#!/usr/bin/expect -f
and then pass the IP address to the script as a command line argument
expect myloginscript.exp 128.0.0.1 the_password
In the expect script, you'll grab that IP address from the arguments list
set ip [lindex $argv 0]
set pass [lindex $argv 1]
(Putting the password on the command line is not good security practice. You can research better methods of passing the password to your expect script.)
To use ssh, you'll be asked "are you sure" only the first time to connect, so let's make that conditional. That is done by letting the expect command wait for several patterns:
spawn ssh root#$ip
expect {
"(yes/no)?" {
send "yes\r"
# continue to wait for the password prompt
exp_continue
}
"password" {
send "$pass\r"
}
}
Once that is sent, you should expect to see your shell prompt. The pattern for this is up to your own configuration but it typically ends with a hash and a space.
expect -re {# $}
Now you can automate the rest of the commands:
send "cat /pathto/config.txt\r"
expect -re {# $}
# note the quoting
send "sqlite3 /database.db \"update table set param='X'\"\r"
expect -re {# $}
At this point, you'll want to log off:
send "exit\r"
expect eof
On the other hand, if you set up ssh private key authentication (see ssh-keygen and ssh-copy-id), you can just do this:
ssh root#IP sqlite3 /database.db "update table set param='$X'"
and not have to get into expect at all.

Bash Scripting, send "logout" command being skipped during ssh session

Essentially I connect to remote host, authenticate, run command, logout. However logout command is being skipped.
/usr/bin/expect << EOD
spawn ssh $_host
expect "Password: ";
send "$_pass\r";
expect "$_host>";
send "sh arp | inc $_host2\r";
expect "$_host>";
send "logout\r";
EOD
echo "blah blah"
What i get is my expected output from arp command however, blah blah will be entered into the terminal of the remote host. It seems the logout command is being skipped, somewhat new to bash scripting but it seems that expect doesn't instantly see "$_host>" when executing and skips it? Appreciate any feedback.
Don't use expect here at all. Even if you must get the password from a variable, using sshpass for the purpose will avoid mixing two separate languages (bash and TCL).
SSHPASS="$_pass" sshpass -e "$_host" "sh arp | inc $_host2"
I believe the \r ( carrige return ) should be \n ( enter / new line ) ?
send "logout\r"; -> send "logout\n";
If that will help - I would replace it in the entire script ..
Suggesting:
/usr/bin/expect << EOD
match_max 1000000
spawn ssh $_host
expect "Password: ";
send "$_pass\n";
expect "$_host>";
send "sh arp | inc $_host2\n";
expect "$_host>";
send "logout\n";
EOD
echo "blah blah"

How can I iterate through a range of port numbers, capture the working port and SSH from expect

currently the below script sample works, however i need to iterate through the port range of 8801 and 8899. (must be in the expect section, standard bash for loop will not work here).
#!/bin/bash
testFunction(){
/usr/bin/expect << EOF
#!/usr/bin/expect
spawn ssh admin#localhost -p 8802
expect "password"
send "password\r"
expect "$ "
interact
EOF
}
Try using environment variables. It's quick and dirty but it works:
#!/bin/bash
function testFunction {
local TESTPORT=$1
expect << EOF
spawn sshpass -f x ssh -p ${TESTPORT} admin#localhost
expect "$ "
send -- "w\r"
expect "$ "
send "exit\r"
EOF
}
testFunction 8802
testFunction 8803
I also used sshpass instead of having the ssh password in the expect script. I was using this to test the idea. In this case, the file "x" has the password in it (and is set with highly restrictive permissions).
Can you replace that hard coded 8802 in your HERE doc with a variable? And then you'd be able to put the entire testFunction in a loop, passing in a different port for each iteration.

Handle multiple statements in an Expect script

I am new to Expect scripting.
I wrote an Expect script for ssh in a Linux machine, where I am facing a problem in ssh'ing to different Linux machines. Below I have copied the script.
!/usr/local/bin/expect
set LinuxMachine [lindex $argv 0]
spawn ssh root#$LinuxMachine
expect "root#$LinuxMachine's password:"
send "root123\n"
expect "[root#Client_FC12_172_85 ~]#"
send "ls"
interact
When I supply 10.213.172.85 from command line the expect in the 4th line, it reads as "root#10.213.172.85's password:" and logs in successfully
But some Linux will expect
The authenticity of host '10.213.172.108 (10.213.172.108)' can't be established.
RSA key fingerprint is da:d0:a0:e1:d8:7a:23:8b:c7:d8:40:8c:b2:b2:9b:95.
Are you sure you want to continue connecting (yes/no)
In this case the script will not work.
How can I have two Expect statements in one Expect command?
You can use exp_continue in such a case:
expect {
"Are you sure you want to continue connecting (yes/no)" {
send "yes\r"
exp_continue
}
"root#$LinuxMachine's password:" {
send "root123\r"
expect "[root#Client_FC12_172_85 ~]#"
send "ls\r"
interact
}
}
In the above, the Expect block waits for either the yes/no question OR the prompt for password. If the latter, it moves on with providing password, expecting prompt, sending ls command and giving control back.
If the former, it will answer 'yes' and repeat the expect block, ready to find the prompt for a password (or even the yes/no question again, for that matter - not that you will need that).
I would also include some timeouts with meaningful messages in case some expect option does not match as expected, but the above should work.
As a side comment, you don't want to set a root password in a script... I recommend using ssh key authentication.
We like to call it "long log in". There are ssh options that don't check the host keys:
send -- "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no username#host\n"
expect {
"Password" {
send -- "$passwd\n"
}
Part of the Bash script that calls on the expect sets the password:
echo -n "Enter LDAP password: "
read -s passwd
echo

Bash/Expect Script for SSH

I am new to Expect and scripting in general. I am trying to make a few scripts to make my life a bit easier when pulling network device configurations. I managed to create a basic Expect script to SSH to a device and save the configuration.
I want to expand upon this and allow the script to connect to a number of IP addresses instead of just one like I have right now. I have a file named list.txt with a few different IP addresses with each IP address on a separate line.
What would I need to do to have the Expect script connect to each of these IP addresses and perform the rest of the tasks in the script as well?
Here is the Expect script I have so far:
#!/usr/bin/expect -f
# Tells interpreter where the expect program is located. This may need adjusting according to
# your specific environment. Type ' which expect ' (without quotes) at a command prompt
# to find where it is located on your system and adjust the following line accordingly.
#
#
# Use the built in telnet program to connect to an IP and port number
spawn ssh 192.168.1.4 -l admin
#
# The first thing we should see is a User Name prompt
#expect "login as:"
#
# Send a valid username to the device
#send "admin"
#
# The next thing we should see is a Password prompt
expect "Password:"
#
# Send a valid password to the device
send "password\n"
#
# If the device automatically assigns us to a privileged level after successful logon,
# then we should be at an enable prompt
expect "Last login:"
#
# Tell the device to turn off paging
#
# After each command issued at the enable prompt, we expect the enable prompt again to tell us the
# command has executed and is ready for another command
expect "admin#"
#
# Turn off the paging
send "set cli pager off\n"
#
# Show us the running configuration on the screen
send "show config running\n"
#
# Set the date.
set date [timestamp -format %C%y%m%d]
#
# Test output sent to file with a timestamp on end
#-noappend will create a new file if one already exists
log_file -noappend /home/test.cfg$date
#
expect "admin#"
#
# Exit out of the network device
send "exit\n"
#
# The interact command is part of the expect script, which tells the script to hand off control to the user.
# This will allow you to continue to stay in the device for issuing future commands, instead of just closing
# the session after finishing running all the commands.`enter code here`
interact
Do I need to integrate this with a Bash script? If so, is it possible to read one line of the list.txt file, use that as the IP address/host variable and then read the next and repeat?
I would do this (untested):
#!/usr/bin/expect -f
set logfile "/home/text.cfg[clock format [clock seconds] -format %Y%m%d]"
close [open $logfile w] ;# truncate the logfile if it exists
set ip_file "list.txt"
set fid [open $ip_file r]
while {[gets $fid ip] != -1} {
spawn ssh $ip -l admin
expect "Password:"
send "password\r"
expect "admin#"
send "set cli pager off\r"
log_file $logfile
send "show config running\r"
expect "admin#"
log_file
send "exit\r"
expect eof
}
close $fid
Notes:
I removed all your comments for brevity
use \r to simulate hitting enter when you send commands.
I assumed you only want to log the "show config running" output
use expect eof after you send "exit"
This is a Perl version for this issue:
Install instruction:
cpan Expect
This script works perfectly for my needs.
Parameter 1: Connection string (example: admin#10.34.123.10)
Parameter 2: Clear text password
Parameter 3: Command to execute
#!/usr/bin/perl
use strict;
use Expect;
my $timeout = 1;
my $command = "ssh " . $ARGV[0] . " " . $ARGV[2];
#print " => $command\n";
my $exp = Expect->spawn($command) or die "Cannot spawn $command: $!\n";
$exp->raw_pty(1);
LOGIN:
$exp->expect($timeout,
[ 'ogin: $' => sub {
$exp->send("luser\n");
exp_continue;
}
],
[ 'yes\/no\)\?\s*$' => sub {
$exp->send("yes\n");
goto LOGIN;
}
],
[ 'assword:\s*$' => sub {
$exp->send($ARGV[1]."\n");
#print "password send: ", $ARGV[1];
exp_continue;
}
],
'-re', qr'[#>:] $'
);
$exp->soft_close();
A possibility is to pass the IP address as a parameter in your Expect script:
set host_ip [lindex $argv 0]
and then make a shell script, calling your Expect script inside a while loop:
ips_file="list.txt"
while read line
do
your_expect_script line
done < $ips_file
Or use set ip [gets stdin] to the IP address from the user input.
For example,
puts "Enter your IP address\n"
set ip [get stdin]
Use this in spawn. We can do the same for multiple IP addresses using a loop -
spawn ssh $ip -l admin
Here's a good way to integrate expect with bash:
ssh_util.expect-
#!/usr/bin/expect
set timeout -1
set ip [lindex $argv 0]
set user [lindex $argv 1]
set pwd [lindex $argv 2]
set commands [lrange $argv 3 [llength $argv]]
spawn ssh -o LogLevel=QUIET -t $user#$ip $commands
expect {
yes/no {send "yes\r" ; exp_continue}
*?assword {send "$pwd\r" ; exp_continue}
}
You can run this in the terminal with ./ssh_util.expect <commands...>. In your shell script you can use it to run commands on your host machine like this:
example.sh -
#! /bin/bash
# ssh_exp <commands...>
ssh_exp () {
./ssh_util.expect 192.168.1.4 username password $*
}
# run commands on host machine here
ssh_exp ls
ssh_exp ls -la
ssh_exp echo "Echo from host machine"
# you can even run sudo commands (if sudo password is same as user password)
ssh_exp sudo apt install
Make sure to run
chmod +x ssh_util.expect
chmod +x example.sh
in the terminal to make both files executable. Hope this helps!

Resources