def get_challenge(self): if self.salt is not None: log.error("challenge already sent!") return None self.salt = get_hex_uuid()+get_hex_uuid() #we need the raw password, so tell the client to use "xor": return self.salt, "xor"
def get_challenge(self): if self.salt is not None: log.error("challenge already sent!") return None self.salt = get_hex_uuid()+get_hex_uuid() #this authenticator can use the safer "hmac" digest: return self.salt, "hmac"
def _process_challenge(self, packet): authlog("processing challenge: %s", packet[1:]) def warn_server_and_exit(code, message, server_message="authentication failed"): authlog.error("Error: authentication failed:") authlog.error(" %s", message) self.disconnect_and_quit(code, server_message) if not self.password_file and not os.environ.get('XPRA_PASSWORD'): warn_server_and_exit(EXIT_PASSWORD_REQUIRED, "this server requires authentication, please provide a password", "no password available") return password = self.load_password() if not password: warn_server_and_exit(EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file, "no password available") return salt = packet[1] if self.encryption: assert len(packet)>=3, "challenge does not contain encryption details to use for the response" server_cipher = typedict(packet[2]) key = self.get_encryption_key() if key is None: warn_server_and_exit(EXIT_ENCRYPTION, "the server does not use any encryption", "client requires encryption") return if not self.set_server_encryption(server_cipher, key): return #all server versions support a client salt, #they also tell us which digest to use: digest = packet[3] client_salt = get_hex_uuid()+get_hex_uuid() #TODO: use some key stretching algorigthm? (meh) try: from xpra.codecs.xor.cyxor import xor_str #@UnresolvedImport salt = xor_str(salt, client_salt) except: salt = xor(salt, client_salt) if digest==b"hmac": import hmac, hashlib def s(v): try: return v.encode() except: return str(v) password = s(password) salt = s(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() elif digest==b"xor": #don't send XORed password unencrypted: if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS: warn_server_and_exit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest, "invalid digest") return challenge_response = xor(password, salt) else: warn_server_and_exit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest, "invalid digest") return if digest: authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), challenge_response) self.password_sent = True self.remove_packet_handlers("challenge") self.send_hello(challenge_response, client_salt)
def _process_challenge(self, packet): log("processing challenge: %s", packet[1:]) if not self.password_file: self.warn_and_quit( EXIT_PASSWORD_REQUIRED, "server requires authentication, please provide a password") return password = self.load_password() if not password: self.warn_and_quit( EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file) return salt = packet[1] if self.encryption: assert len( packet ) >= 3, "challenge does not contain encryption details to use for the response" server_cipher = packet[2] key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return if not self.set_server_encryption(server_cipher, key): return digest = "hmac" client_can_salt = len(packet) >= 4 client_salt = None if client_can_salt: #server supports client salt, and tells us which digest to use: digest = packet[3] client_salt = get_hex_uuid() + get_hex_uuid() #TODO: use some key stretching algorigthm? (meh) salt = xor(salt, client_salt) if digest == "hmac": import hmac challenge_response = hmac.HMAC(password, salt).hexdigest() elif digest == "xor": #don't send XORed password unencrypted: if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS: self.warn_and_quit( EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest) return challenge_response = xor(password, salt) else: self.warn_and_quit( EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest) return if digest: log("%s(%s, %s)=%s", digest, password, salt, challenge_response) self.password_sent = True self.send_hello(challenge_response, client_salt)
def new_cipher_caps(proto, cipher, encryption_key): iv = get_hex_uuid()[:16] key_salt = get_hex_uuid() + get_hex_uuid() iterations = 1000 proto.set_cipher_in(cipher, iv, encryption_key, key_salt, iterations) return { "cipher": cipher, "cipher.iv": iv, "cipher.key_salt": key_salt, "cipher.key_stretch_iterations": iterations }
def new_cipher_caps(proto, cipher, encryption_key): iv = get_hex_uuid()[:16] key_salt = get_hex_uuid()+get_hex_uuid() iterations = 1000 proto.set_cipher_in(cipher, iv, encryption_key, key_salt, iterations) return { "cipher" : cipher, "cipher.iv" : iv, "cipher.key_salt" : key_salt, "cipher.key_stretch_iterations" : iterations }
def make_hello_base(self): capabilities = get_network_caps() capabilities.update({ "version": local_version, "encoding.generic": True, "namespace": True, "file-transfer": self.file_transfer, "file-size-limit": self.file_size_limit, "printing": self.printing, "hostname": socket.gethostname(), "uuid": self.uuid, "username": self.username, "name": get_name(), "client_type": self.client_type(), "python.version": sys.version_info[:3], "compression_level": self.compression_level, }) if self.display: capabilities["display"] = self.display def up(prefix, d): updict(capabilities, prefix, d) up("platform", get_platform_info()) up("build", get_version_info()) mid = get_machine_id() if mid: capabilities["machine_id"] = mid if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS iv = get_hex_uuid()[:16] key_salt = get_hex_uuid() + get_hex_uuid() iterations = 1000 capabilities.update({ "cipher": self.encryption, "cipher.iv": iv, "cipher.key_salt": key_salt, "cipher.key_stretch_iterations": iterations, }) key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations) log("encryption capabilities: %s", [(k, v) for k, v in capabilities.items() if k.startswith("cipher")]) return capabilities
def _process_challenge(self, packet): log("processing challenge: %s", packet[1:]) if not self.password_file and not os.environ.get('XPRA_PASSWORD'): self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requires authentication, please provide a password") return password = self.load_password() if not password: self.warn_and_quit(EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file) return salt = packet[1] if self.encryption: assert len(packet)>=3, "challenge does not contain encryption details to use for the response" server_cipher = packet[2] key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return if not self.set_server_encryption(server_cipher, key): return digest = "hmac" client_can_salt = len(packet)>=4 client_salt = None if client_can_salt: #server supports client salt, and tells us which digest to use: digest = packet[3] client_salt = get_hex_uuid()+get_hex_uuid() #TODO: use some key stretching algorigthm? (meh) salt = xor(salt, client_salt) if digest=="hmac": import hmac challenge_response = hmac.HMAC(password, salt).hexdigest() elif digest=="xor": #don't send XORed password unencrypted: if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS: self.warn_and_quit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest) return challenge_response = xor(password, salt) else: self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest) return if digest: log("%s(%s, %s)=%s", digest, password, salt, challenge_response) self.password_sent = True for d in (self._packet_handlers, self._ui_packet_handlers): try: del d["challenge"] except: pass self.send_hello(challenge_response, client_salt)
def __init__(self, session_options, video_encoder_modules, pings, disp_desc, cipher, encryption_key, caps): self.session_options = session_options self.video_encoder_modules = video_encoder_modules self.pings = pings self.disp_desc = disp_desc self.cipher = cipher self.encryption_key = encryption_key self.caps = caps log("ProxyInstance%s", (session_options, video_encoder_modules, disp_desc, cipher, encryption_key, "%s: %s.." % (type(caps), ellipsizer(caps)))) self.uuid = get_hex_uuid() self.client_protocol = None self.server_protocol = None #ping handling: self.client_last_ping = 0 self.server_last_ping = 0 self.client_last_ping_echo = 0 self.server_last_ping_echo = 0 self.client_last_ping_latency = 0 self.server_last_ping_latency = 0 self.client_ping_timer = None self.server_ping_timer = None self.client_challenge_packet = None self.exit = False self.lost_windows = None self.encode_queue = None #holds draw packets to encode self.encode_thread = None self.video_encoding_defs = None self.video_encoders = None self.video_encoders_last_used_time = None self.video_encoder_types = None self.video_helper = None
def make_hello(self, challenge_response=None): capabilities = {} add_version_info(capabilities) capabilities["python.version"] = sys.version_info[:3] if challenge_response: assert self.password capabilities["challenge_response"] = challenge_response if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS capabilities["cipher"] = self.encryption iv = get_hex_uuid()[:16] capabilities["cipher.iv"] = iv key_salt = get_hex_uuid() capabilities["cipher.key_salt"] = key_salt iterations = 1000 capabilities["cipher.key_stretch_iterations"] = iterations self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations) log("encryption capabilities: %s", [(k, v) for k, v in capabilities.items() if k.startswith("cipher")]) capabilities["platform"] = sys.platform capabilities["platform.release"] = python_platform.release() capabilities["platform.machine"] = python_platform.machine() capabilities["platform.processor"] = python_platform.processor() capabilities["client_type"] = self.client_type() capabilities["namespace"] = True capabilities["raw_packets"] = True capabilities["chunked_compression"] = True capabilities["bencode"] = True capabilities["rencode"] = has_rencode if has_rencode: capabilities["rencode.version"] = rencode_version capabilities["hostname"] = socket.gethostname() capabilities["uuid"] = self.uuid try: from xpra.platform.info import get_username, get_name capabilities["username"] = get_username() capabilities["name"] = get_name() except: log.error("failed to get username/name", exc_info=True) capabilities["randr_notify"] = False #only client.py cares about this capabilities["windows"] = False #only client.py cares about this if self._reverse_aliases: capabilities["aliases"] = self._reverse_aliases return capabilities
def make_hello_base(self): capabilities = get_network_caps() capabilities.update({ "version" : local_version, "encoding.generic" : True, "namespace" : True, "file-transfer" : self.file_transfer, "file-size-limit" : self.file_size_limit, "printing" : self.printing, "hostname" : socket.gethostname(), "uuid" : self.uuid, "username" : self.username, "name" : get_name(), "client_type" : self.client_type(), "python.version" : sys.version_info[:3], "compression_level" : self.compression_level, }) if self.display: capabilities["display"] = self.display def up(prefix, d): updict(capabilities, prefix, d) up("platform", get_platform_info()) up("build", get_version_info()) mid = get_machine_id() if mid: capabilities["machine_id"] = mid if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS iv = get_hex_uuid()[:16] key_salt = get_hex_uuid()+get_hex_uuid() iterations = 1000 capabilities.update({ "cipher" : self.encryption, "cipher.iv" : iv, "cipher.key_salt" : key_salt, "cipher.key_stretch_iterations": iterations, }) key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations) log("encryption capabilities: %s", [(k,v) for k,v in capabilities.items() if k.startswith("cipher")]) return capabilities
def test_file(self): from xpra.os_util import get_hex_uuid password = get_hex_uuid() f = self._temp_file(strtobytes(password)) self._test_auth("file", "", EXIT_PASSWORD_REQUIRED) self._test_auth("file:filename=%s" % f.name, "", EXIT_PASSWORD_REQUIRED) self._test_auth("file:filename=%s" % f.name, "", EXIT_OK, password) self._test_auth("file:filename=%s" % f.name, "", EXIT_FAILURE, password+"A") f.close()
def test_file(self): from xpra.os_util import get_hex_uuid password = get_hex_uuid() f = self._temp_file(password) self._test_auth("file", "", EXIT_PASSWORD_REQUIRED) self._test_auth("file:filename=%s" % f.name, "", EXIT_PASSWORD_REQUIRED) self._test_auth("file:filename=%s" % f.name, "", EXIT_OK, password) self._test_auth("file:filename=%s" % f.name, "", EXIT_FAILURE, password+"A") f.close()
def make_hello(self, challenge_response=None): capabilities = {} add_version_info(capabilities) capabilities["python.version"] = sys.version_info[:3] if challenge_response: assert self.password capabilities["challenge_response"] = challenge_response if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS capabilities["cipher"] = self.encryption iv = get_hex_uuid()[:16] capabilities["cipher.iv"] = iv key_salt = get_hex_uuid() capabilities["cipher.key_salt"] = key_salt iterations = 1000 capabilities["cipher.key_stretch_iterations"] = iterations self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations) log("encryption capabilities: %s", [(k,v) for k,v in capabilities.items() if k.startswith("cipher")]) capabilities["platform"] = sys.platform capabilities["platform.release"] = python_platform.release() capabilities["platform.machine"] = python_platform.machine() capabilities["platform.processor"] = python_platform.processor() capabilities["client_type"] = self.client_type() capabilities["namespace"] = True capabilities["raw_packets"] = True capabilities["chunked_compression"] = True capabilities["bencode"] = True capabilities["rencode"] = has_rencode if has_rencode: capabilities["rencode.version"] = rencode_version capabilities["hostname"] = socket.gethostname() capabilities["uuid"] = self.uuid try: from xpra.platform.info import get_username, get_name capabilities["username"] = get_username() capabilities["name"] = get_name() except: log.error("failed to get username/name", exc_info=True) capabilities["randr_notify"] = False #only client.py cares about this capabilities["windows"] = False #only client.py cares about this if self._reverse_aliases: capabilities["aliases"] = self._reverse_aliases return capabilities
def make_hello(self): self.value = get_hex_uuid() capabilities = GObjectXpraClient.make_hello(self) capabilities.update({ "connect_test_request": self.value, #older servers don't know about connect-test, #pretend that we're interested in info: "info_request": True, "info-namespace": True, }) return capabilities
def do_run_client(self, client_display, *args): from xpra.x11.vfb_util import xauth_add xauth_data = get_hex_uuid() xauth_add(self.default_env["XAUTHORITY"], client_display, xauth_data, os.getuid(), os.getgid()) env = self.get_run_env() env["DISPLAY"] = client_display global uq env["XPRA_LOG_PREFIX"] = "client %i: " % uq uq += 1 log("starting test client on Xvfb %s", client_display) return self.run_xpra(["attach"] + list(args), env)
def test_multifile(self): from xpra.platform.info import get_username username = get_username() from xpra.os_util import get_hex_uuid password = get_hex_uuid() displays = "" data = "%s|%s|%i|%i|%s||" % (username, password, os.getuid(), os.getgid(), displays) f = self._temp_file(data) self._test_auth("multifile", "", EXIT_PASSWORD_REQUIRED) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_PASSWORD_REQUIRED) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_OK, password) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_FAILURE, password+"A") f.close()
def do_run_client(self, client_display, *args): from xpra.x11.vfb_util import xauth_add xauth_data = get_hex_uuid() xauthority = self.default_env.get("XAUTHORITY", osexpand("~/.Xauthority")) xauth_add(xauthority, client_display, xauth_data, os.getuid(), os.getgid()) env = self.get_run_env() env["DISPLAY"] = client_display env["XPRA_LOG_PREFIX"] = "client %i: " % X11ClientTestUtil.uq X11ClientTestUtil.uq += 1 log("starting test client on Xvfb %s", client_display) return self.run_xpra(["attach"] + list(args), env=env)
def test_multifile(self): from xpra.platform.info import get_username username = get_username() from xpra.os_util import get_hex_uuid password = get_hex_uuid() displays = "" data = "%s|%s|%i|%i|%s||" % (username, password, os.getuid(), os.getgid(), displays) f = self._temp_file(strtobytes(data)) self._test_auth("multifile", "", EXIT_PASSWORD_REQUIRED) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_PASSWORD_REQUIRED) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_OK, password) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_FAILURE, password+"A") f.close()
def __init__(self, *args, **kwargs): CommandConnectClient.__init__(self, *args) self.value = get_hex_uuid() self.hello_extra.update({ "connect_test_request": self.value, #tells proxy servers we don't want to connect to the real / new instance: "connect": False, #older servers don't know about connect-test, #pretend that we're interested in info: "info_request": True, "info-namespace": True, }) self.hello_extra.update(kwargs)
def test_networkstate(self): opts = AdHocStruct() opts.pings = True opts.bandwidth_limit = 0 opts.bandwidth_detection = True self._test_mixin_class(NetworkState, opts, {"start_time" : time.time()}) x = self.mixin if x.server_start_time<x.start_time: raise Exception("invalid time: server=%s vs start=%s" % (x.server_start_time, x.start_time)) x.uuid = get_hex_uuid() x.send_info_request() packet = ["info-response", {"foo" : "bar"}] self.handle_packet(packet) assert x.server_last_info.get("foo")=="bar"
def copy_and_verify(self, display1, display2, synced=True, wait=1, selection="clipboard"): log("copy_and_verify%s", (display1, display2, synced, wait, selection)) value = get_hex_uuid() self.set_clipboard_value(display1, value) #wait for synchronization to occur: time.sleep(wait) new_value = self.get_clipboard_value(display2) if synced: assert new_value==value, "clipboard contents for %s do not match, expected '%s' but got '%s'" % (selection, value, new_value) else: assert new_value!=value, "clipboard contents for %s match but synchronization was not expected: value='%s'" % (selection, value) if SANITY_CHECKS and display2!=display1: #verify that the value has not changed on the original display: new_value = self.get_clipboard_value(display1) return value
def copy_and_verify(self, display1, display2, synced=True, wait=1, selection="clipboard"): log("copy_and_verify%s", (display1, display2, synced, wait, selection)) value = get_hex_uuid() self.set_clipboard_value(display1, value) #wait for synchronization to occur: time.sleep(wait) new_value = self.get_clipboard_value(display2) if synced: assert new_value==value, "clipboard contents do not match, expected '%s' but got '%s'" % (value, new_value) else: assert new_value!=value, "clipboard contents match but synchronization was not expected: value='%s'" % value if SANITY_CHECKS and display2!=display1: #verify that the value has not changed on the original display: new_value = self.get_clipboard_value(display1) return value
def test_networkstate(self): x = NetworkState() fake_protocol = AdHocStruct() fake_protocol.get_info = lambda: {} x._protocol = fake_protocol opts = AdHocStruct() opts.pings = True opts.bandwidth_limit = 0 opts.bandwidth_detection = True x.init(opts) assert x.get_caps() is not None x.server_capabilities = typedict({"start_time": time.time()}) x.parse_server_capabilities() assert x.server_start_time >= x.start_time, "server_start_time=%s vs start_time=%s" % ( x.server_start_time, x.start_time) x.uuid = get_hex_uuid() x.send_info_request() packet = ["info-response", {"foo": "bar"}] x._process_info_response(packet) assert x.server_last_info.get("foo") == "bar"
def test_file(self): def genfiledata(_a): password = uuid.uuid4().hex return password, password self._test_file_auth("file", genfiledata) #no digest -> no challenge a = self._init_auth("file", filename="foo") assert a.requires_challenge() try: a.get_challenge(["not-a-valid-digest"]) except ValueError: pass a.password_filename = "./this-path-should-not-exist" assert a.load_password_file() is None assert a.stat_password_filetime() == 0 #inaccessible: if POSIX: filename = "./test-file-auth-%s-%s" % (get_hex_uuid(), os.getpid()) with open(filename, 'wb') as f: os.fchmod(f.fileno(), 0o200) #write-only a.password_filename = filename a.load_password_file()
def start_Xvfb(xvfb_str, display_name): # We need to set up a new server environment xauthority = os.environ.get("XAUTHORITY", os.path.expanduser("~/.Xauthority")) if not os.path.exists(xauthority): try: open(xauthority, 'wa').close() except Exception as e: #trying to continue anyway! sys.stderr.write("Error trying to create XAUTHORITY file %s: %s\n" % (xauthority, e)) if not xvfb_str: raise InitException("the 'xvfb' command is not defined") #identify logfile argument if it exists and if we may have to rename it: xvfb_cmd = xvfb_str.split() if '-logfile' in xvfb_cmd and display_name[0]=='S': xvfb_cmd = xvfb_str.split() logfile_argindex = xvfb_cmd.index('-logfile') + 1 assert logfile_argindex<len(xvfb_cmd), "invalid xvfb command string: -logfile should not be last" xorg_log_file = xvfb_cmd[logfile_argindex] else: xorg_log_file = None #apply string substitutions: subs = {"XAUTHORITY" : xauthority, "USER" : os.environ.get("USER", "unknown-user"), "HOME" : os.environ.get("HOME", os.getcwd()), "DISPLAY" : display_name} xvfb_str = shellsub(xvfb_str, subs) def setsid(): #run in a new session if os.name=="posix": os.setsid() xvfb_cmd = xvfb_str.split() if not xvfb_cmd: raise Exception("cannot start Xvfb, command definition is missing!") xvfb_executable = xvfb_cmd[0] if display_name[0]=='S': # 'S' means that we allocate the display automatically r_pipe, w_pipe = os.pipe() xvfb_cmd += ["-displayfd", str(w_pipe)] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def preexec(): setsid() #duplicate python's _close_fds() function #(taking care to exclude the pipe) try: MAXFD = os.sysconf("SC_OPEN_MAX") except: MAXFD = 256 for i in range(3, MAXFD): if i not in (r_pipe, w_pipe): try: os.close(i) except: pass xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False, stdin=subprocess.PIPE, preexec_fn=preexec) # Read the display number from the pipe we gave to Xvfb # waiting up to 10 seconds for it to show up limit = time.time()+10 buf = "" while time.time()<limit and len(buf)<8: r, _, _ = select.select([r_pipe], [], [], max(0, limit-time.time())) if r_pipe in r: buf += os.read(r_pipe, 8) if buf[-1] == '\n': break os.close(r_pipe) os.close(w_pipe) if len(buf) == 0: raise OSError("%s did not provide a display number using -displayfd" % xvfb_executable) if buf[-1] != '\n': raise OSError("%s output not terminated by newline: %s" % (xvfb_executable, buf)) try: n = int(buf[:-1]) except: raise OSError("%s display number is not a valid number: %s" % (xvfb_executable, buf[:-1])) if n<0 or n>=2**16: raise OSError("%s provided an invalid display number: %s" % (xvfb_executable, n)) new_display_name = ":%s" % n sys.stdout.write("Using display number provided by %s: %s\n" % (xvfb_executable, new_display_name)) if xorg_log_file != None: #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log f0 = shellsub(xorg_log_file, subs) subs["DISPLAY"] = new_display_name #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log f1 = shellsub(xorg_log_file, subs) if f0 != f1: try: os.rename(f0, f1) except Exception as e: sys.stderr.write("failed to rename Xorg log file from '%s' to '%s'\n" % (f0, f1)) sys.stderr.write(" %s\n" % e) display_name = new_display_name else: # use display specified xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) xvfb_cmd.append(display_name) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=setsid) from xpra.os_util import get_hex_uuid xauth_cmd = ["xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid()] try: code = subprocess.call(xauth_cmd) if code != 0: raise OSError("non-zero exit code: %s" % code) except OSError as e: #trying to continue anyway! sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e)) return xvfb, display_name
def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None, progress_cb=None): assert mode in ( "start", "start-desktop", "upgrade", "upgrade-desktop", "shadow", "proxy", ) def _progress(i, msg): if progress_cb: progress_cb(i, msg) progress = _progress progress(10, "initializing environment") try: cwd = os.getcwd() except OSError: cwd = os.path.expanduser("~") warn("current working directory does not exist, using '%s'\n" % cwd) validate_encryption(opts) if opts.encoding == "help" or "help" in opts.encodings: return show_encoding_help(opts) #remove anything pointing to dbus from the current env #(so we only detect a dbus instance started by pam, # and override everything else) for k in tuple(os.environ.keys()): if k.startswith("DBUS_"): del os.environ[k] use_display = parse_bool("use-display", opts.use_display) starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" upgrading_desktop = mode == "upgrade-desktop" shadowing = mode == "shadow" proxying = mode == "proxy" if not proxying and POSIX and not OSX: #we don't support wayland servers, #so make sure GDK will use the X11 backend: from xpra.os_util import saved_env saved_env["GDK_BACKEND"] = "x11" os.environ["GDK_BACKEND"] = "x11" has_child_arg = (opts.start_child or opts.start_child_on_connect or opts.start_child_after_connect or opts.start_child_on_last_client_exit) if proxying or upgrading or upgrading_desktop: #when proxying or upgrading, don't exec any plain start commands: opts.start = opts.start_child = [] elif opts.exit_with_children: assert has_child_arg, "exit-with-children was specified but start-child* is missing!" elif opts.start_child: warn("Warning: the 'start-child' option is used,") warn(" but 'exit-with-children' is not enabled,") warn(" use 'start' instead") if opts.bind_rfb and (proxying or starting): get_util_logger().warn( "Warning: bind-rfb sockets cannot be used with '%s' mode" % mode) opts.bind_rfb = [] if not shadowing and not starting_desktop: opts.rfb_upgrade = 0 if upgrading or upgrading_desktop or shadowing: #there should already be one running #so change None ('auto') to False if opts.pulseaudio is None: opts.pulseaudio = False #get the display name: if shadowing and not extra_args: if WIN32 or OSX: #just a virtual name for the only display available: display_name = "Main" else: from xpra.scripts.main import guess_X11_display dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) display_name = guess_X11_display(dotxpra, desktop_display) elif (upgrading or upgrading_desktop) and not extra_args: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: if len(extra_args) > 1: error_cb( "too many extra arguments (%i): only expected a display number" % len(extra_args)) if len(extra_args) == 1: display_name = extra_args[0] if not shadowing and not upgrading and not use_display: display_name_check(display_name) else: if proxying: #find a free display number: dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) all_displays = dotxpra.sockets() #ie: [("LIVE", ":100"), ("LIVE", ":200"), ...] displays = [v[1] for v in all_displays] display_name = None for x in range(1000, 20000): v = ":%s" % x if v not in displays: display_name = v break if not display_name: error_cb( "you must specify a free virtual display name to use with the proxy server" ) elif use_display: #only use automatic guess for xpra displays and not X11 displays: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: # We will try to find one automaticaly # Use the temporary magic value 'S' as marker: display_name = 'S' + str(os.getpid()) if not (shadowing or proxying or upgrading or upgrading_desktop) and \ opts.exit_with_children and not has_child_arg: error_cb( "--exit-with-children specified without any children to spawn; exiting immediately" ) atexit.register(run_cleanups) # Generate the script text now, because os.getcwd() will # change if/when we daemonize: from xpra.server.server_util import ( xpra_runner_shell_script, write_runner_shell_scripts, find_log_dir, create_input_devices, source_env, ) script = None if POSIX and getuid() != 0: script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir) uid = int(opts.uid) gid = int(opts.gid) username = get_username_for_uid(uid) home = get_home_for_uid(uid) ROOT = POSIX and getuid() == 0 protected_fds = [] protected_env = {} stdout = sys.stdout stderr = sys.stderr # Daemonize: if POSIX and opts.daemon: #daemonize will chdir to "/", so try to use an absolute path: if opts.password_file: opts.password_file = tuple( os.path.abspath(x) for x in opts.password_file) from xpra.server.server_util import daemonize daemonize() displayfd = 0 if POSIX and opts.displayfd: try: displayfd = int(opts.displayfd) if displayfd > 0: protected_fds.append(displayfd) except ValueError as e: stderr.write("Error: invalid displayfd '%s':\n" % opts.displayfd) stderr.write(" %s\n" % e) del e clobber = int(upgrading or upgrading_desktop) * CLOBBER_UPGRADE | int( use_display or 0) * CLOBBER_USE_DISPLAY start_vfb = not (shadowing or proxying or clobber) xauth_data = None if start_vfb: xauth_data = get_hex_uuid() # if pam is present, try to create a new session: pam = None PAM_OPEN = POSIX and envbool("XPRA_PAM_OPEN", ROOT and uid != 0) if PAM_OPEN: try: from xpra.server.pam import pam_session #@UnresolvedImport except ImportError as e: stderr.write("Error: failed to import pam module\n") stderr.write(" %s" % e) del e PAM_OPEN = False if PAM_OPEN: fdc = FDChangeCaptureContext() with fdc: pam = pam_session(username) env = { #"XDG_SEAT" : "seat1", #"XDG_VTNR" : "0", "XDG_SESSION_TYPE": "x11", #"XDG_SESSION_CLASS" : "user", "XDG_SESSION_DESKTOP": "xpra", } #maybe we should just bail out instead? if pam.start(): pam.set_env(env) items = {} if display_name.startswith(":"): items["XDISPLAY"] = display_name if xauth_data: items["XAUTHDATA"] = xauth_data pam.set_items(items) if pam.open(): #we can't close it, because we're not going to be root any more, #but since we're the process leader for the session, #terminating will also close the session #add_cleanup(pam.close) protected_env = pam.get_envlist() os.environ.update(protected_env) #closing the pam fd causes the session to be closed, #and we don't want that! protected_fds += fdc.get_new_fds() #get XDG_RUNTIME_DIR from env options, #which may not be have updated os.environ yet when running as root with "--uid=" xrd = os.path.abspath(parse_env(opts.env).get("XDG_RUNTIME_DIR", "")) if ROOT and (uid > 0 or gid > 0): #we're going to chown the directory if we create it, #ensure this cannot be abused, only use "safe" paths: if not any(x for x in ("/run/user/%i" % uid, "/tmp", "/var/tmp") if xrd.startswith(x)): xrd = "" #these paths could cause problems if we were to create and chown them: if xrd.startswith("/tmp/.X11-unix") or xrd.startswith( "/tmp/.XIM-unix"): xrd = "" if not xrd: xrd = os.environ.get("XDG_RUNTIME_DIR") xrd = create_runtime_dir(xrd, uid, gid) if xrd: #this may override the value we get from pam #with the value supplied by the user: protected_env["XDG_RUNTIME_DIR"] = xrd if script: # Write out a shell-script so that we can start our proxy in a clean # environment: write_runner_shell_scripts(script) import datetime extra_expand = { "TIMESTAMP": datetime.datetime.now().strftime("%Y%m%d-%H%M%S") } log_to_file = opts.daemon or os.environ.get("XPRA_LOG_TO_FILE", "") == "1" if start_vfb or log_to_file: #we will probably need a log dir #either for the vfb, or for our own log file log_dir = opts.log_dir or "" if not log_dir or log_dir.lower() == "auto": log_dir = find_log_dir(username, uid=uid, gid=gid) if not log_dir: raise InitException( "cannot find or create a logging directory") #expose the log-dir as "XPRA_LOG_DIR", #this is used by Xdummy for the Xorg log file if "XPRA_LOG_DIR" not in os.environ: os.environ["XPRA_LOG_DIR"] = log_dir if log_to_file: from xpra.server.server_util import select_log_file, open_log_file, redirect_std_to_log log_filename0 = osexpand( select_log_file(log_dir, opts.log_file, display_name), username, uid, gid, extra_expand) if os.path.exists(log_filename0) and not display_name.startswith("S"): #don't overwrite the log file just yet, #as we may still fail to start log_filename0 += ".new" logfd = open_log_file(log_filename0) if POSIX and ROOT and (uid > 0 or gid > 0): try: os.fchown(logfd, uid, gid) except OSError as e: noerr(stderr.write, "failed to chown the log file '%s'\n" % log_filename0) noerr(stderr.flush) stdout, stderr = redirect_std_to_log(logfd, *protected_fds) noerr( stderr.write, "Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) noerr(stderr.flush) os.environ["XPRA_SERVER_LOG"] = log_filename0 else: #server log does not exist: os.environ.pop("XPRA_SERVER_LOG", None) #warn early about this: if (starting or starting_desktop ) and desktop_display and opts.notifications and not opts.dbus_launch: print_DE_warnings() if start_vfb and opts.xvfb.find("Xephyr") >= 0 and opts.sync_xvfb <= 0: warn("Warning: using Xephyr as vfb") warn(" you should also enable the sync-xvfb option") warn(" to keep the Xephyr window updated") progress(10, "creating sockets") from xpra.net.socket_util import get_network_logger, setup_local_sockets, create_sockets sockets = create_sockets(opts, error_cb) sanitize_env() os.environ.update(source_env(opts.source)) if POSIX: if xrd: os.environ["XDG_RUNTIME_DIR"] = xrd if not OSX: os.environ["XDG_SESSION_TYPE"] = "x11" if not starting_desktop: os.environ["XDG_CURRENT_DESKTOP"] = opts.wm_name configure_imsettings_env(opts.input_method) if display_name[0] != 'S': os.environ["DISPLAY"] = display_name if POSIX: os.environ["CKCON_X11_DISPLAY"] = display_name elif not start_vfb or opts.xvfb.find("Xephyr") < 0: os.environ.pop("DISPLAY", None) os.environ.update(protected_env) from xpra.log import Logger log = Logger("server") log("env=%s", os.environ) UINPUT_UUID_LEN = 12 UINPUT_UUID_MIN_LEN = 12 UINPUT_UUID_MAX_LEN = 32 # 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 and use_display is None: #use-display='auto' so we have to figure out #if we have to start the vfb or not: if not display_name: use_display = False else: progress(20, "connecting to the display") start_vfb = verify_display( None, display_name, log_errors=False, timeout=1) != 0 if start_vfb: progress(20, "starting a virtual display") assert not proxying and xauth_data pixel_depth = validate_pixel_depth(opts.pixel_depth, starting_desktop) from xpra.x11.vfb_util import start_Xvfb, check_xvfb_process, parse_resolution 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(UINPUT_UUID_LEN) vfb_geom = "" try: vfb_geom = parse_resolution(opts.resize_display) except Exception: pass xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid) for f in cleanups: add_cleanup(f) xvfb_pid = xvfb.pid #always update as we may now have the "real" display name: os.environ["DISPLAY"] = display_name os.environ["CKCON_X11_DISPLAY"] = display_name os.environ.update(protected_env) if display_name != odisplay_name and pam: pam.set_items({"XDISPLAY": display_name}) def check_xvfb(timeout=0): return check_xvfb_process(xvfb, timeout=timeout, command=opts.xvfb) else: if POSIX and clobber: #if we're meant to be using a private XAUTHORITY file, #make sure to point to it: from xpra.x11.vfb_util import get_xauthority_path xauthority = get_xauthority_path(display_name, username, uid, gid) if os.path.exists(xauthority): log("found XAUTHORITY=%s", xauthority) os.environ["XAUTHORITY"] = xauthority def check_xvfb(timeout=0): #pylint: disable=unused-argument return True if POSIX and not OSX and displayfd > 0: from xpra.platform.displayfd import write_displayfd try: display_no = display_name[1:] #ensure it is a string containing the number: display_no = str(int(display_no)) log("writing display_no='%s' to displayfd=%i", display_no, displayfd) assert write_displayfd(displayfd, display_no), "timeout" except Exception as e: log.error("write_displayfd failed", exc_info=True) log.error("Error: failed to write '%s' to fd=%s", display_name, displayfd) log.error(" %s", str(e) or type(e)) del e if not check_xvfb(1): noerr(stderr.write, "vfb failed to start, exiting\n") return EXIT_VFB_ERROR if WIN32 and os.environ.get("XPRA_LOG_FILENAME"): os.environ["XPRA_SERVER_LOG"] = os.environ["XPRA_LOG_FILENAME"] if opts.daemon: log_filename1 = osexpand( select_log_file(log_dir, opts.log_file, display_name), username, uid, gid, extra_expand) if log_filename0 != log_filename1: # we now have the correct log filename, so use it: try: os.rename(log_filename0, log_filename1) except (OSError, IOError): pass else: os.environ["XPRA_SERVER_LOG"] = log_filename1 if odisplay_name != display_name: #this may be used by scripts, let's try not to change it: noerr(stderr.write, "Actual display used: %s\n" % display_name) noerr(stderr.write, "Actual log file name is now: %s\n" % log_filename1) noerr(stderr.flush) noerr(stdout.close) noerr(stderr.close) #we should not be using stdout or stderr from this point: del stdout del stderr if not check_xvfb(): noerr(stderr.write, "vfb failed to start, exiting\n") return EXIT_VFB_ERROR #create devices for vfb if needed: devices = {} if not start_vfb and not proxying and not shadowing and envbool( "XPRA_UINPUT", True): #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 prop = "_XPRA_UINPUT_ID" cmd = ["xprop", "-display", display_name, "-root", prop] log("looking for '%s' on display '%s' with XAUTHORITY='%s'", prop, display_name, os.environ.get("XAUTHORITY")) try: code, out, err = get_status_output(cmd) except Exception as e: log("failed to get existing uinput id: %s", e) del e else: log("Popen(%s)=%s", cmd, (code, out, err)) if code == 0 and out.find("=") > 0: uinput_uuid = out.split("=", 1)[1] log("raw uinput uuid=%s", uinput_uuid) uinput_uuid = strtobytes(uinput_uuid.strip('\n\r"\\ ')) if uinput_uuid: if len(uinput_uuid) > UINPUT_UUID_MAX_LEN or len( uinput_uuid) < UINPUT_UUID_MIN_LEN: log.warn("Warning: ignoring invalid uinput id:") log.warn(" '%s'", uinput_uuid) uinput_uuid = None else: log.info("retrieved existing uinput id: %s", bytestostr(uinput_uuid)) 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) os.environ.update({ "HOME": home, "USER": username, "LOGNAME": username, }) shell = get_shell_for_uid(uid) if shell: os.environ["SHELL"] = shell #now we've changed uid, it is safe to honour all the env updates: configure_env(opts.env) os.environ.update(protected_env) if opts.chdir: log("chdir(%s)", opts.chdir) os.chdir(opts.chdir) dbus_pid, dbus_env = 0, {} if not shadowing and POSIX and not OSX and not clobber: no_gtk() assert starting or starting_desktop or proxying try: from xpra.server.dbus.dbus_start import start_dbus except ImportError as e: log("dbus components are not installed: %s", e) else: dbus_pid, dbus_env = start_dbus(opts.dbus_launch) if dbus_env: os.environ.update(dbus_env) if not proxying: if POSIX and not OSX: no_gtk() if starting or starting_desktop or shadowing: r = verify_display(xvfb, display_name, shadowing) if r: return r #on win32, this ensures that we get the correct screen size to shadow: from xpra.platform.gui import init as gui_init log("gui_init()") gui_init() progress(50, "creating local sockets") #setup unix domain socket: netlog = get_network_logger() local_sockets = setup_local_sockets(opts.bind, opts.socket_dir, opts.socket_dirs, display_name, clobber, opts.mmap_group, opts.socket_permissions, username, uid, gid) netlog("setting up local sockets: %s", local_sockets) sockets.update(local_sockets) if POSIX and (starting or upgrading or starting_desktop or upgrading_desktop): #all unix domain sockets: ud_paths = [ sockpath for stype, _, sockpath, _ in local_sockets if stype == "unix-domain" ] if ud_paths: #choose one so our xdg-open override script can use to talk back to us: if opts.forward_xdg_open: for x in ("/usr/libexec/xpra", "/usr/lib/xpra"): xdg_override = os.path.join(x, "xdg-open") if os.path.exists(xdg_override): os.environ["PATH"] = x + os.pathsep + os.environ.get( "PATH", "") os.environ["XPRA_SERVER_SOCKET"] = ud_paths[0] break else: log.warn("Warning: no local server sockets,") if opts.forward_xdg_open: log.warn(" forward-xdg-open cannot be enabled") log.warn(" non-embedded ssh connections will not be available") set_server_features(opts) if not proxying and POSIX and not OSX: if not check_xvfb(): return 1 from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source if os.environ.get("NO_AT_BRIDGE") is None: os.environ["NO_AT_BRIDGE"] = "1" init_gdk_display_source() #(now we can access the X11 server) if uinput_uuid: save_uinput_id(uinput_uuid) progress(60, "initializing server") if shadowing: app = make_shadow_server() elif proxying: app = make_proxy_server() else: if starting or upgrading: app = make_server(clobber) else: assert starting_desktop or upgrading_desktop app = make_desktop_server(clobber) app.init_virtual_devices(devices) try: app.exec_cwd = opts.chdir or cwd app.display_name = display_name app.init(opts) progress(70, "initializing sockets") app.init_sockets(sockets) app.init_dbus(dbus_pid, dbus_env) if not shadowing and not proxying: app.init_display_pid(xvfb_pid) app.original_desktop_display = desktop_display del opts if not app.server_ready(): return 1 progress(80, "finalizing") app.server_init() app.setup() app.init_when_ready(_when_ready) except InitException as e: log.error("xpra server initialization error:") log.error(" %s", e) app.cleanup() return 1 except Exception as e: log.error("Error: cannot start the %s server", app.session_type, exc_info=True) log.error(str(e)) log.info("") if upgrading or upgrading_desktop: #something abnormal occurred, #don't kill the vfb on exit: from xpra.server import EXITING_CODE app._upgrading = EXITING_CODE app.cleanup() return 1 try: progress(100, "running") log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") app.cleanup() return EXIT_OK except Exception: log.error("server error", exc_info=True) app.cleanup() return -128 else: if r > 0: r = 0 return r
return False if proto.authenticator: challenge = proto.authenticator.get_challenge() if challenge is None: auth_failed("invalid state: unexpected challenge response") return False salt, digest = challenge log.info("Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest) if digest not in self.digest_modes: auth_failed("cannot proceed without %s digest support" % digest) return False else: log.warn("Warning: client expects a challenge but this connection is unauthenticated") #fake challenge so the client will send the real hello: from xpra.os_util import get_hex_uuid salt = get_hex_uuid()+get_hex_uuid() digest = "hmac" proto.challenge_sent = True proto.send_now(("challenge", salt, auth_caps or "", digest)) return False if not proto.authenticator.authenticate(challenge_response, client_salt): auth_failed("invalid challenge response") return False log("authentication challenge passed") else: #did the client expect a challenge? if c.boolget("challenge"): log.warn("this server does not require authentication (client supplied a challenge)") return auth_caps
def init_uuid(self): self._loop_uuid = LOOP_PREFIX + get_hex_uuid() log("init_uuid() %s uuid=%s", self._selection, self._loop_uuid)
def get_challenge(self): return get_hex_uuid(), "hmac"
def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): try: cwd = os.getcwd() except OSError: cwd = os.path.expanduser("~") warn("current working directory does not exist, using '%s'\n" % cwd) validate_encryption(opts) if opts.encoding == "help" or "help" in opts.encodings: return show_encoding_help(opts) assert mode in ("start", "start-desktop", "upgrade", "shadow", "proxy") starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" shadowing = mode == "shadow" proxying = mode == "proxy" clobber = upgrading or opts.use_display start_vfb = not shadowing and not proxying and not clobber if shadowing and is_Wayland(): warn("shadow servers do not support Wayland, switch to X11") if opts.bind_rfb and (proxying or starting): get_util_logger().warn( "Warning: bind-rfb sockets cannot be used with '%s' mode" % mode) opts.bind_rfb = "" if not shadowing and not starting_desktop: opts.rfb_upgrade = 0 if upgrading or shadowing: #there should already be one running opts.pulseaudio = False #get the display name: if shadowing and not extra_args: if WIN32 or OSX: #just a virtual name for the only display available: display_name = ":0" else: from xpra.scripts.main import guess_X11_display dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) display_name = guess_X11_display(dotxpra, desktop_display) elif upgrading and not extra_args: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: if len(extra_args) > 1: error_cb( "too many extra arguments (%i): only expected a display number" % len(extra_args)) if len(extra_args) == 1: display_name = extra_args[0] if not shadowing and not proxying and not opts.use_display: display_name_check(display_name) else: if proxying: #find a free display number: dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) all_displays = dotxpra.sockets() #ie: [("LIVE", ":100"), ("LIVE", ":200"), ...] displays = [v[1] for v in all_displays] display_name = None for x in range(1000, 20000): v = ":%s" % x if v not in displays: display_name = v break if not display_name: error_cb( "you must specify a free virtual display name to use with the proxy server" ) elif opts.use_display: #only use automatic guess for xpra displays and not X11 displays: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: # We will try to find one automaticaly # Use the temporary magic value 'S' as marker: display_name = 'S' + str(os.getpid()) if not shadowing and not proxying and not upgrading and opts.exit_with_children and not opts.start_child: error_cb( "--exit-with-children specified without any children to spawn; exiting immediately" ) atexit.register(run_cleanups) # Generate the script text now, because os.getcwd() will # change if/when we daemonize: from xpra.server.server_util import ( xpra_runner_shell_script, write_runner_shell_scripts, write_pidfile, find_log_dir, create_input_devices, ) script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir) uid = int(opts.uid) gid = int(opts.gid) username = get_username_for_uid(uid) home = get_home_for_uid(uid) xauth_data = None if start_vfb: xauth_data = get_hex_uuid() ROOT = POSIX and getuid() == 0 protected_fds = [] protected_env = {} stdout = sys.stdout stderr = sys.stderr # Daemonize: if POSIX and opts.daemon: #daemonize will chdir to "/", so try to use an absolute path: if opts.password_file: opts.password_file = tuple( os.path.abspath(x) for x in opts.password_file) from xpra.server.server_util import daemonize daemonize() displayfd = 0 if POSIX and opts.displayfd: try: displayfd = int(opts.displayfd) if displayfd > 0: protected_fds.append(displayfd) except ValueError as e: stderr.write("Error: invalid displayfd '%s':\n" % opts.displayfd) stderr.write(" %s\n" % e) del e # if pam is present, try to create a new session: pam = None PAM_OPEN = POSIX and envbool("XPRA_PAM_OPEN", ROOT and uid != 0) if PAM_OPEN: try: from xpra.server.pam import pam_session #@UnresolvedImport except ImportError as e: stderr.write("Error: failed to import pam module\n") stderr.write(" %s" % e) del e PAM_OPEN = False if PAM_OPEN: fdc = FDChangeCaptureContext() with fdc: pam = pam_session(username) env = { #"XDG_SEAT" : "seat1", #"XDG_VTNR" : "0", "XDG_SESSION_TYPE": "x11", #"XDG_SESSION_CLASS" : "user", "XDG_SESSION_DESKTOP": "xpra", } #maybe we should just bail out instead? if pam.start(): pam.set_env(env) items = {} if display_name.startswith(":"): items["XDISPLAY"] = display_name if xauth_data: items["XAUTHDATA"] = xauth_data pam.set_items(items) if pam.open(): #we can't close it, because we're not going to be root any more, #but since we're the process leader for the session, #terminating will also close the session #add_cleanup(pam.close) protected_env = pam.get_envlist() os.environ.update(protected_env) #closing the pam fd causes the session to be closed, #and we don't want that! protected_fds += fdc.get_new_fds() #get XDG_RUNTIME_DIR from env options, #which may not be have updated os.environ yet when running as root with "--uid=" xrd = os.path.abspath(parse_env(opts.env).get("XDG_RUNTIME_DIR", "")) if ROOT and (uid > 0 or gid > 0): #we're going to chown the directory if we create it, #ensure this cannot be abused, only use "safe" paths: if not any(x for x in ("/run/user/%i" % uid, "/tmp", "/var/tmp") if xrd.startswith(x)): xrd = "" #these paths could cause problems if we were to create and chown them: if xrd.startswith("/tmp/.X11-unix") or xrd.startswith( "/tmp/.XIM-unix"): xrd = "" if not xrd: xrd = os.environ.get("XDG_RUNTIME_DIR") xrd = create_runtime_dir(xrd, uid, gid) if xrd: #this may override the value we get from pam #with the value supplied by the user: protected_env["XDG_RUNTIME_DIR"] = xrd if opts.pidfile: write_pidfile(opts.pidfile, uid, gid) if POSIX and not ROOT: # Write out a shell-script so that we can start our proxy in a clean # environment: write_runner_shell_scripts(script) if start_vfb or opts.daemon: #we will probably need a log dir #either for the vfb, or for our own log file log_dir = opts.log_dir or "" if not log_dir or log_dir.lower() == "auto": log_dir = find_log_dir(username, uid=uid, gid=gid) if not log_dir: raise InitException( "cannot find or create a logging directory") #expose the log-dir as "XPRA_LOG_DIR", #this is used by Xdummy for the Xorg log file if "XPRA_LOG_DIR" not in os.environ: os.environ["XPRA_LOG_DIR"] = log_dir if opts.daemon: from xpra.server.server_util import select_log_file, open_log_file, redirect_std_to_log log_filename0 = osexpand( select_log_file(log_dir, opts.log_file, display_name), username, uid, gid) logfd = open_log_file(log_filename0) if ROOT and (uid > 0 or gid > 0): try: os.fchown(logfd, uid, gid) except: pass stdout, stderr = redirect_std_to_log(logfd, *protected_fds) try: stderr.write("Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) except IOError: #we tried our best, logging another error won't help pass #warn early about this: if (starting or starting_desktop ) and desktop_display and opts.notifications and not opts.dbus_launch: print_DE_warnings() log = get_util_logger() sockets, mdns_recs, wrap_socket_fn = create_sockets(opts, error_cb) sanitize_env() if POSIX: if xrd: os.environ["XDG_RUNTIME_DIR"] = xrd os.environ["XDG_SESSION_TYPE"] = "x11" if not starting_desktop: os.environ["XDG_CURRENT_DESKTOP"] = opts.wm_name configure_imsettings_env(opts.input_method) if display_name[0] != 'S': os.environ["DISPLAY"] = display_name os.environ["CKCON_X11_DISPLAY"] = display_name else: try: del os.environ["DISPLAY"] except KeyError: pass os.environ.update(protected_env) log("env=%s", os.environ) UINPUT_UUID_LEN = 12 UINPUT_UUID_MIN_LEN = 12 UINPUT_UUID_MAX_LEN = 32 # 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, starting_desktop) from xpra.x11.vfb_util import start_Xvfb, check_xvfb_process 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(UINPUT_UUID_LEN) 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 #always update as we may now have the "real" display name: os.environ["DISPLAY"] = display_name os.environ["CKCON_X11_DISPLAY"] = display_name os.environ.update(protected_env) if display_name != odisplay_name and pam: pam.set_items({"XDISPLAY": display_name}) def check_xvfb(): return check_xvfb_process(xvfb) else: if POSIX and clobber: #if we're meant to be using a private XAUTHORITY file, #make sure to point to it: from xpra.x11.vfb_util import get_xauthority_path xauthority = get_xauthority_path(display_name, username, uid, gid) if os.path.exists(xauthority): os.environ["XAUTHORITY"] = xauthority def check_xvfb(): return True if POSIX and not OSX and displayfd > 0: from xpra.platform.displayfd import write_displayfd try: display = display_name[1:] log("writing display='%s' to displayfd=%i", display, displayfd) assert write_displayfd(displayfd, display), "timeout" except Exception as e: log.error("write_displayfd failed", exc_info=True) log.error("Error: failed to write '%s' to fd=%s", display_name, displayfd) log.error(" %s", str(e) or type(e)) del e try: os.close(displayfd) except IOError: pass kill_display = None if not proxying: add_cleanup(close_gtk_display) if not proxying and not shadowing: def kill_display(): if xvfb_pid: kill_xvfb(xvfb_pid) add_cleanup(kill_display) if opts.daemon: def noerr(fn, *args): try: fn(*args) except: pass log_filename1 = osexpand( select_log_file(log_dir, opts.log_file, display_name), username, uid, gid) if log_filename0 != log_filename1: # we now have the correct log filename, so use it: os.rename(log_filename0, log_filename1) if odisplay_name != display_name: #this may be used by scripts, let's try not to change it: noerr(stderr.write, "Actual display used: %s\n" % display_name) noerr(stderr.write, "Actual log file name is now: %s\n" % log_filename1) noerr(stderr.flush) noerr(stdout.close) noerr(stderr.close) #we should not be using stdout or stderr from this point: del stdout del stderr if not check_xvfb(): #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 prop = "_XPRA_UINPUT_ID" cmd = ["xprop", "-display", display_name, "-root", prop] log("looking for '%s' on display '%s' with XAUTHORITY='%s'", prop, display_name, os.environ.get("XAUTHORITY")) try: code, out, err = get_status_output(cmd) except Exception as e: log("failed to get existing uinput id: %s", e) del e else: log("Popen(%s)=%s", cmd, (code, out, err)) if code == 0 and out.find("=") > 0: uinput_uuid = out.split("=", 1)[1] log("raw uinput uuid=%s", uinput_uuid) uinput_uuid = strtobytes(uinput_uuid.strip('\n\r"\\ ')) if uinput_uuid: if len(uinput_uuid) > UINPUT_UUID_MAX_LEN or len( uinput_uuid) < UINPUT_UUID_MIN_LEN: log.warn("Warning: ignoring invalid uinput id:") log.warn(" '%s'", uinput_uuid) uinput_uuid = None else: log.info("retrieved existing uinput id: %s", bytestostr(uinput_uuid)) 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) os.environ.update({ "HOME": home, "USER": username, "LOGNAME": username, }) shell = get_shell_for_uid(uid) if shell: os.environ["SHELL"] = shell #now we've changed uid, it is safe to honour all the env updates: configure_env(opts.env) os.environ.update(protected_env) if opts.chdir: os.chdir(opts.chdir) display = None if not proxying: no_gtk() if POSIX and not OSX and (starting or starting_desktop or shadowing): #check that we can access the X11 display: from xpra.x11.vfb_util import verify_display_ready if not verify_display_ready(xvfb, display_name, shadowing): return 1 if not PYTHON3: from xpra.x11.gtk2.gdk_display_util import verify_gdk_display #@UnusedImport else: from xpra.x11.gtk3.gdk_display_util import verify_gdk_display #@Reimport display = verify_gdk_display(display_name) if not display: return 1 #on win32, this ensures that we get the correct screen size to shadow: from xpra.platform.gui import init as gui_init gui_init() #setup unix domain socket: from xpra.server.socket_util import get_network_logger, setup_local_sockets netlog = get_network_logger() if not opts.socket_dir and not opts.socket_dirs: #we always need at least one valid socket dir from xpra.platform.paths import get_socket_dirs opts.socket_dirs = get_socket_dirs() local_sockets = setup_local_sockets(opts.bind, opts.socket_dir, opts.socket_dirs, display_name, clobber, opts.mmap_group, opts.socket_permissions, username, uid, gid) netlog("setting up local sockets: %s", local_sockets) ssh_port = get_ssh_port() ssh_access = ssh_port > 0 and opts.ssh.lower().strip() not in FALSE_OPTIONS for rec, cleanup_socket in local_sockets: socktype, socket, sockpath = rec #ie: ("unix-domain", sock, sockpath), cleanup_socket sockets.append(rec) netlog("%s %s : %s", socktype, sockpath, socket) add_cleanup(cleanup_socket) if opts.mdns and ssh_access: netlog("ssh %s:%s : %s", "", ssh_port, socket) add_mdns(mdns_recs, "ssh", "", ssh_port) def b(v): return str(v).lower() not in FALSE_OPTIONS #turn off some server mixins: from xpra.server import server_features impwarned = [] def impcheck(*modules): for mod in modules: try: __import__("xpra.%s" % mod, {}, {}, []) except ImportError: if mod not in impwarned: impwarned.append(mod) log = get_util_logger() log.warn("Warning: missing %s module", mod) return False return True server_features.notifications = opts.notifications and impcheck( "notifications") server_features.webcam = b(opts.webcam) and impcheck("codecs") server_features.clipboard = b(opts.clipboard) and impcheck("clipboard") server_features.audio = (b(opts.speaker) or b(opts.microphone)) and impcheck("sound") server_features.av_sync = server_features.audio and b(opts.av_sync) server_features.fileprint = b(opts.printing) or b(opts.file_transfer) server_features.mmap = b(opts.mmap) server_features.input_devices = not opts.readonly and impcheck("keyboard") server_features.commands = impcheck("server.control_command") server_features.dbus = opts.dbus_proxy and impcheck("dbus") server_features.encoding = impcheck("codecs") server_features.logging = b(opts.remote_logging) #server_features.network_state = ?? server_features.display = opts.windows server_features.windows = opts.windows and impcheck("codecs") server_features.rfb = b(opts.rfb_upgrade) and impcheck("server.rfb") kill_dbus = None if shadowing: app = make_shadow_server() elif proxying: app = make_proxy_server() else: if not check_xvfb(): return 1 assert starting or starting_desktop or upgrading from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source, close_gdk_display_source init_gdk_display_source() insert_cleanup(close_gdk_display_source) #(now we can access the X11 server) #make sure the pid we save is the real one: if not check_xvfb(): return 1 if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) if POSIX: save_uinput_id(uinput_uuid) dbus_pid = -1 dbus_env = {} if clobber: #get the saved pids and env dbus_pid = get_dbus_pid() dbus_env = get_dbus_env() log("retrieved existing dbus attributes") else: assert starting or starting_desktop if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) bus_address = protected_env.get("DBUS_SESSION_BUS_ADDRESS") log("dbus_launch=%s, current DBUS_SESSION_BUS_ADDRESS=%s", opts.dbus_launch, bus_address) if opts.dbus_launch and not bus_address: #start a dbus server: def kill_dbus(): log("kill_dbus: dbus_pid=%s" % dbus_pid) if dbus_pid <= 0: return try: os.kill(dbus_pid, signal.SIGINT) except Exception as e: log.warn( "Warning: error trying to stop dbus with pid %i:", dbus_pid) log.warn(" %s", e) add_cleanup(kill_dbus) #this also updates os.environ with the dbus attributes: dbus_pid, dbus_env = start_dbus(opts.dbus_launch) if dbus_pid > 0: save_dbus_pid(dbus_pid) if dbus_env: save_dbus_env(dbus_env) log("dbus attributes: pid=%s, env=%s", dbus_pid, dbus_env) if dbus_env: os.environ.update(dbus_env) os.environ.update(protected_env) if POSIX: #all unix domain sockets: ud_paths = [ sockpath for (stype, _, sockpath), _ in local_sockets if stype == "unix-domain" ] if ud_paths: #choose one so our xdg-open override script can use to talk back to us: if opts.forward_xdg_open: for x in ("/usr/libexec/xpra", "/usr/lib/xpra"): xdg_override = os.path.join(x, "xdg-open") if os.path.exists(xdg_override): os.environ[ "PATH"] = x + os.pathsep + os.environ.get( "PATH", "") os.environ[ "XPRA_XDG_OPEN_SERVER_SOCKET"] = ud_paths[0] break else: log.warn("Warning: no local server sockets,") if opts.forward_xdg_open: log.warn(" forward-xdg-open cannot be enabled") log.warn(" ssh connections will not be available") log("env=%s", os.environ) try: # This import is delayed because the module depends on gtk: from xpra.x11.bindings.window_bindings import X11WindowBindings X11Window = X11WindowBindings() if (starting or starting_desktop) and not clobber and opts.resize_display: from xpra.x11.vfb_util import set_initial_resolution set_initial_resolution(starting_desktop) except ImportError as e: log.error( "Failed to load Xpra server components, check your installation: %s" % e) return 1 if starting or upgrading: if not X11Window.displayHasXComposite(): log.error( "Xpra 'start' subcommand runs as a compositing manager") log.error( " it cannot use a display which lacks the XComposite extension!" ) return 1 if starting: #check for an existing window manager: from xpra.x11.gtk_x11.wm_check import wm_check if not wm_check(display, opts.wm_name, upgrading): return 1 log("XShape=%s", X11Window.displayHasXShape()) app = make_server(clobber) else: assert starting_desktop app = make_desktop_server() app.init_virtual_devices(devices) if proxying or upgrading: #when proxying or upgrading, don't exec any plain start commands: opts.start = opts.start_child = [] elif opts.exit_with_children: assert opts.start_child, "exit-with-children was specified but start-child is missing!" elif opts.start_child: log.warn("Warning: the 'start-child' option is used,") log.warn(" but 'exit-with-children' is not enabled,") log.warn(" use 'start' instead") try: app._ssl_wrap_socket = wrap_socket_fn app.original_desktop_display = desktop_display app.exec_cwd = opts.chdir or cwd app.init(opts) app.setup() except InitException as e: log.error("xpra server initialization error:") log.error(" %s", e) return 1 except Exception as e: log.error("Error: cannot start the %s server", app.session_type, exc_info=True) log.error(str(e)) log.info("") return 1 #publish mdns records: if opts.mdns: from xpra.platform.info import get_username from xpra.server.socket_util import mdns_publish mdns_info = { "display": display_name, "username": get_username(), "uuid": app.uuid, "platform": sys.platform, "type": app.session_type, } MDNS_EXPOSE_NAME = envbool("XPRA_MDNS_EXPOSE_NAME", True) if MDNS_EXPOSE_NAME and app.session_name: mdns_info["name"] = app.session_name for mode, listen_on in mdns_recs.items(): mdns_publish(display_name, mode, listen_on, mdns_info) del opts log("%s(%s)", app.init_sockets, sockets) app.init_sockets(sockets) log("%s(%s)", app.init_when_ready, _when_ready) app.init_when_ready(_when_ready) try: #from here on, we own the vfb, even if we inherited one: if (starting or starting_desktop or upgrading) and clobber: #and it will be killed if exit cleanly: xvfb_pid = get_xvfb_pid() log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") return 0 except Exception: log.error("server error", exc_info=True) return -128 else: if r > 0: # Upgrading/exiting, so leave X and dbus servers running if kill_display: _cleanups.remove(kill_display) if kill_dbus: _cleanups.remove(kill_dbus) from xpra.server import EXITING_CODE if r == EXITING_CODE: log.info("exiting: not cleaning up Xvfb") else: log.info("upgrading: not cleaning up Xvfb") r = 0 return r
salt, digest = challenge log.info( "Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest) if digest not in self.digest_modes: auth_failed( "cannot proceed without %s digest support" % digest) return False else: log.warn( "Warning: client expects a challenge but this connection is unauthenticated" ) #fake challenge so the client will send the real hello: from xpra.os_util import get_hex_uuid salt = get_hex_uuid() + get_hex_uuid() digest = "hmac" proto.challenge_sent = True proto.send_now(("challenge", salt, auth_caps or "", digest)) return False if not proto.authenticator.authenticate(challenge_response, client_salt): auth_failed("invalid challenge response") return False log("authentication challenge passed") else: #did the client expect a challenge? if c.boolget("challenge"): log.warn( "this server does not require authentication (client supplied a challenge)"
def verify_hello(self, proto, c): remote_version = c.strget("version") verr = version_compat_check(remote_version) if verr is not None: self.disconnect_client(proto, VERSION_ERROR, "incompatible version: %s" % verr) proto.close() return False def auth_failed(msg): log.warn("Warning: authentication failed: %s", msg) self.timeout_add(1000, self.disconnect_client, proto, msg) # authenticator: username = c.strget("username") if proto.authenticator is None and proto.auth_class: try: proto.authenticator = proto.auth_class(username) except Exception as e: log.warn("error instantiating %s: %s", proto.auth_class, e) auth_failed("authentication failed") return False self.digest_modes = c.get("digest", ("hmac",)) # client may have requested encryption: cipher = c.strget("cipher") cipher_iv = c.strget("cipher.iv") key_salt = c.strget("cipher.key_salt") iterations = c.intget("cipher.key_stretch_iterations") auth_caps = {} if cipher and cipher_iv: if cipher not in ENCRYPTION_CIPHERS: log.warn("unsupported cipher: %s", cipher) auth_failed("unsupported cipher") return False encryption_key = self.get_encryption_key(proto.authenticator) if encryption_key is None: auth_failed("encryption key is missing") return False proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations) # use the same cipher as used by the client: auth_caps = new_cipher_caps(proto, cipher, encryption_key) log("server cipher=%s", auth_caps) else: auth_caps = None # verify authentication if required: if (proto.authenticator and proto.authenticator.requires_challenge()) or c.get("challenge") is not None: challenge_response = c.strget("challenge_response") client_salt = c.strget("challenge_client_salt") log( "processing authentication with %s, response=%s, client_salt=%s, challenge_sent=%s", proto.authenticator, challenge_response, binascii.hexlify(client_salt or ""), proto.challenge_sent, ) # send challenge if this is not a response: if not challenge_response: if proto.challenge_sent: auth_failed("invalid state, challenge already sent - no response!") return False if proto.authenticator: challenge = proto.authenticator.get_challenge() if challenge is None: auth_failed("invalid state: unexpected challenge response") return False salt, digest = challenge log.info( "Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest, ) if digest not in self.digest_modes: auth_failed("cannot proceed without %s digest support" % digest) return False else: log.warn("Warning: client expects a challenge but this connection is unauthenticated") # fake challenge so the client will send the real hello: from xpra.os_util import get_hex_uuid salt = get_hex_uuid() + get_hex_uuid() digest = "hmac" proto.challenge_sent = True proto.send_now(("challenge", salt, auth_caps or "", digest)) return False if not proto.authenticator.authenticate(challenge_response, client_salt): auth_failed("invalid challenge response") return False log("authentication challenge passed") else: # did the client expect a challenge? if c.boolget("challenge"): log.warn("this server does not require authentication (client supplied a challenge)") return auth_caps
xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def setsid(): #run in a new session if os.name == "posix": os.setsid() xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=setsid) from xpra.os_util import get_hex_uuid xauth_cmd = [ "xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid() ] try: code = subprocess.call(xauth_cmd) if code != 0: raise OSError("non-zero exit code: %s" % code) except OSError, e: #trying to continue anyway! sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e)) return xvfb def check_xvfb_process(xvfb=None): if xvfb is None: #we don't have a process to check
"HOME" : os.environ.get("HOME", os.getcwd()), "DISPLAY" : display_name} for var,value in subs.items(): xvfb_str = xvfb_str.replace("$%s" % var, value) xvfb_str = xvfb_str.replace("${%s}" % var, value) xvfb_cmd = xvfb_str.split()+[display_name] xvfb_executable = xvfb_cmd[0] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def setsid(): #run in a new session if os.name=="posix": os.setsid() xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=setsid) from xpra.os_util import get_hex_uuid xauth_cmd = ["xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid()] try: code = subprocess.call(xauth_cmd) if code != 0: raise OSError("non-zero exit code: %s" % code) except OSError, e: #trying to continue anyway! sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e)) return xvfb def check_xvfb_process(xvfb=None): if xvfb is None: #we don't have a process to check return True if xvfb.poll() is None: #process is running
def test_uuid(self): assert len(get_hex_uuid()) == 32 assert isinstance(get_int_uuid(), int) assert get_int_uuid() != 0 assert get_user_uuid() != 0
def start_Xvfb(xvfb_str, display_name): # We need to set up a new server environment xauthority = os.environ.get("XAUTHORITY", os.path.expanduser("~/.Xauthority")) if not os.path.exists(xauthority): try: open(xauthority, 'wa').close() except Exception as e: #trying to continue anyway! sys.stderr.write( "Error trying to create XAUTHORITY file %s: %s\n" % (xauthority, e)) if not xvfb_str: raise InitException("the 'xvfb' command is not defined") #identify logfile argument if it exists and if we may have to rename it: xvfb_cmd = xvfb_str.split() if '-logfile' in xvfb_cmd and display_name[0] == 'S': xvfb_cmd = xvfb_str.split() logfile_argindex = xvfb_cmd.index('-logfile') + 1 assert logfile_argindex < len( xvfb_cmd ), "invalid xvfb command string: -logfile should not be last" xorg_log_file = xvfb_cmd[logfile_argindex] else: xorg_log_file = None #apply string substitutions: subs = { "XAUTHORITY": xauthority, "USER": os.environ.get("USER", "unknown-user"), "HOME": os.environ.get("HOME", os.getcwd()), "DISPLAY": display_name } xvfb_str = shellsub(xvfb_str, subs) def setsid(): #run in a new session if os.name == "posix": os.setsid() xvfb_cmd = xvfb_str.split() if not xvfb_cmd: raise InitException( "cannot start Xvfb, the command definition is missing!") xvfb_executable = xvfb_cmd[0] if display_name[0] == 'S': # 'S' means that we allocate the display automatically r_pipe, w_pipe = os.pipe() xvfb_cmd += ["-displayfd", str(w_pipe)] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def preexec(): setsid() #duplicate python's _close_fds() function #(taking care to exclude the pipe) try: MAXFD = os.sysconf("SC_OPEN_MAX") except: MAXFD = 256 for i in range(3, MAXFD): if i not in (r_pipe, w_pipe): try: os.close(i) except: pass xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False, stdin=subprocess.PIPE, preexec_fn=preexec) # Read the display number from the pipe we gave to Xvfb # waiting up to 10 seconds for it to show up limit = time.time() + 10 buf = "" while time.time() < limit and len(buf) < 8: r, _, _ = select.select([r_pipe], [], [], max(0, limit - time.time())) if r_pipe in r: buf += os.read(r_pipe, 8) if buf[-1] == '\n': break os.close(r_pipe) os.close(w_pipe) if len(buf) == 0: raise OSError( "%s did not provide a display number using -displayfd" % xvfb_executable) if buf[-1] != '\n': raise OSError("%s output not terminated by newline: %s" % (xvfb_executable, buf)) try: n = int(buf[:-1]) except: raise OSError("%s display number is not a valid number: %s" % (xvfb_executable, buf[:-1])) if n < 0 or n >= 2**16: raise OSError("%s provided an invalid display number: %s" % (xvfb_executable, n)) new_display_name = ":%s" % n sys.stdout.write("Using display number provided by %s: %s\n" % (xvfb_executable, new_display_name)) if xorg_log_file != None: #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log f0 = shellsub(xorg_log_file, subs) subs["DISPLAY"] = new_display_name #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log f1 = shellsub(xorg_log_file, subs) if f0 != f1: try: os.rename(f0, f1) except Exception as e: sys.stderr.write( "failed to rename Xorg log file from '%s' to '%s'\n" % (f0, f1)) sys.stderr.write(" %s\n" % e) display_name = new_display_name else: # use display specified xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) xvfb_cmd.append(display_name) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=setsid) from xpra.os_util import get_hex_uuid xauth_cmd = [ "xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid() ] try: code = subprocess.call(xauth_cmd) if code != 0: raise OSError("non-zero exit code: %s" % code) except OSError as e: #trying to continue anyway! sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e)) return xvfb, display_name
def init_uuid(self): self._loop_uuid = LOOP_PREFIX+get_hex_uuid() log("init_uuid() %s uuid=%s", self._selection, self._loop_uuid) set_string(self._clipboard, self._loop_uuid)
def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): try: cwd = os.getcwd() except: cwd = os.path.expanduser("~") warn("current working directory does not exist, using '%s'\n" % cwd) validate_encryption(opts) if opts.encoding == "help" or "help" in opts.encodings: return show_encoding_help(opts) from xpra.server.socket_util import parse_bind_ip, parse_bind_vsock, get_network_logger bind_tcp = parse_bind_ip(opts.bind_tcp) bind_udp = parse_bind_ip(opts.bind_udp) bind_ssl = parse_bind_ip(opts.bind_ssl) bind_ws = parse_bind_ip(opts.bind_ws) bind_wss = parse_bind_ip(opts.bind_wss) bind_rfb = parse_bind_ip(opts.bind_rfb, 5900) bind_vsock = parse_bind_vsock(opts.bind_vsock) assert mode in ("start", "start-desktop", "upgrade", "shadow", "proxy") starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" shadowing = mode == "shadow" proxying = mode == "proxy" clobber = upgrading or opts.use_display start_vfb = not shadowing and not proxying and not clobber if upgrading or shadowing: #there should already be one running opts.pulseaudio = False #get the display name: if shadowing and len(extra_args) == 0: if WIN32 or OSX: #just a virtual name for the only display available: display_name = ":0" else: from xpra.scripts.main import guess_X11_display dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) display_name = guess_X11_display(dotxpra) elif upgrading and len(extra_args) == 0: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: if len(extra_args) > 1: error_cb( "too many extra arguments (%i): only expected a display number" % len(extra_args)) if len(extra_args) == 1: display_name = extra_args[0] if not shadowing and not proxying and not opts.use_display: display_name_check(display_name) else: if proxying: #find a free display number: dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) all_displays = dotxpra.sockets() #ie: [("LIVE", ":100"), ("LIVE", ":200"), ...] displays = [v[1] for v in all_displays] display_name = None for x in range(1000, 20000): v = ":%s" % x if v not in displays: display_name = v break if not display_name: error_cb( "you must specify a free virtual display name to use with the proxy server" ) elif opts.use_display: #only use automatic guess for xpra displays and not X11 displays: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: # We will try to find one automaticaly # Use the temporary magic value 'S' as marker: display_name = 'S' + str(os.getpid()) if not shadowing and not proxying and not upgrading and opts.exit_with_children and not opts.start_child: error_cb( "--exit-with-children specified without any children to spawn; exiting immediately" ) atexit.register(run_cleanups) # Generate the script text now, because os.getcwd() will # change if/when we daemonize: from xpra.server.server_util import xpra_runner_shell_script, write_runner_shell_scripts, write_pidfile, find_log_dir, create_input_devices script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir) uid = int(opts.uid) gid = int(opts.gid) username = get_username_for_uid(uid) home = get_home_for_uid(uid) xauth_data = None if start_vfb: xauth_data = get_hex_uuid() ROOT = POSIX and getuid() == 0 protected_fds = [] protected_env = {} stdout = sys.stdout stderr = sys.stderr # Daemonize: if POSIX and opts.daemon: #daemonize will chdir to "/", so try to use an absolute path: if opts.password_file: opts.password_file = os.path.abspath(opts.password_file) from xpra.server.server_util import daemonize daemonize() displayfd = 0 if POSIX and opts.displayfd: try: displayfd = int(opts.displayfd) if displayfd > 0: protected_fds.append(displayfd) except ValueError as e: stderr.write("Error: invalid displayfd '%s':\n" % opts.displayfd) stderr.write(" %s\n" % e) del e # if pam is present, try to create a new session: pam = None PAM_OPEN = POSIX and envbool("XPRA_PAM_OPEN", ROOT and uid != 0) if PAM_OPEN: try: from xpra.server.pam import pam_session #@UnresolvedImport except ImportError as e: stderr.write("Error: failed to import pam module\n") stderr.write(" %s" % e) del e PAM_OPEN = False if PAM_OPEN: fdc = FDChangeCaptureContext() with fdc: pam = pam_session(username) env = { #"XDG_SEAT" : "seat1", #"XDG_VTNR" : "0", "XDG_SESSION_TYPE": "x11", #"XDG_SESSION_CLASS" : "user", "XDG_SESSION_DESKTOP": "xpra", } #maybe we should just bail out instead? if pam.start(): pam.set_env(env) items = {} if display_name.startswith(":"): items["XDISPLAY"] = display_name if xauth_data: items["XAUTHDATA"] = xauth_data pam.set_items(items) if pam.open(): #we can't close it, because we're not going to be root any more, #but since we're the process leader for the session, #terminating will also close the session #add_cleanup(pam.close) protected_env = pam.get_envlist() os.environ.update(protected_env) #closing the pam fd causes the session to be closed, #and we don't want that! protected_fds += fdc.get_new_fds() #get XDG_RUNTIME_DIR from env options, #which may not be have updated os.environ yet when running as root with "--uid=" xrd = os.path.abspath(parse_env(opts.env).get("XDG_RUNTIME_DIR", "")) if ROOT and (uid > 0 or gid > 0): #we're going to chown the directory if we create it, #ensure this cannot be abused, only use "safe" paths: if not any(x for x in ("/run/user/%i" % uid, "/tmp", "/var/tmp") if xrd.startswith(x)): xrd = "" #these paths could cause problems if we were to create and chown them: if xrd.startswith("/tmp/.X11-unix") or xrd.startswith( "/tmp/.XIM-unix"): xrd = "" if not xrd: xrd = os.environ.get("XDG_RUNTIME_DIR") xrd = create_runtime_dir(xrd, uid, gid) if xrd: #this may override the value we get from pam #with the value supplied by the user: protected_env["XDG_RUNTIME_DIR"] = xrd if opts.pidfile: write_pidfile(opts.pidfile, uid, gid) if POSIX and not ROOT: # Write out a shell-script so that we can start our proxy in a clean # environment: write_runner_shell_scripts(script) if start_vfb or opts.daemon: #we will probably need a log dir #either for the vfb, or for our own log file log_dir = opts.log_dir or "" if not log_dir or log_dir.lower() == "auto": log_dir = find_log_dir(username, uid=uid, gid=gid) if not log_dir: raise InitException( "cannot find or create a logging directory") #expose the log-dir as "XPRA_LOG_DIR", #this is used by Xdummy for the Xorg log file if "XPRA_LOG_DIR" not in os.environ: os.environ["XPRA_LOG_DIR"] = log_dir if opts.daemon: from xpra.server.server_util import select_log_file, open_log_file, redirect_std_to_log log_filename0 = select_log_file(log_dir, opts.log_file, display_name) logfd = open_log_file(log_filename0) if ROOT and (uid > 0 or gid > 0): try: os.fchown(logfd, uid, gid) except: pass stdout, stderr = redirect_std_to_log(logfd, *protected_fds) try: stderr.write("Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) except: #we tried our best, logging another error won't help pass #warn early about this: if (starting or starting_desktop) and desktop_display: print_DE_warnings(desktop_display, opts.pulseaudio, opts.notifications, opts.dbus_launch) log = get_util_logger() netlog = get_network_logger() mdns_recs = {} sockets = [] #SSL sockets: wrap_socket_fn = None need_ssl = False ssl_opt = opts.ssl.lower() if ssl_opt in TRUE_OPTIONS or bind_ssl or bind_wss: need_ssl = True if opts.bind_tcp or opts.bind_ws: if ssl_opt == "auto" and opts.ssl_cert: need_ssl = True elif ssl_opt == "tcp" and opts.bind_tcp: need_ssl = True elif ssl_opt == "www": need_ssl = True if need_ssl: from xpra.scripts.main import ssl_wrap_socket_fn try: wrap_socket_fn = ssl_wrap_socket_fn(opts, server_side=True) netlog("wrap_socket_fn=%s", wrap_socket_fn) except Exception as e: netlog("SSL error", exc_info=True) cpaths = csv("'%s'" % x for x in (opts.ssl_cert, opts.ssl_key) if x) raise InitException( "cannot create SSL socket, check your certificate paths (%s): %s" % (cpaths, e)) from xpra.server.socket_util import setup_tcp_socket, setup_udp_socket, setup_vsock_socket, setup_local_sockets, has_dual_stack min_port = int(opts.min_port) def hosts(host_str): if host_str == "*": if has_dual_stack(): #IPv6 will also listen for IPv4: return ["::"] #no dual stack, so we have to listen on both IPv4 and IPv6 explicitly: return ["0.0.0.0", "::"] return [host_str] def add_mdns(socktype, host_str, port): recs = mdns_recs.setdefault(socktype.lower(), []) for host in hosts(host_str): rec = (host, port) if rec not in recs: recs.append(rec) def add_tcp_socket(socktype, host_str, iport): if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) for host in hosts(host_str): socket = setup_tcp_socket(host, iport, socktype) sockets.append(socket) add_mdns(socktype, host, iport) def add_udp_socket(socktype, host_str, iport): if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) for host in hosts(host_str): socket = setup_udp_socket(host, iport, socktype) sockets.append(socket) add_mdns(socktype, host, iport) # Initialize the TCP sockets before the display, # That way, errors won't make us kill the Xvfb # (which may not be ours to kill at that point) netlog("setting up SSL sockets: %s", csv(bind_ssl)) for host, iport in bind_ssl: add_tcp_socket("ssl", host, iport) netlog("setting up https / wss (secure websockets): %s", csv(bind_wss)) for host, iport in bind_wss: add_tcp_socket("wss", host, iport) tcp_ssl = ssl_opt in TRUE_OPTIONS or (ssl_opt == "auto" and opts.ssl_cert) netlog("setting up TCP sockets: %s", csv(bind_tcp)) for host, iport in bind_tcp: add_tcp_socket("tcp", host, iport) if tcp_ssl: add_mdns("ssl", host, iport) netlog("setting up UDP sockets: %s", csv(bind_udp)) for host, iport in bind_udp: add_udp_socket("udp", host, iport) netlog("setting up http / ws (websockets): %s", csv(bind_ws)) for host, iport in bind_ws: add_tcp_socket("ws", host, iport) if tcp_ssl: add_mdns("wss", host, iport) if bind_rfb and (proxying or starting): log.warn("Warning: bind-rfb sockets cannot be used with '%s' mode" % mode) else: netlog("setting up rfb sockets: %s", csv(bind_rfb)) for host, iport in bind_rfb: add_tcp_socket("rfb", host, iport) netlog("setting up vsock sockets: %s", csv(bind_vsock)) for cid, iport in bind_vsock: socket = setup_vsock_socket(cid, iport) sockets.append(socket) #add_mdns("vsock", str(cid), iport) # systemd socket activation: try: from xpra.platform.xposix.sd_listen import get_sd_listen_sockets except ImportError: pass else: sd_sockets = get_sd_listen_sockets() netlog("systemd sockets: %s", sd_sockets) for stype, socket, addr in sd_sockets: sockets.append((stype, socket, addr)) netlog("%s : %s", (stype, [addr]), socket) if stype == "tcp": host, iport = addr add_mdns("tcp", host, iport) sanitize_env() if POSIX: if xrd: os.environ["XDG_RUNTIME_DIR"] = xrd os.environ["XDG_SESSION_TYPE"] = "x11" if not starting_desktop: os.environ["XDG_CURRENT_DESKTOP"] = opts.wm_name configure_imsettings_env(opts.input_method) if display_name[0] != 'S': os.environ["DISPLAY"] = display_name os.environ["CKCON_X11_DISPLAY"] = display_name else: try: del os.environ["DISPLAY"] except: pass os.environ.update(protected_env) log("env=%s", os.environ) UINPUT_UUID_LEN = 12 UINPUT_UUID_MIN_LEN = 12 UINPUT_UUID_MAX_LEN = 32 # 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, check_xvfb_process 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(UINPUT_UUID_LEN) 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 #always update as we may now have the "real" display name: os.environ["DISPLAY"] = display_name os.environ["CKCON_X11_DISPLAY"] = display_name os.environ.update(protected_env) if display_name != odisplay_name and pam: pam.set_items({"XDISPLAY": display_name}) def check_xvfb(): return check_xvfb_process(xvfb) else: def check_xvfb(): return True if POSIX and not OSX and displayfd > 0: from xpra.platform.displayfd import write_displayfd try: display = display_name[1:] log("writing display='%s' to displayfd=%i", display, displayfd) assert write_displayfd(displayfd, display), "timeout" except Exception as e: log.error("write_displayfd failed", exc_info=True) log.error("Error: failed to write '%s' to fd=%s", display_name, displayfd) log.error(" %s", str(e) or type(e)) del e try: os.close(displayfd) except: pass if not proxying: def close_display(): close_gtk_display() kill_xvfb(xvfb_pid) add_cleanup(close_display) else: close_display = None if opts.daemon: def noerr(fn, *args): try: fn(*args) except: pass log_filename1 = select_log_file(log_dir, opts.log_file, display_name) if log_filename0 != log_filename1: # we now have the correct log filename, so use it: os.rename(log_filename0, log_filename1) if odisplay_name != display_name: #this may be used by scripts, let's try not to change it: noerr(stderr.write, "Actual display used: %s\n" % display_name) noerr(stderr.write, "Actual log file name is now: %s\n" % log_filename1) noerr(stderr.flush) noerr(stdout.close) noerr(stderr.close) #we should not be using stdout or stderr from this point: del stdout del stderr if not check_xvfb(): #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 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) del e else: log("Popen(%s)=%s", cmd, (code, out, err)) if code == 0 and out.find("=") > 0: uinput_uuid = out.split("=", 1)[1] log("raw uinput uuid=%s", uinput_uuid) uinput_uuid = strtobytes(uinput_uuid.strip('\n\r"\\ ')) if uinput_uuid: if len(uinput_uuid) > UINPUT_UUID_MAX_LEN or len( uinput_uuid) < UINPUT_UUID_MIN_LEN: log.warn("Warning: ignoring invalid uinput id:") log.warn(" '%s'", uinput_uuid) uinput_uuid = None else: log.info("retrieved existing uinput id: %s", bytestostr(uinput_uuid)) 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) os.environ.update({ "HOME": home, "USER": username, "LOGNAME": username, }) shell = get_shell_for_uid(uid) if shell: os.environ["SHELL"] = shell #now we've changed uid, it is safe to honour all the env updates: configure_env(opts.env) os.environ.update(protected_env) if opts.chdir: os.chdir(opts.chdir) display = None if not proxying: no_gtk() if POSIX and not OSX and (starting or starting_desktop or shadowing): #check that we can access the X11 display: from xpra.x11.vfb_util import verify_display_ready if not verify_display_ready(xvfb, display_name, shadowing): return 1 if not PYTHON3: from xpra.x11.gtk2.gdk_display_util import verify_gdk_display #@UnusedImport else: from xpra.x11.gtk3.gdk_display_util import verify_gdk_display #@Reimport display = verify_gdk_display(display_name) if not display: return 1 #on win32, this ensures that we get the correct screen size to shadow: from xpra.platform.gui import init as gui_init gui_init() #setup unix domain socket: if not opts.socket_dir and not opts.socket_dirs: #we always need at least one valid socket dir from xpra.platform.paths import get_socket_dirs opts.socket_dirs = get_socket_dirs() local_sockets = setup_local_sockets(opts.bind, opts.socket_dir, opts.socket_dirs, display_name, clobber, opts.mmap_group, opts.socket_permissions, username, uid, gid) netlog("setting up local sockets: %s", local_sockets) for rec, cleanup_socket in local_sockets: socktype, socket, sockpath = rec #ie: ("unix-domain", sock, sockpath), cleanup_socket sockets.append(rec) netlog("%s %s : %s", socktype, sockpath, socket) add_cleanup(cleanup_socket) if opts.mdns: ssh_port = get_ssh_port() netlog("ssh %s:%s : %s", "", ssh_port, socket) if ssh_port: add_mdns("ssh", "", ssh_port) kill_dbus = None if shadowing: from xpra.platform.shadow_server import ShadowServer app = ShadowServer() elif proxying: from xpra.server.proxy.proxy_server import ProxyServer app = ProxyServer() else: if not check_xvfb(): return 1 assert starting or starting_desktop or upgrading from xpra.x11.gtk2.gdk_display_source import init_gdk_display_source init_gdk_display_source() #(now we can access the X11 server) #make sure the pid we save is the real one: if not check_xvfb(): return 1 if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) if POSIX: save_uinput_id(uinput_uuid or "") dbus_pid = -1 dbus_env = {} if clobber: #get the saved pids and env dbus_pid = get_dbus_pid() dbus_env = get_dbus_env() log("retrieved existing dbus attributes") else: assert starting or starting_desktop if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) bus_address = protected_env.get("DBUS_SESSION_BUS_ADDRESS") log("dbus_launch=%s, current DBUS_SESSION_BUS_ADDRESS=%s", opts.dbus_launch, bus_address) if opts.dbus_launch and not bus_address: #start a dbus server: def kill_dbus(): log("kill_dbus: dbus_pid=%s" % dbus_pid) if dbus_pid <= 0: return try: os.kill(dbus_pid, signal.SIGINT) except Exception as e: log.warn( "Warning: error trying to stop dbus with pid %i:", dbus_pid) log.warn(" %s", e) add_cleanup(kill_dbus) #this also updates os.environ with the dbus attributes: dbus_pid, dbus_env = start_dbus(opts.dbus_launch) if dbus_pid > 0: save_dbus_pid(dbus_pid) if dbus_env: save_dbus_env(dbus_env) log("dbus attributes: pid=%s, env=%s", dbus_pid, dbus_env) if dbus_env: os.environ.update(dbus_env) os.environ.update(protected_env) log("env=%s", os.environ) try: # This import is delayed because the module depends on gtk: from xpra.x11.bindings.window_bindings import X11WindowBindings X11Window = X11WindowBindings() if (starting or starting_desktop) and not clobber and opts.resize_display: from xpra.x11.vfb_util import set_initial_resolution set_initial_resolution(starting_desktop) except ImportError as e: log.error( "Failed to load Xpra server components, check your installation: %s" % e) return 1 if starting or upgrading: if not X11Window.displayHasXComposite(): log.error( "Xpra 'start' subcommand runs as a compositing manager") log.error( " it cannot use a display which lacks the XComposite extension!" ) return 1 if starting: #check for an existing window manager: from xpra.x11.gtk2.wm import wm_check if not wm_check(display, opts.wm_name, upgrading): return 1 log("XShape=%s", X11Window.displayHasXShape()) from xpra.x11.server import XpraServer app = XpraServer(clobber) else: assert starting_desktop from xpra.x11.desktop_server import XpraDesktopServer app = XpraDesktopServer() app.init_virtual_devices(devices) #publish mdns records: if opts.mdns: from xpra.platform.info import get_username from xpra.server.socket_util import mdns_publish mdns_info = { "display": display_name, "username": get_username(), "uuid": strtobytes(app.uuid), "platform": sys.platform, "type": app.session_type, } if opts.session_name: mdns_info["session"] = opts.session_name for mode, listen_on in mdns_recs.items(): mdns_publish(display_name, mode, listen_on, mdns_info) try: app._ssl_wrap_socket = wrap_socket_fn app.original_desktop_display = desktop_display app.exec_cwd = opts.chdir or cwd app.init(opts) app.init_components(opts) except InitException as e: log.error("xpra server initialization error:") log.error(" %s", e) return 1 except Exception as e: log.error("Error: cannot start the %s server", app.session_type, exc_info=True) log.error(str(e)) log.info("") return 1 #honour start child, html webserver, and setup child reaper if not proxying and not upgrading: if opts.exit_with_children: assert opts.start_child, "exit-with-children was specified but start-child is missing!" app.start_commands = opts.start app.start_child_commands = opts.start_child app.start_after_connect = opts.start_after_connect app.start_child_after_connect = opts.start_child_after_connect app.start_on_connect = opts.start_on_connect app.start_child_on_connect = opts.start_child_on_connect app.exec_start_commands() del opts log("%s(%s)", app.init_sockets, sockets) app.init_sockets(sockets) log("%s(%s)", app.init_when_ready, _when_ready) app.init_when_ready(_when_ready) try: #from here on, we own the vfb, even if we inherited one: if (starting or starting_desktop or upgrading) and clobber: #and it will be killed if exit cleanly: xvfb_pid = get_xvfb_pid() log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") r = 0 except Exception: log.error("server error", exc_info=True) r = -128 if r > 0: # Upgrading/exiting, so leave X and dbus servers running if close_display: _cleanups.remove(close_display) if kill_dbus: _cleanups.remove(kill_dbus) from xpra.server.server_core import ServerCore if r == ServerCore.EXITING_CODE: log.info("exiting: not cleaning up Xvfb") else: log.info("upgrading: not cleaning up Xvfb") log("cleanups=%s", _cleanups) r = 0 return r
def init_client_mmap(mmap_group=None, socket_filename=None, size=128 * 1024 * 1024, filename=None): """ Initializes an mmap area, writes the token in it and returns: (success flag, mmap_area, mmap_size, temp_file, mmap_filename) The caller must keep hold of temp_file to ensure it does not get deleted! This is used by the client. """ def rerr(): return False, False, None, 0, None, None log("init_mmap%s", (mmap_group, socket_filename, size, filename)) mmap_filename = filename mmap_temp_file = None delete = True def validate_size(size): assert size >= 64 * 1024 * 1024, "mmap size is too small: %sB (minimum is 64MB)" % std_unit( size) assert size <= 4 * 1024 * 1024 * 1024, "mmap is too big: %sB (maximum is 4GB)" % std_unit( size) try: import mmap unit = max(4096, mmap.PAGESIZE) #add 8 bytes for the mmap area control header zone: mmap_size = roundup(size + 8, unit) if WIN32: validate_size(mmap_size) if not filename: from xpra.os_util import get_hex_uuid filename = "xpra-%s" % get_hex_uuid() mmap_filename = filename mmap_area = mmap.mmap(0, mmap_size, filename) #not a real file: delete = False mmap_temp_file = None else: assert POSIX if filename: if os.path.exists(filename): fd = os.open(filename, os.O_EXCL | os.O_RDWR) mmap_size = os.path.getsize(mmap_filename) validate_size(mmap_size) #mmap_size = 4*1024*1024 #size restriction needed with ivshmem delete = False log.info("Using existing mmap file '%s': %sMB", mmap_filename, mmap_size // 1024 // 1024) else: validate_size(mmap_size) import errno flags = os.O_CREAT | os.O_EXCL | os.O_RDWR try: fd = os.open(filename, flags) mmap_temp_file = None #os.fdopen(fd, 'w') mmap_filename = filename except OSError as e: if e.errno == errno.EEXIST: log.error( "Error: the mmap file '%s' already exists", filename) return rerr() raise else: validate_size(mmap_size) import tempfile from xpra.platform.paths import get_mmap_dir mmap_dir = get_mmap_dir() subs = os.environ.copy() subs.update({ "UID": os.getuid(), "GID": os.getgid(), "PID": os.getpid(), }) mmap_dir = shellsub(mmap_dir, subs) if mmap_dir and not os.path.exists(mmap_dir): os.mkdir(mmap_dir, 0o700) if not mmap_dir or not os.path.exists(mmap_dir): raise Exception("mmap directory %s does not exist!" % mmap_dir) #create the mmap file, the mkstemp that is called via NamedTemporaryFile ensures #that the file is readable and writable only by the creating user ID try: temp = tempfile.NamedTemporaryFile(prefix="xpra.", suffix=".mmap", dir=mmap_dir) except OSError as e: log.error("Error: cannot create mmap file:") log.error(" %s", e) return rerr() #keep a reference to it so it does not disappear! mmap_temp_file = temp mmap_filename = temp.name fd = temp.file.fileno() #set the group permissions and gid if the mmap-group option is specified mmap_group = (mmap_group or "") if POSIX and mmap_group and mmap_group not in FALSE_OPTIONS: group_id = None if mmap_group == "SOCKET": group_id = get_socket_group(socket_filename) elif mmap_group.lower() == "auto": group_id = xpra_group() if not group_id and socket_filename: group_id = get_socket_group(socket_filename) elif mmap_group.lower() in TRUE_OPTIONS: log.info("parsing legacy mmap-group value '%s' as 'auto'", mmap_group) log.info(" please update your configuration") group_id = xpra_group() or get_socket_group( socket_filename) else: group_id = get_group_id(mmap_group) if group_id > 0: log("setting mmap file %s to group id=%i", mmap_filename, group_id) try: os.fchown(fd, -1, group_id) except OSError as e: log("fchown(%i, %i, %i) on %s", fd, -1, group_id, mmap_filename, exc_info=True) log.error( "Error: failed to change group ownership of mmap file to '%s':", mmap_group) log.error(" %s", e) from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP os.fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) log("using mmap file %s, fd=%s, size=%s", mmap_filename, fd, mmap_size) os.lseek(fd, mmap_size - 1, os.SEEK_SET) assert os.write(fd, b'\x00') os.lseek(fd, 0, os.SEEK_SET) mmap_area = mmap.mmap(fd, length=mmap_size) return True, delete, mmap_area, mmap_size, mmap_temp_file, mmap_filename except Exception as e: log("failed to setup mmap: %s", e, exc_info=True) log.error("Error: mmap setup failed:") log.error(" %s", e) clean_mmap(mmap_filename) return rerr()
def verify_hello(self, proto, c): remote_version = c.strget("version") verr = version_compat_check(remote_version) if verr is not None: self.disconnect_client(proto, VERSION_ERROR, "incompatible version: %s" % verr) proto.close() return False def auth_failed(msg): log.warn("Warning: authentication failed: %s", msg) self.timeout_add(1000, self.disconnect_client, proto, msg) #authenticator: username = c.strget("username") if proto.authenticator is None and proto.auth_class: try: proto.authenticator = proto.auth_class(username) except Exception as e: log.warn("error instantiating %s: %s", proto.auth_class, e) auth_failed("authentication failed") return False self.digest_modes = c.get("digest", ("hmac", )) #client may have requested encryption: cipher = c.strget("cipher") cipher_iv = c.strget("cipher.iv") key_salt = c.strget("cipher.key_salt") iterations = c.intget("cipher.key_stretch_iterations") auth_caps = {} if cipher and cipher_iv: if cipher not in ENCRYPTION_CIPHERS: log.warn("unsupported cipher: %s", cipher) auth_failed("unsupported cipher") return False encryption_key = self.get_encryption_key(proto.authenticator) if encryption_key is None: auth_failed("encryption key is missing") return False proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations) #use the same cipher as used by the client: auth_caps = new_cipher_caps(proto, cipher, encryption_key) log("server cipher=%s", auth_caps) else: auth_caps = None #verify authentication if required: if (proto.authenticator and proto.authenticator.requires_challenge() ) or c.get("challenge") is not None: challenge_response = c.strget("challenge_response") client_salt = c.strget("challenge_client_salt") log( "processing authentication with %s, response=%s, client_salt=%s, challenge_sent=%s", proto.authenticator, challenge_response, binascii.hexlify(client_salt or ""), proto.challenge_sent) #send challenge if this is not a response: if not challenge_response: if proto.challenge_sent: auth_failed( "invalid state, challenge already sent - no response!") return False if proto.authenticator: challenge = proto.authenticator.get_challenge() if challenge is None: auth_failed( "invalid state: unexpected challenge response") return False salt, digest = challenge log.info( "Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest) if digest not in self.digest_modes: auth_failed( "cannot proceed without %s digest support" % digest) return False else: log.warn( "Warning: client expects a challenge but this connection is unauthenticated" ) #fake challenge so the client will send the real hello: from xpra.os_util import get_hex_uuid salt = get_hex_uuid() + get_hex_uuid() digest = "hmac" proto.challenge_sent = True proto.send_now(("challenge", salt, auth_caps or "", digest)) return False if not proto.authenticator.authenticate(challenge_response, client_salt): auth_failed("invalid challenge response") return False log("authentication challenge passed") else: #did the client expect a challenge? if c.boolget("challenge"): log.warn( "this server does not require authentication (client supplied a challenge)" ) return auth_caps
def get_iv(): IV = None #IV = "0000000000000000" return IV or get_hex_uuid()[:16]