From 7c27942454d86bb1dfddd45f14067c5dc50d6d2b Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sat, 2 Sep 2017 17:29:19 +0000 Subject: [PATCH] #1631: use a hackish udev program to set the correct permissions on the input device - we hijack the version attribute to pass the uid to udev git-svn-id: https://xpra.org/svn/Xpra/trunk@16777 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- debian/xpra.install | 1 + rpmbuild/xpra.spec | 1 + src/setup.py | 2 +- src/xpra/os_util.py | 4 ++ src/xpra/scripts/server.py | 81 ++++++++++++++++++++++++--------- src/xpra/server/server_util.py | 62 ++++++++++++++----------- src/xpra/x11/vfb_util.py | 78 +++++++++++++------------------ src/xpra/x11/x11_server_base.py | 13 ++++-- 8 files changed, 141 insertions(+), 101 deletions(-) diff --git a/debian/xpra.install b/debian/xpra.install index a9c8a2483c..f91bc5adf1 100644 --- a/debian/xpra.install +++ b/debian/xpra.install @@ -4,6 +4,7 @@ usr/lib/sysusers.d/xpra.conf usr/bin/xpra usr/bin/xpra_launcher usr/bin/xpra_browser +usr/bin/udev_product_version usr/lib/cups/backend/xpraforwarder usr/share/applications/xpra-launcher.desktop usr/share/applications/xpra-browser.desktop diff --git a/rpmbuild/xpra.spec b/rpmbuild/xpra.spec index 10477aeb32..37ba488c69 100644 --- a/rpmbuild/xpra.spec +++ b/rpmbuild/xpra.spec @@ -573,6 +573,7 @@ rm -rf $RPM_BUILD_ROOT %{_datadir}/mime/packages/application-x-xpraconfig.xml %files common-server +%{_bindir}/udev_product_version %{_prefix}/lib/systemd/system/xpra.service %{_prefix}/lib/systemd/system/xpra.socket %{_prefix}/lib/cups/backend/xpraforwarder diff --git a/src/setup.py b/src/setup.py index 96ab91dc6b..ac327c410f 100755 --- a/src/setup.py +++ b/src/setup.py @@ -1425,7 +1425,7 @@ def add_service_exe(script, icon, base_name): #******************************************************************************* else: #OSX and *nix: - scripts += ["scripts/xpra", "scripts/xpra_launcher", "scripts/xpra_browser"] + scripts += ["scripts/xpra", "scripts/xpra_launcher", "scripts/xpra_browser", "scripts/udev_product_version"] add_data_files("share/man/man1", ["man/xpra.1", "man/xpra_launcher.1", "man/xpra_browser.1"]) add_data_files("share/xpra", ["README", "COPYING"]) add_data_files("share/xpra/icons", glob.glob("icons/*")) diff --git a/src/xpra/os_util.py b/src/xpra/os_util.py index 33702121d2..ea26dcd4dc 100644 --- a/src/xpra/os_util.py +++ b/src/xpra/os_util.py @@ -182,6 +182,10 @@ def rel(v): return rel(sys_platform) +def get_rand_chars(l=16, chars=b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"): + import random + return b"".join(chars[random.randint(0, len(chars)-1)] for _ in range(l)) + def get_hex_uuid(): return uuid.uuid4().hex diff --git a/src/xpra/scripts/server.py b/src/xpra/scripts/server.py index dbaad10bd4..6cb1386715 100644 --- a/src/xpra/scripts/server.py +++ b/src/xpra/scripts/server.py @@ -91,6 +91,12 @@ def save_dbus_pid(pid): def get_dbus_pid(): return _get_int("_XPRA_DBUS_PID") +def save_uinput_id(uuid): + _save_str("_XPRA_UINPUT_ID", uuid) + +#def get_uinput_id(): +# return _get_str("_XPRA_UINPUT_ID") + def get_dbus_env(): env = {} for n,load in ( @@ -157,6 +163,25 @@ def display_name_check(display_name): except: pass +def close_gtk_display(): + # Close our display(s) first, so the server dying won't kill us. + # (if gtk has been loaded) + gtk_mod = sys.modules.get("gtk") + if gtk_mod: + for d in gtk_mod.gdk.display_manager_get().list_displays(): + d.close() + +def kill_xvfb(xvfb_pid): + if xvfb_pid: + from xpra.log import Logger + log = Logger("server") + log.info("killing xvfb with pid %s", xvfb_pid) + try: + os.kill(xvfb_pid, signal.SIGTERM) + except OSError as e: + log.info("failed to kill xvfb process with pid %s:", xvfb_pid) + log.info(" %s", e) + def print_DE_warnings(desktop_display, pulseaudio, notifications, dbus_launch): de = os.environ.get("XDG_SESSION_DESKTOP") or os.environ.get("SESSION_DESKTOP") @@ -348,6 +373,7 @@ def show_encoding_help(opts): def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): + from xpra.os_util import strtobytes try: cwd = os.getcwd() except: @@ -675,20 +701,21 @@ def add_udp_socket(socktype, host, iport): os.environ.update(protected_env) log("env=%s", os.environ) - #create devices for vfb if needed: - devices = {} - if start_vfb and opts.input_devices.lower()=="uinput": - devices = create_input_devices(uid) - # Start the Xvfb server first to get the display_name if needed odisplay_name = display_name xvfb = None xvfb_pid = None + uinput_uuid = None if start_vfb: assert not proxying and xauth_data pixel_depth = validate_pixel_depth(opts.pixel_depth) from xpra.x11.vfb_util import start_Xvfb - xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, devices) + from xpra.server.server_util import has_uinput + uinput_uuid = None + if has_uinput() and opts.input_devices.lower() in ("uinput", "auto") and not shadowing: + from xpra.os_util import get_rand_chars + uinput_uuid = get_rand_chars(8) + xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid) for f in cleanups: add_cleanup(f) xvfb_pid = xvfb.pid @@ -698,6 +725,7 @@ def add_udp_socket(socktype, host, iport): os.environ.update(protected_env) if display_name!=odisplay_name and pam: pam.set_items({"XDISPLAY" : display_name}) + if POSIX and not OSX and displayfd>0: from xpra.platform.displayfd import write_displayfd try: @@ -713,23 +741,13 @@ def add_udp_socket(socktype, host, iport): except: pass - close_display = None if not proxying: def close_display(): - # Close our display(s) first, so the server dying won't kill us. - # (if gtk has been loaded) - gtk_mod = sys.modules.get("gtk") - if gtk_mod: - for d in gtk_mod.gdk.display_manager_get().list_displays(): - d.close() - if xvfb_pid: - log.info("killing xvfb with pid %s", xvfb_pid) - try: - os.kill(xvfb_pid, signal.SIGTERM) - except OSError as e: - log.info("failed to kill xvfb process with pid %s:", xvfb_pid) - log.info(" %s", e) + close_gtk_display() + kill_xvfb(xvfb_pid) add_cleanup(close_display) + else: + close_display = None if opts.daemon: def noerr(fn, *args): @@ -758,6 +776,25 @@ def noerr(fn, *args): #xvfb problem: exit now return 1 + #create devices for vfb if needed: + devices = {} + if not start_vfb and not proxying and not shadowing: + #try to find the existing uinput uuid: + #use a subprocess to avoid polluting our current process + #with X11 connections before we get a chance to change uid + from xpra.os_util import get_status_output + cmd = ["xprop", "-display", display_name, "-root", "_XPRA_UINPUT_ID"] + try: + code, out, err = get_status_output(cmd) + except Exception as e: + log("failed to get existing uinput id: %s", e) + else: + log("Popen(%s)=%s", cmd, (code, out, err)) + if code==0 and out.find("=")>0: + uinput_uuid = strtobytes(out.split("=", 1)[1]) + if uinput_uuid: + devices = create_input_devices(uinput_uuid, uid) + if ROOT and (uid!=0 or gid!=0): log("root: switching to uid=%i, gid=%i", uid, gid) setuidgid(uid, gid) @@ -784,7 +821,7 @@ def noerr(fn, *args): from xpra.x11.vfb_util import verify_display_ready if not verify_display_ready(xvfb, display_name, shadowing): return 1 - from xpra.x11.gtk2.gdk_display_util import verify_gdk_display + from xpra.x11.gtk2.gdk_display_util import verify_gdk_display #@Reimport display = verify_gdk_display(display_name) if not display: return 1 @@ -833,6 +870,7 @@ def noerr(fn, *args): save_xvfb_pid(xvfb_pid) if POSIX: + save_uinput_id(uinput_uuid or "") dbus_pid = -1 dbus_env = {} if clobber: @@ -904,7 +942,6 @@ def kill_dbus(): #publish mdns records: if opts.mdns: - from xpra.os_util import strtobytes from xpra.platform.info import get_username from xpra.server.socket_util import mdns_publish mdns_info = { diff --git a/src/xpra/server/server_util.py b/src/xpra/server/server_util.py index 54f3bb5e22..c9b405c151 100644 --- a/src/xpra/server/server_util.py +++ b/src/xpra/server/server_util.py @@ -295,13 +295,26 @@ def get_uinput_device_path(device): log.error(" %", e) return None -def create_uinput_pointer_device(uid, gid): +def has_uinput(): from xpra.log import Logger log = Logger("server") try: import uinput + assert uinput except (ImportError, NameError) as e: - log.error("Error: cannot create uinput devices:") + log.info("cannot access python uinput module:") + log.info(" %s", e) + return False + else: + return True + +def create_uinput_pointer_device(uuid, uid): + from xpra.log import Logger + log = Logger("server") + try: + import uinput + except (ImportError, NameError) as e: + log.error("Error: cannot access python uinput module:") log.error(" %s", e) return None events = ( @@ -320,38 +333,33 @@ def create_uinput_pointer_device(uid, gid): ) BUS_USB = 0x03 #BUS_VIRTUAL = 0x06 - name = "Xpra Virtual Pointer" + VENDOR = 0xffff + PRODUCT = 0x1000 + #our udev_product_version script will use the version attribute to set + #the udev OWNER value + VERSION = uid + name = "Xpra Virtual Pointer %s" % uuid try: - uinput_pointer = uinput.Device(events, name=name, bustype=BUS_USB, vendor=0, product=0, version=0) + uinput_pointer = uinput.Device(events, name=name, bustype=BUS_USB, vendor=VENDOR, product=PRODUCT, version=VERSION) except OSError as e: - log.error("Error: cannot open uinput,") - log.error(" make sure that the kernel module is loaded") - log.error(" and that the /dev/uinput device exists:") - log.error(" %s", e) + log("uinput.Device creation failed", exc_info=True) + if os.getuid()==0: + #running as root, this should work! + log.error("Error: cannot open uinput,") + log.error(" make sure that the kernel module is loaded") + log.error(" and that the /dev/uinput device exists:") + log.error(" %s", e) + else: + log.info("cannot access uinput: %s", e) return None dev_path = get_uinput_device_path(uinput_pointer) if not dev_path: uinput_pointer.destroy() return None - try: - #log("chown%s", (dev_path, uid, gid)) - os.lchown(dev_path, uid, gid) - #udev rules change the device ownership - #FIXME: fix udev or use inotify? (racy) - import time - time.sleep(1) - log("chown%s", (dev_path, uid, gid)) - os.lchown(dev_path, uid, gid) - #os.lchown("/dev/input/mouse2", uid, gid) - except OSError as e: - log.error("Error: failed to change ownership of '%s':", name) - log.error(" at %s:", dev_path) - log.error(" %s", e) - return None return name, uinput_pointer, dev_path -def create_uinput_devices(uid, gid): - d = create_uinput_pointer_device(uid, gid) +def create_uinput_devices(uinput_uuid, uid): + d = create_uinput_pointer_device(uinput_uuid, uid) if not d: return {} name, uinput_pointer, dev_path = d @@ -363,5 +371,5 @@ def create_uinput_devices(uid, gid): } } -def create_input_devices(uid, gid=-1): - return create_uinput_devices(uid, gid) +def create_input_devices(uinput_uuid, uid): + return create_uinput_devices(uinput_uuid, uid) diff --git a/src/xpra/x11/vfb_util.py b/src/xpra/x11/vfb_util.py index 6e5d44a177..246584034e 100644 --- a/src/xpra/x11/vfb_util.py +++ b/src/xpra/x11/vfb_util.py @@ -23,21 +23,11 @@ assert len(DEFAULT_DESKTOP_VFB_RESOLUTION)==2 -XORG_DEVICE_OPTIONS = { - "pointer" : """ - MatchIsPointer "True" - Driver "libinput" - Option "AccelProfile" "flat" -""", - "keyboard" : 'MatchIsKeyboard "True"', - } - - -def create_xorg_device_configs(xorg_conf_dir, devices, uid, gid): +def create_xorg_device_configs(xorg_conf_dir, device_uuid, uid, gid): from xpra.log import Logger log = Logger("server", "x11") cleanups = [] - if not devices: + if not device_uuid: return cleanups def makedir(dirname): @@ -62,40 +52,34 @@ def cleanup_dir(): for d in dirs: makedir(d) - #create individual device files: + #create individual device files, + #only pointer for now: i = 0 - for dev_type, devdef in devices.items(): - #ie: - #name = "pointer" - #devdef = {"uinput" : uninput.Device, "device" : "/dev/input20" } - match_type = XORG_DEVICE_OPTIONS.get(dev_type) - uinput = devdef.get("uinput") - device = devdef.get("device") - name = devdef.get("name") - if match_type and uinput and device and name: - conf_file = os.path.join(xorg_conf_dir, "%02i-%s.conf" % (i, dev_type)) - with open(conf_file, "wb") as f: - f.write(""" -Section "InputClass" + dev_type = "pointer" + name = "Xpra Virtual Pointer %s" % device_uuid + conf_file = os.path.join(xorg_conf_dir, "%02i-%s.conf" % (i, dev_type)) + with open(conf_file, "wb") as f: + f.write(b"""Section "InputClass" Identifier "xpra-virtual-%s" MatchProduct "%s" - MatchDevicePath "%s" + MatchUSBID "ffff:ffff" + MatchIsPointer "True" + Driver "libinput" + Option "AccelProfile" "flat" Option "Ignore" "False" -%s EndSection -""" % (dev_type, name, device, match_type)) - os.fchown(f.fileno(), uid, gid) - #Option "AccelerationProfile" "-1" - #Option "AccelerationScheme" "none" - #Option "AccelSpeed" "-1" - def cleanup_conf_file(): - log("cleanup_conf_file: %s", conf_file) - os.unlink(conf_file) - cleanups.insert(0, cleanup_conf_file) +""" % (dev_type, name)) + os.fchown(f.fileno(), uid, gid) + #Option "AccelerationProfile" "-1" + #Option "AccelerationScheme" "none" + #Option "AccelSpeed" "-1" + def cleanup_conf_file(): + log("cleanup_conf_file: %s", conf_file) + os.unlink(conf_file) + cleanups.insert(0, cleanup_conf_file) return cleanups - -def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, devices={}): +def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid=None): if not POSIX: raise InitException("starting an Xvfb is not supported on %s" % os.name) if not xvfb_str: @@ -128,16 +112,11 @@ def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xau "PID" : os.getpid(), "HOME" : HOME, "DISPLAY" : display_name, - "XDG_RUNTIME_DIR" : os.environ.get("XDG_RUNTIME_DIR", _get_runtime_dir()), - "XPRA_LOG_DIR" : os.environ.get("XPRA_LOG_DIR"), } def pathexpand(s): return shellsub(s, subs) - - #create uinput device definition files: - #(we are assuming that Xorg is configured to use this path..) - xorg_conf_dir = pathexpand(get_Xdummy_confdir()) - cleanups = create_xorg_device_configs(xorg_conf_dir, devices, uid, gid) + subs["XDG_RUNTIME_DIR"] = pathexpand(os.environ.get("XDG_RUNTIME_DIR", _get_runtime_dir())) + subs["XPRA_LOG_DIR"] = pathexpand(os.environ.get("XPRA_LOG_DIR")) #identify logfile argument if it exists, #as we may have to rename it, or create the directory for it: @@ -172,7 +151,8 @@ def pathexpand(s): if not xvfb_cmd: raise InitException("cannot start Xvfb, the command definition is missing!") - if devices: + if uinput_uuid: + #use uinput: #identify -config xorg.conf argument and replace it with the uinput one: try: config_argindex = xvfb_cmd.index("-config") @@ -184,6 +164,10 @@ def pathexpand(s): xorg_conf = xorg_conf.replace("xorg.conf", "xorg-uinput.conf") if os.path.exists(xorg_conf): xvfb_cmd[config_argindex+1] = xorg_conf + #create uinput device definition files: + #(we have to assume that Xorg is configured to use this path..) + xorg_conf_dir = pathexpand(get_Xdummy_confdir()) + cleanups = create_xorg_device_configs(xorg_conf_dir, uinput_uuid, uid, gid) xvfb_executable = xvfb_cmd[0] if (xvfb_executable.endswith("Xorg") or xvfb_executable.endswith("Xdummy")) and pixel_depth>0: diff --git a/src/xpra/x11/x11_server_base.py b/src/xpra/x11/x11_server_base.py index 00fa25acdf..a1129de328 100644 --- a/src/xpra/x11/x11_server_base.py +++ b/src/xpra/x11/x11_server_base.py @@ -178,6 +178,7 @@ def last_client_exited(self): X11ServerCore.last_client_exited(self) def init_virtual_devices(self, devices): + #(this runs in the main thread - before the main loop starts) #for the time being, we only use the pointer if there is one: pointer = devices.get("pointer") if pointer: @@ -186,19 +187,23 @@ def init_virtual_devices(self, devices): #name = pointer.get("name") device_path = pointer.get("device") if uinput_device: + xtest = XTestPointerDevice() + ox, oy = 100, 100 + xtest.move_pointer(0, ox, oy) self.pointer_device = UInputPointerDevice(uinput_device, device_path) - nx, ny = 100, 100 + nx, ny = 200, 200 self.pointer_device.move_pointer(0, nx, ny) def verify_uinput_moved(): pos = None #@UnusedVariable with xswallow: pos = X11Keyboard.query_pointer() mouselog("X11Keyboard.query_pointer=%s", pos) - if pos!=(nx, ny): + if pos==(ox, oy): mouselog.warn("Warning: %s failed verification", self.pointer_device) + mouselog.warn(" expected pointer at %s, now at %s", (nx, ny), pos) mouselog.warn(" usign XTest fallback") - self.pointer_device = XTestPointerDevice() - self.timeout_add(100, verify_uinput_moved) + self.pointer_device = xtest + self.timeout_add(1000, verify_uinput_moved) try: mouselog.info("pointer device emulation using %s", str(self.pointer_device).replace("PointerDevice", "")) except Exception as e: