Expect in Alias - bash

I am using a Bash alias that allows me to shorten the SSH command in order for me to log into my routers. Quite trivial, but a time saver! What I would now like to do is take this a step further and fully automate the logging-in of the routers.
For example in my ~/.bashrc file I have the following entry:
sshFuncB()
{
ssh -o StrictHostKeyChecking=no superuser#$1 - | /usr/bin/expect<<EOF
set timeout 5
set send_human {.1 .3 1 .05 2}
expect {
"password: " { send -h "MYPASSWORD\r" }
"No route to host" { exit 1 }
timeout { exit 1 }
}
set timeout 2
sleep 1
expect {
"N]?" { send "y\r"; exp_continue }
timeout { exit 1 }
}
expect eof
EOF
}
alias z=sshFunc
However, when I type z myrouterhostname this does not give the desired output. I must find a way to start the SSH connection and have expect automate logging in before returning control to user.
Any ideas?

This can be done as follows,
sshFuncB()
{
expect -c "
spawn ssh -o StrictHostKeyChecking=no superuser#$1
set timeout 5
set send_human {.1 .3 1 .05 2}
expect {
\"password: \" { send -h \"MYPASSWORD\r\" }
\"No route to host\" { exit 1 }
timeout { exit 1 }
}
set timeout 2
sleep 1
expect {
\"N]?\" { send \"y\r\"; exp_continue }
timeout { exit 1 }
}
expect eof
"
}
alias z=sshFuncB
Note the use of -c flag in expect which you can refer from here of you have any doubts.
If we use double quotes for the expect code with -c flag, it will allow the bash substitutions. If you use single quotes for the same, then bash substitutions won't work. (You have used #1 inside expect, which is why I used double quotes) Since I have used double quotes for the whole expect code, we have to escape the each double quotes with backslash inside the expect statement like as follows,
expect {
# Escaping the double quote with backslash
\"password: \" {some_action_here}
}
One more update. Since this is about connecting to the router and do some of your manual operations, then it is better to have interact at the end.

Related

Read program output from stdin instead of using spawn

I'm trying to write a shell function that spawns a ssh process and authentificates with a password. Then, I'd like to use the spawned process to do further stuff with expect.
Here's the function I have so far:
ssh_cmd() {
ssh $USER#$HOST 2>&1 | expect -c "
log_user 0
set timeout 5
expect password: {
send \"$PASS\n\"
sleep 2
log_user 1
}
"
}
And then I'd like to use given function in other places to interact with the ssh process like this:
ssh_cmd | expect -c "
expect '#' {
send pwd\n
send exit\n
}
expect eof
"
However, running the ssh_cmd function with -d option for expect I get the following result:
expect version 5.45.4
expect: does "" (spawn_id exp0) match glob pattern "password:"? no
ubnt#ui1's password: expect: timed out
From what I understand, the output of ssh does not get piped correctly. I know the common way to do this would be to use spawn, but that would mean the process would get killed after expect exits and I could not have a generic function that authentificates ssh sessions and keeps the process alive for further usage.
What you're designing won't work. Expect needs the process to be spawned from within the expect interpreter using the spawn command. Passing the command's stdout into expect is insufficient.
You could try this:
ssh_cmd() {
# a default bit of code if user does not provide one.
# you probably want some checking and emit an error message.
local user_code=${1:-set timeout 1; send "exit\r"; expect eof}
expect -c "
log_user 0
set timeout 5
spawn ssh -l $USER $HOST
expect password: {
send \"$PASS\n\"
sleep 2
log_user 1
}
$user_code
"
}
and then invoke it like:
ssh_cmd '
expect "#" {
send pwd\r
send exit\r
}
expect eof
'
Note that single quotes have no special meaning in expect, they are just plain characters: you probably don't want to expect the prompt to be the 3 character pattern '#'

how to make command "ps" don't show password in expect script?

