pexpect: howto dump all content from child "as is"? - python

We have some strange setup on our "shared" server that will not remember my git password for certain situations. I tried hard to fix the real issue; but at some point I gave up and created this python script:
#!/usr/bin/env python3
"""
pass4worder: a simply python script that runs a custom command; and "expects" that command to ask for a password.
The script will send a custom password - until the command comes back with EOF.
"""
import getpass
import pexpect
import sys
def main():
if len(sys.argv) == 1:
print("pass4worder.py ERROR: at least one argument (the command to run) is required!")
sys.exit(1)
command = " ".join(sys.argv[1:])
print('Command to run: <{}>'.format(command))
password = getpass.getpass("Enter the password to send: ")
child = pexpect.spawn(command)
print(child.readline)
counter = 0
while True:
try:
expectAndSendPassword(child, password)
counter = logAndIncreaseCounter(counter)
except pexpect.EOF:
print("Received EOF - exiting now!")
print(child.before)
sys.exit(0)
def expectAndSendPassword(child, password):
child.expect("Password .*")
print(child.before)
child.sendline(password)
def logAndIncreaseCounter(counter):
print("Sent password ... count: {}".format(counter))
return counter + 1
main()
This solution does the job; but I am not happy about how those prints look like; example:
> pass4worder.py git pull
Command to run: <git pull>
Enter the password to send:
<bound method SpawnBase.readline of <pexpect.pty_spawn.spawn object at 0x7f6b0f5ed780>>
Received EOF - exiting now!
b'Already up-to-date.\r\n'
I would rather prefer something like:
Already up-to-date
Received EOF - exiting now!
In other words: I am looking for a way so that pexect simply prints everything "as is" to stdout ... while still doing its job.
Is that possible?
(any other hints regarding my script are welcome too)

child.readline is a function so I think you actually wanted print(child.readline() ).
Change print(child.before) to print(child.before.decode() ). bytes.decode() converts bytes (b'string') to str.

Related

How to Skip the login password prompt if not entered

How to Skip the login password prompt if not entered or password is wrong..
I have Below python fabric code.. which works fine but stucks with wrong passwords..
import sys
from fabric.api import *
env.skip_bad_hosts=True
env.command_timeout=60
env.user = 'test'
env.shell = "/bin/sh -c"
env.warn_only = True
env.password = 'mypass'
def read_hosts():
env.hosts = [line.strip() for line in sys.stdin.readlines()]
def cyberark():
with settings(warn_only=True):
output=sudo("/monitor.sh",shell=False)
When i run it, it stands there only until i break it manually...
[pc-karn] Executing task 'cyberark'
[pc-karn] sudo: /monitor.sh
[pc-karn] Login password for 'test':
Is there anyway to set the env where if the password given wrong with 2 consecutive sequence and it will go to the next host in line.
You can use this parameters -
with settings(abort_on_prompts=True):
This parameter terminate the program when prompt the user for input. You can read about it here.
I don't know if this solve your problem, as far as i know Fabric what you are looking for is not possible, but at least you know your program always terminate, and can fix the passwords issue.
When I want to dodge a password prompt but continue my script on fail, I use abort_on_prompts=True and catch the SystemExit exception that is raised by abort_on_prompt.
try :
with settings(abort_on_prompts=True):
output=sudo("/monitor.sh",shell=False)
except SystemExit as se :
print "What I want to do when it fails"

how to check if ssh command ran through pexpect spawn command ran successfully or not .

I am writing a simple python script to test connectivity to multiple linux hosts running centos on them. For this I am thinking of using pexpect module and ssh . pexpect will send the password stored in a variable when prompted for. The problem is that how to check if the password was accepted successfully or not. Is there a way to do so. The code is given below. Please add you expert comments.
This example has code written to ssh to localhost only.So a for loop is not yet included.
import pexpect
from getpass import getpass
import sys
# Defining Global Variables
log_file = '/tmp/AccessValidation'
# Getting password from user and storing in a variable
passs = getpass("Please enter your password: ")
# Connect to server using ssh connection and run a command to verify access.
child = pexpect.spawn("ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no 127.0.0.1 'uptime'")
child.expect('Password:')
child.sendline(passs)
One of the things you could do is to have an expect for the command prompt. So if your prompt is: someuser#host$ you could do child.expect(".*\$").
Another thing you could do is to have multiple expects and then check those against the ones you want. For example:
i = child.expect([".*\$", "Password Incorrect"])
if i != 0:
print "Incorrect credentials"
else:
print "Command executed correctly"
You can view some examples within Pexpect's readthedocs page. Pexpect also has the pxssh class that is specialized to handle ssh connections and may be of some use also. I personally haven't used it but the syntax seems the same, just with more options relating to ssh.
Thanks Cory Shay for helping me figure out the correct way to solve my problem . Below is the code I have written and this works.
import pexpect
from getpass import getpass
import sys
# Defining Global Variables
log_file = '/tmp/AccessValidation'
# Getting password from user and storing in a variable
passs = getpass("Please enter your password: ")
# Connect to server using ssh connection and run a command to verify access.
child = pexpect.spawn("ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no 127.0.0.1 'hostname' ")
child.expect('Password:')
child.sendline(passs)
result = child.expect(['Password:', pexpect.EOF])
if result == 0:
print "Access Denied"
elif result == 1:
print "Access Granted"

