Expect fails but I don't see why - bash

I have a bash script that gets info from Heroku so that I can pull a copy of my database. That script works fine in cygwin. But to run it in cron it halts because the shell that it uses halts as Heroku's authentication through Heroku Toolbelt.
Here is my crontab:
SHELL=/usr/bin/bash
5 8-18 * * 1-5 /cygdrive/c/Users/sam/work/push_db.sh >>/cygdrive/c/Users/sam/work/output.txt
I have read the Googles and the man page within cygwin to come up with this addition:
#!/usr/bin/bash
. /home/sam.walton/.profile
echo $SHELL
curl -H "Accept: application/vnd.heroku+json; version=3" -n https://api.heroku.com/
#. $HOME/.bash_profile
echo `heroku.bat pgbackups:capture --expire`
#spawn heroku.bat pgbackups:capture --expire
expect {
"Email:" { send -- "$($HEROKU_LOGIN)\r"}
"Password (typing will be hidden):" { send -- "$HEROKU_PW\r" }
timeout { echo "timed out during login"; exit 1 }
}
sleep 2
echo "first"
curl -o latest.dump -L "$(heroku.bat pgbackups:url | dos2unix)"
Here's the output from the output.txt
/usr/bin/bash
{
"links":[
{
"rel":"schema",
"href":"https://api.heroku.com/schema"
}
]
}
Enter your Heroku credentials. Email: Password (typing will be hidden): Authentication failed. Enter your Heroku credentials. Email: Password (typing will be hidden): Authentication failed. Enter your Heroku credentials. Email: Password (typing will be hidden): Authentication failed.
As you can see it appears that the output is not getting the result of the send command as it appears it's waiting. I've done so many experiments with the credentials and the expect statements. All stop here. I've seen few examples and attempted to try those out but I'm getting fuzzy eyed which is why I'm posting here. What am I not understanding?
Thanks to comments, I'm reminded to explicitly place my env variables in .bashrc:
[[ -s $USERPROFILE/.pik/.pikrc ]] && source "$USERPROFILE/.pik/.pikrc"
export HEROKU_LOGIN=myEmailHere
export HEROKU_PW=myPWhere
My revised script per #Dinesh's excellent example is below:
. /home/sam.walton/.bashrc echo $SHELL echo $HEROKU_LOGIN curl -H "Accept: application/vnd.heroku+json; version=3" -n https://api.heroku.com/
expect -d -c " spawn heroku.bat pgbackups:capture --expire --app gw-inspector expect {
"Email:" { send -- "myEmailHere\r"; exp_continue}
"Password (typing will be hidden):" { send -- "myPWhere\r" }
timeout { puts "timed out during login"; exit 1 } } " sleep 2 echo "first"
This should work but while the echo of the variable fails, giving me a clue that the variable is not being called, I am testing hardcoding the variables directly to eliminate that as a variable. But as you can see by my output not only is the echo yielding nothing, there is no clue that any diagnostics are being passed which makes me wonder if the script is even being called to run from expect, as well as the result of the spawn command. To restate, the heroku.bat command works outside the expect closure but the results are above. The result of the command directly above is:
/usr/bin/bash
{
"links":[
{
"rel":"schema",
"href":"https://api.heroku.com/schema"
}
]
}
What am I doing wrong that will show me diagnostic notes?

If you are going to use the expect code inside your bash script, instead of calling it separately, then you should have use the -c flag option.
From your code, I assume that you have the environmental variables HEROKU_LOGIN and HEROKU_PW declared in the bashrc file.
#!/usr/bin/bash
#Your code here
expect -c "
spawn <your-executable-process-here>
expect {
# HEROKU_LOGIN & HEROKU_PW will be replaced with variable values.
"Email:" { send -- "$HEROKU_LOGIN\r";exp_continue}
"Password (typing will be hidden):" { send "$HEROKU_PW\r" }
timeout { puts"timed out during login"; exit 1 }
}
"
#Your further bash code here
You should not use echo command inside expect code. Use puts instead. The option of spawning the process inside expect code will be more robust than spawning it outside.
Notice the use of double quotes with the expect -c flag. If you use single quotes, then bash script won't do any form of substitution. So, if you need bash variable substitution, you should use double quotes for the expect with -c flag.
To know about usage of -c flag, have a look at here
If you still have any issue, you can debug by appending -d with the following way.
expect -d -c "
our code here
"

Related

Expect script return value

