Testing account existence using expect - bash

I have a list of 400 servers and I like to check unix account existence with expect to loop it
I wrote a bash script that uses expect command but it returns me error message that I don't understand the meaning
#!/bin/bash
fic_serv="test.txt"
echo "Passwd"
stty -echo
read -s passwd
stty echo
suffix="suffix"
account="acc"
for server in `cat $fic_serv`
do
prompt="[$acc#$server ~]$ "
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect "Password: "
send "$passwd\r"
expect $prompt
send "logout\r"
"
done
[acc#serv ~]$ couldn't read file "
send "passwd\r"
expect [acc#server ~]$
send "logout\r"
": no such file or directory
(I modified the value)

You should use while, not for, to parse files in Bash. Use a "redirect" to treat a file as standard input and read one line at a time.
while read server; do
...
done < $fic_serv
Your major problem is Expect interprets your "s as "end of script". Escape them, as in \", or use {}, as in:
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect {Password: }
send {$passwd\r}
expect $prompt
send {logout\r}
"

If you have 400 servers to manage, I strongly recommend you use ansible.
You could just put the list of hosts into a file, let's call it inventory, and run the following command:
ansible -i inventory -m shell -a "id acc" all

Using here-docs in the shell to embed code for another language is usually better than quoting hell, and sharing variables through the environment is easier and safer than parameter expansion:
export account passwd
while IFS= read -r server; do
export prompt="[$acc#$server ~]$ "
export host="$server.$suffix"
expect << 'END_EXPECT'
spawn ssh -o StrictHostKeyChecking=no $env(account)#$env(host)
expect "Password: "
send "$env(passwd)\r"
expect $env(prompt)
send "logout\r"
expect eof
END_EXPECT
done < "$fic_serv"
As shown, I like to indent the heredoc to make it more obvious.
And depending on the error message or login prompt, there can be more logic to indicate that the account name and/or password are incorrect.

Related

Expect: how to spawn a command containing a backslash?

I have the following script:
#!/bin/bash
echo -n "Enter user name: "
read USER
echo -n "Enter password: "
read -s PWD
cat $HOME/etc/switches.txt | while read IP SWITCH
do
echo ${SWITCH}
/usr/bin/expect <<EOD
# Change to 1 to Log to STDOUT
log_user 1
# Change to 1 to enable verbose debugging
exp_internal 1
# Set timeout for the script
set timeout 20
spawn ssh -l {$USER} -oCheckHostIP=no -oStrictHostKeyChecking=no -q $IP
match_max [expr 32 * 1024]
expect "Password:"
send $PWD
send "\n"
expect "#"
send "show fcip summary | grep TRNK\n"
EOD
echo
done
When I run it, the backslash in the username disappears, giving these result:
Enter user name: corp\user
Enter password:
=== ss3303-m-esannw-m01a ===
spawn ssh -l corpuser -oCheckHostIP=no -oStrictHostKeyChecking=no -q 10.247.184.70
[...]
I suspect my problem is due in part to embedding my expect script inside a bash script. I've tried using $USER and "$USER" as well, with the same results. Using corp\\\\user (yes, four backslashes!) does work but is inconvenient. I'm seriously considering using sed or something to multiply the backslashes, but would love to hear other ideas.
You might have better luck passing the variables through the environment so expect can access them directly, instead of relying on the shell to substitute the values into the heredoc:
#!/bin/bash
read -p "Enter user name: " USER
read -sp "Enter password: " PWD
export USER PWD IP
while read IP SWITCH
do
echo ${SWITCH}
# the heredoc is single quoted below
/usr/bin/expect <<'EOD'
# Change to 1 to Log to STDOUT
log_user 1
# Change to 1 to enable verbose debugging
exp_internal 1
# Set timeout for the script
set timeout 20
match_max [expr {32 * 1024}]
spawn ssh -l $env(USER) -oCheckHostIP=no -oStrictHostKeyChecking=no -q $env(IP)
expect "Password:"
send -- "$env(PWD)\r"
expect "#"
send "show fcip summary | grep TRNK\r"
expect eof
EOD
echo
done <$HOME/etc/switches.txt
Notes:
the heredoc is single-quoted: the shell will not try to interpolate variables
exported the shell variables used in the expect code
use \r to "press enter" for the send command.
tidied up the input of the username and password
tidied up reading the text file

Redirect grep output to file in server

I am attempting to write a script that greps for something in a number of servers and appends the output of all of them into a single file. The servers are password protected. I use expect to enter the servers and pass the grep command but I am hoping to get the output of each of them to end up in a single file.
Here is an overview of what I want to do:
spawn ssh xxx#server1
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep <something> /some/log/file >> file.txt"
expect "$ "
send "exit\r"
... then continue doing this in dozens more servers with the output of the grep command appending to file.txt each time. I don't mind where the file.txt actually is. It can be on my local computer or any of the servers.
The best I've come up with would be to put each of these in a file on the server the grep is being done on and then scp all those files to local and appending them all. This seems incredibly wasteful though, so I am looking for a way to send the output to a server or to local from a server.
It would be both easier to automate and more secure if you used public key authentication instead of password authentication to get to the servers. Then you could simply loop over them like this:
for host in server1 server2 server3 ...; do
ssh -n "$host" 'grep <something> /some/log/file'
done >file.txt
Since you have password access, you can easily put a public key in .ssh/authorized_keys to enable key access first. You can do it with your expect script:
spawn ssh xxx#server1
expect "password: "
send "PASSWORD\r"
expect "$ "
send "mkdir -p .ssh\r"
expect "$ "
send "cat >>.ssh/authorized_keys <<EOF"
send "(public key goes here)\r"
send "EOF\r"
expect "$ "
send "chmod 0700 .ssh\r"
expect "$ "
send "chmod 0600 .ssh/authorized_keys\r"
expect "$ "
send "exit\r"
If for some reason you must use a solution with password-entry, you can append to a file with expect with something like:
log_user 0 # to not see the output on screen
set fh [open foo.log a] # open the file for appending
set servers {user#server user#server2 […]}
foreach s $servers {
spawn ssh user#server
[…]
send "command"
expect "$ " { puts $fh "$expect_out(buffer)"}
}
close $fh

reading a variable from the user in an expect script

Below is my script:
#!/usr/bin/expect
echo "enter unique id"
read test
mkdir -p /evoting_test/$test
spawn scp -r abc#10.150.10.104:/shareddata/was/l1/*.pdf /rishabh/$test/
set pass "abc123"
expect {
password: {send "$pass\r"; exp_continue}
}
I am getting error:
invalid command name "echo"
while executing
"echo "enter uNIQUE id" "
(file "./scp_test.sh" line 2)
It is not reading variable from user and use that variable in command
The "expect" way to do that is:
send_user "enter unique id: "
expect_user -re "(.*)\n"
set test $expect_out(1,string)
send_user "you said $test\n"
Since expect extends tcl, you could use:
puts -nonewline "enter unique id: "
flush stdout
gets stdin test
puts "you said $test"
Additionally, you'll get an error for mkdir -- that's an external command. You can do one of:
exec mkdir -p /evoting_test/$test # invoke the external command
file mkdir /evoting_test/$test # use the builtin
See http://tcl.tk/man/tcl8.6/TclCmd/file.htm
IMHO the best way to solve this out would be to separate the two files each of which is using a different interpreter as per its requirement.
First File: will contain the code to scp all the pdf files to the desired location and you can read the destination location in this script as well.
Use expect to run the above file and get your work done.

how to add ssh key to host via bash script

I've been trying to automate the creation of a user and configuration of the ssh access.
So far I created a script that access the host and creates the new user via expect, as follows:
expect -c '
spawn ssh '$user'#'$ip';
expect "assword: ";
send "'$passwd'\r";
expect "prompt\n";
send "adduser '$new_user'\r";
...
send "mkdir /home/'$new_user'/.ssh\r";
expect "prompt\n";
send "exit\r";
'
This works fine, after that I need to add the .pub key file to the authorized keys file in the host, there is where hell started.
I tried:
ssh_key='/home/.../key.pub'
content=$(cat $ssh_key)
expect -c '
spawn ssh '$user'#'$ip' "echo '$content' >> /home/'$new_user'/.ssh/authorized_keys;
expect "password:";
...
'
and got:
missing "
while executing
"spawn ssh root#000.00.00.00 ""
couldn't read file "<ssh .pub key content> ...
I tried also:
cat $ssh_key | ssh $user#$ip "cat >> /home/$new_user/.ssh/authorized_keys"
Without success, I only get the password query blinking, I can't connect the expect with this last method.
I'm going to ignore the larger problems here and focus specifically on your question. (There are larger problems: Don't use expect here -- if you rely on sshpass instead you can simplify this script immensely).
Right now, when you close your single quotes, you aren't starting any other kind of quotes. That means that when you substitute a variable with whitespace, you end the -c argument passed to expect.
Instead of doing this:
'foo'$bar'baz'
do this:
'foo'"$bar"'baz'
...so your script will look more like:
ssh_key='/home/.../key.pub'
content=$(<"$ssh_key")
expect -c '
spawn ssh '"$user"'#'"$ip"' "echo '"$content"' >> /home/'"$new_user"'/.ssh/authorized_keys;
expect "password:";
...
'
In terms of avoiding this altogether, though, consider something more like the following:
#!/bin/bash
# ^^^^- NOT /bin/sh
content=$(<"$ssh_key") # more efficient alternative to $(cat ...)
# generate shell-quoted versions of your variables
# these are safe to substitute into a script
# ...even if the original content contains evil things like $(rm -rf /*)
printf -v content_q '%q' "$content"
printf -v new_user_q '%q' "$new_user"
# use those shell-quoted versions remotely
sshpass -f"$password_file" ssh "$host" bash -s <<EOF
adduser ${new_user_q}
printf '%s\n' ${content_q} >>/home/${new_user_q}/.ssh/authorized_keys
EOF

perl program not running thorough automated script until it is executed manually first

i am writing code to automate some steps . First it is required to switch user and then run a perl script. Here is my code
if [ -a /try/Test ]
then
su trial -c ". /try/.profile Test"
expect -c 'spawn try1;
send "3\r";
send "1\r";
send "show\r";
interact';
fi
try1 is my perl program which i am trying to call.This script throws this error
couldn't execute "try1": no such file or directory
while executing
"spawn try1"
but once i do this step manually and then run this script then this script runs without nay error.
I think you've already asked about it (and I did answer, didn't I)?
Here's the basic skeleton (make sure to add error/timeout/unexpected output handling):
# collect password
stty -echo
send_user -- "Password: "
expect_user -re "(.*)\n"
send_user "\n"
stty echo
set pass $expect_out(1,string)
spawn sudo sh;
expect -re ": *$";
send -- "$pass\r"
expect -re "\$ *$";
send "echo SETTING PARAMS\r";
expect -re "\$ *$";
send "echo RUNNING MY COMMAND\r";
expect -re "\$ *$";
interact

Resources