Loop a function with specific condition - linux

I have a function:
set timeout 20
set f [open "password.txt"]
set password [read $f]
close $f
spawn ssh user#example.net -p 724
expect "user#example.net's password:"
send $password
interact
in the password.txt I have only 1 word
After running this script using expect in terminal I have the access to the server if that single word that exist there is the right one.
I have 2 problems:
SOLVED---------1)I want to loop the execution of this script in such a way that in case I will have 2,5,100 words in password.txt the script will try all of them for password, not only the first one as it is now.
2)If I will manage to loop the execution of the function using all words from the
password.txt, I need to make on if statement that will solve this issue(after 3 attempts to write the password server disconnects you so you need to connect again after each 3 attempts.)
Update: the server answer after first 2 attempts like this Permission denied, please try again. after the last attempt (3th one) it's says Permission denied (publickey,password). and it throw's me to my normal command line state.
I am trying to do something like
while read line
do
echo "Trying: $line"
echo ssh user#example.net -p 724 --password "$line" ## NOT REAL CODE
done < password.txt
but have no idea how to implement this logic in the expect syntax.
Till now I managed to write this
set timeout 20
set f [open "password.txt" r]
set password [read $f]
close $f
foreach i $password {
puts "trying this as a pass : $i"
spawn ssh user#example.net -p 724
expect "user#example.net's password:"
send $i
interact
}
It solves me first problem but the bug is that I need to press manaul "enter" after the server asks for the password. And after I hit "enter" the script tries first word from the list, then if it's wrong one again I need to press "enter" and the script will try the second word from the list.
How can I simulate pressing "enter" in the script?

Related

Looping over lines from a file in expect

New at scripting, beware...
I'm attempting to use this code that i put together from reading a couple blogs. The idea is for this script to read the IPs that i have saved on ips.txt and then run the code to ssh to the read IP using the credentials given, perform a couple send commands as described below, exit the ssh session, and repeat with the second line in the ips.txt file, which is a different IP, until it finishes the IPs list.
Note: the ips.txt file is a simple list of IP addresses as follows (no spaces between the IPs):
192.168.0.2
192.168.0.3
192.168.0.4
...
The spawn, expect, and send commands work fine. It also loops fine back up to the beginning of the code but it will NOT read the second IP in the ips.txt file; it will just read the first one again and perform the same steps over and over.
Please assist...
#!/usr/bin/expect
set timeout 180
set username admin
set password Changeme1
set fildes [open "ips.txt" r]
set ip [gets $fildes]
while {[string length $ip] != 1} {
spawn ssh $username#$ip
expect "password:"
send "$password\r"
expect ".mi"
send "show sw\r"
expect ".mi"
send "exit\r"
set ip [gets $fildes]
}
close $fildes
Let's try to boil the code down to something that minimally reproduces the problem. You say that the loop repeats with the same ip value, right? So let's remove the code interacting with the remote system:
#!/usr/bin/expect
set fildes [open "ips.txt" r]
set ip [gets $fildes]
while {[string length $ip] != 1} {
puts $ip
set ip [gets $fildes]
}
close $fildes
What happens when you run this?
I expect that the while condition will never be satisfied: you'll read the file, printing each line, then you print an inifinite number of blank lines. When you read past the last line of a file, you get the empty string as a result, not a string with length 1.
You most likely want
#!/usr/bin/expect
set timeout 180
set username admin
set password Changeme1
set fildes [open "ips.txt" r]
# the 2-argument form of `gets` returns -1 when it can't read another line
while {[gets $filedes ip] != -1} {
spawn ssh $username#$ip
expect "password:"
send "$password\r"
expect ".mi"
send "show sw\r"
expect ".mi"
send "exit\r"
expect eof ;# wait for the connection to close
}
close $fildes

How to turn off stderr in Expect script or stop "Connection to ... closed" message after exiting?

