expect, interact and then again expect - linux

There are several posts regarding the same, but i still not able to make my expect script work properly. My intention is to automate everything but leave the password enter for the user. So there are 3 parts of the script:
automated login
give the user interaction to enter the password
give control back to Expect script to continue work
So i have script which will be spawned and which have 3 read commands. First and last should be filled by Expect and second one i would like to enter my self:
#!/bin/ksh
read user?User:
echo "Expect entered the username $user"
read pass?Password:
echo "User entered the password $pass"
read command?"Shell>"
echo "Expect entered the command $command"
My expect script:
#!/usr/bin/expect
spawn ./some_script
expect User
send I-am-expect\r
expect Password
interact
expect Shell
send I-am-expect-again
Unfortunately after i have entered the password the script does not continue and left in the interact mode:
[root#localhost ~]# ./my-expect
spawn ./some_script
User:I-am-expect
Expect entered the username I-am-expect
Password:i am user
User entered the password i am user
Shell>
And finally when i entering something on the "Shell" and pressing [ENTER] expect exits with the error:
Expect entered the command
expect: spawn id exp4 not open
while executing
"expect Shell"
(file "./my-expect" line 7)
[root#localhost ~]#
I appriciate any explanation or resolution of this issue. I am using expect version 5.45

You can read (expect_user) the user's password by yourself and then send it to the spawn'ed program. For example:
[STEP 101] # cat foo.exp
proc expect_prompt {} \
{
global spawn_id
expect -re {bash-[.0-9]+(#|\$)}
}
spawn ssh -t 127.0.0.1 bash --noprofile --norc
expect "password: "
stty -echo
expect_user -timeout 3600 -re "(.*)\[\r\n]"
stty echo
send "$expect_out(1,string)\r"
expect_prompt
send "exit\r"
expect eof
[STEP 102] # expect foo.exp
spawn ssh -t 127.0.0.1 bash --noprofile --norc
root#127.0.0.1's password:
bash-4.3# exit
exit
Connection to 127.0.0.1 closed.
[STEP 103] #

The interact should be given with proper condition for the exit criteria.
The following script will execute the user commands in the shell
exeCmds.sh
#!/bin/bash
read -p "User: " user
echo "Expect entered the username $user"
read -p "Password: " pass
echo "User entered the password $pass"
while :
do
# Simply executing the user inputs in the shell
read -p "Shell> " command
$command
done
automateCmdsExec.exp
#!/usr/bin/expect
spawn ./exeCmds.sh
expect User
send dinesh\r
expect Password
send welcome!2E\r
expect Shell>
puts "\nUser can interact now..."
puts -nonewline "Type 'proceed' for the script to take over\nShell> "
while 1 {
interact "proceed" {puts "User interaction completed.";break}
}
puts "Script take over the control now.."
# Now, sending 'whoami' command from script to shell
send "whoami\r"
expect Shell>
# Your further code here...
The script automateCmdsExec.exp will address the login needs of the bash script and when the prompt arrives, it will hand over the control to user.
We should define an exit criteria for the interact for which I have used the word proceed. (You can alter it as per your need).
Once interact matched the word proceed. it will return the control back to the expect script.
For demo purpose, I kept one more send-expect pair of command.
i.e.
send "whoami\r"
expect Shell>
You can keep your further code below the interact, thus it can be executed by script.

Related

Expect if condition with ssh password

I am currently trying to create a script with error handling.
Basically the script tests the ssh connection with this command :
ssh -o BatchMode=yes $machine uname -a
There is 3 potential situation that i want to handle :
SSH works just fine without password
SSH is blocked because the machine isn't in the known_hosts file in .ssh
SSH is blocked because the machine isn't in the known_hosts file in .ssh AND it requires a password to continue (which means the id_rsa.pub isn't in the authorized_keys file in .ssh
I have on main script that is calling an expect script here is what the main script looks like :
ssh -o BatchMode=yes ${machine} uname -a &> temp-file.txt 2>&1
# Here we test the ssh connection just once and we store the output of the command in a temp file
if [ $? -eq 255 ]
# If the ssh didn't work
then
if grep -q "Host key verification failed." temp-file.txt
# If the error message is "Host key verification failed."
then
expect script-expect-knownhosts.exp ${machine} 2>&1 >/dev/null
And here is the script-expect-knownhosts.exp file in which i tried to make a condition :
#!/usr/bin/expect -f
set machine [lindex $argv 0]
# Here we state that the first argument used with the command will be the $machine variable
set prompt "#|%|>|\$ $"
set timeout 60
spawn ssh $machine
# We do a ssh on the machine
set prompt "#|%|>|\$ $"
expect {
"Are you sure you want to continue connecting (yes/no)? " {send "yes\r";exp_continue}
# If he asks for a yes/no answer, then answer yes to add the machine to the known_hosts file
-exact "Password: " {send -- "^C";exp_continue}
# If he asks for a password, then send a CTRL + C
-re $prompt {send "exit\r";exp_continue}
# If the prompt shows up (if after the yes/no question, we don't need to put a password in) then type exit
}
So here is what happens when i execute the expect script with a machine in case number 2 (works just fine):
spawn ssh machine
Are you sure you want to continue connecting (yes/no)? yes
machine:~ # exit
deconnection
Connection to machine closed.
And here is what happens when i execute the expect script with a machine in case number 3 :
spawn ssh machine
Are you sure you want to continue connecting (yes/no)? yes
Password:
And it stays stuck on Password until i manually do a CTRL + C
In cases 2 and 3 you don't need exp_continue because you are stopping the connection process.
For case 2, I don't think you really want to send a control-C. When you do this interactively, typing control-C has the effect of sending a signal to kill the process you are interacting with. What you really want is to stop the ssh process, so instead of send -- "^C";exp_continue you should just do close.

Using 'expect' command to pass password to SSH running script remotely

I need to create a bash script that will remotely run another script on a batch of machines. To do so I am passing a script through SSH.
ssh -p$port root#$ip 'bash -s' < /path/to/script/test.sh
I thought it would use my RSA keys but I am getting error:
"Enter password: ERROR 1045 (28000): Access denied for user 'root'#'localhost' (using password: YES)"
I tried using sshpass to no avail. So my next solution was using expect. I have never used expect before and I'm positive my syntax is way off.
ssh -p$port root#$ip 'bash -s' < /path/to/script/test.sh
/usr/bin/expect <<EOD
expect "password"
send "$spass\n"
send "\n"
EOD
I have root access to all machines and ANY solution will do as long as the code remains within bash. Just keep in mind that this will be done in a loop with global variables ($spass, $ip, $port, etc) passed from a parent script.
You are doing it wrong in two means:
If you want expect to interact with ssh, you need to start ssh from expect script and not before.
If you put the script (/path/to/script/test.sh) to stdin of ssh, you can't communicate with the ssh process any more.
You should rather copy the script to remote host using scp and then run it.
Expect script might look like this:
/usr/bin/expect <<EOF
spawn ssh -p$port root#$ip
expect "password"
send "$Spass\r"
expect "$ "
send "/path/to/script/on/remote/server/test.sh\r"
expect "$ "
interact
EOF
#!/usr/bin/expect
#Replace with remote username and remote ipaddress
spawn /usr/bin/ssh -o StrictHostKeyChecking=no username#IPAddress
#Replace with remote username and remote ipaddress
expect "username#IPAddress's password: "
#Provide remote system password
send "urpassword\n"
#add commands to be executed. Also possible to execute bash scripts
expect "$ " {send "pwd\n"} # bash command
expect "$ " {send "cd mytest\n"}
expect "$ " {send "./first.sh\n"} # bash scripts
expect "$ " {send "exit\n"}
interact

Get one line of user input and then execute it as Bash commands

I have wrote a expect script that helps to execute commands in remote machine. When the execution is completed, I want to get one line of user input and then send it to the remote bash, here is the code snippet:
#! /usr/bin/env expect
...
spawn ssh -l $user $host
...
send_tty -- "Enter your command: "
set timeout -1
# match only printable characters (prevent from pressing TAB)
expect_tty eof exit -re {([[:print:]]*)\n}
send_tty -- "\n"
set timeout 10
# send the command to remote shell
send "$expect_out(1,string)"
expect "$GENERAL_PROMPT"
However, if the input is something like: ls /", my program will be blocked because the remote shell expects to get more characters by prompting the string "> ". Actually, I hope bash won't prompt for more input instead of just printing error message:
$ read COMMAND
ls /"
$ eval "$COMMAND"
bash: unexpected EOF while looking for matching `"'
bash: syntax error: unexpected end of file
Can I achieve this in my script?
#!/usr/bin/expect
set prompt "#|%|>|\\\$ $"; # A generalized prompt to match known prompts.
spawn ssh -l dinesh xxx.xx.xx.xxx
expect {
"(yes/no)" { send "yes\r";exp_continue}
"password"
}
send "mypassword\r"
expect -re $prompt
send_tty -- "Enter your command: "
set timeout -1
# match only printable characters (prevent from pressing TAB)
expect_tty eof exit -re {([[:print:]]*)\n}
send_tty -- "\n"
set timeout 10
puts "\nUSER INPUT : $expect_out(1,string)"
# send the command to remote shell
# Using 'here-doc', to handle possible user inputs, instead of quoting it with any other symbol like single quotes or backticks
send "read COMMAND <<END\r"
expect -re $prompt
send "$expect_out(1,string)\r"
expect -re $prompt
send "END\r"
expect -re $prompt
# Since we want to send the literal dollar sign, I am sending it within braces
send {eval $COMMAND}
# Now sending 'Return' key
send "\r"
expect -re $prompt
Why 'here-doc' used ?
If I have used backticks or single quotes to escape the commands, then if user gave backticks or single quotes in the commands itself, then it may fail. So, to overcome that only, I have added here-doc.
Output :
dinesh#MyPC:~/stackoverflow$ ./zhujs
spawn ssh -l dinesh xxx.xx.xx.xxx
dinesh#xxx.xx.xx.xxx's password:
[dinesh#lab ~]$ matched_literal_dollar_sign
Enter your command: ls /"
USER INPUT : ls /"
read COMMAND <<END
> ls /"
> END
[dinesh#lab ~]$ eval $COMMAND
-bash: unexpected EOF while looking for matching `"'
-bash: syntax error: unexpected end of file
[dinesh#lab ~]$ dinesh#MyPC:~/stackoverflow$
Update :
The main reason for using here-doc is due to the fact that it makes the read to act as non-blocking command. i.e. We can proceed quickly with next command. Else, we have to wait till the timeout of Expect. (Of course, we could change the timeout value dynamically.)
This is just one way of doing it. You can alter it if you want, with simply having the read command.
I think this would be a good case for interact -- get expect to step aside and let the user interact directly with the spawned program.
spawn ssh -l $user $host
#...
send_user "You are now about to take control: type QQQ to return control to the program\n"
interact {
QQQ return
}
send_user "Thanks, I'm back in charge ...\n"
This is a one line version
read > export cmd ; eval $cmd ; unset cmd

How to pass a value from bash into expect?

I've got a really simple bash script which requires expect.
I need to pass a value from bash into expect and I'm not trying to ssh into another server or anything (cause I only seem to find questions regarding logging into another server via ssh).
The idea is simply something like this:
#!/usr/bin/env bash
echo "Please enter your password: "
read PASSWD
x=$(expect -c '
spawn su -c 'whoami'
expect "Password:"
send "$PASSWD\r"
interact
')
So this doesn't work. The expect shell doesn't recognize the $PASSWD variable.
How may this be accomplished?
Thank you.
Another option would be to store the PASSWD in the environment and let expect pick it up there:
read -p "Your password: " passwd
export passwd
expect -c '... ; send "$env(passwd)\r"; ...'
Probably the best choice security-wise is have expect prompt for the password: then, the password will not show up on the command line nor in the environment.
expect -c <<'END'
stty -echo
send_user "Your password: "
expect_user -re "(.*)\n"
send_user "\n"
set passwd $expect_out(1,string)
stty echo
# your script starts here
...
send "$passwd\r"
...
END
Variables within single quotes are not expanded by the shell, that's why in this case $PASSWD remains the literal string $PASSWD.
Try changing the quotes:
#!/usr/bin/env bash
echo "Please enter your password: "
read PASSWD
x=$(expect -c "
spawn su -c 'whoami'
expect 'Password:'
send '$PASSWD\r'
interact
")
One more note: you should be aware that this could pose a security risk, as the password will be visible in plaintext in the process list while the command is running.

Automating telnet session using Bash scripts

I am working on automating some telnet related tasks, using Bash scripts.
Once automated, there will be no interaction of the user with telnet (that is, the script will be totally automated).
The scripts looks something like this:
# execute some commands on the local system
# access a remote system with an IP address: 10.1.1.1 (for example)
telnet 10.1.1.1
# execute some commands on the remote system
# log all the activity (in a file) on the local system
# exit telnet
# continue with executing the rest of the script
There are two problems I am facing here:
How to execute the commands on the remote system from the script (without human interaction)?
From my experience with some test code, I was able to deduce that when telnet 10.1.1.1 is executed, telnet goes into an interactive session and the subsequent lines of code in the script are executed on the local system. How can I run the lines of code on the remote system rather than on the local one?
I am unable to get a log file for the activity in the telnet session on the local system. The stdout redirect I used makes a copy on the remote system (I do not want to perform a copy operation to copy the log to the local system). How can I achieve this functionality?
While I'd suggest using expect, too, for non-interactive use the normal shell commands might suffice. telnet accepts its command on stdin, so you just need to pipe or write the commands into it through heredoc:
telnet 10.1.1.1 <<EOF
remotecommand 1
remotecommand 2
EOF
(Edit: Judging from the comments, the remote command needs some time to process the inputs or the early SIGHUP is not taken gracefully by telnet. In these cases, you might try a short sleep on the input:)
{ echo "remotecommand 1"; echo "remotecommand 2"; sleep 1; } | telnet 10.1.1.1
In any case, if it's getting interactive or anything, use expect.
Write an expect script.
Here is an example:
#!/usr/bin/expect
#If it all goes pear shaped the script will timeout after 20 seconds.
set timeout 20
#First argument is assigned to the variable name
set name [lindex $argv 0]
#Second argument is assigned to the variable user
set user [lindex $argv 1]
#Third argument is assigned to the variable password
set password [lindex $argv 2]
#This spawns the telnet program and connects it to the variable name
spawn telnet $name
#The script expects login
expect "login:"
#The script sends the user variable
send "$user "
#The script expects Password
expect "Password:"
#The script sends the password variable
send "$password "
#This hands control of the keyboard over to you (Nice expect feature!)
interact
To run:
./myscript.expect name user password
Telnet is often used when you learn the HTTP protocol. I used to use that script as a part of my web scraper:
echo "open www.example.com 80"
sleep 2
echo "GET /index.html HTTP/1.1"
echo "Host: www.example.com"
echo
echo
sleep 2
Let's say the name of the script is get-page.sh, then this will give you an HTML document:
get-page.sh | telnet
I hope this will be helpful to someone ;)
This worked for me..
I was trying to automate multiple telnet logins which require a username and password. The telnet session needs to run in the background indefinitely since I am saving logs from different servers to my machine.
telnet.sh automates telnet login using the 'expect' command. More info can be found here: http://osix.net/modules/article/?id=30
telnet.sh
#!/usr/bin/expect
set timeout 20
set hostName [lindex $argv 0]
set userName [lindex $argv 1]
set password [lindex $argv 2]
spawn telnet $hostName
expect "User Access Verification"
expect "Username:"
send "$userName\r"
expect "Password:"
send "$password\r";
interact
sample_script.sh is used to create a background process for each of the telnet sessions by running telnet.sh. More information can be found in the comments section of the code.
sample_script.sh
#!/bin/bash
#start screen in detached mode with session-name 'default_session'
screen -dmS default_session -t screen_name
#save the generated logs in a log file 'abc.log'
screen -S default_session -p screen_name -X stuff "script -f /tmp/abc.log $(printf \\r)"
#start the telnet session and generate logs
screen -S default_session -p screen_name -X stuff "expect telnet.sh hostname username password $(printf \\r)"
Make sure there is no screen running in the backgroud by using the
command 'screen -ls'.
Read
http://www.gnu.org/software/screen/manual/screen.html#Stuff to read
more about screen and its options.
'-p' option in sample_script.sh
preselects and reattaches to a specific window to send a command via
the ‘-X’ option otherwise you get a 'No screen session found' error.
You can use expect scripts instaed of bash.
Below example show how to telnex into an embedded board having no password
#!/usr/bin/expect
set ip "<ip>"
spawn "/bin/bash"
send "telnet $ip\r"
expect "'^]'."
send "\r"
expect "#"
sleep 2
send "ls\r"
expect "#"
sleep 2
send -- "^]\r"
expect "telnet>"
send "quit\r"
expect eof
The answer by #thiton was helpful but I wanted to avoid the sleep command. Also telnet didn't exit the interactive mode, so my script got stuck.
I solved that by sending telnet command with curl (which seems to wait for the response) and by explicitly telling telnet to quit like this:
curl telnet://10.1.1.1:23 <<EOF
remotecommand 1
remotecommand 2
quit
EOF
Following is working for me...
put all of your IPs you want to telnet in IP_sheet.txt
while true
read a
do
{
sleep 3
echo df -kh
sleep 3
echo exit
} | telnet $a
done<IP_sheet.txt
#!/bin/bash
ping_count="4"
avg_max_limit="1500"
router="sagemcom-fast-2804-v2"
adress="192.168.1.1"
user="admin"
pass="admin"
VAR=$(
expect -c "
set timeout 3
spawn telnet "$adress"
expect \"Login:\"
send \"$user\n\"
expect \"Password:\"
send \"$pass\n\"
expect \"commands.\"
send \"ping ya.ru -c $ping_count\n\"
set timeout 9
expect \"transmitted\"
send \"exit\"
")
count_ping=$(echo "$VAR" | grep packets | cut -c 1)
avg_ms=$(echo "$VAR" | grep round-trip | cut -d '/' -f 4 | cut -d '.' -f 1)
echo "1_____ping___$count_ping|||____$avg_ms"
echo "$VAR"
Use ssh for that purpose. Generate keys without using a password and place it to .authorized_keys at the remote machine. Create the script to be run remotely, copy it to the other machine and then just run it remotely using ssh.
I used this approach many times with a big success. Also note that it is much more secure than telnet.
Here is how to use telnet in bash shell/expect
#!/usr/bin/expect
# just do a chmod 755 one the script
# ./YOUR_SCRIPT_NAME.sh $YOUHOST $PORT
# if you get "Escape character is '^]'" as the output it means got connected otherwise it has failed
set ip [lindex $argv 0]
set port [lindex $argv 1]
set timeout 5
spawn telnet $ip $port
expect "'^]'."
Script for obtain version of CISCO-servers:
#!/bin/sh
servers='
192.168.34.1
192.168.34.3
192.168.34.2
192.168.34.3
'
user='cisco_login'
pass='cisco_password'
show_version() {
host=$1
expect << EOF
set timeout 20
set host $host
set user $user
set pass $pass
spawn telnet $host
expect "Username:"
send "$user\r"
expect "Password:"
send "$pass\r"
expect -re ".*#"
send "show version\r"
expect -re ".*-More-.*"
send " "
expect -re ".*#"
send "exit\r"
EOF
}
for ip in $servers; do
echo '---------------------------------------------'
echo "$ip"
show_version $ip | grep -A3 'SW Version'
done
Here is a solution that will work with a list of extenders. This only requires bash - some of the answers above require expect and you may not be able to count on expect being installed.
#!/bin/bash
declare -a Extenders=("192.168.1.48" "192.168.1.50" "192.168.1.51")
# "192.168.1.52" "192.168.1.56" "192.168.1.58" "192.168.1.59" "192.168.1.143")
sleep 5
# Iterate the string array using for loop
for val in ${Extenders[#]}; do
{ sleep 0.2; echo "root"; sleep 0.2; echo "ls"; sleep 0.2; } | telnet $val
done
Play with tcpdump or wireshark and see what commands are sent to the server itself
Try this
printf (printf "$username\r\n$password\r\nwhoami\r\nexit\r\n") | ncat $target 23
Some servers require a delay with the password as it does not hold lines on the stack
printf (printf "$username\r\n";sleep 1;printf "$password\r\nwhoami\r\nexit\r\n") | ncat $target 23**

Resources