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

14
我有一个脚本,当我插入我的外部驱动器时从udev运行。它一直工作正常。但是,在从Linux 3.8 / Xorg 1.12 / Mint 14Ubuntu 12.10兼容)升级到Linux 3.11 / Xorg 1.14 / Mint 16Ubuntu 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."

但在 Mint 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逻辑都可以工作。将测试vars回显到>>/tmp/test.log可以正常工作。现在只是访问显示器不再起作用了。
这让我发疯了。现在实现这个的正确方法是什么?
更新2013-12-20
因此,在以前的Ubuntu中,X命令会自动找到当前用户的X
现在,每次似乎都需要这两件事:
在使用用户的X上:
  • xhost +si:localuser:root
root/udev端:
  • 将使用用户的~/.Xauthority文件复制到/root
这感觉像是一个时间倒退。这只有在我每次都登录为相同的用户时才能脚本化工作,所以当脚本执行时,我可以从该用户的home中复制.Xauthority文件。
旧版Ubuntu使用了什么“技巧”使这个自动完成?

你是否从一个正常的X会话中检查了XAUTHORITY环境变量?如果我没记错,Ubuntu已经更改了.Xauthority文件的默认路径。 - rodrigo
它仍然在~中(并且相应地设置了ENV)- 我相信它一直都在~中。 - Redsandro
但是,如果脚本在以root身份运行的同时,桌面会话由另一个用户启动,则“~”将不同。但是请确保您已经检查过了! - rodrigo
你可能想在http://serverfault.com上尝试这个问题,那里可能会得到更好的结果。 - Donovan
是的,至少我认为我已经解决了这个问题。显然,root曾经有访问权限(正如以前可以在没有任何额外授权选项的情况下工作的脚本所示)。但现在似乎不再有了,所以应该通过xhost +来解决。 - Redsandro
在脚本中使用 xhost + 命令无法生效,因为需要连接到服务器发送 开启对所有人的访问权限 的命令。但是您无法连接到服务器,因为还未被允许。您需要从已登录的会话中执行 xhost + 命令。 - rodrigo
6个回答

18

我尝试解释X服务器的安全模型,以我所知为基础。我不是专家,所以可能有些内容存在错误,而且不同的发行版甚至同一发行版的不同版本都存在差别,正如 OP 所指出的。

连接 X 服务器的授权方式主要有两种:

  • xhost 方式(主机访问):服务器维护了一个列表,包含允许连接到服务器的主机、本地用户、组等信息。
  • xauth 方式(基于 Cookie):服务器有一个随机生成的 cookie 列表,任何展示其中一项 cookie 的人都将被授权。

现在说说与发行版相关的内容...

当启动系统启动 X 服务器时,通常会传递一个形如 -auth <文件名> 的命令行参数。此文件包含用于授权的初始 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 +)。原因与您没有钥匙时无法从外部打开房门的原因相同... 即使您是 root 用户也是如此!

这是否意味着 root 用户无法连接到服务器?当然不是!但首先您需要知道已登录用户如何配置连接服务器。为此,请作为已登录用户运行:

$ 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,您只需在/etc/gdm/Init/中添加一个可执行脚本,其中包含xhost命令,并且它将在下次启动时自动运行。

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

示例:此脚本应能够将您连接到X服务器作为root用户

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

当然,XAUTHORITY 变量的路径取决于您使用的登录管理器(欢迎界面)。您可以使用用户文件(您说它在 /home/redsandro/.Xauthority,但我不太确定)。或者,您可以使用欢迎界面 cookie。要获取欢迎界面 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的事情所付出的努力,但是在尝试这个新的想法半个小时后,我仍然无法从不同的tts(为了测试目的)根据X进行操作。因此,尽管这是一个很好的总结,但这不是我正在寻找的解决方案。我可能只需要一个或两个特定的简单命令在我的根脚本中。我需要它们确切无比,因为我自己已经失败了两次,没能找到它们。 - Redsandro
1
@Redsandro:好的,我们一步一步来。首先,您确定了您的X服务器使用哪种身份验证方法吗?如果是xauth cookie,您确定了该文件在哪里吗?为了确保万无一失,请注意可能会有旧的.Xauthority文件-删除它并尝试从用户会话打开X程序:如果失败了,那就是它! - rodrigo
@Redsandro:拜托!这个文件每次你登录时都会重新生成。而且你可以将其重命名以增加安全性。请查看我添加的脚本,了解如何以root身份连接到X。 - rodrigo
1
@Redsandro:这不是信任的问题……问题在于我非常有信心这些工具能像我想的那样工作。而你的工具也必须同样有效。没有访问你的机器,我很难知道发生了什么,而且你没有回答我的一些问题,所以我只能猜测(而且我不能使用 MDM,因为它不适用于我的操作系统)。最重要的未回答问题是:X 服务器的命令行是什么(ps -ef | grep Xorg)? - rodrigo
1
@Redsandro:别担心,我们这些天都很慢。是的,X服务器有很多名称。请查看我的更新答案。 - rodrigo
显示剩余6条评论

