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: 8d6c8c863e1a97162f1c49598ffeb4cfde96e9c5.patch

File 8d6c8c863e1a97162f1c49598ffeb4cfde96e9c5.patch, 40.8 KB (added by brief, 12 months ago)
  • xpra/client/gobject_client_base.py

     
    246246        self._ui_packet_handlers["screenshot"] = self._process_screenshot
    247247
    248248
     249class RegisterAtProxyClient(HelloRequestClient):
     250    def _process_connection_lost(self, packet):
     251        errwrite("\n")
     252        super()._process_connection_lost(packet)
     253
     254    def hello_request(self):
     255        return {
     256            "register_request"  : True,
     257            "displays"          : self.displays,
     258            "uid"               : self.uid,
     259            "gid"               : self.gid,
     260            "env_options"       : self.env_options,
     261            "session_options"   : self.session_options
     262        }
     263   
     264    def server_connection_established(self, caps : typedict):
     265        log("server_connection_established() exit_code=%s", self.exit_code)
     266        success = caps.strget("success")
     267        if success:
     268            log("successfully registered")
     269
     270            session_id = caps.strget("session_id")
     271            update_token = caps.strget("update_token")
     272
     273            sys.stdout.write("session_id    : " + session_id)
     274            sys.stdout.write("update_token  : " + update_token)
     275            sys.stdout.flush()
     276        else:
     277            log("registered error")
     278
     279       
     280        if not self.exit_code:
     281            self.quit(0)
     282        return True
     283
     284    def __init__(self, opts, displays=None, uid=None, gid=None, env_options=None, session_options=None):
     285        super().__init__(opts)
     286       
     287        self.displays = displays
     288        self.uid = uid
     289        self.gid = gid
     290        self.env_options = env_options
     291        self.session_options = session_options
     292
     293class UpdateProxyClient(RegisterAtProxyClient):
     294    """ update the proxy server """
     295   
     296    def hello_request(self):
     297        return {
     298            "update_request"    : True,
     299            "displays"          : self.displays,
     300            "uid"               : self.uid,
     301            "gid"               : self.gid,
     302            "env_options"       : self.env_options,
     303            "session_options"   : self.session_options
     304        }
     305   
     306    def server_connection_established(self, caps: typedict):
     307        log("server_connection_established() exit_code=%s", self.exit_code)
     308        success = caps.strget("success")
     309        if success:
     310            log("successfully updated")
     311        else:
     312            log("update error")
     313       
     314        if not self.exit_code:
     315            self.quit(0)
     316        return True
     317
     318       
     319    def __init__(self, opts, displays=None, uid=None, gid=None, env_options=None, session_options=None):
     320        super().__init__(opts, displays, uid, gid, env_options, session_options)
     321       
     322       
    249323class InfoXpraClient(CommandConnectClient):
    250324    """ This client does one thing only:
    251325        it queries the server with an 'info' request
  • xpra/scripts/config.py

     
    667667                    "password-file"     : list,
    668668                    "start-env"         : list,
    669669                    "env"               : list,
     670                    "proxy-sessions"    : list,
     671                    "proxies"           : list,
    670672               }
    671673
    672674#in the options list, available in session files,
     
    687689    "start-after-connect", "start-child-after-connect",
    688690    "start-on-connect", "start-child-on-connect",
    689691    "start-on-last-client-exit", "start-child-on-last-client-exit",
     692    "proxies",
    690693    ]
    691694BIND_OPTIONS = ["bind", "bind-tcp", "bind-udp", "bind-ssl", "bind-ws", "bind-wss", "bind-vsock", "bind-rfb"]
    692695
     
    771774    "start-after-connect", "start-child-after-connect",
    772775    "start-on-connect", "start-child-on-connect",
    773776    "start-on-last-client-exit", "start-child-on-last-client-exit",
     777    "proxy-sessions",
    774778    ]
    775779tmp = os.environ.get("XPRA_PROXY_START_OVERRIDABLE_OPTIONS", "")
    776780if tmp:
     
    10761080                    "start-child-on-last-client-exit"   : [],
    10771081                    "start-env"         : DEFAULT_ENV,
    10781082                    "env"               : [],
     1083                    "proxy-sessions"    : ["auth"],
     1084                    "proxies"           : [],
    10791085                    }
    10801086    return GLOBAL_DEFAULTS
    10811087#fields that got renamed:
  • xpra/scripts/main.py

     
    351351        if mode not in ("attach", "listen", "start", "start-desktop", "upgrade", "upgrade-desktop", "proxy", "shadow"):
    352352            from xpra.platform import set_name
    353353            set_name("Xpra", "Xpra %s" % mode.strip("_"))
     354   
     355    if mode=="proxy" and options.proxy_sessions:
     356        options.proxy_sessions = validated_proxy_sessions(options.proxy_sessions)
     357       
     358       
     359    if mode in (  # TODO O not sure here
     360        "start", "start-desktop", "shadow",
     361        "request-start", "request-start-desktop", "request-shadow",
     362    ) and options.proxies:
     363        options.proxies = validated_proxies(error_cb, options)
    354364
    355365    if mode in (
    356366        "start", "start-desktop",
     
    365375        return 128+signal.SIGINT
    366376
    367377
     378def validated_proxy_sessions(proxy_sessions):
     379   
     380    if len(proxy_sessions) > 1:
     381        # remove default
     382        proxy_sessions.remove("auth")
     383   
     384    for proxy_session in proxy_sessions:
     385        if not (proxy_session in (
     386                "auth", "sys",
     387                "mdns", "register",
     388        )):
     389            raise InitException("invalid source on proxy-sessions: %s"%proxy_session)
     390       
     391    return proxy_sessions
     392
     393
     394def validated_proxies(error_cb, options):
     395    # TODO O
     396    for proxy in options.proxies:
     397        parse_display_name(error_cb, options, proxy)
     398    return options.proxies
     399
     400
    368401def do_run_mode(script_file, error_cb, options, args, mode, defaults):
    369402    display_is_remote = isdisplaytype(args, "ssh", "tcp", "ssl", "vsock")
    370403    if mode in ("start", "start-desktop", "shadow") and display_is_remote:
     
    449482        if options.splash is True or (
    450483            options.splash is not False and (
    451484                not POSIX or (
    452                     (os.environ.get("DISPLAY") or os.environ.get("XDG_SESSION_DESKTOP")) and 
     485                    (os.environ.get("DISPLAY") or os.environ.get("XDG_SESSION_DESKTOP")) and
    453486                    not (os.environ.get("SSH_CONNECTION") or os.environ.get("SSH_CLIENT"))
    454487                    )
    455488                )
     
    509542                from xpra.child_reaper import getChildReaper
    510543                getChildReaper().add_process(proc, "client-attach", cmd, ignore=True, forget=False)
    511544            add_when_ready(attach_client)
     545           
     546        if not (mode=="proxy" and supports_proxy) and options.proxies:
     547            def update_proxies():
     548
     549                for proxy in options.proxies:
     550                    # try to update proxy with token
     551                    get_util_logger().debug(proxy)
     552                    # TODO O repeat
     553                    # TODO O exponential back-off
     554
     555                    update_options = options
     556                    update_display_desc = pick_display(error_cb, update_options, [proxy])
     557                   
     558                    from xpra.client.gobject_client_base import UpdateProxyClient
     559                    update_app = UpdateProxyClient(update_options, "123.4.5.7") # TODO O
     560
     561                    try:
     562                        connect_to_server(update_app, update_display_desc, update_options)
     563                    except Exception:
     564                        update_app.cleanup()
     565                        raise
     566                       
     567                       
     568                    do_run_client(update_app)
     569
     570                   
     571
     572            add_when_ready(update_proxies)
     573           
     574           
    512575        return run_server(error_cb, options, mode, script_file, args, current_display, progress_cb)
    513576    elif mode in (
    514577        "attach", "listen", "detach",
     
    519582        return run_client(error_cb, options, args, mode)
    520583    elif mode in ("stop", "exit"):
    521584        return run_stopexit(mode, error_cb, options, args)
     585    elif mode in "register":
     586        # try to register on proxy with username/password
     587
     588        proxyToRegister = args[0]
     589        get_util_logger().debug(proxyToRegister)
     590   
     591        validate_encryption(options)
     592        register_display_desc = pick_display(error_cb, options, [proxyToRegister])
     593   
     594        from xpra.client.gobject_client_base import RegisterAtProxyClient
     595        register_app = RegisterAtProxyClient(options)
     596   
     597        try:
     598            connect_to_server(register_app, register_display_desc, options)
     599        except Exception:
     600            register_app.cleanup()
     601            raise
     602   
     603        do_run_client(register_app)
    522604    elif mode == "top":
    523605        from xpra.client.top_client import TopClient, TopSessionClient
    524606        app = None
     
    18341916    return app
    18351917
    18361918
    1837 def make_progress_process(): 
     1919def make_progress_process():
    18381920    #start the splash subprocess
    18391921    from xpra.platform.paths import get_nodock_command
    18401922    cmd = get_nodock_command()+["splash"]
  • xpra/scripts/parsing.py

     
    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("--proxies", action="append",
     639        dest = "proxies", default = list(defaults.proxies 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.")
     
    12041209                      help="Specifies the file containing the encryption key to use for TCP sockets."
    12051210                      +" (default: '%default')")
    12061211
     1212    if supports_proxy:
     1213        group.add_option("--proxy-sessions", action="append",
     1214                         dest="proxy_sessions", default=list(defaults.proxy_sessions or []),
     1215                         help="Specifies the source of sessions a proxy server offers. Default: %s."%enabled_str(
     1216                             defaults.proxy_sessions))
     1217
     1218   
     1219
    12071220    options, args = parser.parse_args(cmdline[1:])
    12081221
    12091222    #ensure all the option fields are set even though
  • xpra/scripts/server.py

     
    419419        get_util_logger().warn("Warning: bind-rfb sockets cannot be used with '%s' mode" % mode)
    420420        opts.bind_rfb = []
    421421
     422    if proxying and opts.proxies:
     423        warn("Warning: the 'proxies' option is used,")
     424        warn("but no server started")
     425
     426    # TODO O include all sql-mechisms
     427    if proxying and \
     428        "register" in opts.proxy_sessions and \
     429            not (any(auth.startswith( "sqlite" ) for auth in opts.auth)
     430            or any(auth.startswith( "sqlite" ) for auth in opts.tcp_auth)):
     431        error_cb("--proxy_sessions=register need sqlite auth mechanism; exiting immediately")
     432
    422433    if not shadowing and not starting_desktop:
    423434        opts.rfb_upgrade = 0
    424435
  • xpra/server/auth/sqlauthbase.py

     
    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
     7import datetime
     8import secrets
     9from abc import abstractmethod, ABC
     10
     11from typing import Type
     12
    713from xpra.util import csv, parse_simple_dict
    814from xpra.os_util import getuid, getgid
    915from xpra.server.auth.sys_auth_base import SysAuthenticator, log
     
    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 = 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 = self.authenticate_hmac
    2132
     
    3142        return tuple(str(x[0]) for x in data)
    3243
    3344    def get_sessions(self):
    34         cursor = self.db_cursor(self.sessions_query, (self.username, self.password_used or ""))
     45        cursor = self.db_cursor(self.sessions_query, (self.username, ))
    3546        data = cursor.fetchone()
    3647        if not data:
    3748            return None
     
    4455            displays = []
    4556            env_options = {}
    4657            session_options = {}
     58            updated = ""
     59            source = ""
     60            session_id = 0
    4761            if len(data)>2:
    4862                displays = [x.strip() for x in str(data[2]).split(",")]
    4963            if len(data)>3:
     
    5064                env_options = parse_simple_dict(str(data[3]), ";")
    5165            if len(data)>4:
    5266                session_options = parse_simple_dict(str(data[4]), ";")
     67            if len(data) > 5:
     68                updated = str(data[5])
     69            if len(data) > 6:
     70                source = str(data[6])
     71            if len(data) > 7: # TODO O why check if present?
     72                session_id = int(data[7])
    5373        except Exception as e:
    5474            log("parse_session_data() error on row %s", data, exc_info=True)
    5575            log.error("Error: sqlauth database row parsing problem:")
    5676            log.error(" %s", e)
    5777            return None
    58         return uid, gid, displays, env_options, session_options
     78        return uid, gid, displays, env_options, session_options, updated, source, session_id
    5979
     80    def get_user_id(self):
     81        cursor = self.db_cursor(self.user_query, (self.username,))
     82        return cursor.fetchone()["id"]
    6083
     84    # add a new session
     85    def proxy_register(self, update_token, displays = "", uid = "nobody", gid = "nobody", env_options = "", session_options = ""):
     86   
     87        updated = datetime.datetime.now()
     88        source = "register"
     89
     90        user_id = self.get_user_id()
     91   
     92        cursor = self.db_cursor(self.sessions_add, (uid, gid, displays, env_options, session_options, updated, source, update_token))
     93        session_id = cursor.lastrowid
     94
     95        self.db_cursor(self.user_session_add, (user_id, session_id))
     96       
     97        return session_id
     98
     99    # update a session
     100    def proxy_update(self, update_token, displays, uid, gid, env_options, session_options):
     101   
     102        cursor = self.db_cursor(self.sessions_get_by_token, (update_token,))
     103        sessionRow = cursor.fetchone()
     104
     105        desc = cursor.description
     106        column_names = [col[0] for col in desc]
     107        session = dict(zip(column_names, sessionRow))
     108   
     109        if displays is not None:
     110            session["displays"] = displays
     111   
     112        if uid is not None:
     113            session["uid"] = uid
     114
     115        if gid is not None:
     116            session["gid"] = gid
     117
     118        if env_options is not None:
     119            session["env_options"] = env_options
     120
     121        if session_options is not None:
     122            session["session_options"] = session_options
     123   
     124        session["updated"] = datetime.datetime.now()
     125        session["source"] = "update"
     126   
     127        self.db_cursor(self.sessions_update,
     128                                (session["uid"], session["gid"], session["displays"], session["env_options"],
     129                                 session["session_options"], session["updated"], session["source"], session["id"]))
     130   
     131        return True
     132
     133class QueriesBase(ABC):
     134    @staticmethod
     135    @abstractmethod
     136    def users_get():
     137        pass
     138   
     139    @staticmethod
     140    @abstractmethod
     141    def users_get_with_password():
     142        pass
     143
     144    @staticmethod
     145    @abstractmethod
     146    def password_get():
     147        pass
     148
     149    @staticmethod
     150    @abstractmethod
     151    def users_add():
     152        pass
     153
     154    @staticmethod
     155    @abstractmethod
     156    def users_delete():
     157        pass
     158
     159
     160    @staticmethod
     161    @abstractmethod
     162    def sessions_query():
     163        pass
     164
     165    @staticmethod
     166    @abstractmethod
     167    def sessions_query_by_username():
     168        pass
     169   
     170    @staticmethod
     171    @abstractmethod
     172    def sessions_get():
     173        pass
     174
     175    @staticmethod
     176    @abstractmethod
     177    def sessions_get_by_token():
     178        pass
     179
     180    @staticmethod
     181    @abstractmethod
     182    def sessions_add():
     183        pass
     184
     185    @staticmethod
     186    @abstractmethod
     187    def sessions_update():
     188        pass
     189
     190    @staticmethod
     191    @abstractmethod
     192    def sessions_delete():
     193        pass
     194
     195
     196    @staticmethod
     197    @abstractmethod
     198    def user_session_add():
     199        pass
     200
     201    @staticmethod
     202    @abstractmethod
     203    def user_session_delete_by_user():
     204        pass
     205
     206    @staticmethod
     207    @abstractmethod
     208    def user_session_delete_by_session():
     209        pass
     210
     211class DatabaseBaseQueries(QueriesBase):
     212    users_get = (
     213        "SELECT id, username, password, email "
     214        "FROM users "
     215        "WHERE username=(%s)")
     216    users_get_with_password = (
     217        "SELECT id, username, password, email "
     218        "FROM users "
     219        "WHERE username=(%s) "
     220        "AND password=(%s)")
     221    password_get = "SELECT password FROM users WHERE username=(%s)"
     222    users_add = (
     223        "INSERT INTO users(username, password, email) "
     224        "VALUES(%s, %s, %s)")
     225    users_delete = "DELETE FROM users WHERE username=(%s)"
     226   
     227    sessions_query = (
     228        "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 "
     229        "FROM sessions AS s "
     230        "JOIN user_session AS us ON us.session = s.id "
     231        "LEFT JOIN users AS u ON u.id = us.user "
     232        "ORDER BY u.id, s.id")
     233    sessions_query_by_username = (
     234        "SELECT s.uid, s.gid, s.displays, s.env_options, s.session_options, s.updated, s.source, s.id "
     235        "FROM sessions AS s "
     236        "JOIN user_session AS us ON us.session = s.id "
     237        "JOIN users AS u ON u.id = us.user "
     238        "WHERE u.username=(%s)")
     239    sessions_get = (
     240        "SELECT id, uid, gid, displays, env_options, session_options, updated, source "
     241        "FROM sessions "
     242        "WHERE id=(%s)")
     243    sessions_get_by_token = (
     244        "SELECT id, uid, gid, displays, env_options, session_options, updated, source "
     245        "FROM sessions "
     246        "WHERE update_token=(%s)")
     247    sessions_add = (
     248        "INSERT INTO sessions "
     249        "(uid, gid, displays, env_options, session_options, updated, source, update_token) "
     250        "VALUES(%s, %s, %s, %s, %s, %s, %s, %s)")
     251    sessions_update = (
     252        "UPDATE sessions "
     253        "SET uid = %s, "
     254        "gid = %s, "
     255        "displays = %s, "
     256        "env_options = %s, "
     257        "session_options = %s, "
     258        "updated = %s, "
     259        "source = %s "
     260        "WHERE id=(%s)")
     261    sessions_delete = "DELETE FROM sessions WHERE id=(%s)"
     262   
     263    user_session_add = (
     264        "INSERT INTO user_session "
     265        "(user, session) "
     266        "VALUES(%s, %s)")
     267    user_session_delete_by_user = "DELETE FROM user_session WHERE user=(%s)"
     268    user_session_delete_by_session = "DELETE FROM user_session WHERE session=(%s)"
     269   
     270
    61271class DatabaseUtilBase:
    62272
    63273    def __init__(self, uri):
    64274        self.uri = uri
    65275        self.param = "?"
     276        self.queries: Type[QueriesBase] = DatabaseBaseQueries
    66277
    67278    def exec_database_sql_script(self, cursor_cb, *sqlargs):
    68279        raise NotImplementedError()
    69280
    70281    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)
     282        sql_users = ("CREATE TABLE users ("
     283            "id INTEGER PRIMARY KEY NOT NULL, "
     284            "username VARCHAR(255) NOT NULL, "
     285            "password VARCHAR(255), "
     286            "email VARCHAR(255), "
     287            "UNIQUE(username)"
     288            ")")
     289        self.exec_database_sql_script(None, sql_users)
     290       
     291        sql_sessions = ("CREATE TABLE sessions ("
     292            "id INTEGER PRIMARY KEY NOT NULL, "
     293            "uid VARCHAR(63), "
     294            "gid VARCHAR(63), "
     295            "displays VARCHAR(8191), "
     296            "env_options VARCHAR(8191), "
     297            "session_options VARCHAR(8191), "
     298            "updated VARCHAR(8191), "
     299            "source VARCHAR(8191), "
     300            "update_token VARCHAR(8191) NOT NULL, "
     301            "UNIQUE(update_token)"
     302            ")")
     303        self.exec_database_sql_script(None, sql_sessions)
     304       
     305        sql_user_session = ("CREATE TABLE user_session ("
     306            "user int NOT NULL, "
     307            "session int NOT NULL, "
     308            "PRIMARY KEY (user, session), "
     309            "FOREIGN KEY(user) REFERENCES users(id), "
     310            "FOREIGN KEY(session) REFERENCES sessions(id)"
     311            ")")
     312        self.exec_database_sql_script(None, sql_user_session)
    80313
    81     def add_user(self, username, password, uid=getuid(), gid=getgid(),
     314    def add_user(self, username, password, email =""):
     315        self.exec_database_sql_script(None, self.queries.users_add, (str(username), str(password), str(email)))
     316
     317    def add_session(self, username, uid=getuid(), gid=getgid(),
    82318                 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))
     319   
     320        update_token = secrets.token_urlsafe(64)
     321       
     322        user_cursor = self.exec_database_sql_script(None, self.queries.users_get, (username,))
     323        user_row = user_cursor.fetchone()
     324        if not user_row:
     325            print("no user found with name: %", username)
     326            user_cursor.close()
     327            return
     328        user_id = user_row.id
     329        print("found id:"%user_id)
     330        user_cursor.close()
     331   
     332        session_cursor = self.exec_database_sql_script(None, self.queries.sessions_add,
     333                                      (uid, gid, displays, env_options, session_options, datetime.datetime.now(),
     334                                       "commandline", update_token))
     335        session_id = session_cursor.lastrowid
     336        session_cursor.close()
     337       
     338        user_session_cursor = self.exec_database_sql_script(None, self.queries.user_session_add,
     339                                      (user_id, session_id))
     340        user_session_cursor.close()
    87341
     342        print("add session success")
     343        print("session_id  :" % session_id)
     344        print("update_token:" % update_token)
     345   
     346
    88347    def remove_user(self, username, password=None):
    89         sql = "DELETE FROM users WHERE username=%s" % self.param
     348        user_sql = self.queries.users_get
    90349        sqlargs = (username, )
    91350        if password:
    92             sql += " AND password=%s" % self.param
     351            user_sql = self.queries.users_get_with_password
    93352            sqlargs = (username, password)
    94         self.exec_database_sql_script(None, sql, sqlargs)
     353        users_cursor = self.exec_database_sql_script(None, user_sql, sqlargs)
     354        user = users_cursor.fetchone()
     355        user_id = user.id
     356       
     357        self.exec_database_sql_script(None, self.queries.user_session_delete_by_user, user_id)
    95358
     359        self.exec_database_sql_script(None, self.queries.users_delete, username)
     360
     361    def remove_session(self, session_id):
     362        self.exec_database_sql_script(None, self.queries.user_session_delete_by_session, session_id)
     363       
     364        self.exec_database_sql_script(None, self.queries.sessions_delete, session_id)
     365       
    96366    def list_users(self):
    97         fields = ("username", "password", "uid", "gid", "displays", "env_options", "session_options")
     367        fields = ("user_id", "username", "password", "email", "session_id", "uid", "gid", "displays", "env_options", "session_options", "updated", "source", "update_token")
    98368        def fmt(values, sizes):
    99369            s = ""
    100370            for i, field in enumerate(values):
     
    121391            for row in rows:
    122392                print(fmt(row, sizes))
    123393            cursor.close()
    124         sql = "SELECT %s FROM users" % csv(fields)
    125         self.exec_database_sql_script(cursor_callback, sql)
     394        self.exec_database_sql_script(cursor_callback, self.queries.sessions_query, )
    126395
    127396    def authenticate(self, username, password):
    128397        auth_class = self.get_authenticator_class()
     
    129398        a = auth_class(username, self.uri)
    130399        passwords = a.get_passwords()
    131400        assert passwords
    132         log("authenticate: got %i passwords", len(passwords))
     401        assert len(passwords) in 1
    133402        assert password in passwords
    134403        a.password_used = password
    135         sessions = a.get_sessions()
    136         assert sessions
    137         print("success, found sessions: %s" % (sessions, ))
     404        print("success, found password")
     405        #sessions = a.get_sessions()
     406        #assert sessions
     407        #print("success, found sessions: %s" % (sessions, ))
    138408
    139409    def get_authenticator_class(self):
    140410        raise NotImplementedError()
     
    146416        print("usage:")
    147417        print(" %s %s create" % (argv[0], conn_str))
    148418        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))
     419        print(" %s %s add_user username password [email]" % (argv[0], conn_str))
     420        print(" %s %s add_session username [uid, gid, displays, env_options, session_options]" % (argv[0], conn_str))
     421        print(" %s %s remove_user username [password]" % (argv[0], conn_str))
     422        print(" %s %s remove_session session_id" % (argv[0], conn_str))
    151423        print(" %s %s authenticate username password" % (argv[0], conn_str))
    152424        return 1
    153425    from xpra.platform import program_context
     
    162434            if l!=3:
    163435                return usage()
    164436            dbutil.create()
    165         elif cmd=="add":
    166             if l<5 or l>10:
     437        elif cmd=="add_user":
     438            if l not in (5, 6):
    167439                return usage()
    168440            dbutil.add_user(*argv[3:])
    169         elif cmd=="remove":
     441        elif cmd=="add_session":
     442            if l<4 or l>9:
     443                return usage()
     444            dbutil.add_session(*argv[3:])
     445        elif cmd=="remove_user":
    170446            if l not in (4, 5):
    171447                return usage()
    172448            dbutil.remove_user(*argv[3:])
     449        elif cmd=="remove_session":
     450            if l not in 4:
     451                return usage()
     452            dbutil.remove_session(*argv[3:])
    173453        elif cmd=="list":
    174454            if l!=3:
    175455                return usage()
  • xpra/server/auth/sqlite_auth.py

     
    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, \
     14    DatabaseBaseQueries
    1315
    1416
    1517class Authenticator(SQLAuthenticator):
     
    2022            exec_cwd = kwargs.get("exec_cwd", os.getcwd())
    2123            filename = os.path.join(exec_cwd, filename)
    2224        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=(?)")
     25       
     26        self.password_query = kwargs.pop("password_query", SqliteDatabaseQueries.password_get)
     27        self.sessions_query = kwargs.pop("sessions_query", SqliteDatabaseQueries.sessions_query)
     28        self.sessions_get = kwargs.pop("sessions_get", SqliteDatabaseQueries.sessions_get)
     29        self.sessions_get_by_token = kwargs.pop("sessions_get_by_token", SqliteDatabaseQueries.sessions_get_by_token)
     30        self.sessions_add = kwargs.pop("sessions_add", SqliteDatabaseQueries.sessions_add)
     31        self.sessions_update = kwargs.pop("sessions_update", SqliteDatabaseQueries.sessions_update)
     32        self.user_query = kwargs.pop("user_query", SqliteDatabaseQueries.users_get)
     33        self.user_session_add = kwargs.pop("user_session_add", SqliteDatabaseQueries.user_session_add)
     34       
    2735        self.authenticate = self.authenticate_hmac
    2836
    2937    def __repr__(self):
     
    3947        cursor = db.cursor()
    4048        cursor.execute(*sqlargs)
    4149        log("db_cursor(%s)=%s", sqlargs, cursor)
     50        db.commit()
    4251        return cursor
    4352
    4453    def parse_session_data(self, data):
     
    4857            displays = []
    4958            env_options = {}
    5059            session_options = {}
     60            updated = ""
     61            source = ""
    5162            if data["displays"]:
    5263                displays = [x.strip() for x in str(data["displays"]).split(",")]
    5364            if data["env_options"]:
     
    5465                env_options = parse_simple_dict(str(data["env_options"]), ";")
    5566            if data["session_options"]:
    5667                session_options=parse_simple_dict(str(data["session_options"]), ";")
     68            if data["updated"]:
     69                updated = str(data["updated"])
     70            if data["source"]:
     71                source = str(data["source"])
    5772        except Exception as e:
    5873            log("get_sessions() error on row %s", data, exc_info=True)
    5974            log.error("Error: sqlauth database row parsing problem:")
    6075            log.error(" %s", e)
    6176            return None
    62         return uid, gid, displays, env_options, session_options
     77        return uid, gid, displays, env_options, session_options, updated, source
    6378
     79class SqliteDatabaseQueries(QueriesBase):
     80    users_get = (
     81        "SELECT id, username, password, email "
     82        "FROM users "
     83        "WHERE username = (?)")
     84    users_get_with_password = (
     85        "SELECT id, username, password, email "
     86        "FROM users "
     87        "WHERE username=(?) "
     88        "AND password=(?)")
     89    password_get = "SELECT password FROM users WHERE username=(?)"
     90    users_add = (
     91        "INSERT INTO users(username, password, email) "
     92        "VALUES((?), (?), (?))")
     93    users_delete = "DELETE FROM users WHERE username=(?)"
     94   
     95    sessions_query = (
     96        "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 "
     97        "FROM sessions AS s "
     98        "JOIN user_session AS us ON us.session = s.id "
     99        "LEFT JOIN users AS u ON u.id = us.user "
     100        "ORDER BY u.id, s.id")
     101    sessions_query_by_username = (
     102        "SELECT s.uid, s.gid, s.displays, s.env_options, s.session_options, s.updated, s.source, s.id "
     103        "FROM sessions AS s "
     104        "JOIN user_session AS us ON us.session = s.id "
     105        "JOIN users AS u ON u.id = us.user "
     106        "WHERE u.username=(?)")
     107    sessions_get = (
     108        "SELECT id, uid, gid, displays, env_options, session_options, updated, source "
     109        "FROM sessions "
     110        "WHERE id=(?)")
     111    sessions_get_by_token = (
     112        "SELECT id, uid, gid, displays, env_options, session_options, updated, source "
     113        "FROM sessions "
     114        "WHERE update_token=(?)")
     115    sessions_add = (
     116        "INSERT INTO sessions "
     117        "(uid, gid, displays, env_options, session_options, updated, source, update_token) "
     118        "VALUES((?), (?), (?), (?), (?), (?), (?), (?))")
     119    sessions_update = (
     120        "UPDATE sessions "
     121        "SET uid = (?), "
     122        "gid = (?), "
     123        "displays = (?), "
     124        "env_options = (?), "
     125        "session_options = (?), "
     126        "updated = (?), "
     127        "source = (?) "
     128        "WHERE id = (?)")
     129    sessions_delete = "DELETE FROM sessions WHERE id=(?)"
     130   
    64131
     132    user_session_add = (
     133        "INSERT INTO user_session "
     134        "(user, session) "
     135        "VALUES((?), (?))")
     136    user_session_delete_by_user = "DELETE FROM user_session WHERE user=(?)"
     137    user_session_delete_by_session = "DELETE FROM user_session WHERE session=(?)"
     138
    65139class SqliteDatabaseUtil(DatabaseUtilBase):
    66140
    67141    def __init__(self, uri):
     
    69143        import sqlite3
    70144        assert sqlite3.paramstyle=="qmark"
    71145        self.param = "?"
     146        self.queries: Type[QueriesBase] = SqliteDatabaseQueries
    72147
    73148    def exec_database_sql_script(self, cursor_cb, *sqlargs):
    74149        import sqlite3
  • xpra/server/proxy/proxy_server.py

     
    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
     
    268269                    self.send_disconnect(proto, "timeout")
    269270            self.timeout_add(10*1000, force_exit_request_client)
    270271            return
     272        if is_req("register") or is_req("update"):
     273   
     274            from xpra.server.auth.sqlauthbase import SQLAuthenticator
     275   
     276            authenticator = next(  # TODO O fails if none present
     277                authenticator for authenticator in proto.authenticators if isinstance(authenticator, SQLAuthenticator))
     278           
     279            if is_req("register"):
     280                self.process_register(proto, authenticator, c)
     281                return
     282            elif is_req("update"):
     283                self.process_update(proto, authenticator, c)
     284                return
    271285        self.proxy_auth(proto, c, auth_caps)
    272286
    273287    def proxy_auth(self, client_proto, c, auth_caps):
     
    639653                    info["instances"] = instances_info
    640654                    info["proxies"] = len(instances)
    641655        return info
     656
     657    # acts upon a proxy register request:
     658    # - register new session
     659    # - return session_id, update_token
     660    def process_register(self, proto, authenticator, c):
     661   
     662        update_token = secrets.token_urlsafe(64)
     663   
     664        displays = c.strget("displays")
     665        uid = c.strget("uid")
     666        gid = c.strget("gid")
     667        env_options = c.strget("env_options")
     668        session_options = c.strget("session_options")
     669   
     670        session_id = authenticator.proxy_register(update_token, displays, uid, gid, env_options,
     671                                                  session_options)
     672   
     673        payload = {"success": True, "mode": "register", "session_id": session_id, "update_token": update_token}
     674        proto.send_now(("hello", payload))
     675
     676    # acts upon a proxy update request:
     677    # - update the session from token
     678    # - return success
     679    def process_update(self, proto, authenticator, c):
     680        update_token = c.strget("update_token")  # TODO O get from request or submitted value
     681   
     682        displays = c.strget("displays")
     683        uid = c.strget("uid")
     684        gid = c.strget("gid")
     685        env_options = c.strget("env_options")
     686        session_options = c.strget("session_options")
     687   
     688        authenticator.proxy_update(update_token, displays, uid, gid, env_options, session_options)
     689
     690        # start_thread(self.keep_alive, "KeepAlive", daemon=True, args=(update_token, displays))
     691   
     692        payload = {"success": True, "mode": "update"}
     693        proto.send_now(("hello", payload))
     694       
     695 No newline at end of file
  • xpra/server/server_core.py

     
    77# later version. See the file COPYING for details.
    88
    99import os
     10import secrets
    1011import sys
    1112import errno
    1213import socket
     
    186187        self.server_idle_timeout = 0
    187188        self.server_idle_timer = None
    188189        self.bandwidth_limit = 0
     190        self.proxy_sessions = {}
    189191
    190192        self.init_uuid()
    191193        sanity_checks()
     
    239241        self.init_ssl(opts)
    240242        if self.pidfile:
    241243            self.pidinode = write_pidfile(self.pidfile)
     244        self.proxy_sessions = opts.proxy_sessions
    242245
    243 
    244246    def init_ssl(self, opts):
    245247        self.ssl_mode = opts.ssl
    246248        if self.ssl_mode.lower() in FALSE_OPTIONS:
     
    16161618        proto.send_now(("challenge", salt, auth_caps or "", digest, salt_digest, prompt))
    16171619        self.schedule_verify_connection_accepted(proto, CHALLENGE_TIMEOUT)
    16181620
     1621    def verify_update_token(self, proto, packet, c):
     1622        def auth_failed(msg):
     1623            authlog.warn("Warning: token verification failed")
     1624            authlog.warn(" %s", msg)
     1625            self.timeout_add(1000, self.disconnect_client, proto, msg)
     1626
     1627        if not "register" in self.proxy_sessions:
     1628            authlog("registering not enabled")
     1629            auth_failed(SERVER_ERROR)
     1630            return
     1631
     1632        from xpra.server.auth.sqlauthbase import SQLAuthenticator
     1633        update_authenticator = next( # TODO O fails if none present
     1634            update_authenticator for update_authenticator in proto.authenticators if isinstance(update_authenticator, SQLAuthenticator))
     1635
     1636        if update_authenticator is None:
     1637            log.error("Error: the proxy server requires an sql authentication mode")
     1638            auth_failed(SERVER_ERROR)
     1639            return
     1640       
     1641        # TODO O verify token
     1642       
     1643       
     1644        self.auth_verified(proto, packet, None)
     1645
    16191646    def verify_auth(self, proto, packet, c):
    16201647        def auth_failed(msg):
    16211648            authlog.warn("Warning: authentication failed")
     
    16391666                auth_failed(str(e))
    16401667                return
    16411668
     1669        proxy_update_request = c.boolget("update_request")
     1670        if proxy_update_request:
     1671            self.verify_update_token(proto, packet, c)
     1672            return
     1673
    16421674        digest_modes = c.strtupleget("digest", ("hmac", ))
    16431675        salt_digest_modes = c.strtupleget("salt-digest", ("xor",))
    16441676        #client may have requested encryption: