Exemple #1
0
def handle_socket_error(sockpath, sperms, e):
    log = get_network_logger()
    log("socket creation error", exc_info=True)
    if sockpath.startswith("/var/run/xpra") or sockpath.startswith("/run/xpra"):
        log.info("cannot create group socket '%s'", sockpath)
        log.info(" %s", e)
        dirname = sockpath[:sockpath.find("xpra")+len("xpra")]
        if not os.path.exists(dirname):
            log.info(" %s does not exist", dirname)
        #only show extra information if the socket permissions
        #would have been accessible by the group:
        elif POSIX and (sperms & 0o40):
            uid = getuid()
            username = get_username_for_uid(uid)
            groups = get_groups(username)
            log.info(" user '%s' is a member of groups: %s", username, csv(groups) or "no groups!")
            if "xpra" not in groups:
                log.info("  add 'xpra' group membership to enable group socket sharing")
            for x in path_permission_info(dirname):
                log.info("  %s", x)
    elif sockpath.startswith("/var/run/user") or sockpath.startswith("/run/user"):
        log.warn("Warning: cannot create socket '%s':", sockpath)
        log.warn(" %s", e)
        run_user = sockpath.split("/user")[0]+"/user"
        if not os.path.exists(run_user):
            log.warn(" %s does not exist", run_user)
        else:
            log.warn(" ($XDG_RUNTIME_DIR has not been created?)")
    else:
        log.error("Error: failed to create socket '%s':", sockpath)
        log.error(" %s", e)
        raise InitExit(EXIT_SOCKET_CREATION_ERROR,
                       "failed to create socket %s" % sockpath)
Exemple #2
0
def handle_socket_error(sockpath, e):
    log = get_network_logger()
    log("socket creation error", exc_info=True)
    if sockpath.startswith("/var/run/xpra") or sockpath.startswith(
            "/run/xpra"):
        log.warn("Warning: cannot create socket '%s'", sockpath)
        log.warn(" %s", e)
        dirname = sockpath[:sockpath.find("xpra") + len("xpra")]
        if not os.path.exists(dirname):
            log.warn(" %s does not exist", dirname)
        if POSIX:
            uid = getuid()
            username = get_username_for_uid(uid)
            groups = get_groups(username)
            log.warn(" user '%s' is a member of groups: %s", username,
                     csv(groups) or "no groups!")
            if "xpra" not in groups:
                log.warn("  missing 'xpra' group membership?")
            for x in path_permission_info(dirname):
                log.warn("  %s", x)
    elif sockpath.startswith("/var/run/user") or sockpath.startswith(
            "/run/user"):
        log.warn("Warning: cannot create socket '%s':", sockpath)
        log.warn(" %s", e)
        run_user = sockpath.split("/user")[0] + "/user"
        if not os.path.exists(run_user):
            log.warn(" %s does not exist", run_user)
        else:
            log.warn(" ($XDG_RUNTIME_DIR has not been created?)")
    else:
        log.error("Error: failed to create socket '%s':", sockpath)
        log.error(" %s", e)
        raise InitException("failed to create socket %s" % sockpath)
Exemple #3
0
def xpra_group():
    if POSIX:
        try:
            username = os.getgroups()
            groups = get_groups(username)
            if MMAP_GROUP in groups:
                group_id = get_group_id(MMAP_GROUP)
                if group_id >= 0:
                    return group_id
        except Exception:
            log("xpra_group()", exc_info=True)
    return 0
