在Python中以超级用户身份打开文件

5

我需要打开一个系统文件并从中读取内容。这个文件通常只有root(超级用户)才能读取。我有一种方法可以要求用户输入超级用户密码。我想使用这些凭据打开文件并从中读取内容,而不必将整个程序运行为超级用户进程。是否有一种跨平台的方法实现这个功能?


5
权限不是跨平台的。Windows 解决方案与 Linux 解决方案完全不同。您可能需要删除该标签。 - S.Lott
你还做了一个危险的假设,即存在一个根密码。许多系统禁用根密码身份验证,要求您使用sudo或等效物。 - Daniel Pryden
好主意... 我会考虑的 - Sergio
4个回答

4
你所需要的是特权提升,这非常取决于你运行的平台。一般来说,你的程序需要以超级用户身份运行部分内容。例如,在Unix系统上,你可以使用sudo读取文件的内容。
但正如之前提到的,这实际上取决于你运行的系统。

事实证明,这个文件往往很大,因此返回其内容是不可取的。 - Sergio
@Sergio:你是说流式读取不足以满足需求,需要随机访问吗?这将是一个重要的限制。 - Daniel Pryden
@Daniel:基本上就是这样。 - Sergio

4

由于特权在类Unix系统和Windows上的工作方式完全不同,因此您需要具有特定于平台的代码。无论如何,您都需要将程序分为两个单独的程序,其中一个以提升的权限运行,另一个以标准/降低的权限运行。

在类Unix系统(包括Linux和Mac OS X)中,以提升权限运行的可执行文件应该执行以下操作:

  1. 假设您正在以root身份运行并打开文件进行阅读。由于您提到该文件非常大,因此实际上不需要读取整个文件,只需保持一个打开的文件描述符。如果打开失败,请打印错误消息并退出。
  2. 使用setreuid(2)setregid(2)将用户ID和组ID设置回非特权用户。
  3. 使用其中一个exec(3)函数来执行非特权可执行文件。
  4. 如果您想使该程序无需使用sudo即可运行,则将其所有者设置为root,并使用chown root the-program; chmod +s the-program将其设置为设置用户ID可执行文件。

非特权程序现在将以普通权限运行,但是当它启动时,它将具有一个可以用于从您的特殊文件中读取的打开文件描述符(文件描述符#3)。

对于Windows来说,它类似但略有不同:
  1. 假设您以root身份运行并使用CreateFile打开文件进行读取。不要使用默认安全属性 - 创建一个SECURITY_ATTRIBUTES结构,将bInheritHandle设置为TRUE,以便句柄将被子进程继承。如果打开文件失败,请打印错误消息并退出。
  2. 使用CreateProcess启动子进程。在命令行上传递上面的句柄(例如,作为数字值打印);您也可以使用共享内存区域,但这比解决此问题更麻烦。
  3. 在此可执行文件中嵌入清单,将requireAdministrator设置为true。完成后,运行程序时,您将收到一个UAC提示,询问您是否允许该程序进行更改。
子进程通过解析命令行获取继承的句柄,然后可以随意读取数据。这种方法的一个问题是,当您继承句柄时,必须使用低级系统调用(Unix上的read(2),Windows上的ReadFile)从中读取--您不能使用更高级别的函数,如C的fread(3)或C ++的iostream(好吧,Unix有fdopen(3),但据我所知,Windows上没有相应的函数)。正如您现在已经注意到的那样,以上所有内容都是用C编写的。在Unix中,这很容易转换为Python,因为os模块拥有许多好东西,如setreuidexec*fdopen。在Windows上,您可能可以使用ctypes模块和/或Pywin32来完成其中一些工作,但最好还是使用C。

3
我会将程序拆分成两部分。
1. 处理打开文件和访问内容的部分。它可以假设已经具备所需的特权。 2. 其他不需要特殊权限的所有部分。
在配置中添加一个条目,描述如何执行需要额外特权的命令。例如:
access_special_file: sudo access_special_file 或者
access_special_file: runas /user:AccountWithPrivs access_special_file 这样可以将一些特权升级的系统细节转移到系统 shell 中,从而可能更方便地获得所需的权限。

2

在Linux上,这非常简单,就像@ViktorKerkez展示的那样。这是我如何流式传输我的WiFi密码文件(仅限root/sudo读取):

import subprocess
import sys

# substitute your Windoze/DOS/PowerlessShell command here:
cat_wifi_pws = 'sudo cat /etc/NetworkManager/system-connections/*'

process = subprocess.Popen(cat_wifi_pws, stdout=subprocess.PIPE, shell=True)
# read one line at a time, as it becomes available
for line in iter(process.stdout.readline, ''):
    sys.stdout.write(line)

当然,这将提示您输入sudo密码。如果您的系统有gksudo并且您喜欢对话框,则可以使用它。作为奖励,如果在/etc/sudoers中有一个合适的timeout_default,并且您最近在启动Python解释器的同一Shell中运行了sudo,则根本不需要输入密码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接