I have make an example as below. The password(mingps)is the shell variable. When execute the shell script, in the mean while, execute command "ps -ef", I found the result of "ps" showed the password(mingps). For security reason, I don't want to show the password when execute command "ps -ef". So how to hide it? Thanks in advance.
#!/bin/sh
MalbanIP="XXX.XXX.XXX.XXX"
MalbanLogin="ming"
MalbanPwd="mingps"
MalbanCmd="netstat"
firstTime="true"
/usr/bin/expect <<EOF
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"; set firstTime "false"; exp_continue
}
}
"password" {
if {$firstTime == "true"} {
send "$MalbanPwd\r"; set firstTime "false"
} else {
log_user 1; puts stdout "password is wrong"; log_user 0;
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
EOF
exit 0
Option 1
The best way is to switch to using RSA keys to log in, as this will enable you to significantly strengthen your overall system security substantially. With that, you can probably avoid using Expect entirely.
Option 2
However, if you can't do that, the key to fixing things is to not pass it as either an argument or an environment variable (since ps can see both with the right options). Instead, you pass the password by writing it into a file and giving the name of that file to the Expect script. The file needs to be in a directory that only the current user can read; chmod go-rx will help there.
MalbanPwdFile=/home/malban/.securedDirectory/examplefile.txt
# Put this just before the spawn
set f [open $MalbanPwdFile]
set MalbanPwd [gets $f]
close $f
You might also need to put a backslash in front of the use of $MalbanPwd so that it doesn't get substituted by the shell script part too early.
Option 3
Or you could stop using that shell wrapper and do everything directly in Tcl/Expect.
#!/usr/bin/expect
set MalbanIP "XXX.XXX.XXX.XXX"
set MalbanLogin "ming"
set MalbanPwd "mingps"
set MalbanCmd "netstat"
set firstTime true
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"
set firstTime false
exp_continue
}
}
"password" {
if {$firstTime} {
send "$MalbanPwd\r"
set firstTime false
} else {
log_user 1
puts stdout "password is wrong"
log_user 0
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
I suspect that this last option will work best for you in the longer term. It's definitely the simplest one (other than switching to RSA keys, which is what I've got deployed on my own infrastructure) and I think it is going to avoid some subtle bugs that you've got in your current code (due to substitution of variables at the wrong time).

How to pass a bash array into expect

I have a shell script that passes a array variable to expect. But, in the expect part, it only takes the first argument and says "couldn't read file --server: no such file or directory"
below is the example program:-
Here i want the complete value of ${CMPREQUEST_ARGS[#]} which is
cmpclient --ir --server 10.10.10.10 --port 4040
CMPREQUEST=($CMPCLIENT "${CMPREQUEST_ARGS[#]}")
echo "TEST:${CMPREQUEST[#]}:TEST" //echo prints the value of ${CMPREQUEST[#]} correctly.
expect -c "
log_file -noappend -a \"/srv/Log/log/cmpclient-$app_id.log\"
log_user 1
set RET_VAL 1
set timeout 86400
puts "TEST2:${CMPREQUEST[#]}:TEST2"
spawn \${CMPREQUEST[#]}
expect {
-re \"SUCCESS\:\ write\ X509\" {
set RET_VAL 0
}
timeout { set RET_VAL 1 }
}
exit \$RET_VAL
"
exit $?
I am getting this error in spawn
couldn't read file --server: no such file or directory
Please guide..Any help would be highly appreciated.
Thanks in advance!
This boils down to quoting hell. Use a shell here-doc to hold the expect code, then you don't need to escape all the "interior" quotes. Your main issue is the spawn command, where you are preventing the shell from expanding the array.
Try this:
expect <<END_EXPECT
log_file -noappend -a /srv/Log/log/cmpclient-$app_id.log
log_user 1
set RET_VAL 1
set timeout 86400
puts "TEST2:${CMPREQUEST[#]}:TEST2"
spawn ${CMPREQUEST[#]}
expect {
-re {SUCCESS: write X509} {
set RET_VAL 0
}
timeout { set RET_VAL 1 }
}
exit \$RET_VAL
END_EXPECT
Only the expect variable RET_VAL needs to be protected from the shell.

Expect returns same value

I need to be able to return a different password after the first one fails which will be the second time the prompt asks for the same expect value "Password:"
(expect -c "
#exp_internal 1
set passwords {PASS1 PASS2}
set index 0
set timeout 20
# Start the session with the input variable and the rest of the hostname
spawn telnet $host
set timeout 3
expect {
-ex \"Password:\" {
send \"[lindex $passwords $index]\r\"
incr index
exp_continue;
}
}
I just can't get it to work. It looks like there is nothing in the lindex send:
-ex "Password:" {
send "[lindex ]\r"
The problem is you're using double quotes to group the expect script -- bash is interpreting the $variables as bash variables. You need to either:
use single quotes to delimit the script, or
escape all the \$expect_variables so bash does not substitute them first.

while loops within expect

I am using expect within bash. I want my script to telnet into a box, expect a prompt, send a command. If there is a different prompt now, it has to proceed or else it has to send that command again.
My script goes like this:
\#!bin/bash
//I am filling up IP and PORT1 here
expect -c "
set timeout -1
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
set temp 1
while( $temp == 1){
expect {
Prompt1 { send \"command\" }
Prompt2 {send \"Yes\"; set done 0}
}
}
"
Output:
invalid command name "while("
while executing
"while( == 1){"
Kindly help me.
I tried to change it to while [ $temp == 1] {
I am still facing the error below:
Output:
invalid command name "=="
while executing
"== 1"
invoked from within
"while [ == 1] {
expect {
This is how I'd implement this:
expect -c '
set timeout -1
spawn telnet [lindex $argv 0] [lindex $argv 1]
send "\r"
send "\r"
expect {
Prompt1 {
send "command"
exp_continue
}
Prompt2 {
send "Yes\r"
}
}
}
' $IP $PORT1
use single quotes around the expect script to protect expect variables
pass the shell variables as arguments to the script.
use "exp_continue" to loop instead of an explicit while loop (you had the wrong terminating variable name anyway)
The syntax for while is "while test body". There must be a spce between each of those parts which is why you get the error "no such command while)"
Also, because of tcl quoting rules, 99.99% of the time the test needs to be in curly braces. So, the syntax is:
while {$temp == 1} {
For more information see http://tcl.tk/man/tcl8.5/TclCmd/while.htm
(you probably have other problems related to your choice of shell quotes; this answer addresses your specific question about the while statement)

Resources