So I have been trying to learn bash scripting with expect and I'm running into an annoying problem where even after I turn off output by setting log_user to 0, the logout message for when I exit the ssh server is still printed as "logout
Connection to blah.blah.blah closed". Going the hackish way and issuing the exit command inside the script to expect is undesirable, and neglects to print a new line in the terminal, leaving an unsightly terminal prompt behind. How do I suppress or turn off this connection closed message in my expect script? Here's a template of the code for my expect script:
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password:"
set PASSW "your_password_here\n"
# Set SFTP Prompt
set SFTP "sftp> "
# Set Remote Prompt and Project Directories
# The GL server is super weird, so make sure RPROMPT ends in the
# name of the project compilation directory and PROJDIR is the
# relative path (from home but without the tilde) of the main
# project directory.
set RPROMPT "projdir_here]"
set PROJDIR "path_to_projdir_here"
# Set SSH Address
set ADDRESS your_username_here#your_address_here
# Annoying long output turned off
log_user 0
send_user "Logging in...\n"
spawn sftp $ADDRESS
expect $PASSP
send $PASSW
expect $SFTP
send_user "Uploading necessary files...\n"
send "cd $PROJDIR\n"
expect $SFTP
send "put *.cpp .\n"
expect $SFTP
send "put *.h .\n"
expect $SFTP
send "put Makefile .\n"
expect $SFTP
send "exit\n"
spawn ssh $ADDRESS
expect $PASSP
send $PASSW
expect "]"
send "cd $PROJDIR\n"
expect $RPROMPT
send_user "Cleaning remote directory...\n"
send "make clean\n"
# Output turned on temporarily so compiler feedback can be determined
expect $RPROMPT
log_user 1
send_user "Compiling on server...\n"
send "make\n"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect $RPROMPT
log_user 0
send_user "\nSubmitting Requisite Files...\n"
send "make submit\n"
expect $RPROMPT
send_user "Quitting...\n"
# I can't figure out how to turn off stderr in expect so that an annoying 'connection closed'
# message is printed even the log_user has been set to 0
send "logout\n"
# Transfer control to user
interact
You can see that at the end of the program, before I use the interact command, that the offending message is printed after "logout\n" is sent and the connection to the server is closed. This happens despite log_user being set to 0 above. How on earth do I turn off stderr output inside my script? I've even looked at the manual pages for expect at expect man page but there didn't seem to be much useful there. I don't understand how to turn off stderr inside an expect script so that this message isn't printed, help would be much appreciated! I really just want the script to be quiet when I'm logging out. Because it's pretty obvious that I issued a logout command, I'm not sure why this output goes to stderr. Is there a command in ssh that won't output a "Connection to...closed" message when the user exits? It would be better if I could just pipe stderr to oblivion or suppress it somehow. The normal ways don't seem to work because this is an expect script.
It is just the incorrect use of log_user. It should be used as,
log_user 0
send "logout\r"
expect eof
log_user 1
Note that I am expecting for eof pattern after sending logout\r, because it will lead to the closure of the connection. Always use \r in place of \n while using the send command.
Read here to know more about the log_user to suppress the output.
Instead of having a prompt to defined as a static string, it can be generalized as,
# We escaped the `$` symbol with backslash to match literal '$'
# The last dollar sign represents line-end.
set prompt "#|>|%|\\\$ $";
While the expect is used, we have to accompany with -re flag to specify that as a regular expression.
expect -re $prompt
SshDemo.exp
#!/usr/bin/expect
set prompt "#|>|\\\$ $"
spawn ssh dinesh#myserver
expect {
"(yes/no)" { send "yes\r";exp_continue}
"password"
}
send "welcome123\r"
expect -re $prompt
# Simply executing 'ls -l' command...
send "ls -l\r"
expect -re $prompt
log_user 0
send "logout\r"
expect eof
log_user 1
Here's my new, improved script based on the suggestions above. I also decided to use scp instead of sftp because it's much more concise and I'm only uploading files.
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password: "
set PASSW "your_password_here"
# Set Remote Prompt and Project Directories
# The ssh server is super weird, so make sure PROJDIR is the
# directory name of the main project directory and PATH is
# the relative path to the project directory from home (with the tilde)
set PROJDIR "your_projdir_name_here"
set PATH "your_projdir_path_here"
# Set SSH Address
set ADDRESS your_username_here#source_hostname_here
# Turn off annoying output
log_user 0
send_user "Logging in and uploading necessary files...\n"
# upload files via scp, (shows upload prompt for user, concise)
spawn bash -c "scp *.cpp *.h Makefile $ADDRESS:$PATH"
expect $PASSP
send "$PASSW\r"
interact
spawn ssh $ADDRESS
expect $PASSP
send "$PASSW\r"
expect "]"
send "cd $PATH\r"
expect "$PROJDIR]"
send_user "\nCleaning remote directory...\n"
send "make clean\r"
# Output turned on temporarily so compiler feedback can be determined
expect "$PROJDIR]"
log_user 1
send_user "Compiling on server...\n\n"
send "make\r"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect "$PROJDIR]"
log_user 0
send_user "\n\nSubmitting Requisite Files...\n"
send "make submit\r"
expect "$PROJDIR]"
send_user "Quitting...\n"
send "logout\r"
expect eof
# Exit
exit

