parent
7aa494c365
commit
8ef5744631
@ -0,0 +1,112 @@ |
||||
""" |
||||
The runscript module is responsible for running external scripts and processing |
||||
their output. |
||||
""" |
||||
|
||||
import logging |
||||
import os |
||||
import pwd |
||||
import grp |
||||
import subprocess |
||||
|
||||
|
||||
log = logging.getLogger('RUNSCRIPT') |
||||
|
||||
def run_as(uid, gid, groups): |
||||
"""Closure that changes the current running user and groups. Called before |
||||
executing scripts by Subprocess.""" |
||||
def set_acc(): |
||||
"""Change user and groups""" |
||||
os.setgroups(groups) |
||||
os.setgid(gid) |
||||
os.setuid(uid) |
||||
return set_acc |
||||
|
||||
def run_script(form_def, form_values, stdout=None, stderr=None): |
||||
""" |
||||
Perform a callback for the form `form_def`. This calls a script. |
||||
`form_values` is a dictionary of validated values as returned by |
||||
FormDefinition.validate(). If form_def.output is of type 'raw', `stdout` |
||||
and `stderr` have to be open filehandles where the output of the |
||||
callback should be written. The output of the script is hooked up to |
||||
the output, depending on the output type. |
||||
""" |
||||
# Validate params |
||||
if form_def.output == 'raw' and (stdout is None or stderr is None): |
||||
msg = 'stdout and stderr cannot be none if script output ' \ |
||||
'is \'raw\'' |
||||
raise ValueError(msg) |
||||
|
||||
# Pass form values to the script through the environment as strings. |
||||
env = os.environ.copy() |
||||
for key, value in form_values.items(): |
||||
env[key] = str(value) |
||||
|
||||
# Get the user uid, gid and groups we should run as. If the current |
||||
# user is root, we run as the given user or 'nobody' if no user was |
||||
# specified. Otherwise, we run as the user we already are. |
||||
if os.getuid() == 0: |
||||
if form_def.run_as is not None: |
||||
runas_pw = pwd.getpwnam(form_def.run_as) |
||||
else: |
||||
# Run as nobody |
||||
runas_pw = pwd.getpwnam('nobody') |
||||
runas_gr = grp.getgrgid(runas_pw.pw_gid) |
||||
groups = [ |
||||
g.gr_gid |
||||
for g in grp.getgrall() |
||||
if runas_pw.pw_name in g.gr_mem |
||||
] |
||||
msg = "Running script as user={0}, gid={1}, groups={2}" |
||||
run_as_fn = run_as(runas_pw.pw_uid, runas_pw.pw_gid, groups) |
||||
log.info(msg.format(runas_pw.pw_name, runas_gr.gr_name, |
||||
str(groups))) |
||||
else: |
||||
run_as_fn = None |
||||
if form_def.run_as is not None: |
||||
log.critical("Not running as root, so we can't run the " |
||||
"script as user '{0}'".format(form_def.run_as)) |
||||
|
||||
# If the form output type is 'raw', we directly stream the output to |
||||
# the browser. Otherwise we store it for later displaying. |
||||
if form_def.output == 'raw': |
||||
try: |
||||
proc = subprocess.Popen(form_def.script, |
||||
shell=True, |
||||
stdout=stdout, |
||||
stderr=stderr, |
||||
env=env, |
||||
close_fds=True, |
||||
preexec_fn=run_as_fn) |
||||
stdout, stderr = proc.communicate(input) |
||||
log.info("Exit code: {0}".format(proc.returncode)) |
||||
return proc.returncode |
||||
except OSError as err: |
||||
log.exception(err) |
||||
stderr.write(str(err) + '. Please see the log file.') |
||||
return -1 |
||||
else: |
||||
try: |
||||
proc = subprocess.Popen(form_def.script, |
||||
shell=True, |
||||
stdin=subprocess.PIPE, |
||||
stdout=subprocess.PIPE, |
||||
stderr=subprocess.PIPE, |
||||
env=env, |
||||
close_fds=True, |
||||
preexec_fn=run_as_fn) |
||||
stdout, stderr = proc.communicate() |
||||
log.info("Exit code: {0}".format(proc.returncode)) |
||||
return { |
||||
'stdout': stdout, |
||||
'stderr': stderr, |
||||
'exitcode': proc.returncode |
||||
} |
||||
except OSError as err: |
||||
log.exception(err) |
||||
return { |
||||
'stdout': '', |
||||
'stderr': 'Internal error: {0}. Please see the log ' |
||||
'file.'.format(str(err)), |
||||
'exitcode': -1 |
||||
} |
Loading…
Reference in new issue