Use subprocess to send a password
Asked Answered
M

14

32

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?

Missi answered 5/3, 2010 at 15:16 Comment(1)
Duplicate of #2356891 ?Millymilman
A
6

Perhaps you should use an expect-like library instead?

For instance Pexpect (example). There are other, similar python libraries as well.

Annice answered 5/3, 2010 at 15:29 Comment(6)
+1 for Pexpect. That is the right solution for interacting with console processes.Fricandeau
Is there a stdlib expect module? I need to stick to stdlib.Missi
Pexpect is not stdlib. But it is pure python, so just put the pexpect.py file somewhere on your python path. For example next to your script file.Annice
I'd rather not have to keep track of yet another non-standard library for our product since it's to something not super important anyway. However the library is still good from what I understand. From what I've seen there is no stdlib solution to this problem but I'll give you credit for the good library suggestion. I'm probably just gonna have our code call a bash script if anyone stumbles upon this question later and was wondering what I did.Missi
-1 because the question specifically asked for a standard Python library solution, not pexpect. Just sayin'.Counterstatement
The example link noah.org/wiki/Pexpect#Overview is dead now; I guess the same thing as at pexpect.readthedocs.io/en/stable/examples.htmlAbstriction
G
12

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

Gandhi answered 5/3, 2010 at 15:22 Comment(4)
Hm... this still seems to be stopping and asking for the password.Missi
You do read stdout of the process in a second thread, don't you? Otherwise, odd things can happen. Also, check whether sftp allows a password from stdin by using echo password | sftp ... from the command line. It's possible that sftp always opens a shell.Gandhi
passwords are not written through standard input as far as I know. Is this code tested?Liber
@SebastianGabrielVinci Yes. Most programs put the console in RAW/NOECHO mode while they wait for a password, so you can't see it but they all read from stdin. Some code also checks that stdin is a tty. For those, you need to use a pseudo tty.Gandhi
L
7
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.

Lewallen answered 19/1, 2018 at 9:36 Comment(1)
doesn't work. Communicate is not for that at least for python 3.7+ doesn't work!Exciseman
A
6

Perhaps you should use an expect-like library instead?

For instance Pexpect (example). There are other, similar python libraries as well.

Annice answered 5/3, 2010 at 15:29 Comment(6)
+1 for Pexpect. That is the right solution for interacting with console processes.Fricandeau
Is there a stdlib expect module? I need to stick to stdlib.Missi
Pexpect is not stdlib. But it is pure python, so just put the pexpect.py file somewhere on your python path. For example next to your script file.Annice
I'd rather not have to keep track of yet another non-standard library for our product since it's to something not super important anyway. However the library is still good from what I understand. From what I've seen there is no stdlib solution to this problem but I'll give you credit for the good library suggestion. I'm probably just gonna have our code call a bash script if anyone stumbles upon this question later and was wondering what I did.Missi
-1 because the question specifically asked for a standard Python library solution, not pexpect. Just sayin'.Counterstatement
The example link noah.org/wiki/Pexpect#Overview is dead now; I guess the same thing as at pexpect.readthedocs.io/en/stable/examples.htmlAbstriction
I
5

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
Ivanna answered 12/12, 2016 at 4:47 Comment(1)
This is definitely faster than paramiko. I got a 2x speed up switching to this implementationLarimore
C
2

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
Chiton answered 21/9, 2020 at 2:34 Comment(0)
C
2
import subprocess

# Replace 'command' with your actual sudo command
command = ['sudo', '-S', 'your_command_here']

# Start the process
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

# Pass the password to sudo
sudo_password = 'your_sudo_password_here\n'
out, err = process.communicate(sudo_password)

# Print output
print(out)

In this example, we've added the -S option to the sudo command to tell it to read the password from standard input. We've also modified the subprocess.Popen() call to include the universal_newlines=True argument, which tells subprocess to treat the input and output streams as text.

Note that using -S to read the password from standard input can be a security risk, as it means that the password will be visible in the process list and in the command history. It's generally better to use an askpass helper or configure sudo to not require a password for the specific command you're running, if possible.

Cairo answered 18/4, 2023 at 8:20 Comment(0)
P
1

I would recommend scrapping the subprocess approach and using the paramiko package for sftp access.

Panchito answered 5/3, 2010 at 18:42 Comment(0)
B
0

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.

Boutis answered 26/3, 2018 at 17:47 Comment(0)
C
0

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)
Carmon answered 10/8, 2018 at 6:18 Comment(2)
You should read the 4.th comment on the question of this thread.Artel
@Artel what specifically are you drawing my attention to? The answer I have provided is closest to the requirements - pure Python.Carmon
S
0

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()
Singultus answered 7/12, 2020 at 18:59 Comment(0)
S
0

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
Supertonic answered 27/8, 2022 at 20:45 Comment(0)
L
-2

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())
Lloyd answered 21/5, 2019 at 17:41 Comment(0)
P
-2
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)
Pitchblende answered 22/7, 2019 at 16:11 Comment(1)
Can you provide some context to explain to the OP why this is a good or helpful answer (especially in relation to the other existing answers)?Terri
G
-3

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.

Guilty answered 29/10, 2019 at 13:47 Comment(1)
It might be working with sftp but not with git. It probably uses some other mechanism to get passwordMethanol

© 2022 - 2024 — McMap. All rights reserved.