Python paramiko module using multiple commands

I have a class that creates the connection. I can connect and execute 1 command before the channel is closed. On another system i have i can execute multiple commands and the channel does not close. Obviously its a config issue with the systems i am trying to connect to.
class connect:
newconnection = ''
def __init__(self,username,password):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect('somehost', username=username,password=password,port=2222,timeout=5)
except:
print "Count not connect"
sys.exit()
self.newconnection = ssh
def con(self):
return self.newconnection
Then i use 'ls' command just to print some output
sshconnection = connect('someuser','somepassword').con()
stdin, stdout, stderr = sshconnection.exec_command("ls -lsa")
print stdout.readlines()
print stdout
stdin, stdout, stderr = sshconnection.exec_command("ls -lsa")
print stdout.readlines()
print stdout
sshconnection.close()
sys.exit()
After the first exec_command runs it prints the expected output of the dir list. When i print stdout after the first exec_command it looks like the channel is closed
<paramiko.ChannelFile from <paramiko.Channel 1 (closed) -> <paramiko.Transport at 0x2400f10L (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>>>
Like i said on another system i am able to keep running commands and the connection doesn't close. Is there a way i can keep this open? or a better way i can see the reason why it closes?
edit: So it looks like you can only run 1 command per SSHClient.exec_command... so i decided to get_transport().open_session() and then run a command. The first one always works. The second one always fails and the scripts just hangs
With just paramiko after the exec_command executes the channel is closed and the ssh returns an auth prompt.
Seems its not possible with just paramiko, try fabric or another tool.
** fabric did not work out too.
Please see the following referece as it provides a way to do this in Paramiko:
How do you execute multiple commands in a single session in Paramiko? (Python)
it's possible with netmiko (tested on windows).
this example is written for connecting to cisco devices but the principle is adaptable for others as well.
import netmiko
from netmiko import ConnectHandler
import json
def connect_enable_silent(ip_address,ios_command):
with open ("credentials.txt") as line:
line_1 = json.load(line)
for k,v in line_1.items():
router=(k,v)
try:
ssh = ConnectHandler(**router[1],device_type="cisco_ios",ip=ip_address)
ssh.enable()
except netmiko.ssh_exception.NetMikoAuthenticationException:
#incorrect credentials
continue
except netmiko.ssh_exception.NetMikoTimeoutException:
#oddly enough if it can log in but not able to authenticate to enable mode the ssh.enable() command does not give an authentication error
#but a time-out error instead
try:
ssh = ConnectHandler(username = router[1]['username'],password = router[1]['password'],device_type="cisco_ios", ip=ip_address)
except netmiko.ssh_exception.NetMikoTimeoutException:
# connection timed out (ssh not enabled on device, try telnet)
continue
except Exception:
continue
else:
output = ssh.send_command(ios_command)
ssh.disconnect()
if "at '^' marker." in output:
#trying to run a command that requires enble mode but not authenticated to enable mode
continue
return output
except Exception:
continue
else:
output = ssh.send_command(ios_command)
ssh.disconnect()
return output
output = connect_enable_silent(ip_address,ios_command)
for line in output.split('\n'):
print(line)
Credentials text is meant to store different credentials in case you are planning to call this function to access multiple devices and not all of them using the same credentials. It is in the format:
{"credentials_1":{"username":"username_1","password":"password_1","secret":"secret_1"},
"credentials_2":{"username":"username_2","password":"password_2","secret":"secret_2"},
"credentials_3": {"username": "username_3", "password": "password_3"}
}
The exceptions can be changed to do different things, in my case i just needed it to not return an error and continue trying the next set, which is why most exceptions are silenced.

How to redirect output to client

I'm a relatively new programmer in Python, and I have created this XMLRPC Server function as follows:
def shell(self, command, username):
if username in loggedIn:
return os.system(command)
else:
string = time.asctime() , " not logged in"
string = "".join(string)
return string
For the client side, I have written
command = raw_input ("$ ")
if command == "exit":
exit()
else:
server.shell(command, username)
However, when I run the command in the client program, the output would be in the server window and not the client window, something like this:
#client side
$ ls
#server side
#some results
localhost - - [14/Feb/2013 14:26:25] "POST /RPC2 HTTP/1.0" 200 -
And the cd command is also broken (i.e. I couldn't change to other directories even when the command is issued). Is there any way of doing so, and if so, how?
os.system doesn't return output to the command line, only the return code. If you want to capture stdout/stderr, you have to use subprocess.Popen. You can then also provide a working directory to execute your command in (using the cwd argument). As far as I know, cd might work in the shell, but won't change the working directory for the python instance. So the next call will not run in the directory you changed to. Using Popen with cwd should work.

SCP a tar file using pexpect

I am using ssh to log into a camera, scp a tarball over to it and extract files from the tarbal and then run the script. I am having problems with Pexpect, though. Pexpect times out when the tarball is being copied over. It seem's not to wait until it is done. And then it start's doing the same thing with the untar command, The code I have is below:
ssh_newkey = 'Are you sure you want to continue connecting'
copy = pexpect.spawn('ssh service#10.10.10.10')
i=copy.expect([ssh_newkey,'password:',pexpect.EOF])
if i==0:
copy.sendline('yes')
i=copy.expect([ssh_newkey,'password:',pexpect.EOF])
if i==1:
copy.sendline("service")
print 'Password Accepted'
copy.expect('service#user:')
copy.sendline('su - root')
i=copy.expect('Password:')
copy.sendline('root')
i=copy.expect('#')
copy.sendline('cd /tmp')
i=copy.expect("#")
copy.sendline('scp user#20.20.20.20:/home/user/tarfile.tar.gz .')
i=copy.expect([ssh_newkey,'password:',pexpect.EOF])
if i==0:
copy.sendline('yes')
i=copy.expect([ssh_newkey,'password:',pexpect.EOF])
else:
pass
copy.sendline('userpwd')
i=copy.expect('#')
copy.sendline('tar -zxvf tarfile.tar.gz bin/installer.sh')
i=copy.expect("#")
copy.sendline("setsid /tmp/bin/installer.sh /tmp/tarfile.tar.gz > /dev/null 2>&1 &")
elif i==2:
print "I either got key or connection timeout"
else:
pass
Can anyone help find a solution for this?
Thanks
I'm not sure if this is correct, but I'd try setting the timeout to None:
copy = pexpect.spawn('ssh service#10.10.10.10', timeout=None)
According to the source code, pexpect seems to not check the timeout when it's set to None.
Anyway, the reason I'm answering this even though I'm not sure whether it solves your problem is that I wanted to recommend using paramiko instead. I had good experience using it for communication over SSH in the past.
Is there a reason your using pexpect or even paramiko?
if you setup a public/private key then you can just use as a single example:
command = "scp user#20.20.20.20:/home/user/tarfile.tar.gz"
split_command = shlex.split(command)
subprocess.call(split_command)
Then as per the suggestion above use paramiko to send commands.
you can use the keyfile for that as well:
The following class method will give you a persistent session (although it is untested):
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
from paramiko import SSHClient, AutoAddPolicy, AuthenticationException, RSAKey
from subprocess import call
class CommsSuite(object):
def __init__(self):
self.ssh_client = SSHClient()
#--------------------------------------
def _session_send(command):
"""
Use to send commands over ssh in a 'interactive_session'
Verifies session is present
If the interactive_session is not present then print the failed command.
This may be updated to raise an error,
which would probably make more sense.
#param command: the command to send across as a string
::TODO:: consider raise exception here as failed
session will most likely be fatal.
"""
if self.session.send_ready():
self.session.send("%s\n" % command)
else:
print("Session cannot send %s" % command)
#--------------------------------------
def _get_persistent_session(_timeout = 5):
"""
connect to the host and establish an interactive session.
#param _timeout: sets the timout to prevent blocking.
"""
privatekeyfile = os.path.expanduser('~/.ssh/id_rsa')#this must point to your keyfile
private_key = RSAKey.from_private_key_file(privatekeyfile)
self.ssh_client.set_missing_host_key_policy(AutoAddPolicy())
self.ssh_client.connect(hostname,
username = <username>,
pkey = private_key,
timeout = _timeout)
self.transport = self.ssh_client.get_transport()
self.session = self.transport.open_session()
self.session.exec_command("bash -s")
_get_persistent_session()
# build a comma seperated list of commands here as a string "[a,b,c]"
commands = ["tar -zxvf tarfile.tar.gz bin/installer.sh", "setsid /tmp/bin/installer.sh /tmp/tarfile.tar.gz > /dev/null 2>&1"]
# then run the list of commands
if len(commands) > 0:
for command in commands:
_session_send(command)
self.session.close()#close the session when done
CommsSuite()

Categories

Resources