Expect script to handle RSA finger print keys - linux

I am trying to make my expect script more robust and to be able to handle more situations to be more automated. Currently my script works fine, however, there are times where I will be asked to add the RSA keys to known_hosts, I want this to default to yes all the time. My server doesn't always ask for the keys, once added then after awhile it wont ask until you delete the keys or switch gatways. After looking online i have tried to add this (commented code) in my working code and after added that it stalls at password input screen if RSA key had been added already.
So my question is, is there a way to handle this situation lets say if RSA has been added already, it will just skip to the password line?
[user#gateway my_direcotry]$ cat loadItTest
#!/usr/bin/expect -f
set timeout 600
set user root
set host 1.1.1.1
set pass pass
spawn ssh $user#$host
#expect {
# -re "RSA key fingerprint" {send "yes\r"}
#}
expect "assword:"
send "$pass\r"
expect "#"
Sample output:
[root#gateway my_direcotry]# loadItTest
spawn ssh root#1.1.1.1
root#1.1.1.1's password:

This is where the exp_continue command comes into play, to essentially create a "loop" within the expect command:
spawn ssh $user#$host
expect {
"RSA key fingerprint" {send "yes\r"; exp_continue}
"assword:" {send "$pass\r"}
}
expect "#"
If you see the "RSA key" pattern, answer "yes" but then keep waiting for the "assword" pattern. If you don't see "assword" before "RSA key", that's OK.
The body for the "assword" pattern does not contain exp_continue. After you send the password, the enclosing expect command will return, and the next command is to expect your prompt.
Note I removed the -re option: there are no regex-special characters in that pattern.

Related

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

How to make a string as an optional to wait for it in an expect script?

The below script works in the case, where all three questions are being asked, but sometimes the Do you trust the above certificate [y|N] --> question is not asked by asadmin ... and so my expect fails.
Question
Is it possible to make the Do you trust the above certificate [y|N] --> question optional, so the expect script doesn't fails when this question in not asked?
#!/usr/bin/expect
set password [lindex $argv 0]
spawn asadmin --user admin change-admin-password
expect "password"
send "\n"
expect "password"
send "$password\n"
expect "password"
send "$password\n"
expect "Do you trust the above certificate \[y\|N\] -->"
send "y\n"
expect eof
exit
set password [lindex $argv 0]
spawn asadmin --user admin change-admin-password
# First time, when we see the password, we are simply typing 'return' key
expect "password"
send "\n"
expect {
"password" { send "$password\n"; exp_continue }
-ex "Do you trust the above certificate \[y|N] -->" {send "y\n";exp_continue}
timeout { puts "Timeout happened." }
eof { exit }
}
As you can see, exp_continue will help us in getting what you need.
If expect sees password, it will send the password value. Notice the use of exp_continue in there.
It will cause the expect to run again. So, the expect will see the password twice and if suppose, expect sees the question, it will send 'y\n'. If it sees eof before, then script will exit.
Please note that I have kept the first expect statement with password separately outside. The reason being is nothing but the value we are sending is different for the first time alone.
Also note the use of the -ex flag in the expect statement as below.
-ex "Do you trust the above certificate \[y|N] -->" {send "y\n";exp_continue}
It will make the expect to prevent any sort of special pattern matching. It is sufficient to escape the first square bracket alone.

Passing Variable with Special characters as password breaks bash script. How do I Sanitize special characters in bash?

I am looking for a solution to sanitizing variables in my bash password change script.
The script below is working, however I have found that some "Special Characters" will break my script. I am not controlling at the moment what Characters are being passed through. I am either looking to sanitize the variables before passing them through, or pushing the variables as a whole untouched. I have tried using '${PASS}' in place of "${PASS}" however the script would not complete when this was the case.
I would appreciate any recommendations anyone could offer. I have tried searching for the answer to this question before posting but didn't find anything relative so i am sorry if this has been answered elsewhere.
#!/bin/bash
# Two variables are passed, Username and new Password.
USERNAME=$1
PASS=$2
expect << EOF
spawn passwd ${USERNAME}
expect "Enter new UNIX password:"
send "${PASS}\r"
expect "Retype new UNIX password:"
send "${PASS}\r"
expect eof;
EOF
expect << EOF
spawn htdigest /.passwd "Authenticated Users" ${USERNAME}
expect "New password:"
send "${PASS}\r"
expect "Re-type new password:"
send "${PASS}\r"
expect eof;
EOF
expect << EOF
spawn htpasswd /squiddb ${USERNAME}
expect "New password:"
send "${PASS}\r"
expect "Re-type new password:"
send "${PASS}\r"
expect eof;
EOF
Thank you in advance!
Send the username and password to the expect scripts via command-line arguments to expect instead. As done now, a double quote would confuse expect since the here-to document is interpolated fully before sent to expect's stdin.
A password like 'hej"' without the single quotes would lead to a send command for expect looking like this:
send "hej"\r"
expect will not enjoy that.
You can access the argument via argv, beware of quoting. Do not that you will expose the username and password to anyone doing "ps" on that box if you pass them as arguments on the command line to expect. But you already do that when calling the script in the question...
Why not use the Expect shell directly for doing this.
#!/usr/bin/expect
set timeout 20
set user [lindex $argv 0]
set password [lindex $argv 1]
spawn passwd $user
expect "Enter new UNIX password:"
send "$password\r";
expect "Retype new UNIX password:"
send "$password\r";
wait 1
spawn htdigest /.passwd "Authenticated Users" $user
expect "New password:"
send "$password\r"
expect "Re-type new password:"
send "$password\r"
wait 1
spawn htpasswd /squiddb $user
expect "New password:"
send "$password\r"
expect "Re-type new password:"
send "$password\r"
exit 0
Execute the above like
./SCRIPTNAME.exp user password

parse information from expect command

I need to parse resulting data from a telnet/ssh command and act on the data.
As an example, I want to interact with a spawn session (ssh here), list files in current dir and collect only file of a certain extension to later execute a command on those files only.
What I've got so far:
#!/usr/bin/expect
set timeout 3
match_max 10000
set prompt {$ }
spawn ssh $user#$host
expect "password: "
send $pw\r
expect $prompt
# here's the command I need to parse resulting data
send "ls -1\r"
expect -re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
That script opens a ssh session, sends the command and displays all the files in current dir (log and others) but I need to process each file matching a given pattern, how can I do this?
script output:
$ DATA:
0_system.log
1_system.log
2_system.log
3_system.log
a.log
a.sh
blah
b.sh
data.csv
First of all... wouldn't ls -1 *.log be easier, and do the trick?
That said, I have found that usually you have to be very careful when using (.*), it can have at times unexpected results.
I would go with "any alphanumeric character plus underscore" (if that works for your filenames) - see my suggested expect block below. Also, keep in mind that by using $expect_out(1,string) you are saving only the filename, without the extension - not sure if that is what you want. If you want the whole thing, $expect_out(0,string) is the way to go in this case.
This will do it:
expect -re "(\[a-zA-Z0-9_\]*)\.log" {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
Hope that helps!
James
Corrected Expect probably should be:
expect {
-re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue;
}
}

Using if/else in expect script

I recently updated and reformatted my /etc/hosts file and would like to run through every system (roughly 1000) to refresh my known_hosts file. I'm looking at using an expect script to send "yes" to the RSA fingerprint question. Simple enough. However, I know some of these systems are completely new to me and my password has not been set. This creates two possibilities:
"yes" is sent to the RSA fingerprint question and I'm logged into
the server. I'll then need to send an exit to close the connection
before moving onto the next host. Or...
"yes" is sent to the RSA fingerprint question and I'm presented with
the prompts to update my password starting with the current and
followed by the new password entered twice. The connection will
automatically close after the password is updated moving onto the
next host.
I think I have a basic grasp of the concept of "if/else" in expect, but I don't fully understand how to nest them, if there is a better way, or if I'm completely off-base to begin with.
This is what I have right now:
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/.pw1.txt]
set pw2 [exec cat /home/user/.pw2.txt]
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting"{
send "yes\r"
expect {
"current" {
send $pw2\r
} "New password" {
send $pw1\r
} "Retype new password" {
send $pw1\r
}
}
expect "msnyder"
send "exit\r"
}
interact
}
The file1 variable is the list of hosts to run the script against.
I know it isn't accurate because it errors on line 22. But, I have no idea what needs to be fixed.
Two errors I spotted:
missing close brace, probably for the "continue connecting" block
missing space before the open brace of "continue connecting". Tcl (hence Expect) is very sensitive to whitespace as it is parsed into words before the commands are evaluated. For the very few gory details, see the 12 syntax rules of Tcl.
Your code might look like:
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
exp_continue
}
"New password" {
send -- $pw1\r
exp_continue
}
"Retype new password" {
send -- $pw1\r
exp_continue
}
msnyder
}
send "exit\r"
}
}
interact
}
Notes:
exp_continue is used to "loop" back up to the expect statement: in this case, you will expect to see all of "current", "new" and "retype", so you don't want to bail out until you see your prompt.
get into the habit of typing send -- something. Without the double dash, you'll be surprised the day someone types in a password with a leading dash.
You can probably avoid having the script run like a human and just spawn an expected to fail ssh connection which will automatically accept the host RSA key and not bother with prompting for a password; you can do that later for new systems where you need to initiate a password.
Temporarily try adding this to your ~/.ssh/config file until your script is finished:
Host *
Protocol 2
PasswordAuthentication 0
StrictHostKeyChecking 'no'
CheckHostIP 'no'
UserKnownHostsFile ~/.ssh/known_hosts_new
Then when the new know_hosts_new file is loaded up, you can replace the default ~/.ssh/known_hosts and remove the UserKnownHostsFile line from your config.

Resources