Paramiko expect sends a command before the expected output is received - python-3.x

I'm trying to configure routers with a python script and wanted to use paramiko expect to do the job. The function change_password is part of a class and contains the following paramiko-expect-lines (with self.ssh_client as an instance of paramiko.SSHClient):
with SSHClientInteraction(self.ssh_client, timeout=5, display=True) as self.interact:
# change password
prompt = '.*root#cb_park:~#.*'
self.interact.expect(prompt)
self.interact.send('passwd')
self.interact.expect(['.*Changing password for root.*', '.*New password:.*'])
self.interact.send('NewPassword')
self.interact.expect('.*Retype password:.*')
self.interact.send('NewPassword')
self.interact.expect(['.*passwd: password for root changed by root.*', prompt])
# modify firstlogin
self.interact.send('uci delete vuci.main.firstlogin')
self.interact.expect(prompt)
self.interact.send('uci commit')
self.interact.expect(prompt)
However, sometimes the send command is executed before the prompt appears. For example in the following scenario:
root#cb_park:~# passwd
Changing password for root
New password:
Retype password:
passwd: password for root changed by root
root#cb_park:~# uci delete vuci.main.firstlogin
uci commit
uci: Entry not found
root#cb_park:~# uci commit
'uci commit' is being executed, even though the expected prompt has not been given out. Sometimes this even happens on the second line and the new password is executed right after 'Changing password for root' even though 'New password:' has not been given out which leads to a timeout right after:
root#cb_park:~# passwd
Changing password for root
NewPassword
New password: EXCESS TIME RECV_READY TIMEOUT, did you expect() before a send()
Retype password: EXCESS TIME RECV_READY TIMEOUT, did you expect() before a send()
Passwords don't match
passwd: password for root is unchanged
root#cb_park:~#
I don't really know what the error could be or how to debug. Any idea?
paramiko 2.8.0
paramiko-expect 0.3.0
Python 3.8.10
Cheers Ian

in case anybody has a similar problem:
I took out the multiline expects and replaced it by only expecting the last line. I guess I missunderstood the docs and thought I'd have to expect both lines. New code as follows works without timeout.
with SSHClientInteraction(self.ssh_client, timeout=5, display=True) as self.interact:
# change password
prompt = '.*root#cb_park:~#.*'
self.interact.expect(prompt)
self.interact.send('passwd')
self.interact.expect('.*New password:.*') # instead of self.interact.expect(['.*Changing password for root.*', '.*New password:.*'])
self.interact.send('NewPassword')
self.interact.expect('.*Retype password:.*')
self.interact.send('NewPassword')
self.interact.expect(prompt) # instead of self.interact.expect(['.*passwd: password for root changed by root.*', prompt])
# modify firstlogin
self.interact.send('uci delete vuci.main.firstlogin')
self.interact.expect(prompt)
self.interact.send('uci commit')
self.interact.expect(prompt)
Ian

Related

Replace a single placeholder in a .conf file using bash

Hi everyone I am still learning bash at the moment but I am writing
a script for work that is being used to install Docker and CNTLM because we are running behind some proxies.
I have the installations working but am struggling to change 2 variable placeholders in the cntlm.conf file below.
cnlm.conf
#
# Cntlm Authentication Proxy Configuration
#
# NOTE: all values are parsed literally, do NOT escape spaces,
# do not quote. Use 0600 perms if you use plaintext password.
#
Username $MyUserName
Domain NTADMIN
Password $MyPassWord
# NOTE: Use plaintext password only at your own risk
# Use hashes instead. You can use a "cntlm -M" and "cntlm -H"
# command sequence to get the right config for your environment.
# See cntlm man page
# Example secure config shown below.
PassLM 93409221097752460192457192457999
PassNT 08992693829837928372938729387229
### Only for user 'testuser', domain 'corp-uk'
PassNTLMv2 02793865976487363876348763873467
# Specify the netbios hostname cntlm will send to the parent
# proxies. Normally the value is auto-guessed.
#
# Workstation netbios_hostname
I have been able to change the
PassLM
PassNT
PassNTLMv2
by using replace line with 'sed' but I am unable to change the $MyUserName and $MyPassWord from the variables being used in the bashscript.
Any ideas on how I can do this?
There are various alternatives:
To replace them using sed on a "template" and creating a new file, you can do it like this:
sed 's/\$MyPassword/MySuperPassword/' cnlm.conf > cnlm.new.conf
Now, if you will replace into the same file and you don't know the last value of the password, you can do:
sed -ri 's/^(Password *).*$/\1MySuperPassword/' cnlm.conf
If your new password is in a shell variable, then you can execute the last command like this:
newPasswd="abcde"
sed -ri "s/^(Password *).*$/\1${newPasswd}/" cnlm.conf
Finally, if you want to change the username and the password in the same command:
newUser="user123"
newPasswd="abcde"
sed -ri "s/^(Username *).*$/\1${newUser}/; s/^(Password *).*$/\1${newPasswd}/" cnlm.conf

