从 udev 启动的脚本不再具有 DISPLAY 访问权限



我有一个脚本,当我插入外部驱动器时,它从udev运行。它总是有效。但是从Linux 3.8/Xorg 1.12/Mint 14(兼容Ubuntu 12.10(升级到Linux 3.11/Xorg 1.14/Mint 16(兼容Ubuntu 13.10(后,它不再起作用了。

脚本仍在运行,但需要显示的命令都不起作用。我通过退出 udev 守护程序并手动运行 udevd --debug 以获得详细输出(更多内容见下文(来解决这个问题。

此脚本曾经在 Mint 14/12.10 中工作:

export DISPLAY=:0
UUID=$1
DEV=$2
notify-send -t 700 "mounting $DEV ($UUID)"
gnome-terminal -t "Backing up home..." -x rsync long line of data
zenity --warning --text="Done."

但在铸币厂 16/13.10 中不再如此。如果您想知道可能的解决方案,我逐渐添加了东西,现在看起来像这样:

export DISPLAY=:0.0
xhost +local:
xhost +si:localuser:root
xhost +
DISPLAY=:0.0
export DISPLAY=:0.0
UUID=$1
DEV=$2
notify-send -t 700 "mounting $DEV ($UUID)"
gnome-terminal -t "Backing up home..." -x rsync long line of data
zenity --warning --text="Done." --display=:0.0

但它仍然不起作用。 udevd --debug仍然显示这一点:

'(err) 'No protocol specified'
'(err) ''
'(err) '** (gnome-terminal:24171): WARNING **: Could not open X display'
'(err) 'No protocol specified'
'(err) 'Failed to parse arguments: Cannot open display: '
'(err) 'No protocol specified'
'(err) ''
'(err) '** (zenity:24173): WARNING **: Could not open X display'
'(err) 'No protocol specified'
'(err) ''
'(err) '(zenity:24173): Gtk-WARNING **: cannot open display: :0.0'
'(err) 'No protocol specified'

请注意,任何 bash 逻辑都有效。回显测试变量以>>/tmp/test.log工作。它只是访问不再工作的显示器。

这让我发疯。现在实现这一目标的正确方法是什么?

更新 2013-12-20

因此,在以前的 Ubuntu 中,X命令会自动找到使用用户当前X的方式。

现在,我似乎每次都需要这两样东西:

  • 在使用用户的X上:
    • xhost +si:localuser:root
  • root/udev方面:
    • 使用用户的~/.Xauthority文件将X复制到/root

这"感觉"就像是时光倒流。这仅在我每次以同一用户身份登录时都适用于脚本,因此我可以在脚本执行时从该用户的家中复制.Xauthority文件。

旧的 Ubuntu 用什么"技巧"来自动"神奇"地完成这项工作?

好的,据我所知,我写这个答案是为了尝试澄清 X 服务器的安全模型。我不是这方面的专家,所以我可能弄错了一些(很多?(事情。此外,正如 OP 所指出的那样,不同发行版中的许多内容甚至同一发行版的不同版本都是不同的。

有两种

主要方法可以获得连接到 X 服务器的授权:

  • xhost方式(主机访问(:服务器维护允许连接到服务器的主机、本地用户、组等的列表。
  • xauth方式(基于 Cookie(:服务器有一个随机生成的 cookie 列表,任何显示这些 cookie 之一的人都将被授予访问权限。

现在,分发特定的东西...

当启动系统启动 X 服务器时,通常会传递一个表单为 -auth <filename> 的命令行。此文件包含用于授权的初始 Cookie 列表。它是在使用 xauth 工具运行 X 服务器之前创建的。然后就在 X 服务器之后,登录管理器启动,并指示它从同一文件中读取 cookie,以便它可以连接。

现在,当用户rodrigo登录时,必须授权它连接到服务器。这是由登录管理器完成的,它有两个选项:

  • 它的作用相当于:xhost +si:localuser:rodrigo
  • 它生成另一个cookie,将其添加到服务器并将其传递给用户。可以通过两种方式完成此传递:
    • 它写在文件$HOME/.Xauthority(新用户的家(中。
    • 它被写在其他地方(/var/run/gdm/auth-for-rodrigo-xxxx(,环境变量XAUTHORITY被设置为该文件的名称。

此外,它可以做这两件事。一些登录管理器甚至默认将root用户添加到授权用户列表中(好像xhost +si:localuser:root(。

但请注意,如果您无权连接到 X 服务器,则无法将自己添加到列表中(例如,运行xhost +(。原因和为什么没有钥匙就不能从外面打开房子一样......即使你是根,也是真的!

这是否意味着根用户无法连接到服务器?绝对不行!但是要首先做到这一点,您必须知道如何配置登录的用户以连接到服务器。对于以登录用户身份运行的:

$ xhost

它将显示一条消息以及授权用户、主机或组的列表(如果有(:

access control enabled, only authorized clients can connect
SI:localuser:rodrigo

然后运行:

$ echo $XAUTHORITY

查看授权文件的保存位置。如果它是空的,那么它将是~/.Xauthority .然后:

$ xauth list :0

查看您的授权 Cookie 列表。

现在,如果服务器中有任何cookie,则root用户应该能够连接,使XAUTHORITY环境变量指向正确的cookie文件。请注意,在许多设置中,登录管理器的cookie也被保留在周围。就找吧!

root 访问权限的另一种可能性是修改Xsession文件以添加命令xhost +si:localuser:root并获得永久访问权限。详细信息因使用的特定程序而异,但对于gdm,您只需使用 xhost 命令在/etc/gdm/Init/中添加一个可执行脚本,它将在下次启动时自动运行。

PS:您可以使用 sudo -i 检查您对 X 服务器的 root 访问权限,但请注意,某些sudo配置可能会保留DISPLAYXAUTHORITYHOME变量并修改测试结果。

示例:此脚本应该能够以 root 用户身份将您连接到 X 服务器

export DISPLAY=:0
export XAUTHORITY=`ls /var/run/gdm/auth-for-gdm-*/database`
xrandr #just for show

当然,XAUTHORITY变量的路径将取决于您使用的登录管理器(欢迎员(。您可以使用用户文件(您说它是/home/redsandro/.Xauthority但我不太确定(。或者您可以使用迎宾饼干。要获取迎宾 cookie,您可以使用以下命令:

$ pgrep -a Xorg

在我的系统中,它给出了:

408 /usr/bin/Xorg :0 -background none -verbose -auth /var/run/gdm/auth-for-gdm-gDg3Ij/database -seat seat0 -nolisten tcp vt1

所以我的文件/var/run/gdm/auth-for-gdm-gDg3Ij/database.gDg3Ij是随机的,每次重新启动服务器时都会更改,这就是ls ...技巧的原因。

使用 GDM cookie 而不是用户的好处是它不依赖于登录的用户。它甚至可以在没有用户的情况下工作!

更新:从您的最新评论中,我看到您的 X 服务器命令是:

/usr/bin/X :0 -audit 0 -auth /var/lib/mdm/:0.Xauth -nolisten tcp vt8

因此,有用于启动登录管理器的cookie的名称。如果我是对的,如果您能够读取该文件,它应该一直可用。而且您是 root,因此,以下行应该足以让您以 root 身份访问显示:

export DISPLAY=:0
export XAUTHORITY=/var/lib/mdm/:0.Xauth
zenity --info --text 'Happy New Year'

快速搜索显示以下内容:

X身份验证基于cookie - 秘密的小块随机 只有您和 X 服务器知道的数据...所以,你需要让 其他用户了解您的 cookie 是什么。一种方法是 以下: 在你发出 su 或 sudo 之前(但在 ssh'ed 之后 远程系统(如果您使用的是 SSH(,请求 cookie 为 当前连接到 X 服务器的显示器:

$ xauth list $DISPLAY 你会得到类似的东西

somehost.somedomain:10 mit-magic-cookie-1 4d22408a71a55b41ccd1657d377923ae

然后,在完成 su 之后,告诉新用户 cookie 是什么:

$ xauth add somehost.somedomain:10 MIT-MAGIC-COOKIE-1 4d22408a71a55b41ccd1657d377923ae

(只需将上述"Xauth 列表"的输出复制并粘贴到"Xauth 添加'(就是这样。现在,您应该能够启动任何 X 应用程序。

作为参考,这里是起源 http://www.linuxquestions.org/questions/linux-newbie-8/xlib-connection-to-0-0-refused-by-server-xlib-no-protocol-specified-152556/

这并不漂亮,但我还没有看到任何解决方案。所以这是迄今为止最好的一个。

  • 在使用用户的X上:
    • xhost +si:localuser:root
  • 在根/乌德夫端:
    • 使用用户的~/.Xauthority文件将X复制到/root(*请参阅下面的注释(

现在它起作用了。试试zenity --warning --text=Hooray

这仅在您知道哪个用户将登录到 X 时才有效。因此,仅当计算机由具有单个用户帐户的单个用户使用时,它才可接受。

*( 注
这是值得注意的,因为我尝试了记录在案的xauth merge /home/redsandro/.Xauthority$XAUTHORITY=/home/redsandro/.Xauthority方式。这些记录在案的方法如今什么也没做,即使root有权阅读它。您需要从字面上看整个.Xauthority文件,而不是仅指向它。

较新版本的 Ubuntu 使用不同的显示管理器,因此您必须知道您使用的是哪一个。在 Rodrigo 的帖子中,有一个提示显示了如何使用以下命令发现它:

ls /var/run/gdm/auth-for-gdm-*/database

要检查这一点,请列出/var/run 目录并使用 "pgrep -a Xorg" 命令。在 Ubuntu 16* 中,它使用 sddm,因此您可以使用

ls/var/run/sddm* 导出 XAUTHORITY 变量。

脚本将如下所示:

#!/bin/bash
export DISPLAY=:0
export XAUTHORITY=`ls /var/run/sddm*`
HDMI_STATUS="$(cat /sys/class/drm/card0-HDMI-A-1/status)"
USER="your username"
export XAUTHORITY=/home/$USER/.Xauthority
export DISPLAY=:0
if [ "$HDMI_STATUS" = connected ];
then
sudo -u $USER pactl set-card-profile 0 output:hdmi-stereo+input:analog-stereo
else
sudo -u $USER pactl set-card-profile 0 output:analog-stereo+input:analog-stereo
fi
exit 0

然后运行:

sudo chmod 755 /usr/local/bin/toggle-sound
echo 'ACTION=="change", SUBSYSTEM=="drm", RUN+="/usr/local/bin/toggle-sound"' | sudo tee /etc/udev/rules.d/99-hdmi-sound.rules
sudo udevadm control --reload-rules

我必须在Kali Linux 2016中使用它才能让它工作:

#!/bin/bash
set -x
xhost local:root
export DISPLAY=:0.0
su root -c 'zenity --notification --text="I am a notification!"'

如果直接从 udev 调用脚本不起作用,为什么不启动调用该脚本的 systemd 服务呢?

这是我的解决方案:

首先是 udev 规则,当拔出具有ID_PART_ENTRY_UUID的设备(或分区(时运行 media-storage-unplugged.service

/

etc/udev/rules.d/storage-unplugged.rules:

ACTION=="remove", KERNEL=="sd[a-z][0-9]", ENV{ID_PART_ENTRY_UUID}=="replace-with-your-uuid", SYMLINK+="storage", RUN+="/usr/bin/systemctl --no-block start media-storage-unplugged.service"
/

etc/systemd/system/media-storage-unplugged.service: (service file(

[Unit]
Description=Triggered when storage is unplugged
[Service]
Type=oneshot
ExecStart=/usr/local/bin/storage_unplugged
[Install]
WantedBy=multi-user.target
/

usr/local/bin/storage_unplugged (在这里发挥创意(

#!/bin/bash
notify-send-to-user "storage unplugged"
exit 0
/

usr/local/bin/notify-send-to-user

#!/bin/bash
function ns() {
    #Detect the name of the display in use
    local display=":$(ls /tmp/.X11-unix/* | sed 's#/tmp/.X11-unix/X##' | head -n 1)"
    #Detect the user using such display (NOTE: Didn't work on Arch linux since the "who" command doesn't show which display the user is using)
    #local user=$(who | grep '('$display')' | awk '{print $1}' | head -n 1)
    #Statically assign user:
    local user="user" # Replace with your user
    #Detect the id of the user
    local uid=$(id -u $user)
    sudo -u $user DISPLAY=$display DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus notify-send "$@"
}
ns "$@"

根据您的需求调整此方法:)

最新更新