Exemple #4
0
def create_unix_domain_socket(sockpath, socket_permissions=0o600):
    #convert this to a umask!
    umask = 0o777 - socket_permissions
    listener = socket.socket(socket.AF_UNIX)
    listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    #bind the socket, using umask to set the correct permissions
    orig_umask = os.umask(umask)
    try:
        listener.bind(sockpath)
    finally:
        os.umask(orig_umask)
    try:
        inode = os.stat(sockpath).st_ino
    except:
        inode = -1
    #set to the "xpra" group if we are a member of it, or if running as root:
    uid = getuid()
    username = get_username_for_uid(uid)
    groups = get_groups(username)
    if uid == 0 or GROUP in groups:
        group_id = get_group_id(GROUP)
        if group_id >= 0:
            try:
                os.lchown(sockpath, -1, group_id)
            except Exception as e:
                log = get_network_logger()
                log.warn("Warning: failed to set '%s' group ownership", GROUP)
                log.warn(" on socket '%s':", sockpath)
                log.warn(" %s", e)
            #don't know why this doesn't work:
            #os.fchown(listener.fileno(), -1, group_id)
    def cleanup_socket():
        log = get_network_logger()
        try:
            cur_inode = os.stat(sockpath).st_ino
        except:
            log.info("socket '%s' already deleted", sockpath)
            return
        delpath = sockpath
        log("cleanup_socket '%s', original inode=%s, new inode=%s", sockpath,
            inode, cur_inode)
        if cur_inode == inode:
            log.info("removing socket %s", delpath)
            try:
                os.unlink(delpath)
            except:
                pass

    return listener, cleanup_socket
Exemple #5
0
    def proxy_session(self, client_proto, c, auth_caps, sessions):
        def disconnect(reason, *extras):
            log("disconnect(%s, %s)", reason, extras)
            self.send_disconnect(client_proto, reason, *extras)
        uid, gid, displays, env_options, session_options = sessions
        if POSIX:
            if getuid()==0:
                if uid==0 or gid==0:
                    log.error("Error: proxy instances cannot run as root")
                    log.error(" use a different uid and gid (ie: nobody)")
                    disconnect(AUTHENTICATION_ERROR, "cannot run proxy instances as root")
                    return
            else:
                uid = getuid()
                gid = getgid()
            username = get_username_for_uid(uid)
            groups = get_groups(username)
            log("username(%i)=%s, groups=%s", uid, username, groups)
        else:
            #the auth module recorded the username we authenticate against
            assert client_proto.authenticators
            for authenticator in client_proto.authenticators:
                username = getattr(authenticator, "username", "")
                if username:
                    break
        #ensure we don't loop back to the proxy:
        proxy_virtual_display = os.environ.get("DISPLAY")
        if proxy_virtual_display in displays:
            displays.remove(proxy_virtual_display)
        #remove proxy instance virtual displays:
        displays = [x for x in displays if not x.startswith(":proxy-")]
        #log("unused options: %s, %s", env_options, session_options)
        proc = None
        socket_path = None
        display = None
        sns = c.dictget("start-new-session")
        authlog("proxy_session: displays=%s, start_sessions=%s, start-new-session=%s", displays, self._start_sessions, sns)
        if len(displays)==0 or sns:
            if not self._start_sessions:
                disconnect(SESSION_NOT_FOUND, "no displays found")
                return
            try:
                proc, socket_path, display = self.start_new_session(username, uid, gid, sns, displays)
                log("start_new_session%s=%s", (username, uid, gid, sns, displays), (proc, socket_path, display))
            except Exception as e:
                log("start_server_subprocess failed", exc_info=True)
                log.error("Error: failed to start server subprocess:")
                log.error(" %s", e)
                disconnect(SERVER_ERROR, "failed to start a new session")
                return
        if display is None:
            display = c.strget("display")
            authlog("proxy_session: proxy-virtual-display=%s (ignored), user specified display=%s, found displays=%s", proxy_virtual_display, display, displays)
            if display==proxy_virtual_display:
                disconnect(SESSION_NOT_FOUND, "invalid display")
                return
            if display:
                if display not in displays:
                    disconnect(SESSION_NOT_FOUND, "display '%s' not found" % display)
                    return
            else:
                if len(displays)!=1:
                    disconnect(SESSION_NOT_FOUND, "please specify a display, more than one is available: %s" % csv(displays))
                    return
                display = displays[0]

        connect = c.boolget("connect", True)
        #ConnectTestXpraClient doesn't want to connect to the real session either:
        ctr = c.strget("connect_test_request")
        log("connect=%s, connect_test_request=%s", connect, ctr)
        if not connect or ctr:
            log("proxy_session: not connecting to the session")
            hello = {"display" : display}
            if socket_path:
                hello["socket-path"] = socket_path
            #echo mode if present:
            mode = sns.get("mode")
            if mode:
                hello["mode"] = mode
            client_proto.send_now(("hello", hello))
            return

        def stop_server_subprocess():
            log("stop_server_subprocess() proc=%s", proc)
            if proc and proc.poll() is None:
                proc.terminate()

        log("start_proxy(%s, {..}, %s) using server display at: %s", client_proto, auth_caps, display)
        def parse_error(*args):
            stop_server_subprocess()
            disconnect(SESSION_NOT_FOUND, "invalid display string")
            log.warn("Error: parsing failed for display string '%s':", display)
            for arg in args:
                log.warn(" %s", arg)
            raise Exception("parse error on %s: %s" % (display, args))
        opts = make_defaults_struct(username=username, uid=uid, gid=gid)
        opts.username = username
        disp_desc = parse_display_name(parse_error, opts, display)
        if uid or gid:
            disp_desc["uid"] = uid
            disp_desc["gid"] = gid
        log("display description(%s) = %s", display, disp_desc)
        try:
            server_conn = connect_to(disp_desc, opts)
        except Exception as e:
            log("cannot connect", exc_info=True)
            log.error("Error: cannot start proxy connection:")
            for x in str(e).splitlines():
                log.error(" %s", x)
            log.error(" connection definition:")
            print_nested_dict(disp_desc, prefix=" ", lchar="*", pad=20, print_fn=log.error)
            disconnect(SESSION_NOT_FOUND, "failed to connect to display")
            stop_server_subprocess()
            return
        log("server connection=%s", server_conn)

        #no other packets should be arriving until the proxy instance responds to the initial hello packet
        def unexpected_packet(packet):
            if packet:
                log.warn("Warning: received an unexpected packet on the proxy connection %s:", client_proto)
                log.warn(" %s", repr_ellipsized(packet))
        client_conn = client_proto.steal_connection(unexpected_packet)
        client_state = client_proto.save_state()
        cipher = None
        encryption_key = None
        if auth_caps:
            cipher = auth_caps.get("cipher")
            if cipher:
                encryption_key = self.get_encryption_key(client_proto.authenticators, client_proto.keyfile)
        log("start_proxy(..) client connection=%s", client_conn)
        log("start_proxy(..) client state=%s", client_state)

        #this may block, so run it in a thread:
        def do_start_proxy():
            log("do_start_proxy()")
            message_queue = MQueue()
            try:
                ioe = client_proto.wait_for_io_threads_exit(5+self._socket_timeout)
                if not ioe:
                    log.error("Error: some network IO threads have failed to terminate")
                    return
                client_conn.set_active(True)
                process = ProxyInstanceProcess(uid, gid, env_options, session_options, self._socket_dir,
                                               self.video_encoders, self.csc_modules,
                                               client_conn, disp_desc, client_state, cipher, encryption_key, server_conn, c, message_queue)
                log("starting %s from pid=%s", process, os.getpid())
                self.processes[process] = (display, message_queue)
                process.start()
                log("process started")
                popen = process._popen
                assert popen
                #when this process dies, run reap to update our list of proxy processes:
                self.child_reaper.add_process(popen, "xpra-proxy-%s" % display, "xpra-proxy-instance", True, True, self.reap)
            finally:
                #now we can close our handle on the connection:
                client_conn.close()
                server_conn.close()
                message_queue.put("socket-handover-complete")
        start_thread(do_start_proxy, "start_proxy(%s)" % client_conn)
