Cannot use expect in Bash script - linux

In my bash script file, I try to use expect to provide password for ssh command but it doesn't work. Here is my script:
#!/bin/bash
/usr/bin/expect << EOD
spawn ssh root#192.168.1.201
expect "root#192.168.1.201's password:"
send "mypassword\r"
interact
expect eof
EOD
And the output after I execute the script:
[oracle#BTMVNSRV191 Desktop]$ ./login.sh
spawn ssh root#192.168.1.201
root#192.168.1.201's password: [oracle#BTMVNSRV191 Desktop]$
Could someone let me know, how to use expect in my script without changing #!/bin/bash to #!/usr/bin/expect?

The following works as a single line of bash script in OS X Terminal. It was only intended for use on a firewall protected LAN. Further details at my original post.
expect -c 'spawn ssh -o StrictHostKeyChecking=no remote-user#remote-IP;
expect assword; send remote-password\r; expect remote-user$;
send "sudo shutdown -h +1\r"; expect assword; send remote-password\r; interact'

ssh (and any other password reading tool) reads its password not from its standard input. It uses some tricky ioctl()-s on its terminal device. This is because you can't give them your password in a pipe.
It is not really a big problem, because widely used cleartext passwords caused more harm as if sometimes we need to find some alternative, password-less solution.
In cases of the ssh, there is a very simple thing for that. Google for ssh-keygen. I suggest to use that, configure a passwordless ssh and everything will be fine.

Related

How to ssh from one server to multiple server and switch to root and then change root password

From server A, i want to ssh multiple server using a non root user as direct root login is disabled to all server.
then i need to su - to switch to root
perform some normal operations, like changing directory, listing file etc.
and at the end change the root password using passwd command
I have expect installed on all servers and i am able to ssh from server A to any of the other servers but stuck in switching to root user and performing other operations as listed above.
#!/bin/bash
ssh user#ip<<'ENDSSH'
./su2root
#random operations
pwd
whoami
#random operations
./changepass
ENDSSH
su2root
#!/usr/bin/expect -f
spawn su -
set password "rootpass"
expect "assword:"
send "$password\r"
changepass
#!/usr/bin/expect -f
set newpassword "newrootpass"
spawn passwd
expect "*assword*"
send "$newpassword\r"
expect "*assword*"
send "$newpassword\r"
expect eof
exit 0
expecting to accomplish it using shell scripting only. Thanks in advance.
My initial comment on your question noted that you should be looking into running expect locally and spawning ssh sessions to the remote servers.
While not exactly answering your question, here is a simple expect script that logs into a remote system (one of my systems at home named valhalla), uses sudo -i to become root and executes the id command to show the user UID/GIDs. Note that I use ssh keys for logins which is why there is no expect for the initial login password.
The remote systems do not need expect in this example.
#!/usr/bin/expect
# vi: set ts=2 sw=2 noai ic showmode showmatch:
spawn ssh valhalla
expect "valhalla:"
send "sudo -i\r"
expect "assword for *"
send "XXXXXX\r"
expect "# "
send "id\r"
expect "(root)"
exit 0

how to use expect in linux? [duplicate]

I'm trying to use expect in a Bash script to provide the SSH password. Providing the password works, but I don't end up in the SSH session as I should. It goes back strait to Bash.
My script:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com'
expect "password"
send "$PWD\n"
EOD
echo "you're out"
The output of my script:
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
usr#$myhost.example.com's password: you're out
I would like to have my SSH session and, only when I exit it, to go back to my Bash script.
The reason why I am using Bash before expect is because I have to use a menu. I can choose which unit/device to connect to.
To those who want to reply that I should use SSH keys, please abstain.
Mixing Bash and Expect is not a good way to achieve the desired effect. I'd try to use only Expect:
#!/usr/bin/expect
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
# Use the correct prompt
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
send "my_password\r"
interact -o -nobuffer -re $prompt return
send "my_command1\r"
interact -o -nobuffer -re $prompt return
send "my_command2\r"
interact
Sample solution for bash could be:
#!/bin/bash
/usr/bin/expect -c 'expect "\n" { eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com; interact }'
This will wait for Enter and then return to (for a moment) the interactive session.
The easiest way is to use sshpass. This is available in Ubuntu/Debian repositories and you don't have to deal with integrating expect with Bash.
An example:
sshpass -p<password> ssh <arguments>
sshpass -ptest1324 ssh user#192.168.1.200 ls -l /tmp
The above command can be easily integrated with a Bash script.
Note: Please read the Security Considerations section in man sshpass for a full understanding of the security implications.
Add the 'interact' Expect command just before your EOD:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
expect "password"
send -- "$PWD\r"
interact
EOD
echo "you're out"
This should let you interact with the remote machine until you log out. Then you'll be back in Bash.
After looking for an answer for the question for months, I finally find a really best solution: writing a simple script.
#!/usr/bin/expect
set timeout 20
set cmd [lrange $argv 1 end]
set password [lindex $argv 0]
eval spawn $cmd
expect "assword:" # matches both 'Password' and 'password'
send -- "$password\r"; # -- for passwords starting with -, see https://stackoverflow.com/a/21280372/4575793
interact
Put it to /usr/bin/exp, then you can use:
exp <password> ssh <anything>
exp <password> scp <anysrc> <anydst>
Done!
A simple Expect script:
File Remotelogin.exp
#!/usr/bin/expect
set user [lindex $argv 1]
set ip [lindex $argv 0]
set password [lindex $argv 2]
spawn ssh $user#$ip
expect "password"
send "$password\r"
interact
Example:
./Remotelogin.exp <ip> <user name> <password>
Also make sure to use
send -- "$PWD\r"
instead, as passwords starting with a dash (-) will fail otherwise.
The above won't interpret a string starting with a dash as an option to the send command.
Use the helper tool fd0ssh (from hxtools, source for ubuntu, source for openSUSE, not pmt). It works without having to expect a particular prompt from the ssh program.
It is also "much safer than passing the password on the command line as sshpass does" ( - comment by Charles Duffy).
Another way that I found useful to use a small Expect script from a Bash script is as follows.
...
Bash script start
Bash commands
...
expect - <<EOF
spawn your-command-here
expect "some-pattern"
send "some-command"
...
...
EOF
...
More Bash commands
...
This works because ...If the string "-" is supplied as a filename, standard input is read instead...
sshpass is broken if you try to use it inside a Sublime Text build target, inside a Makefile. Instead of sshpass, you can use passh
With sshpass you would do:
sshpass -p pa$$word ssh user#host
With passh you would do:
passh -p pa$$word ssh user#host
Note: Do not forget to use -o StrictHostKeyChecking=no. Otherwise, the connection will hang on the first time you use it. For example:
passh -p pa$$word ssh -o StrictHostKeyChecking=no user#host
References:
Send command for password doesn't work using Expect script in SSH connection
How can I disable strict host key checking in ssh?
How to disable SSH host key checking
scp without known_hosts check
pam_mount and sshfs with password authentication

How can I script a remote connection that is elevated via su?

I have scripts and programs which run commands on a remote computer, and I need some of the commands to be elevated, I.e. su. Since this is scripted, I cannot rely on a user to enter the password; it needs to be passed to su by the script.
I have tried a bunch of things, including echoing the password to su like so:
ssh user#host "echo password | su -c myCommand"
and
echo password | ssh user#host su -c myCommand
and in programming languages like C# creating a new process and reading from its stdout and writing the password to its stdin.
I've tried some alternate things others have mentioned online, but they generally involve commands that I do not have on some of the machines. Some of them have no expect or sudo or other alternatives others have mentioned.
How can I do this without installing any other tools?
The su tag says general su support is off topic, but this is not about su support or usage, it's specifically about a programming problem which requires elevated su use, so hopefully it's not taken as off topic. "Linux & Unix" and "Super User" users may be interested in this question too, but it really applies more to programming.
TL;DR: Use echo pass | ssh -t -t user#host 'su -c "command"'
This one gave me headaches and took a while to figure out. There are hints about the answer elsewhere, but I have not seen the answer stated explicitly anywhere, so it took a bit of playing around with to get right.
Using ssh
echo <password> | ssh -t -t <user>#<host> 'su -c "<command>"'
The quotes seem to be necessary. If I omitted one or the other pair of quotes I got incorrect results for some commands. For example, <…> ssh -t -t me#mypc su -c "ls -l /root/something_secure" would give me the ls contents of me's home directory, ignoring the -l and the /root/something_secure. So I had to have the quotes set up like that.
The -t is where the magic is at for our need, and yes you should put 2 of them. Doing echo pass | su -c command can fail because su doesn't take a password from standard input. Doing echo pass | ssh <…> su -c command seems like a good idea at first, because su needs to get input from ssh, and ssh gets it from standard input, but really su gets it from a "tty", not from standard input. The -t tells ssh to fake it by using a pseudo-tty which sends standard input to this fake tty. Sometimes ssh will complain and not want to allocate the pseudo-tty for you, so doing the -t twice tells ssh to shut up and do it anyway.
-t' Force pseudo-tty allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful,
e.g. when implementing menu services. Multiple -t options force tty
allocation, even if ssh has no local tty.
From man ssh
Example:
echo MySecretPass | ssh -t -t me#mydevice 'su -c "chown me /some/file"'
Ssh does not have a password parameter. If you have not set up a password-less secure login, you should do so. Read up here. Otherwise, this answer will supply the password to su for you, but you would still have to supply the password to ssh for the initial ssh login.
Using plink (Putty command line ssh-like tool for Windows)
echo <password> | plink -t -t -pw <password> <user>#<host> "su -c '<command>'"
Again, the quotes seemed to be necessary. If I omitted them, the results were incorrect, similar as with using ssh.
Example:
echo RootPass | plink -t -t -pw MePass me#mydevice "su -c 'echo Rooty!> /root/rootiness'"
Reasoning is similar to the ssh section above.
Your favorite programming language
If your favorite programming language supports the ability to execute a shell command and control the standard input/output for it, then instead of piping an echo you can instead run the ssh or plink command above (minus the piped echo), then put the password into standard input.
So for C#
Process process = new Process();
// If I remember correctly, I think UseShellExecute needs to be false to redirect std in/out
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
// You can use either ssh or plink; I'll use plink here
process.StartInfo.FileName = "<path to plink>\\plink.exe";
process.StartInfo.Arguments = "-t -t -l " + username + " -pw " + password + " " + hostname + " 'su -c \"MyCommand\"'";
process.StartInfo.CreateNoWindow = true;
process.OutputDataReceived += new DataReceivedEventHandler(FunctionCallbackYouMake);
process.ErrorDataReceived += new DataReceivedEventHandler(AnotherFunctionYouMake);
// I'm not actually sure if this next line is necessary, but I've been using it because of an example I used
process.SynchronizingObject = null;
process.Start();
And then read the data coming in through the ssh (plink) connection by using the function callback you make and provide to OutputDataReceived. It has the form void f(object sending_process, DataReceivedEventArgs event)
You can write data to process.StandardInput, including the su password, such as
process.StandardInput.Write(password);
process.StandardInput.Flush();
I've done something similar in Java as well, so I know you can do pretty much the same thing there, but it's been so long I don't recall all the details, but it also involves a process object and setting up std in/out handlers. If I have time later I might include some of those details.
No matter what language or method you use, some programs may block if they get too much stuff in their output streams or error streams, so make sure that you register for both standard output and standard error even if you don't care about them, even if all you do is read their data and discard it.
But don't hard-code the password!
Since this is for programming, there is generally no need to hard-code passwords into scripts. You could if you absolutely had to, but preferably use variables place of the username, password, and host name so that you are more secure and your tool is more flexible.
For example, I have a line in Windows cmd which does the following
echo %password% | plink -t -t -pw %password% %username%#%ip% "su -c '<what I want to do goes here>'"
And the username, password, and ip are supplied as arguments to the cmd file. Also note the C# example above, they are variables.

shell scripting "expect"ing various responses from ssh

So I have a shell script which sshs into a machine, then using expect passes a password via expect and then runs a matlab script. The problem is I would like to do this for a whole range of machines on the network and if it's one that's I've not connected before asking me for a password it tells me that it can't authenticate the machine as asks me if I want to connect and awaits a yes\no response.
I want my shell script to be able to handle situations where it just asks for the password and where it also manages to deal with the yes\no. I don't know how to go about doing this with "expect" as when it doesn't receive what it expects it just hangs.
Also for some reason my additional code to handle the yes no response doesn't work when testing it with machines I know I haven't logged in with. This is format with which they reply.
"The authenticity of host 'seven (128.40.41.89)' can't be established.
RSA key fingerprint is d6:9d:d0:61:5f:bb:36:9a:74:2c:f6:b9:a7:79:03:98.
Are you sure you want to continue connecting (yes/no)?"
And this is my code which for some reason doesn't work with my addition, but works fine with just the "assword:" bit which I found on stack exchange. I can see the terminal and it asks the yes\no question, but then my script doesn't respond. I'm guessing I'm somehow using the expect wrong, but I have no idea how.
#!/bin/sh
ssh seven
expect "(yes\no)? "
send "yes\r"
expect "assword:"
send "my password goes here\r"
matlab -nodisplay -nojvm -nosplash -nodesktop -r \
"try, run('\path\matlab_script.m'), catch me, fprintf('%s / %s \n',me.identifier,me.message), end, exit"
One possible workaround is running ssh with StrictHostKeyChecking set to no:
ssh seven -o StrictHostKeyChecking=no
This will autimatically add unknown host keys to the list of known hosts.

Provide password to ssh command inside bash script, Without the usage of public keys and Expect

I want to use SSH inside a script, but this script is not going to be executed on my machine.
In my implementation there are two limitations.
I can not work outside shell's standards,therefore i can not use expect because i do not know if it will be available on this machine.
I can not expect that this machine will have public keys for the SSH.
What are the possible options-solutions ?
How can i provide ssh with the requested password with an automated and secure way without adding extra dependencies?
Will it be possible to provide the password inside the script?
Thank you all in advance :)
Install sshpass, then launch the command:
sshpass -p "yourpassword" ssh -o StrictHostKeyChecking=no yourusername#hostname
For security reasons you must avoid providing password on a command line otherwise anyone running ps command can see your password. Better to use sshpass utility like this:
#!/bin/bash
export SSHPASS="your-password"
sshpass -e ssh -oBatchMode=no sshUser#remoteHost
You might be interested in How to run the sftp command with a password from Bash script?
First of all: Don't put secrets in clear text unless you know why it is a safe thing to do (i.e. you have assessed what damage can be done by an attacker knowing the secret).
If you are ok with putting secrets in your script, you could ship an ssh key with it and execute in an ssh-agent shell:
#!/usr/bin/env ssh-agent /usr/bin/env bash
KEYFILE=`mktemp`
cat << EOF > ${KEYFILE}
-----BEGIN RSA PRIVATE KEY-----
[.......]
EOF
ssh-add ${KEYFILE}
# do your ssh things here...
# Remove the key file.
rm -f ${KEYFILE}
A benefit of using ssh keys is that you can easily use forced commands to limit what the keyholder can do on the server.
A more secure approach would be to let the script run ssh-keygen -f ~/.ssh/my-script-key to create a private key specific for this purpose, but then you would also need a routine for adding the public key to the server.
AFAIK there is no possibility beside from using keys or expect if you are using the command line version ssh. But there are library bindings for the most programming languages like C, python, php, ... . You could write a program in such a language. This way it would be possible to pass the password automatically. But note this is of course a security problem as the password will be stored in plain text in that program
I completely agree with everybody who says this is almost certainly a terrible idea. It is extremely likely to allow others to attack your computers.
USE AT YOUR OWN RISK AFTER EVALUATING THE SECURITY HAZARDS
Answer
Make a program /path/to/saypass which outputs the password, such as
#!/bin/sh
echo 'secret'
Make it executable with
chmod +x /path/to/saypass
Then this is the main command:
SSH_ASKPASS="/path/to/saypass" DISPLAY=anything setsid ssh username#hostname [farcommand]
This
sets the two environment variables SSH_ASKPASS and DISPLAY
and then runs setsid
which then runs ssh without a controlling terminal
which connects to the far hostname
... runs saypass locally to get the password
... tells it to the far server
... and assuming it's correct
which then runs farcommand (if given), or an interactive shell.
I normally test with date or hostname for the optional farcommand.
There are lots of places for this to go wrong.
Explanation
The trick to this is that standard Linux command line ssh has a couple of environment variables you can use to choose a program which gets executed to supply the password.
ssh(1) manual page says:
SSH_ASKPASS If ssh needs a passphrase, it will read the passphrase from the current terminal if it was run from a terminal. If ssh does not have a terminal associated with it but DISPLAY and SSH_ASKPASS are set, it will execute the program specified by SSH_ASKPASS and open an X11 window to read the passphrase.
So: you need a program (shell script or any other kind) which will output the password. Then you need to convince ssh to use it:
With SSH_ASKPASS set to /path/to/saypass
With DISPLAY set to something silly
With no controlling terminal (this is what setsid does)
Which you put together in the following sh command:
SSH_ASKPASS="/path/to/saypass" DISPLAY=anything setsid ssh username#hostname [command]
ssh will execute
/path/to/saypass "username#hostname's password:"
Fingerprint check
If the fingerprint is needed, where you'd normally see the message
The authenticity of host '*hostname* (*ipaddress*)' can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no)?
Then ssh will run your command like this:
/path/to/saypass "Please type 'yes' or 'no':"
All-in-one script
The following is a single script for creating, using, and removing a saypass within the main script. Everyone will tell you do not put plaintext passwords in files and also never hardcode a password. They tell you this for good reason: it will cause you a lot of trouble. Use at your own risk.
#!/bin/sh
echo "#!/bin/sh\necho 'secret';rm -f /tmp/saypass.$$" > /tmp/saypass.$$
chmod 775 /tmp/saypass.$$
SSH_ASKPASS="/tmp/saypass.$$" DISPLAY=anything setsid ssh "$#"
SCP
This also works for scp, the copy program on top of ssh:
SSH_ASKPASS=/path/to/saypas DISPLAY=anything setsid scp username#hostname:/path/to/farfile .
Caveat
Really don't use this except in dire, dire, circumstances, such as where you have hundreds of computers and you can't install anything like ssh keys, sshpass even expect.
If you do use it, please don't tell anyone I told you how to do it. It really is terrible.
I don't know what the man page means about "open an X11 window", no such thing happens in my testing.
Tested on
OpenSSH_6.6.1p1 Ubuntu-2ubuntu2, OpenSSL 1.0.1f 6 Jan 2014 on Ubuntu 14.04.1 LTS,
OpenSSH_7.2p2 Ubuntu-4ubuntu2.1, OpenSSL 1.0.2g 1 Mar 2016 on Ubuntu 16.04.2 LTS
OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017 on Ubuntu 18.04.5 LTS

Resources