plink in PowerShell to run commands on Linux machine (SuSE)

I am trying to automate the process of adding extra storage in a linux machine. I'm using plink in PowerShell installed on my Windows machine.
Below is the code:
$plinkpath = "C:\Users\mydrive\Modules\plink.exe"
if (Test-Path $plinkpath) {
Set-Alias plink $plinkpath
} else {
throw "Plink.exe is reqruied"
}
$passw = "linuxadmin$123"
$commands = #(
"sudo su;",
"pvcreate /dev/sde;",
"vgcreate test_vog /dev/sde",
"lvcreate -l 100%FREE -n test_lev test_vog;",
"mkfs.ext3 /dev/test_vog/test_lev;",
"mkdir /azurenew;",
"echo ""/dev/test_vog/test_lev /azurenew/ ext3 defaults 1 1"" >> /etc/fstab;",
"mount /azurenew/;"
)
Approach 1: Using .ppk file
plink -ssh -i "C:\Users\amurthy\Documents\WindowsPowerShell\Modules\sshprivate.ppk" linuxadmin#xx.xx.xx.xxx $commands
In the above situation PowerShell hangs and no response on the console. Not sure what's happening.
Approach 2: using direct log in
plink -P "22" -v "linuxadmin#xx.xx.xx.xxx" -pw "linuxadmin$123" $commands
Here, I get below response on console
Using username "linuxadmin".
Sent password
Password authentication failed
I do not understand why the passoword authentication failed though I am able to login using putty.exe with that password.
Can anyone please help me here to solve my above automation problem? If you have any better solution altogether really welcome.
The password login attempt fails because you defined the password in a double-quoted string. PowerShell tries to expand the (undefined) variable $123 in linuxadmin$123, so you're actually passing just linuxadmin as the password. You could use a single-quoted string to avoid this, but public key authentication (your first approach) is the better approach anyway, so I recommend sticking with that.
I'm not sure why your first approach causes the console to hang, though. If the key were password-protected you should be prompted for the password. There's a semicolon missing at the end of "vgcreate test_vog /dev/sde", but if that caused an issue I'd expect to see an error message.
Try running plink with the parameter -v (verbose messages) to get a better picture of what's going on.

Expect script - quotes needed with send string conflict with quotes required by expect

I'm trying to use expect to remotely login into a server a change a user password. The application requires that if the password you want to change contains special characters that it be quoted. Problem is, the expect send statement needs to be quoted as well, and when I try and combine the two, the script fails. If I remove the quotes from around the password and use no special characters it runs fine.
send "CHANGEUSERPASSWORD $username PASSWORD "etdgsfdh$"\r"
If I use the line above and run the script I get
extra characters after close-quote
while executing
"send "CHANGEUSERPASSWORD $username PASSWORD "e"
Also, it seems to dislike only the $ character as I can append a # to the end of the password without enclosing it in quotes and the script runs fine. How can I get this to work using a password that contains a $ which requires me to wrap it in quotes, which then apparently conflicts with expect quoting? Please note that the $ can be anywhere in the password, not just at the end as in my example.
Here is my full script for context:
#!/usr/bin/expect -f
set password [lindex $argv 0];
spawn telnet 192.168.100.100 106
expect "200 PWD Server ready"
send "USER user\r"
expect "300 please send the PASS"
send "PASS pass\r"
expect "200 login OK, proceed"
send "CHANGEUSERPASSWORD $username PASSWORD "etdgsfdh$"\r"
expect "200 OK"
send "quit\r"
interact
I have also tried to set the password as a variable using this line:
set password [lindex $argv 0];
And changing this line:
send "CHANGEACCOUNTPASSWORD user PASSWORD $password;" send "\r"
Then I run
./script password
and I get this error:
usage: send [args] string
while executing
"send "CHANGEUSERPASSWORD user PASSWORD $password;" send "\r""
What am I doing wrong?
You can simply escape the inner quotes
send "CHANGEUSERPASSWORD $username PASSWORD \"etdgsfdh$\"\r"
Note that a $ is only special when it's followed by a valid variable name (that may be in braces) -- see http://tcl.tk/man/tcl8.6/TclCmd/Tcl.htm#M12
I assume you're quoting the password because the remote system needs it to be quoted. To answer your question, you'd do:
lassign $argv username passwd # grab the command line arguments
...
send "CHANGEUSERPASSWORD $username PASSWORD \"$passwd\"\r"
If the remote system doesn't require the literal quotes, then simply
send "CHANGEUSERPASSWORD $username PASSWORD $passwd\r"
It is quite simple:
Create a password variable:
set password {etdgsfdh$}
Then send the password to the system:
send "CHANGEUSERPASSWORD $username PASSWORD ${password}\r"
This worked for me.
The only way to escape double quotes in Expect (in my example: "someurl.org" and "_blank") that worked for me was this:
send {UPDATE tablename SET value = ' visible link name ' WHERE id = 1;}
Escaping those four double quotes with a backslash in front of each of them did not work for me.