Exemple #6
0
def create_unix_domain_socket(sockpath,
                              mmap_group=False,
                              socket_permissions="600"):
    if mmap_group:
        #when using the mmap group option, use '660'
        umask = 0o117
    else:
        #parse octal mode given as config option:
        try:
            if type(socket_permissions) == int:
                sperms = socket_permissions
            else:
                #assume octal string:
                sperms = int(socket_permissions, 8)
            assert sperms >= 0 and sperms <= 0o777
        except ValueError:
            raise ValueError(
                "invalid socket permissions (must be an octal number): '%s'" %
                socket_permissions)
        #now convert this to a umask!
        umask = 0o777 - sperms
    listener = socket.socket(socket.AF_UNIX)
    listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    #bind the socket, using umask to set the correct permissions
    orig_umask = os.umask(umask)
    try:
        listener.bind(sockpath)
    finally:
        os.umask(orig_umask)
    try:
        inode = os.stat(sockpath).st_ino
    except:
        inode = -1
    #set to the "xpra" group if we are a member of it, or if running as root:
    uid = getuid()
    username = get_username_for_uid(uid)
    groups = get_groups(username)
    if uid == 0 or GROUP in groups:
        group_id = get_group_id(GROUP)
        if group_id >= 0:
            try:
                os.lchown(sockpath, -1, group_id)
            except Exception as e:
                log = get_network_logger()
                log.warn("Warning: failed to set '%s' group ownership", GROUP)
                log.warn(" on socket '%s':", sockpath)
                log.warn(" %s", e)
            #don't know why this doesn't work:
            #os.fchown(listener.fileno(), -1, group_id)
    def cleanup_socket():
        log = get_network_logger()
        try:
            cur_inode = os.stat(sockpath).st_ino
        except:
            log.info("socket '%s' already deleted", sockpath)
            return
        delpath = sockpath
        log("cleanup_socket '%s', original inode=%s, new inode=%s", sockpath,
            inode, cur_inode)
        if cur_inode == inode:
            log.info("removing socket %s", delpath)
            try:
                os.unlink(delpath)
            except:
                pass

    return listener, cleanup_socket
