"Expect" command line script to connect to VPN and enter password - bash

I am trying to write an "Expect" script to connect to a VPN...
Trying to write and expect (https://likegeeks.com/expect-command/) script to connect to a vpn, is this the right idea:
The commands to connect are:
sudo vpnName [ENTER] *Password* [ENTER] Random number 1-100 [ENTER] [ENTER]
So the expect script would be something like:
#!/usr/bin/expect -f
set randNum [(( ( RANDOM % 100 ) + 1 ))]
send -- "sudo vpnName\r"
send -- "*password*\r"
send -- "randNum\r \r"

You are trying to combine a bash expression with tcl (language expect is using)
Use instead:
set randNum [expr {int(rand()*100) + 1}]

In expect, you need to spawn a process before you can interact with it:
#!/usr/bin/expect -f
set randNum [expr {int(rand()*100) + 1}] # as per #Sorin's answer
spawn sudo vpnName
expect "assword" # wait for the password prompt
send -- "*password*\r"
expect "whatever matches the random number prompt"
send -- "$randNum\r\r"
# this keeps the vpn process running, but returns interactive control to you:
interact
A tip: while debugging expect code, launch it with expect -d -f file.exp -- this is very valuable to let to see if your expect patterns are matching as you think they should.

Related

What does it mean to combine interact with return in expect?

I want to use shell and expect to log in to the server with only one command.
But when interact and return are used in one statement, I don't understand what they mean.In addition, "Password" will only appear when SSH link multiplexing is disconnected, so I hope that when "Password" does not appear, it will not send "mypassord".
So how to write scripts to deal with these two situations in a unified way?
When SSH multiplexed links were established, my script was stuck in "expect "Password"".How to deal with it?
In addition, I would like to ask what does the -o parameter mean after the interact? And what is the meaning of using interact and return together?
What's the difference between "send --" and "send "?
I have enabled SSH master connection. Before I log on to the server, I need to first enter the password, and then enter a number to represent which machine I log on to. But when SSH multiplexed links are established, the option of entering passwords becomes redundant.
#!/usr/bin/expect
# ssh command
set cmd [lindex $argv 0]
set relay_num [lindex $argv 1]
set timeout -1
# run ssh command
spawn bash -c "$cmd"
expect "Password*"
send "mypassord\r"
interact -o -nobuffer -re "Option" return
send -- "$relay_num\r"
interact
In combination with the -o switch, that line is looking at the output of the command for the regex "Option". The return will cause the interact command to end and the script continues to the send command (that is explained in the man page).
I think it would be more clear to use expect "Option" instead.

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/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!

Automating pear install with expect

I'm working on a small script to try and automate the installation of pear on a system should it be missing - basically, taking the interactive portions of the installation process and making them non-interactive so all I need to do is execute the script and it does it for me on its own. This is my first time trying to use expect and it's, well, not what I expected.
Sample code below:
/usr/bin/expect <<EOD
set timeout 20
set number [lindex $argv 0]
set path [lindex $argv 1]
spawn php -q go-pear.phar
expect "1-11, 'all' or Enter to continue:"
send "$number\r"
interact
expect "Installation base ($prefix) [/usr/local] :"
send "$path\r"
interact
EOD
This is not quite working at the moment as it's trying to fill in the /usr/local portion in the first menu as follows:
1-11, 'all' or Enter to continue: invalid command name "/usr/local"
while executing
"/usr/local"
invoked from within
"expect "Installation base () [/usr/local] :""
Can anyone show me what I'm missing here? Also, I'd like to get this so I don't have to run it as ./script $var0 $var1 but rather just run it as ./script with everything being contained within and filled in, but not sure how to do that.
Thanks in advance for any help you can offer.
One problem is that the [...] inside the string tells expect to execute a command. I think you need to escape them with \[...\].
Hope this helps a little =)
I ended up figuring this out. In case anyone else wants to do something similar, here's what worked for me:
/usr/bin/expect <<EOF
set send_slow {1 .1}
set timeout -1
spawn php -q go-pear.phar
expect "1-11, 'all' or Enter to continue:"
sleep .1
send -s -- "1\r"
expect -exact "1\r"
sleep .1
send -s -- "/usr/local\r"
expect -exact "/usr/local\r"
expect "1-11, 'all' or Enter to continue:"
sleep .1
send -s -- "\r"
expect -exact "\r"
expect EOF

Remote SSH and command execution using BASH and Expect

I'm trying to achieve the following with a bash script:
try for SSH connection, if fails, error out if SSH connection is
once confirmed, execute the 'top' command and save results to file
scp file back from remote server
I know the invidiual commands, which would be:
1) to check the ssh connection:
ssh -q user#ip exit
echo $?
This should return '0' on success and '255' on error.
2) to execute top and save to file would be:
top -n 1 -b > /tmp/top.out
3) scp back file from remote host
expect -c "
set timeout 1
spawn scp user#host:/tmp/top.out root#anotherhost:/.
expect yes/no { send yes\r ; exp_continue }
expect password: { send password\r }
expect 100%
sleep 1
exit
"
Now putting this altogether is my problem, to be more specific:
I can't seem to be able to get the returned '0' and '255' values when using expect to test the SCP connection.
I can't seem to be able to execute the top command using expect again, i.e. this doesn't work:
expect -c "
set timeout 1
spawn ssh user#host top -n 3 -b > /tmp/top.out
expect password: { send password\r }
sleep 1
exit
"
and therefore the 3rd bit won't work either.
Any help is appreciated. Please bear in mind that my script is a .sh script with the #!/bin/bash declaration -- I cannot use #!/usr/bin/expect for various reasons.
Now that's ugly stuff. There are many pitfalls, probably you are not quite sure at what places you execute with a shell, or with plain argv array. Also the expect stuff is not the way that is supposed to be done, that's only brittle.
(AFAIK, expect is mainly meant to be used to communicate with modems (AT command set) and the like).
Use SSH keys for automated ssh and scp instead of passwords. Once you've done that (or even before, but then you have to enter passwords manually), launch this in your shell:
$ ssh user#server "top -n 1 -b" > /tmp/top.out
and the file will be on your local machine. (Because redirection was done locally, not yet remotely). No need to scp.
That's all there is to it.
Check this one:
https://stackoverflow.com/a/23632210/524743
expect <<'END'
log_user 0
spawn sh -c {echo hello; exit 42}
expect eof
puts $expect_out(buffer)
lassign [wait] pid spawnid os_error_flag value
if {$os_error_flag == 0} {
puts "exit status: $value"
} else {
puts "errno: $value"
}
END
hello
exit status: 42
From the expect man page
wait [args]
delays until a spawned process (or the current process if none is named) terminates.
wait normally returns a list of four integers. The first integer is the pid of the process that was waited upon. The second
integer is the corresponding spawn id. The third integer is -1 if an
operating system error occurred, or 0 otherwise. If the third integer
was 0, the fourth integer is the status returned by the spawned
process. If the third integer was -1, the fourth integer is the value
of errno set by the operating system. The global variable errorCode is
also set.

Resources