I'm including simple Expect commands within a Bash script (I know I could be just writing a pure Expect script, but I would like to get it to work from within Bash).
The script is below:
#!/bin/bash
OUTPUT=$(expect -c '
spawn ssh mihail911#blah.org
expect "password:"
send "dog\r"
')
Upon ssh'ing to the above address, it will return something of the form mihail911's password: on the prompt, so I think my expect line is valid.
When I run this my script does not print anything. It does not even show the password: prompt. In general, even if I manually provide an incorrect password, I will receive a Incorrect password-type response prompt. Why is nothing printing and how can I get my script to execute properly?
I have tried debugging by using the -d flag and it seems to show that at least the first expect prompt is being matched properly.
In addition, what values should I expect in the OUTPUT variable? When I echo this variable, it simply prints the first the first command of the expect portion of the script and then mihail911's password:. Is this what it's supposed to be printing?
Use:
#!/bin/bash
OUTPUT=$(expect -c '
# To suppress any other form of output generated by spawned process
log_user 0
spawn ssh dinesh#xxx.xxx.xx.xxx
# To match some common prompts. Update it as per your needs.
# To match literal dollar, it is escaped with backslash
set prompt "#|>|\\$"
expect {
eof {puts "Connection rejected by the host"; exit 0}
timeout {puts "Unable to access the host"; exit 0;}
"password:"
}
send "root\r"
expect {
timeout {puts "Unable to access the host"; exit 0;}
-re $prompt
}
send "date\r"
# Matching only the date cmd output alone
expect {
timeout { puts "Unable to access the host";exit 0}
-re "\n(\[^\r]*)\r"
}
send_user "$expect_out(1,string)\n"
exit 1
')
echo "Expect's return value: $?"; # Printing value returned from 'Expect'
echo "Expect Output: $OUTPUT"
Output:
dinesh#MyPC:~/stackoverflow$ ./Meric
Expect's return value: 1
Expect Output: Wed Sep 2 09:35:14 IST 2015
dinesh#MyPC:~/stackoverflow$

escape $ in expect script

#!/bin/bash
/usr/bin/expect << SSHLOGIN
spawn ssh -o StrictHostKeyChecking=no admin#$host
expect {
Password: {
send "Pass$word\n"
expect {
OK: {
send "xstatus\n"
send "quit\n"
}
}
}
}
SSHLOGIN
It is not able to ssh because it is not escaping the '$' character in "Pass$word\n" since $ is part of the password and there is no variable being passed. How would you escape it? I know in bash, you would add '\', but since the password is in the expect script portion, that does not work.
EDIT:
changing Pass$word\n to Pass\\\$word\n works
Here-documents are bash code, so you'd still use \$.
The expect script is TCL, so you'll need to escape the $ there too. With two levels of escaping, you get:
send "Pass\\\$word\n"

Script not logging to log file. Why?

I have an expect/Tcl script as part of my bash script that logs into a remote router. Now, for testing purposes I am trying to handle the issue of time-out's. My problem is that the expect/Tcl script is not logging to my log file, and when it does it is logging everything the SSH connection is printing to my prompt which is not what I want.
Here's my expect script:
/usr/bin/expect<<EOF
set timeout 5
set send_human {.1 .3 1 .05 2}
set myStamp [exec date +\[%d\/%m\/%Y\ \%T\]]
set log_file ~/mylogfile.log
spawn ssh -o "StrictHostKeyChecking no" "me\#$1"
expect {
"password: " { send -h "mypassword\r" }
"No route to host" { exit 1 }
timeout { send_log "\$myStamp Timed out to $1\n"]; exit 1 }
}
send -h "reboot in 1\r"
sleep 1
send -h "exit\r"
expect eof
EOF
Please bear in mind that this is part of a function within my bash script that is passed the router name, hence the argument $1.
Any ideas?
You want to use the log_file command, not set a log_file variable
log_file ~/mylogfile.log
Other notes:
Tcl has a very nice builtin command to handle time, don't need to call out to date:
set myStamp [clock format [clock seconds] -format {[%d/%m/%Y %T]}]
the # character is not special in Tcl/expect and does not need to be escaped:
spawn ssh -o "StrictHostKeyChecking no" "me#$1"
As noted, log_file logs a transcript of the session. Just to log specific messages, you can use plain Tcl:
/usr/bin/expect <<EOF
proc log_msg {msg {to_stdout no}} {
set log_line "[timestamp -format {[%Y-%m-%d %T]}] \$msg"
set fh [open ~/mylogfile.log a]
puts \$fh \$log_line
close \$fh
if {\$to_stdout} {puts \$log_line}
}
# ...
expect {
"No route to host" {
log_msg "No route to host" yes
exit 1
}
timeout { log_msg "Timed out to $1"]; exit 1 }
}
# ...
EOF
This opens and closes the log for each message, which adds a bit of overhead. If milliseconds are important, open the log in the global scope, and use the global variable holding the file hendle in the log_msg proc.

Remove send command from terminal output while keeping needed output