Exemple #7
0
    def start_proxy(self, client_proto, c, auth_caps):
        def disconnect(reason, *extras):
            log("disconnect(%s, %s)", reason, extras)
            self.send_disconnect(client_proto, reason, *extras)

        #find the target server session:
        if not client_proto.authenticator:
            log.error("Error: the proxy server requires an authentication mode,")
            try:
                log.error(" client connection '%s' does not specify one", client_proto._conn.socktype)
            except:
                pass
            log.error(" use 'none' to disable authentication")
            disconnect(SESSION_NOT_FOUND, "no sessions found")
            return
        try:
            sessions = client_proto.authenticator.get_sessions()
        except Exception as e:
            authlog("failed to get the list of sessions", exc_info=True)
            authlog.error("Error: failed to get the list of sessions using '%s' authenticator", client_proto.authenticator)
            authlog.error(" %s", e)
            disconnect(AUTHENTICATION_ERROR, "cannot access sessions")
            return
        authlog("start_proxy(%s, {..}, %s) found sessions: %s", client_proto, auth_caps, sessions)
        if sessions is None:
            disconnect(SESSION_NOT_FOUND, "no sessions found")
            return
        uid, gid, displays, env_options, session_options = sessions
        if os.name=="posix":
            if uid==0 or gid==0:
                log.error("Error: proxy instances should not run as root")
                log.error(" use a different uid and gid (ie: nobody)")
                disconnect(AUTHENTICATION_ERROR, "cannot run proxy instances as root")
                return
            username = get_username_for_uid(uid)
            groups = get_groups(username)
            if "xpra" not in groups:
                log.error("Error: user '%s' (uid=%i) is not in the xpra group", username, uid)
                log.error(" it belongs to: %s", csv(groups) or None)
                disconnect(PERMISSION_ERROR, "user missing 'xpra' group membership")
                return
        #ensure we don't loop back to the proxy:
        proxy_virtual_display = os.environ.get("DISPLAY")
        if proxy_virtual_display in displays:
            displays.remove(proxy_virtual_display)
        #remove proxy instance virtual displays:
        displays = [x for x in displays if not x.startswith(":proxy-")]
        #log("unused options: %s, %s", env_options, session_options)
        opts = make_defaults_struct()
        display = None
        proc = None
        sns = c.dictget("start-new-session")
        authlog("start_proxy: displays=%s, start-new-session=%s", displays, bool(sns))
        if len(displays)==0 or sns:
            if self._start_sessions:
                #start a new session
                mode = sns.get("mode", "start")
                assert mode in ("start", "start-desktop", "shadow"), "invalid start-new-session mode '%s'" % mode
                sns = typedict(sns)
                display = sns.get("display")
                args = []
                if display:
                    args = [display]
                start = sns.strlistget("start")
                start_child = sns.strlistget("start-child")
                exit_with_children = sns.boolget("exit-with-children")
                exit_with_client = sns.boolget("exit-with-client")
                log("starting new server subprocess: mode=%s, socket-dir=%s, socket-dirs=%s, start=%s, start-child=%s, exit-with-children=%s, exit-with-client=%s, uid=%s, gid=%s",
                    mode, opts.socket_dir, opts.socket_dirs, start, start_child, exit_with_children, exit_with_client, uid, gid)
                try:
                    proc, socket_path = start_server_subprocess(sys.argv[0], args, mode, opts,
                                                                opts.socket_dir, opts.socket_dirs,
                                                                start, start_child,
                                                                exit_with_children, exit_with_client,
                                                                uid=uid, gid=gid)
                    display = "socket:%s" % socket_path
                except Exception as e:
                    log("start_server_subprocess failed", exc_info=True)
                    log.error("Error: failed to start server subprocess:")
                    log.error(" %s", e)
                    disconnect(SERVER_ERROR, "failed to start a new session")
                    return
                if proc:
                    self.child_reaper.add_process(proc, "server-%s" % display, "xpra start", True, True)
            else:
                disconnect(SESSION_NOT_FOUND, "no displays found")
                return
        if display is None:
            display = c.strget("display")
            authlog("start_proxy: proxy-virtual-display=%s (ignored), user specified display=%s, found displays=%s", proxy_virtual_display, display, displays)
            if display==proxy_virtual_display:
                disconnect(SESSION_NOT_FOUND, "invalid display")
                return
            if display:
                if display not in displays:
                    disconnect(SESSION_NOT_FOUND, "display '%s' not found" % display)
                    return
            else:
                if len(displays)!=1:
                    disconnect(SESSION_NOT_FOUND, "please specify a display, more than one is available: %s" % csv(displays))
                    return
                display = displays[0]

        def stop_server_subprocess():
            if proc:
                proc.terminate()

        log("start_proxy(%s, {..}, %s) using server display at: %s", client_proto, auth_caps, display)
        def parse_error(*args):
            stop_server_subprocess()
            disconnect(SESSION_NOT_FOUND, "invalid display string")
            log.warn("Error: parsing failed for display string '%s':", display)
            for arg in args:
                log.warn(" %s", arg)
            raise Exception("parse error on %s: %s" % (display, args))
        opts.username = client_proto.authenticator.username
        disp_desc = parse_display_name(parse_error, opts, display)
        if uid or gid:
            disp_desc["uid"] = uid
            disp_desc["gid"] = gid
        log("display description(%s) = %s", display, disp_desc)
        try:
            server_conn = connect_to(disp_desc, opts)
        except Exception as e:
            log("cannot connect", exc_info=True)
            log.error("Error: cannot start proxy connection:")
            log.error(" %s", e)
            log.error(" connection definition:")
            print_nested_dict(disp_desc, prefix=" ", lchar="*", pad=20, print_fn=log.error)
            disconnect(SESSION_NOT_FOUND, "failed to connect to display")
            stop_server_subprocess()
            return
        log("server connection=%s", server_conn)

        #no other packets should be arriving until the proxy instance responds to the initial hello packet
        def unexpected_packet(packet):
            if packet:
                log.warn("Warning: received an unexpected packet on the proxy connection %s:", client_proto)
                log.warn(" %s", repr_ellipsized(packet))
        client_conn = client_proto.steal_connection(unexpected_packet)
        client_state = client_proto.save_state()
        cipher = None
        encryption_key = None
        if auth_caps:
            cipher = auth_caps.get("cipher")
            if cipher:
                encryption_key = self.get_encryption_key(client_proto.authenticator, client_proto.keyfile)
        log("start_proxy(..) client connection=%s", client_conn)
        log("start_proxy(..) client state=%s", client_state)

        #this may block, so run it in a thread:
        def do_start_proxy():
            log("do_start_proxy()")
            message_queue = MQueue()
            try:
                ioe = client_proto.wait_for_io_threads_exit(5+self._socket_timeout)
                if not ioe:
                    log.error("Error: some network IO threads have failed to terminate")
                    return
                client_conn.set_active(True)
                process = ProxyInstanceProcess(uid, gid, env_options, session_options, self._socket_dir,
                                               self.video_encoders, self.csc_modules,
                                               client_conn, client_state, cipher, encryption_key, server_conn, c, message_queue)
                log("starting %s from pid=%s", process, os.getpid())
                self.processes[process] = (display, message_queue)
                process.start()
                log("process started")
                popen = process._popen
                assert popen
                #when this process dies, run reap to update our list of proxy processes:
                self.child_reaper.add_process(popen, "xpra-proxy-%s" % display, "xpra-proxy-instance", True, True, self.reap)
            finally:
                #now we can close our handle on the connection:
                client_conn.close()
                server_conn.close()
                message_queue.put("socket-handover-complete")
        start_thread(do_start_proxy, "start_proxy(%s)" % client_conn)