Linux bash script - For loops issues

I'm working on a bash script that will add users in a batch process. This code goes as follows:
#!/bin/bash
# A script that creates users.
echo "This is a script to create new users on this system."
echo "How many users do you want to add?"
read am
echo " "
for i in {0..$am..1}
do
echo "Enter a username below:"
read usern
sudo useradd $usern
sudo passwd $usern
echo " "
echo "User $am '$usern' added."
done
In this case, I wanted to make 4 users. I went through and entered the username "callum3" and set the password as "1234" for ease of login. Once I input everything (correctly, may I add) the terminal window displays the following.
User 4 'callum3' added.
This shows that my for loop isn't actually working, when I can see nothing wrong with it. I have tried using a while loop with no luck there either.
Am I making a rookie mistake here or is there something deeper going on?
Although I suspected it, for a better understanding on what could be wrong with your script I pasted it in shellcheck.net. That the problem is in the line:
for i in {0..$am..1}
Bash doesn't support variables in brace range expansions. That is, you cannot use a variable in an expression like {..}.
Instead, use seq. With seq $var you get a sequence from 1 (default) to $var:
for i in $(seq "$am")
I feel like I'm missing something in that nobody has suggested an arithmetic for loop:
for ((i=0; i<am; i++)); do
…
done
This has the particular benefit in bash of being both readable and not requiring a subshell.
You can use:
for i in `seq 0 $((am-1))`
do
...
done
Sequence will start from 0 and end at $am-1

Passing variable to `expect` in bash array

