xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #2125: hp.patch

File hp.patch, 79.5 KB (added by brief, 19 months ago)
  • src/xpra/server/auth/sqlauthbase.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/auth/sqlauthbase.py b/src/xpra/server/auth/sqlauthbase.py
    a b  
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
    7 from xpra.util import csv, parse_simple_dict
     7import datetime
     8import secrets
     9from abc import abstractmethod, ABC
     10
     11from typing import Type
     12
     13from xpra.util import parse_simple_dict
    814from xpra.os_util import getuid, getgid
    915from xpra.server.auth.sys_auth_base import SysAuthenticator, log
    1016
     
    1218class SQLAuthenticator(SysAuthenticator):
    1319
    1420    def __init__(self, username, **kwargs):
    15         self.password_query = kwargs.pop("password_query", "SELECT password FROM users WHERE username=(%s)")
    16         self.sessions_query = kwargs.pop("sessions_query",
    17                                          "SELECT uid, gid, displays, env_options, session_options "+
    18                                          "FROM users WHERE username=(%s) AND password=(%s)")
     21        self.password_query = kwargs.pop("password_query", DatabaseBaseQueries.password_get)
     22        self.sessions_query_by_username = kwargs.pop("sessions_query", DatabaseBaseQueries.sessions_query_by_username)
     23        self.sessions_get = kwargs.pop("sessions_get", DatabaseBaseQueries.sessions_get)
     24        self.sessions_get_by_token = kwargs.pop("sessions_get_by_token", DatabaseBaseQueries.sessions_get_by_token)
     25        self.sessions_add = kwargs.pop("sessions_add", DatabaseBaseQueries.sessions_add)
     26        self.sessions_update = kwargs.pop("sessions_update", DatabaseBaseQueries.sessions_update)
     27        self.user_query = kwargs.pop("user_query", DatabaseBaseQueries.users_get)
     28        self.user_session_add = kwargs.pop("user_session_add", DatabaseBaseQueries.user_session_add)
     29
    1930        super().__init__(username, **kwargs)
    2031        self.authenticate_check = self.authenticate_hmac
     32       
     33        self.allow_proxy_registration = str(kwargs.get("register")) == "1"
     34        self.allow_proxy_update = str(kwargs.get("update")) == "1"
     35        self.hole_punch = str(kwargs.get("hole_punch")) == "1"
     36        self.supports_token = self.allow_proxy_update
    2137
     38        self.sessionAuthenticatedViaToken = None
     39
    2240    def db_cursor(self, *sqlargs):
    2341        raise NotImplementedError()
    2442
     
    4462            displays = []
    4563            env_options = {}
    4664            session_options = {}
     65            updated = ""
     66            source = ""
     67            update_token = ""
     68            session_id = 0
    4769            if len(data)>2:
    4870                displays = [x.strip() for x in str(data[2]).split(",")]
    4971            if len(data)>3:
    5072                env_options = parse_simple_dict(str(data[3]), ";")
    5173            if len(data)>4:
    5274                session_options = parse_simple_dict(str(data[4]), ";")
     75            if len(data) > 5:
     76                updated = str(data[5])
     77            if len(data) > 6:
     78                source = str(data[6])
     79            if len(data) > 7:
     80                update_token = str(data[7])
     81            if len(data) > 8:
     82                session_id = int(data[8])
    5383        except Exception as e:
    5484            log("parse_session_data() error on row %s", data, exc_info=True)
    5585            log.error("Error: sqlauth database row parsing problem:")
    5686            log.error(" %s", e)
    5787            return None
    58         return uid, gid, displays, env_options, session_options
     88        return uid, gid, displays, env_options, session_options, updated, source, update_token, session_id
    5989
     90    def get_user_id(self):
     91        cursor = self.db_cursor(self.user_query, (self.username,))
     92        return cursor.fetchone()["id"]
    6093
     94    # add a new session
     95    def proxy_register(self, update_token, displays = "", uid = "nobody", gid = "nobody", env_options = "", session_options = ""):
     96   
     97        updated = datetime.datetime.now()
     98        source = "register"
     99
     100        user_id = self.get_user_id()
     101   
     102        cursor = self.db_cursor(self.sessions_add, (uid, gid, displays, env_options, session_options, updated, source, update_token))
     103        session_id = cursor.lastrowid
     104
     105        self.db_cursor(self.user_session_add, (user_id, session_id))
     106       
     107        return session_id
     108
     109    # update a session
     110    def update_session(self, update_token, displays=None, uid=None, gid=None, env_options=None, session_options=None):
     111   
     112        cursor = self.db_cursor(self.sessions_get_by_token, (update_token,))
     113        sessionRow = cursor.fetchone()
     114
     115        desc = cursor.description
     116        column_names = [col[0] for col in desc]
     117        session = dict(zip(column_names, sessionRow))
     118   
     119        updated = False
     120   
     121        if displays is not None:
     122            session["displays"] = displays
     123            updated = True
     124   
     125        if uid is not None:
     126            session["uid"] = uid
     127            updated = True
     128
     129        if gid is not None:
     130            session["gid"] = gid
     131            updated = True
     132
     133        if env_options is not None:
     134            session["env_options"] = env_options
     135            updated = True
     136
     137        if session_options is not None:
     138            session["session_options"] = session_options
     139            updated = True
     140           
     141        if updated:
     142            session["updated"] = datetime.datetime.now()
     143            session["source"] = "update"
     144   
     145            self.db_cursor(self.sessions_update,
     146                                (session["uid"], session["gid"], session["displays"], session["env_options"],
     147                                 session["session_options"], session["updated"], session["source"], session["id"]))
     148   
     149        return True
     150
     151    def authenticate_with_token(self, c):
     152   
     153        proxy_update_request = c.boolget("update_request", False)
     154        proxy_update_mode = c.boolget("token_mode", False)
     155        if not proxy_update_request and not proxy_update_mode:
     156            return False
     157       
     158        # update_token = c.strget("token") # TODO how to get token from url (session_desc) (display_desc?)
     159        update_token = 'tadf3eAuRxDE3ZjMcGeuDaUIGo6hC0RnbC2Lh5Xbsy0LAOP9JroujyYDf4wMam5Am8wJsElmcsQmnhVinXFOwA'
     160        session = self.get_session_by_token(update_token)
     161        if not session:
     162            return False
     163   
     164        self.sessionAuthenticatedViaToken = session
     165        return True
     166
     167class QueriesBase(ABC):
     168    @staticmethod
     169    @abstractmethod
     170    def users_get():
     171        pass
     172   
     173    @staticmethod
     174    @abstractmethod
     175    def users_get_with_password():
     176        pass
     177
     178    @staticmethod
     179    @abstractmethod
     180    def password_get():
     181        pass
     182
     183    @staticmethod
     184    @abstractmethod
     185    def users_add():
     186        pass
     187
     188    @staticmethod
     189    @abstractmethod
     190    def users_delete():
     191        pass
     192
     193
     194    @staticmethod
     195    @abstractmethod
     196    def sessions_query():
     197        pass
     198
     199    @staticmethod
     200    @abstractmethod
     201    def sessions_query_by_username():
     202        pass
     203   
     204    @staticmethod
     205    @abstractmethod
     206    def sessions_get():
     207        pass
     208
     209    @staticmethod
     210    @abstractmethod
     211    def sessions_get_by_token():
     212        pass
     213
     214    @staticmethod
     215    @abstractmethod
     216    def sessions_add():
     217        pass
     218
     219    @staticmethod
     220    @abstractmethod
     221    def sessions_update():
     222        pass
     223
     224    @staticmethod
     225    @abstractmethod
     226    def sessions_delete():
     227        pass
     228
     229
     230    @staticmethod
     231    @abstractmethod
     232    def user_session_add():
     233        pass
     234
     235    @staticmethod
     236    @abstractmethod
     237    def user_session_delete_by_user():
     238        pass
     239
     240    @staticmethod
     241    @abstractmethod
     242    def user_session_delete_by_session():
     243        pass
     244
     245class DatabaseBaseQueries(QueriesBase):
     246    users_get = (
     247        "SELECT id, username, password, email "
     248        "FROM users "
     249        "WHERE username=(%s)")
     250    users_get_with_password = (
     251        "SELECT id, username, password, email "
     252        "FROM users "
     253        "WHERE username=(%s) "
     254        "AND password=(%s)")
     255    password_get = "SELECT password FROM users WHERE username=(%s)"
     256    users_add = (
     257        "INSERT INTO users(username, password, email) "
     258        "VALUES(%s, %s, %s)")
     259    users_delete = "DELETE FROM users WHERE username=(%s)"
     260   
     261    sessions_query = (
     262        "SELECT u.id AS user_id, u.username, u.password, u.email, s.id AS session_id, s.uid, s.gid, s.displays, s.env_options, s.session_options, s.updated, s.source, s.update_token "
     263        "FROM sessions AS s "
     264        "JOIN user_session AS us ON us.session = s.id "
     265        "LEFT JOIN users AS u ON u.id = us.user "
     266        "ORDER BY u.id, s.id")
     267    sessions_query_by_username = (
     268        "SELECT s.uid, s.gid, s.displays, s.env_options, s.session_options, s.updated, s.source, s.update_token, s.id "
     269        "FROM sessions AS s "
     270        "JOIN user_session AS us ON us.session = s.id "
     271        "JOIN users AS u ON u.id = us.user "
     272        "WHERE u.username=(%s)")
     273    sessions_get = (
     274        "SELECT uid, gid, displays, env_options, session_options, updated, source, update_token, id "
     275        "FROM sessions "
     276        "WHERE id=(%s)")
     277    sessions_get_by_token = (
     278        "SELECT uid, gid, displays, env_options, session_options, updated, source, update_token, id "
     279        "FROM sessions "
     280        "WHERE update_token=(%s)")
     281    sessions_add = (
     282        "INSERT INTO sessions "
     283        "(uid, gid, displays, env_options, session_options, updated, source, update_token) "
     284        "VALUES(%s, %s, %s, %s, %s, %s, %s, %s)")
     285    sessions_update = (
     286        "UPDATE sessions "
     287        "SET uid = %s, "
     288        "gid = %s, "
     289        "displays = %s, "
     290        "env_options = %s, "
     291        "session_options = %s, "
     292        "updated = %s, "
     293        "source = %s "
     294        "WHERE id=(%s)")
     295    sessions_delete = "DELETE FROM sessions WHERE id=(%s)"
     296   
     297    user_session_add = (
     298        "INSERT INTO user_session "
     299        "(user, session) "
     300        "VALUES(%s, %s)")
     301    user_session_delete_by_user = "DELETE FROM user_session WHERE user=(%s)"
     302    user_session_delete_by_session = "DELETE FROM user_session WHERE session=(%s)"
     303   
     304
    61305class DatabaseUtilBase:
    62306
    63307    def __init__(self, uri):
    64308        self.uri = uri
    65309        self.param = "?"
     310        self.queries: Type[QueriesBase] = DatabaseBaseQueries
    66311
    67312    def exec_database_sql_script(self, cursor_cb, *sqlargs):
    68313        raise NotImplementedError()
    69314
    70315    def create(self):
    71         sql = ("CREATE TABLE users ("
    72                "username VARCHAR(255) NOT NULL, "
    73                "password VARCHAR(255), "
    74                "uid VARCHAR(63), "
    75                "gid VARCHAR(63), "
    76                "displays VARCHAR(8191), "
    77                "env_options VARCHAR(8191), "
    78                "session_options VARCHAR(8191))")
    79         self.exec_database_sql_script(None, sql)
     316        sql_users = ("CREATE TABLE users ("
     317            "id INTEGER PRIMARY KEY NOT NULL, "
     318            "username VARCHAR(255) NOT NULL, "
     319            "password VARCHAR(255), "
     320            "email VARCHAR(255), "
     321            "UNIQUE(username)"
     322            ")")
     323        self.exec_database_sql_script(None, sql_users)
     324       
     325        sql_sessions = ("CREATE TABLE sessions ("
     326            "id INTEGER PRIMARY KEY NOT NULL, "
     327            "uid VARCHAR(63), "
     328            "gid VARCHAR(63), "
     329            "displays VARCHAR(8191), "
     330            "env_options VARCHAR(8191), "
     331            "session_options VARCHAR(8191), "
     332            "updated VARCHAR(8191), "
     333            "source VARCHAR(8191), "
     334            "update_token VARCHAR(8191) NOT NULL, "
     335            "UNIQUE(update_token)"
     336            ")")
     337        self.exec_database_sql_script(None, sql_sessions)
     338       
     339        sql_user_session = ("CREATE TABLE user_session ("
     340            "user int NOT NULL, "
     341            "session int NOT NULL, "
     342            "PRIMARY KEY (user, session), "
     343            "FOREIGN KEY(user) REFERENCES users(id), "
     344            "FOREIGN KEY(session) REFERENCES sessions(id)"
     345            ")")
     346        self.exec_database_sql_script(None, sql_user_session)
    80347
    81     def add_user(self, username, password, uid=getuid(), gid=getgid(),
     348    def add_user(self, username, password, email =""):
     349        self.exec_database_sql_script(None, self.queries.users_add, (str(username), str(password), str(email)))
     350
     351    def add_session(self, username, uid=getuid(), gid=getgid(),
    82352                 displays="", env_options="", session_options=""):
    83         sql = "INSERT INTO users(username, password, uid, gid, displays, env_options, session_options) "+\
    84               "VALUES(%s, %s, %s, %s, %s, %s, %s)" % ((self.param,)*7)
    85         self.exec_database_sql_script(None, sql,
    86                                         (username, password, uid, gid, displays, env_options, session_options))
     353   
     354        update_token = secrets.token_urlsafe(64)
     355       
     356        user_cursor = self.exec_database_sql_script(None, self.queries.users_get, (username,))
     357        user_row = user_cursor.fetchone()
     358        if not user_row:
     359            print("no user found with name: %", username)
     360            user_cursor.close()
     361            return
     362        user_id = user_row.id
     363        print("found id:"%user_id)
     364        user_cursor.close()
     365   
     366        session_cursor = self.exec_database_sql_script(None, self.queries.sessions_add,
     367                                      (uid, gid, displays, env_options, session_options, datetime.datetime.now(),
     368                                       "commandline", update_token))
     369        session_id = session_cursor.lastrowid
     370        session_cursor.close()
     371       
     372        user_session_cursor = self.exec_database_sql_script(None, self.queries.user_session_add,
     373                                      (user_id, session_id))
     374        user_session_cursor.close()
     375
     376        print("add session success")
     377        print("session_id  :" % session_id)
     378        print("update_token:" % update_token)
    87379
    88380    def remove_user(self, username, password=None):
    89         sql = "DELETE FROM users WHERE username=%s" % self.param
     381        user_sql = self.queries.users_get
    90382        sqlargs = (username, )
    91383        if password:
    92             sql += " AND password=%s" % self.param
     384            user_sql = self.queries.users_get_with_password
    93385            sqlargs = (username, password)
    94         self.exec_database_sql_script(None, sql, sqlargs)
     386        users_cursor = self.exec_database_sql_script(None, user_sql, sqlargs)
     387        user = users_cursor.fetchone()
     388        user_id = user.id
     389       
     390        self.exec_database_sql_script(None, self.queries.user_session_delete_by_user, user_id)
    95391
     392        self.exec_database_sql_script(None, self.queries.users_delete, username)
     393
     394    def remove_session(self, session_id):
     395        self.exec_database_sql_script(None, self.queries.user_session_delete_by_session, session_id)
     396       
     397        self.exec_database_sql_script(None, self.queries.sessions_delete, session_id)
     398       
    96399    def list_users(self):
    97         fields = ("username", "password", "uid", "gid", "displays", "env_options", "session_options")
     400        fields = ("user_id", "username", "password", "email", "session_id", "uid", "gid", "displays", "env_options", "session_options", "updated", "source", "update_token")
    98401        def fmt(values, sizes):
    99402            s = ""
    100403            for i, field in enumerate(values):
     
    121424            for row in rows:
    122425                print(fmt(row, sizes))
    123426            cursor.close()
    124         sql = "SELECT %s FROM users" % csv(fields)
    125         self.exec_database_sql_script(cursor_callback, sql)
     427        self.exec_database_sql_script(cursor_callback, self.queries.sessions_query, )
    126428
    127429    def authenticate(self, username, password):
    128430        auth_class = self.get_authenticator_class()
     
    146448        print("usage:")
    147449        print(" %s %s create" % (argv[0], conn_str))
    148450        print(" %s %s list" % (argv[0], conn_str))
    149         print(" %s %s add username password [uid, gid, displays, env_options, session_options]" % (argv[0], conn_str))
    150         print(" %s %s remove username [password]" % (argv[0], conn_str))
     451        print(" %s %s add_user username password [email]" % (argv[0], conn_str))
     452        print(" %s %s add_session username [uid, gid, displays, env_options, session_options]" % (argv[0], conn_str))
     453        print(" %s %s remove_user username [password]" % (argv[0], conn_str))
     454        print(" %s %s remove_session session_id" % (argv[0], conn_str))
    151455        print(" %s %s authenticate username password" % (argv[0], conn_str))
    152456        return 1
    153457    from xpra.platform import program_context
     
    162466            if l!=3:
    163467                return usage()
    164468            dbutil.create()
    165         elif cmd=="add":
    166             if l<5 or l>10:
     469        elif cmd=="add_user":
     470            if l not in (5, 6):
    167471                return usage()
    168472            dbutil.add_user(*argv[3:])
    169         elif cmd=="remove":
     473        elif cmd=="add_session":
     474            if l<4 or l>9:
     475                return usage()
     476            dbutil.add_session(*argv[3:])
     477        elif cmd=="remove_user":
    170478            if l not in (4, 5):
    171479                return usage()
    172480            dbutil.remove_user(*argv[3:])
     481        elif cmd=="remove_session":
     482            if l not in 4:
     483                return usage()
     484            dbutil.remove_session(*argv[3:])
    173485        elif cmd=="list":
    174486            if l!=3:
    175487                return usage()
  • src/xpra/server/proxy/proxy_server.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/proxy/proxy_server.py b/src/xpra/server/proxy/proxy_server.py
    a b  
    55# later version. See the file COPYING for details.
    66
    77import os
     8import secrets
    89import sys
    910import time
    1011from multiprocessing import Queue as MQueue, freeze_support #@UnresolvedImport
    1112from gi.repository import GLib
    1213
     14from xpra.net.address import address, addressFromString, addressFromTuple
    1315from xpra.util import (
    1416    LOGIN_TIMEOUT, AUTHENTICATION_ERROR, SESSION_NOT_FOUND, SERVER_ERROR,
    1517    repr_ellipsized, print_nested_dict, csv, envfloat, envbool, envint, typedict,
     
    9597        self._socket_timeout = PROXY_SOCKET_TIMEOUT
    9698        self._ws_timeout = PROXY_WS_TIMEOUT
    9799
     100        self.hp_update_conns = []
     101
    98102    def init(self, opts):
    99103        log("ProxyServer.init(%s)", opts)
    100104        self.pings = int(opts.pings)
     
    312316                    self.send_disconnect(proto, "timeout")
    313317            self.timeout_add(10*1000, force_exit_request_client)
    314318            return
     319   
     320        if is_req("register"):
     321            self.process_register(proto, c)
     322            return
     323       
     324        if is_req("update"):
     325            self.process_update(proto, c)
     326            return
     327       
    315328        self.proxy_auth(proto, c, auth_caps)
    316329
    317330    def proxy_auth(self, client_proto, c, auth_caps):
     
    331344            return
    332345        sessions = None
    333346        authenticator = None
     347        hole_punch_authenticator = False
    334348        for authenticator in client_proto.authenticators:
    335349            try:
    336350                auth_sessions = authenticator.get_sessions()
    337351                authlog("proxy_auth %s.get_sessions()=%s", authenticator, auth_sessions)
    338352                if auth_sessions:
    339353                    sessions = auth_sessions
     354                    hole_punch_authenticator = authenticator.hole_punch
    340355                    break
    341356            except Exception as e:
    342357                authlog("failed to get the list of sessions from %s", authenticator, exc_info=True)
     
    348363        if sessions is None:
    349364            disconnect(SESSION_NOT_FOUND, "no sessions found")
    350365            return
    351         self.proxy_session(client_proto, c, auth_caps, sessions)
     366       
     367        authlog("proxy_auth(%s, {..}, %s) found session: %s", client_proto, auth_caps, session_selected)
     368        self.proxy_session(client_proto, c, auth_caps, session_selected, hole_punch_authenticator)
    352369
    353     def proxy_session(self, client_proto, c, auth_caps, sessions):
     370    def proxy_session(self, client_proto, c, auth_caps, session, hole_punch_authenticator):
    354371        def disconnect(reason, *extras):
    355372            log("disconnect(%s, %s)", reason, extras)
    356373            self.send_disconnect(client_proto, reason, *extras)
    357         uid, gid, displays, env_options, session_options = sessions
     374        uid, gid, displays, env_options, session_options, updated, source, update_token, session_id = session
    358375        if POSIX:
    359376            if getuid()==0:
    360377                if uid==0 or gid==0:
     
    461478            disp_desc["gid"] = gid
    462479        log("display description(%s) = %s", display, disp_desc)
    463480        try:
    464             server_conn = connect_to(disp_desc, opts)
     481            if self.hole_punch and opts.hole_punch:
     482                opts.local_port = client_proto._conn.local[1]
     483                server_conn = self.hp_update_conns[0]   # TODO
     484            else:
     485                server_conn = connect_to(disp_desc, opts)
    465486        except Exception as e:
    466487            log("cannot connect", exc_info=True)
    467488            log.error("Error: cannot start proxy connection:")
     
    495516            if env_options:
    496517                log.warn("environment options are ignored in threaded mode")
    497518            from xpra.server.proxy.proxy_instance_thread import ProxyInstanceThread
     519            hp_local_server_address = addressFromString(env_options["hp-local-server-address"])
    498520            pit = ProxyInstanceThread(session_options, self.video_encoders, self.pings,
    499521                                      client_proto, server_conn,
    500                                       disp_desc, cipher, encryption_key, c)
     522                                      disp_desc, cipher, encryption_key, c, hole_punch_authenticator, hp_local_server_address)
    501523            pit.stopped = self.reap
    502524            pit.run()
    503525            self.instances[pit] = (False, display, None)
     
    531553                process = ProxyInstanceProcess(uid, gid, env_options, session_options, self._socket_dir,
    532554                                               self.video_encoders, self.pings,
    533555                                               client_conn, disp_desc, client_state,
    534                                                cipher, encryption_key, server_conn, c, message_queue)
     556                                               cipher, encryption_key, server_conn, c, message_queue, authenticator.hole_punch, hp_local_server_address)
    535557                log("starting %s from pid=%s", process, os.getpid())
    536558                self.instances[process] = (True, display, message_queue)
    537559                process.start()
     
    686708                    info["instances"] = instances_info
    687709                    info["proxies"] = len(instances)
    688710        return info
     711
     712    # acts upon a proxy register request:
     713    # - register new session
     714    # - return session_id, update_token
     715    def process_register(self, proto, c):
     716        if proto and proto.authenticators:
     717            update_token = secrets.token_urlsafe(64)
     718   
     719            displays = c.strget("displays")
     720            uid = c.strget("uid")
     721            gid = c.strget("gid")
     722            env_options = c.strget("env_options")
     723            session_options = c.strget("session_options")
     724           
     725            for authenticator in proto.authenticators:
     726                if authenticator.allow_proxy_registration:
     727                    session_id = authenticator.proxy_register(update_token, displays, uid, gid, env_options,
     728                                                                    session_options)
     729                    if session_id:
     730                        log("registration processed")
     731   
     732                        payload = {"success": True, "mode": "register", "session_id": session_id,
     733                                   "update_token": update_token}
     734                        proto.send_now(("hello", payload))
     735                        return
     736
     737        # no success on any authenticator
     738        log("registration processing failed")
     739
     740        payload = {"success": False, "mode": "register"}
     741        proto.send_now(("hello", payload))
     742       
     743
     744    # acts upon a proxy update request:
     745    # - update the session from token
     746    # - return success
     747    def process_update(self, proto, c):
     748        if proto and proto.authenticators:
     749            remoteDisplays = c.strget("displays", None)
     750            uid = c.strget("uid", None)
     751            gid = c.strget("gid", None)
     752            env_options = c.strget("env_options", None)
     753            session_options = c.strget("session_options", None)
     754            token_mode = c.boolget("token_mode", True)
     755            add_delete = c.boolget("add_delete", True)
     756
     757           
     758            server_local_address = addressFromTuple(c.tupleget("hp-local-server-address", ""))
     759            server_remote_address = address(proto._conn.remote[0], proto._conn.remote[1])
     760
     761            displays = "tcp://" + server_remote_address + "/" # TODO
     762           
     763            env_options = "hp-local-server-address=" + server_local_address
     764           
     765            for authenticator in proto.authenticators:
     766                if authenticator.allow_proxy_update:
     767                    session = authenticator.sessionAuthenticatedViaToken
     768
     769                    update = False
     770                    if token_mode and session and authenticator.supports_token:          # token_mode
     771                        # TODO O ?
     772           
     773                        db_uid, db_gid, db_displays, db_env_options, db_session_options, db_updated, db_source, db_update_token, db_session_id = session
     774                        update = authenticator.update_session(db_update_token, displays, uid, gid, env_options, session_options)
     775                       
     776                    elif not token_mode and session is None:                            # credentials_mode
     777                        if add_delete:
     778                            # TODO O insert session
     779                            update = True
     780                        else:
     781                            # TODO O delete session
     782                            update = True
     783                           
     784                    if update:
     785                         # start_thread(self.keep_alive, "KeepAlive", daemon=True, args=(update_token, displays))
     786               
     787                        log.error(" == HP == PROXY  successfully updated")
     788                   
     789                        payload = {"success": True, "mode": "update"}
     790                        proto.send_now(("hello", payload))
     791                        self.hp_update_conns.append(proto._conn)
     792                        return
     793
     794        # no success on any authenticator
     795        log.error(" == HP == PROXY  updated error")
     796
     797        payload = {"success": False, "mode": "update"}
     798        proto.send_now(("hello", payload))
     799
     800# def keep_alive(self, update_token, client):
     801#     # try to keep client alive
     802#     # TODO O repeat
     803#     # TODO O exponential back-off
     804#
     805#     keep_alive_options = options
     806#     retry_display_desc = pick_display(error_cb, keep_alive_options, [client])
     807#
     808#     from xpra.client.gobject_client_base import KeepAliveClient
     809#     retry_app = KeepAliveClient(keep_alive_options, update_token)  # TODO O
     810#
     811#     try:
     812#         connect_to_server(retry_app, retry_display_desc, keep_alive_options)
     813#     except Exception:
     814#         retry_app.cleanup()
     815#         raise
     816#
     817#     do_run_client(retry_app)
     818 No newline at end of file
  • src/xpra/net/socket_util.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/net/socket_util.py b/src/xpra/net/socket_util.py
    a b  
    385385        listener = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    386386        sockaddr = res[0][-1]
    387387    listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     388    if hasattr(socket, 'SO_REUSEPORT'):
     389        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    388390    log("%s.bind(%s)", listener, sockaddr)
    389391    listener.bind(sockaddr)
    390392    return listener
  • src/xpra/server/server_core.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/server_core.py b/src/xpra/server/server_core.py
    a b  
    1313import signal
    1414import platform
    1515import threading
     16import traceback
     17from typing import Dict
    1618from weakref import WeakKeyDictionary
    1719from time import sleep, time
    1820
     21from xpra.net.address import address, addressFromTuple
     22from xpra.net.punch_hole import Puncher
     23from xpra.scripts.main import pick_display
    1924from xpra.version_util import (
    2025    XPRA_VERSION, full_version_str, version_compat_check, get_version_info_full,
    2126    get_platform_info, get_host_info,
    2227    )
    2328from xpra.scripts.server import deadly_signal
    2429from xpra.server.server_util import write_pidfile, rm_pidfile
    25 from xpra.scripts.config import InitException, parse_bool, parse_with_unit, TRUE_OPTIONS, FALSE_OPTIONS
     30from xpra.scripts.config import InitException, parse_bool, parse_with_unit, TRUE_OPTIONS, FALSE_OPTIONS, \
     31    make_defaults_struct
    2632from xpra.net.common import may_log_packet, SOCKET_TYPES, MAX_PACKET_SIZE
    2733from xpra.net.socket_util import (
    2834    hosts, mdns_publish, peek_connection, PEEK_TIMEOUT_MS,
     
    190196        self.server_idle_timeout = 0
    191197        self.server_idle_timer = None
    192198        self.bandwidth_limit = 0
    193 
     199        self.proxy_sessions = {}
     200        self.hole_punch = True
     201       
    194202        self.init_uuid()
    195203        sanity_checks()
    196204
     
    243251        self.init_ssl(opts)
    244252        if self.pidfile:
    245253            self.pidinode = write_pidfile(self.pidfile)
    246 
     254        self.proxy_sessions = opts.proxy_sessions
     255        self.registerProxies = opts.registerProxies
    247256
    248257    def init_ssl(self, opts):
    249258        self.ssl_mode = opts.ssl
     
    361370        self.idle_add(self.reset_server_timeout)
    362371        self.idle_add(self.server_is_ready)
    363372        self.idle_add(self.print_run_info)
     373        self.idle_add(self.update_proxies)
    364374        self.do_run()
    365375        log("run()")
    366376        return 0
     
    564574        authlog("get_auth_module(%s, %s, {..})", socket_type, auth_str)
    565575        #separate options from the auth module name
    566576        #either with ":" or "," as separator
     577        # ie: --bind-tcp=0.0.0.0:10000,auth=sqlite:filename=./userlist.sdb:register=1:update=1:hole_punch=1
    567578        scpos = auth_str.find(":")
    568579        cpos = auth_str.find(",")
    569         if cpos<0 or scpos<cpos:
    570             parts = auth_str.split(":", 1)
     580        separator = ","
     581        if cpos < 0 or scpos < cpos:
     582            separator = ":"
    571583        else:
    572             parts = auth_str.split(",", 1)
     584            separator = ","
     585        parts = auth_str.split(separator, 1)
    573586        auth = parts[0]
    574587        auth_options = {}
    575588        if len(parts)>1:
    576             auth_options = parse_simple_dict(parts[1])
     589            auth_options = parse_simple_dict(parts[1], separator)
    577590        auth_options["exec_cwd"] = self.exec_cwd
    578591        try:
    579592            if auth=="sys":
     
    803816        for sock_def, options in self._socket_info.items():
    804817            socktype, sock, info, _ = sock_def
    805818            netlog("init_sockets(%s) will add %s socket %s (%s)", self._socket_info, socktype, sock, info)
    806             self.socket_info[sock] = info
    807             self.socket_options[sock] = options
    808             self.idle_add(self.add_listen_socket, socktype, sock, options)
     819            self.idle_add(self.add_listen_socket, socktype, sock, info, options)
    809820            if socktype=="unix-domain" and info:
    810821                try:
    811822                    p = os.path.abspath(info)
     
    16901701                auth_failed(str(e))
    16911702                return
    16921703
     1704        allowed_token_requests = {"update"}
     1705        is_token_request = False
     1706
     1707        # verify only update_request is processed with tokens
     1708        def is_allowed_token_req():
     1709            for allowed_token_request in allowed_token_requests:
     1710                if c.boolget("%s_request"%allowed_token_request):
     1711                    return True
     1712   
     1713            return False
     1714
     1715        if is_allowed_token_req():
     1716            for authenticator in proto.authenticators:
     1717                if not authenticator.supports_token:
     1718                    continue
     1719                   
     1720                #this authentication module supports token
     1721                if authenticator.authenticate_with_token(c):
     1722                    authenticator.passed = True
     1723                    authlog("authentication passed for %s (token provided)", authenticator)
     1724                    is_token_request = True
     1725                   
     1726
    16931727        digest_modes = c.strtupleget("digest", ("hmac", ))
    16941728        salt_digest_modes = c.strtupleget("salt-digest", ("xor",))
    16951729        #client may have requested encryption:
     
    17211755            auth_caps = new_cipher_caps(proto, cipher, encryption_key, padding_options)
    17221756            authlog("server cipher=%s", auth_caps)
    17231757        else:
    1724             if proto.encryption and conn.socktype in ENCRYPTED_SOCKET_TYPES:
     1758            if proto.encryption and conn.socktype in ENCRYPTED_SOCKET_TYPES and not is_token_request:
    17251759                authlog("client does not provide encryption tokens")
    17261760                auth_failed("missing encryption tokens")
    17271761                return
     
    18651899        if is_req("info"):
    18661900            self.send_hello_info(proto)
    18671901            return True
     1902        #if is_req("keep-alive"):
     1903        #    self.process_keep_alive(proto)
     1904        #    return True
     1905
    18681906        if self._closing:
    18691907            self.disconnect_client(proto, SERVER_EXIT, "server is shutting down")
    18701908            return True
     
    22042242            if conn:
    22052243                conn.remote = bfrom
    22062244        protocol.process_udp_data(uuid, seqno, synchronous, chunk, chunks, data, bfrom)
     2245
     2246    def update_proxies(self):
     2247        def report_status(status: str):
     2248            log.error(status)
     2249           
     2250        if self.registerProxies:
     2251            local_server_addresses = []
     2252
     2253            for listen_socket in self.listen_sockets.values():
     2254                if listen_socket.socktype != "tcp":
     2255                    continue
     2256               
     2257                address = addressFromTuple(listen_socket.info)
     2258                local_server_addresses.append(address)
     2259           
     2260            for proxy in self.registerProxies:
     2261                # try to update proxy with token
     2262                log.error(proxy)
     2263                # TODO O repeat
     2264                # TODO O exponential back-off
     2265           
     2266                update_options = make_defaults_struct()
     2267                proxy_display_desc = pick_display(log, update_options, [proxy])
     2268           
     2269                token_mode = True
     2270                if proxy_display_desc.get('update_token', None) is None:
     2271                    # credentials_mode
     2272                    token_mode = False
     2273
     2274                from xpra.client.gobject_client_base import UpdateProxyClient
     2275                for local_server_address in local_server_addresses:
     2276                    update_app = UpdateProxyClient(token_mode, True, local_server_address)
     2277                    Puncher.client_connect(update_app, local_server_address, proxy_display_desc, report_status, update_options)
     2278                   
     2279
     2280    #def process_keep_alive(self, proto):
     2281    #    payload = {"success": True}
     2282    #    proto.send_now(("hello", payload))
     2283   
     2284    # # acts upon a hole punch init request (server):
     2285    # # - answer proxy
     2286    # # - connect to client
     2287    # def hp_server_process_init(self, proto, caps):
     2288    #     def report_status(status: str):
     2289    #         payload = {"status": status}
     2290    #         proto.send_now(("hp-server-status", payload))
     2291    #
     2292    #     log.error(" == HP == SERVER process_init")
     2293    #
     2294    #     local_address = address(proto._conn.local[0], proto._conn.local[1])
     2295    #     client_addresses = caps.tupleget("hp-client-addresses")
     2296    #
     2297    #     from xpra.client.gobject_client_base import ProxyServerRequestClient
     2298    #     request_server_app = ProxyServerRequestClient()
     2299    #
     2300    #     hole_puncher = Puncher(report_status)
     2301    #     hole_puncher.addConnect(request_server_app, local_address, client_addresses)
     2302    #     hole_puncher.startThreads()
  • src/xpra/server/auth/sys_auth_base.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/auth/sys_auth_base.py b/src/xpra/server/auth/sys_auth_base.py
    a b  
    8383    def requires_challenge(self) -> bool:
    8484        return True
    8585
     86    def allow_proxy_registration(self) -> bool:
     87        return False
     88
     89    def allow_proxy_update(self) -> bool:
     90        return False
     91
     92    def supports_token(self) -> bool:
     93        return self.allow_proxy_update()
     94
    8695    def get_challenge(self, digests):
    8796        if self.salt is not None:
    8897            log.error("Error: authentication challenge already sent!")
  • src/xpra/scripts/config.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/scripts/config.py b/src/xpra/scripts/config.py
    a b  
    624624                    "bandwidth-detection" : bool,
    625625                    "ssh-upgrade"       : bool,
    626626                    "splash"            : bool,
     627                    "hole-punch"        : bool,
    627628                    #arrays of strings:
    628629                    "pulseaudio-configure-commands" : list,
    629630                    "socket-dirs"       : list,
     
    670671                    "password-file"     : list,
    671672                    "start-env"         : list,
    672673                    "env"               : list,
     674                    "proxy-sessions"    : list,
     675                    "registerProxies"   : list,
    673676               }
    674677
    675678#in the options list, available in session files,
     
    690693    "start-after-connect", "start-child-after-connect",
    691694    "start-on-connect", "start-child-on-connect",
    692695    "start-on-last-client-exit", "start-child-on-last-client-exit",
     696    "proxies",
    693697    ]
    694698BIND_OPTIONS = ["bind", "bind-tcp", "bind-udp", "bind-ssl", "bind-ws", "bind-wss", "bind-vsock", "bind-rfb"]
    695699
     
    10331037                    "bandwidth-detection" : True,
    10341038                    "ssh-upgrade"       : True,
    10351039                    "splash"            : None,
     1040                    "hole-punch"        : True,
    10361041                    "pulseaudio-configure-commands"  : [" ".join(x) for x in DEFAULT_PULSEAUDIO_CONFIGURE_COMMANDS],
    10371042                    "socket-dirs"       : get_socket_dirs(),
    10381043                    "client-socket-dirs" : get_client_socket_dirs(),
     
    10781083                    "start-child-on-last-client-exit"   : [],
    10791084                    "start-env"         : DEFAULT_ENV,
    10801085                    "env"               : [],
     1086                    "proxy-sessions"    : ["auth"],
     1087                    "registerProxies"   : [],
    10811088                    }
    10821089    return GLOBAL_DEFAULTS
    10831090#fields that got renamed:
  • src/xpra/scripts/server.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/scripts/server.py b/src/xpra/scripts/server.py
    a b  
    413413        get_util_logger().warn("Warning: bind-rfb sockets cannot be used with '%s' mode" % mode)
    414414        opts.bind_rfb = []
    415415
     416    if proxying and len(opts.registerProxies) > 0:
     417        warn("Warning: the 'registerProxies' option is used,")
     418        warn("but no server started")
     419
    416420    if not shadowing and not starting_desktop:
    417421        opts.rfb_upgrade = 0
    418422
  • src/xpra/scripts/parsing.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/scripts/parsing.py b/src/xpra/scripts/parsing.py
    a b  
    235235                        "control DISPLAY command [arg1] [arg2]..",
    236236                        "print DISPLAY filename",
    237237                        "shell [DISPLAY]",
     238                        "register proxy_address"
    238239                        "showconfig",
    239240                        "list",
    240241                        "list-windows",
     
    634635                "pulseaudio-configure-commands" : defaults.pulseaudio_configure_commands,
    635636                })
    636637
     638    group.add_option("--registerProxies", action="append",
     639        dest = "registerProxies", default = list(defaults.registerProxies or []),
     640        help = "Specifies the proxies to register to.")
     641
    637642    group = optparse.OptionGroup(parser, "Server Controlled Features",
    638643                "These options be specified on the client or on the server, "
    639644                "but the server's settings will have precedence over the client's.")
     
    760765                "microphone-codec"  : defaults.microphone_codec,
    761766                "sound-source"      : defaults.sound_source,
    762767                })
     768    group.add_option("--hole-punch", action="store", metavar="yes|no",
     769                      dest="hole_punch", default=defaults.hole_punch,
     770                      help="Enables hole punching. "
     771                      +" Default: %s." % enabled_or_auto(defaults.hole_punch))
    763772
    764773    group = optparse.OptionGroup(parser, "Encoding and Compression Options",
    765774                "These options are used by the client to specify the desired picture and network data compression."
     
    12041213                      help="Specifies the file containing the encryption key to use for TCP sockets."
    12051214                      +" (default: '%default')")
    12061215
     1216    if supports_proxy:
     1217        group.add_option("--proxy-sessions", action="append",
     1218                         dest="proxy_sessions", default=list(defaults.proxy_sessions or []),
     1219                         help="Specifies the source of sessions a proxy server offers. Default: %s."%enabled_str(
     1220                             defaults.proxy_sessions))
     1221
     1222   
     1223
    12071224    options, args = parser.parse_args(cmdline[1:])
    12081225
    12091226    #ensure all the option fields are set even though
  • src/xpra/scripts/main.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/scripts/main.py b/src/xpra/scripts/main.py
    a b  
    386386        if mode not in ("attach", "listen", "start", "start-desktop", "upgrade", "upgrade-desktop", "proxy", "shadow"):
    387387            from xpra.platform import set_name
    388388            set_name("Xpra", "Xpra %s" % mode.strip("_"))
     389   
     390    if mode=="proxy" and options.proxy_sessions:
     391        options.proxy_sessions = validated_proxy_sessions(options.proxy_sessions)
     392       
     393       
     394    if mode in (  # TODO O not sure here
     395        "start", "start-desktop", "shadow",
     396        "request-start", "request-start-desktop", "request-shadow",
     397    ) and options.registerProxies:
     398        options.registerProxies = validated_proxies(error_cb, options)
    389399
    390400    if mode in (
    391401        "start", "start-desktop",
     
    400410        return 128+signal.SIGINT
    401411
    402412
     413def validated_proxy_sessions(proxy_sessions):
     414    if len(proxy_sessions) > 1:
     415        # remove default
     416        proxy_sessions.remove("auth")
     417   
     418    for proxy_session in proxy_sessions:
     419        if not (proxy_session in (
     420                "auth", "sys",
     421                "mdns", "register",
     422        )):
     423            raise InitException("invalid source on proxy-sessions: %s"%proxy_session)
     424   
     425    return proxy_sessions
     426
     427
     428def validated_proxies(error_cb, options):
     429    # TODO O
     430    for proxy in options.registerProxies:
     431        parse_display_name(error_cb, options, proxy)
     432    return options.registerProxies
     433
    403434def do_run_mode(script_file, error_cb, options, args, mode, defaults):
    404435    display_is_remote = isdisplaytype(args, "ssh", "tcp", "ssl", "vsock")
    405436    if mode in ("start", "start-desktop", "shadow") and display_is_remote:
     
    422453                if state==DotXpra.LIVE:
    423454                    noerr(sys.stdout.write, "existing live display found, attaching")
    424455                    return do_run_mode(script_file, error_cb, options, args, "attach", defaults)
     456    if mode in "register":
     457        # try to register on proxy with username/password
     458
     459        proxyToRegister = args[0]
     460        get_util_logger().debug(proxyToRegister)
     461
     462        validate_encryption(options)
     463        register_display_desc = pick_display(error_cb, options, [proxyToRegister])
     464
     465        from xpra.client.gobject_client_base import RegisterAtProxyClient
     466        register_app = RegisterAtProxyClient(options)
    425467
    426     if (mode in ("start", "start-desktop", "upgrade", "upgrade-desktop") and supports_server) or \
     468        try:
     469            connect_to_server(register_app, register_display_desc, options)
     470        except Exception:
     471            register_app.cleanup()
     472            raise
     473
     474        return do_run_client(register_app)
     475    elif (mode in ("start", "start-desktop", "upgrade", "upgrade-desktop") and supports_server) or \
    427476        (mode=="shadow" and supports_shadow) or (mode=="proxy" and supports_proxy):
    428477        return run_server(script_file, error_cb, options, args, mode, defaults)
    429478    elif mode in (
     
    11501199        raise InitException("connection failed: %s" % e) from None
    11511200
    11521201
    1153 def socket_connect(dtype, host, port):
     1202def socket_connect(dtype, host, port, reuse=False,local_port=0):
    11541203    if dtype=="udp":
    11551204        socktype = socket.SOCK_DGRAM
    11561205    else:
     
    11741223            if dtype!="udp":
    11751224                from xpra.net.bytestreams import SOCKET_TIMEOUT
    11761225                sock.settimeout(SOCKET_TIMEOUT)
     1226            if reuse:
     1227                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     1228                if hasattr(socket, 'SO_REUSEPORT'):
     1229                    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
     1230            if dtype=="tcp" and local_port!=0:
     1231                sock.bind(('', local_port))
    11771232            log = Logger("network")
    11781233            try:
    11791234                log("socket.connect(%s)", sockaddr)
     
    11811236                sock.settimeout(None)
    11821237                return sock
    11831238            except Exception as e:
     1239                log.error("failed to connect using %s%s for %s", sock.connect, sockaddr, addr, exc_info=True)
    11841240                log("failed to connect using %s%s for %s", sock.connect, sockaddr, addr, exc_info=True)
    11851241            noerr(sock.close)
    11861242        if monotonic_time()-start>=CONNECT_TIMEOUT:
     
    11971253    dtype = display_desc["type"]
    11981254    username = display_desc.get(prefix+"username")
    11991255    host = display_desc[prefix+"host"]
     1256    port = display_desc["port"]
    12001257    try:
    12011258        port = int(display_desc.get(prefix+port_key))
    12021259        if not 0<port<2**16:
     
    13001357    if dtype in ("tcp", "ssl", "ws", "wss", "udp"):
    13011358        host = display_desc["host"]
    13021359        port = display_desc["port"]
    1303         sock = socket_connect(dtype, host, port)
     1360        local_port = 0
     1361        if hasattr(opts, 'local_port') and opts.local_port!=0:
     1362            local_port = opts.local_port
     1363        sock = socket_connect(dtype, host, port, opts.hole_punch, local_port)
    13041364        sock.settimeout(None)
    13051365        conn = SocketConnection(sock, sock.getsockname(), sock.getpeername(), display_name, dtype, socket_options=display_desc)
    13061366
  • src/xpra/server/proxy/proxy_instance_thread.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/proxy/proxy_instance_thread.py b/src/xpra/server/proxy/proxy_instance_thread.py
    a b  
    55
    66from gi.repository import GLib
    77
     8from xpra.net import address
    89from xpra.net.protocol_classes import get_server_protocol_class
    910from xpra.server.proxy.proxy_instance import ProxyInstance
    1011from xpra.codecs.video_helper import getVideoHelper
     
    1819    def __init__(self, session_options,
    1920                 video_encoders, pings,
    2021                 client_proto, server_conn,
    21                  disp_desc, cipher, encryption_key, caps):
     22                 disp_desc, cipher, encryption_key, caps, hole_punch, hp_local_server_address: address):
    2223        super().__init__(session_options,
    2324                         video_encoders, pings,
    24                          disp_desc, cipher, encryption_key, caps)
     25                         disp_desc, cipher, encryption_key, caps, hole_punch, hp_local_server_address)
    2526        self.client_protocol = client_proto
    2627        self.server_conn = server_conn
    2728
  • src/xpra/server/proxy/proxy_instance_process.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/proxy/proxy_instance_process.py b/src/xpra/server/proxy/proxy_instance_process.py
    a b  
    99from queue import Queue
    1010from multiprocessing import Process
    1111
     12from xpra.net import address
    1213from xpra.server.proxy.proxy_instance import ProxyInstance
    1314from xpra.scripts.server import deadly_signal
    1415from xpra.net.protocol_classes import get_client_protocol_class, get_server_protocol_class
     
    6465
    6566    def __init__(self, uid, gid, env_options, session_options, socket_dir,
    6667                 video_encoder_modules, pings,
    67                  client_conn, disp_desc, client_state, cipher, encryption_key, server_conn, caps, message_queue):
     68                 client_conn, disp_desc, client_state, cipher, encryption_key, server_conn, caps, message_queue, hole_punch, hp_local_server_address: address):
    6869        ProxyInstance.__init__(self, session_options,
    6970                               video_encoder_modules, pings,
    70                                disp_desc, cipher, encryption_key, caps)
     71                               disp_desc, cipher, encryption_key, caps, hole_punch, hp_local_server_address)
    7172        QueueScheduler.__init__(self)
    7273        Process.__init__(self, name=str(client_conn), daemon=False)
    7374        self.client_conn = client_conn
  • src/xpra/server/proxy/proxy_instance.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/proxy/proxy_instance.py b/src/xpra/server/proxy/proxy_instance.py
    a b  
    33# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    6 
     6import secrets
    77import socket
     8import threading
    89from time import sleep, time
    910from queue import Queue
    1011
     
    1516from xpra.codecs.loader import load_codec, get_codec
    1617from xpra.codecs.image_wrapper import ImageWrapper
    1718from xpra.codecs.video_helper import getVideoHelper, PREFERRED_ENCODER_ORDER
    18 from xpra.scripts.config import parse_number, parse_bool
     19from xpra.net.address import address, addressFromTuple
     20from xpra.scripts.config import parse_number, parse_bool, InitException, make_defaults_struct
    1921from xpra.os_util import (
    2022    get_hex_uuid,
    2123    monotonic_time, bytestostr, strtobytes,
    2224    )
     25from xpra.scripts.main import pick_display, do_run_client, connect_to_server
    2326from xpra.util import (
    2427    flatten_dict, typedict, updict, ellipsizer, envint, envbool,
    2528    csv, first_time, SERVER_SHUTDOWN,
     
    5255
    5356    def __init__(self, session_options,
    5457                 video_encoder_modules, pings,
    55                  disp_desc, cipher, encryption_key, caps):
     58                 disp_desc, cipher, encryption_key, caps, hole_punch, hp_local_server_address: address):
    5659        self.session_options = session_options
    5760        self.video_encoder_modules = video_encoder_modules
    5861        self.pings = pings
     
    6063        self.cipher = cipher
    6164        self.encryption_key = encryption_key
    6265        self.caps = caps
     66        self.hole_punch = hole_punch
     67        self.hp_local_server_address = hp_local_server_address
    6368        log("ProxyInstance%s", (
    6469            session_options,
    6570            video_encoder_modules,
     
    121126        if self.caps.boolget("ping-echo-sourceid"):
    122127            self.schedule_client_ping()
    123128
     129        hp_client = self.caps.boolget("hole_punch", False)
     130        hp_client_address = addressFromTuple(self.caps.tupleget("hp-client-address", ""))
     131
     132        if self.hole_punch and self.client_protocol.socket_type=="tcp" and hp_client and hp_client_address!="":
     133            self.hp_proxy_init(hp_client_address)
     134            return
     135
    124136        self.send_hello()
    125137
    126138    def start_network_threads(self):
     
    222234
    223235    ################################################################################
    224236
    225     def send_hello(self, challenge_response=None, client_salt=None):
     237    def send_client_hello_payload(self, payload):
     238        hello = self.filter_server_caps(self.caps)
     239        hello.update(payload)
     240        self.queue_client_packet(("hello", hello))
     241
     242    def send_server_hello_payload(self, payload):
    226243        hello = self.filter_client_caps(self.caps)
    227         if challenge_response:
    228             hello.update({
     244        hello.update(payload)
     245        self.queue_server_packet(("hello", hello))
     246
     247    def send_hello(self, challenge_response=None, client_salt=None):
     248        self.send_server_hello_payload({
    229249                "challenge_response"      : challenge_response,
    230250                "challenge_client_salt"   : client_salt,
    231251                })
    232         self.queue_server_packet(("hello", hello))
    233252
    234253
    235254    def sanitize_session_options(self, options):
     
    336355            self.client_last_ping_latency = 1000*monotonic_time()-self.client_last_ping_echo
    337356            log("ping-echo: client latency=%.1fms", self.client_last_ping_latency)
    338357            return
     358        if packet_type=="hp-client-status":
     359            self.client_last_ping_echo = monotonic_time()
     360            c = typedict(packet[1])
     361            self.hp_proxy_process_client_status(proto, c)
     362            return
     363
    339364        #the packet types below are forwarded:
    340365        if packet_type=="disconnect":
    341366            reason = bytestostr(packet[1])
     
    553578                log.info("sending %s challenge response", digest)
    554579                self.send_hello(challenge_response, client_salt)
    555580                return
     581        elif packet_type=="hp-server-status":
     582            self.server_last_ping_echo = monotonic_time()
     583            c = typedict(packet[1])
     584            self.hp_proxy_process_server_status(proto, c)
     585            return
     586
    556587        self.queue_client_packet(packet)
    557588
     589    # acts upon a client attach request:
     590    # - requests client to punch hole
     591    # - requests server to punch hole
     592    def hp_proxy_init(self, client_local_address):
     593         log.error(" == HP == PROXY  init")
     594       
     595         client_remote_address = address(self.client_protocol._conn.remote[0], self.client_protocol._conn.remote[1])
     596         client_addresses = [client_local_address.toTuple(), client_remote_address.toTuple()]
     597         
     598         
     599         server_remote_address = address(self.server_protocol._conn.remote[0], self.server_protocol._conn.remote[1])
     600         server_addresses = [server_remote_address.toTuple(), self.hp_local_server_address.toTuple()]
     601         
     602         
     603         token = secrets.token_urlsafe(64)
     604
     605
     606         # request client to init
     607         log.error(" == HP == PROXY  initClient")
     608
     609         payload = {"request": "hp-client-init",
     610                    "hp-server-addresses": server_addresses,
     611                    "token": token}
     612         self.send_client_hello_payload(payload)
     613
     614         # request server to init
     615         log.error(" == HP == PROXY  initServer")
     616
     617         payload = {"request": "hp-server-init",
     618                    "hp-client-addresses": client_addresses,
     619                    "token": token}
     620         self.send_server_hello_payload(payload)
     621
     622    def hp_proxy_process_server_status(self, proto, c):
     623        status = c.strget("status", "")
     624        log.error("server status: " + status)
     625
     626
     627    def hp_proxy_process_client_status(self, proto, c):
     628        status = c.strget("status", "")
     629        log.error("client status: " + status)
     630
     631    #     port = c.intget("hp-port", 0)
     632    #
     633    #     if port != 0:    # TODO add multiple ips
     634    #         client_endpoint = self.client_protocol._conn.remote
     635    #         client_socktype = self.client_protocol._conn.socktype
     636    #         hp_client_display = client_socktype + "://" + client_endpoint[0] + ":" + str(port) + "/"
     637    #
     638    #         # request server to punch-hole
     639    #         log.error(" == HP == PROXY  startServer with port " + str(port))
     640    #
     641    #         #payload = {"request": "hp-server-init",
     642    #         #           "hp-client-display": hp_client_display}
     643    #         #self.send_server_hello_payload(payload)
    558644
    559645    def stop_encode_thread(self):
    560646        #empty the encode queue:
  • src/xpra/client/gobject_client_base.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/client/gobject_client_base.py b/src/xpra/client/gobject_client_base.py
    a b  
    1010from gi.repository import GLib
    1111from gi.repository import GObject
    1212
     13from xpra.net import address
     14from xpra.scripts.config import make_defaults_struct
    1315from xpra.util import (
    1416    nonl, sorted_nicely, print_nested_dict, envint, flatten_dict, typedict,
    1517    disconnect_is_an_error, ellipsizer, DONE, first_time,
     
    254256        super().init_packet_handlers()
    255257        self._ui_packet_handlers["screenshot"] = self._process_screenshot
    256258
     259#class KeepAliveClient(HelloRequestClient):
     260#    def _process_connection_lost(self, packet):
     261#        errwrite("\n")
     262#        super()._process_connection_lost(packet)
     263#
     264#    def hello_request(self):
     265#        return {
     266#            "keep-alive_request"  : True,
     267#        }
     268#
     269#    def server_connection_established(self, caps: typedict):
     270#        log("server_connection_established() exit_code=%s", self.exit_code)
     271#        success = caps.strget("success") == "True"
     272#        if success:
     273#            log("successfully keep-alive")
     274#        else:
     275#            log("keep-alive error")
     276#
     277#        # TODO O retry
     278#
     279#        if not self.exit_code:
     280#            self.quit(0)
     281#        return True
     282#
     283#    def timeout(self, *_args):
     284#        self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not disconnect us")
     285#
     286#        # TODO O retry
     287#
     288#    def __init__(self, opts):
     289#        super().__init__(opts)
    257290
     291class ProxyClientRequestClient(HelloRequestClient):
     292   
     293    def __init__(self, token):
     294        opts = make_defaults_struct()
     295        super().__init__(opts)
     296        self.token = token
     297   
     298    def _process_connection_lost(self, packet):
     299        log.error(" == HP == CLIENT client failed to establish direct connection (lost)")
     300        super()._process_connection_lost(packet)
     301
     302    def hello_request(self):
     303        return {
     304            "request": "hp-client-request",
     305            "token": self.token
     306        }
     307
     308    def timeout(self, *_args):
     309        log.error(" == HP == CLIENT client failed to establish direct connection (timeout)")
     310        super().timeout(self, *_args)
     311
     312    def server_connection_established(self, caps: typedict) -> bool:
     313        log("server_connection_established() exit_code=%s", self.exit_code)
     314        success = caps.strget("success") == "True"
     315        if success:
     316            log.error(" == HP == CLIENT client has established a direct connection")
     317            return True
     318        else:
     319            log.error(" == HP == CLIENT client failed to establish direct connection (no success)")
     320            return False
     321
     322    def quit(self, exit_code):
     323        log("quit(%s) current exit_code=%s", exit_code, self.exit_code)
     324        if self.exit_code is None:
     325            self.exit_code = exit_code
     326        self.cleanup()
     327
     328    def exit_loop(self):
     329            self.quit(0)
     330
     331
     332class ProxyServerRequestClient(HelloRequestClient):
     333   
     334    def __init__(self, token):
     335        opts = make_defaults_struct()
     336        super().__init__(opts)
     337        self.token = token
     338   
     339    def _process_connection_lost(self, packet):
     340        log.error(" == HP == SERVER server failed to establish direct connection (lost)")
     341        super()._process_connection_lost(packet)
     342
     343    def hello_request(self):
     344        return {
     345            "request": "hp-server-request",
     346            "token": self.token
     347        }
     348    def timeout(self, *_args):
     349        log.error(" == HP == SERVER server failed to establish direct connection (timeout)")
     350        super().timeout(self, *_args)
     351
     352    def server_connection_established(self, caps: typedict) -> bool:
     353        log("server_connection_established() exit_code=%s", self.exit_code)
     354        success = caps.strget("success") == "True"
     355        if success:
     356            log.error(" == HP == SERVER server has established a direct connection")
     357            return True
     358        else:
     359            log.error(" == HP == SERVER server failed to establish direct connection (no success)")
     360            return False
     361
     362    def quit(self, exit_code):
     363        log("quit(%s) current exit_code=%s", exit_code, self.exit_code)
     364        if self.exit_code is None:
     365            self.exit_code = exit_code
     366        self.cleanup()
     367   
     368    def exit_loop(self):
     369        self.quit(0)
     370
     371class RegisterAtProxyClient(HelloRequestClient):
     372   
     373    def __init__(self, opts, displays=None, uid=None, gid=None, env_options=None, session_options=None):
     374        super().__init__(opts)
     375       
     376        self.displays = displays
     377        self.uid = uid
     378        self.gid = gid
     379        self.env_options = env_options
     380        self.session_options = session_options
     381   
     382    def _process_connection_lost(self, packet):
     383        errwrite("\n")
     384        super()._process_connection_lost(packet)
     385
     386    def hello_request(self):
     387        return {
     388            "register_request"  : True,
     389            "displays"          : self.displays,
     390            "uid"               : self.uid,
     391            "gid"               : self.gid,
     392            "env_options"       : self.env_options,
     393            "session_options"   : self.session_options
     394        }
     395   
     396    def server_connection_established(self, caps: typedict) -> bool:
     397        log("server_connection_established() exit_code=%s", self.exit_code)
     398        success = caps.strget("success") == "True"
     399        if success:
     400            log.error(" == HP == SERVER successfully registered")
     401
     402            session_id = caps.strget("session_id")
     403            update_token = caps.strget("update_token")
     404
     405            sys.stdout.write("session_id    : " + session_id + "\n")
     406            sys.stdout.write("update_token  : " + update_token + "\n")
     407            sys.stdout.flush()
     408            return True
     409        else:
     410            log.error(" == HP == SERVER register error")
     411            return False
     412
     413    def quit(self, exit_code):
     414        log("quit(%s) current exit_code=%s", exit_code, self.exit_code)
     415        if self.exit_code is None:
     416            self.exit_code = exit_code
     417        self.cleanup()
     418
     419    def exit_loop(self):
     420        self.quit(0)
     421
     422class UpdateProxyClient(HelloRequestClient):
     423    """ update the proxy server """
     424
     425    def __init__(self, token_mode, add_delete, local_server_address: address):
     426        opts = make_defaults_struct()
     427        super().__init__(opts)
     428   
     429        self.token_mode = token_mode
     430        self.add_delete = add_delete
     431        self.local_server_address = local_server_address
     432   
     433    def hello_request(self):
     434        return {
     435            "update_request"            : True,
     436            "token_mode"                : self.token_mode,
     437            "add_delete"                : self.add_delete,
     438            "hp-local-server-address"   : self.local_server_address.toTuple()
     439        }
     440   
     441    def server_connection_established(self, caps: typedict) -> bool:
     442        log("server_connection_established() exit_code=%s", self.exit_code)
     443        success = caps.strget("success") == "True"
     444        if success:
     445            log.error(" == HP == SERVER successfully updated")
     446            return True
     447        else:
     448            log.error(" == HP == SERVER update error")
     449            return False
     450       
     451    def do_command(self, caps : typedict):
     452        self.quit(EXIT_OK)
     453   
     454    def quit(self, exit_code):
     455        log("quit(%s) current exit_code=%s", exit_code, self.exit_code)
     456        if self.exit_code is None:
     457            self.exit_code = exit_code
     458        self.cleanup()
     459       
     460    def exit_loop(self):
     461        self.quit(0)
     462       
    258463class InfoXpraClient(CommandConnectClient):
    259464    """ This client does one thing only:
    260465        it queries the server with an 'info' request
  • src/xpra/server/server_base.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py
    a b  
    77# later version. See the file COPYING for details.
    88
    99import os
     10import uuid
    1011from threading import Thread, Lock
     12from typing import Optional, Type
    1113
    1214from xpra.server.server_core import ServerCore
    1315from xpra.server.mixins.server_base_controlcommands import ServerBaseControlCommands
     
    10021004        self.do_init_aliases(packet_types)
    10031005
    10041006    def process_packet(self, proto, packet):
    1005         try:
    1006             handler = None
     1007        handler = None
     1008        packet_type = None
     1009        try:
    10071010            packet_type = bytestostr(packet[0])
    10081011            def call_handler():
    10091012                may_log_packet(False, packet_type, packet)
  • src/xpra/client/client_base.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/client/client_base.py b/src/xpra/client/client_base.py
    a b  
    1212import string
    1313
    1414from xpra.log import Logger
     15from xpra.net.address import addressFromTuple, address
     16from xpra.net.punch_hole import Puncher
    1517from xpra.scripts.config import InitExit
    1618from xpra.common import SPLASH_EXIT_DELAY
    1719from xpra.child_reaper import getChildReaper, reaper_cleanup
     
    117119        self.server_padding_options = [DEFAULT_PADDING]
    118120        self.server_client_shutdown = True
    119121        self.server_compressors = []
     122        self.hole_punch = True
    120123        #protocol stuff:
    121124        self._protocol = None
    122125        self._priority_packets = []
     
    379382                #avoid sending the full hello: tell the server we want
    380383                #a packet challenge first
    381384                hello["challenge"] = True
     385            elif self.hole_punch:
     386                hp_client_address = addressFromTuple(self._protocol._conn.local)
     387                hello.update({"hole_punch": True, "hp-client-address": hp_client_address.toTuple()})
    382388            else:
    383389                hello.update(self.make_hello())
    384390        except InitExit as e:
     
    892898        try:
    893899            caps = typedict(packet[1])
    894900            netlog("processing hello from server: %s", ellipsizer(caps))
     901
     902            request = caps.strget("request")
     903           
     904            if request == "hp-client-init":
     905                self.hp_client_process_init(packet, caps)
     906                return
     907            elif request == "hp-server-init":
     908                self.hp_server_process_init(packet, caps)
     909                return
     910            elif request == "hp-server-request":
     911                self.hp_client_process_server_request(packet, caps)
     912                return
     913            elif request == "hp-client-request":
     914                self.hp_server_process_client_request(packet, caps)
     915                return True
    895916            if not self.server_connection_established(caps):
    896917                self.warn_and_quit(EXIT_FAILURE, "failed to establish connection")
    897918            else:
     
    900921            netlog.info("error in hello packet", exc_info=True)
    901922            self.warn_and_quit(EXIT_FAILURE, "error processing hello packet from server: %s" % e)
    902923
     924    # acts upon a hole punch init request (client):
     925    # - answer proxy
     926    # - connect to server
     927    def hp_client_process_init(self, packet, caps):
     928        log.error(" == HP == CLIENT process_init")
     929       
     930        def report_status(status, payload=None):
     931            status_payload = self.make_hello_base()
     932            status_payload["status"] = status
     933            if payload is not None:
     934                status_payload.update(payload)
     935            self.send("hp-client-status", status_payload)
     936
     937        conn = self._protocol._conn
     938
     939        local_address = address(conn.local[0], conn.local[1]) # TODO
     940        server_addresses = caps.tupleget("hp-server-addresses")
     941        token = caps.strget("token")
     942       
     943        def hp_client_main():
     944            hole_puncher = Puncher(report_status)
     945            hole_puncher.clientAddListen(local_address)
     946
     947            from xpra.client.gobject_client_base import ProxyClientRequestClient
     948            request_client_app = ProxyClientRequestClient(token)
     949           
     950            hole_puncher.addConnect(request_client_app, local_address, server_addresses)
     951            hole_puncher.startThreads()
     952       
     953        self.idle_add(hp_client_main)
     954
     955    # acts upon a hole punch init request (server):
     956    # - answer proxy
     957    # - connect to client
     958    def hp_server_process_init(self, proto, caps):
     959        def report_status(status: str, payload=None):
     960            status_payload = self.make_hello_base()
     961            status_payload["status"] = status
     962            if payload is not None:
     963                status_payload.update(payload)
     964            self.send("hp-server-status", status_payload)
     965   
     966        log.error(" == HP == SERVER process_init")
     967
     968        conn = self._protocol._conn
     969   
     970        local_address = address(conn.local[0], conn.local[1])
     971        client_addresses = caps.tupleget("hp-client-addresses")
     972        token = caps.strget("token")
     973       
     974        def hp_server_main():
     975            from xpra.client.gobject_client_base import ProxyServerRequestClient
     976            request_server_app = ProxyServerRequestClient(token)
     977       
     978            hole_puncher = Puncher(report_status)
     979            hole_puncher.addConnect(request_server_app, local_address, client_addresses)
     980            hole_puncher.startThreads()
     981
     982        self.idle_add(hp_server_main)
     983
     984    # acts upon a hole punch client request (server):
     985    def hp_server_process_client_request(self, proto, c):
     986        log.error(" == HP == SERVER received a client connection")
     987        # answer client
     988        payload = {"success": True}
     989        self.send("hello", payload)
     990   
     991        # TODO rpt proxy
     992   
     993    # acts upon a hole punch server request (client):
     994    def hp_client_process_server_request(self, packet, c):
     995        log.error(" == HP == CLIENT received a server connection")
     996        # answer server
     997        payload = { "success": True }
     998        self.send("hello", payload)
    903999
    9041000    def server_connection_established(self, caps : typedict) -> bool:
    9051001        netlog("server_connection_established(..)")
  • src/xpra/server/auth/sqlite_auth.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/xpra/server/auth/sqlite_auth.py b/src/xpra/server/auth/sqlite_auth.py
    a b  
    66
    77import os
    88import sys
     9from typing import Type
    910
    1011from xpra.util import parse_simple_dict
    1112from xpra.server.auth.sys_auth_base import log, parse_uid, parse_gid
    12 from xpra.server.auth.sqlauthbase import SQLAuthenticator, DatabaseUtilBase, run_dbutil
     13from xpra.server.auth.sqlauthbase import SQLAuthenticator, DatabaseUtilBase, run_dbutil, QueriesBase
    1314
    1415
    1516class Authenticator(SQLAuthenticator):
     
    2021            exec_cwd = kwargs.get("exec_cwd", os.getcwd())
    2122            filename = os.path.join(exec_cwd, filename)
    2223        self.filename = filename
    23         self.password_query = kwargs.pop("password_query", "SELECT password FROM users WHERE username=(?)")
    24         self.sessions_query = kwargs.pop("sessions_query",
    25                                          "SELECT uid, gid, displays, env_options, session_options "+
    26                                          "FROM users WHERE username=(?) AND password=(?)")
     24       
     25        self.password_query = kwargs.pop("password_query", SqliteDatabaseQueries.password_get)
     26        self.sessions_query_by_username = kwargs.pop("sessions_query", SqliteDatabaseQueries.sessions_query_by_username)
     27        self.sessions_get = kwargs.pop("sessions_get", SqliteDatabaseQueries.sessions_get)
     28        self.sessions_get_by_token = kwargs.pop("sessions_get_by_token", SqliteDatabaseQueries.sessions_get_by_token)
     29        self.sessions_add = kwargs.pop("sessions_add", SqliteDatabaseQueries.sessions_add)
     30        self.sessions_update = kwargs.pop("sessions_update", SqliteDatabaseQueries.sessions_update)
     31        self.user_query = kwargs.pop("user_query", SqliteDatabaseQueries.users_get)
     32        self.user_session_add = kwargs.pop("user_session_add", SqliteDatabaseQueries.user_session_add)
     33       
    2734        self.authenticate_check = self.authenticate_hmac
    2835
    2936    def __repr__(self):
     
    3946        cursor = db.cursor()
    4047        cursor.execute(*sqlargs)
    4148        log("db_cursor(%s)=%s", sqlargs, cursor)
     49        db.commit()
    4250        return cursor
    4351
    4452    def parse_session_data(self, data):
     
    4856            displays = []
    4957            env_options = {}
    5058            session_options = {}
     59            updated = ""
     60            source = ""
     61            update_token = ""
     62            session_id = 0
    5163            if data["displays"]:
    5264                displays = [x.strip() for x in str(data["displays"]).split(",")]
    5365            if data["env_options"]:
    5466                env_options = parse_simple_dict(str(data["env_options"]), ";")
    5567            if data["session_options"]:
    5668                session_options=parse_simple_dict(str(data["session_options"]), ";")
     69            if data["updated"]:
     70                updated = str(data["updated"])
     71            if data["source"]:
     72                source = str(data["source"])
     73            if data["update_token"]:
     74                update_token = str(data["update_token"])
     75            if data["id"]:
     76                session_id = str(data["id"])
    5777        except Exception as e:
    5878            log("get_sessions() error on row %s", data, exc_info=True)
    5979            log.error("Error: sqlauth database row parsing problem:")
    6080            log.error(" %s", e)
    6181            return None
    62         return uid, gid, displays, env_options, session_options
     82        return uid, gid, displays, env_options, session_options, updated, source, update_token, session_id
    6383
     84class SqliteDatabaseQueries(QueriesBase):
     85    users_get = (
     86        "SELECT id, username, password, email "
     87        "FROM users "
     88        "WHERE username = (?)")
     89    users_get_with_password = (
     90        "SELECT id, username, password, email "
     91        "FROM users "
     92        "WHERE username=(?) "
     93        "AND password=(?)")
     94    password_get = "SELECT password FROM users WHERE username=(?)"
     95    users_add = (
     96        "INSERT INTO users(username, password, email) "
     97        "VALUES((?), (?), (?))")
     98    users_delete = "DELETE FROM users WHERE username=(?)"
     99   
     100    sessions_query = (
     101        "SELECT u.id AS user_id, u.username, u.password, u.email, s.id AS session_id, s.uid, s.gid, s.displays, s.env_options, s.session_options, s.updated, s.source, s.update_token "
     102        "FROM sessions AS s "
     103        "JOIN user_session AS us ON us.session = s.id "
     104        "LEFT JOIN users AS u ON u.id = us.user "
     105        "ORDER BY u.id, s.id")
     106    sessions_query_by_username = (
     107        "SELECT s.uid, s.gid, s.displays, s.env_options, s.session_options, s.updated, s.source, s.update_token, s.id "
     108        "FROM sessions AS s "
     109        "JOIN user_session AS us ON us.session = s.id "
     110        "JOIN users AS u ON u.id = us.user "
     111        "WHERE u.username=(?)")
     112    sessions_get = (
     113        "SELECT uid, gid, displays, env_options, session_options, updated, source, update_token, id "
     114        "FROM sessions "
     115        "WHERE id=(?)")
     116    sessions_get_by_token = (
     117        "SELECT id, uid, gid, displays, env_options, session_options, updated, source, update_token, id "
     118        "FROM sessions "
     119        "WHERE update_token=(?)")
     120    sessions_add = (
     121        "INSERT INTO sessions "
     122        "(uid, gid, displays, env_options, session_options, updated, source, update_token) "
     123        "VALUES((?), (?), (?), (?), (?), (?), (?), (?))")
     124    sessions_update = (
     125        "UPDATE sessions "
     126        "SET uid = (?), "
     127        "gid = (?), "
     128        "displays = (?), "
     129        "env_options = (?), "
     130        "session_options = (?), "
     131        "updated = (?), "
     132        "source = (?) "
     133        "WHERE id = (?)")
     134    sessions_delete = "DELETE FROM sessions WHERE id=(?)"
     135   
    64136
     137    user_session_add = (
     138        "INSERT INTO user_session "
     139        "(user, session) "
     140        "VALUES((?), (?))")
     141    user_session_delete_by_user = "DELETE FROM user_session WHERE user=(?)"
     142    user_session_delete_by_session = "DELETE FROM user_session WHERE session=(?)"
     143
    65144class SqliteDatabaseUtil(DatabaseUtilBase):
    66145
    67146    def __init__(self, uri):
     
    69148        import sqlite3
    70149        assert sqlite3.paramstyle=="qmark"
    71150        self.param = "?"
     151        self.queries: Type[QueriesBase] = SqliteDatabaseQueries
    72152
    73153    def exec_database_sql_script(self, cursor_cb, *sqlargs):
    74154        import sqlite3