Exemple #8
0
    def start_proxy(self, client_proto, c, auth_caps):
        def disconnect(reason, *extras):
            log("disconnect(%s, %s)", reason, extras)
            self.send_disconnect(client_proto, reason, *extras)

        #find the target server session:
        if not client_proto.authenticator:
            log.error(
                "Error: the proxy server requires an authentication mode,")
            try:
                log.error(" client connection '%s' does not specify one",
                          client_proto._conn.socktype)
            except:
                pass
            log.error(" use 'none' to disable authentication")
            disconnect(SESSION_NOT_FOUND, "no sessions found")
            return
        try:
            sessions = client_proto.authenticator.get_sessions()
        except Exception as e:
            authlog("failed to get the list of sessions", exc_info=True)
            authlog.error(
                "Error: failed to get the list of sessions using '%s' authenticator",
                client_proto.authenticator)
            authlog.error(" %s", e)
            disconnect(AUTHENTICATION_ERROR, "cannot access sessions")
            return
        authlog("start_proxy(%s, {..}, %s) found sessions: %s", client_proto,
                auth_caps, sessions)
        if sessions is None:
            disconnect(SESSION_NOT_FOUND, "no sessions found")
            return
        uid, gid, displays, env_options, session_options = sessions
        if os.name == "posix":
            if uid == 0 or gid == 0:
                log.error("Error: proxy instances should not run as root")
                log.error(" use a different uid and gid (ie: nobody)")
                disconnect(AUTHENTICATION_ERROR,
                           "cannot run proxy instances as root")
                return
            username = get_username_for_uid(uid)
            groups = get_groups(username)
            if "xpra" not in groups:
                log("user '%s' (uid=%i) is not in the xpra group", username,
                    uid)
                log(" it belongs to: %s", csv(groups) or None)
        #ensure we don't loop back to the proxy:
        proxy_virtual_display = os.environ.get("DISPLAY")
        if proxy_virtual_display in displays:
            displays.remove(proxy_virtual_display)
        #remove proxy instance virtual displays:
        displays = [x for x in displays if not x.startswith(":proxy-")]
        #log("unused options: %s, %s", env_options, session_options)
        opts = make_defaults_struct()
        display = None
        proc = None
        sns = c.dictget("start-new-session")
        authlog("start_proxy: displays=%s, start-new-session=%s", displays,
                bool(sns))
        if len(displays) == 0 or sns:
            if self._start_sessions:
                #start a new session
                mode = sns.get("mode", "start")
                assert mode in (
                    "start", "start-desktop",
                    "shadow"), "invalid start-new-session mode '%s'" % mode
                sns = typedict(sns)
                display = sns.get("display")
                args = []
                if display:
                    args = [display]
                start = sns.strlistget("start")
                start_child = sns.strlistget("start-child")
                exit_with_children = sns.boolget("exit-with-children")
                exit_with_client = sns.boolget("exit-with-client")
                log(
                    "starting new server subprocess: mode=%s, socket-dir=%s, socket-dirs=%s, start=%s, start-child=%s, exit-with-children=%s, exit-with-client=%s, uid=%s, gid=%s",
                    mode, opts.socket_dir, opts.socket_dirs, start,
                    start_child, exit_with_children, exit_with_client, uid,
                    gid)
                try:
                    proc, socket_path = start_server_subprocess(
                        sys.argv[0],
                        args,
                        mode,
                        opts,
                        opts.socket_dir,
                        opts.socket_dirs,
                        start,
                        start_child,
                        exit_with_children,
                        exit_with_client,
                        uid=uid,
                        gid=gid)
                    display = "socket:%s" % socket_path
                except Exception as e:
                    log("start_server_subprocess failed", exc_info=True)
                    log.error("Error: failed to start server subprocess:")
                    log.error(" %s", e)
                    disconnect(SERVER_ERROR, "failed to start a new session")
                    return
                if proc:
                    self.child_reaper.add_process(proc, "server-%s" % display,
                                                  "xpra start", True, True)
            else:
                disconnect(SESSION_NOT_FOUND, "no displays found")
                return
        if display is None:
            display = c.strget("display")
            authlog(
                "start_proxy: proxy-virtual-display=%s (ignored), user specified display=%s, found displays=%s",
                proxy_virtual_display, display, displays)
            if display == proxy_virtual_display:
                disconnect(SESSION_NOT_FOUND, "invalid display")
                return
            if display:
                if display not in displays:
                    disconnect(SESSION_NOT_FOUND,
                               "display '%s' not found" % display)
                    return
            else:
                if len(displays) != 1:
                    disconnect(
                        SESSION_NOT_FOUND,
                        "please specify a display, more than one is available: %s"
                        % csv(displays))
                    return
                display = displays[0]

        def stop_server_subprocess():
            if proc:
                proc.terminate()

        log("start_proxy(%s, {..}, %s) using server display at: %s",
            client_proto, auth_caps, display)

        def parse_error(*args):
            stop_server_subprocess()
            disconnect(SESSION_NOT_FOUND, "invalid display string")
            log.warn("Error: parsing failed for display string '%s':", display)
            for arg in args:
                log.warn(" %s", arg)
            raise Exception("parse error on %s: %s" % (display, args))

        opts.username = client_proto.authenticator.username
        disp_desc = parse_display_name(parse_error, opts, display)
        if uid or gid:
            disp_desc["uid"] = uid
            disp_desc["gid"] = gid
        log("display description(%s) = %s", display, disp_desc)
        try:
            server_conn = connect_to(disp_desc, opts)
        except Exception as e:
            log("cannot connect", exc_info=True)
            log.error("Error: cannot start proxy connection:")
            log.error(" %s", e)
            log.error(" connection definition:")
            print_nested_dict(disp_desc,
                              prefix=" ",
                              lchar="*",
                              pad=20,
                              print_fn=log.error)
            disconnect(SESSION_NOT_FOUND, "failed to connect to display")
            stop_server_subprocess()
            return
        log("server connection=%s", server_conn)

        #no other packets should be arriving until the proxy instance responds to the initial hello packet
        def unexpected_packet(packet):
            if packet:
                log.warn(
                    "Warning: received an unexpected packet on the proxy connection %s:",
                    client_proto)
                log.warn(" %s", repr_ellipsized(packet))

        client_conn = client_proto.steal_connection(unexpected_packet)
        client_state = client_proto.save_state()
        cipher = None
        encryption_key = None
        if auth_caps:
            cipher = auth_caps.get("cipher")
            if cipher:
                encryption_key = self.get_encryption_key(
                    client_proto.authenticator, client_proto.keyfile)
        log("start_proxy(..) client connection=%s", client_conn)
        log("start_proxy(..) client state=%s", client_state)

        #this may block, so run it in a thread:
        def do_start_proxy():
            log("do_start_proxy()")
            message_queue = MQueue()
            try:
                ioe = client_proto.wait_for_io_threads_exit(
                    5 + self._socket_timeout)
                if not ioe:
                    log.error(
                        "Error: some network IO threads have failed to terminate"
                    )
                    return
                client_conn.set_active(True)
                process = ProxyInstanceProcess(
                    uid, gid, env_options, session_options, self._socket_dir,
                    self.video_encoders, self.csc_modules, client_conn,
                    client_state, cipher, encryption_key, server_conn, c,
                    message_queue)
                log("starting %s from pid=%s", process, os.getpid())
                self.processes[process] = (display, message_queue)
                process.start()
                log("process started")
                popen = process._popen
                assert popen
                #when this process dies, run reap to update our list of proxy processes:
                self.child_reaper.add_process(popen, "xpra-proxy-%s" % display,
                                              "xpra-proxy-instance", True,
                                              True, self.reap)
            finally:
                #now we can close our handle on the connection:
                client_conn.close()
                server_conn.close()
                message_queue.put("socket-handover-complete")

        start_thread(do_start_proxy, "start_proxy(%s)" % client_conn)