def authenticate_hmac(self, challenge_response, client_salt): self.sessions = None if not self.salt: log.error("Error: illegal challenge response received - salt cleared or unset") return None #ensure this salt does not get re-used: if client_salt is None: salt = self.salt else: salt = xor(self.salt, client_salt) self.salt = None entry = self.get_auth_info() log("authenticate(%s) auth-info=%s", self.username, entry) if entry is None: log.error("Error: authentication failed") log.error(" no password for '%s' in '%s'", self.username, self.password_filename) return None fpassword, uid, gid, displays, env_options, session_options = entry verify = hmac.HMAC(strtobytes(fpassword), strtobytes(salt), digestmod=hashlib.md5).hexdigest() log("authenticate(%s) password='******', hex(salt)=%s, hash=%s", challenge_response, fpassword, binascii.hexlify(strtobytes(salt)), verify) if not hmac.compare_digest(verify, challenge_response): log("expected '%s' but got '%s'", verify, challenge_response) log.error("Error: hmac password challenge for '%s' does not match", self.username) return False self.sessions = uid, gid, displays, env_options, session_options return True
def _test_file_auth(self, name, module, genauthdata): #no file, no go: a = self._init_auth(module) assert a.requires_challenge() p = a.get_password() assert not p, "got a password from %s: %s" % (a, p) #challenge twice is a fail assert a.get_challenge() assert not a.get_challenge() assert not a.get_challenge() for muck in (0, 1): f = tempfile.NamedTemporaryFile(prefix=name) filename = f.name with f: a = self._init_auth(module, {"password_file" : filename}) password, filedata = genauthdata(a) print("saving password file data='%s' to '%s'", filedata, filename) f.write(strtobytes(filedata)) f.flush() assert a.requires_challenge() salt, mac = a.get_challenge() assert salt assert mac=="hmac" password = strtobytes(password) client_salt = strtobytes(uuid.uuid4().hex+uuid.uuid4().hex) auth_salt = strtobytes(xor(salt, client_salt)) if muck==0: verify = hmac.HMAC(password, auth_salt, digestmod=hashlib.md5).hexdigest() assert a.authenticate(verify, client_salt) assert not a.authenticate(verify, client_salt) assert a.get_password()==password elif muck==1: for verify in ("whatever", None, "bad"): assert not a.authenticate(verify, client_salt)
def authenticate_hmac(self, challenge_response, client_salt): if not self.salt: log.error("Error: illegal challenge response received - salt cleared or unset") return None #ensure this salt does not get re-used: if client_salt is None: salt = self.salt else: salt = xor(self.salt, client_salt) self.salt = None password = self.get_password() if not password: log.error("Error: %s authentication failed", self) log.error(" no password defined for '%s'", self.username) return False verify = hmac.HMAC(strtobytes(password), strtobytes(salt), digestmod=hashlib.md5).hexdigest() log("authenticate(%s) password=%s, hex(salt)=%s, hash=%s", challenge_response, password, binascii.hexlify(strtobytes(salt)), verify) if hasattr(hmac, "compare_digest"): eq = hmac.compare_digest(verify, challenge_response) else: eq = verify==challenge_response if not eq: log("expected '%s' but got '%s'", verify, challenge_response) log.error("Error: hmac password challenge for '%s' does not match", self.username) return False return True
def get_key(password, key_salt, block_size, iterations): global backend from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=block_size, salt=strtobytes(key_salt), iterations=iterations, backend=backend) key = kdf.derive(strtobytes(password)) return key
def test_large_xor_speed(self): start = time.time() size = 1*1024*1024 #1MB zeroes = strtobytes(chr(0)*size) ones = strtobytes(chr(1)*size) count = 10 for _ in range(count): self.check_xor(zeroes, ones, ones) end = time.time() speed = size/(end-start)/1024/1024 #print("%iMB/s: took %ims on average (%s iterations)" % (speed, 1000*(end-start)/count, count)) assert speed>0, "running the xor speed test took too long"
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.has_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 password = strtobytes(password) salt = strtobytes(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 _test_hmac_auth(self, auth_class, password, **kwargs): for x in (password, "somethingelse"): a = self._init_auth(auth_class, **kwargs) assert a.requires_challenge() assert a.get_password() salt, mac = a.get_challenge() assert salt assert mac=="hmac", "invalid mac: %s" % mac client_salt = strtobytes(uuid.uuid4().hex+uuid.uuid4().hex) auth_salt = strtobytes(xor(salt, client_salt)) verify = hmac.HMAC(x, auth_salt, digestmod=hashlib.md5).hexdigest() passed = a.authenticate(verify, client_salt) assert passed == (x==password), "expected authentication to %s with %s vs %s" % (["fail", "succeed"][x==password], x, password) assert not a.authenticate(verify, client_salt)
def send_hello(self, challenge_response=None, client_salt=None): try: hello = self.make_hello_base() if self.has_password() and not challenge_response: #avoid sending the full hello: tell the server we want #a packet challenge first hello["challenge"] = True else: hello.update(self.make_hello()) except InitExit as e: log.error("error preparing connection:") log.error(" %s", e) self.quit(EXIT_INTERNAL_ERROR) return except Exception as e: log.error("error preparing connection: %s", e, exc_info=True) self.quit(EXIT_INTERNAL_ERROR) return if challenge_response: assert self.has_password(), "got a password challenge response but we don't have a password! (malicious or broken server?)" hello["challenge_response"] = challenge_response if client_salt: hello["challenge_client_salt"] = client_salt log("send_hello(%s) packet=%s", binascii.hexlify(strtobytes(challenge_response or "")), hello) self.send("hello", hello)
def get_pixbuf_from_data(rgb_data, has_alpha, w, h, rowstride): if is_gtk3(): data = array.array('B', strtobytes(rgb_data)) return GdkPixbuf.Pixbuf.new_from_data(data, GdkPixbuf.Colorspace.RGB, True, 8, w, h, rowstride, None, None) return gdk.pixbuf_new_from_data(rgb_data, gdk.COLORSPACE_RGB, has_alpha, 8, w, h, rowstride)
def test_xor_str(self): zeroes = strtobytes(chr(0)*16) ones = strtobytes(chr(1)*16) ff = strtobytes(chr(255)*16) fe = strtobytes(chr(254)*16) empty = b"" lstr = b"\0x80"*64 self.check_xor(zeroes, zeroes, zeroes) self.check_xor(ones, ones, zeroes) self.check_xor(ff, ones, fe) self.check_xor(fe, ones, ff) #feed some invalid data: self.fail_xor(ones, empty) self.fail_xor(empty, zeroes) self.fail_xor(lstr, ff) self.fail_xor(bool, int)
def send_hello(self, challenge_response=None, client_salt=None): try: hello = self.make_hello_base() if (self.password_file or os.environ.get('XPRA_PASSWORD')) and not challenge_response: #avoid sending the full hello: tell the server we want #a packet challenge first hello["challenge"] = True else: hello.update(self.make_hello()) except InitExit as e: log.error("error preparing connection:") log.error(" %s", e) self.quit(EXIT_INTERNAL_ERROR) return except Exception as e: log.error("error preparing connection: %s", e, exc_info=True) self.quit(EXIT_INTERNAL_ERROR) return if challenge_response: assert self.password_file or os.environ.get('XPRA_PASSWORD') hello["challenge_response"] = challenge_response if client_salt: hello["challenge_client_salt"] = client_salt log("send_hello(%s) packet=%s", binascii.hexlify(strtobytes(challenge_response or "")), hello) self.send("hello", hello)
def validate_backend(try_backend): import binascii from xpra.os_util import strtobytes try_backend.init() message = b"some message1234" password = "******" key_salt = DEFAULT_SALT iterations = DEFAULT_ITERATIONS block_size = DEFAULT_BLOCKSIZE key = try_backend.get_key(password, key_salt, block_size, iterations) log("validate_backend(%s) key=%s", try_backend, binascii.hexlify(key)) assert key is not None, "backend %s failed to generate a key" % try_backend enc = try_backend.get_encryptor(key, DEFAULT_IV) log("validate_backend(%s) encryptor=%s", try_backend, enc) assert enc is not None, "backend %s failed to generate an encryptor" % enc dec = try_backend.get_decryptor(key, DEFAULT_IV) log("validate_backend(%s) decryptor=%s", try_backend, dec) assert dec is not None, "backend %s failed to generate a decryptor" % enc ev = enc.encrypt(message) evs = binascii.hexlify(strtobytes(ev)) log("validate_backend(%s) encrypted(%s)=%s", try_backend, message, evs) dv = dec.decrypt(ev) log("validate_backend(%s) decrypted(%s)=%s", try_backend, evs, dv) assert dv==message log("validate_backend(%s) passed", try_backend)
def nasty_rgb_via_png_paint(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, rgb_format): log("nasty_rgb_via_png_paint%s", (cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, rgb_format)) #PIL fallback PIL = get_codec("PIL") if has_alpha: oformat = "RGBA" else: oformat = "RGB" #use frombytes rather than frombuffer to be compatible with python3 new-style buffers #this is slower, but since this codepath is already dreadfully slow, we don't care bdata = strtobytes(memoryview_to_bytes(img_data)) try: img = PIL.Image.frombytes(oformat, (width,height), bdata, "raw", rgb_format.replace("X", "A"), rowstride, 1) except ValueError as e: raise Exception("failed to parse raw %s data to %s: %s" % (rgb_format, oformat, e)) #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: png = BytesIOClass() img.save(png, format="PNG") reader = BytesIOClass(png.getvalue()) png.close() img = cairo.ImageSurface.create_from_png(reader) self.cairo_paint_surface(img, x, y) return True
def capsget(self, key, default=None): v = self.get(key) #py3k and bytes as keys... if v is None and type(key)==str: v = self.get(strtobytes(key), default) if sys.version >= '3' and type(v)==bytes: v = bytestostr(v) return v
def get_default_systemd_run(): #don't use systemd-run on CentOS / RedHat #(it causes failures with "Failed to create bus connection: No such file or directory") from xpra.os_util import load_binary_file, strtobytes data = strtobytes(load_binary_file("/etc/redhat-release") or "") if data and (data.find(b"RedHat")>=0 or data.find(b"CentOS")>=0): return "no" return "auto"
def test_env(self): for var_name in ("XPRA_PASSWORD", "SOME_OTHER_VAR_NAME"): password = strtobytes(uuid.uuid4().hex) os.environ[var_name] = password try: kwargs = {} if var_name!="XPRA_PASSWORD": kwargs["name"] = var_name self._test_hmac_auth(env_auth, password, name=var_name) finally: del os.environ[var_name]
def check(self, str_value): b = strtobytes(str_value) assert b s = bytestostr(b) assert s assert s==str_value if not _memoryview: return mv = _memoryview(b) mvb = memoryview_to_bytes(mv) mvs = bytestostr(mvb) assert mvs==str_value
def _add_chunks_to_queue(self, chunks, proto_flags, start_send_cb=None, end_send_cb=None): """ the write_lock must be held when calling this function """ counter = 0 items = [] for index, level, data in chunks: scb, ecb = None, None #fire the start_send_callback just before the first packet is processed: if counter == 0: scb = start_send_cb #fire the end_send callback when the last packet (index==0) makes it out: if index == 0: ecb = end_send_cb payload_size = len(data) actual_size = payload_size if self.cipher_out: proto_flags |= Protocol.FLAGS_CIPHER #note: since we are padding: l!=len(data) padding = (self.cipher_out_block_size - len(data) % self.cipher_out_block_size) * " " if len(padding) == 0: padded = data else: padded = data + padding actual_size = payload_size + len(padding) assert len(padded) == actual_size data = self.cipher_out.encrypt(padded) assert len(data) == actual_size log("sending %s bytes encrypted with %s padding", payload_size, len(padding)) if proto_flags & Protocol.FLAGS_NOHEADER: #for plain/text packets (ie: gibberish response) items.append((data, scb, ecb)) elif pack_header_and_data is not None and actual_size < PACKET_JOIN_SIZE: if type(data) == unicode: data = str(data) header_and_data = pack_header_and_data( actual_size, proto_flags, level, index, payload_size, data) items.append((header_and_data, scb, ecb)) else: header = pack_header(proto_flags, level, index, payload_size) items.append((header, scb, None)) items.append((strtobytes(data), None, ecb)) counter += 1 self._write_queue.put(items) self.output_packetcount += 1
def init_client_mmap(token, mmap_group=None, socket_filename=None, size=128*1024*1024): """ 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. """ if not can_use_mmap(): log.error("cannot use mmap: python version is too old?") return False, None, 0, None, None log("init_mmap(%s, %s, %s)", token, mmap_group, socket_filename) try: import mmap import tempfile from stat import S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP mmap_dir = os.getenv("TMPDIR", "/tmp") if not os.path.exists(mmap_dir): raise Exception("TMPDIR %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 temp = tempfile.NamedTemporaryFile(prefix="xpra.", suffix=".mmap", dir=mmap_dir) #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 if mmap_group and type(socket_filename)==str and os.path.exists(socket_filename): s = os.stat(socket_filename) os.fchown(fd, -1, s.st_gid) os.fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) assert size>=1024*1024, "mmap size is too small: %s (minimum is 1MB)" % to_std_unit(size) assert size<=1024*1024*1024, "mmap is too big: %s (maximum is 1GB)" % to_std_unit(size) unit = max(4096, mmap.PAGESIZE) mmap_size = roundup(size + 8, unit) log("using mmap file %s, fd=%s, size=%s", mmap_filename, fd, mmap_size) SEEK_SET = 0 #os.SEEK_SET==0 but this is not available in python2.4 os.lseek(fd, mmap_size-1, SEEK_SET) assert os.write(fd, strtobytes('\x00')) os.lseek(fd, 0, SEEK_SET) mmap_area = mmap.mmap(fd, length=mmap_size) write_mmap_token(mmap_area, token) return True, mmap_area, mmap_size, mmap_temp_file, mmap_filename except Exception, e: log.error("failed to setup mmap: %s", e, exc_info=True) clean_mmap(mmap_filename) return False, None, 0, None, None
def _add_chunks_to_queue(self, chunks, start_send_cb=None, end_send_cb=None): """ the write_lock must be held when calling this function """ counter = 0 items = [] for proto_flags,index,level,data in chunks: scb, ecb = None, None #fire the start_send_callback just before the first packet is processed: if counter==0: scb = start_send_cb #fire the end_send callback when the last packet (index==0) makes it out: if index==0: ecb = end_send_cb payload_size = len(data) actual_size = payload_size if self.cipher_out: proto_flags |= FLAGS_CIPHER #note: since we are padding: l!=len(data) padding_size = self.cipher_out_block_size - (payload_size % self.cipher_out_block_size) if padding_size==0: padded = data else: # pad byte value is number of padding bytes added padded = data + pad(self.cipher_out_padding, padding_size) actual_size += padding_size assert len(padded)==actual_size, "expected padded size to be %i, but got %i" % (len(padded), actual_size) data = self.cipher_out.encrypt(padded) assert len(data)==actual_size, "expected encrypted size to be %i, but got %i" % (len(data), actual_size) cryptolog("sending %s bytes %s encrypted with %s padding", payload_size, self.cipher_out_name, padding_size) if proto_flags & FLAGS_NOHEADER: assert not self.cipher_out #for plain/text packets (ie: gibberish response) log("sending %s bytes without header", payload_size) items.append((data, scb, ecb)) elif actual_size<PACKET_JOIN_SIZE: if type(data) not in JOIN_TYPES: data = bytes(data) header_and_data = pack_header(proto_flags, level, index, payload_size) + data items.append((header_and_data, scb, ecb)) else: header = pack_header(proto_flags, level, index, payload_size) items.append((header, scb, None)) items.append((strtobytes(data), None, ecb)) counter += 1 self._write_queue.put(items) self.output_packetcount += 1
def do_save(self, filename): log("do_save(%s)", filename) if not filename.lower().endswith(".zip"): filename = filename+".zip" basenoext, _ = os.path.splitext(os.path.basename(filename)) data = self.get_text_data() import zipfile zf = zipfile.ZipFile(filename, mode='w', compression=zipfile.ZIP_DEFLATED) try: for title, tooltip, dtype, s in data: cfile = os.path.join(basenoext, title.replace(" ", "_")+"."+dtype) info = zipfile.ZipInfo(cfile, date_time=time.localtime(time.time())) info.compress_type = zipfile.ZIP_DEFLATED #very poorly documented: info.external_attr = 0o644 << 16 info.comment = strtobytes(tooltip) zf.writestr(info, s) finally: zf.close()
def _cmp(o, r): #our own deep compare function, #ignores tuple vs list differences, #and gives us a clue about where the problem is if type(o)==type(r) and o==r: return if type(r) in (tuple, list) and type(o) in (tuple, list): assert len(r)==len(o), "list/tuple differs in length: expected %s but got %s" % (o, r) for i in range(len(r)): _cmp(o[i], r[i]) return if type(r)==dict and type(o)==dict: for k,ov in o.items(): #with py3k, the key can end up being bytes instead of string... rv = r.get(k, r.get(bytestostr(k), r.get(strtobytes(k)))) assert rv is not None, "restored dict is missing %s: %s" % (k, r) _cmp(ov, rv) return import sys if sys.version_info[0]<3 and type(o)==unicode and type(r)==str: o = o.encode("utf-8") elif type(o)==bytes and type(r)==str: o = o.decode("utf-8") elif type(o)==str and type(r)==bytes: r = r.decode("utf-8") if o==r: return print("") print("original %s:" % type(o)) print("returned %s:" % type(r)) try: print("original: %s" % binascii.hexlify(str(o))) print("returned: %s" % binascii.hexlify(str(r))) except: pass assert False, "value does not match: expected %s (%s) but got %s (%s)" % (o, type(o), r, type(r))
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_tcp, parse_bind_vsock bind_tcp = parse_bind_tcp(opts.bind_tcp) bind_ssl = parse_bind_tcp(opts.bind_ssl) 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 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 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() # if pam is present, try to create a new session: pam = None protected_fds = [] protected_env = {} 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) 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() xrd = create_runtime_dir(uid, gid) 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) stderr.write("Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) #warn early about this: if (starting or starting_desktop) and desktop_display: print_DE_warnings(desktop_display, opts.pulseaudio, opts.notifications, opts.dbus_launch) from xpra.log import Logger log = Logger("server") netlog = Logger("network") 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: need_ssl = True if opts.bind_tcp: if ssl_opt == "auto" and opts.ssl_cert: need_ssl = True elif ssl_opt == "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_vsock_socket, setup_local_sockets min_port = int(opts.min_port) netlog("setting up SSL sockets: %s", bind_ssl) for host, iport in bind_ssl: if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) _, tcp_socket, host_port = setup_tcp_socket(host, iport, "SSL") socket = ("SSL", wrap_socket_fn(tcp_socket), host_port) sockets.append(socket) rec = "ssl", [(host, iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) # 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) tcp_ssl = ssl_opt in TRUE_OPTIONS or (ssl_opt == "auto" and opts.ssl_cert) def add_tcp_mdns_rec(host, iport): rec = "tcp", [(host, iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) if tcp_ssl: #SSL is also available on this TCP socket: rec = "ssl", [(host, iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) netlog("setting up TCP sockets: %s", bind_tcp) for host, iport in bind_tcp: if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) socket = setup_tcp_socket(host, iport) sockets.append(socket) add_tcp_mdns_rec(host, iport) # VSOCK: netlog("setting up vsock sockets: %s", bind_vsock) for cid, iport in bind_vsock: socket = setup_vsock_socket(cid, iport) sockets.append(socket) rec = "vsock", [("", iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) # systemd socket activation: try: from xpra.server.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_tcp_mdns_rec(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) # Start the Xvfb server first to get the display_name if needed from xpra.server.vfb_util import start_Xvfb, check_xvfb_process, verify_display_ready, verify_gdk_display, set_initial_resolution odisplay_name = display_name xvfb = None xvfb_pid = None if start_vfb: assert not proxying and xauth_data pixel_depth = validate_pixel_depth(opts.pixel_depth) xvfb, display_name = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, xauth_data) 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}) close_display = None if not proxying: def close_display(): # Close our display(s) first, so the server dying won't kill us. # (if gtk has been loaded) gtk_mod = sys.modules.get("gtk") if gtk_mod: for d in gtk_mod.gdk.display_manager_get().list_displays(): d.close() if xvfb_pid: log.info("killing xvfb with pid %s", xvfb_pid) try: os.kill(xvfb_pid, signal.SIGTERM) except OSError as e: log.info("failed to kill xvfb process with pid %s:", xvfb_pid) log.info(" %s", e) add_cleanup(close_display) 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_process(xvfb): #xvfb problem: exit now return 1 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 os.environ.update(protected_env) if opts.chdir: os.chdir(opts.chdir) display = None if not proxying: no_gtk() if POSIX and (starting or starting_desktop or shadowing): #check that we can access the X11 display: if not verify_display_ready(xvfb, display_name, shadowing): return 1 display = verify_gdk_display(display_name) if not display: return 1 import gtk #@Reimport assert gtk #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", (socktype, [sockpath]), socket) add_cleanup(cleanup_socket) if opts.mdns: ssh_port = get_ssh_port() rec = "ssh", [("", ssh_port)] netlog("%s : %s", rec, socket) if ssh_port and rec not in mdns_recs: mdns_recs.append(rec) kill_dbus = None if shadowing: from xpra.platform.shadow_server import ShadowServer app = ShadowServer() server_type_info = "shadow" elif proxying: from xpra.server.proxy.proxy_server import ProxyServer app = ProxyServer() server_type_info = "proxy" else: 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) if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) if POSIX: 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: 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) server_type_info = "xpra" else: assert starting_desktop from xpra.x11.desktop_server import XpraDesktopServer app = XpraDesktopServer() server_type_info = "xpra desktop" #publish mdns records: if opts.mdns: from xpra.os_util import strtobytes from xpra.platform.info import get_username from xpra.server.socket_util import mdns_publish mdns_info = { "display": display_name, "username": get_username(), "uuid": strtobytes(app.uuid), "platform": sys.platform, "type": { "xpra": "seamless", "xpra desktop": "desktop" }.get(server_type_info, server_type_info), } if opts.session_name: mdns_info["session"] = opts.session_name for mode, listen_on in mdns_recs: 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", server_type_info, 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) e = app.run() log("%s()=%s", app.run, e) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") e = 0 except Exception as e: log.error("server error", exc_info=True) e = -128 if e > 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 e == ServerCore.EXITING_CODE: log.info("exiting: not cleaning up Xvfb") else: log.info("upgrading: not cleaning up Xvfb") log("cleanups=%s", _cleanups) e = 0 return e
def get_password(self): file_data = self.load_password_file() if file_data is None: return None return strtobytes(file_data)
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.has_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_salt(len(salt)) #TODO: use some key stretching algorigthm? (meh) salt = xor(salt, client_salt) if digest == b"hmac": import hmac, hashlib password = strtobytes(password) salt = strtobytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() elif digest == b"xor": #don't send XORed password unencrypted: encrypted = self._protocol.cipher_out or self._protocol.get_info( ).get("type") == "ssl" if not encrypted 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 salt = salt[:len(password)] challenge_response = strtobytes(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 get_auth_info(self): self.load_password_file() if not self.password_filedata: return None return self.password_filedata.get(strtobytes(self.username))
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: from xpra.net.crypto import crypto_backend_init, new_cipher_caps, DEFAULT_PADDING crypto_backend_init() padding_options = self.caps.strlistget("cipher.padding.options", [DEFAULT_PADDING]) auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key, padding_options) caps.update(auth_caps) #may need to bump packet size: proto.max_packet_size = maxw*maxh*4*4 file_transfer = self.caps.boolget("file-transfer") and c.boolget("file-transfer") file_size_limit = max(self.caps.intget("file-size-limit"), c.intget("file-size-limit")) file_max_packet_size = int(file_transfer) * (1024 + file_size_limit*1024*1024) self.client_protocol.max_packet_size = max(self.client_protocol.max_packet_size, file_max_packet_size) self.server_protocol.max_packet_size = max(self.server_protocol.max_packet_size, file_max_packet_size) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type=="lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type=="draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", "png", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=8: #hard to distinguish png cursors from normal cursors... try: int(packet[1]) self._packet_recompress(packet, 8, "cursor") except: self._packet_recompress(packet, 9, "cursor") elif packet_type=="window-icon": self._packet_recompress(packet, 5, "icon") elif packet_type=="send-file": if packet[6]: packet[6] = Compressed("file-data", packet[6]) elif packet_type=="send-file-chunk": if packet[3]: packet[3] = Compressed("file-chunk-data", packet[3]) elif packet_type=="challenge": from xpra.net.crypto import get_salt #client may have already responded to the challenge, #so we have to handle authentication from this end salt = packet[1] digest = packet[3] client_salt = get_salt(len(salt)) salt = xor_str(salt, client_salt) if digest!=b"hmac": self.stop("digest mode '%s' not supported", std(digest)) return password = self.session_options.get("password") if not password: self.stop("authentication requested by the server, but no password available for this session") return import hmac, hashlib password = strtobytes(password) salt = strtobytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() log.info("sending %s challenge response", digest) self.send_hello(challenge_response, client_salt) return self.queue_client_packet(packet)
def _process_send_file_chunk(self, packet): chunk_id, chunk, file_data, has_more = packet[1:5] chunk_id = net_utf8(chunk_id) filelog("_process_send_file_chunk%s", (chunk_id, chunk, "%i bytes" % len(file_data), has_more)) chunk_state = self.receive_chunks_in_progress.get(chunk_id) if not chunk_state: filelog.error("Error: cannot find the file transfer id '%r'", chunk_id) self.cancel_file(chunk_id, "file transfer id %r not found" % chunk_id, chunk) return if chunk_state[-4]: filelog("got chunk for a cancelled file transfer, ignoring it") return def progress(position, error=None): start = chunk_state[0] send_id = chunk_state[-3] filesize = chunk_state[6] self.transfer_progress_update(False, send_id, monotonic()-start, position, filesize, error) fd = chunk_state[1] if chunk_state[-1]+1!=chunk: filelog.error("Error: chunk number mismatch, expected %i but got %i", chunk_state[-1]+1, chunk) self.cancel_file(chunk_id, "chunk number mismatch", chunk) osclose(fd) progress(-1, "chunk no mismatch") return file_data = strtobytes(file_data) #update chunk number: chunk_state[-1] = chunk digest = chunk_state[8] written = chunk_state[9] try: os.write(fd, file_data) digest.update(file_data) written += len(file_data) chunk_state[9] = written except OSError as e: filelog.error("Error: cannot write file chunk") filelog.error(" %s", e) self.cancel_file(chunk_id, "write error: %s" % e, chunk) osclose(fd) progress(-1, "write error (%s)" % e) return self.send("ack-file-chunk", chunk_id, True, "", chunk) if has_more: progress(written) timer = chunk_state[-2] if timer: self.source_remove(timer) #remote end will send more after receiving the ack timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_receiving, chunk_id, chunk) chunk_state[-2] = timer return self.receive_chunks_in_progress.pop(chunk_id, None) osclose(fd) #check file size and digest then process it: filename, mimetype, printit, openit, filesize, options = chunk_state[2:8] if written!=filesize: filelog.error("Error: expected a file of %i bytes, got %i", filesize, written) progress(-1, "file size mismatch") return expected_digest = options.strget("sha1") if expected_digest and digest.hexdigest()!=expected_digest: progress(-1, "checksum mismatch") self.digest_mismatch(filename, digest, expected_digest, "sha1") return progress(written) start_time = chunk_state[0] elapsed = monotonic()-start_time mimetype = bytestostr(mimetype) filelog("%i bytes received in %i chunks, took %ims", filesize, chunk, elapsed*1000) self.process_downloaded_file(filename, mimetype, printit, openit, filesize, options)
def capsget(self, capabilities, key, default): v = capabilities.get(strtobytes(key), default) if sys.version >= '3' and type(v) == bytes: v = bytestostr(v) return v
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, desktop_display) 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 = 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: #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() 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) ws_upgrades = opts.html and (os.path.isabs(opts.html) or opts.html.lower() in TRUE_OPTIONS + ["auto"]) netlog("setting up SSL sockets: %s", csv(bind_ssl)) for host, iport in bind_ssl: add_tcp_socket("ssl", host, iport) if ws_upgrades: add_tcp_socket("wss", 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) if ws_upgrades: add_mdns("ws", host, iport) if ws_upgrades and tcp_ssl: add_mdns("wss", 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, 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: 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 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, 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 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) if opts.forward_xdg_open and os.environ.get( "DISPLAY") and local_sockets: #find one xdg-open can use to talk back to us: udpaths = [ sockpath for (stype, _, sockpath), _ in local_sockets if stype == "unix-domain" ] if udpaths: os.environ["XPRA_XDG_OPEN_SERVER_SOCKET"] = udpaths[0] 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", "") break 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 assert not PYTHON3 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 and not PYTHON3 from xpra.x11.desktop_server import XpraDesktopServer app = XpraDesktopServer() 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") 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 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") log("cleanups=%s", _cleanups) r = 0 try: return r finally: run_cleanups() import gc gc.collect()
def test_password(self): password = strtobytes(uuid.uuid4().hex) self._test_hmac_auth("password", password, value=password)
def _get_cipher(key, iv): global backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes return Cipher(algorithms.AES(key), modes.CBC(strtobytes(iv)), backend=backend)
def get(key, default=None): return capabilities.get(strtobytes(key), default)
def enc(x): try: return x.encode("utf8") except: return strtobytes(x)
def start_win32_shadow(self, username, password, new_session_dict): log("start_win32_shadow%s", (username, "..", new_session_dict)) from xpra.platform.win32.wtsapi import find_session session_info = find_session(username) if not session_info: #first, Logon: try: from xpra.platform.win32.desktoplogon_lib import Logon Logon(strtobytes(username), strtobytes(password)) except Exception: log.error("Error: failed to logon as '%s'", username, exc_info=True) #hwinstaold = set_window_station("winsta0") app_dir = get_app_dir() shadow_command = os.path.join(app_dir, "Xpra-Shadow.exe") paexec = os.path.join(app_dir, "paexec.exe") named_pipe = username.replace(" ", "_") cmd = [] exe = shadow_command #use paexec to access the GUI session: if envbool("XPRA_PAEXEC", True) and os.path.exists(paexec) and os.path.isfile(paexec): #find the session-id to shadow: if not session_info: session_info = find_session(username) if session_info: cmd = [ "paexec.exe", "-i", str(session_info["SessionID"]), "-s", ] exe = paexec else: log.warn("Warning: session not found for username '%s'", username) else: log.warn("Warning: starting without paexec, expect a black screen") cmd += [ shadow_command, "--bind=%s" % named_pipe, #"--tray=no", ] #unless explicitly stated otherwise, exit with client: if new_session_dict.get("exit-with-client", None) is not False: cmd.append("--exit-with-client=yes") from xpra.log import debug_enabled_categories if debug_enabled_categories: cmd += ["-d", ",".join(tuple(debug_enabled_categories))] env = self.get_proxy_env() env["XPRA_REDIRECT_OUTPUT"] = "1" #env["XPRA_LOG_FILENAME"] = "E:\\Shadow-Instance.log" proc = exec_command(username, cmd, exe, app_dir, env) from xpra.platform.win32.dotxpra import DotXpra dotxpra = DotXpra() for t in range(10): r = pollwait(proc, 1) if r is not None: log("pollwait=%s", r) try: log("stdout=%s", proc.stdout.read()) log("stderr=%s", proc.stderr.read()) except (OSError, AttributeError): log("failed to read stdout / stderr of subprocess", exc_info=True) if r != 0: raise Exception( "shadow subprocess failed with exit code %s" % r) else: raise Exception("shadow subprocess has already terminated") if t >= 4: state = dotxpra.get_display_state(named_pipe) log("get_display_state(%s)=%s", state) if state == DotXpra.LIVE: #TODO: test the named pipe sleep(2) break self.child_reaper.add_process(proc, "server-%s" % username, "xpra shadow", True, True) return proc, "named-pipe://%s" % named_pipe, named_pipe
def _process_send_file(self, packet): #the remote end is sending us a file basefilename, mimetype, printit, openit, filesize, file_data, options = packet[1:8] send_id = "" if len(packet)>=9: send_id = packet[8] if not self.accept_data(send_id, b"file", basefilename, printit, openit): filelog.warn("Warning: file transfer rejected for file '%s'", bytestostr(basefilename)) return options = typedict(options) if printit: l = printlog assert self.printing else: l = filelog assert self.file_transfer l("receiving file: %s", [basefilename, mimetype, printit, openit, filesize, "%s bytes" % len(file_data), options]) assert filesize>0, "invalid file size: %s" % filesize if filesize>self.file_size_limit*1024*1024: l.error("Error: file '%s' is too large:", basefilename) l.error(" %iMB, the file size limit is %iMB", filesize//1024//1024, self.file_size_limit) return #basefilename should be utf8: try: base = basefilename.decode("utf8") except: base = bytestostr(basefilename) filename, fd = safe_open_download_file(base, mimetype) self.file_descriptors.add(fd) chunk_id = options.strget("file-chunk-id") if chunk_id: chunk_id = strtobytes(chunk_id) if len(self.receive_chunks_in_progress)>=MAX_CONCURRENT_FILES: self.send("ack-file-chunk", chunk_id, False, "too many file transfers in progress", 0) os.close(fd) return digest = hashlib.sha1() chunk = 0 timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_receiving, chunk_id, chunk) chunk_state = [ monotonic_time(), fd, filename, mimetype, printit, openit, filesize, options, digest, 0, timer, chunk, ] self.receive_chunks_in_progress[chunk_id] = chunk_state self.send("ack-file-chunk", chunk_id, True, "", chunk) return #not chunked, full file: assert file_data, "no data, got %s" % (file_data,) if len(file_data)!=filesize: l.error("Error: invalid data size for file '%s'", basefilename) l.error(" received %i bytes, expected %i bytes", len(file_data), filesize) return #check digest if present: def check_digest(algo="sha1", libfn=hashlib.sha1): digest = options.get(algo) if digest: u = libfn() u.update(file_data) l("%s digest: %s - expected: %s", algo, u.hexdigest(), digest) self.check_digest(basefilename, u.hexdigest(), digest, algo) check_digest("sha1", hashlib.sha1) check_digest("md5", hashlib.md5) try: os.write(fd, file_data) finally: os.close(fd) self.do_process_downloaded_file(filename, mimetype, printit, openit, filesize, options)
def get_cursor_data(hCursor): #w, h = get_fixed_cursor_size() if not hCursor: return None x, y = 0, 0 dc = None memdc = None bitmap = None old_handle = None pixels = None try: ii = ICONINFO() ii.cbSize = ctypes.sizeof(ICONINFO) if not GetIconInfo(hCursor, ctypes.byref(ii)): raise WindowsError() #@UndefinedVariable x = ii.xHotspot y = ii.yHotspot cursorlog( "get_cursor_data(%#x) hotspot at %ix%i, hbmColor=%#x, hbmMask=%#x", hCursor, x, y, ii.hbmColor or 0, ii.hbmMask or 0) if not ii.hbmColor: #FIXME: we don't handle black and white cursors return None iie = ICONINFOEXW() iie.cbSize = ctypes.sizeof(ICONINFOEXW) if not GetIconInfoExW(hCursor, ctypes.byref(iie)): raise WindowsError() #@UndefinedVariable name = iie.szResName[:MAX_PATH] cursorlog("wResID=%i, sxModName=%s, szResName=%s", iie.wResID, iie.sxModName[:MAX_PATH], name) bm = Bitmap() if not GetObjectA(ii.hbmColor, ctypes.sizeof(Bitmap), ctypes.byref(bm)): raise WindowsError() #@UndefinedVariable cursorlog( "cursor bitmap: type=%i, width=%i, height=%i, width bytes=%i, planes=%i, bits pixel=%i, bits=%#x", bm.bmType, bm.bmWidth, bm.bmHeight, bm.bmWidthBytes, bm.bmPlanes, bm.bmBitsPixel, bm.bmBits or 0) w = bm.bmWidth h = bm.bmHeight dc = GetDC(None) assert dc, "failed to get a drawing context" memdc = CreateCompatibleDC(dc) assert memdc, "failed to get a compatible drawing context from %s" % dc bitmap = CreateCompatibleBitmap(dc, w, h) assert bitmap, "failed to get a compatible bitmap from %s" % dc old_handle = SelectObject(memdc, bitmap) #check if icon is animated: UINT_MAX = 2**32 - 1 if not DrawIconEx(memdc, 0, 0, hCursor, w, h, UINT_MAX, 0, 0): cursorlog("cursor is animated!") #if not DrawIcon(memdc, 0, 0, hCursor): if not DrawIconEx(memdc, 0, 0, hCursor, w, h, 0, 0, win32con.DI_NORMAL): raise WindowsError() #@UndefinedVariable buf_size = bm.bmWidthBytes * h buf = ctypes.create_string_buffer(b"", buf_size) r = GetBitmapBits(bitmap, buf_size, ctypes.byref(buf)) cursorlog("get_cursor_data(%#x) GetBitmapBits(%#x, %#x, %#x)=%i", hCursor, bitmap, buf_size, ctypes.addressof(buf), r) if r == 0: cursorlog.error("Error: failed to copy screen bitmap data") return None elif r != buf_size: cursorlog.warn( "Warning: invalid cursor buffer size, got %i bytes but expected %i", r, buf_size) return None else: #32-bit data: pixels = bytearray(strtobytes(buf.raw)) has_alpha = False has_pixels = False for i in range(len(pixels) // 4): has_pixels = has_pixels or pixels[i * 4] != 0 or pixels[ i * 4 + 1] != 0 or pixels[i * 4 + 2] != 0 has_alpha = has_alpha or pixels[i * 4 + 3] != 0 if has_pixels and has_alpha: break if has_pixels and not has_alpha: #generate missing alpha - don't ask me why for i in range(len(pixels) // 4): if pixels[i * 4] != 0 or pixels[i * 4 + 1] != 0 or pixels[i * 4 + 2] != 0: pixels[i * 4 + 3] = 0xff return [0, 0, w, h, x, y, hCursor, bytes(pixels), strtobytes(name)] except Exception as e: cursorlog("get_cursor_data(%#x)", hCursor, exc_info=True) cursorlog.error("Error: failed to grab cursor:") cursorlog.error(" %s", str(e) or type(e)) return None finally: if old_handle: SelectObject(memdc, old_handle) if bitmap: DeleteObject(bitmap) if memdc: DeleteDC(memdc) if dc: ReleaseDC(None, dc)
def bytesget(self, k, default=None): v = self.capsget(k, default) if v is None: return default from xpra.os_util import strtobytes return strtobytes(v)
def hexstr(v): return binascii.hexlify(strtobytes(v))
def get_password(self) -> str: file_data = self.load_password_file() if file_data is None: return None return strtobytes(file_data)
def make_websocket_accept_hash(key): GUID = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" accept = sha1(strtobytes(key) + GUID).digest() return b64encode(accept)
def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): assert mode in ( "start", "start-desktop", "upgrade", "upgrade-desktop", "shadow", "proxy", ) 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] starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" upgrading_desktop = mode == "upgrade-desktop" shadowing = mode == "shadow" proxying = mode == "proxy" clobber = upgrading or upgrading_desktop or opts.use_display start_vfb = not (shadowing or proxying or clobber) if not proxying and PYTHON3 and POSIX and not OSX: #we don't support wayland servers, #so make sure GDK will use the X11 backend: os.environ["GDK_BACKEND"] = "x11" 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 opts.start_child, "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 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 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 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 or proxying or upgrading or upgrading_desktop) 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, 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 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 (OSError, IOError): 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() from xpra.net.socket_util import get_network_logger, setup_local_sockets, create_sockets sockets = create_sockets(opts, error_cb) sanitize_env() 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 else: try: del os.environ["DISPLAY"] except KeyError: pass 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: 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): log("found XAUTHORITY=%s", 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_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 opts.daemon: def noerr(fn, *args): try: fn(*args) except Exception: 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 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 from xpra.server.dbus.dbus_start import start_dbus dbus_pid, dbus_env = start_dbus(opts.dbus_launch) if dbus_env: os.environ.update(dbus_env) display = None if not proxying: if POSIX and not OSX: no_gtk() if 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 log("X11 display is ready") no_gtk() from xpra.x11.gtk_x11.gdk_display_source import verify_gdk_display display = verify_gdk_display(display_name) if not display: return 1 log("GDK can access the display") #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() #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 += 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) 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) app.init_sockets(sockets) app.init_dbus(dbus_pid, dbus_env) if not shadowing and (xvfb_pid or clobber): app.init_display_pid(xvfb_pid) app.original_desktop_display = desktop_display del opts if not app.server_ready(): return 1 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("") app.cleanup() return 1 try: log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") app.cleanup() return 0 except Exception: log.error("server error", exc_info=True) app.cleanup() return -128 else: if r>0: r = 0 return r
def enc(x): try: return bytestostr(x).encode("utf8") except UnicodeEncodeError: return strtobytes(x)
def b(s): try: return s.encode("utf8") except: return strtobytes(s)
def _parse_security_result(self, packet): self.share = packet != b"\0" log("parse_security_result: sharing=%s, sending ClientInit with session-name=%s", self.share, self.session_name) #send ClientInit self._packet_parser = self._parse_rfb w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift = self._get_rfb_pixelformat() packet = struct.pack(b"!HH"+PIXEL_FORMAT+b"I", w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift, 0, 0, 0, len(self.session_name))+strtobytes(self.session_name) self.send(packet) self._process_packet_cb(self, [b"authenticated"]) return 1
def _bell_signaled(self, wm, event): log("bell signaled on window %#x", get_xwindow(event.window)) if not self.bell: return wid = 0 if event.window!=get_default_root_window() and event.window_model is not None: try: wid = self._window_to_id[event.window_model] except: pass log("_bell_signaled(%s,%r) wid=%s", wm, event, wid) for ss in self._server_sources.values(): ss.bell(wid, event.device, event.percent, event.pitch, event.duration, event.bell_class, event.bell_id, strtobytes(event.bell_name or ""))
def do_update_server_settings(self, settings, reset=False, dpi=0, double_click_time=0, double_click_distance=(-1, -1), antialias={}, cursor_size=-1): if not self._xsettings_enabled: log("ignoring xsettings update: %s", settings) return if reset: #FIXME: preserve serial? (what happens when we change values which had the same serial?) self.reset_settings() self._settings = {} if self._default_xsettings: #try to parse default xsettings into a dict: try: for _, prop_name, value, _ in self._default_xsettings[1]: self._settings[prop_name] = value except Exception as e: log("failed to parse %s", self._default_xsettings) log.warn("Warning: failed to parse default XSettings:") log.warn(" %s", e) old_settings = dict(self._settings) log("server_settings: old=%s, updating with=%s", nonl(old_settings), nonl(settings)) log( "overrides: dpi=%s, double click time=%s, double click distance=%s", dpi, double_click_time, double_click_distance) log("overrides: antialias=%s", antialias) self._settings.update(settings) from xpra.x11.xsettings_prop import XSettingsTypeInteger, XSettingsTypeString, BLACKLISTED_XSETTINGS for k, v in settings.items(): #cook the "resource-manager" value to add the DPI and/or antialias values: if k == b"resource-manager" and (dpi > 0 or antialias or cursor_size > 0): value = bytestostr(v) #parse the resources into a dict: values = {} options = value.split("\n") for option in options: if not option: continue parts = option.split(":\t", 1) if len(parts) != 2: log("skipped invalid option: '%s'", option) continue if parts[0] in BLACKLISTED_XSETTINGS: log("skipped blacklisted option: '%s'", option) continue values[parts[0]] = parts[1] if cursor_size > 0: values["Xcursor.size"] = cursor_size if dpi > 0: values["Xft.dpi"] = dpi values["Xft/DPI"] = dpi * 1024 values["gnome.Xft/DPI"] = dpi * 1024 if antialias: ad = typedict(antialias) subpixel_order = "none" sss = tuple(self._server_sources.values()) if len(sss) == 1: #only honour sub-pixel hinting if a single client is connected #and only when it is not using any scaling (or overriden with SCALED_FONT_ANTIALIAS): ss = sss[0] ds_unscaled = getattr(ss, "desktop_size_unscaled", None) ds_scaled = getattr(ss, "desktop_size", None) if SCALED_FONT_ANTIALIAS or (not ds_unscaled or ds_unscaled == ds_scaled): subpixel_order = ad.strget("orientation", "none").lower() values.update({ "Xft.antialias": ad.intget("enabled", -1), "Xft.hinting": ad.intget("hinting", -1), "Xft.rgba": subpixel_order, "Xft.hintstyle": _get_antialias_hintstyle(ad) }) log("server_settings: resource-manager values=%s", nonl(values)) #convert the dict back into a resource string: value = '' for vk, vv in values.items(): value += "%s:\t%s\n" % (vk, vv) #record the actual value used self._settings[b"resource-manager"] = value v = value.encode("utf-8") #cook xsettings to add various settings: #(as those may not be present in xsettings on some platforms.. like win32 and osx) if k==b"xsettings-blob" and \ (self.double_click_time>0 or self.double_click_distance!=(-1, -1) or antialias or dpi>0): #start by removing blacklisted options: def filter_blacklisted(): serial, values = v new_values = [] for _t, _n, _v, _s in values: if bytestostr(_n) in BLACKLISTED_XSETTINGS: log("skipped blacklisted option %s", (_t, _n, _v, _s)) else: new_values.append((_t, _n, _v, _s)) return serial, new_values v = filter_blacklisted() def set_xsettings_value(name, value_type, value): #remove existing one, if any: serial, values = v new_values = [(_t, _n, _v, _s) for (_t, _n, _v, _s) in values if _n != name] new_values.append((value_type, name, value, 0)) return serial, new_values def set_xsettings_int(name, value): if value < 0: #not set, return v unchanged return v return set_xsettings_value(name, XSettingsTypeInteger, value) if dpi > 0: v = set_xsettings_int(b"Xft/DPI", dpi * 1024) if double_click_time > 0: v = set_xsettings_int(b"Net/DoubleClickTime", self.double_click_time) if antialias: ad = typedict(antialias) v = set_xsettings_int(b"Xft/Antialias", ad.intget("enabled", -1)) v = set_xsettings_int(b"Xft/Hinting", ad.intget("hinting", -1)) v = set_xsettings_value( b"Xft/RGBA", XSettingsTypeString, ad.strget("orientation", "none").lower()) v = set_xsettings_value(b"Xft/HintStyle", XSettingsTypeString, _get_antialias_hintstyle(ad)) if double_click_distance != (-1, -1): #some platforms give us a value for each axis, #but X11 only has one, so take the average try: x, y = double_click_distance if x > 0 and y > 0: d = iround((x + y) / 2.0) d = max(1, min(128, d)) #sanitize it a bit v = set_xsettings_int(b"Net/DoubleClickDistance", d) except Exception as e: log.warn( "error setting double click distance from %s: %s", double_click_distance, e) if k not in old_settings or v != old_settings[k]: if k == b"xsettings-blob": self.set_xsettings(v) elif k == b"resource-manager": from xpra.x11.gtk_x11.prop import prop_set p = "RESOURCE_MANAGER" log("server_settings: setting %s to %s", nonl(p), nonl(v)) prop_set(self.root_window, p, "latin1", strtobytes(v).decode("latin1")) else: log.warn("Warning: unexpected setting '%s'", bytestostr(k))
def capsget(self, capabilities, key, default): v = capabilities.get(strtobytes(key), default) if PYTHON3 and isinstance(v, bytes): v = bytestostr(v) return v
def _test_file_auth(self, mod_name, genauthdata, display_count=0): #no file, no go: a = self._init_auth(mod_name) assert a.requires_challenge() p = a.get_passwords() assert not p, "got passwords from %s: %s" % (a, p) #challenge twice is a fail assert a.get_challenge(get_digests()) assert not a.get_challenge(get_digests()) assert not a.get_challenge(get_digests()) #muck: # 0 - OK # 1 - bad: with warning about newline # 2 - verify bad passwords # 3 - verify no password for muck in (0, 1, 2, 3): with TempFileContext(prefix=mod_name) as context: f = context.file filename = context.filename with f: a = self._init_auth(mod_name, filename=filename) password, filedata = genauthdata(a) #print("saving password file data='%s' to '%s'" % (filedata, filename)) if muck != 3: f.write(strtobytes(filedata)) if muck == 1: f.write(b"\n") f.flush() assert a.requires_challenge() salt, mac = a.get_challenge(get_digests()) assert salt assert mac in get_digests() assert mac != "xor" password = strtobytes(password) client_salt = strtobytes(uuid.uuid4().hex + uuid.uuid4().hex)[:len(salt)] salt_digest = a.choose_salt_digest(get_digests()) assert salt_digest auth_salt = strtobytes( gendigest(salt_digest, client_salt, salt)) if muck == 0: digestmod = get_digest_module(mac) verify = hmac.HMAC(password, auth_salt, digestmod=digestmod).hexdigest() assert self.capsauth( a, verify, client_salt), "%s failed" % a.authenticate if display_count > 0: sessions = a.get_sessions() assert len(sessions) >= 3 displays = sessions[2] assert len( displays ) == display_count, "expected %i displays but got %i : %s" % ( display_count, len(sessions), sessions) assert not self.capsauth( a, verify, client_salt), "authenticated twice!" passwords = a.get_passwords() assert len( passwords ) == 1, "expected just one password in file, got %i" % len( passwords) assert password in passwords else: for verify in ("whatever", None, "bad"): assert not self.capsauth(a, verify, client_salt) return a
def test_password(self): password = strtobytes(uuid.uuid4().hex) self._test_hmac_auth(password_auth, password, value=password)
def test_bytes(self): assert strtobytes(b"hello") == b"hello"
def parse_server_capabilities(self): for c in CLIENT_BASES: if not c.parse_server_capabilities(self): log.info("failed to parse server capabilities in %s", c) return False c = self.server_capabilities self.server_session_name = strtobytes(c.rawget("session_name", b"")).decode("utf-8") set_name("Xpra", self.session_name or self.server_session_name or "Xpra") self.server_platform = c.strget("platform") self.server_sharing = c.boolget("sharing") self.server_sharing_toggle = c.boolget("sharing-toggle") self.server_lock = c.boolget("lock") self.server_lock_toggle = c.boolget("lock-toggle") self.server_keyboard = c.boolget("keyboard", True) self.server_toggle_keyboard_sync = self.server_keyboard and c.boolget( "toggle_keyboard_sync", True) self.server_pointer = c.boolget("pointer", True) self.server_start_new_commands = c.boolget("start-new-commands") if self.server_start_new_commands: self.server_xdg_menu = c.dictget("xdg-menu", None) if self.start_new_commands or self.start_child_new_commands: if self.server_start_new_commands: self.after_handshake(self.send_start_new_commands) else: log.warn("Warning: cannot start new commands") log.warn(" the feature is currently disabled on the server") self.server_commands_info = c.boolget("server-commands-info") self.server_commands_signals = c.strlistget("server-commands-signals") self.server_readonly = c.boolget("readonly") if self.server_readonly and not self.readonly: log.info("server is read only") self.readonly = True if not self.server_keyboard and self.keyboard_helper: #swallow packets: def nosend(*_args): pass self.keyboard_helper.send = nosend i = platform_name( self._remote_platform, c.strlistget("platform.linux_distribution") or c.strget("platform.release", "")) r = self._remote_version if self._remote_revision: r += "-r%s" % self._remote_revision mode = c.strget("server.mode", "server") bits = c.intget("python.bits", 32) log.info("Xpra %s server version %s %i-bit", mode, std(r), bits) if i: log.info(" running on %s", std(i)) if c.boolget("proxy"): proxy_hostname = c.strget("proxy.hostname") proxy_platform = c.strget("proxy.platform") proxy_release = c.strget("proxy.platform.release") proxy_version = c.strget("proxy.version") proxy_version = c.strget("proxy.build.version", proxy_version) proxy_distro = c.strget("proxy.linux_distribution") msg = "via: %s proxy version %s" % (platform_name( proxy_platform, proxy_distro or proxy_release), std(proxy_version or "unknown")) if proxy_hostname: msg += " on '%s'" % std(proxy_hostname) log.info(msg) return True
def capsget(self, capabilities, key, default): v = capabilities.get(strtobytes(key), default) if sys.version >= '3' and type(v)==bytes: v = bytestostr(v) return v
def get_pixbuf_from_data(rgb_data, has_alpha, w, h, rowstride): data = array.array('B', strtobytes(rgb_data)) return GdkPixbuf.Pixbuf.new_from_data(data, GdkPixbuf.Colorspace.RGB, has_alpha, 8, w, h, rowstride, None, None)
from xpra.gtk_common.gobject_util import no_arg_signal, SIGNAL_RUN_LAST from xpra.gtk_common.gtk_util import GetClipboard, selection_owner_set, selection_add_target, selectiondata_get_selection, selectiondata_get_target, selectiondata_get_data, selectiondata_get_data_type, selectiondata_get_format, selectiondata_set, clipboard_request_contents, set_clipboard_data, PROPERTY_CHANGE_MASK from xpra.gtk_common.nested_main import NestedMainLoop from xpra.net.compression import Compressible from xpra.os_util import WIN32, POSIX, monotonic_time, strtobytes, bytestostr, hexstr, get_hex_uuid, is_X11, is_Wayland from xpra.util import csv, envint, envbool, repr_ellipsized, typedict, first_time from xpra.platform.features import CLIPBOARD_GREEDY MIN_CLIPBOARD_COMPRESSION_SIZE = 512 MAX_CLIPBOARD_PACKET_SIZE = 4 * 1024 * 1024 MAX_CLIPBOARD_RECEIVE_SIZE = envint("XPRA_MAX_CLIPBOARD_RECEIVE_SIZE", -1) MAX_CLIPBOARD_SEND_SIZE = envint("XPRA_MAX_CLIPBOARD_SEND_SIZE", -1) from xpra.platform.features import CLIPBOARDS as PLATFORM_CLIPBOARDS ALL_CLIPBOARDS = [strtobytes(x) for x in PLATFORM_CLIPBOARDS] CLIPBOARDS = PLATFORM_CLIPBOARDS CLIPBOARDS_ENV = os.environ.get("XPRA_CLIPBOARDS") if CLIPBOARDS_ENV is not None: CLIPBOARDS = CLIPBOARDS_ENV.split(",") CLIPBOARDS = [strtobytes(x).upper().strip() for x in CLIPBOARDS] del CLIPBOARDS_ENV TEST_DROP_CLIPBOARD_REQUESTS = envint("XPRA_TEST_DROP_CLIPBOARD") STORE_ON_EXIT = envbool("XPRA_CLIPBOARD_STORE_ON_EXIT", True) DELAY_SEND_TOKEN = envint("XPRA_DELAY_SEND_TOKEN", 100) LOOP_DISABLE = envbool("XPRA_CLIPBOARD_LOOP_DISABLE", True) LOOP_PREFIX = os.environ.get("XPRA_CLIPBOARD_LOOP_PREFIX", "Xpra-Clipboard-Loop-Detection:")
def parse_encoding_caps(self, c): self.set_encoding(c.strget("encoding", None), None) #encoding options (filter): #1: these properties are special cased here because we #defined their name before the "encoding." prefix convention, #or because we want to pass default values (zlib/lz4): for k, ek in { "initial_quality": "initial_quality", "quality": "quality", }.items(): if k in c: self.encoding_options[ek] = c.intget(k) for k, ek in { "zlib": "rgb_zlib", "lz4": "rgb_lz4", }.items(): if k in c: self.encoding_options[ek] = c.boolget(k) #2: standardized encoding options: for k in c.keys(): #yaml gives us str.. k = strtobytes(k) if k.startswith(b"theme.") or k.startswith(b"encoding.icons."): self.icons_encoding_options[k.replace(b"encoding.icons.", b"").replace( b"theme.", b"")] = c.get(k) elif k.startswith(b"encoding."): stripped_k = k[len(b"encoding."):] if stripped_k in ( b"transparency", b"rgb_zlib", b"rgb_lz4", b"rgb_lzo", ): v = c.boolget(k) elif stripped_k in (b"initial_quality", b"initial_speed", b"min-quality", b"quality", b"min-speed", b"speed"): v = c.intget(k) else: v = c.get(k) self.encoding_options[stripped_k] = v log("encoding options: %s", self.encoding_options) log("icons encoding options: %s", self.icons_encoding_options) #handle proxy video: add proxy codec to video helper: pv = self.encoding_options.boolget("proxy.video") proxylog("proxy.video=%s", pv) if pv: #enabling video proxy: try: self.parse_proxy_video() except Exception: proxylog.error("failed to parse proxy video", exc_info=True) sc = self.encoding_options.get("scaling.control", self.scaling_control) if sc is not None: #"encoding_options" are exposed via "xpra info", #so we can't have None values in there (bencoder would choke) self.default_encoding_options["scaling.control"] = sc q = self.encoding_options.intget("quality", self.default_quality) #0.7 onwards: if q > 0: self.default_encoding_options["quality"] = q mq = self.encoding_options.intget("min-quality", self.default_min_quality) if mq > 0 and (q <= 0 or q > mq): self.default_encoding_options["min-quality"] = mq s = self.encoding_options.intget("speed", self.default_speed) if s > 0: self.default_encoding_options["speed"] = s ms = self.encoding_options.intget("min-speed", self.default_min_speed) if ms > 0 and (s <= 0 or s > ms): self.default_encoding_options["min-speed"] = ms log("default encoding options: %s", self.default_encoding_options) self.auto_refresh_delay = c.intget("auto_refresh_delay", 0) #are we going to need a cuda context? common_encodings = tuple(x for x in self.encodings if x in self.server_encodings) from xpra.codecs.loader import has_codec if "jpeg" in common_encodings and has_codec("enc_nvjpeg"): from xpra.codecs.cuda_common.cuda_context import get_device_context self.cuda_device_context = get_device_context( self.encoding_options) log.warn("cuda_device_context=%s", self.cuda_device_context)