以 root 身份连接到用户 dbus
Connecting to user dbus as root
如果我们正常打开一个python解释器,输入以下内容:
import dbus
bus = dbus.SessionBus()
bus.list_names()
我们看到了用户会话 dbus 上的所有服务。现在假设我们想在同一个脚本中做一些 root-only 的事情来确定通过 dbus 传递的信息,所以我们 运行 解释器与 sudo python
和 运行 同样的事情,我们只查看 root 用户会话 dbus 上的项目的简短列表,并尝试使用 get_object
连接到用户 dbus 上的任何内容相应地产生未找到的错误。
到目前为止我已经尝试插入
import os
os.seteuid(int(os.environ['SUDO_UID']))
但这只会让 SessionBus()
给出 org.freedesktop.DBus.Error.NoReply
所以这可能是无稽之谈。有没有办法以超级用户身份使用 python dbus 绑定连接到用户的 dbus 服务?
您可以设置 DBUS_SESSION_BUS_ADDRESS
环境变量来选择您要连接的 dbus 会话。
不正确的权限(即缺少 seteuid
)导致立即 NoReply
,并且未定义 DBUS_SESSION_BUS_ADDRESS
响应为 Using X11 for dbus-daemon autolaunch was disabled at compile time, set your DBUS_SESSION_BUS_ADDRESS instead
。
这是我使用的测试代码:
import os
import dbus
# become user
uid = os.environ["SUDO_UID"]
print(f"I'm {os.geteuid()}, becoming {uid}")
os.seteuid(int(uid))
# set the dbus address
os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path=/run/user/{uid}/bus"
bus = dbus.SessionBus()
print(bus.list_names())
# I'm 0, becoming 1000
# dbus.Array([dbus.String('org.freedesktop.DBus'), dbus.String('org.fr .. <snip>
我对 DBus 知之甚少,但这个问题让我很好奇。
TL;DR:使用 dbus.bus.BusConnection
和目标用户的套接字地址,seteuid
以获得访问权限。
第一个问题:DBus 为会话总线连接到什么套接字?
$ cat list_bus.py
import dbus
print(dbus.SessionBus().list_names())
$ strace -o list_bus.trace python3 list_bus.py
$ grep ^connect list_bus.trace
connect(3, {sa_family=AF_UNIX, sun_path="/run/user/1000/bus"}, 20) = 0
也许它依赖于环境变量?知道了!
$ env|grep /run/user/1000/bus
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
从 root 帐户跟踪行为,它似乎不知道要连接到的地址。谷歌搜索变量名称让我进入 D-Bus Specification,部分“Well-known 消息总线实例”。
第二个问题:我们可以直接连接到套接字而不需要 D-Bus 库猜测正确的地址吗? dbus-python tutorial 状态:
For special purposes, you might use a non-default Bus, or a connection which isn’t a Bus at all, using some new API added in dbus-python 0.81.0.
查看 changelog,这似乎指的是这些:
Bus has a superclass dbus.bus.BusConnection (a connection to a bus daemon, but without the shared-connection semantics or any deprecated API) for the benefit of those wanting to subclass bus daemon connections
让我们试试看:
$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
145
root 权限如何?
# python3
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3/dist-packages/dbus/bus.py", line 124, in __new__
bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not
receive a reply. Possible causes include: the remote application did not send
a reply, the message bus security policy blocked the reply, the reply timeout
expired, or the network connection was broken.
>>> import os
>>> os.seteuid(1000)
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
143
所以这回答了问题:使用BusConnection
而不是SessionBus
并明确指定地址,结合seteuid
获得访问权限.
奖励:以 root 身份连接而不使用 seteuid
我还是想知道是否可行
以 root 用户身份直接访问总线,而无需诉诸 seteuid
。后
一些搜索查询,我发现 a systemd ticket
加上这句话:
dbus-daemon is the component enforcing access ... (but you can drop an xml policy file in, to make it so).
这让我 askubuntu question 讨论如何修改站点本地会话总线策略。
只是为了玩玩,我在一个终端中 运行 这个:
$ cp /usr/share/dbus-1/session.conf session.conf
$ (edit session.conf to modify the include for local customization)
$ diff /usr/share/dbus-1/session.conf session.conf
50c50
< <include ignore_missing="yes">/etc/dbus-1/session-local.conf</include>
---
> <include ignore_missing="yes">session-local.conf</include>
$ cat > session-local.conf
<busconfig>
<policy context="mandatory">
<allow user="root"/>
</policy>
</busconfig>
$ dbus-daemon --config-file session.conf --print-address
unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98
$ dbus-daemon --config-file session.conf --print-address
unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98
在另一个终端中,我无法以 root 用户身份连接到此总线:
# python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
>>> from dbus.bus import BusConnection
>>> address = "unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98"
>>> BusConnection(address).list_names()
dbus.Array([dbus.String('org.freedesktop.DBus'), dbus.String(':1.0')], signature=dbus.Signature('s'))
在全局安装 session-local.conf
时,这还应该能够访问系统上的 所有 会话总线:
# cp session-local.conf /etc/dbus-1/session-local.conf
# kill -HUP 1865 # reload config of my users session dbus-daemon
# python3
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
143
并且有效 - 现在 root 可以连接到任何会话总线而无需诉诸 seteuid
。别忘了
# rm /etc/dbus-1/session-local.conf
如果您的 root 用户不需要此功能。
Bluehorn 的回答对我很有帮助。我没有足够的声誉来支持他的回答,但我想我会分享我的解决方案。我才学习 python(仅来自 shell 脚本编写),所以如果这确实是错误的并且恰好在我的系统上工作,请告诉我,哈哈。
这些是我编写的用于监视 cpu 温度和控制 linux 中的风扇速度的守护程序的部分,因此它需要 root 权限。不确定如果 运行 作为普通用户在多个用户登录时它会如何工作。我猜它不会...
import os, pwd
from dbus import SessionBus, Interface
from dbus.bus import BusConnection
# Subclassing dbus.Interface because why not
class Dbus(Interface):
def __init__(self, uid):
method = 'org.freedesktop.Notifications'
path = '/' + method.replace('.', '/')
if os.getuid() == uid:
obj = SessionBus().get_object( method, path )
else:
os.seteuid(uid)
obj = BusConnection( "unix:path=/run/user/" + str(uid) + "/bus" )
obj.get_object( method, path )
super().__init__(obj, method)
# Did this so my notifications would work
# when running as root or non root
class DbusNotificationHandler:
app_icon = r"/path/to/my/apps/icon.png"
name = "MacFanD"
def __init__(self):
loggedIn, users = [ os.getlogin() ], []
for login in pwd.getpwall():
if login.pw_name in loggedIn:
users.append( login.pw_uid )
self.users = []
for i in users:
self.users.append( Dbus(i) )
def notification(self, msg, mtype=None):
if not isinstance(msg, list) or len(msg) < 2:
raise TypeError("Expecting a list of 2 for 'msg' parameter")
hint = {}
if mtype == 'temp':
icon = 'dialog-warning'
hint['urgency'] = 2
db_id = 498237618
timeout = 0
elif mtype == 'warn':
icon = 'dialog-warning'
hint['urgency'] = 2
db_id = 0
timeout = 5000
else:
icon = self.app_icon
hint['urgency'] = 1
db_id = 0
timeout = 5000
for db in self.users:
db.Notify( self.name, db_id, icon, msg[0], msg[1:], [], hint, timeout )
handler = DbusNotificationHandler()
notify = handler.notification
msg = [ "Daemon Started", "Daemon is now running - %s"%os.getpid() ]
notify(msg)
temp = "95 Celsius"
msg = [ "High Temp Warning", "CPU temperature has reached %s"%temp ]
notify(msg, 'warn')
如果我们正常打开一个python解释器,输入以下内容:
import dbus
bus = dbus.SessionBus()
bus.list_names()
我们看到了用户会话 dbus 上的所有服务。现在假设我们想在同一个脚本中做一些 root-only 的事情来确定通过 dbus 传递的信息,所以我们 运行 解释器与 sudo python
和 运行 同样的事情,我们只查看 root 用户会话 dbus 上的项目的简短列表,并尝试使用 get_object
连接到用户 dbus 上的任何内容相应地产生未找到的错误。
到目前为止我已经尝试插入
import os
os.seteuid(int(os.environ['SUDO_UID']))
但这只会让 SessionBus()
给出 org.freedesktop.DBus.Error.NoReply
所以这可能是无稽之谈。有没有办法以超级用户身份使用 python dbus 绑定连接到用户的 dbus 服务?
您可以设置 DBUS_SESSION_BUS_ADDRESS
环境变量来选择您要连接的 dbus 会话。
不正确的权限(即缺少 seteuid
)导致立即 NoReply
,并且未定义 DBUS_SESSION_BUS_ADDRESS
响应为 Using X11 for dbus-daemon autolaunch was disabled at compile time, set your DBUS_SESSION_BUS_ADDRESS instead
。
这是我使用的测试代码:
import os
import dbus
# become user
uid = os.environ["SUDO_UID"]
print(f"I'm {os.geteuid()}, becoming {uid}")
os.seteuid(int(uid))
# set the dbus address
os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path=/run/user/{uid}/bus"
bus = dbus.SessionBus()
print(bus.list_names())
# I'm 0, becoming 1000
# dbus.Array([dbus.String('org.freedesktop.DBus'), dbus.String('org.fr .. <snip>
我对 DBus 知之甚少,但这个问题让我很好奇。
TL;DR:使用 dbus.bus.BusConnection
和目标用户的套接字地址,seteuid
以获得访问权限。
第一个问题:DBus 为会话总线连接到什么套接字?
$ cat list_bus.py
import dbus
print(dbus.SessionBus().list_names())
$ strace -o list_bus.trace python3 list_bus.py
$ grep ^connect list_bus.trace
connect(3, {sa_family=AF_UNIX, sun_path="/run/user/1000/bus"}, 20) = 0
也许它依赖于环境变量?知道了!
$ env|grep /run/user/1000/bus
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
从 root 帐户跟踪行为,它似乎不知道要连接到的地址。谷歌搜索变量名称让我进入 D-Bus Specification,部分“Well-known 消息总线实例”。
第二个问题:我们可以直接连接到套接字而不需要 D-Bus 库猜测正确的地址吗? dbus-python tutorial 状态:
For special purposes, you might use a non-default Bus, or a connection which isn’t a Bus at all, using some new API added in dbus-python 0.81.0.
查看 changelog,这似乎指的是这些:
Bus has a superclass dbus.bus.BusConnection (a connection to a bus daemon, but without the shared-connection semantics or any deprecated API) for the benefit of those wanting to subclass bus daemon connections
让我们试试看:
$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
145
root 权限如何?
# python3
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3/dist-packages/dbus/bus.py", line 124, in __new__
bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not
receive a reply. Possible causes include: the remote application did not send
a reply, the message bus security policy blocked the reply, the reply timeout
expired, or the network connection was broken.
>>> import os
>>> os.seteuid(1000)
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
143
所以这回答了问题:使用BusConnection
而不是SessionBus
并明确指定地址,结合seteuid
获得访问权限.
奖励:以 root 身份连接而不使用 seteuid
我还是想知道是否可行
以 root 用户身份直接访问总线,而无需诉诸 seteuid
。后
一些搜索查询,我发现 a systemd ticket
加上这句话:
dbus-daemon is the component enforcing access ... (but you can drop an xml policy file in, to make it so).
这让我 askubuntu question 讨论如何修改站点本地会话总线策略。
只是为了玩玩,我在一个终端中 运行 这个:
$ cp /usr/share/dbus-1/session.conf session.conf
$ (edit session.conf to modify the include for local customization)
$ diff /usr/share/dbus-1/session.conf session.conf
50c50
< <include ignore_missing="yes">/etc/dbus-1/session-local.conf</include>
---
> <include ignore_missing="yes">session-local.conf</include>
$ cat > session-local.conf
<busconfig>
<policy context="mandatory">
<allow user="root"/>
</policy>
</busconfig>
$ dbus-daemon --config-file session.conf --print-address
unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98
$ dbus-daemon --config-file session.conf --print-address
unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98
在另一个终端中,我无法以 root 用户身份连接到此总线:
# python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
>>> from dbus.bus import BusConnection
>>> address = "unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98"
>>> BusConnection(address).list_names()
dbus.Array([dbus.String('org.freedesktop.DBus'), dbus.String(':1.0')], signature=dbus.Signature('s'))
在全局安装 session-local.conf
时,这还应该能够访问系统上的 所有 会话总线:
# cp session-local.conf /etc/dbus-1/session-local.conf
# kill -HUP 1865 # reload config of my users session dbus-daemon
# python3
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
143
并且有效 - 现在 root 可以连接到任何会话总线而无需诉诸 seteuid
。别忘了
# rm /etc/dbus-1/session-local.conf
如果您的 root 用户不需要此功能。
Bluehorn 的回答对我很有帮助。我没有足够的声誉来支持他的回答,但我想我会分享我的解决方案。我才学习 python(仅来自 shell 脚本编写),所以如果这确实是错误的并且恰好在我的系统上工作,请告诉我,哈哈。
这些是我编写的用于监视 cpu 温度和控制 linux 中的风扇速度的守护程序的部分,因此它需要 root 权限。不确定如果 运行 作为普通用户在多个用户登录时它会如何工作。我猜它不会...
import os, pwd
from dbus import SessionBus, Interface
from dbus.bus import BusConnection
# Subclassing dbus.Interface because why not
class Dbus(Interface):
def __init__(self, uid):
method = 'org.freedesktop.Notifications'
path = '/' + method.replace('.', '/')
if os.getuid() == uid:
obj = SessionBus().get_object( method, path )
else:
os.seteuid(uid)
obj = BusConnection( "unix:path=/run/user/" + str(uid) + "/bus" )
obj.get_object( method, path )
super().__init__(obj, method)
# Did this so my notifications would work
# when running as root or non root
class DbusNotificationHandler:
app_icon = r"/path/to/my/apps/icon.png"
name = "MacFanD"
def __init__(self):
loggedIn, users = [ os.getlogin() ], []
for login in pwd.getpwall():
if login.pw_name in loggedIn:
users.append( login.pw_uid )
self.users = []
for i in users:
self.users.append( Dbus(i) )
def notification(self, msg, mtype=None):
if not isinstance(msg, list) or len(msg) < 2:
raise TypeError("Expecting a list of 2 for 'msg' parameter")
hint = {}
if mtype == 'temp':
icon = 'dialog-warning'
hint['urgency'] = 2
db_id = 498237618
timeout = 0
elif mtype == 'warn':
icon = 'dialog-warning'
hint['urgency'] = 2
db_id = 0
timeout = 5000
else:
icon = self.app_icon
hint['urgency'] = 1
db_id = 0
timeout = 5000
for db in self.users:
db.Notify( self.name, db_id, icon, msg[0], msg[1:], [], hint, timeout )
handler = DbusNotificationHandler()
notify = handler.notification
msg = [ "Daemon Started", "Daemon is now running - %s"%os.getpid() ]
notify(msg)
temp = "95 Celsius"
msg = [ "High Temp Warning", "CPU temperature has reached %s"%temp ]
notify(msg, 'warn')