How to interact with ssh using subprocess module - python

I'm trying to spawn an ssh child process using subprocess.
I'm working on Python 2.7.6 on Windows 7
here is my code:
from subprocess import *
r=Popen("ssh sshserver#localhost", stdout=PIPE)
stdout, stderr=r.communicate()
print(stdout)
print(stderr)
The outputs:
None
stdout should contain:
sshserver#localhost's password:

Here's an example of working SSH code that handles the promt for yes/no on the certificate part and also when asked for a password.
#!/usr/bin/python
import pty, sys
from subprocess import Popen, PIPE, STDOUT
from time import sleep
from os import fork, waitpid, execv, read, write
class ssh():
def __init__(self, host, execute='echo "done" > /root/testing.txt', askpass=False, user='root', password=b'SuperSecurePassword'):
self.exec = execute
self.host = host
self.user = user
self.password = password
self.askpass = askpass
self.run()
def run(self):
command = [
'/usr/bin/ssh',
self.user+'#'+self.host,
'-o', 'NumberOfPasswordPrompts=1',
self.exec,
]
# PID = 0 for child, and the PID of the child for the parent
pid, child_fd = pty.fork()
if not pid: # Child process
# Replace child process with our SSH process
execv(command[0], command)
## if we havn't setup pub-key authentication
## we can loop for a password promt and "insert" the password.
while self.askpass:
try:
output = read(child_fd, 1024).strip()
except:
break
lower = output.lower()
# Write the password
if b'password:' in lower:
write(child_fd, self.password + b'\n')
break
elif b'are you sure you want to continue connecting' in lower:
# Adding key to known_hosts
write(child_fd, b'yes\n')
elif b'company privacy warning' in lower:
pass # This is an understood message
else:
print('Error:',output)
waitpid(pid, 0)
The reason (and correct me if i'm wrong here) for you not being able to read the stdin straight away is because SSH runs as a subprocess under a different process ID which you need to read/attach to.
Since you're using windows, pty will not work. there's two solutions that would work better and that's pexpect and as someone pointed out key-based authentication.
In order to achieve a key-based authentication you only need to do the following:
On your client, run: ssh-keygen
Copy your id_rsa.pub content (one line) into /home/user/.ssh/authorized_keys on the server.
And you're done.
If not, go with pexpect.
import pexpect
child = pexpect.spawn('ssh user#host.com')
child.expect('Password:')
child.sendline('SuperSecretPassword')

Related

ssh via python subprocess.Popen: terminate if password requested

I'm using a python script to manage ssh fingerprint problems after a workstation(s) is reimaged.
I attempt to connect with ssh, and if I get a any warnings I deal with them.
However, if there are no errors, then I am asked for a password to connect. At this point I want to terminate the process. However, the script hangs on the password request.
Here's the method:
def ssh_fingerprint_changed(node):
"""
Checks if a node's ssh fingerprint has changed or an old key is found, which can occur when a node is reimaged.
It does this by attempting to connect via ssh and inspecting stdout for an error message.
:param node: the ip or hostname of the node
:return: True if the node's fingerprint doesn't match the client's records. Else False.
"""
changed = False
cmd = ["ssh", "-q", ADMIN_USER + "#" + node, "exit"]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
print("Checking for fingerprint changes")
for line in proc.stdout: # loop on lines
print("in for loop") # NEVER REACHES HERE IF NO ERRORS, WAITING FOR PASSWORD
if b"Offending key" in line:
print("Offending key found.")
proc.stdin.write(b"no\n") # don't connect
changed = True
elif b"REMOTE HOST IDENTIFICATION HAS CHANGED!" in line:
print("REMOTE HOST IDENTIFICATION HAS CHANGED!")
changed = True
print(changed) # NEVER REACHES HERE IF NO ERRORS, WAITING FOR PASSWORD
if not changed: # then everything's good, but it will be waiting for a password to connect
print("Good to go, terminating ssh test.")
rc = proc.terminate()
else:
rc = proc.wait()
return changed
If I run this from the terminal ./my_python_script.py, I have the problems. Oddly, if I run in PyCharm, it doesn't hang on the password request and terminates shh, continuing with the script as expected.
The easy answer is simply to tell ssh that you don't want to support password authentication at all; you'll still get the messages you want if the host key is changed, but you won't ever have the process hanging waiting for a password to be entered.
cmd = ['ssh',
'-o', 'PasswordAuthentication no', ### <-- THIS LINE HERE
'-o', 'StrictHostKeyChecking yes', ### also, never modify known_hosts
'-q',
'%s#%s' % (ADMIN_USER, + node),
'exit']
If you did not want to process other prompts, I would suggest setting stdin=subprocess.DEVNULL (in Python 3) or passing the -n argument to ssh to prevent stdin from being passed to the process at all.

Python users creation script

For training reasons im trying to write a python script that creates and sets user accounts and passwords :
import subprocess
from subprocess import Popen
users = ["user1"]
default_passwd = 'password'
for user in users:
p1 = subprocess.Popen(["useradd" ,user])
proc = Popen(['echo' ,default_passwd , '|' , 'passwd', user, '--stdin'])
proc.communicate()
While the user is created , the passwd process fails.
Any help would be appreciated.
Why don't you pass password along with command useradd?
so that it creates a user with password without prompting!!
import os
import crypt
password ="your-password"
crypted_password = crypt.crypt(password,"22")
os.system("useradd -p "+ crypted_password +" student")
#Naren answer is neat and much more readable; but for the purpose of answering your subprocess question, it should be like this
import subprocess
users = ["user1"]
default_passwd = 'password'
for user in users:
p1 = subprocess.Popen(['useradd', user, '-p'])
proc = subprocess.Popen(['echo', default_passwd], stdout=subprocess.PIPE)
p1.communicate(proc.stdout)
proc.communicate()
p1 opens a subshell with useradd user1 command executed and waits for input
proc then executes echo default_passwd, but instead of sending output to sys.stdout, it pipes it to subprocess.PIPE
The communicate on p1 sends the output of proc.stdout to the stdin of p1 and waits for it completion
The last commands wait for proc process to finish and exit

start python program in the background that can prompt the user for password

Basically I wanted to start a daemon in the background that will still prompt the user in the console for a password. I created this with pexpect, but when this program ends it kills the daemon since it is a child process. So obviously pexpect isn't going to work for this. Does any body know of a way to do this?
#!/usr/bin/env python
import pexpect
import getpass
child = pexpect.spawn('python daemon.py &')
child.expect_exact('Password:')
passwd = getpass.getpass()
child.sendline(passwd)
index = child.expect_exact('Started Successfully')
print index
pexpect has a method close(self, force=True) which closes its connection to the child process.
According to the documentation, the child pocess is terminated if force=True so
child.close(force=False)
should disconnect, but leave the application running.
Dany suggested, "Had you considered using os.fork() and then run the daemon module?"
Your answer was "yea, but I didn't know how to pass the password to the daemon. Have to keep the password safe i.e. not storing on the hdd and not showing it on the screen."
But you don't have to do anything to pass the password to the daemon. After fork, it's still accessible. For example:
passwd = getpass.getpass()
pid = os.fork()
if pid:
print 'Parent thinks passwd is', passwd
else:
print 'Child thinks passwd is', passwd
So, read the password in the parent, then fork the daemon; it already has the password, so it doesn't need a tty for anything. Problem solved.

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

Use subprocess to send a password

I'm attempting to use the python subprocess module to log in to a secure ftp site and then grab a file. However I keep getting hung up on just trying to send the password when it is requested. I so far have the following code:
from subprocess import Popen, PIPE
proc = Popen(['sftp','user#server', 'stop'], stdin=PIPE)
proc.communicate('password')
This still stops at the password prompt. If I enter the password manually it then goes to the ftp site and then enters the password on the command line. I've seen people suggest using pexpect but long story short I need a standard library solution. Is there anyway with subprocess and/or any other stdlib? What am I forgetting above?
Try
proc.stdin.write('yourPassword\n')
proc.stdin.flush()
That should work.
What you describe sounds like stdin=None where the child process inherits the stdin of the parent (your Python program).
Perhaps you should use an expect-like library instead?
For instance Pexpect (example). There are other, similar python libraries as well.
from subprocess import Popen, PIPE
proc = Popen(['sftp','user#server', 'stop'], stdin=PIPE)
proc.communicate(input='password')
Try with input=‘password’ in communicate, that worked for me.
Use Paramiko for SFTP. For anything else, this works:
import subprocess
args = ['command-that-requires-password', '-user', 'me']
proc = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.stdin.write('mypassword\n')
proc.stdin.flush()
stdout, stderr = proc.communicate()
print stdout
print stderr
For some reason, I couldn't get any of the standard library answers here to work for me - getting very strange problems with all of them. Someone else here: unable to provide password to a process with subprocess [python] had the same problem, and concluded that ultimately you just have to go with pexpect to be able to send a password.
I wanted to add my final code here to just save the time of anyone having a similar problem, since I wasted so much time on this (Python 3, 2020):
ssh_password = getpass("user's password: ")
ssh_password = (ssh_password + "\n").encode()
scp_command = 'scp xx.xx.xx.xx:/path/to/file.log /local/save/path/'
child = pexpect.spawn(scp_command)
# make output visible for debugging / progress watching
child.logfile = sys.stdout.buffer
i = child.expect([pexpect.TIMEOUT, "password:"])
if i == 0:
print("Got unexpected output: {} {}".format(child.before, child.after))
return
else:
child.sendline(ssh_password)
child.read()
The above code runs an SCP command to pull a file from the remote server onto your local computer - alter the server IP and paths as necessary.
Key things to remember:
Have to have a pexpect.TIMEOUT in the child.expect call
Have to encode to bytes whatever strings you pass in, and have to use the default encode
Write pexpect output to sys.stdout.buffer so that you can actually see what is going on
Have to have a child.read() at the end
I would recommend scrapping the subprocess approach and using the paramiko package for sftp access.
This same problem plagued me for a week. I had to submit a password from user input through subprocess securely because I was trying to avoid introducing a command injection vulnerability. Here is how I solved the problem with a little help from a colleague.
import subprocess
command = ['command', 'option1', '--password']
subprocess.Popen(command, stdin=subprocess.PIPE).wait(timeout=60)
The .wait(timeout=int) was the most important component because it allows the user to feed input to stdin. Otherwise, the timeout is defaulted to 0 and leaves the user no time to enter input, which consequently results in a None or null string. Took me FOREVER to figure this out.
For repeat use-cases where you know you'll have to do this multiple times, you can override the popen function and use it as a private method which I was told by the same programmer is best practice if you anticipate someone else will be interested in maintaining the code later on and you don't want them to mess with it.
def _popen(cmd):
proc_h = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc_h.wait(timeout=60)
return proc_h.poll() == os.EX_OK
It is important to remove stdout=subprocess.PIPE if the user is going to be prompted for input. Otherwise, the process appears to hang for 60 seconds, and the user doesn't get a prompt, nor do they realize they are expected to give a password. The stdout will naturally go to the shell window and allow the user to pass input to popen().
Also, just to explain why you return proc_h.poll() == os.EX_OK, is that it returns 0 if the command succeeded. This is just c-style best-practice for when you want to return system error codes in the event the function fails, while accounting for the fact that return 0 will be treated as "false" by the interpreter.
This is a pure Python solution using expect - not pexpect.
If on Ubuntu you first need to install expect with:
sudo apt install expect
Python 3.6 or later:
def sftp_rename(from_name, to_name):
sftp_password = 'abigsecret'
sftp_username = 'foo'
destination_hostname = 'some_hostname'
from_name = 'oldfilename.txt'
to_name = 'newfilename.txt'
commands = f"""
spawn sftp -o "StrictHostKeyChecking no"
{sftp_username}#{destination_hostname}
expect "password:"
send "{sftp_password}\r"
expect "sftp>"
send "rename {from_name} {to_name}\r"
expect "sftp>"
send "bye\r"
expect "#"
"""
sp = subprocess.Popen(['expect', '-c', commands], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
since what you want is just grab a file, I am trying to use "sub process" but it is not works for me. So now I am using paramiko, here is my code:
here is one tutorial I found online
Transfer a file from local server to remote server and vice versa using paramiko of python
"https://www.youtube.com/watch?v=dtvV2xKaVjw"
underneath is my code for transfering all the files in one folder from Linux to windows
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='11.11.11.1111', username='root', password='********', port=22)
sftp_client = ssh.open_sftp()
source_folder = '/var/ftp/file_pass'
local_folder = 'C:/temp/file_pass'
inbound_files = sftp_client.listdir(source_folder)
print(inbound_files)
for ele in inbound_files:
try:
path_from = source_folder + '/' + ele
path_to = local_folder + '/'+ ele
sftp_client.get(path_from, path_to)
except:
print(ele)
sftp_client.close()
ssh.close()
Python have a built in library called ftplib, that can be used for ftp processes without any hassle. (Assuming the remote server have a ftp service running)
from ftplib import FTP
ftp = FTP('ftp.us.debian.org') # connect to host, default port
ftp.login() # user anonymous, passwd anonymous#
##'230 Login successful.'
ftp.cwd('debian') # change into "debian" directory
##'250 Directory successfully changed.'
ftp.retrlines('LIST')
Otherwise, You can use scp command, which is a command line tool. The problem with the password can be avoided creating password less user for remote host.
import os
os.system('scp remoteuser#remotehost:/remote/location/remotefile.txt /client/location/')
To create a passwordless user in linux systems,
Fallow below Steps. Fallow this SO answer.
> ssh-keyscan remotehost
> known_hosts ssh-keygen -t rsa # ENTER toevery field (One time)
> ssh-copy-id remoteuser#remotehost
The safest way to do this is to prompt for the password beforehand and then pipe it into the command. Prompting for the password will avoid having the password saved anywhere in your code. Here's an example:
from getpass import getpass
from subprocess import Popen, PIPE
password = getpass("Please enter your password: ")
proc = Popen("sftp user#server stop".split(), stdin=PIPE)
# Popen only accepts byte-arrays so you must encode the string
proc.communicate(password.encode())
import subprocess
args = ['command', 'arg1', 'arg2']
proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.stdin.write(b'password') ##The b prefix is necessary because it needs a byte type
proc.stdin.flush()
stdout, stderr = proc.communicate()
print(stdout)
print(stderr)
You just forgot the line return (aka user pressing Enter) in your password.
from subprocess import Popen, PIPE
proc = Popen(['sftp','user#server', 'stop'], stdin=PIPE)
proc.communicate('password\n'.encode())
Also .encode() because by default proc.communicate() accept bytes-like object.

Categories

Resources