xpra icon
Bug tracker and wiki

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


Ticket #426: auth-v8.patch

File auth-v8.patch, 85.1 KB (added by Antoine Martin, 8 years ago)

adds attempts at signal handling and process cleanup + 1 important server fix

  • setup.py

     
    862862
    863863
    864864#*******************************************************************************
    865 toggle_packages(server_ENABLED, "xpra.server", "xpra.server.stats")
     865toggle_packages(server_ENABLED, "xpra.server", "xpra.server.stats", "xpra.server.auth")
    866866if WIN32 and not server_ENABLED:
    867867    #with py2exe, we have to remove the default packages and let it figure it out...
    868868    #(otherwise, we can't remove specific files from those packages)
  • tests/xpra/test_protocol_base.py

     
    1010logging.basicConfig(format="%(asctime)s %(message)s")
    1111logging.root.setLevel(logging.DEBUG)
    1212
    13 from xpra.net.protocol import Protocol, set_scheduler
     13from xpra.net.protocol import Protocol
    1414from xpra.net.bytestreams import SocketConnection
    1515from xpra.log import Logger
    1616log = Logger()
    1717
    1818import gobject
    1919gobject.threads_init()
    20 set_scheduler(gobject)
    2120
    2221TEST_SOCKFILE = "./test-socket"
    2322
     
    5554        sock.settimeout(None)
    5655        sock.setblocking(1)
    5756        sc = makeSocketConnection(sock, str(address)+"server")
    58         protocol = Protocol(sc, self.process_packet)
     57        protocol = Protocol(gobject, sc, self.process_packet)
    5958        protocol.salt = None
    6059        protocol.set_compression_level(1)
    6160        protocol.start()
     
    7675        sock.connect(TEST_SOCKFILE)
    7776        sock.settimeout(None)
    7877        sc = makeSocketConnection(sock, "test-client-socket")
    79         self.protocol = Protocol(sc, self.process_packet, None)
     78        self.protocol = Protocol(gobject, sc, self.process_packet, None)
    8079        self.protocol.start()
    8180        if len(self.packets)>0:
    8281            gobject.timeout_add(1000, self.send_packet)
  • xpra/client/client_base.py

     
    1818from xpra.scripts.config import ENCRYPTION_CIPHERS, python_platform
    1919from xpra.version_util import version_compat_check, add_version_info
    2020from xpra.platform.features import GOT_PASSWORD_PROMPT_SUGGESTION
    21 from xpra.os_util import get_hex_uuid, get_machine_id, SIGNAMES, strtobytes, bytestostr
     21from xpra.platform.info import get_username, get_name
     22from xpra.os_util import get_hex_uuid, get_machine_id, load_binary_file, SIGNAMES, strtobytes, bytestostr
    2223from xpra.util import typedict
    2324
    2425EXIT_OK = 0
     
    3435EXIT_MMAP_TOKEN_FAILURE = 10
    3536
    3637DEFAULT_TIMEOUT = 20*1000
     38ALLOW_UNENCRYPTED_PASSWORDS = os.environ.get("XPRA_ALLOW_UNENCRYPTED_PASSWORDS", "0")=="1"
    3739
    3840
    3941class XpraClientBase(object):
     
    5052    def __init__(self):
    5153        self.exit_code = None
    5254        self.compression_level = 0
     55        self.username = None
    5356        self.password = None
    5457        self.password_file = None
    5558        self.password_sent = False
    5659        self.encryption = None
     60        self.encryption_keyfile = None
    5761        self.quality = -1
    5862        self.min_quality = 0
    5963        self.speed = 0
     
    7882
    7983    def init(self, opts):
    8084        self.compression_level = opts.compression_level
     85        self.username = opts.username
    8186        self.password_file = opts.password_file
    8287        self.encryption = opts.encryption
     88        self.encryption_keyfile = opts.encryption_keyfile
    8389        self.quality = opts.quality
    8490        self.min_quality = opts.min_quality
    8591        self.speed = opts.speed
     
    115121        #overriden in subclasses!
    116122        return "Python"
    117123
     124    def get_scheduler(self):
     125        raise NotImplementedError()
     126
    118127    def setup_connection(self, conn):
    119128        log.debug("setup_connection(%s)", conn)
    120         self._protocol = Protocol(conn, self.process_packet, self.next_packet)
     129        self._protocol = Protocol(self.get_scheduler(), conn, self.process_packet, self.next_packet)
    121130        self._protocol.large_packets.append("keymap-changed")
    122131        self._protocol.large_packets.append("server-settings")
    123132        self._protocol.set_compression_level(self.compression_level)
     
    145154            i += 1
    146155
    147156    def send_hello(self, challenge_response=None):
    148         hello = self.make_hello(challenge_response)
     157        hello = self.make_hello_base(challenge_response)
     158        if challenge_response or not self.password_file:
     159            #could be the real hello, so we need all the details:
     160            hello.update(self.make_hello())
    149161        log.debug("send_hello(%s) packet=%s", challenge_response, hello)
    150162        self.send("hello", hello)
    151163
    152     def make_hello(self, challenge_response=None):
    153         capabilities = {}
     164    def make_hello_base(self, challenge_response=None):
     165        capabilities = {"namespace"             : True,
     166                        "raw_packets"           : True,
     167                        "chunked_compression"   : True,
     168                        "digest"                : ("hmac", "xor"),
     169                        "rencode"               : has_rencode,
     170                        "lz4"                   : has_lz4,
     171                        "hostname"              : socket.gethostname(),
     172                        "uuid"                  : self.uuid,
     173                        "username"              : self.username,
     174                        "name"                  : get_name(),
     175                        "platform"              : sys.platform,
     176                        "platform.release"      : python_platform.release(),
     177                        "platform.machine"      : python_platform.machine(),
     178                        "platform.processor"    : python_platform.processor(),
     179                        "client_type"           : self.client_type(),
     180                        "python.version"        : sys.version_info[:3],
     181                        }
     182        if has_rencode:
     183            capabilities["rencode.version"] = rencode_version
    154184        add_version_info(capabilities)
    155         capabilities["python.version"] = sys.version_info[:3]
     185
    156186        if challenge_response:
    157187            assert self.password
    158188            capabilities["challenge_response"] = challenge_response
     
    165195            capabilities["cipher.key_salt"] = key_salt
    166196            iterations = 1000
    167197            capabilities["cipher.key_stretch_iterations"] = iterations
    168             self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations)
     198            key = self.get_encryption_key()
     199            if key is None:
     200                self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing")
     201                return
     202            self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations)
    169203            log("encryption capabilities: %s", [(k,v) for k,v in capabilities.items() if k.startswith("cipher")])
    170         capabilities["platform"] = sys.platform
    171         capabilities["platform.release"] = python_platform.release()
    172         capabilities["platform.machine"] = python_platform.machine()
    173         capabilities["platform.processor"] = python_platform.processor()
    174         capabilities["client_type"] = self.client_type()
    175         capabilities["namespace"] = True
    176         capabilities["raw_packets"] = True
    177         capabilities["chunked_compression"] = True
    178         capabilities["rencode"] = has_rencode
    179         capabilities["lz4"] = has_lz4
    180         if has_rencode:
    181             capabilities["rencode.version"] = rencode_version
    182         capabilities["hostname"] = socket.gethostname()
    183         capabilities["uuid"] = self.uuid
    184         try:
    185             from xpra.platform.info import get_username, get_name
    186             capabilities["username"] = get_username()
    187             capabilities["name"] = get_name()
    188         except:
    189             log.error("failed to get username/name", exc_info=True)
    190         capabilities["randr_notify"] = False    #only client.py cares about this
    191         capabilities["windows"] = False         #only client.py cares about this
     204        return capabilities
     205
     206    def make_hello(self):
     207        capabilities = {
     208                        "randr_notify"        : False,        #only client.py cares about this
     209                        "windows"            : False,        #only client.py cares about this
     210                       }
    192211        if self._reverse_aliases:
    193212            capabilities["aliases"] = self._reverse_aliases
    194213        return capabilities
     
    304323        if self.encryption:
    305324            assert len(packet)>=3, "challenge does not contain encryption details to use for the response"
    306325            server_cipher = packet[2]
    307             self.set_server_encryption(server_cipher)
    308         import hmac
    309         challenge_response = hmac.HMAC(self.password, salt)
    310         password_hash = challenge_response.hexdigest()
     326            key = self.get_encryption_key()
     327            if key is None:
     328                self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing")
     329                return
     330            if not self.set_server_encryption(server_cipher, key):
     331                return
     332        digest = "hmac"
     333        if len(packet)>=4:
     334            digest = packet[3]
     335        if digest=="hmac":
     336            import hmac
     337            challenge_response = hmac.HMAC(self.password, salt).hexdigest()
     338        elif digest=="xor":
     339            #don't send XORed password unencrypted:
     340            if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS:
     341                self.warn_and_quit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest)
     342                return
     343            from xpra.util import xor
     344            challenge_response = xor(self.password, salt)
     345        else:
     346            self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest)
     347            return
    311348        self.password_sent = True
    312         self.send_hello(password_hash)
     349        self.send_hello(challenge_response)
    313350
    314     def set_server_encryption(self, capabilities):
     351    def set_server_encryption(self, capabilities, key):
    315352        def get(key, default=None):
    316353            return capabilities.get(strtobytes(key), default)
    317354        cipher = get("cipher")
     
    324361        if cipher not in ENCRYPTION_CIPHERS:
    325362            self.warn_and_quit(EXIT_ENCRYPTION, "unsupported server cipher: %s, allowed ciphers: %s" % (cipher, ", ".join(ENCRYPTION_CIPHERS)))
    326363            return False
    327         self._protocol.set_cipher_out(cipher, cipher_iv, self.get_password(), key_salt, iterations)
     364        self._protocol.set_cipher_out(cipher, cipher_iv, key, key_salt, iterations)
     365        return True
    328366
    329367
    330     def get_password(self):
    331         if self.password is None:
    332             self.load_password()
    333         return self.password
     368    def get_encryption_key(self):
     369        key = load_binary_file(self.encryption_keyfile)
     370        if key is None and self.password_file:
     371            key = load_binary_file(self.password_file)
     372            if key:
     373                log("used password file as encryption key")
     374        if key is None:
     375            raise Exception("failed to load encryption keyfile %s" % self.encryption_keyfile)
     376        return key.strip("\n\r")
    334377
    335378    def load_password(self):
    336         try:
    337             filename = os.path.expanduser(self.password_file)
    338             passwordFile = open(filename, "rU")
    339             self.password = passwordFile.read()
    340             passwordFile.close()
    341             while self.password.endswith("\n") or self.password.endswith("\r"):
    342                 self.password = self.password[:-1]
    343         except IOError, e:
     379        filename = os.path.expanduser(self.password_file)
     380        self.password = load_binary_file(filename)
     381        if self.password is None:
    344382            self.warn_and_quit(EXIT_PASSWORD_FILE_ERROR, "failed to open password file %s: %s" % (self.password_file, e))
    345383            return False
     384        self.password = self.password.strip("\n\r")
    346385        log("password read from file %s is %s", self.password_file, self.password)
    347386        return True
    348387
     
    375414        self._protocol.chunked_compression = c.boolget("chunked_compression")
    376415        if use_rencode and c.boolget("rencode"):
    377416            self._protocol.enable_rencode()
    378         if c.boolget("lz4") and has_lz4 and self._protocol.chunked_compression and self.compression_level>0 and self.compression_level<3:
     417        if c.boolget("lz4") and has_lz4 and self._protocol.chunked_compression and self.compression_level==1:
    379418            self._protocol.enable_lz4()
    380419        if self.encryption:
    381420            #server uses a new cipher after second hello:
    382             self.set_server_encryption(c)
     421            key = self.get_encryption_key()
     422            assert key, "encryption key is missing"
     423            if not self.set_server_encryption(c, key):
     424                return False
    383425        self._protocol.aliases = c.dictget("aliases", {})
    384426        if self.pings:
    385427            self.timeout_add(1000, self.send_ping)
  • xpra/client/gobject_client_base.py

     
    1313import re
    1414from xpra.util import nonl
    1515from xpra.client.client_base import XpraClientBase, DEFAULT_TIMEOUT, EXIT_TIMEOUT, EXIT_OK
    16 from xpra.net.protocol import set_scheduler
    17 set_scheduler(gobject)
    1816
    1917
    2018class GObjectXpraClient(XpraClientBase, gobject.GObject):
     
    4139    def source_remove(self, *args):
    4240        return gobject.source_remove(*args)
    4341
     42    def get_scheduler(self):
     43        return gobject
    4444
     45
    4546    def client_type(self):
    4647        #overriden in subclasses!
    4748        return "Python/GObject"
     
    8081        self.gobject_mainloop.run()
    8182        return  self.exit_code
    8283
    83     def make_hello(self, challenge_response=None):
    84         capabilities = XpraClientBase.make_hello(self, challenge_response)
     84    def make_hello(self):
     85        capabilities = XpraClientBase.make_hello(self)
    8586        capabilities["keyboard"] = False
    8687        return capabilities
    8788
     
    133134        GObjectXpraClient.init_packet_handlers(self)
    134135        self._ui_packet_handlers["screenshot"] = self._process_screenshot
    135136
    136     def make_hello(self, challenge_response=None):
    137         capabilities = GObjectXpraClient.make_hello(self, challenge_response)
     137    def make_hello(self):
     138        capabilities = GObjectXpraClient.make_hello(self)
    138139        capabilities["screenshot_request"] = True
    139140        return capabilities
    140141
     
    165166                log.info("%s=%s", k, nonl(v))
    166167        self.quit(0)
    167168
    168     def make_hello(self, challenge_response=None):
    169         capabilities = GObjectXpraClient.make_hello(self, challenge_response)
    170         log.debug("make_hello(%s) adding info_request to %s", challenge_response, capabilities)
     169    def make_hello(self):
     170        capabilities = GObjectXpraClient.make_hello(self)
     171        log.debug("make_hello() adding info_request to %s", capabilities)
    171172        capabilities["info_request"] = True
    172173        return capabilities
    173174
     
    185186        props = packet[1]
    186187        self.warn_and_quit(EXIT_OK, str(props.get("version")))
    187188
    188     def make_hello(self, challenge_response=None):
    189         capabilities = GObjectXpraClient.make_hello(self, challenge_response)
    190         log.debug("make_hello(%s) adding version_request to %s", challenge_response, capabilities)
     189    def make_hello(self):
     190        capabilities = GObjectXpraClient.make_hello(self)
     191        log.debug("make_hello() adding version_request to %s", capabilities)
    191192        capabilities["version_request"] = True
    192193        return capabilities
    193194
  • xpra/client/gtk2/client.py

     
    205205            self.tray.set_blinking(False)
    206206
    207207
    208     def make_hello(self, challenge_response=None):
    209         capabilities = GTKXpraClient.make_hello(self, challenge_response)
     208    def make_hello(self):
     209        capabilities = GTKXpraClient.make_hello(self)
    210210        if xor_str is not None:
    211211            capabilities["encoding.supports_delta"] = [x for x in ("png", "rgb24", "rgb32") if x in self.get_core_encodings()]
    212212        return capabilities
  • xpra/client/gtk3/client.py

     
    2323    INPUT_ONLY = None #got moved again???? Gtk.WindowWindowClass.INPUT_ONLY
    2424    ClientWindowClass = ClientWindow
    2525
    26     def make_hello(self, challenge_response=None):
     26    def make_hello(self):
    2727        capabilities = GTKXpraClient.make_hello(self, challenge_response)
    2828        if xor_str is not None:
    2929            capabilities["encoding.supports_delta"] = [x for x in ("rgb24", "rgb32") if x in self.get_core_encodings()]
  • xpra/client/gtk_base/gtk_client_base.py

     
    137137        return self.mask_to_names(modifiers_mask)
    138138
    139139
    140     def make_hello(self, challenge_response=None):
    141         capabilities = UIXpraClient.make_hello(self, challenge_response)
     140    def make_hello(self):
     141        capabilities = UIXpraClient.make_hello(self)
    142142        capabilities["named_cursors"] = len(cursor_names)>0
    143143        add_gtk_version_info(capabilities, gtk, "", True)
    144144        return capabilities
  • xpra/client/qt4/client.py

     
    1111log = Logger()
    1212
    1313from xpra.client.ui_client_base import UIXpraClient
    14 from xpra.net.protocol import set_scheduler
    1514from xpra.client.qt4.qt_keyboard_helper import QtKeyboardHelper
    1615from xpra.client.qt4.scheduler import getQtScheduler
    1716from xpra.client.qt4.client_window import ClientWindow
    18 set_scheduler(getQtScheduler())
    1917
    2018sys.modules['gtk']=None
    2119sys.modules['pygtk']=None
     
    4644    def client_toolkit(self):
    4745        return "qt4"
    4846
     47    def get_scheduler(self):
     48        return getQtScheduler()
    4949
     50
    5051    def connect(self, *args):
    5152        log.warn("connect(%s) not implemented for Qt!", args)
    5253
     
    105106        return []
    106107
    107108
    108     def make_hello(self, challenge_response=None):
    109         capabilities = UIXpraClient.make_hello(self, challenge_response)
     109    def make_hello(self):
     110        capabilities = UIXpraClient.make_hello(self)
    110111        capabilities["named_cursors"] = False
    111112        #add_qt_version_info(capabilities, QtGui)
    112113        return capabilities
  • xpra/client/ui_client_base.py

     
    535535            self._focused = None
    536536
    537537
    538     def make_hello(self, challenge_response=None):
    539         capabilities = XpraClientBase.make_hello(self, challenge_response)
     538    def make_hello(self):
     539        capabilities = XpraClientBase.make_hello(self)
    540540        if self.readonly:
    541541            #don't bother sending keyboard info, as it won't be used
    542542            capabilities["keyboard"] = False
  • xpra/dotxpra.py

     
    1818class ServerSockInUse(Exception):
    1919    pass
    2020
     21def osexpand(s, actual_username=""):
     22    if len(actual_username)>0 and s.startswith("~/"):
     23        #replace "~/" with "~$actual_username/"
     24        s = "~%s/%s" % (actual_username, s[2:])
     25    return os.path.expandvars(os.path.expanduser(s))
     26
     27
    2128class DotXpra(object):
    22     def __init__(self, sockdir=None, confdir=None):
     29    def __init__(self, sockdir=None, confdir=None, actual_username=""):
    2330        from xpra.platform.paths import get_default_socket_dir, get_default_conf_dir
    24         def expand(s):
    25             return os.path.expandvars(os.path.expanduser(s))
    26         self._confdir = expand(confdir or get_default_conf_dir())
    27         self._sockdir = expand(sockdir or get_default_socket_dir())
     31        self._confdir = osexpand(confdir or get_default_conf_dir(), actual_username)
     32        self._sockdir = osexpand(sockdir or get_default_socket_dir(), actual_username)
    2833        if not os.path.exists(self._confdir):
    2934            os.mkdir(self._confdir, o0700)
    3035        if not os.path.exists(self._sockdir):
     
    103108            os.unlink(socket_path)
    104109        return socket_path
    105110
    106     def sockets(self):
     111    def sockets(self, check_uid=0):
    107112        results = []
    108113        base = os.path.join(self._sockdir, self._prefix)
    109114        potential_sockets = glob.glob(base + "*")
    110115        for path in potential_sockets:
    111             if stat.S_ISSOCK(os.stat(path).st_mode):
     116            s = os.stat(path)
     117            if stat.S_ISSOCK(s.st_mode):
     118                if check_uid>0:
     119                    if s.st_uid!=check_uid:
     120                        #socket uid does not match
     121                        continue
    112122                local_display = ":" + path[len(base):]
    113123                state = self.server_state(local_display)
    114124                results.append((state, local_display))
  • xpra/net/protocol.py

     
    2525from xpra.log import Logger, debug_if_env
    2626log = Logger()
    2727debug = debug_if_env(log, "XPRA_NETWORK_DEBUG")
    28 from xpra.os_util import Queue, strtobytes
     28from xpra.os_util import Queue, strtobytes, get_hex_uuid
    2929from xpra.daemon_thread import make_daemon_thread
    3030from xpra.net.bencode import bencode, bdecode
    3131from xpra.simple_stats import std_unit, std_unit_dec
     
    9292FAKE_JITTER = int(os.environ.get("XPRA_FAKE_JITTER", "0"))
    9393
    9494
     95def new_cipher_caps(proto, cipher, encryption_key):
     96    iv = get_hex_uuid()[:16]
     97    key_salt = get_hex_uuid()
     98    iterations = 1000
     99    proto.set_cipher_in(cipher, iv, encryption_key, key_salt, iterations)
     100    return {
     101                 "cipher"           : cipher,
     102                 "cipher.iv"        : iv,
     103                 "cipher.key_salt"  : key_salt,
     104                 "cipher.key_stretch_iterations" : iterations
     105                 }
    95106
     107
    96108def repr_ellipsized(obj, limit=100):
    97109    if isinstance(obj, str) and len(obj) > limit:
    98110        try:
     
    135147    return LevelCompressed(datatype, cdata, cl, algo)
    136148
    137149
    138 #The 'scheduler' instance will generally be gobject
    139 #but we don't want to depend on it, so we inject it here:
    140 scheduler = None
    141 def set_scheduler(s):
    142     global scheduler
    143     scheduler = s
    144 
    145 
    146150class Protocol(object):
    147151    CONNECTION_LOST = "connection-lost"
    148152    GIBBERISH = "gibberish"
     
    150154    FLAGS_RENCODE = 0x1
    151155    FLAGS_CIPHER = 0x2
    152156
    153     def __init__(self, conn, process_packet_cb, get_packet_cb=None):
     157    def __init__(self, scheduler, conn, process_packet_cb, get_packet_cb=None):
    154158        """
    155159            You must call this constructor and source_has_more() from the main thread.
    156160        """
     161        assert scheduler is not None
    157162        assert conn is not None
     163        self.scheduler = scheduler
    158164        self._conn = conn
    159165        if FAKE_JITTER>0:
    160             fj = FakeJitter(process_packet_cb)
     166            fj = FakeJitter(self.scheduler, process_packet_cb)
    161167            self._process_packet_cb =  fj.process_packet_cb
    162168        else:
    163169            self._process_packet_cb = process_packet_cb
     
    179185        self._encoder = self.bencode
    180186        self._compress = zcompress
    181187        self._decompressor = decompressobj()
    182         self._compression_level = 0
     188        self.compression_level = 0
    183189        self.cipher_in = None
    184190        self.cipher_in_name = None
    185191        self.cipher_in_block_size = 0
     
    193199        self._write_format_thread = make_daemon_thread(self._write_format_thread_loop, "format")
    194200        self._source_has_more = threading.Event()
    195201
     202    STATE_FIELDS = ("max_packet_size", "large_packets", "aliases",
     203                    "chunked_compression",
     204                    "cipher_in", "cipher_in_name", "cipher_in_block_size",
     205                    "cipher_out", "cipher_out_name", "cipher_out_block_size",
     206                    "compression_level")
     207    def save_state(self):
     208        state = {}
     209        for x in Protocol.STATE_FIELDS:
     210            state[x] = getattr(self, x)
     211        state["zcompress"] = self._compress==zcompress
     212        state["lz4"] = lz4_compress and self._compress==lz4_compress
     213        #state["connection"] = self._conn
     214        return state
     215
     216    def restore_state(self, state):
     217        assert state is not None
     218        for x in Protocol.STATE_FIELDS:
     219            assert x in state, "field %s is missing" % x
     220            setattr(self, x, state[x])
     221        if state.get("lz4", False):
     222            self.enable_lz4()
     223
    196224    def set_packet_source(self, get_packet_cb):
    197225        self._get_packet_cb = get_packet_cb
    198226
     
    208236        #stretch the password:
    209237        block_size = 32         #fixme: can we derive this?
    210238        secret = PBKDF2(password, key_salt, dkLen=block_size, count=iterations)
    211         #secret = (password+password+password+password+password+password+password+password)[:32]
    212239        debug("get_cipher(%s, %s, %s) secret=%s, block_size=%s", ciphername, iv, password, secret.encode('hex'), block_size)
    213240        return AES.new(secret, AES.MODE_CBC, iv), block_size
    214241
     
    241268        info[prefix+"output.cipher" + suffix] = self.cipher_out_name or ""
    242269        info[prefix+"chunked_compression" + suffix] = self.chunked_compression
    243270        info[prefix+"large_packets" + suffix] = self.large_packets
    244         info[prefix+"compression_level" + suffix] = self._compression_level
     271        info[prefix+"compression_level" + suffix] = self.compression_level
    245272        info[prefix+"max_packet_size" + suffix] = self.max_packet_size
    246273        for k,v in self.aliases.items():
    247274            info[prefix+"alias." + k + suffix] = v
     
    259286
    260287    def start(self):
    261288        def do_start():
     289            #debug("Protocol.do_start() closed=%s", self._closed)
    262290            if not self._closed:
    263291                self._write_thread.start()
    264292                self._read_thread.start()
    265293                self._read_parser_thread.start()
    266294                self._write_format_thread.start()
    267         scheduler.idle_add(do_start)
     295        #debug("Protocol.start() will call %s via %s", do_start, self.scheduler.idle_add)
     296        self.scheduler.idle_add(do_start)
    268297
    269298    def send_now(self, packet):
    270299        if self._closed:
     
    406435        """
    407436        packets = []
    408437        packet = list(packet_in)
    409         level = self._compression_level
     438        level = self.compression_level
    410439        for i in range(1, len(packet)):
    411440            item = packet[i]
    412441            ti = type(item)
     
    455484
    456485    def set_compression_level(self, level):
    457486        #this may be used next time encode() is called
    458         self._compression_level = level
     487        self.compression_level = level
    459488
    460489    def _io_thread_loop(self, name, callback):
    461490        try:
     
    519548
    520549    def _call_connection_lost(self, message="", exc_info=False):
    521550        debug("will call connection lost: %s", message)
    522         scheduler.idle_add(self._connection_lost, message, exc_info)
     551        self.scheduler.idle_add(self._connection_lost, message, exc_info)
    523552
    524553    def _connection_lost(self, message="", exc_info=False):
    525554        log.info("connection lost: %s", message, exc_info=exc_info)
     
    527556        return False
    528557
    529558    def _gibberish(self, msg, data):
    530         scheduler.idle_add(self._process_packet_cb, self, [Protocol.GIBBERISH, data])
     559        self.scheduler.idle_add(self._process_packet_cb, self, [Protocol.GIBBERISH, data])
    531560        # Then hang up:
    532         scheduler.timeout_add(1000, self._connection_lost, msg)
     561        self.scheduler.timeout_add(1000, self._connection_lost, msg)
    533562
    534563
    535564    def _read_parse_thread_loop(self):
     
    559588            buf = self._read_queue.get()
    560589            if not buf:
    561590                debug("read thread: empty marker, exiting")
    562                 scheduler.idle_add(self.close)
     591                self.scheduler.idle_add(self.close)
    563592                return
    564593            if read_buffer:
    565594                read_buffer = read_buffer + buf
     
    609638                                self._call_connection_lost("invalid packet: size requested is %s (maximum allowed is %s - packet header: 0x%s), dropping this connection!" %
    610639                                                              (size_to_check, self.max_packet_size, repr_ellipsized(packet_header)))
    611640                        return False
    612                     scheduler.timeout_add(1000, check_packet_size, payload_size, read_buffer[:32])
     641                    self.scheduler.timeout_add(1000, check_packet_size, payload_size, read_buffer[:32])
    613642
    614643                if bl<payload_size:
    615644                    # incomplete packet, wait for the rest to arrive
     
    628657                    debug("received %s encrypted bytes with %s padding", payload_size, len(padding))
    629658                    data = self.cipher_in.decrypt(raw_string)
    630659                    if padding:
    631                         def debug_str():
     660                        def debug_str(s):
    632661                            try:
    633                                 return list(bytearray(raw_string))
     662                                return list(bytearray(s))
    634663                            except:
    635                                 return list(str(raw_string))
    636                         assert data.endswith(padding), "decryption failed: string does not end with '%s': %s (%s) -> %s (%s)" % \
    637                             (padding, debug_str(raw_string), type(raw_string), debug_str(data), type(data))
     664                                return list(str(s))
     665                        if not data.endswith(padding):
     666                            log("decryption failed: string does not end with '%s': %s (%s) -> %s (%s)",
     667                            padding, debug_str(raw_string), type(raw_string), debug_str(data), type(data))
     668                            self._connection_lost("encryption error (wrong key?)")
     669                            return
    638670                        data = data[:-len(padding)]
    639671                #uncompress if needed:
    640672                if compression_level>0:
     
    712744                    self.close()
    713745                else:
    714746                    debug("flush_then_close: still waiting for queue to flush")
    715                     scheduler.timeout_add(100, wait_for_queue, timeout-1)
     747                    self.scheduler.timeout_add(100, wait_for_queue, timeout-1)
    716748            else:
    717749                debug("flush_then_close: queue is now empty, sending the last packet and closing")
    718750                chunks, proto_flags = self.encode(last_packet)
     
    720752                    self.close()
    721753                self._add_chunks_to_queue(chunks, proto_flags, start_send_cb=None, end_send_cb=close_cb)
    722754                self._write_lock.release()
    723                 scheduler.timeout_add(5*1000, self.close)
     755                self.scheduler.timeout_add(5*1000, self.close)
    724756
    725757        def wait_for_write_lock(timeout=100):
    726758            if not self._write_lock.acquire(False):
     
    729761                    self.close()
    730762                else:
    731763                    debug("flush_then_close: write lock is busy, will retry %s more times", timeout)
    732                     scheduler.timeout_add(10, wait_for_write_lock, timeout-1)
     764                    self.scheduler.timeout_add(10, wait_for_write_lock, timeout-1)
    733765            else:
    734766                debug("flush_then_close: acquired the write lock")
    735767                #we have the write lock - we MUST free it!
     
    745777        if self._closed:
    746778            return
    747779        self._closed = True
    748         scheduler.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
     780        self.scheduler.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
    749781        if self._conn:
    750782            try:
    751783                self._conn.close()
     
    757789                log.error("error closing %s", self._conn, exc_info=True)
    758790            self._conn = None
    759791        self.terminate_io_threads()
    760         scheduler.idle_add(self.clean)
     792        self.scheduler.idle_add(self.clean)
    761793
     794    def steal_connection(self):
     795        #so we can re-use this connection somewhere else
     796        #(frees all protocol threads and resources)
     797        assert not self._closed
     798        conn = self._conn
     799        self._closed = True
     800        self._conn = None
     801        self.terminate_io_threads()
     802        self.scheduler.idle_add(self.clean)
     803        return conn
     804
    762805    def clean(self):
    763806        #clear all references to ensure we can get garbage collected quickly:
    764807        self._get_packet_cb = None
     
    784827
    785828class FakeJitter(object):
    786829
    787     def __init__(self, process_packet_cb):
     830    def __init__(self, scheduler, process_packet_cb):
     831        self.scheduler = scheduler
    788832        self.real_process_packet_cb = process_packet_cb
    789833        self.delay = FAKE_JITTER
    790834        self.ok_delay = 10*1000
     
    797841    def start_buffering(self):
    798842        log.info("FakeJitter.start_buffering() will buffer for %s ms", FAKE_JITTER)
    799843        self.delaying = True
    800         scheduler.timeout_add(FAKE_JITTER, self.flush)
     844        self.scheduler.timeout_add(FAKE_JITTER, self.flush)
    801845
    802846    def flush(self):
    803847        log.info("FakeJitter.flush() processing %s delayed packets", len(self.pending))
     
    809853            self.delaying = False
    810854        finally:
    811855            self.lock.release()
    812         scheduler.timeout_add(self.ok_delay, self.start_buffering)
     856        self.scheduler.timeout_add(self.ok_delay, self.start_buffering)
    813857        log.info("FakeJitter.flush() will start buffering again in %s ms", self.ok_delay)
    814858
    815859    def process_packet_cb(self, proto, packet):
  • xpra/os_util.py

     
    157157                pass
    158158    return  str(v).strip("\n\r")
    159159
     160def load_binary_file(filename):
     161    if not os.path.exists(filename):
     162        return None
     163    f = None
     164    try:
     165        f = open(filename, "rU")
     166        try:
     167            return f.read()
     168        finally:
     169            f.close()
     170    except:
     171        return None
    160172
     173
    161174def main():
    162175    import logging
    163176    logging.basicConfig(format="%(asctime)s %(message)s")
  • xpra/scripts/config.py

     
    373373                    "title"             : str,
    374374                    "host"              : str,
    375375                    "username"          : str,
     376                    "auth"              : str,
    376377                    "remote-xpra"       : str,
    377378                    "session-name"      : str,
    378379                    "client-toolkit"    : str,
     
    384385                    "clipboard-filter-file" : str,
    385386                    "pulseaudio-command": str,
    386387                    "encryption"        : str,
     388                    "encryption_keyfile": str,
    387389                    "mode"              : str,
    388390                    "ssh"               : str,
    389391                    "xvfb"              : str,
     
    443445        return GLOBAL_DEFAULTS
    444446    from xpra.platform.features import DEFAULT_SSH_CMD
    445447    try:
    446         import getpass
    447         username = getpass.getuser()
     448        from xpra.platform.info import get_username
     449        username = get_username()
    448450    except:
    449451        username = ""
    450452    GLOBAL_DEFAULTS = {
     
    452454                    "title"             : "@title@ on @client-machine@",
    453455                    "host"              : "",
    454456                    "username"          : username,
     457                    "auth"              : "",
    455458                    "remote-xpra"       : ".xpra/run-xpra",
    456459                    "session-name"      : "",
    457460                    "client-toolkit"    : "",
     
    466469                                            +" --load=module-null-sink --load=module-native-protocol-unix "
    467470                                            +" --log-level=2 --log-target=stderr",
    468471                    "encryption"        : "",
     472                    "encryption_keyfile": "",
    469473                    "mode"              : "tcp",
    470474                    "ssh"               : DEFAULT_SSH_CMD,
    471475                    "xvfb"              : "Xvfb +extension Composite -screen 0 3840x2560x24+32 -nolisten tcp -noreset -auth $XAUTHORITY",
  • xpra/scripts/main.py

     
    1616import shlex
    1717
    1818from xpra import __version__ as XPRA_VERSION
    19 from xpra.dotxpra import DotXpra
     19from xpra.dotxpra import DotXpra, osexpand
    2020from xpra.platform.features import LOCAL_SERVERS_SUPPORTED, SHADOW_SUPPORTED, CAN_DAEMONIZE
    2121from xpra.platform.options import add_client_options
    2222from xpra.platform.paths import get_default_socket_dir
     
    4040
    4141def main(script_file, cmdline):
    4242    platform_init()
    43     if os.name=="posix" and os.getuid()==0:
    44         warn("\nWarning: running as root")
    4543    try:
    4644        import glib
    4745        glib.set_prgname("Xpra")
     
    301299    group.add_option("--ssh", action="store",
    302300                      dest="ssh", default=defaults.ssh, metavar="CMD",
    303301                      help="How to run ssh (default: '%default')")
     302    group.add_option("--username", action="store",
     303                      dest="username", default=defaults.username,
     304                      help="The username supplied by the client for authentication (default: '%default')")
     305    group.add_option("--auth", action="store",
     306                      dest="auth", default=defaults.auth,
     307                      help="The authentication module (default: '%default')")
    304308    group.add_option("--mmap-group", action="store_true",
    305309                      dest="mmap_group", default=defaults.mmap_group,
    306310                      help="When creating the mmap file with the client, set the group permission on the mmap file to the same value as the owner of the server socket file we connect to (default: '%default')")
     
    318322        group.add_option("--encryption", action="store",
    319323                          dest="encryption", default=defaults.encryption,
    320324                          metavar="ALGO",
    321                           help="Specifies the encryption cipher to use, only %s is currently supported. (default: None)" % (", ".join(ENCRYPTION_CIPHERS)))
     325                          help="Specifies the encryption cipher to use, supported algorithms are: %s (default: None)" % (", ".join(ENCRYPTION_CIPHERS)))
     326        group.add_option("--encryption-keyfile", action="store",
     327                          dest="encryption_keyfile", default=defaults.encryption_keyfile,
     328                          metavar="FILE",
     329                          help="Specifies the file containing the encryption key. (default: '%default')")
    322330    else:
    323331        hidden_options["encryption"] = ''
     332        hidden_options["encryption_keyfile"] = ''
    324333
    325334    options, args = parser.parse_args(cmdline[1:])
    326335    if not args:
     
    367376        assert len(ENCRYPTION_CIPHERS)>0, "cannot use encryption: no ciphers available"
    368377        if options.encryption not in ENCRYPTION_CIPHERS:
    369378            parser.error("encryption %s is not supported, try: %s" % (options.encryption, ", ".join(ENCRYPTION_CIPHERS)))
    370         if not options.password_file:
    371             parser.error("encryption %s cannot be used without a password (see --password-file option)" % options.encryption)
     379        if not options.password_file and not options.encryption_keyfile:
     380            parser.error("encryption %s cannot be used without a keyfile (see --encryption-keyfile option)" % options.encryption)
    372381    #ensure opengl is either True, False or None
    373382    options.opengl = parse_bool("opengl", options.opengl)
    374383
     
    398407
    399408    #configure default logging handler:
    400409    mode = args.pop(0)
    401     if mode in ("start", "upgrade", "attach", "shadow"):
     410    if os.name=="posix" and os.getuid()==0 and mode!="proxy":
     411        warn("\nWarning: running as root")
     412
     413    if mode in ("start", "upgrade", "attach", "shadow", "proxy"):
    402414        if show_codec_help("attach" not in cmdline[1:],
    403415                           options.speaker_codec, options.microphone_codec):
    404416            return 0
     
    499511        desc["type"] = "unix-domain"
    500512        desc["local"] = True
    501513        desc["display"] = display_name
    502         desc["socket_dir"] = opts.socket_dir or get_default_socket_dir()
     514        desc["socket_dir"] = osexpand(opts.socket_dir or get_default_socket_dir(), opts.username)
    503515        return desc
    504516    elif display_name.startswith("tcp:") or display_name.startswith("tcp/"):
    505517        separator = display_name[3] # ":" or "/"
  • xpra/server/auth/__init__.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
  • xpra/server/auth/file_auth.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import os.path
     7import sys
     8import pwd
     9import hmac
     10
     11from xpra.os_util import get_hex_uuid
     12from xpra.dotxpra import DotXpra
     13from xpra.log import Logger, debug_if_env
     14log = Logger()
     15debug = debug_if_env(log, "XPRA_AUTH_DEBUG")
     16
     17
     18password_file = None
     19socket_dir = None
     20def init(opts):
     21    global password_file, socket_dir
     22    password_file = opts.password_file
     23    socket_dir = opts.socket_dir
     24
     25
     26auth_data = None
     27auth_data_time = None
     28def load_auth_file():
     29    global auth_data, auth_data_time, password_file, socket_dir
     30    if not os.path.exists(password_file):
     31        log.error("password file is missing: %s", e)
     32        auth_data = None
     33        return auth_data
     34    ptime = 0
     35    try:
     36        ptime = os.stat(password_file).st_mtime
     37    except Exception, e:
     38        log.error("error accessing password file time: %s", e)
     39    if auth_data is None or ptime!=auth_data_time:
     40        auth_data = {}
     41        auth_data_time = ptime
     42        f = None
     43        try:
     44            try:
     45                f = open(password_file, mode='rb')
     46                data = f.read()
     47            finally:
     48                if f:
     49                    f.close()
     50        except Exception, e:
     51            log.error("error loading %s: %s", password_file, e)
     52            data = ""
     53        i = 0
     54        for line in data.splitlines():
     55            i += 1
     56            line = line.strip()
     57            if len(line)==0:
     58                continue
     59            if line.find(":")<0:
     60                #assume old style file with just the password
     61                #get all the displays for the current user:
     62                sockdir = DotXpra(socket_dir)
     63                results = sockdir.sockets()
     64                displays = [display for state, display in results if state==DotXpra.LIVE]
     65                auth_data[""] = line, os.getuid(), os.getgid(), displays, {}, {}
     66                continue
     67            ldata = line.split(":")
     68            if len(ldata)<4:
     69                log.warn("skipped line %s of %s: not enough fields", i, password_file)
     70                continue
     71            #parse fields:
     72            username = ldata[0]
     73            password = ldata[1]
     74            def getsysid(s, default_value):
     75                if not s:
     76                    return default_value
     77                try:
     78                    return int(s)
     79                except:
     80                    return default_value
     81                uid = getsysid(ldata[2], os.getuid())
     82                gid = getsysid(ldata[3], os.getgid())
     83            displays = ldata[4].split(",")
     84            env_options = {}
     85            session_options = {}
     86            if len(ldata)>=6:
     87                env_options = parseOptions(ldata[5])
     88            if len(ldata)>=7:
     89                session_options = parseOptions(ldata[6])
     90            auth_data[username] = password, uid, displays, env_options, session_options
     91    return auth_data
     92
     93
     94class Authenticator(object):
     95    def __init__(self, username):
     96        self.username = username
     97        self.salt = None
     98        self.sessions = None
     99
     100    def get_challenge(self):
     101        if self.salt is not None:
     102            log.error("challenge already sent!")
     103            return None
     104        self.salt = get_hex_uuid()
     105        #this authenticator can use the safer "hmac" digest:
     106        return self.salt, "hmac"
     107
     108    def get_entry(self):
     109        ad = load_auth_file()
     110        username = self.username
     111        if username not in ad:
     112            #maybe this is an old style file with just the password?
     113            if len(ad)==1 and ad.keys()[0]=="":
     114                #then ignore the username
     115                username = ""
     116            else:
     117                return None
     118        return ad[username]
     119
     120    def get_password(self):
     121        entry = self.get_entry()
     122        if entry is None:
     123            return None
     124        return entry[0]
     125
     126    def authenticate(self, challenge_response):
     127        global password_file
     128        if not self.salt:
     129            log.error("illegal challenge response received - salt cleared or unset")
     130            return None
     131        #ensure this salt does not get re-used:
     132        salt = self.salt
     133        self.salt = None
     134        entry = self.get_entry()
     135        if entry is None:
     136            log.error("usename %s does not exist in %s", username, password_file)
     137            return None
     138        fpassword, uid, gid, displays, env_options, session_options = entry
     139        hash = hmac.HMAC(fpassword, salt).hexdigest()
     140        log("authenticate(%s) password=%s, salt=%s, hash=%s", challenge_response, fpassword, salt, hash)
     141        if hash!=challenge_response:
     142            log.error("hmac password challenge for %s does not match", self.username)
     143            return False
     144        self.sessions = uid, gid, displays, env_options, session_options
     145        return True
     146
     147    def get_sessions(self):
     148        return self.sessions
     149
     150    def __str__(self):
     151        return "Password File Authenticator"
  • xpra/server/auth/pam.py

    Property changes on: xpra/server/auth/file_auth.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
     1#!/usr/bin/env python
     2# (c) 2007 Chris AtLee <chris@atlee.ca>
     3# Licensed under the MIT license:
     4# http://www.opensource.org/licenses/mit-license.php
     5"""
     6PAM module for python
     7
     8Provides an authenticate function that will allow the caller to authenticate
     9a user against the Pluggable Authentication Modules (PAM) on the system.
     10
     11Implemented using ctypes, so no compilation is necessary.
     12"""
     13__all__ = ['authenticate']
     14
     15from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof, cdll        #@UnresolvedImport
     16from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int                            #@UnresolvedImport
     17from ctypes.util import find_library                                                    #@UnresolvedImport
     18
     19paml = find_library("pam")
     20if paml:
     21    LIBPAM = CDLL(paml)
     22else:
     23    import sys
     24    if sys.platform.startswith("darwin"):
     25        LIBPAM = cdll.LoadLibrary("/usr/lib/libpam.dylib")
     26    else:
     27        #solaris doesn't find much on its own...
     28        LIBPAM = cdll.LoadLibrary("/lib/libpam.so.1")
     29LIBC = CDLL(find_library("c"))
     30
     31CALLOC = LIBC.calloc
     32CALLOC.restype = c_void_p
     33CALLOC.argtypes = [c_uint, c_uint]
     34
     35STRDUP = LIBC.strdup
     36STRDUP.argstypes = [c_char_p]
     37STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
     38
     39# Various constants
     40PAM_PROMPT_ECHO_OFF = 1
     41PAM_PROMPT_ECHO_ON = 2
     42PAM_ERROR_MSG = 3
     43PAM_TEXT_INFO = 4
     44
     45class PamHandle(Structure):
     46    """wrapper class for pam_handle_t"""
     47    _fields_ = [
     48            ("handle", c_void_p)
     49            ]
     50
     51    def __init__(self):
     52        Structure.__init__(self)
     53        self.handle = 0
     54
     55class PamMessage(Structure):
     56    """wrapper class for pam_message structure"""
     57    _fields_ = [
     58            ("msg_style", c_int),
     59            ("msg", c_char_p),
     60            ]
     61
     62    def __repr__(self):
     63        return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
     64
     65class PamResponse(Structure):
     66    """wrapper class for pam_response structure"""
     67    _fields_ = [
     68            ("resp", c_char_p),
     69            ("resp_retcode", c_int),
     70            ]
     71
     72    def __repr__(self):
     73        return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
     74
     75CONV_FUNC = CFUNCTYPE(c_int,
     76        c_int, POINTER(POINTER(PamMessage)),
     77               POINTER(POINTER(PamResponse)), c_void_p)
     78
     79class PamConv(Structure):
     80    """wrapper class for pam_conv structure"""
     81    _fields_ = [
     82            ("conv", CONV_FUNC),
     83            ("appdata_ptr", c_void_p)
     84            ]
     85
     86PAM_START = LIBPAM.pam_start
     87PAM_START.restype = c_int
     88PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
     89        POINTER(PamHandle)]
     90
     91PAM_AUTHENTICATE = LIBPAM.pam_authenticate
     92PAM_AUTHENTICATE.restype = c_int
     93PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
     94
     95def authenticate(username, password, service='login'):
     96    """Returns True if the given username and password authenticate for the
     97    given service.  Returns False otherwise
     98
     99    ``username``: the username to authenticate
     100
     101    ``password``: the password in plain text
     102
     103    ``service``: the PAM service to authenticate against.
     104                 Defaults to 'login'"""
     105    @CONV_FUNC
     106    def my_conv(n_messages, messages, p_response, app_data):
     107        """Simple conversation function that responds to any
     108        prompt where the echo is off with the supplied password"""
     109        # Create an array of n_messages response objects
     110        addr = CALLOC(n_messages, sizeof(PamResponse))
     111        p_response[0] = cast(addr, POINTER(PamResponse))
     112        for i in range(n_messages):
     113            if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
     114                pw_copy = STRDUP(str(password))
     115                p_response.contents[i].resp = cast(pw_copy, c_char_p)
     116                p_response.contents[i].resp_retcode = 0
     117        return 0
     118
     119    handle = PamHandle()
     120    conv = PamConv(my_conv, 0)
     121    retval = PAM_START(service, username, pointer(conv), pointer(handle))
     122
     123    if retval != 0:
     124        # TODO: This is not an authentication error, something
     125        # has gone wrong starting up PAM
     126        return False
     127
     128    retval = PAM_AUTHENTICATE(handle, 0)
     129    return retval == 0
     130
     131if __name__ == "__main__":
     132    import getpass
     133    print(authenticate(getpass.getuser(), getpass.getpass()))
  • xpra/server/auth/pam_auth.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import sys
     7import pwd
     8
     9from xpra.dotxpra import DotXpra
     10from xpra.server.auth.sys_auth_base import SysAuthenticator, log, debug, init
     11
     12
     13check = None
     14#choice of two pam modules we can use
     15try:
     16    import PAM                        #@UnresolvedImport
     17    PAM_SERVICE = 'login'
     18    PAM_PASSWORD = "password"
     19
     20    class PAM_conv:
     21        def __init__(self, password):
     22            self.password = password
     23
     24        def pam_conv_password(auth, query_list, *args):
     25            try:
     26                resp = []
     27                for i in range(len(query_list)):
     28                    query, pam_type = query_list[i]
     29                    if pam_type == PAM.PAM_PROMPT_ECHO_ON or pam_type == PAM.PAM_PROMPT_ECHO_OFF:
     30                        resp.append((self.password, 0))
     31                    elif pam_type == PAM.PAM_PROMPT_ERROR_MSG or pam_type == PAM.PAM_PROMPT_TEXT_INFO:
     32                        log("pam_conf_password: ERROR/INFO: '%s'", query)
     33                        resp.append(('', 0))
     34                    else:
     35                        log.error("pam_conf_password unknown type: '%s'", pam_type)
     36            except Exception, e:
     37                log.error("pam_conv_password error: %s", e)
     38            return    resp
     39
     40    def check(username, password):
     41        debug("PAM check(%s, [..])", username)
     42        auth = PAM.pam()
     43        auth.start(PAM_SERVICE)
     44        auth.set_item(PAM.PAM_USER, username)
     45        conv = PAM_conv(password)
     46        auth.set_item(PAM.PAM_CONV, conv.pam_conv_password)
     47        try:
     48            auth.authenticate()
     49            return    True
     50            #auth.acct_mgmt()
     51        except PAM.error, resp:
     52            log.error("PAM.authenticate() error: %s", resp)
     53            return    False
     54        except Exception, e:
     55            log.error("PAM.authenticate() internal error: %s", e)
     56            return    False
     57except Exception, e:
     58    debug("PAM module not available: %s", e)
     59
     60try:
     61    from xpra.server.auth import pam
     62    assert pam
     63    def check(username, password):
     64        debug("pam check(%s, [..])", username)
     65        return pam.authenticate(username, password)
     66except:
     67    debug("pam module not available: %s", e)
     68
     69
     70if check is None:
     71    raise ImportError("cannot use pam_auth without a pam python module")
     72
     73
     74class Authenticator(SysAuthenticator):
     75
     76    def __init__(self, username):
     77        SysAuthenticator.__init__(self, username)
     78        self.pw = pwd.getpwnam(username)
     79        assert self.pw is not None, "username %s not found" % username
     80
     81    def get_uid(self):
     82        #get the uid from the password database:
     83        return self.pw.pw_uid
     84
     85    def get_gid(self):
     86        return self.pw.pw_gid
     87
     88    def check(self, password):
     89        return check(self.username, password)
     90
     91    def __str__(self):
     92        return "PAM Authenticator"
  • xpra/server/auth/sys_auth_base.py

    Property changes on: xpra/server/auth/pam_auth.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
     1# This file is part of Xpra.
     2# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import sys
     7import pwd
     8
     9from xpra.dotxpra import DotXpra
     10from xpra.util import xor
     11from xpra.os_util import get_hex_uuid
     12from xpra.log import Logger, debug_if_env
     13log = Logger()
     14debug = debug_if_env(log, "XPRA_AUTH_DEBUG")
     15
     16
     17socket_dir = None
     18def init(opts):
     19    global socket_dir
     20    socket_dir = opts.socket_dir
     21
     22
     23class SysAuthenticator(object):
     24    def __init__(self, username):
     25        self.username = username
     26        self.salt = None
     27
     28    def get_challenge(self):
     29        if self.salt is not None:
     30            log.error("challenge already sent!")
     31            return None
     32        self.salt = get_hex_uuid()+get_hex_uuid()
     33        #we need the raw password, so tell the client to use "xor":
     34        return self.salt, "xor"
     35
     36    def get_uid(self):
     37        raise NotImplementedError()
     38
     39    def get_password(self):
     40        return None
     41
     42    def check(self, username, password):
     43        raise NotImplementedError()
     44
     45    def authenticate(self, challenge_response):
     46        global socket_dir
     47        if self.salt is None:
     48            log.error("got a challenge response with no salt!")
     49            return False
     50        password = xor(challenge_response, self.salt)
     51        #warning: enabling logging here would log the actual system password!
     52        #log("authenticate(%s) password=%s", challenge_response, password)
     53        #verify login:
     54        try :
     55            if not self.check(password):
     56                return False
     57        except Exception, e:
     58            log.error("authentication error: %s", e)
     59            return False
     60        return True
     61
     62    def get_sessions(self):
     63        sockdir = DotXpra(socket_dir, actual_username=self.username)
     64        uid = self.get_uid()
     65        gid = self.get_gid()
     66        results = sockdir.sockets(check_uid=uid)
     67        displays = [display for state, display in results if state==DotXpra.LIVE]
     68        return uid, gid, displays, {}, {}
  • xpra/server/auth/win32_auth.py

    Property changes on: xpra/server/auth/sys_auth_base.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
     1# This file is part of Xpra.
     2# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import os
     7
     8from xpra.server.auth.sys_auth_base import SysAuthenticator, init
     9import win32security            #@UnresolvedImport
     10assert win32security            #avoid pydev warning
     11
     12
     13class Authenticator(SysAuthenticator):
     14
     15    def get_uid(self):
     16        #uid is left unchanged:
     17        return os.getuid()
     18
     19    def get_gid(self):
     20        #gid is left unchanged:
     21        return os.getgid()
     22
     23    def check(self, password):
     24        win32security.LogonUser(username, '', password, win32security.LOGON32_LOGON_NETWORK, win32security.LOGON32_PROVIDER_DEFAULT)
     25
     26    def __str__(self):
     27        return "Win32 Authenticator"
  • xpra/server/auth_proxy.py

    Property changes on: xpra/server/auth/win32_auth.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    1 # coding=utf8
    21# This file is part of Xpra.
    32# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
    43# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
    54# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    65# later version. See the file COPYING for details.
    76
     7import os
     8import signal
    89import gobject
    910gobject.threads_init()
     11from threading import Timer
    1012
    1113from xpra.log import Logger
    1214log = Logger()
     
    1416from xpra.server.server_core import ServerCore
    1517from xpra.scripts.config import make_defaults_struct
    1618from xpra.scripts.main import parse_display_name, connect_to
    17 from xpra.net.protocol import Protocol
    18 from xpra.net.protocol import set_scheduler
    19 from xpra.os_util import Queue
    20 set_scheduler(gobject)
     19from xpra.scripts.server import deadly_signal
     20from xpra.net.protocol import Protocol, new_cipher_caps
     21from xpra.os_util import Queue, SIGNAMES
    2122
    2223
     24PROXY_QUEUE_SIZE = int(os.environ.get("XPRA_PROXY_QUEUE_SIZE", "10"))
     25USE_THREADING = os.environ.get("XPRA_USE_THREADING", "0")=="1"
     26if USE_THREADING:
     27    #use threads
     28    from threading import Thread as Process
     29else:
     30    #use processes:
     31    from multiprocessing import Process
     32
     33
    2334class ProxyServer(ServerCore):
    2435
    2536    def __init__(self):
     37        log("ProxyServer.__init__()")
     38        ServerCore.__init__(self)
    2639        self.main_loop = None
    27         self.client_to_server = Queue(10)
    28         self.server_to_client = Queue(10)
    29         self.client_protocol = None
    30         self.server_protocol = None
    31         log("AuthProxy.__init__()")
    32         ServerCore.__init__(self)
     40        self.processes = []
    3341        self.idle_add = gobject.idle_add
    3442        self.timeout_add = gobject.timeout_add
    3543        self.source_remove = gobject.source_remove
    3644
     45    def init(self, opts):
     46        log("ProxyServer.init(%s)", opts)
     47        if not opts.auth:
     48            raise Exception("The proxy server requires an authentication mode")
     49        ServerCore.init(self, opts)
     50
     51    def init_aliases(self):
     52        pass
     53
    3754    def do_run(self):
    3855        self.main_loop = gobject.MainLoop()
    3956        self.main_loop.run()
    4057
    4158    def do_quit(self):
    42         for x in (self.client_protocol, self.server_protocol):
    43             if x:
    44                 x.close()
    45         self.client_protocol = None
    46         self.server_protocol = None
     59        processes = self.processes
     60        self.processes = []
     61        for process in processes:
     62            process.stop()
    4763        self.main_loop.quit()
    4864
    4965    def add_listen_socket(self, socktype, sock):
     
    5268        self.socket_types[sock] = socktype
    5369
    5470    def verify_connection_accepted(self, protocol):
    55         pass
     71        #if we start a proxy, the protocol will be closed
     72        #(a new one is created in the proxy process)
     73        if not protocol._closed:
     74            self.send_disconnect(protocol, "connection timeout")
    5675
    5776    def hello_oked(self, proto, packet, c, auth_caps):
     77        self.accept_client(proto, c)
     78        log.info("aliases=%s", proto.aliases)
    5879        if c.boolget("info_request"):
    5980            log.info("sending response to info request")
    6081            self.send_info(proto)
    6182            return
    62         self.start_proxy(proto, packet)
     83        self.start_proxy(proto, c, auth_caps)
    6384
    6485    def send_info(self, proto):
    6586        caps = self.make_hello()
    6687        caps["server_type"] = "proxy"
    6788        proto.send_now(["hello", caps])
    6889
    69     def start_proxy(self, proto, packet):
    70         log.info("start_proxy(%s, %s)", proto, packet)
    71         #from now on, we forward client packets:
    72         self.client_protocol = proto
    73         self.client_protocol.set_packet_source(self.get_client_packet)
    74         proto._process_packet_cb = self.process_client_packet
    75         #figure out where the real server lives:
    76         #FIXME: hardcoded
    77         #FIXME: forward hello to server: need to remove auth and encoding params
    78         target = "tcp:192.168.1.100:10000"
     90    def start_proxy(self, client_proto, c, auth_caps):
     91        assert client_proto.authenticator is not None
     92        #find the target server session:
     93        def disconnect(msg):
     94            self.send_disconnect(client_proto, msg)
     95        sessions = client_proto.authenticator.get_sessions()
     96        if sessions is None:
     97            disconnect("no sessions found")
     98            return
     99        log.info("start_proxy(%s, {..}, %s) found sessions: %s", client_proto, auth_caps, sessions)
     100        uid, gid, displays, env_options, session_options = sessions       
     101        if len(displays)==0:
     102            disconnect("no displays found")
     103            return
     104        display = c.strget("display")
     105        proxy_virtual_display = os.environ["DISPLAY"]
     106        #ensure we don't loop back to the proxy:
     107        if proxy_virtual_display in displays:
     108            displays.remove(proxy_virtual_display)
     109        if display==proxy_virtual_display:
     110            disconnect("invalid display")
     111            return
     112        if display:
     113            if display not in displays:
     114                disconnect("display not found")
     115                return
     116        else:
     117            if len(displays)!=1:
     118                disconnect("please specify a display (more than one available)")
     119                return
     120            display = displays[0]
     121
     122        log.info("start_proxy(%s, {..}, %s) using server display at: %s", client_proto, auth_caps, display)
     123        client_conn = client_proto.steal_connection()
     124        client_state = client_proto.save_state()
     125        cipher = auth_caps.get("cipher")
     126        encryption_key = None
     127        if cipher:
     128            encryption_key = self.get_encryption_key(client_proto.authenticator)
     129        log.info("start_proxy(%s, {..}) client connection=%s", client_proto, client_conn)
     130        log.info("start_proxy(%s, {..}) client state=%s", client_proto, client_state)
    79131        def parse_error(*args):
    80132            log.warn("parse error on %s: %s", target, args)
     133            raise Exception("parse error on %s: %s" % (target, args))
    81134        opts = make_defaults_struct()
    82         disp_desc = parse_display_name(parse_error, opts, target)
    83         log.info("display description(%s) = %s", target, disp_desc)
    84         conn = connect_to(disp_desc)
    85         log.info("server connection=%s", conn)
    86         self.server_protocol = Protocol(conn, self.process_server_packet, self.get_server_packet)
    87         log.info("server protocol=%s", self.server_protocol)
     135        opts.username = c.strget("username")
     136        disp_desc = parse_display_name(parse_error, opts, display)
     137        log.info("display description(%s) = %s", display, disp_desc)
     138        server_conn = connect_to(disp_desc)
     139        log.info("server connection=%s", server_conn)
     140        process = ProxyProcess(uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, c, self.proxy_ended)
     141        log.info("starting %s from pid=%s", process, os.getpid())
     142        process.start()
     143        #FIXME: remove processes that have terminated
     144        self.processes.append(process)
     145        #now we can close our handle on the connection:
     146        #client_conn.close()
     147        #server_conn.close()
     148
     149    def proxy_ended(self, proxy_process):
     150        log.info("proxy_ended(%s)", proxy_process)
     151        if proxy_process in self.processes:
     152            self.processes.remove(proxy_process)
     153
     154
     155class ProxyProcess(Process):
     156
     157    def __init__(self, uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, caps, exit_cb):
     158        Process.__init__(self, name=str(client_conn))
     159        assert uid!=0 and gid!=0
     160        self.uid = uid
     161        self.gid = gid
     162        self.client_conn = client_conn
     163        self.client_state = client_state
     164        self.cipher = cipher
     165        self.encryption_key = encryption_key
     166        self.server_conn = server_conn
     167        self.caps = caps
     168        self.exit_cb = exit_cb
     169        log.info("ProxyProcess%s pid=%s", (uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, "{..}"), os.getpid())
     170        self.client_protocol = None
     171        self.server_protocol = None
     172        self.main_queue = Queue()
     173
     174    def signal_quit(self, signum, frame):
     175        log.info("")
     176        log.info("proxy process got signal %s, exiting", SIGNAMES.get(signum, signum))
     177        signal.signal(signal.SIGINT, deadly_signal)
     178        signal.signal(signal.SIGTERM, deadly_signal)
     179        self.stop(SIGNAMES.get(signum, signum))
     180
     181    def idle_add(self, fn, *args, **kwargs):
     182        self.main_queue.put((fn, args, kwargs))
     183
     184    def timeout_add(self, timeout, fn, *args, **kwargs):
     185        #self.main_queue.put((timeout, fn, , args, kwargs))
     186        timer = None
     187        def idle_exec():
     188            v = fn(*args, **kwargs)
     189            if not bool(v):
     190               timer.cancel()
     191            return False
     192        def timer_exec():
     193            #just run via idle_add:
     194            self.idle_add(idle_exec)
     195        timer = Timer(timeout*1000.0, timer_exec)
     196        timer.start()
     197
     198    def run(self):
     199        log.info("ProxyProcess.run() pid=%s", os.getpid())
     200        #change uid and gid:
     201        if os.getgid()!=self.gid:
     202            os.setgid(self.gid)
     203        if os.getuid()!=self.uid:
     204            os.setuid(self.uid)
     205        if not USE_THREADING:
     206            #signal.signal(signal.SIGTERM, self.signal_quit)
     207            #signal.signal(signal.SIGINT, self.signal_quit)
     208            pass
     209
     210        #setup protocol wrappers:
     211        self.client_to_server = Queue(PROXY_QUEUE_SIZE)
     212        self.server_to_client = Queue(PROXY_QUEUE_SIZE)
     213        self.client_protocol = Protocol(self, self.client_conn, self.process_client_packet, self.get_client_packet)
     214        self.client_protocol.restore_state(self.client_state)
     215        self.server_protocol = Protocol(self, self.server_conn, self.process_server_packet, self.get_server_packet)
     216
     217        #server connection tweaks:
    88218        self.server_protocol.large_packets.append("keymap-changed")
    89219        self.server_protocol.large_packets.append("server-settings")
    90220        self.server_protocol.set_compression_level(0)
     221
    91222        self.server_protocol.start()
     223        self.client_protocol.start()
     224
    92225        #forward the hello packet:
    93         self.client_to_server.put(packet)
     226        #FIXME: more filtering needed
     227        #hello_packet["encodings"]=("rgb", )
     228        pcaps = {}
     229        for k,v in self.caps.items():
     230            if not k.startswith("cipher") and not k.startswith("mmap") and not k.startswith("aliases"):
     231                pcaps[k] = v
     232        pcaps["proxy"] = True
     233        hello_packet = ("hello", pcaps)
     234        self.client_to_server.put(hello_packet)
    94235        self.server_protocol.source_has_more()
    95236
     237        try:
     238            try:
     239                self.run_queue()
     240            except KeyboardInterrupt, e:
     241                self.stop(str(e))
     242        finally:
     243            self.exit_cb(self)
     244
     245    def run_queue(self):
     246        #process "idle_add"/"timeout_add" events in the main loop:
     247        while True:
     248            v = self.main_queue.get()
     249            if v is None:
     250                break
     251            fn, args, kwargs = v
     252            try:
     253                v = fn(*args, **kwargs)
     254                if bool(v):
     255                    #re-run it
     256                    self.main_queue.put(v)
     257            except:
     258                log.error("error during main loop callback %s", fn, exc_info=True)
     259
     260    def stop(self, reason="proxy terminating"):
     261        log.info("stop()")
     262        #empty the main queue:
     263        q = Queue()
     264        q.put(None)
     265        self.main_queue = q
     266        for proto in (self.client_protocol, self.server_protocol):
     267            if proto:
     268                proto.flush_then_close(["disconnect", reason])
     269
    96270    def get_server_packet(self):
    97271        #server wants a packet
    98         return self.client_to_server.get(),
     272        p = self.client_to_server.get()
     273        log.info("forwarding client packet %s", p[0])
     274        return p,
    99275
    100276    def get_client_packet(self):
    101277        #server wants a packet
    102         return self.server_to_client.get(),
     278        p = self.server_to_client.get()
     279        log.info("forwarding server packet %s", p[0])
     280        return p,
    103281
    104282    def process_server_packet(self, proto, packet):
    105         log.info("process_server_packet: %s", packet[0])
     283        packet_type = packet[0]
     284        log.info("process_server_packet: %s", packet_type)
     285        #log.info("process_server_packet: %s", packet)
     286        if packet_type==Protocol.CONNECTION_LOST:
     287            self.stop("server connection lost")
     288            return
     289        elif packet_type=="hello":
     290            pcaps = packet[1]
     291            if "aliases" in pcaps:
     292                del pcaps["aliases"]
     293            auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key)
     294            pcaps.update(auth_caps)
    106295        #forward  packet received from server to the client
    107296        self.server_to_client.put(packet)
    108297        self.client_protocol.source_has_more()
    109298
    110299    def process_client_packet(self, proto, packet):
    111300        log.info("process_client_packet: %s", packet[0])
     301        log.info("process_client_packet: %s", packet)
    112302        #forward  packet received from client to the server
     303        packet_type = packet[0]
     304        if packet_type==Protocol.CONNECTION_LOST:
     305            self.stop("client connection lost")
     306            return
     307        elif packet_type=="set_deflate":
     308            #echo it back to the client:
     309            self.server_to_client.put(packet)
     310            self.client_protocol.source_has_more()
     311            return
    113312        self.client_to_server.put(packet)
    114313        self.server_protocol.source_has_more()
  • xpra/server/gtk_server_base.py

     
    1717                           gtk_main_quit_on_fatal_exceptions_enable)
    1818from xpra.server.server_base import ServerBase
    1919from xpra.gtk_common.gtk_util import add_gtk_version_info
    20 from xpra.net.protocol import set_scheduler
    21 set_scheduler(gobject)
    2220
    2321
    2422class GTKServerBase(ServerBase):
  • xpra/server/server_base.py

     
    368368
    369369
    370370    def _process_disconnect(self, proto, packet):
    371         self.disconnect(proto, "on client request")
     371        self.disconnect_protocol(proto, "on client request")
    372372
    373373    def _process_connection_lost(self, proto, packet):
    374374        log.info("Connection lost")
     
    618618        info["server.python.full_version"] = sys.version
    619619        info["server.python.version"] = sys.version_info[:3]
    620620        info["session.name"] = self.session_name or ""
    621         info["features.password_file"] = self.password_file or ""
     621        info["features.authenticator"] = str((self.auth_class or str)(""))
    622622        info["features.randr"] = self.randr
    623623        info["features.cursors"] = self.cursors
    624624        info["features.bell"] = self.bell
  • xpra/server/server_core.py

     
    99import types
    1010import os.path
    1111import sys
    12 import hmac
    1312import time
    1413import socket
    1514import signal
     
    2120from xpra.scripts.config import ENCRYPTION_CIPHERS, python_platform
    2221from xpra.scripts.server import deadly_signal
    2322from xpra.net.bytestreams import SocketConnection
    24 from xpra.os_util import set_application_name, get_hex_uuid, SIGNAMES
     23from xpra.os_util import set_application_name, load_binary_file, SIGNAMES
    2524from xpra.version_util import version_compat_check, add_version_info
    26 from xpra.net.protocol import Protocol, has_rencode, has_lz4, rencode_version, use_rencode
     25from xpra.net.protocol import Protocol, has_rencode, has_lz4, rencode_version, use_rencode, new_cipher_caps
    2726from xpra.util import typedict
    2827
    2928
     
    3938    def __init__(self):
    4039        log("ServerCore.__init__()")
    4140        self.start_time = time.time()
     41        self.auth_class = None
    4242
    4343        self._upgrading = False
    4444        #networking bits:
     
    5050        self.session_name = "Xpra"
    5151
    5252        #Features:
     53        self.digest_modes = ("hmac", )
     54        self.encryption_keyfile = None
     55        self.password_file = None
    5356        self.compression_level = 1
    54         self.password_file = ""
    5557
    5658        self.init_packet_handlers()
    5759        self.init_aliases()
     
    7072        self.session_name = opts.session_name
    7173        set_application_name(self.session_name)
    7274
     75        self.encryption_keyfile = opts.encryption_keyfile
     76        self.password_file = opts.password_file
    7377        self.compression_level = opts.compression_level
    74         self.password_file = opts.password_file
    7578
     79        self.init_auth(opts)
     80
     81    def init_auth(self, opts):
     82        auth = opts.auth
     83        if not auth and opts.password_file:
     84            log.warn("no authentication module specified with 'password_file', using 'file' based authentication")
     85            auth = "file"
     86        if auth=="":
     87            return
     88        elif auth=="file":
     89            from xpra.server.auth import file_auth
     90            auth_module = file_auth
     91        elif auth=="pam":
     92            from xpra.server.auth import pam_auth
     93            auth_module = pam_auth
     94        elif auth=="win32":
     95            from xpra.server.auth import win32_auth
     96            auth_module = win32_auth
     97        else:
     98            raise Exception("invalid auth module: %s" % auth)
     99        try:
     100            auth_module.init(opts)
     101        except Exception, e:
     102            raise Exception("failed to initialize %s module: %s" % (auth_module, e))
     103        try:
     104            self.auth_class = getattr(auth_module, "Authenticator")
     105        except Exception, e:
     106            raise Exception("Authenticator class not found in %s" % auth_module)
     107
    76108    def init_sockets(self, sockets):
    77109        ### All right, we're ready to accept customers:
    78110        for socktype, sock in sockets:
     
    167199        log("new_connection(%s) sock=%s, sockname=%s, address=%s, peername=%s", args, sock, sockname, address, peername)
    168200        sc = SocketConnection(sock, sockname, address, target, socktype)
    169201        log.info("New connection received: %s", sc)
    170         protocol = Protocol(sc, self.process_packet)
     202        protocol = Protocol(self, sc, self.process_packet)
    171203        protocol.large_packets.append("info-response")
    172         protocol.salt = None
    173204        protocol.set_compression_level(self.compression_level)
     205        protocol.authenticator = None
    174206        self._potential_protocols.append(protocol)
    175207        protocol.start()
    176208        self.timeout_add(10*1000, self.verify_connection_accepted, protocol)
     
    208240        self.disconnect_client(proto, "invalid packet format")
    209241
    210242
    211     def _send_password_challenge(self, proto, server_cipher):
    212         proto.salt = get_hex_uuid()
    213         log.info("Password required, sending challenge")
    214         proto.send_now(("challenge", proto.salt, server_cipher))
    215 
    216     def _verify_password(self, proto, client_hash, password):
    217         salt = proto.salt
    218         proto.salt = None
    219         if not salt:
    220             self.send_disconnect(proto, "illegal challenge response received - salt cleared or unset")
    221             return False
    222         password_hash = hmac.HMAC(password, salt)
    223         if client_hash != password_hash.hexdigest():
    224             def login_failed(*args):
    225                 log.error("Password supplied does not match! dropping the connection.")
    226                 self.send_disconnect(proto, "invalid password")
    227             self.timeout_add(1000, login_failed)
    228             return False
    229         log.info("Password matches!")
    230         sys.stdout.flush()
    231         return True
    232 
    233     def get_password(self):
    234         if not self.password_file:
    235             return None
    236         filename = os.path.expanduser(self.password_file)
    237         if not filename:
    238             return None
    239         try:
    240             passwordFile = open(filename, "rU")
    241             password  = passwordFile.read()
    242             passwordFile.close()
    243             while len(password)>0 and password[-1] in ("\n", "\r"):
    244                 password = password[:-1]
    245             return password
    246         except IOError, e:
    247             log.error("cannot open password file %s: %s", filename, e)
    248             return None
    249 
    250 
    251243    def _process_hello(self, proto, packet):
    252244        capabilities = packet[1]
    253245        c = typedict(capabilities)
     
    255247        proto.chunked_compression = c.boolget("chunked_compression")
    256248        if use_rencode and c.boolget("rencode"):
    257249            proto.enable_rencode()
    258         if c.boolget("lz4") and has_lz4 and proto.chunked_compression and self.compression_level>0 and self.compression_level<3:
     250        if c.boolget("lz4") and has_lz4 and proto.chunked_compression and self.compression_level==1:
    259251            proto.enable_lz4()
    260252
    261253        log("process_hello: capabilities=%s", capabilities)
     
    278270            self.disconnect_client(proto, "incompatible version: %s" % verr)
    279271            proto.close()
    280272            return  False
     273
     274        def auth_failed(msg):
     275            self.timeout_add(1000, self.disconnect_client, proto, msg)
     276
     277        #authenticator:
     278        username = c.strget("username")
     279        if proto.authenticator is None and self.auth_class:
     280            try:
     281                proto.authenticator = self.auth_class(username)
     282            except Exception, e:
     283                auth_failed("authentication failed")
     284                return False
     285        self.digest_modes = c.get("digest", ("hmac", ))
     286
    281287        #client may have requested encryption:
    282288        cipher = c.strget("cipher")
    283289        cipher_iv = c.strget("cipher.iv")
    284290        key_salt = c.strget("cipher.key_salt")
    285291        iterations = c.intget("cipher.key_stretch_iterations")
    286         password = None
    287         if bool(self.password_file) or (cipher is not None and cipher_iv is not None):
    288             #we will need the password:
    289             log("process_hello password is required!")
    290             password = self.get_password()
    291             if not password:
    292                 self.send_disconnect(proto, "password not found")
    293                 return False
    294292        auth_caps = {}
    295293        if cipher and cipher_iv:
    296294            if cipher not in ENCRYPTION_CIPHERS:
    297295                log.warn("unsupported cipher: %s", cipher)
    298                 self.send_disconnect(proto, "unsupported cipher")
     296                auth_failed("unsupported cipher")
    299297                return False
    300             proto.set_cipher_out(cipher, cipher_iv, password, key_salt, iterations)
     298            encryption_key = self.get_encryption_key(proto.authenticator)
     299            if encryption_key is None:
     300                auth_failed("encryption key is missing")
     301                return False
     302            proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations)
    301303            #use the same cipher as used by the client:
    302             iv = get_hex_uuid()[:16]
    303             key_salt = get_hex_uuid()
    304             iterations = 1000
    305             proto.set_cipher_in(cipher, iv, password, key_salt, iterations)
    306             auth_caps = {
    307                          "cipher"           : cipher,
    308                          "cipher.iv"        : iv,
    309                          "cipher.key_salt"  : key_salt,
    310                          "cipher.key_stretch_iterations" : iterations
    311                          }
     304            auth_caps = new_cipher_caps(proto, cipher, encryption_key)
    312305            log("server cipher=%s", auth_caps)
     306        else:
     307            auth_caps = None
    313308
    314         if self.password_file:
    315             log("password auth required")
     309        #verify authentication if required:
     310        if proto.authenticator:
     311            log("processing authentication with %s", proto.authenticator)
    316312            #send challenge if this is not a response:
    317             client_hash = c.strget("challenge_response")
    318             if not client_hash or not proto.salt:
    319                 self._send_password_challenge(proto, auth_caps or "")
     313            challenge_response = c.strget("challenge_response")
     314            if not challenge_response:
     315                challenge = proto.authenticator.get_challenge()
     316                if challenge is None:
     317                    auth_failed("invalid authentication state: unexpected challenge response")
     318                    return False
     319                salt, digest = challenge
     320                log.info("Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest)
     321                if digest not in self.digest_modes:
     322                    auth_failed("cannot proceed without %s digest support" % digest)
     323                    return False
     324                proto.send_now(("challenge", salt, auth_caps or "", digest))
    320325                return False
    321             if not self._verify_password(proto, client_hash, password):
     326            if not proto.authenticator.authenticate(challenge_response):
     327                auth_failed("authentication failed")
    322328                return False
     329            log("authentication challenge passed")
    323330        return auth_caps
    324331
     332    def get_encryption_key(self, authenticator=None):
     333        #if we have a keyfile specified, use that:
     334        v = None
     335        if self.encryption_keyfile:
     336            log("trying to load encryption key from keyfile: %s", self.encryption_keyfile)
     337            v = load_binary_file(self.encryption_keyfile)
     338        if v is None and authenticator:
     339            log("trying to get encryption key from: %s", authenticator)
     340            v = authenticator.get_password()
     341        if v is None and self.password_file:
     342            log("trying to load encryption key from password file: %s", self.password_file)
     343            v = load_binary_file(self.password_file)
     344        if v is None:
     345            return None
     346        return v.strip("\n\r")
     347
    325348    def hello_oked(self, proto, packet, c, auth_caps):
    326349        pass
    327350
     
    350373        capabilities["elapsed_time"] = int(now - self.start_time)
    351374        capabilities["raw_packets"] = True
    352375        capabilities["chunked_compression"] = True
     376        capabilities["digest"] = ("hmac", "xor")
    353377        capabilities["lz4"] = has_lz4
    354378        capabilities["rencode"] = has_rencode
    355379        if has_rencode:
  • xpra/util.py

     
    7272    if x is None:
    7373        return None
    7474    return str(x).replace("\n", "\\n").replace("\r", "\\r")
     75
     76def xor(s1,s2):   
     77    return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(s1,s2))