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-v10.patch

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

many fixes (except encryption drop outs)

  • 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

     
    88import os
    99import sys
    1010import socket
     11import binascii
    1112from xpra.gtk_common.gobject_compat import import_gobject, import_glib
    1213gobject = import_gobject()
    1314
     
    1819from xpra.scripts.config import ENCRYPTION_CIPHERS, python_platform
    1920from xpra.version_util import version_compat_check, add_version_info
    2021from xpra.platform.features import GOT_PASSWORD_PROMPT_SUGGESTION
    21 from xpra.os_util import get_hex_uuid, get_machine_id, SIGNAMES, strtobytes, bytestostr
     22from xpra.platform.info import get_username, get_name
     23from xpra.os_util import get_hex_uuid, get_machine_id, load_binary_file, SIGNAMES, strtobytes, bytestostr
    2224from xpra.util import typedict
    2325
    2426EXIT_OK = 0
     
    3436EXIT_MMAP_TOKEN_FAILURE = 10
    3537
    3638DEFAULT_TIMEOUT = 20*1000
     39ALLOW_UNENCRYPTED_PASSWORDS = os.environ.get("XPRA_ALLOW_UNENCRYPTED_PASSWORDS", "0")=="1"
    3740
    3841
    3942class XpraClientBase(object):
     
    5053    def __init__(self):
    5154        self.exit_code = None
    5255        self.compression_level = 0
     56        self.username = None
    5357        self.password = None
    5458        self.password_file = None
    5559        self.password_sent = False
    5660        self.encryption = None
     61        self.encryption_keyfile = None
    5762        self.quality = -1
    5863        self.min_quality = 0
    5964        self.speed = 0
     
    7883
    7984    def init(self, opts):
    8085        self.compression_level = opts.compression_level
     86        self.username = opts.username
    8187        self.password_file = opts.password_file
    8288        self.encryption = opts.encryption
     89        self.encryption_keyfile = opts.encryption_keyfile
    8390        self.quality = opts.quality
    8491        self.min_quality = opts.min_quality
    8592        self.speed = opts.speed
     
    115122        #overriden in subclasses!
    116123        return "Python"
    117124
     125    def get_scheduler(self):
     126        raise NotImplementedError()
     127
    118128    def setup_connection(self, conn):
    119129        log.debug("setup_connection(%s)", conn)
    120         self._protocol = Protocol(conn, self.process_packet, self.next_packet)
     130        self._protocol = Protocol(self.get_scheduler(), conn, self.process_packet, self.next_packet)
    121131        self._protocol.large_packets.append("keymap-changed")
    122132        self._protocol.large_packets.append("server-settings")
    123133        self._protocol.set_compression_level(self.compression_level)
     
    145155            i += 1
    146156
    147157    def send_hello(self, challenge_response=None):
    148         hello = self.make_hello(challenge_response)
    149         log.debug("send_hello(%s) packet=%s", challenge_response, hello)
     158        hello = self.make_hello_base(challenge_response)
     159        if challenge_response or not self.password_file:
     160            #could be the real hello, so we need all the details:
     161            hello.update(self.make_hello())
     162        log.debug("send_hello(%s) packet=%s", binascii.hexlify(challenge_response or ""), hello)
    150163        self.send("hello", hello)
    151164
    152     def make_hello(self, challenge_response=None):
    153         capabilities = {}
     165    def make_hello_base(self, challenge_response=None):
     166        capabilities = {"namespace"             : True,
     167                        "raw_packets"           : True,
     168                        "chunked_compression"   : True,
     169                        "digest"                : ("hmac", "xor"),
     170                        "rencode"               : has_rencode,
     171                        "lz4"                   : has_lz4,
     172                        "hostname"              : socket.gethostname(),
     173                        "uuid"                  : self.uuid,
     174                        "username"              : self.username,
     175                        "name"                  : get_name(),
     176                        "platform"              : sys.platform,
     177                        "platform.release"      : python_platform.release(),
     178                        "platform.machine"      : python_platform.machine(),
     179                        "platform.processor"    : python_platform.processor(),
     180                        "client_type"           : self.client_type(),
     181                        "python.version"        : sys.version_info[:3],
     182                        }
     183        if has_rencode:
     184            capabilities["rencode.version"] = rencode_version
    154185        add_version_info(capabilities)
    155         capabilities["python.version"] = sys.version_info[:3]
     186
    156187        if challenge_response:
    157188            assert self.password
    158189            capabilities["challenge_response"] = challenge_response
     
    165196            capabilities["cipher.key_salt"] = key_salt
    166197            iterations = 1000
    167198            capabilities["cipher.key_stretch_iterations"] = iterations
    168             self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations)
     199            key = self.get_encryption_key()
     200            if key is None:
     201                self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing")
     202                return
     203            self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations)
    169204            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
     205        return capabilities
     206
     207    def make_hello(self):
     208        capabilities = {
     209                        "randr_notify"        : False,        #only client.py cares about this
     210                        "windows"            : False,        #only client.py cares about this
     211                       }
    192212        if self._reverse_aliases:
    193213            capabilities["aliases"] = self._reverse_aliases
    194214        return capabilities
     
    293313        self.warn_and_quit(EXIT_CONNECTION_LOST, "Connection lost")
    294314
    295315    def _process_challenge(self, packet):
     316        log("processing challenge: %s", packet[1:])
    296317        if not self.password_file and not self.password:
    297318            self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "password is required by the server")
    298319            return
     
    304325        if self.encryption:
    305326            assert len(packet)>=3, "challenge does not contain encryption details to use for the response"
    306327            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()
     328            key = self.get_encryption_key()
     329            if key is None:
     330                self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing")
     331                return
     332            if not self.set_server_encryption(server_cipher, key):
     333                return
     334        digest = "hmac"
     335        if len(packet)>=4:
     336            digest = packet[3]
     337        if digest=="hmac":
     338            import hmac
     339            challenge_response = hmac.HMAC(self.password, salt).hexdigest()
     340            log("hmac(%s, %s)=%s", self.password, salt, challenge_response)
     341        elif digest=="xor":
     342            #don't send XORed password unencrypted:
     343            if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS:
     344                self.warn_and_quit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest)
     345                return
     346            from xpra.util import xor
     347            challenge_response = xor(self.password, salt)
     348        else:
     349            self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest)
     350            return
    311351        self.password_sent = True
    312         self.send_hello(password_hash)
     352        self.send_hello(challenge_response)
    313353
    314     def set_server_encryption(self, capabilities):
     354    def set_server_encryption(self, capabilities, key):
    315355        def get(key, default=None):
    316356            return capabilities.get(strtobytes(key), default)
    317357        cipher = get("cipher")
     
    324364        if cipher not in ENCRYPTION_CIPHERS:
    325365            self.warn_and_quit(EXIT_ENCRYPTION, "unsupported server cipher: %s, allowed ciphers: %s" % (cipher, ", ".join(ENCRYPTION_CIPHERS)))
    326366            return False
    327         self._protocol.set_cipher_out(cipher, cipher_iv, self.get_password(), key_salt, iterations)
     367        self._protocol.set_cipher_out(cipher, cipher_iv, key, key_salt, iterations)
     368        return True
    328369
    329370
    330     def get_password(self):
    331         if self.password is None:
    332             self.load_password()
    333         return self.password
     371    def get_encryption_key(self):
     372        key = load_binary_file(self.encryption_keyfile)
     373        if key is None and self.password_file:
     374            key = load_binary_file(self.password_file)
     375            if key:
     376                log("used password file as encryption key")
     377        if key is None:
     378            raise Exception("failed to load encryption keyfile %s" % self.encryption_keyfile)
     379        return key.strip("\n\r")
    334380
    335381    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:
     382        filename = os.path.expanduser(self.password_file)
     383        self.password = load_binary_file(filename)
     384        if self.password is None:
    344385            self.warn_and_quit(EXIT_PASSWORD_FILE_ERROR, "failed to open password file %s: %s" % (self.password_file, e))
    345386            return False
    346         log("password read from file %s is %s", self.password_file, self.password)
     387        self.password = self.password.strip("\n\r")
     388        log("password read from file %s is %s", self.password_file, "".join(["*" for x in self.password]))
    347389        return True
    348390
    349391    def _process_hello(self, packet):
    350392        if not self.password_sent and self.password_file:
    351393            log.warn("Warning: the server did not request our password!")
    352394        self.server_capabilities = packet[1]
     395        log("processing hello from server: %s", self.server_capabilities)
    353396        c = typedict(self.server_capabilities)
    354397        self.parse_server_capabilities(c)
    355398
     
    375418        self._protocol.chunked_compression = c.boolget("chunked_compression")
    376419        if use_rencode and c.boolget("rencode"):
    377420            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:
     421        if c.boolget("lz4") and has_lz4 and self._protocol.chunked_compression and self.compression_level==1:
    379422            self._protocol.enable_lz4()
    380423        if self.encryption:
    381424            #server uses a new cipher after second hello:
    382             self.set_server_encryption(c)
     425            key = self.get_encryption_key()
     426            assert key, "encryption key is missing"
     427            if not self.set_server_encryption(c, key):
     428                return False
    383429        self._protocol.aliases = c.dictget("aliases", {})
    384430        if self.pings:
    385431            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
    199227    def get_cipher(self, ciphername, iv, password, key_salt, iterations):
    200         debug("get_cipher_in(%s, %s, %s, %s, %s)", ciphername, iv, password, key_salt, iterations)
     228        debug("get_cipher(%s, %s, %s, %s, %s)", ciphername, iv, password, key_salt, iterations)
    201229        if not ciphername:
    202230            return None, 0
    203231        assert iterations>=100
     
    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]
    212         debug("get_cipher(%s, %s, %s) secret=%s, block_size=%s", ciphername, iv, password, secret.encode('hex'), block_size)
     239        debug("get_cipher(..) secret=%s, block_size=%s", secret.encode('hex'), block_size)
    213240        return AES.new(secret, AES.MODE_CBC, iv), block_size
    214241
    215242    def set_cipher_in(self, ciphername, iv, password, key_salt, iterations):
    216243        if self.cipher_in_name!=ciphername:
    217244            log.info("receiving data using %s encryption", ciphername)
    218245            self.cipher_in_name = ciphername
     246        debug("set_cipher_in%s", (ciphername, iv, password, key_salt, iterations))
    219247        self.cipher_in, self.cipher_in_block_size = self.get_cipher(ciphername, iv, password, key_salt, iterations)
    220248
    221249    def set_cipher_out(self, ciphername, iv, password, key_salt, iterations):
    222250        if self.cipher_out_name!=ciphername:
    223251            log.info("sending data using %s encryption", ciphername)
    224252            self.cipher_out_name = ciphername
     253        debug("set_cipher_out%s", (ciphername, iv, password, key_salt, iterations))
    225254        self.cipher_out, self.cipher_out_block_size = self.get_cipher(ciphername, iv, password, key_salt, iterations)
    226255
    227256    def __str__(self):
     
    241270        info[prefix+"output.cipher" + suffix] = self.cipher_out_name or ""
    242271        info[prefix+"chunked_compression" + suffix] = self.chunked_compression
    243272        info[prefix+"large_packets" + suffix] = self.large_packets
    244         info[prefix+"compression_level" + suffix] = self._compression_level
     273        info[prefix+"compression_level" + suffix] = self.compression_level
    245274        info[prefix+"max_packet_size" + suffix] = self.max_packet_size
    246275        for k,v in self.aliases.items():
    247276            info[prefix+"alias." + k + suffix] = v
     
    259288
    260289    def start(self):
    261290        def do_start():
     291            #debug("Protocol.do_start() closed=%s", self._closed)
    262292            if not self._closed:
    263293                self._write_thread.start()
    264294                self._read_thread.start()
    265295                self._read_parser_thread.start()
    266296                self._write_format_thread.start()
    267         scheduler.idle_add(do_start)
     297        #debug("Protocol.start() will call %s via %s", do_start, self.scheduler.idle_add)
     298        self.scheduler.idle_add(do_start)
    268299
    269300    def send_now(self, packet):
    270301        if self._closed:
     
    406437        """
    407438        packets = []
    408439        packet = list(packet_in)
    409         level = self._compression_level
     440        level = self.compression_level
    410441        for i in range(1, len(packet)):
    411442            item = packet[i]
    412443            ti = type(item)
     
    455486
    456487    def set_compression_level(self, level):
    457488        #this may be used next time encode() is called
    458         self._compression_level = level
     489        self.compression_level = level
    459490
    460491    def _io_thread_loop(self, name, callback):
    461492        try:
     
    519550
    520551    def _call_connection_lost(self, message="", exc_info=False):
    521552        debug("will call connection lost: %s", message)
    522         scheduler.idle_add(self._connection_lost, message, exc_info)
     553        self.scheduler.idle_add(self._connection_lost, message, exc_info)
    523554
    524555    def _connection_lost(self, message="", exc_info=False):
    525556        log.info("connection lost: %s", message, exc_info=exc_info)
     
    527558        return False
    528559
    529560    def _gibberish(self, msg, data):
    530         scheduler.idle_add(self._process_packet_cb, self, [Protocol.GIBBERISH, data])
     561        self.scheduler.idle_add(self._process_packet_cb, self, [Protocol.GIBBERISH, data])
    531562        # Then hang up:
    532         scheduler.timeout_add(1000, self._connection_lost, msg)
     563        self.scheduler.timeout_add(1000, self._connection_lost, msg)
    533564
    534565
    535566    def _read_parse_thread_loop(self):
     
    559590            buf = self._read_queue.get()
    560591            if not buf:
    561592                debug("read thread: empty marker, exiting")
    562                 scheduler.idle_add(self.close)
     593                self.scheduler.idle_add(self.close)
    563594                return
    564595            if read_buffer:
    565596                read_buffer = read_buffer + buf
     
    609640                                self._call_connection_lost("invalid packet: size requested is %s (maximum allowed is %s - packet header: 0x%s), dropping this connection!" %
    610641                                                              (size_to_check, self.max_packet_size, repr_ellipsized(packet_header)))
    611642                        return False
    612                     scheduler.timeout_add(1000, check_packet_size, payload_size, read_buffer[:32])
     643                    self.scheduler.timeout_add(1000, check_packet_size, payload_size, read_buffer[:32])
    613644
    614645                if bl<payload_size:
    615646                    # incomplete packet, wait for the rest to arrive
     
    628659                    debug("received %s encrypted bytes with %s padding", payload_size, len(padding))
    629660                    data = self.cipher_in.decrypt(raw_string)
    630661                    if padding:
    631                         def debug_str():
     662                        def debug_str(s):
    632663                            try:
    633                                 return list(bytearray(raw_string))
     664                                return list(bytearray(s))
    634665                            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))
     666                                return list(str(s))
     667                        if not data.endswith(padding):
     668                            log("decryption failed: string does not end with '%s': %s (%s) -> %s (%s)",
     669                            padding, debug_str(raw_string), type(raw_string), debug_str(data), type(data))
     670                            self._connection_lost("encryption error (wrong key?)")
     671                            return
    638672                        data = data[:-len(padding)]
    639673                #uncompress if needed:
    640674                if compression_level>0:
     
    702736            we wait again for the queue to flush,
    703737            then no matter what, we close the connection and stop the threads.
    704738        """
     739        if self._closed:
     740            return
    705741        def wait_for_queue(timeout=10):
    706742            #IMPORTANT: if we are here, we have the write lock held!
    707743            if not self._write_queue.empty():
     
    712748                    self.close()
    713749                else:
    714750                    debug("flush_then_close: still waiting for queue to flush")
    715                     scheduler.timeout_add(100, wait_for_queue, timeout-1)
     751                    self.scheduler.timeout_add(100, wait_for_queue, timeout-1)
    716752            else:
    717753                debug("flush_then_close: queue is now empty, sending the last packet and closing")
    718754                chunks, proto_flags = self.encode(last_packet)
     
    720756                    self.close()
    721757                self._add_chunks_to_queue(chunks, proto_flags, start_send_cb=None, end_send_cb=close_cb)
    722758                self._write_lock.release()
    723                 scheduler.timeout_add(5*1000, self.close)
     759                self.scheduler.timeout_add(5*1000, self.close)
    724760
    725761        def wait_for_write_lock(timeout=100):
    726762            if not self._write_lock.acquire(False):
     
    729765                    self.close()
    730766                else:
    731767                    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)
     768                    self.scheduler.timeout_add(10, wait_for_write_lock, timeout-1)
    733769            else:
    734770                debug("flush_then_close: acquired the write lock")
    735771                #we have the write lock - we MUST free it!
     
    742778        wait_for_write_lock()
    743779
    744780    def close(self):
     781        debug("close() closed=%s", self._closed)
    745782        if self._closed:
    746783            return
    747784        self._closed = True
    748         scheduler.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
     785        self.scheduler.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
    749786        if self._conn:
    750787            try:
    751788                self._conn.close()
     
    757794                log.error("error closing %s", self._conn, exc_info=True)
    758795            self._conn = None
    759796        self.terminate_io_threads()
    760         scheduler.idle_add(self.clean)
     797        self.scheduler.idle_add(self.clean)
    761798
     799    def steal_connection(self):
     800        #so we can re-use this connection somewhere else
     801        #(frees all protocol threads and resources)
     802        assert not self._closed
     803        conn = self._conn
     804        self._closed = True
     805        self._conn = None
     806        self.terminate_io_threads()
     807        self.scheduler.idle_add(self.clean)
     808        return conn
     809
    762810    def clean(self):
    763811        #clear all references to ensure we can get garbage collected quickly:
    764812        self._get_packet_cb = None
     
    784832
    785833class FakeJitter(object):
    786834
    787     def __init__(self, process_packet_cb):
     835    def __init__(self, scheduler, process_packet_cb):
     836        self.scheduler = scheduler
    788837        self.real_process_packet_cb = process_packet_cb
    789838        self.delay = FAKE_JITTER
    790839        self.ok_delay = 10*1000
     
    797846    def start_buffering(self):
    798847        log.info("FakeJitter.start_buffering() will buffer for %s ms", FAKE_JITTER)
    799848        self.delaying = True
    800         scheduler.timeout_add(FAKE_JITTER, self.flush)
     849        self.scheduler.timeout_add(FAKE_JITTER, self.flush)
    801850
    802851    def flush(self):
    803852        log.info("FakeJitter.flush() processing %s delayed packets", len(self.pending))
     
    809858            self.delaying = False
    810859        finally:
    811860            self.lock.release()
    812         scheduler.timeout_add(self.ok_delay, self.start_buffering)
     861        self.scheduler.timeout_add(self.ok_delay, self.start_buffering)
    813862        log.info("FakeJitter.flush() will start buffering again in %s ms", self.ok_delay)
    814863
    815864    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/fail_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.os_util import get_hex_uuid
     10from xpra.log import Logger, debug_if_env
     11log = Logger()
     12debug = debug_if_env(log, "XPRA_AUTH_DEBUG")
     13
     14
     15class Authenticator(object):
     16    def __init__(self, username):
     17        pass
     18
     19    def get_challenge(self):
     20        return get_hex_uuid(), "hmac"
     21
     22    def get_uid(self):
     23        return 1000
     24
     25    def get_gid(self):
     26        return 1000
     27
     28    def get_password(self):
     29        return None
     30
     31    def authenticate(self, challenge_response):
     32        return False
     33
     34    def get_sessions(self):
     35        return None
  • xpra/server/auth/file_auth.py

    Property changes on: xpra/server/auth/fail_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
     6#use authentication from a file with the following format:
     7#1) either a single password with no "|" characters, which will be used for all usernames
     8#2) a list of entries of the form:
     9# username|password|uid|gid|displays|env_options|session_options
     10#
     11
     12import os.path
     13import sys
     14import pwd
     15import hmac
     16
     17from xpra.os_util import get_hex_uuid
     18from xpra.dotxpra import DotXpra
     19from xpra.log import Logger, debug_if_env
     20log = Logger()
     21debug = debug_if_env(log, "XPRA_AUTH_DEBUG")
     22
     23
     24password_file = None
     25socket_dir = None
     26def init(opts):
     27    global password_file, socket_dir
     28    password_file = opts.password_file
     29    socket_dir = opts.socket_dir
     30
     31
     32def parseOptions(s):
     33    return {}
     34
     35
     36auth_data = None
     37auth_data_time = None
     38def load_auth_file():
     39    global auth_data, auth_data_time, password_file, socket_dir
     40    if not os.path.exists(password_file):
     41        log.error("password file is missing: %s", e)
     42        auth_data = None
     43        return auth_data
     44    ptime = 0
     45    try:
     46        ptime = os.stat(password_file).st_mtime
     47    except Exception, e:
     48        log.error("error accessing password file time: %s", e)
     49    if auth_data is None or ptime!=auth_data_time:
     50        auth_data = {}
     51        auth_data_time = ptime
     52        f = None
     53        try:
     54            try:
     55                f = open(password_file, mode='rb')
     56                data = f.read()
     57            finally:
     58                if f:
     59                    f.close()
     60        except Exception, e:
     61            log.error("error loading %s: %s", password_file, e)
     62            data = ""
     63        i = 0
     64        for line in data.splitlines():
     65            i += 1
     66            line = line.strip()
     67            if len(line)==0:
     68                continue
     69            debug("line %s: %s", i, line)
     70            if line.find("|")<0:
     71                #assume old style file with just the password
     72                #get all the displays for the current user:
     73                sockdir = DotXpra(socket_dir)
     74                results = sockdir.sockets()
     75                displays = [display for state, display in results if state==DotXpra.LIVE]
     76                auth_data[""] = line, os.getuid(), os.getgid(), displays, {}, {}
     77                debug("Warning: assuming this is a single password for all users")
     78                continue
     79            ldata = line.split("|")
     80            debug("found %s fields at line %s", len(ldata), i)
     81            if len(ldata)<4:
     82                log.warn("skipped line %s of %s: not enough fields", i, password_file)
     83                continue
     84            #parse fields:
     85            username = ldata[0]
     86            password = ldata[1]
     87            def getsysid(s, default_value):
     88                if not s:
     89                    return default_value
     90                try:
     91                    return int(s)
     92                except:
     93                    return default_value
     94            uid = getsysid(ldata[2], os.getuid())
     95            gid = getsysid(ldata[3], os.getgid())
     96            displays = ldata[4].split(",")
     97            env_options = {}
     98            session_options = {}
     99            if len(ldata)>=6:
     100                env_options = parseOptions(ldata[5])
     101            if len(ldata)>=7:
     102                session_options = parseOptions(ldata[6])
     103            auth_data[username] = password, uid, gid, displays, env_options, session_options
     104    debug("loaded auth data from file %s: %s", password_file, auth_data)
     105    return auth_data
     106
     107
     108class Authenticator(object):
     109    def __init__(self, username):
     110        self.username = username
     111        self.salt = None
     112        self.sessions = None
     113
     114    def get_challenge(self):
     115        if self.salt is not None:
     116            log.error("challenge already sent!")
     117            return None
     118        self.salt = get_hex_uuid()
     119        #this authenticator can use the safer "hmac" digest:
     120        return self.salt, "hmac"
     121
     122    def get_entry(self):
     123        ad = load_auth_file()
     124        username = self.username
     125        if username not in ad:
     126            #maybe this is an old style file with just the password?
     127            if len(ad)==1 and ad.keys()[0]=="":
     128                #then ignore the username
     129                username = ""
     130            else:
     131                return None
     132        return ad[username]
     133
     134    def get_password(self):
     135        entry = self.get_entry()
     136        if entry is None:
     137            return None
     138        return entry[0]
     139
     140    def authenticate(self, challenge_response):
     141        global password_file
     142        if not self.salt:
     143            log.error("illegal challenge response received - salt cleared or unset")
     144            return None
     145        #ensure this salt does not get re-used:
     146        salt = self.salt
     147        self.salt = None
     148        entry = self.get_entry()
     149        if entry is None:
     150            log.error("usename %s does not exist in %s", self.username, password_file)
     151            return None
     152        fpassword, uid, gid, displays, env_options, session_options = entry
     153        hash = hmac.HMAC(fpassword, salt).hexdigest()
     154        debug("authenticate(%s) password=%s, salt=%s, hash=%s", challenge_response, fpassword, salt, hash)
     155        if hash!=challenge_response:
     156            debug("expected '%s' but got '%s'", hash, challenge_response)
     157            log.error("hmac password challenge for %s does not match", self.username)
     158            return False
     159        self.sessions = uid, gid, displays, env_options, session_options
     160        return True
     161
     162    def get_sessions(self):
     163        return self.sessions
     164
     165    def __str__(self):
     166        return "Password File Authenticator"
     167
     168
     169
     170def main(args):
     171    return 0
     172
     173if __name__ == "__main__":
     174    sys.exit(main(sys.argv))
  • 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        try:
     79            self.pw = pwd.getpwnam(username)
     80        except:
     81            self.pw = None
     82
     83    def get_uid(self):
     84        assert self.pw, "username not found"
     85        return self.pw.pw_uid
     86
     87    def get_gid(self):
     88        assert self.pw, "username not found"
     89        return self.pw.pw_gid
     90
     91    def check(self, password):
     92        if self.pw is None:
     93            return False
     94        return check(self.username, password)
     95
     96    def __str__(self):
     97        return "PAM Authenticator"
     98
     99
     100def main(args):
     101    if len(args)!=3:
     102        print("invalid number of arguments")
     103        print("usage:")
     104        print("%s username password")
     105        return 1
     106    a = Authenticator(args[1])
     107    if a.check(args[2]):
     108        print("success")
     109        return 0
     110    else:
     111        print("failed")
     112        return -1
     113
     114if __name__ == "__main__":
     115    sys.exit(main(sys.argv))
  • 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_gid(self):
     40        raise NotImplementedError()
     41
     42    def get_password(self):
     43        return None
     44
     45    def check(self, username, password):
     46        raise NotImplementedError()
     47
     48    def authenticate(self, challenge_response):
     49        global socket_dir
     50        if self.salt is None:
     51            log.error("got a challenge response with no salt!")
     52            return False
     53        password = xor(challenge_response, self.salt)
     54        #warning: enabling logging here would log the actual system password!
     55        #log("authenticate(%s) password=%s", challenge_response, password)
     56        #verify login:
     57        try :
     58            if not self.check(password):
     59                return False
     60        except Exception, e:
     61            log.error("authentication error: %s", e)
     62            return False
     63        return True
     64
     65    def get_sessions(self):
     66        sockdir = DotXpra(socket_dir, actual_username=self.username)
     67        uid = self.get_uid()
     68        gid = self.get_gid()
     69        results = sockdir.sockets(check_uid=uid)
     70        displays = [display for state, display in results if state==DotXpra.LIVE]
     71        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)
    79123        def parse_error(*args):
     124            disconnect("invalid display string")
    80125            log.warn("parse error on %s: %s", target, args)
     126            raise Exception("parse error on %s: %s" % (target, args))
    81127        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)
     128        opts.username = c.strget("username")
     129        disp_desc = parse_display_name(parse_error, opts, display)
     130        log.info("display description(%s) = %s", display, disp_desc)
     131        try:
     132            server_conn = connect_to(disp_desc)
     133        except Exception, e:
     134            disconnect("failed to connect to display")
     135            return
     136        log.info("server connection=%s", server_conn)
     137
     138        #grab client connection so we can pass it to the ProxyProcess:
     139        client_conn = client_proto.steal_connection()
     140        client_state = client_proto.save_state()
     141        cipher = auth_caps.get("cipher")
     142        encryption_key = None
     143        if cipher:
     144            encryption_key = self.get_encryption_key(client_proto.authenticator)
     145        log.info("start_proxy(%s, {..}) client connection=%s", client_proto, client_conn)
     146        log.info("start_proxy(%s, {..}) client state=%s", client_proto, client_state)
     147
     148        process = ProxyProcess(uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, c, self.proxy_ended)
     149        log.info("starting %s from pid=%s", process, os.getpid())
     150        process.start()
     151        #FIXME: remove processes that have terminated
     152        self.processes.append(process)
     153        #now we can close our handle on the connection:
     154        client_conn.close()
     155        server_conn.close()
     156
     157    def proxy_ended(self, proxy_process):
     158        log.info("proxy_ended(%s)", proxy_process)
     159        if proxy_process in self.processes:
     160            self.processes.remove(proxy_process)
     161        log.info("processes: %s", self.processes)
     162
     163
     164class ProxyProcess(Process):
     165
     166    def __init__(self, uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, caps, exit_cb):
     167        Process.__init__(self, name=str(client_conn))
     168        assert uid!=0 and gid!=0
     169        self.uid = uid
     170        self.gid = gid
     171        self.client_conn = client_conn
     172        self.client_state = client_state
     173        self.cipher = cipher
     174        self.encryption_key = encryption_key
     175        self.server_conn = server_conn
     176        self.caps = caps
     177        self.exit_cb = exit_cb
     178        log.info("ProxyProcess%s pid=%s", (uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, "{..}"), os.getpid())
     179        self.client_protocol = None
     180        self.server_protocol = None
     181        self.main_queue = Queue()
     182
     183    def signal_quit(self, signum, frame):
     184        log.info("")
     185        log.info("proxy process got signal %s, exiting", SIGNAMES.get(signum, signum))
     186        signal.signal(signal.SIGINT, deadly_signal)
     187        signal.signal(signal.SIGTERM, deadly_signal)
     188        self.stop(SIGNAMES.get(signum, signum))
     189
     190    def idle_add(self, fn, *args, **kwargs):
     191        self.main_queue.put((fn, args, kwargs))
     192
     193    def timeout_add(self, timeout, fn, *args, **kwargs):
     194        #self.main_queue.put((timeout, fn, , args, kwargs))
     195        timer = None
     196        def idle_exec():
     197            v = fn(*args, **kwargs)
     198            if not bool(v):
     199               timer.cancel()
     200            return False
     201        def timer_exec():
     202            #just run via idle_add:
     203            self.idle_add(idle_exec)
     204        timer = Timer(timeout*1000.0, timer_exec)
     205        timer.start()
     206
     207    def run(self):
     208        log.info("ProxyProcess.run() pid=%s", os.getpid())
     209        #change uid and gid:
     210        if os.getgid()!=self.gid:
     211            os.setgid(self.gid)
     212        if os.getuid()!=self.uid:
     213            os.setuid(self.uid)
     214        if not USE_THREADING:
     215            #signal.signal(signal.SIGTERM, self.signal_quit)
     216            #signal.signal(signal.SIGINT, self.signal_quit)
     217            pass
     218
     219        #setup protocol wrappers:
     220        self.client_to_server = Queue(PROXY_QUEUE_SIZE)
     221        self.server_to_client = Queue(PROXY_QUEUE_SIZE)
     222        self.client_protocol = Protocol(self, self.client_conn, self.process_client_packet, self.get_client_packet)
     223        self.client_protocol.restore_state(self.client_state)
     224        self.server_protocol = Protocol(self, self.server_conn, self.process_server_packet, self.get_server_packet)
     225
     226        #server connection tweaks:
    88227        self.server_protocol.large_packets.append("keymap-changed")
    89228        self.server_protocol.large_packets.append("server-settings")
    90229        self.server_protocol.set_compression_level(0)
     230
    91231        self.server_protocol.start()
     232        self.client_protocol.start()
     233
    92234        #forward the hello packet:
    93         self.client_to_server.put(packet)
     235        hello_packet = ("hello", self.filter_caps())
     236        self.client_to_server.put(hello_packet)
    94237        self.server_protocol.source_has_more()
    95238
     239        try:
     240            try:
     241                self.run_queue()
     242            except KeyboardInterrupt, e:
     243                self.stop(str(e))
     244        finally:
     245            self.exit_cb(self)
     246
     247    def filter_caps(self):
     248        #FIXME: more filtering needed
     249        #hello_packet["encodings"]=("rgb", )
     250        pcaps = {}
     251        for k,v in self.caps.items():
     252            skip = False
     253            for e in ("cipher", "mmap", "aliases"):
     254                if k.startswith(e):
     255                    skip = True
     256                    break
     257            if not skip:
     258                pcaps[k] = v
     259        pcaps["proxy"] = True
     260        return pcaps
     261
     262    def run_queue(self):
     263        #process "idle_add"/"timeout_add" events in the main loop:
     264        while True:
     265            v = self.main_queue.get()
     266            if v is None:
     267                break
     268            fn, args, kwargs = v
     269            try:
     270                v = fn(*args, **kwargs)
     271                if bool(v):
     272                    #re-run it
     273                    self.main_queue.put(v)
     274            except:
     275                log.error("error during main loop callback %s", fn, exc_info=True)
     276
     277    def stop(self, reason="proxy terminating", skip_proto=None):
     278        log.info("stop()")
     279        #empty the main queue:
     280        q = Queue()
     281        q.put(None)
     282        self.main_queue = q
     283        for proto in (self.client_protocol, self.server_protocol):
     284            if proto and proto!=skip_proto:
     285                proto.flush_then_close(["disconnect", reason])
     286
    96287    def get_server_packet(self):
    97288        #server wants a packet
    98         return self.client_to_server.get(),
     289        p = self.client_to_server.get()
     290        log.info("forwarding client packet %s", p[0])
     291        return p,
    99292
    100293    def get_client_packet(self):
    101294        #server wants a packet
    102         return self.server_to_client.get(),
     295        p = self.server_to_client.get()
     296        log.info("forwarding server packet %s", p[0])
     297        return p,
    103298
    104299    def process_server_packet(self, proto, packet):
    105         log.info("process_server_packet: %s", packet[0])
     300        packet_type = packet[0]
     301        log.info("process_server_packet: %s", packet_type)
     302        #log.info("process_server_packet: %s", packet)
     303        if packet_type==Protocol.CONNECTION_LOST:
     304            self.stop("server connection lost", proto)
     305            return
     306        elif packet_type=="hello":
     307            pcaps = packet[1]
     308            if "aliases" in pcaps:
     309                del pcaps["aliases"]
     310            auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key)
     311            pcaps.update(auth_caps)
    106312        #forward  packet received from server to the client
    107313        self.server_to_client.put(packet)
    108314        self.client_protocol.source_has_more()
    109315
    110316    def process_client_packet(self, proto, packet):
    111317        log.info("process_client_packet: %s", packet[0])
     318        log.info("process_client_packet: %s", packet)
    112319        #forward  packet received from client to the server
     320        packet_type = packet[0]
     321        if packet_type==Protocol.CONNECTION_LOST:
     322            self.stop("client connection lost", proto)
     323            return
     324        elif packet_type=="set_deflate":
     325            #echo it back to the client:
     326            self.server_to_client.put(packet)
     327            self.client_protocol.source_has_more()
     328            return
    113329        self.client_to_server.put(packet)
    114330        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=="sys":
     89            if sys.platform.startswith("win"):
     90                auth = "win32"
     91            else:
     92                auth = "pam"
     93            log("will try to use sys auth module '%s' for %s", auth, sys.platform)
     94        if auth=="fail":
     95            from xpra.server.auth import fail_auth
     96            auth_module = fail_auth
     97        elif auth=="file":
     98            from xpra.server.auth import file_auth
     99            auth_module = file_auth
     100        elif auth=="pam":
     101            from xpra.server.auth import pam_auth
     102            auth_module = pam_auth
     103        elif auth=="win32":
     104            from xpra.server.auth import win32_auth
     105            auth_module = win32_auth
     106        else:
     107            raise Exception("invalid auth module: %s" % auth)
     108        try:
     109            auth_module.init(opts)
     110        except Exception, e:
     111            raise Exception("failed to initialize %s module: %s" % (auth_module, e))
     112        try:
     113            self.auth_class = getattr(auth_module, "Authenticator")
     114        except Exception, e:
     115            raise Exception("Authenticator class not found in %s" % auth_module)
     116
    76117    def init_sockets(self, sockets):
    77118        ### All right, we're ready to accept customers:
    78119        for socktype, sock in sockets:
     
    167208        log("new_connection(%s) sock=%s, sockname=%s, address=%s, peername=%s", args, sock, sockname, address, peername)
    168209        sc = SocketConnection(sock, sockname, address, target, socktype)
    169210        log.info("New connection received: %s", sc)
    170         protocol = Protocol(sc, self.process_packet)
     211        protocol = Protocol(self, sc, self.process_packet)
    171212        protocol.large_packets.append("info-response")
    172         protocol.salt = None
    173213        protocol.set_compression_level(self.compression_level)
     214        protocol.authenticator = None
    174215        self._potential_protocols.append(protocol)
    175216        protocol.start()
    176217        self.timeout_add(10*1000, self.verify_connection_accepted, protocol)
     
    208249        self.disconnect_client(proto, "invalid packet format")
    209250
    210251
    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 
    251252    def _process_hello(self, proto, packet):
    252253        capabilities = packet[1]
    253254        c = typedict(capabilities)
     
    255256        proto.chunked_compression = c.boolget("chunked_compression")
    256257        if use_rencode and c.boolget("rencode"):
    257258            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:
     259        if c.boolget("lz4") and has_lz4 and proto.chunked_compression and self.compression_level==1:
    259260            proto.enable_lz4()
    260261
    261262        log("process_hello: capabilities=%s", capabilities)
     
    278279            self.disconnect_client(proto, "incompatible version: %s" % verr)
    279280            proto.close()
    280281            return  False
     282
     283        def auth_failed(msg):
     284            log.info("authentication failed: %s", msg)
     285            self.timeout_add(1000, self.disconnect_client, proto, msg)
     286
     287        #authenticator:
     288        username = c.strget("username")
     289        if proto.authenticator is None and self.auth_class:
     290            try:
     291                proto.authenticator = self.auth_class(username)
     292            except Exception, e:
     293                auth_failed("authentication failed")
     294                return False
     295        self.digest_modes = c.get("digest", ("hmac", ))
     296
    281297        #client may have requested encryption:
    282298        cipher = c.strget("cipher")
    283299        cipher_iv = c.strget("cipher.iv")
    284300        key_salt = c.strget("cipher.key_salt")
    285301        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
    294302        auth_caps = {}
    295303        if cipher and cipher_iv:
    296304            if cipher not in ENCRYPTION_CIPHERS:
    297305                log.warn("unsupported cipher: %s", cipher)
    298                 self.send_disconnect(proto, "unsupported cipher")
     306                auth_failed("unsupported cipher")
    299307                return False
    300             proto.set_cipher_out(cipher, cipher_iv, password, key_salt, iterations)
     308            encryption_key = self.get_encryption_key(proto.authenticator)
     309            if encryption_key is None:
     310                auth_failed("encryption key is missing")
     311                return False
     312            proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations)
    301313            #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                          }
     314            auth_caps = new_cipher_caps(proto, cipher, encryption_key)
    312315            log("server cipher=%s", auth_caps)
     316        else:
     317            auth_caps = None
    313318
    314         if self.password_file:
    315             log("password auth required")
     319        #verify authentication if required:
     320        if proto.authenticator:
     321            log("processing authentication with %s", proto.authenticator)
    316322            #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 "")
     323            challenge_response = c.strget("challenge_response")
     324            if not challenge_response:
     325                challenge = proto.authenticator.get_challenge()
     326                if challenge is None:
     327                    auth_failed("invalid authentication state: unexpected challenge response")
     328                    return False
     329                salt, digest = challenge
     330                log.info("Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest)
     331                if digest not in self.digest_modes:
     332                    auth_failed("cannot proceed without %s digest support" % digest)
     333                    return False
     334                proto.send_now(("challenge", salt, auth_caps or "", digest))
    320335                return False
    321             if not self._verify_password(proto, client_hash, password):
     336            if not proto.authenticator.authenticate(challenge_response):
     337                auth_failed("invalid challenge response")
    322338                return False
     339            log("authentication challenge passed")
    323340        return auth_caps
    324341
     342    def get_encryption_key(self, authenticator=None):
     343        #if we have a keyfile specified, use that:
     344        v = None
     345        if self.encryption_keyfile:
     346            log("trying to load encryption key from keyfile: %s", self.encryption_keyfile)
     347            v = load_binary_file(self.encryption_keyfile)
     348        if v is None and authenticator:
     349            log("trying to get encryption key from: %s", authenticator)
     350            v = authenticator.get_password()
     351        if v is None and self.password_file:
     352            log("trying to load encryption key from password file: %s", self.password_file)
     353            v = load_binary_file(self.password_file)
     354        if v is None:
     355            return None
     356        return v.strip("\n\r")
     357
    325358    def hello_oked(self, proto, packet, c, auth_caps):
    326359        pass
    327360
     
    330363        #max packet size from client (the biggest we can get are clipboard packets)
    331364        proto.max_packet_size = 1024*1024  #1MB
    332365        proto.aliases = c.dictget("aliases")
     366        if proto in self._potential_protocols:
     367            self._potential_protocols.remove(proto)
    333368
    334 
    335369    def make_hello(self):
    336370        capabilities = {}
    337371        capabilities["hostname"] = socket.gethostname()
     
    350384        capabilities["elapsed_time"] = int(now - self.start_time)
    351385        capabilities["raw_packets"] = True
    352386        capabilities["chunked_compression"] = True
     387        capabilities["digest"] = ("hmac", "xor")
    353388        capabilities["lz4"] = has_lz4
    354389        capabilities["rencode"] = has_rencode
    355390        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))
  • xpra/x11/xsettings_prop.py

     
    1616
    1717import sys
    1818import struct
    19 from xpra.log import Logger
     19from xpra.log import Logger, debug_if_env
    2020log = Logger()
     21debug = debug_if_env(log, "XPRA_XSETTINGS_DEBUG")
    2122
    22 #debug = log.info
    23 debug = log.debug
    2423
    2524#undocumented XSETTINGS endianess values:
    2625LITTLE_ENDIAN = 0