I am trying to create a script that will log onto a server, run some commands while providing information back to the user.
I can log onto the server fine using the script, my issue is the output I get back. My script is something like this:
#!/bin/bash
/usr/bin/expect << SSHLOGIN
set timeout 600
spawn ssh user#myServer.com
expect {
"Are you sure you want to continue connecting (yes/no)?" {
send "yes\n";exp_continue
}
Password: {
send "$2\n"
}
}
expect {
"# " {
send "echo \"Current Directory is:\" \n"
}
}
expect "# " {
send "pwd \n"
}
expect {
"# " {
send "exit \n"
}
}
wait
SSHLOGIN
& my output is as follows:
spawn ssh user#myServer.com
Password:
You have new mail.
DISPLAY set to user.myServer:0.0
# echo "Current Directory is:"
Current Directory is:
# pwd
/home/user/
The output I am trying to achieve is something like:
spawn ssh user#myServer.com
Password:
You have new mail.
DISPLAY set to user.myServer:0.0
Current Directory is:
/home/user/
I've tried using log_user 0/1, stty etc.. but I can't seem to get it right with those...
Any help would be appreciated.
The problem is that the std output of the spawned process includes both program output and sent commands, the latter just because of echoing from the remote device.
You could manipulate stdout via the log_user command, turning it off while still expecting & capturing, and printing out the output yourself via the "puts" command. Lastly re-enable, if at all needed. The below works because Expect does not read the echoed command until the expect command.
I can't test now so I'll leave you the regexp to match the pwd output (beware of prompts with current paths), but since the point of your question is not the regexp, I figure the following will do for you:
#!/bin/bash
/usr/bin/expect << SSHLOGIN
set timeout 600
spawn ssh user#myServer.com
expect {
"Are you sure you want to continue connecting (yes/no)?" {
send "yes\n";exp_continue
}
Password: {
send "$2\n"
}
}
expect {
"# " {
send "pwd \n"
log_user 0
expect -re "(include_here_between_the_parenthesis_a_regexp_that_matches_your_pwd_output)" {
puts "Current directory is: $expect_out(1,string)"
}
log_user 1
}
expect {
"# " {
send "exit \n"
}
}
wait
SSHLOGIN
As a last comment... why not change the top line to #!/usr/bin/expect and make this an expect script as opposed to a bash one with a here documnet (or whatever that was called)? It's almost pure expect code after all.
Let me know how that goes, and don't forget upvoting or marking the answer if it indeed helped. :-)

How to use bash/expect to check if an SSH login works

My team manages many servers, and company policy dictates that the passwords on these servers must be changed every couple of weeks. Sometimes, our official database of passwords gets out of date for whatever reason (people forget to update it, usually), but we cannot identify this sometimes until months later, since we don't consistently use every server.
I want to write a script that will scrape the passwords from the database, and use those passwords to attempt an (ssh) login to each server every night, and send an email with the results to the team. I am able to scrape the database for login information, but I'm not sure how to check whether ssh login was successful or not in expect.
I cannot use public key authentication for this task. I want password authentication so I can verify the passwords.
I disable public-key authentication by specifying the following file:
PasswordAuthentication=yes
PubkeyAuthentication=no
My attempts at the expect script:
# $1 = host, $2 = user, $3 = password, $4 = config file
expect -c "spawn ssh $2#$1 -F $4
expect -re \".*?assword.*?\"
send \"$3\n\"
...
send \'^D\'"
I thought maybe exit status could indicate the success? Couldn't find anything in the man pages though.
I've been using something like the script below for a similar task.
#!/bin/sh
# Run using expect from path \
exec expect -f "$0" "$#"
# Above line is only executed by sh
set i 0; foreach n $argv {set [incr i] $n}
set pid [ spawn -noecho ssh $1#$3 $4 ]
set timeout 30
expect {
"(yes/no)" {
sleep 1
send "yes\n"
exp_continue
}
"(y/n)" {
sleep 1
send "y\n"
exp_continue
}
password {
sleep 1
send "$2\n"
exp_continue
}
Password {
sleep 1
send "$2\n"
exp_continue
}
"Last login" {
interact
}
"Permission denied" {
puts "Access not granted, aborting..."
exit 1
}
timeout {
puts "Timeout expired, aborting..."
exit 1
}
eof {
#puts "EOF reached."
}
}
set status [split [wait $pid]]
set osStatus [lindex $status 2]
set procStatus [lindex $status 3]
if { $osStatus == 0 } {
exit $procStatus
} else {
exit $procStatus
}
Do you specifically need to check if you can obtain a shell or is trying to execute a command also OK ?
If you just want to check authentication, you may want to do ssh asimplecommand (using echo, hostname, or something as such) and check if you get the expected result.
You may also want to launch ssh with -v option, and look for Authentication succeeded (at the debug1 log level).
crowbent has provided you an expect script to test ssh login however I would recommend using Non-interactive ssh password auth for testing out ssh/sftp. sshpass is much more secured and less error prone than expect.
The solution to the underlying problem (password database getting out of sync) is to use public key authentication. For everyone. Do NOT bother with passwords when it comes to SSH.
Successful login could be checked like this:
ssh -o PasswordAuthentication=no USER#HOST 'exit' || echo "SSH login failed."

Resources