Skip to content

Commit

Permalink
#1631: use a hackish udev program to set the correct permissions on t…
Browse files Browse the repository at this point in the history
…he 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
  • Loading branch information
totaam committed Sep 2, 2017
1 parent 8afe218 commit 7c27942
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 101 deletions.
1 change: 1 addition & 0 deletions debian/xpra.install
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions rpmbuild/xpra.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/*"))
Expand Down
4 changes: 4 additions & 0 deletions src/xpra/os_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
81 changes: 59 additions & 22 deletions src/xpra/scripts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = {
Expand Down
62 changes: 35 additions & 27 deletions src/xpra/server/server_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand All @@ -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
Expand All @@ -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)
Loading

0 comments on commit 7c27942

Please sign in to comment.