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

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."

Related

How to write a conditional expect script to accept different set of passwords based on servers?

My requirement is to write an expect script that will expect different set of passwords for different set of jump servers. Trouble is that I cannot generalize the
expect 'Password:'
as it is common between different servers. So my only way to identify the server is to read the banner before hand and check for a specific string and send the password accordingly.
I do not know how to read the banner in an expect command for spawn ssh
My code is as below
#!/usr/bin/expect
set host [lindex $argv 0]
set pass1 'xxx'
set pass2 'yy'
spawn ssh $host
expect "Password:*"
send "$pass1\r"
expect {
"Password:*" {
send "$pass2\r"
exp_continue
}
}
It seems strange that you would need to identify the server based on some string in a banner it returns when you already have its identity in the $host variable. Unless the specified hostname resolves to multiple servers that do not share a common password.
Assuming that situation and a banner returned by each host saying "Welcome to servername", you can do something like this:
#!/usr/bin/expect
set host [lindex $argv 0]
spawn ssh $host
expect {
-ex {Welcome to server1} {
set pass "xxx"
}
-ex {Welcome to server2} {
set pass "yy"
}
default {
# Something unexpected happened
exit 1
}
}
expect {
-ex "Password: " {
send "$pass\r"
}
}
This first looks for the welcome message and sets the pass variable to the password for the specific server. Then it sends the selected password to the server after it finds the Password prompt.

Run command on cisco switch

I made the below script to login to a switch and execute a command. as soon as i execute the script it logins to switch and exits without running the command.
#!/usr/bin/expect -f
set timeout 3000
spawn bash -c "ssh ananair#10.60.255.100"
expect "*assword:"
send "pass#123\n"
expect "*#"
send "show interfaces status"
I suspect the problem is the missing \n in your final send. If you simply send "string", then expect never hits Enter at the end of the command, And since there's no final expect, it figures its work is done, and exits, probably even before it has a chance to echo the command it sent but never executed.
Consider the following:
#!/usr/bin/expect -f
set timeout 3000
spawn ssh switchhostname # no need to run this inside a shell
expect "ssword:"
send "1ns3cur3\n" # it would be better to use key based auth...
expect "#"
send "term len 0\n" # tell the switch to avoid "More" prompts
expect "#"
send "show interfaces status\n" # note the final "\n"
expect "#" # don't quit until the "show" command finishes
You might also consider getting access to this information via SNMP.
try \r instead of \n
I have my expect script that I use to login to my cisco switches for me. and then I use interact from the expect script that leaves me at the cisco prompt.
I would need to rework it to not show passwords but I can definitely help you out.
I didn't need timeouts in mine.
#!/usr/bin/expect
proc enable {} {
expect "assword:" {
send "<enable password>\r"
expect "#" {
}
}
}
proc login {} {
send "<login password>\r"
expect {
"failed" {
send "<username>\r"
enable
}
">" {
send "en\r"
enable
}
"#" {
}}
}
set user_computer_attached [lindex $argv 0]
set user_computer [split $user_computer_attached "#"]
spawn ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 \
-oGlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null \
-oStrictHostKeyChecking=no $user_computer_attached
expect_after eof {
wait
spawn telnet [lindex $user_computer 1]
expect "name:"
send [lindex $user_computer 0]
send "\r"
expect "assword:" {
login
}
}
expect "assword:" {
login
}
interact

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).

expect script is not sending correct value

I wrote an expect script which will connect to a server and send a password depending on the prompt. The purpose is to update my password on new servers.
Basically, it will connect to a new server, expect to see (current) UNIX password: and then send my initial password. It will then see the 'New password:and 'Retype new password: prompts sending the new password each time.
My script loads these passwords from files into variables $pw1 and $pw2. I have evaluated them and verified that I have the correct values loaded into the variables. However, when I run the script, I am getting a token manipulation error on the initial password which tells me the value being sent is incorrect.
Perhaps my logic is incorrect?
EDIT: I have connected to one server that I'm running the script against and entered the old password exactly as it is in the file that the script loads into $pw2. It is working so I know that the password is not incorrect.
#! /usr/bin/expect --
#exp_internal 1
#set stty_init raw
set timeout 45
set prompt {\$ $}
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/bin/.pw1.txt]
set pw2 [exec cat /home/user/bin/.pw2.txt]
#puts $pw1
#puts $pw2
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
"*assword*" {
send -- "$pw1\r"
expect {
$prompt {
send -- exit\r
}
}
send -- exit\r
expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
"(current)*" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
}
"continue*" {
send "yes\r"
expect {
"current" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
# expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
}
}
}
}
puts \r
First, I'd like to note that you don't need to nest expect commands: look into the exp_continue command, instead. It will cut your code size in half simply by eliminating duplication.
Second, it looks your logic is, indeed, wrong. The first expect pattern is "*assword*". I would expect this to match when the remote server of your ssh is prompting for the user's current password, and it sends $pw1. However, everywhere else in your code, you seem to be treating $pw2 as the current password, and $pw1 as the new password.
Also, when you rewrite your code using exp_continue to eliminate the duplication and nesting, make sure you re-order your patterns so they're listed in order of most specific to least specific. Otherwise, you'll sometimes find the wrong pattern being matched, and the wrong code being executed.

Is it possible to access an expect-spawned process from other expect scripts, called in bash

I have an expect script that opens a telnet session and performs the authorization. After the authorization has been performed, I want to be able to call from bash other expect scripts, that perform various actions in the same session.
Is it possible?
Not exactly getting what you want because if you provide some code or script then batter but anyway i just having bash script which only open telnet session which is first part what you want now let me know what next part while gone through script so i can modify my script as you want.
#!/bin/bash
testUserAction()
{
expect<<EOF
set timeout 200
spawn telnet $1
expect "login:"
send "testuser\r"
expect "Password"
send "12345678\r"
;;;;;;;;;;;;;#rest of expect which you want
EOF
}
testUserAction 171.172.12.1
It is possible see below example:
#!/usr/local/bin/expect
set user "user"
set host "host"
set pass "password"
spawn telnet $host
set timeout 10
expect {
timeout {
puts "Unable to connect to $host"
exit 1
}
"login: " {
send -- "$user\r"
exp_continue
}
"assword: " {
send -- "$pass\r"
}
}
#Call the other expect script on host
send -- "./test.exp $user\r"
expect {
"($user)" {
puts "OK got it!"
}
}
And then the test.exp script which should be in users home directory on host
#!/usr/local/bin/expect
set name [lindex $argv 0]
puts "Welcome ($name)"

Resources