curl request does not accept a password containing "#" for a url with HTTP basic authentication

As we know that a curl request for a url that is protected by basic authentication can be made by using a format like this curl -v http://username: password#xyz.com . But i have observed that passing credentials like this only works if password does not have a "#" itself. For example If my request is curl -v http://user01:helloworld#xyz.com it will work . But if it is curl -v http://user01:hello#world#xyz.com , it fails . How can we make the first # to be understood as a password, as currently i think # is taken as a conventional delimiter before passing credentials, and therefore request gets confused as to what is the password . Any thoughts on this, or am i missing anything ?
Use -u which defaults to basic auth
-u, --user <user:password>
Specify the user name and password to use for server authentication. Overrides -n, --netrc and --netrc-
optional.
If you simply specify the user name, curl will prompt for a password.
The user name and passwords are split up on the first colon, which makes it impossible to use a colon in
the user name with this option. The password can, still.
Possibly even better would be to define your passwords in the netrc file and then avoid passwords on the command line altogether.

How to write a bash script to give another program response

I have a bash script that does several tasks, including python manage.py syncdb on a fresh database. This command asks for input, like the login info for the admin. Currently, I just type this into the command line every time. Is there a way I can automatically provide these replies as part of the bash script?
Thanks, I don't really know anything about bash.
I'm using Ubuntu 10.10.
I answered a similar question on SF, but this one is more general, and it's good to have on SO.
"You want to use expect for this. It's probably already on your machine [try which expect]. It's the standard tool for any kind of interactive command-line automation. It's a Tcl library, so you'll get some Tcl skills along the way for free. Beware; it's addictive."
I should mention in this case that there is also pexpect, which is a Python expect-alike.
#!/path/to/expect
spawn python manage.py syncdb
expect "login:*"
send -- "myuser\r"
expect "*ssword:*"
send -- "mypass\r"
interact
If the program in question cannot read the input from stdin such as:
echo "some input" | your_progam
then you'll need to look to something like expect and/or autoexepect
You can give defaults values to the variables. In line 4 and 5, if the variables RSRC and LOCAL aren't set, they are set to those default values. This way you can give the options to your script or use the default ones
#!/bin/bash
RSRC=$1
LOCAL=$2
: ${RSRC:="/var/www"}
: ${LOCAL:="/disk2/backup/remote/hot"}
rsync -avz -e 'ssh ' user#myserver:$RSRC $LOCAL
You can do it like this, given an example login.py script:
if __name__ == '__main__':
import sys
user = sys.stdin.readline().strip()
passwd = sys.stdin.readline().strip()
if user == 'root' and passwd == 'password':
print 'Login successful'
sys.exit(0)
sys.stderr.write('error: invalid username or password\n')
sys.exit(1)
good-credentials.txt
root
password
bad-credentials.txt
user
foo
Then you can do the login automatically using:
$cat good-credentials.txt | python login.py
Login successful
$cat bad-credentials.txt | python login.py
error: invalid username or password
The down-side of this approach is you're storing your password in plain text, which isn't great practice.

Resources