1

快速搜索结果如下:

X身份验证基于cookie——只有您和X服务器知道的随机数据片段……因此,您需要让其他用户知道您的cookie。一种方法是在发出su或sudo之前(但在使用ssh远程系统后)请求连接到您的X服务器的当前DISPLAY的cookie:

$ xauth list $ DISPLAY 您会得到类似以下内容:

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

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

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

(只需将上述“xauth list”的输出复制并粘贴到“xauth add”中即可)就这样。现在,您应该能够启动任何X应用程序。

供参考,这是原文 http://www.linuxquestions.org/questions/linux-newbie-8/xlib-connection-to-0-0-refused-by-server-xlib-no-protocol-specified-152556/


这个问题应该使用 xhost + 来解决,对吗?我尝试过了(见第二个脚本)。此外,我认为基于2013年之前的信息的任何解决方案都是不相关的,因为那些约定构建的脚本已经无法工作了。 - Redsandro
这并不一定是真的,但我猜测他们增加了安全方面的考虑,以便 xhost + 不再支持 root。 - Donovan
2
@Redsandro xhost 无法解决 xauth 认证问题。它们是两种完全不同的机制。xhost 是较旧、较简单且安全性较低的机制。xauth 是较新、(有点)更安全且稍微难以正确使用的机制... - twalberg
哦,所以任何xhost(相关)命令都已过时了吗?(用谷歌真的很难获得最新信息,有成百上千个含糊不清的问题和类似但不完全正确的解决方案提到了xhostxauth。) - Redsandro
@Redsandro 我不确定 xhost 是否真的已经被宣布过时... 但是根据我的经验,大多数发行版和其他 Unix 系统现在都将 xauth 配置为主要方法... 但两者仍然都包含且可用。 - twalberg

1
这不太美观,但我还没有看到任何解决方案。所以这是目前为止最好的一个。
在X使用用户上: - xhost +si:localuser:root
在root/udev侧: - 将X使用用户的~/.Xauthority文件复制到/root (*见下面的注释)
现在它可以工作了。尝试zenity --warning --text=Hooray
只有当您知道哪个用户将登录X时才有效。因此,仅当计算机由单个用户使用单个用户帐户时才可接受。
*) 注释 这很值得注意,因为我尝试了文档中的方法xauth merge /home/redsandro/.Xauthority和$XAUTHORITY=/home/redsandro/.Xauthority。即使root有读取权限,这些文档方法现在也根本无法执行任何操作。您需要整个.Xauthority文件而不仅仅是指向它。

这根本不是真的。我有一个最新的Xorg服务器(1.14),它完全按照文档工作。我猜你的用户有一个过时无用的~/.Xauthority文件,而真正的文件在其他地方。例如,在我的系统中,以root身份运行xauth merge /run/gdm/auth-for-*/*将使我访问X服务器,无论哪个用户登录。/run/gdm/auth-for-gdm-*/*也可以工作,因为gdm总是在那里,但这样更有趣。 - rodrigo
你(和其他人)不断提到真正的.Xauthority在别处,但是在哪里?如果它过期了,在中的那个是否有效? - Redsandro
这就是“简单真理”,它“简单地起作用”。 我不知道为什么。 这就是我发起50美元的悬赏的原因。 - Redsandro
我认为你混淆了解决方案。如果您作为已登录用户执行 xhost +si:localuser:root,那么无论 XAUTHORITY 变量和文件如何,root 都将具有访问权限。在 Ubuntu 中使用的 GDM(您是否在使用?)不再将 .Xauthority 保存在 ~ 中,而是保存在 /var/run/gdm/auth-for-* 中。 - rodrigo
我并不是很了解技术细节(这也是我提问的原因),但事实上(最重要的一点)是这个解决方案确实有效。我更喜欢旧方法,它适用于任何已登录用户而不是硬编码的方法,但我还没有看到(或找到)任何相关内容。 - Redsandro
你可能对于 xhost 是正确的,解决方案可以是其中一个,也不一定需要两个都要。 - Redsandro

1
新版本的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

0

我不得不在Kali Linux 2016中使用它才能使其工作:

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

0
如果直接从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:(服务文件)

[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 "$@"

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


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