I am trying to use a FOR loop to iterate over IP addresses (in a bash array), logs in, runs a script and then exits. The array is called ${INSTANCE_IPS[#]}. The following code doesn't work though, as expect doesn't seem to be able to accept the variable $instance.
for instance in ${INSTANCE_IPS[#]}
do
echo $instance
/usr/bin/expect -c '
spawn ssh root#$instance;
expect "?assword: ";
send "<password>\r";
expect "# ";
send ". /usr/local/bin/bootstrap.sh\r";
expect "# ";
send "exit\r" '
done
However, expect complains with:
can't read "instance": no such variable
while executing
"spawn ssh root#$instance"
There is another question on stackoverflow located here, that uses environmental variables to achieve this, however it doesn't allow me to iterate through different IP addresses like I can in an array.
Any help is appreciated.
Cheers
The problem is with quoting. Single quotes surrounding the whole block don't let Bash expand variables ($instance).
You need to switch to double quotes. But then, double quotes inside double quotes are not allowed (unless you escape them), so we are better off using single quotes with expect strings.
Try instead:
for instance in ${INSTANCE_IPS[#]}
do
echo $instance
/usr/bin/expect -c "
spawn ssh root#$instance;
expect '?assword: ';
send '<password>\r';
expect '# ';
send '. /usr/local/bin/bootstrap.sh\r';
expect '# ';
send 'exit\r' "
done
for instance in ${INSTANCE_IPS[&]} ; do
echo $instance
/usr/bin/expect -c '
spawn ssh root#'$instance' "/usr/local/bin/bootstrap.sh"
expect "password:"
send "<password>\r"
expect eof'
done
From the ssh man page:
If command is specified, it is executed on the remote host instead of a login shell.
Specifying a command means expect doesn't have to wait for # to execute your program, then wait for another # just to send the command exit. Instead, when you specify a command to ssh, it executes that command; it exits when done; and then ssh automatically closes the connection.
Alternately, put the value in the environment and expect can find it there
for instance in ${INSTANCE_IPS[&]} ; do
echo $instance
the_host=$instance /usr/bin/expect -c '
spawn ssh root#$env(the_host) ...
Old thread, and one of many, but I've been working on expect for several days. For anyone who comes across this, I belive I've found a doable solution to the problem of passing bash variables inside an expect -c script:
#!/usr/bin/env bash
password="TopSecret"
read -d '' exp << EOF
set user "John Doe"
puts "\$user"
puts "$password"
EOF
expect -c "$exp"
Please note that escaping quotations are typically a cited issue (as #Roberto Reale stated above), which I've solved using a heredoc EOF method, before passing the bash-variable-evaluated string to expect -c. In contrast to escaping quotes, all native expect variables will need to be escaped with \$ (I'm not here to solve all first-world problems--my afternoon schedule is slightly crammed), but this should greatly simplify the problem with little effort. Let me know if you find any issues with this proof of concept.
tl;tr: Been creating an [expect] daemon script with user authentication and just figured this out after I spent a whole day creating separated bash/expect scripts, encrypting my prompted password (via bash) with a different /dev/random salt each iteration, saving the encrypted password to a temp file and passing the salt to the expect script (highly discouraging anyone from easily discovering the password via ps, but not preventative since the expect script could be replaced). Now I should be able to effectively keep it in memory instead.

second "send" command in expect script is not waiting to finish the Output of first command

I have a very small expect script . The flow is as follows
ssh to remote machine
Execute a command and after executing the command you will be in a different prompt ( prompt name is Enter cmd>.
On the Enter cmd> prompt, I need to run many commands
Hence I designed this script
#!/usr/bin/expect
set FULL_CMD { #cmd1 #cmd2 #cmd3 }
puts "STARTING......."
log_file myfile.log ;# <<< === append output to a file
spawn ssh tempuser\#dummyserver
match_max 100000000
expect "password:"
send "temppasswd\r"
expect "*temp*"
send "cd \/home\r"
expect "*temp*"
send "new cmd prompt\r"
expect "Enter cmd>"
foreach tempcmd $FULL_CMD {
send "${tempcmd} \r "
expect -exact "Enter cmd>\r"
send -- "\r"
expect eof }
send "q"
send "exit\r"
puts "I HAVE ENDED......."
My problem: Actually the O/P of first command cmd#1 is very long and I can see that the expect script is not waiting for the O/P of the first command complete and is sending the second command cmd#2 after some time.
This script works fine on systems where O/P of cmd#1 is small, but on systems where the the O/P of cmd#1 is very large (say 1000000 lines), it has trouble, i.e. it issues the first command cmd#1 and the O/P follows, but before it get back to Enter cmd> prompt, the script issues the second command cmd#2
How can I ensure that command cmd#2 is sent only after the output of cmd#1 completes?
Try setting a timeout at the start:
set timeout 60
or use -1 to wait forever.
The problem lies in your foreach tempcmd $FULL_CMD loop:
foreach tempcmd $FULL_CMD {
send "${tempcmd} \r "
expect -exact "Enter cmd>\r"
send -- "\r"
expect eof }
The problem is that you're expecting "Enter cmd>\r", but you will never get the \r in your output.
What you should expect is "Enter cmd>$", where the $ implies the end of the current command output (and not '\nor '\r' oreof`).
foreach tempcmd $FULL_CMD {
send "${tempcmd} \r "
expect -exact "Enter cmd>$" # Check for spaces after '>' and fix as required
send -- "\r"
# expect eof
}
As for expect eof, it matches the end of the input stream which is connected to the output of the spawned process. It will only match once the spawned process terminates, thus it is of no use here
Why do you have "expect eof" in your for loop?
foreach tempcmd $FULL_CMD {
send "$tempcmd\r"
expect "Enter cmd>"
send -- "\r" # why do you need this?
}
It's smart, while developing your program, to enable debugging: add this near the top: exp_internal 1

Resources