paaword.py is a script where getpass() asked the user about the password and validates it. but i want to automate the whole process and used subprocess for it (main.py). And i am using python3.10
Problem:
problem is when i run the main.py in pycharm IDE it works normally (it automates the process). but when I run the script python3 main.py in ubuntu terminal it asked for the input.
I dont know why it behaves deifferent in in IDE and terminal?
password.py
import warnings
import getpass
import time
# Suppress warnings
warnings.filterwarnings("ignore", category=getpass.GetPassWarning)
for x in range(10):
print(f"curnt index {x}")
time.sleep(5)
password = getpass.getpass("Enter your password: ")
if password != "test":
print("wrong password")
else:
print("correct password")
main.py
import subprocess
# subprocess
proc = subprocess.Popen(["python", "password.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
password = "test"
input_data = f"{password}\n"
# read output from the subprocess in real-time
while True:
if proc.poll() is not None:
break
proc.stdin.write(input_data.encode())
proc.stdin.flush()
output = proc.stdout.readline().decode().strip()
if output:
print(output)
output in pycharm:
output in ubuntu terminal (20.04)
Judging by the screenshots, your OS is Linux.
In Linux, getpass() first tries to read directly from the process' controlling terminal (/dev/tty), or, if that fails, stdin using direct terminal I/O; and only if that fails, it falls back to regular I/O, displaying a warning.
Judging by the warnings in the IDE, the latter is exactly what happens in your first case.
Lib/getpass.py:
def unix_getpass(prompt='Password: ', stream=None):
<...>
try:
# Always try reading and writing directly on the tty first.
fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
tty = io.FileIO(fd, 'w+')
<...>
input = io.TextIOWrapper(tty)
<...>
except OSError:
# If that fails, see if stdin can be controlled.
<...>
try:
fd = sys.stdin.fileno()
except (AttributeError, ValueError):
fd = None
passwd = fallback_getpass(prompt, stream) # fallback_getpass is what displays the warnings
input = sys.stdin
<...>
if fd is not None:
try:
old = termios.tcgetattr(fd)
<...>
except termios.error:
<...>
passwd = fallback_getpass(prompt, stream)
<...>
return passwd
As you can see, getpass() is specifically designed to be interactive and resist intercepting its input. So if you need to provide a password automatically, use another way:
store it in a file readable only by you (e.g. SSH does that; you can provide that file as an argument and store other arguments there as well), or
use the system's keyring
and only fall back to getpass if the password was not provided that way and/or if you detect that the program is being run interactively (sys.stdin.isatty())
while it's also possible to provide the password on the command line -- in that case, you have to overwrite it in your process' stored command line to hide it from snooping. I couldn't find a way to do that in Python.
You can check Secure Password Handling in Python | Martin Heinz | Personal Website & Blog for a more detailed rundown of the above. (note: it suggests using envvars and load them from .env which would probably not apply to you. That's designed for .NET projects which due to the rigid structure of MS Visual Studio's build system, have had to rely on envvars for any variable values.)
Related
trying to use python to automate a usage of a command line application called slsk-cli
manually, the procedure is straight-forward - i open a command prompt window and type 'soulseek login', then a prompt requests username, after i type in and press enter i'm requested a password.
so far, i manage to get the prompt of the username but not getting passed that.
subprocess.run('soulseek login',shell=True)
this results in the ?Login output in the python console but also the process is stuck, when i run in debug or also in run
is there a better way to go about this?
Interacting continuously with a system via subprocess can be tricky. However, it seems that your interface prompts are one after the other, which can therefore be chained together, via newline characters which act as Return key strokes.
For example, the program shown below simply prompts a user for their username and a password, to which the 'user' (your script) provides the input via the proc.communicate() method. Once these are provided, the user is asked if they'd like to continue (and do the same thing again). The following subprocess call feeds the following input into the prompter.py script:
username
password
continue reply (y or n)
Example code:
import subprocess
uid = 'Bob'
pwd = 'MyPa$$w0rd'
reply = 'n'
with subprocess.Popen('./prompter.py',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
text=True) as proc:
stdout, stderr = proc.communicate(input='\n'.join([uid, pwd, reply]))
Output:
# Check output.
>>> print(stdout)
Please enter a username: Password: uid='Bob'
pwd='MyPa$$w0rd'
Have another go? [y|n]:
# Sanity check for errors.
>>> print(stderr)
''
Script:
For completeness, I've included the contents of the prompter.py script below.
#!/usr/bin/env python
from time import sleep
def prompter():
while True:
uid = input('\nPlease enter a username: ')
pwd = input('Password: ')
print(f'{uid=}\n{pwd=}')
sleep(2)
x = input('Have another go? [y|n]: ')
if x.lower() == 'n':
break
if __name__ == '__main__':
prompter()
I am writing a CLI that accepts an email and password for auth.
The email prompt uses raw_input() and the password prompt uses getpass() for obfuscation.
This setup works fine when outputting directly to console, but falters when redirecting the output to a log file.
Sample code:
user_email = raw_input('Email: ')
user_password = getpass('Password: ')
Sample output without redirection:
$ python script_that_does_stuff.py
Email: me#email.com
Password:
Doing stuff...
Sample output with redirection:
$ python script_that_does_stuff.py > stuff.log
Because I know that it's expecting a user input here, I can type the email, hit enter, and then it will show:
$ python script_that_does_stuff.py > stuff.log
me#email.com
Password:
After inputting a password, it continues as usual, however the log shows the following:
$ cat stuff.log
Email:Doing stuff...
Question:
How can I force the raw_input() prompt to show up in console like the getpass() prompt does when redirecting output to a file?
Environment
This script lives in a legacy Python 2.7 codebase, and is run primarily on Mac OS systems, occasionally Linux.
You can override sys.stdout temporarily to write to the terminal. For example,
import contextlib
import sys
#contextlib.contextmanager
def output_to_terminal():
try:
with open("/dev/tty") as f:
sys.stdout = f
yield
finally:
# Ensure sys.stdout is restored in the event of an error
sys.stdout = sys.__stdout__
with output_to_terminal():
x = raw_input("> ")
print(x)
(This was derived independently; you may want to check source for Python 3's redirect_stdout, also found in the contextlib module, and back port it for your use.)
This answer on another question seems to work for me.
In short, create a custom input function:
def email_input(prompt=None):
if prompt:
sys.stderr.write(str(prompt))
return raw_input()
The calling code then becomes:
user_email = email_input('Email: ')
user_password = getpass('Password: ')
This results in both the Email and Password prompts being sent to stderr (printing to console), and not messing with the redirected log output.
According to official documentation getpass([prompt[, stream]]) has the second optional parameter which indicates output stream to print the prompt to (stderr by default).
When you redirect the output (stdout) the prompt is still printed to stderr for getpass but raw_input does not support setting an output stream so its prompt is redirecting to to the target file.
So to solve your issue, you have to print your prompt to stderr for email as well.
Here's my sample python code
def gitClone():
proc = subprocess.Popen(['git', 'clone', 'https://someuser#sailclientqa.scm.azurewebsites.net:443/sailclientqa.git'], stdout=subprocess.PIPE)
stdout,stderr = proc.communicate('mypassword')
print(stddata)
print (stderr)
Every execution results in prompt for password. What's the best way for me to do this git clone from python without password prompt.
I'd appreciate your expertise/advice
The naive approach would be to add stdin=subprocess.PIPE to your command so you can feed it password input. But that's not that simple with password inputs which use special ways of getting keyboard input.
On the other hand, as stated in this answer it is possible to pass the password in command line (although not advised since password is stored in git command history with the command line !).
I would modify your code as follows:
add a password parameter. If not set, use python getpass module to prompt for password (maybe you don't need that)
if the password is properly set, pass it in the modified command line
my proposal:
import getpass
def gitClone(password=None):
if password is None:
password = getpass.getpass()
proc = subprocess.Popen(['git', 'clone', 'https://someuser:{}#sailclientqa.scm.azurewebsites.net:443/sailclientqa.git'.format(password)], stdout=subprocess.PIPE)
stdout,stderr = proc.communicate()
print(stddata)
print (stderr)
An alternative if the above solution doesn't work and if you cannot setup ssh keys, is to use pexpect module which handles special password input streams:
def gitClone(mypassword):
child = pexpect.spawn('git clone https://someuser#sailclientqa.scm.azurewebsites.net:443/sailclientqa.gi')
child.expect ('Password:')
child.sendline (mypassword)
It has the nice advantage of not storing the password in git command history.
source: How to redirect data to a "getpass" like password input?
I have a script that I want to run from within Python (2.6.5) that follows the logic below:
Prompts the user for a password. It looks like ("Enter password: ") (*Note: Input does not echo to screen)
Output irrelevant information
Prompt the user for a response ("Blah Blah filename.txt blah blah (Y/N)?: ")
The last prompt line contains text which I need to parse (filename.txt). The response provided doesn't matter (the program could actually exit here without providing one, as long as I can parse the line).
My requirements are somewhat similar to Wrapping an interactive command line application in a Python script, but the responses there seem a bit confusing, and mine still hangs even when the OP mentions that it doesn't for him.
Through looking around, I've come to the conclusion that subprocess is the best way of doing this, but I'm having a few issues. Here is my Popen line:
p = subprocess.Popen("cmd", shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
When I call a read() or readline() on stdout, the prompt is printer to the screen and it hangs.
If I call a write("password\n") for stdin, the prompt is written to the screen and it hangs. The text in write() is not written (I don't the cursor move the a new line).
If I call p.communicate("password\n"), same behavior as write()
I was looking for a few ideas here on the best way to input to stdin and possibly how to parse the last line in the output if your feeling generous, though I could probably figure that out eventually.
If you are communicating with a program that subprocess spawns, you should check out A non-blocking read on a subprocess.PIPE in Python. I had a similar problem with my application and found using queues to be the best way to do ongoing communication with a subprocess.
As for getting values from the user, you can always use the raw_input() builtin to get responses, and for passwords, try using the getpass module to get non-echoing passwords from your user. You can then parse those responses and write them to your subprocess' stdin.
I ended up doing something akin to the following:
import sys
import subprocess
from threading import Thread
try:
from Queue import Queue, Empty
except ImportError:
from queue import Queue, Empty # Python 3.x
def enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
def getOutput(outQueue):
outStr = ''
try:
while True: # Adds output from the Queue until it is empty
outStr+=outQueue.get_nowait()
except Empty:
return outStr
p = subprocess.Popen("cmd", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, universal_newlines=True)
outQueue = Queue()
errQueue = Queue()
outThread = Thread(target=enqueue_output, args=(p.stdout, outQueue))
errThread = Thread(target=enqueue_output, args=(p.stderr, errQueue))
outThread.daemon = True
errThread.daemon = True
outThread.start()
errThread.start()
try:
someInput = raw_input("Input: ")
except NameError:
someInput = input("Input: ")
p.stdin.write(someInput)
errors = getOutput(errQueue)
output = getOutput(outQueue)
Once you have the queues made and the threads started, you can loop through getting input from the user, getting errors and output from the process, and processing and displaying them to the user.
Using threading it might be slightly overkill for simple tasks.
Instead os.spawnvpe can be used. It will spawn script shell as a process. You will be able to communicate interactively with the script.
In this example I passed password as an argument, obviously that is not a good idea.
import os
import sys
from getpass import unix_getpass
def cmd(cmd):
cmd = cmd.split()
code = os.spawnvpe(os.P_WAIT, cmd[0], cmd, os.environ)
if code == 127:
sys.stderr.write('{0}: command not found\n'.format(cmd[0]))
return code
password = unix_getpass('Password: ')
cmd_run = './run.sh --password {0}'.format(password)
cmd(cmd_run)
pattern = raw_input('Pattern: ')
lines = []
with open('filename.txt', 'r') as fd:
for line in fd:
if pattern in line:
lines.append(line)
# manipulate lines
If you just want a user to enter a password without it being echoed to the screen just use the standard library's getpass module:
import getpass
print("You entered:", getpass.getpass())
NOTE:The prompt for this function defaults to "Password: " also this will only work on command lines where echoing can be controlled. So if it doesn't work try running it from terminal.
A series of applications I'm writing require that the user be able to read from a filesystem with KLOG authentication. Some functions require the user to have KLOG tokens (i.e., be authenticated) and others don't. I wrote a small Python decorator so that I can refactor the "you must be KLOGed" functionality within my modules:
# this decorator is defined in ``mymodule.utils.decorators``
def require_klog(method):
def require_klog_wrapper(*args, **kwargs):
# run the ``tokens`` program to see if we have KLOG tokens
out = subprocess.Popen('tokens', stdout=subprocess.PIPE)
# the tokens (if any) are located in lines 4:n-1
tokens_list = out.stdout.readlines()[3:-1]
if tokens_list == []:
# this is where I launch KLOG (if the user is not authenticated)
subprocess.Popen('klog')
out = method(*args, **kwargs)
return out
return require_klog_wrapper
# once the decorator is defined, any function can use it as follows:
from mymodule.utils.decorators import require_klog
#require_klog
def my_function():
# do something (if not KLOGed, it SHUOLD ask for the password... but it does not!)
It is all very simple. Except when I tried to apply the following logic: "if the user is not KLOGed, run KLOG and ask for the password".
I do this using subprocess.Popen('klog') and the password: prompt does come up to the terminal. However, when I write the password it actually is echoed back to the terminal and, worse, nothing happens upon hitting return.
Edit:
After Alex's fast and correct response I solved the problem as follows:
I erased all the *.pyc files from my module's directory (yes - this made a difference)
I used getpass.getpass() to store the password in a local variable
I called the KLOG command with the -pipe option
I passed the locally-stored password to the pipe via the pipe's write method
Below is the corrected decorator:
def require_klog(method):
def require_klog_wrapper(*args, **kwargs):
# run the ``tokens`` program to see if we have KLOG tokens
out = subprocess.Popen('tokens', stdout=subprocess.PIPE)
# the tokens (if any) are located in lines 4:n-1
tokens_list = out.stdout.readlines()[3:-1]
if tokens_list == []:
args = ['klog', '-pipe']
# this is the custom pwd prompt
pwd = getpass.getpass('Type in your AFS password: ')
pipe = subprocess.Popen(args, stdin=subprocess.PIPE)
# here is where the password is sent to the program
pipe.stdin.write(pwd)
return method(*args, **kwargs)
return require_klog_wrapper
Apparently, your script (or something else it's spawning later) and the subprocess running klog are "competing" for the /dev/tty -- and the subprocess is losing (after all, you're not calling the wait method of the object returned from subprocess.Popen, to ensure you wait until it terminates before continuing, so a race condition of some kind would be hardly surprising).
If a wait does not suffice, I would work around this by putting
pwd = getpass.getpass('password:')
in the Python code (with an import getpass at the top of course), then running klog with the -pipe argument and stdin=subprocess.PIPE, and writing pwd to that pipe (with a call to the communicate method of the object returned from subprocess.Popen).