19

The following code throws an error if it's run by a non-root user for a file owned by root, even when the non-root user has sudo privileges:

try:
  f = open(filename, "w+")
except IOError:
  sys.stderr.write('Error: Failed to open file %s' % (filename))
f.write(response + "\n" + new_line)
f.close()

Is there a way to run open(filename, "w+") with sudo privileges, or an alternative function that does this?

user2824889
  • 1,085
  • 5
  • 16
  • 28

7 Answers7

22

You have a few options:

  • Run your script as root or with sudo
  • Set the setuid bit and have root own the script (although on many systems this won't work with scripts, and then the script will be callable by anyone)
  • Detect that you're not running as root (os.geteuid() != 0), then call yourself with sudo infront (which will ask the user to enter their password) and exit:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['sudo', 'python3', *sys.argv])
    sys.exit()

Calling it looks like this:

$ python3 be_root.py
We're not root.
Password:
We're root!
L3viathan
  • 26,748
  • 2
  • 58
  • 81
  • I get a syntax error running your example; I'm pretty sure star argument expansion only works in function calls and this is trying to use it in a list. You could use a list concatenation instead. See [my answer](https://stackoverflow.com/a/45925254/2128530) for more detail. – Peter Henry Aug 28 '17 at 18:30
  • @PeterHenry You're using an outdated version of Python, this is [PEP 448](https://www.python.org/dev/peps/pep-0448/). – L3viathan Aug 28 '17 at 18:38
  • 2
    My mistake; I'm currently working on Python 2.7 project and got my virtual environments mixed up. Thought I was on Python 3. I'll amend my answer as a Python 2.7 compatible solution. – Peter Henry Aug 28 '17 at 18:45
10

TL;DR:

L3viathan's reply has a SynaxError Edit: works fine on Python 3.5+. Here is a version for Python 3.4.3 (distributed by default on Ubuntu 16.04) and below:

if os.geteuid() == 0:
    # do root things
else:
    subprocess.call(['sudo', 'python3'] + sys.argv)  # modified

However, I went with a different approach because it made the code around it simpler in my use case:

if os.geteuid() != 0:
    os.execvp('sudo', ['sudo', 'python3'] + sys.argv)
# do root things

Explaination

L3viathan's reply depends on PEP 448, which was included in Python 3.5 and introduced additional contexts where star argument expansion is permitted. For Python 3.4 and below, list concatenation can be used to do the same thing:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['sudo', 'python3'] + sys.argv)  # modified

But note: subprocess.call() launches a child process, meaning after root finishes running the script, the original user will continue running their script as well. This means you need to put the elevated logic into one side of an if/else block so when the original script finishes, it doesn't try to run any of the logic that requires elevation (L3viathan's example does this).

This isn't necessarily a bad thing - it means both normal/elevated logic can be written in the same script and separated nicely - but my task required root for all the logic. I didn't want to waste an indentation level if the other block was going to be empty, and I hadn't realized how using a child process would affect things, so I tried this:

import os
import sys
import subprocess

if os.geteuid() != 0:
    subprocess.call(['sudo', 'python3'] + sys.argv)

# do things that require root

...and it broke of course, because after root was done, the regular user resumed execution of its script and tried to run the statements that required root.

Then I found this Gist recommending os.execvp() - which replaces the running process instead of launching a child:

import os
import sys

if os.geteuid() != 0:
    os.execvp('sudo', ['sudo', 'python3'] + sys.argv)  # final version

# do things that require root

This appears to behave as expected, saves an indentation level and 3 lines of code.

Caveat: I didn't know about os.execvp() ten minutes ago, and don't know anything yet about possible pitfalls or subtleties around its usage. YMMV.

Peter Henry
  • 443
  • 4
  • 13
5

Another option is to write the file to a directory you have access to, then sudo mv the file to the directory you don't have access to.

JS.
  • 14,781
  • 13
  • 63
  • 75
2

Your script is limited to the permissions it is run with as you cannot change users without already having root privileges.

As Rob said, the only way to do this without changing your file permissions is to run with sudo.

sudo python ./your_file.py
Matt Olan
  • 1,911
  • 1
  • 18
  • 27
2

Having possibility to using sudo don't give you any privileges if you don't actually use it. So as other guys suggested you probably should just start your program with use of sudo. But if you don't like this idea (I don't see any reason for that) you can do other trick.

Your script can check if is run with root privileges or if it work only with user privileges. Than script can actually run itself with higher privileges. Here you have an example (please note that storing password in source code isn't a good idea).

import os.path
import subprocess

password_for_sudo = 'pass'

def started_as_root():
    if subprocess.check_output('whoami').strip() == 'root':
        return True
    return False

def runing_with_root_privileges():
    print 'I have the power!'

def main():
    if started_as_root():
        print 'OK, I have root privileges. Calling the function...'
        runing_with_root_privileges()
    else:
        print "I'm just a user. Need to start new process with root privileges..."
        current_script = os.path.realpath(__file__)
        os.system('echo %s|sudo -S python %s' % (password_for_sudo, current_script))

if __name__ == '__main__':
    main()

Output:

$ python test.py
I'm just a user. Need to start new process with root privileges...
OK, I have root privileges. Calling the function...
I have the power!

$ sudo python test.py
OK, I have root privileges.
Calling the function...
I have the power!
Adam
  • 2,254
  • 3
  • 24
  • 42
1

I like L3viathan's reply. I'd add a fourth option: Use subprocess.Popen() to execute an sh command to write the file. Something like subprocess.Popen('sudo echo \'{}\' > {}'.format(new_line, filename)).

Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
portusato
  • 46
  • 4
0

The recommended approach to invoking subprocesses is to use run() for all use cases it can handle. This is such a case.

#!/usr/bin/env python3

from os import geteuid
from subprocess import run
from sys import argv, exit


def main():
  if geteuid() == 0:
    print("We're root!")
    with open("file_owned_by_root", "w+") as f:
      conf_file="""{
"alt-speed-down": 50,
"alt-speed-enabled": false,
...
"upload-slots-per-torrent": 14,
"utp-enabled": true
}"""
      f.write(conf_file)
  else:
    print("We're not root.")
    run(["doas", *argv])
    exit()

if __name__ == "__main__":
  main()

#https://stackoverflow.com/questions/39499233/write-to-a-file-with-sudo-privileges-in-python

Note that when you have a shebang in your script then there is no need to pass 'python3' inside of the list of arguments to run().

John Smith
  • 835
  • 1
  • 7
  • 19