suClick
suClick

Reputation: 996

Is there any way to excute a command as a specific user in Python?

As far as I kown, there are about 3 ways to excute a system command in Python:

  1. os.system(command) -> exit_status

  2. os.popen(command [, mode='r' [, bufsize]]) -> pipe

  3. commands.getoutput(command)-> string

Now I need to control the excutor of a system command, beside the way like:

os.system('su xxx;' + command)

is there any other more elegant way to reach the same effect?

Upvotes: 3

Views: 1597

Answers (3)

jfs
jfs

Reputation: 414585

Run a command as a different user using subprocess module (implementing @Julian's suggestion). It is similar to @tMC's code but at a higher level:

import os
import pwd
from subprocess import check_output as qx

def change_user(uid, gid=None):
    if gid is None:
        gid = uid
    def preexec_fn():
        os.setgid(gid)
        os.setgroups([gid])
        os.setuid(uid)
    return preexec_fn

print(qx(['id']))
print(qx(['id'], preexec_fn=change_user(pwd.getpwnam('nobody').pw_uid), env={}))
print(qx(['id']))

On old Python versions you might need to add close_fds=True in the subprocess calls to avoid fd leaks.

You could use cwd argument to specify a directory where to run the command e.g., a home directory of the user.

Populate env dictionary to set environment variables for the subprocess.

Upvotes: 2

tMC
tMC

Reputation: 19345

While likely to 'low level' for most implementations, it might be educational to some to understand how this really happens in the higher level modules.

import os
import pwd

pout, pin = os.pipe()                                # Creates an anonymous pipe used to communicate with the child process

if not os.fork():                                    # The fork() syscall duplicated the process retuning 0 to the new child process.
    os.closerange(0, 2)                              # Closing stdin, stdout and stderr- they were inherited from the parent process
    os.dup2(pin, 1)                                  # Copy the input side of the IPC pipe to stdout (always fd 1)
    os.setuid(pwd.getpwnam('nobody').pw_uid)         # Change our user id to that of the user `nobody`.  Also see `setgid()`
    os.execl('/bin/whoami', 'whoami')                # Now that we've connected out stdout to the pipe connected to the parent process
                                                     #     and changed our user id to that of the user we want to run the command with
                                                     #     we can `exec()` the new process; replacing our currently running process, 
                                                     #     inheriting the env.
    os._exit(0)                                      # Since we called `exec` above, this is never eval'd, but is good form

os.wait()                                            # Back at the parent process- `wait()` will, well, wait until the child process exits.
os.close(pin)                                        # Close the input side of the pipe, the parent shouldn't write to. (bi-dirctional IPC
                                                     #     would require 2 pipes.  One in each direction
child_stdout_pipe = os.fdopen(pout, 'r')             # Open the output side of the IPC pipe
child_process_output = child_stdout_pipe.read()      # ...and read from the pipe.  This should include anything that came from the stdout of
                                                     #     the child process.  Since we closed the input side of the pipe, the `read()` 
                                                     #     will read an EOF after all the data in the pipe is returned.

print child_process_output                           # Win! (`write()`ing out the parent stdout want we found in the pipe from the child proc

Upvotes: 0

Julian
Julian

Reputation: 3429

All of the things you've mentioned (which have been succeeded by the subprocess module by the way) are ways of spawning processes. You sound like you're looking for setuid. You can either call a function that will do that (e.g. os.setuid), or, as often is the case depending on what your script does, you can just run the entire script as the elevated user.

Upvotes: 2

Related Questions