I use OpenVPN at my company and am trying to automate user creation process. There's a problem at the certificate generation step I faced now. When trying to build a key for the user (all parameters are predefined) program has to press Enter multiple times and in the end "y" and "Enter" 2 times. I tried using Popen and PIPE, but no luck so far. Would appreciate any insight.
import sys, os
from subprocess import Popen, PIPE
# Generate an .ovpn file
try:
username = sys.argv[1]
except:
print "Error. Supply a username!"
sys.exit()
print("Adding user")
os.system("useradd" + " -m" + " -s" + " /bin/bash" + username)
print("Sourcing vars")
os.system('source + /home/myuser/openvpn-ca/vars')
enter = Popen(['/home/myuser/openvpn-ca/build-key {}'.format(username)]),
stdin=PIPE, shell=True)
enter.communicate(input='\n')
Edit:
This is different than what it was marked [duplicate] for. Here's why:
I don't need to generate a custom certificate, change any values etc. It just needs to press "Enter" multiple times and input "yes" and "Enter" 2 times.
You cannot source a shell script from Python; or rather, you can, but it will simply start a new subprocess which sources something and then disappears, without changing anything in your Python environment or subsequent subprocesses.
Try something like this instead:
import sys
import logging # to get diagnostics on standard error instead
import subprocess
# Maybe switch to level=logging.WARNING once you are confident this works
logging.basicConfig(level=logging.INFO, format='%(module)s:%(asctime)s:%(message)s')
try:
username = sys.argv[1]
except:
logging.error("Error. Supply a username!")
sys.exit()
logging.info("Adding user")
subprocess.run(["useradd", "-m", "-s", "/bin/bash", username],
check=True, universal_newlines=True)
logging.info("Building key")
subprocess.run('''
source /home/myuser/openvpn-ca/vars
/home/myuser/openvpn-ca/build-key {}'''.format(username),
shell=True, check=True, input='\n\n', universal_newlines=True)
The switch to subprocess.run() requires a reasonably new version of Python 3. In older versions, subprocess.check_call() would do roughly the same thing, but didn't have an input= argument, so you really did have to use the basic Popen() for this.
Additional notes:
The plus sign after source was obviously a syntax error
We use check=True throughout to make sure Python checks that the commands finish successfully.
Mixing os.system() with subprocess is not an error, but certainly a suspicious code smell.
(Much) more about using subprocess on U*x here: https://stackoverflow.com/a/51950538/874188
Related
I'm making a shell with python. So far I have gotten cd to work (not pretty I know, but it's all I need for now). When I su root (for example) I get a root shell, but I can't capture the output I receive after running a command. However the shell does accept my commands, as when I type exit it exits. Is there a way to capture the output of a 'new' shell?
import os, subprocess
while True:
command = input("$ ")
if len(command.split(" ")) >= 2:
print(command.split(" ")[0]) #This line is for debugging
if command.split(" ")[0] == "cd" or command.split(" ")[1] == "cd":
os.chdir(command.split(" ")[command.split(" ").index("cd") + 1])
continue
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, universal_newlines=True)
output, error = process.communicate()
print(output.strip("\n"))
EDIT: To make my request a bit more precise, I'd like a way to authenticate as another user from a python script, basically catching the authentication, doing it in the background and then starting a new subprocess.
You really need to understand how subprocess.Popen works. This command executes a new sub-process (on a Unix machine, calls fork and then exec). The new sub-process is a separate process. Your code just calls communicate once and then discards of it.
If you just create a new shell by calling subprocess.Popen and then running su <user> inside of it, the shell will be closed right after that and the next time, you'll be running the command using the same (original) user again.
What you want is probably to create a single subprocess at the beginning of your application and then be a sort of a proxy between the user and the underlying process, and then just keep writing to its stdin and reading from stdout.
Here's an example:
import os, subprocess
process = subprocess.Popen(["bash"], stdin=subprocess.PIPE,
stdout=subprocess.PIPE, universal_newlines=True)
while True:
command = input("$ ")
process.stdin.write(command + "\n")
process.stdin.flush()
output = process.stdout.readline()
print(output.strip("\n"))
(I removed the cd command parsing bit because it wasn't constructive to understanding the solution here, but you can definitely add specific handlers for specific inputs that wrap the underlying shell)
Sorry in advance, I've tried searching through similar questions, but I've come to a dead-end. I've figured out a way around this issue, but I still want to figure out how to use the subprocess module effectively.
I'm writing a Python function that interacts with VMWare's OVFtool.exe (Windows environment) via the subprocess module using Popen to export an OVA file from VSphere. I've made it to the point where I can see and interact with the username and password prompts from the OVFTool.
When I run the below code, it shows me the prompt from the ovftool.exe for the username, and I write the username to stdint and flush, and then I see the prompt for the password. I input the password write it to stdin and it then flushes stdin but then it just stalls on the last proc.stdout.readline(4) with no errors or output. I'm just using a test username/password for now, and it should give me the username and password prompts again, but it seems to just hang on that readline function. My thought is maybe the ovftool process is spawning another process or maybe I'm misunderstanding how to buffer and submit input via stdin (likely)?
I've changed around the buffer amount, I've tried subprocess with and without the universal_new_lines, bufsize=1, and shell=True arguments. What I ended up doing as a workaround is I pass the username and password via the command-line argument for ovftool.exe. Still, I'd like to know what I am doing wrong because I'd like to be able to capture the output in real-time using Popen and interact with it as needed.
Thank you in advance for your help.
def export_extract_ova_file():
vm_name = input("What is the name of the vm you are exporting?")
print(f"Exporting {vm_name} from VSphere.")
with subprocess.Popen(f"\"{ovf_tool_path}\" vi://example_ip/{vm_name}"
f"./{vm_name}.ova", text=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, shell=True) as proc:
proc_read = proc.stdout.readline()
print(proc_read)
proc_read = proc.stdout.readline(10)
print(proc_read)
input("Please enter your username")
proc.stdin.write(username + "\n")
proc.stdin.flush()
proc_read = proc.stdout.readline(10)
print(proc_read)
password = input("Please enter your password")
proc.stdin.write(password + "\n")
proc.stdin.flush()
proc.stdout.flush()
sleep(1.0)
proc_read = proc.stdout.readline(4)
print(proc_read)
I am trying to execute a linux command through Python.
Here, for one of the command (cryptsetup luksChangeKey) I need to pass two keys [current key and new key] when prompted by the command through STDIN.
I tried using communicate() for the same and not able to pass both the keys.
Is there any other option available in python for the above scenario ?
Sample Code:
import subprocess
cmd = 'cryptsetup --batch-mode --key-file - luksChangeKey
/dev/multiplelv_pool_VG_13341/lv4'
process = subprocess.Popen(shlex.split(cmd), stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process.stdin.write("old123\nnew123\n")
process.communicate()
process.stdin.close()
If we manually execute the cryptsetup command, then it will prompt for old key and then the new key to be provided.
I am trying to simulate the same through the Python code.
Here current password is old123 and new password is new123.
My expectation of using '\n' in stdin.write was to split the password into two lines. However, POpen is taking the entire
line as current password and resulting in error
I am using Python 2.7
This work for me:
other.py
a = input()
b = input()
print(a, b)
program.py
import subprocess
cmd = r'python other.py'
process = subprocess.Popen(cmd, stdin=subprocess.PIPE)
process.communicate("input1\ninput2".encode('utf-8'))
process.stdin.close()
# print: >>> input1 input2
The subprocess documentation warns about the use of stdin.write.
Also, I don't know how your code doesn't throw an error, since you are passing a string to stdin and not a bytes-like object? This is what happens for me with your code:
TypeError: a bytes-like object is required, not 'str'
This is not a Python problem but a cryptsetup limitation. It is explicit in the manpage (emphasize mine):
--key-file, -d<br/>
use file as key material.
... If the key file is "-", stdin will be used. With the "-" key file reading will not stop when new line character is detected.
and later:
Notes on Password Processing
...
From stdin: Reading will continue until EOF
Workarounds:
According to this post on SuperUser, it used to be possible to pass both the old and new passwords in the same keyfile, but it would no longer be possible. A trick would be to use stdin for the old key, and stdout for the new one. Python will refuse to write on a process.stdout file because it is only opened for reading, but it is still possible to write on it at the fileno level. Code would become:
import subprocess
cmd = 'cryptsetup --batch-mode --key-file - luksChangeKey
/dev/multiplelv_pool_VG_13341/lv4 /dev/fd/1' # read the new key from /dev/fd/1
process = subprocess.Popen(shlex.split(cmd), stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
os.write(process.stdout.fileno(), 'new123')
process.communicate('old123')
Beware: untested because I currently have no Linux system with LUKS.
A way to trick cryptsetup would be to use the pexpect-module (needs to be installed separately). This runs a subprocess faking a controlling terminal. It then allows to wait for prompts and issue user input when prompted. This is e.g. a way to control SSH and enter a password instead of having to resort to public keys etc.
I am running a python script from another python script and I am wondering how I can catch exceptions from the parent python script.
My parent python script calls another python script n amount of times. Eventually that called script will exit with a 'ValueError' exception. I'm wondering if there is a way for my parent python script to notice this and then stop executing.
Here is the base code as-is:
import os
os.system('python other_script.py')
I have tried things such as this to no avail:
import os
try:
os.system('python other_script.py')
except ValueError:
print("Caught ValueError!")
exit()
and
import os
try:
os.system('python other_script.py')
except:
print("Caught Generic Exception!")
exit()
The os.system() always returns an integer result code. And,
When it returns 0, the command ran successfully;
when it returns a nonzero value, that indicates an error.
For checking that you can simply add a condition,
import os
result = os.system('python other_script.py')
if 0 == result:
print(" Command executed successfully")
else:
print(" Command didn't executed successfully")
But, I recommend you to use subprocess module insted of os.system(). It is a bit complicated than os.system() but it is way more flexible than os.system().
With os.system() the output is sent to the terminal, but with subprocess, you can collect the output so you can search it for error messages or whatever. Or you can just discard the output.
The same program can be done using subprocess as well;
# Importing subprocess
import subprocess
# Your command
cmd = "python other_script.py"
# Starting process
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE.PIPE)
# Getting the output and errors of the program
stdout, stderr = process.communicate()
# Printing the errors
print(stderr)
Hope this helps :)
I've managed to get the cmd being opened by python. However, using runas administrator comes with a password check before cmd.exe is executed.
I'm using this to open cmd...
import subprocess
subprocess.call(["runas", "/user:Administrator", "cmd.exe"])
I'm looking for a way to automatically enter the password into the runas.exe prompt which opens when i run the code. Say if i were to create var = "test" and add it after import subprocess how would i make it so that this variable is passed to and seen as an input to the runas.exe?
The solution would require only python modules which are in version 3.4 or higher.
Update
I have found some code which appears to input straight into runas.exe. However, the apparent input is \x00\r\n when in the code the input is supposed to be test I am fairly certain that if i can get the input to be test then the code will be successful.
The code is as follows :
import subprocess
args = ['runas', '/user:Administrator', 'cmd.exe']
proc = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.stdin.write(b'test\n')
proc.stdin.flush()
stdout, stderr = proc.communicate()
print (stdout)
print (stderr)
Although not an answer to your question, this can be a solution to your problem. Use psexec instead of runas. You can run it like this:
psexec -u user -p password cmd
(or run it from Python using subprocess.Popen or something else)
This piece of code actually works (tested on a Windows 2008 server). I've used it to call runas for a different user and pass his password. A new command prompt opened with new user context, without needing to enter password.
Note that you have to install pywin32 to have access to the win32 API.
The idea is:
to Popen the runas command, without any input redirection, redirecting output
read char by char until we encounter ":" (last char of the password prompt).
send key events to the console using win32 packages, with the final \r to end the password input.
(adapted from this code):
import win32console, win32con, time
import subprocess
username = "me"
domain = "my_domain"
password ="xxx"
free_console=True
try:
win32console.AllocConsole()
except win32console.error as exc:
if exc.winerror!=5:
raise
## only free console if one was created successfully
free_console=False
stdin=win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
p = subprocess.Popen(["runas",r"/user:{}\{}".format(domain,username),"cmd.exe"],stdout=subprocess.PIPE)
while True:
if p.stdout.read(1)==b":":
for c in "{}\r".format(password): # end by CR to send "RETURN"
## write some records to the input queue
x=win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
x.Char=unicode(c) # remove unicode for python 3
x.KeyDown=True
x.RepeatCount=1
x.VirtualKeyCode=0x0
x.ControlKeyState=win32con.SHIFT_PRESSED
stdin.WriteConsoleInput([x])
p.wait()
break