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

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

updated patch (broken multiprocessor support..)

  • 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)
  • xpra/client/client_base.py

     
    1818from xpra.scripts.config import ENCRYPTION_CIPHERS, python_platform
    1919from xpra.version_util import version_compat_check, add_version_info
    2020from xpra.platform.features import GOT_PASSWORD_PROMPT_SUGGESTION
    21 from xpra.os_util import get_hex_uuid, get_machine_id, SIGNAMES, strtobytes, bytestostr
     21from xpra.os_util import get_hex_uuid, get_machine_id, load_binary_file, SIGNAMES, strtobytes, bytestostr
    2222from xpra.util import typedict
    2323
    2424EXIT_OK = 0
     
    3434EXIT_MMAP_TOKEN_FAILURE = 10
    3535
    3636DEFAULT_TIMEOUT = 20*1000
     37ALLOW_UNENCRYPTED_PASSWORDS = os.environ.get("XPRA_ALLOW_UNENCRYPTED_PASSWORDS", "0")=="1"
    3738
    3839
    3940class XpraClientBase(object):
     
    5455        self.password_file = None
    5556        self.password_sent = False
    5657        self.encryption = None
     58        self.encryption_keyfile = None
    5759        self.quality = -1
    5860        self.min_quality = 0
    5961        self.speed = 0
     
    8082        self.compression_level = opts.compression_level
    8183        self.password_file = opts.password_file
    8284        self.encryption = opts.encryption
     85        self.encryption_keyfile = opts.encryption_keyfile
    8386        self.quality = opts.quality
    8487        self.min_quality = opts.min_quality
    8588        self.speed = opts.speed
     
    165168            capabilities["cipher.key_salt"] = key_salt
    166169            iterations = 1000
    167170            capabilities["cipher.key_stretch_iterations"] = iterations
    168             self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations)
     171            key = self.get_encryption_key()
     172            if key is None:
     173                self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing")
     174                return
     175            self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations)
    169176            log("encryption capabilities: %s", [(k,v) for k,v in capabilities.items() if k.startswith("cipher")])
    170177        capabilities["platform"] = sys.platform
    171178        capabilities["platform.release"] = python_platform.release()
     
    175182        capabilities["namespace"] = True
    176183        capabilities["raw_packets"] = True
    177184        capabilities["chunked_compression"] = True
     185        capabilities["digest"] = "hmac", "xor"
    178186        capabilities["rencode"] = has_rencode
    179187        capabilities["lz4"] = has_lz4
    180188        if has_rencode:
     
    304312        if self.encryption:
    305313            assert len(packet)>=3, "challenge does not contain encryption details to use for the response"
    306314            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()
     315            key = self.get_encryption_key()
     316            if key is None:
     317                self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing")
     318                return
     319            if not self.set_server_encryption(server_cipher, key):
     320                return
     321        digest = "hmac"
     322        if len(packet)>=4:
     323            digest = packet[3]
     324        if digest=="hmac":
     325            import hmac
     326            challenge_response = hmac.HMAC(self.password, salt).hexdigest()
     327        elif digest=="xor":
     328            #don't send XORed password unencrypted:
     329            if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS:
     330                self.warn_and_quit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest)
     331                return
     332            from xpra.util import xor
     333            challenge_response = xor(self.password, salt)
     334        else:
     335            self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest)
     336            return
    311337        self.password_sent = True
    312         self.send_hello(password_hash)
     338        self.send_hello(challenge_response)
    313339
    314     def set_server_encryption(self, capabilities):
     340    def set_server_encryption(self, capabilities, key):
    315341        def get(key, default=None):
    316342            return capabilities.get(strtobytes(key), default)
    317343        cipher = get("cipher")
     
    324350        if cipher not in ENCRYPTION_CIPHERS:
    325351            self.warn_and_quit(EXIT_ENCRYPTION, "unsupported server cipher: %s, allowed ciphers: %s" % (cipher, ", ".join(ENCRYPTION_CIPHERS)))
    326352            return False
    327         self._protocol.set_cipher_out(cipher, cipher_iv, self.get_password(), key_salt, iterations)
     353        self._protocol.set_cipher_out(cipher, cipher_iv, key, key_salt, iterations)
     354        return True
    328355
    329356
    330     def get_password(self):
    331         if self.password is None:
    332             self.load_password()
    333         return self.password
     357    def get_encryption_key(self):
     358        key = load_binary_file(self.encryption_keyfile)
     359        if key is None and self.password_file:
     360            key = load_binary_file(self.password_file)
     361            if key:
     362                log("used password file as encryption key")
     363        if key is None:
     364            raise Exception("failed to load encryption keyfile %s" % self.encryption_keyfile)
     365        return key.strip("\n\r")
    334366
    335367    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:
     368        filename = os.path.expanduser(self.password_file)
     369        self.password = load_binary_file(filename)
     370        if self.password is None:
    344371            self.warn_and_quit(EXIT_PASSWORD_FILE_ERROR, "failed to open password file %s: %s" % (self.password_file, e))
    345372            return False
     373        self.password = self.password.strip("\n\r")
    346374        log("password read from file %s is %s", self.password_file, self.password)
    347375        return True
    348376
     
    375403        self._protocol.chunked_compression = c.boolget("chunked_compression")
    376404        if use_rencode and c.boolget("rencode"):
    377405            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:
     406        if c.boolget("lz4") and has_lz4 and self._protocol.chunked_compression and self.compression_level==1:
    379407            self._protocol.enable_lz4()
    380408        if self.encryption:
    381409            #server uses a new cipher after second hello:
    382             self.set_server_encryption(c)
     410            key = self.get_encryption_key()
     411            assert key, "encryption key is missing"
     412            if not self.set_server_encryption(c, key):
     413                return False
    383414        self._protocol.aliases = c.dictget("aliases", {})
    384415        if self.pings:
    385416            self.timeout_add(1000, self.send_ping)
  • xpra/dotxpra.py

     
    1919    pass
    2020
    2121class DotXpra(object):
    22     def __init__(self, sockdir=None, confdir=None):
     22    def __init__(self, sockdir=None, confdir=None, actual_username=""):
    2323        from xpra.platform.paths import get_default_socket_dir, get_default_conf_dir
    2424        def expand(s):
     25            if len(actual_username)>0 and s.startswith("~/"):
     26                #replace "~/" with "~$actual_username/"
     27                s = "~%s/%s" % (actual_username, s[2:])
    2528            return os.path.expandvars(os.path.expanduser(s))
    2629        self._confdir = expand(confdir or get_default_conf_dir())
    2730        self._sockdir = expand(sockdir or get_default_socket_dir())
     
    103106            os.unlink(socket_path)
    104107        return socket_path
    105108
    106     def sockets(self):
     109    def sockets(self, check_uid=0):
    107110        results = []
    108111        base = os.path.join(self._sockdir, self._prefix)
    109112        potential_sockets = glob.glob(base + "*")
    110113        for path in potential_sockets:
    111             if stat.S_ISSOCK(os.stat(path).st_mode):
     114            s = os.stat(path)
     115            if stat.S_ISSOCK(s.st_mode):
     116                if check_uid>0:
     117                    if s.st_uid!=check_uid:
     118                        #socket uid does not match
     119                        continue
    112120                local_display = ":" + path[len(base):]
    113121                state = self.server_state(local_display)
    114122                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:
     
    179191        self._encoder = self.bencode
    180192        self._compress = zcompress
    181193        self._decompressor = decompressobj()
    182         self._compression_level = 0
     194        self.compression_level = 0
    183195        self.cipher_in = None
    184196        self.cipher_in_name = None
    185197        self.cipher_in_block_size = 0
     
    193205        self._write_format_thread = make_daemon_thread(self._write_format_thread_loop, "format")
    194206        self._source_has_more = threading.Event()
    195207
     208    STATE_FIELDS = ("max_packet_size", "large_packets", "aliases",
     209                    "chunked_compression",
     210                    "cipher_in", "cipher_in_name", "cipher_in_block_size",
     211                    "cipher_out", "cipher_out_name", "cipher_out_block_size",
     212                    "compression_level")
     213    def save_state(self):
     214        state = {}
     215        for x in Protocol.STATE_FIELDS:
     216            state[x] = getattr(self, x)
     217        state["zcompress"] = self._compress==zcompress
     218        state["lz4"] = lz4_compress and self._compress==lz4_compress
     219        #state["connection"] = self._conn
     220        return state
     221
     222    def restore_state(self, state):
     223        assert state is not None
     224        for x in Protocol.STATE_FIELDS:
     225            assert x in state, "field %s is missing" % x
     226            setattr(self, x, state[x])
     227        if state.get("lz4", False):
     228            self.enable_lz4()
     229
    196230    def set_packet_source(self, get_packet_cb):
    197231        self._get_packet_cb = get_packet_cb
    198232
     
    208242        #stretch the password:
    209243        block_size = 32         #fixme: can we derive this?
    210244        secret = PBKDF2(password, key_salt, dkLen=block_size, count=iterations)
    211         #secret = (password+password+password+password+password+password+password+password)[:32]
    212245        debug("get_cipher(%s, %s, %s) secret=%s, block_size=%s", ciphername, iv, password, secret.encode('hex'), block_size)
    213246        return AES.new(secret, AES.MODE_CBC, iv), block_size
    214247
     
    241274        info[prefix+"output.cipher" + suffix] = self.cipher_out_name or ""
    242275        info[prefix+"chunked_compression" + suffix] = self.chunked_compression
    243276        info[prefix+"large_packets" + suffix] = self.large_packets
    244         info[prefix+"compression_level" + suffix] = self._compression_level
     277        info[prefix+"compression_level" + suffix] = self.compression_level
    245278        info[prefix+"max_packet_size" + suffix] = self.max_packet_size
    246279        for k,v in self.aliases.items():
    247280            info[prefix+"alias." + k + suffix] = v
     
    259292
    260293    def start(self):
    261294        def do_start():
     295            debug("Protocol.do_start() closed=%s", self._closed)
    262296            if not self._closed:
    263297                self._write_thread.start()
    264298                self._read_thread.start()
    265299                self._read_parser_thread.start()
    266300                self._write_format_thread.start()
     301        debug("Protocol.start() will call %s via %s", do_start, scheduler.idle_add)
    267302        scheduler.idle_add(do_start)
    268303
    269304    def send_now(self, packet):
     
    406441        """
    407442        packets = []
    408443        packet = list(packet_in)
    409         level = self._compression_level
     444        level = self.compression_level
    410445        for i in range(1, len(packet)):
    411446            item = packet[i]
    412447            ti = type(item)
     
    455490
    456491    def set_compression_level(self, level):
    457492        #this may be used next time encode() is called
    458         self._compression_level = level
     493        self.compression_level = level
    459494
    460495    def _io_thread_loop(self, name, callback):
    461496        try:
     
    628663                    debug("received %s encrypted bytes with %s padding", payload_size, len(padding))
    629664                    data = self.cipher_in.decrypt(raw_string)
    630665                    if padding:
    631                         def debug_str():
     666                        def debug_str(s):
    632667                            try:
    633                                 return list(bytearray(raw_string))
     668                                return list(bytearray(s))
    634669                            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))
     670                                return list(str(s))
     671                        if not data.endswith(padding):
     672                            log("decryption failed: string does not end with '%s': %s (%s) -> %s (%s)",
     673                            padding, debug_str(raw_string), type(raw_string), debug_str(data), type(data))
     674                            self._connection_lost("encryption error (wrong key?)")
     675                            return
    638676                        data = data[:-len(padding)]
    639677                #uncompress if needed:
    640678                if compression_level>0:
     
    759797        self.terminate_io_threads()
    760798        scheduler.idle_add(self.clean)
    761799
     800    def steal_connection(self):
     801        #so we can re-use this connection somewhere else
     802        #(frees all protocol threads and resources)
     803        assert not self._closed
     804        conn = self._conn
     805        self._closed = True
     806        self._conn = None
     807        self.terminate_io_threads()
     808        scheduler.idle_add(self.clean)
     809        return conn
     810
    762811    def clean(self):
    763812        #clear all references to ensure we can get garbage collected quickly:
    764813        self._get_packet_cb = None
  • 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,
     
    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

     
    301301    group.add_option("--ssh", action="store",
    302302                      dest="ssh", default=defaults.ssh, metavar="CMD",
    303303                      help="How to run ssh (default: '%default')")
     304    group.add_option("--username", action="store",
     305                      dest="username", default=defaults.username,
     306                      help="The username supplied by the client for authentication (default: '%default')")
     307    group.add_option("--auth", action="store",
     308                      dest="auth", default=defaults.auth,
     309                      help="The authentication module (default: '%default')")
    304310    group.add_option("--mmap-group", action="store_true",
    305311                      dest="mmap_group", default=defaults.mmap_group,
    306312                      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')")
     
    318324        group.add_option("--encryption", action="store",
    319325                          dest="encryption", default=defaults.encryption,
    320326                          metavar="ALGO",
    321                           help="Specifies the encryption cipher to use, only %s is currently supported. (default: None)" % (", ".join(ENCRYPTION_CIPHERS)))
     327                          help="Specifies the encryption cipher to use, supported algorithms are: %s (default: None)" % (", ".join(ENCRYPTION_CIPHERS)))
     328        group.add_option("--encryption-keyfile", action="store",
     329                          dest="encryption_keyfile", default=defaults.encryption_keyfile,
     330                          metavar="FILE",
     331                          help="Specifies the file containing the encryption key. (default: '%default')")
    322332    else:
    323333        hidden_options["encryption"] = ''
     334        hidden_options["encryption_keyfile"] = ''
    324335
    325336    options, args = parser.parse_args(cmdline[1:])
    326337    if not args:
     
    367378        assert len(ENCRYPTION_CIPHERS)>0, "cannot use encryption: no ciphers available"
    368379        if options.encryption not in ENCRYPTION_CIPHERS:
    369380            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)
     381        if not options.password_file and not options.encryption_keyfile:
     382            parser.error("encryption %s cannot be used without a keyfile (see --encryption-keyfile option)" % options.encryption)
    372383    #ensure opengl is either True, False or None
    373384    options.opengl = parse_bool("opengl", options.opengl)
    374385
     
    398409
    399410    #configure default logging handler:
    400411    mode = args.pop(0)
    401     if mode in ("start", "upgrade", "attach", "shadow"):
     412    if mode in ("start", "upgrade", "attach", "shadow", "proxy"):
    402413        if show_codec_help("attach" not in cmdline[1:],
    403414                           options.speaker_codec, options.microphone_codec):
    404415            return 0
  • xpra/server/auth/__init__.py

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

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

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

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

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

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

    Property changes on: xpra/server/auth/win32_auth.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    1 # coding=utf8
    21# This file is part of Xpra.
    32# Copyright (C) 2013 Antoine Martin <antoine@devloop.org.uk>
    43# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
    54# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    65# later version. See the file COPYING for details.
    76
     7import os
    88import gobject
    99gobject.threads_init()
    1010
     
    1414from xpra.server.server_core import ServerCore
    1515from xpra.scripts.config import make_defaults_struct
    1616from xpra.scripts.main import parse_display_name, connect_to
    17 from xpra.net.protocol import Protocol
     17from xpra.net.protocol import Protocol, new_cipher_caps
    1818from xpra.net.protocol import set_scheduler
    1919from xpra.os_util import Queue
    2020set_scheduler(gobject)
    2121
    2222
     23PROXY_QUEUE_SIZE = int(os.environ.get("XPRA_PROXY_QUEUE_SIZE", "10"))
     24USE_THREADING = os.environ.get("XPRA_USE_THREADING", "0")=="1"
     25if USE_THREADING:
     26    #use threads
     27    from threading import Thread as Process
     28else:
     29    #use processes:
     30    from multiprocessing import Process
     31
     32
    2333class ProxyServer(ServerCore):
    2434
    2535    def __init__(self):
     36        log("ProxyServer.__init__()")
     37        ServerCore.__init__(self)
    2638        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)
     39        self.processes = []
    3340        self.idle_add = gobject.idle_add
    3441        self.timeout_add = gobject.timeout_add
    3542        self.source_remove = gobject.source_remove
    3643
     44    def init(self, opts):
     45        log("ProxyServer.init(%s)", opts)
     46        if not opts.auth:
     47            raise Exception("The proxy server requires an authentication mode")
     48        ServerCore.init(self, opts)
     49
    3750    def do_run(self):
    3851        self.main_loop = gobject.MainLoop()
    3952        self.main_loop.run()
    4053
    4154    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
     55        for process in self.processes:
     56            process.stop()
     57        self.processes = []
    4758        self.main_loop.quit()
    4859
    4960    def add_listen_socket(self, socktype, sock):
     
    5263        self.socket_types[sock] = socktype
    5364
    5465    def verify_connection_accepted(self, protocol):
    55         pass
     66        #if we start a proxy, the protocol will be closed
     67        #(a new one is created in the proxy process)
     68        if not protocol._closed:
     69            self.send_disconnect(protocol, "connection timeout")
    5670
    5771    def hello_oked(self, proto, packet, c, auth_caps):
     72        self.accept_client(proto, c)
     73        log.info("aliases=%s", proto.aliases)
    5874        if c.boolget("info_request"):
    5975            log.info("sending response to info request")
    6076            self.send_info(proto)
    6177            return
    62         self.start_proxy(proto, packet)
     78        self.start_proxy(proto, c, auth_caps)
    6379
    6480    def send_info(self, proto):
    6581        caps = self.make_hello()
    6682        caps["server_type"] = "proxy"
    6783        proto.send_now(["hello", caps])
    6884
    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"
     85    def start_proxy(self, client_proto, c, auth_caps):
     86        assert client_proto.authenticator is not None
     87        #find the target server session:
     88        def disconnect(msg):
     89            self.send_disconnect(client_proto, msg)
     90        sessions = client_proto.authenticator.get_sessions()
     91        if sessions is None:
     92            disconnect("no sessions found")
     93            return
     94        log.info("start_proxy(%s, {..}, %s) found sessions: %s", client_proto, auth_caps, sessions)
     95        uid, gid, displays, env_options, session_options = sessions       
     96        if len(displays)==0:
     97            disconnect("no displays found")
     98            return
     99        display = c.strget("display")
     100        proxy_virtual_display = os.environ["DISPLAY"]
     101        #ensure we don't loop back to the proxy:
     102        if proxy_virtual_display in displays:
     103            displays.remove(proxy_virtual_display)
     104        if display==proxy_virtual_display:
     105            disconnect("invalid display")
     106            return
     107        if display:
     108            if display not in displays:
     109                disconnect("display not found")
     110                return
     111        else:
     112            if len(displays)!=1:
     113                disconnect("please specify a display (more than one available)")
     114                return
     115            display = displays[0]
     116
     117        log.info("start_proxy(%s, {..}, %s) using server display at: %s", client_proto, auth_caps, display)
     118        client_conn = client_proto.steal_connection()
     119        client_state = client_proto.save_state()
     120        cipher = auth_caps.get("cipher")
     121        encryption_key = None
     122        if cipher:
     123            encryption_key = self.get_encryption_key(client_proto.authenticator)
     124        log.info("start_proxy(%s, {..}) client connection=%s", client_proto, client_conn)
     125        log.info("start_proxy(%s, {..}) client state=%s", client_proto, client_state)
    79126        def parse_error(*args):
    80127            log.warn("parse error on %s: %s", target, args)
     128            raise Exception("parse error on %s: %s" % (target, args))
    81129        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)
     130        disp_desc = parse_display_name(parse_error, opts, display)
     131        log.info("display description(%s) = %s", display, disp_desc)
     132        server_conn = connect_to(disp_desc)
     133        log.info("server connection=%s", server_conn)
     134        process = ProxyProcess(uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, c)
     135        log.info("starting %s from pid=%s", process, os.getpid())
     136        process.start()
     137        #FIXME: remove old processes
     138        self.processes.append(process)
     139        #now we can close our handle on the connection:
     140        #client_conn.close()
     141        #server_conn.close()
     142
     143
     144class ProxyProcess(Process):
     145
     146    def __init__(self, uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, caps):
     147        import gobject
     148        gobject.threads_init()
     149        set_scheduler(gobject)
     150        Process.__init__(self, name=str(client_conn))
     151        self.uid = uid
     152        self.gid = gid
     153        self.client_conn = client_conn
     154        self.client_state = client_state
     155        self.cipher = cipher
     156        self.encryption_key = encryption_key
     157        self.server_conn = server_conn
     158        self.caps = caps
     159        log.info("ProxyProcess%s pid=%s", (uid, gid, client_conn, client_state, cipher, encryption_key, server_conn, "{..}"), os.getpid())
     160        self.client_protocol = None
     161        self.server_protocol = None
     162        #import logging
     163        #logging.basicConfig(format="%(asctime)s %(message)s")
     164        #logging.root.setLevel(logging.DEBUG)
     165
     166    def run(self):
     167        log.info("ProxyProcess.run() pid=%s", os.getpid())
     168        #change uid and gid:
     169        if os.getuid()!=self.uid:
     170            os.setuid(uid)
     171        if os.getgid()!=self.gid:
     172            os.setgid(gid)
     173
     174        #setup protocol wrappers:
     175        self.client_to_server = Queue(PROXY_QUEUE_SIZE)
     176        self.server_to_client = Queue(PROXY_QUEUE_SIZE)
     177        self.client_protocol = Protocol(self.client_conn, self.process_client_packet, self.get_client_packet)
     178        self.client_protocol.restore_state(self.client_state)
     179        self.server_protocol = Protocol(self.server_conn, self.process_server_packet, self.get_server_packet)
     180
     181        #server connection tweaks:
    88182        self.server_protocol.large_packets.append("keymap-changed")
    89183        self.server_protocol.large_packets.append("server-settings")
    90184        self.server_protocol.set_compression_level(0)
     185
    91186        self.server_protocol.start()
     187        self.client_protocol.start()
     188
    92189        #forward the hello packet:
    93         self.client_to_server.put(packet)
     190        #FIXME: need to remove auth and encoding params
     191        #hello_packet["encodings"]=("rgb", )
     192        pcaps = {}
     193        for k,v in self.caps.items():
     194            if not k.startswith("cipher") and not k.startswith("mmap") and not k.startswith("aliases"):
     195                pcaps[k] = v
     196        pcaps["proxy"] = True
     197        #client expects a new cipher iv:
     198        hello_packet = ("hello", pcaps)
     199        self.client_to_server.put(hello_packet)
    94200        self.server_protocol.source_has_more()
    95201
     202        #wait for threads to finish:
     203        #all_threads = self.client_protocol.get_threads() + self.server_protocol.get_threads()
     204        #or just be lazy and use a main loop:
     205        main_loop = gobject.MainLoop()
     206        log.info("ProxyProcess.run() starting main loop")
     207        main_loop.run()
     208        log.info("ProxyProcess.run() main loop ended")
     209
     210    def stop(self):
     211        log.info("stop()")
     212        for proto in (self.client_protocol, self.server_protocol):
     213            if proto:
     214                proto.flush_then_close(["disconnect", "proxy terminating"])
     215
    96216    def get_server_packet(self):
    97217        #server wants a packet
    98         return self.client_to_server.get(),
     218        p = self.client_to_server.get()
     219        log.info("forwarding server packet %s", p[0])
     220        #auth_caps = new_cipher_caps(self.client_protocol, cipher, encryption_key)
     221        #pcaps.update(auth_caps)
     222        return p,
    99223
    100224    def get_client_packet(self):
    101225        #server wants a packet
     
    103227
    104228    def process_server_packet(self, proto, packet):
    105229        log.info("process_server_packet: %s", packet[0])
     230        log.info("process_server_packet: %s", packet)
    106231        #forward  packet received from server to the client
    107232        self.server_to_client.put(packet)
    108233        self.client_protocol.source_has_more()
    109234
    110235    def process_client_packet(self, proto, packet):
    111236        log.info("process_client_packet: %s", packet[0])
     237        log.info("process_client_packet: %s", packet)
    112238        #forward  packet received from client to the server
    113239        self.client_to_server.put(packet)
    114240        self.server_protocol.source_has_more()
  • xpra/server/server_base.py

     
    618618        info["server.python.full_version"] = sys.version
    619619        info["server.python.version"] = sys.version_info[:3]
    620620        info["session.name"] = self.session_name or ""
    621         info["features.password_file"] = self.password_file or ""
     621        info["features.authenticator"] = str((self.auth_class or str)(""))
    622622        info["features.randr"] = self.randr
    623623        info["features.cursors"] = self.cursors
    624624        info["features.bell"] = self.bell
  • xpra/server/server_core.py

     
    99import types
    1010import os.path
    1111import sys
    12 import hmac
    1312import time
    1413import socket
    1514import signal
     
    2120from xpra.scripts.config import ENCRYPTION_CIPHERS, python_platform
    2221from xpra.scripts.server import deadly_signal
    2322from xpra.net.bytestreams import SocketConnection
    24 from xpra.os_util import set_application_name, get_hex_uuid, SIGNAMES
     23from xpra.os_util import set_application_name, load_binary_file, SIGNAMES
    2524from xpra.version_util import version_compat_check, add_version_info
    26 from xpra.net.protocol import Protocol, has_rencode, has_lz4, rencode_version, use_rencode
     25from xpra.net.protocol import Protocol, has_rencode, has_lz4, rencode_version, use_rencode, new_cipher_caps
    2726from xpra.util import typedict
    2827
    2928
     
    3938    def __init__(self):
    4039        log("ServerCore.__init__()")
    4140        self.start_time = time.time()
     41        self.auth_class = None
    4242
    4343        self._upgrading = False
    4444        #networking bits:
     
    5050        self.session_name = "Xpra"
    5151
    5252        #Features:
     53        self.digest_modes = ("hmac", )
     54        self.encryption_keyfile = None
     55        self.password_file = None
    5356        self.compression_level = 1
    54         self.password_file = ""
    5557
    5658        self.init_packet_handlers()
    5759        self.init_aliases()
     
    7072        self.session_name = opts.session_name
    7173        set_application_name(self.session_name)
    7274
     75        self.encryption_keyfile = opts.encryption_keyfile
     76        self.password_file = opts.password_file
    7377        self.compression_level = opts.compression_level
    74         self.password_file = opts.password_file
    7578
     79        self.init_auth(opts)
     80
     81    def init_auth(self, opts):
     82        auth = opts.auth
     83        if not auth and opts.password_file:
     84            log.warn("no authentication module specified with 'password_file', using 'file' based authentication")
     85            auth = "file"
     86        if auth=="":
     87            return
     88        elif auth=="file":
     89            from xpra.server.auth import file_auth
     90            auth_module = file_auth
     91        elif auth=="pam":
     92            from xpra.server.auth import pam_auth
     93            auth_module = pam_auth
     94        elif auth=="win32":
     95            from xpra.server.auth import win32_auth
     96            auth_module = win32_auth
     97        else:
     98            raise Exception("invalid auth module: %s" % auth)
     99        try:
     100            auth_module.init(opts)
     101        except Exception, e:
     102            raise Exception("failed to initialize %s module: %s" % (auth_module, e))
     103        try:
     104            self.auth_class = getattr(auth_module, "Authenticator")
     105        except Exception, e:
     106            raise Exception("Authenticator class not found in %s" % auth_module)
     107
    76108    def init_sockets(self, sockets):
    77109        ### All right, we're ready to accept customers:
    78110        for socktype, sock in sockets:
     
    169201        log.info("New connection received: %s", sc)
    170202        protocol = Protocol(sc, self.process_packet)
    171203        protocol.large_packets.append("info-response")
    172         protocol.salt = None
    173204        protocol.set_compression_level(self.compression_level)
     205        protocol.authenticator = None
    174206        self._potential_protocols.append(protocol)
    175207        protocol.start()
    176208        self.timeout_add(10*1000, self.verify_connection_accepted, protocol)
     
    208240        self.disconnect_client(proto, "invalid packet format")
    209241
    210242
    211     def _send_password_challenge(self, proto, server_cipher):
    212         proto.salt = get_hex_uuid()
    213         log.info("Password required, sending challenge")
    214         proto.send_now(("challenge", proto.salt, server_cipher))
    215 
    216     def _verify_password(self, proto, client_hash, password):
    217         salt = proto.salt
    218         proto.salt = None
    219         if not salt:
    220             self.send_disconnect(proto, "illegal challenge response received - salt cleared or unset")
    221             return False
    222         password_hash = hmac.HMAC(password, salt)
    223         if client_hash != password_hash.hexdigest():
    224             def login_failed(*args):
    225                 log.error("Password supplied does not match! dropping the connection.")
    226                 self.send_disconnect(proto, "invalid password")
    227             self.timeout_add(1000, login_failed)
    228             return False
    229         log.info("Password matches!")
    230         sys.stdout.flush()
    231         return True
    232 
    233     def get_password(self):
    234         if not self.password_file:
    235             return None
    236         filename = os.path.expanduser(self.password_file)
    237         if not filename:
    238             return None
    239         try:
    240             passwordFile = open(filename, "rU")
    241             password  = passwordFile.read()
    242             passwordFile.close()
    243             while len(password)>0 and password[-1] in ("\n", "\r"):
    244                 password = password[:-1]
    245             return password
    246         except IOError, e:
    247             log.error("cannot open password file %s: %s", filename, e)
    248             return None
    249 
    250 
    251243    def _process_hello(self, proto, packet):
    252244        capabilities = packet[1]
    253245        c = typedict(capabilities)
     
    255247        proto.chunked_compression = c.boolget("chunked_compression")
    256248        if use_rencode and c.boolget("rencode"):
    257249            proto.enable_rencode()
    258         if c.boolget("lz4") and has_lz4 and proto.chunked_compression and self.compression_level>0 and self.compression_level<3:
     250        if c.boolget("lz4") and has_lz4 and proto.chunked_compression and self.compression_level==1:
    259251            proto.enable_lz4()
    260252
    261253        log("process_hello: capabilities=%s", capabilities)
     
    278270            self.disconnect_client(proto, "incompatible version: %s" % verr)
    279271            proto.close()
    280272            return  False
     273
     274        #authenticator:
     275        username = c.strget("username")
     276        if proto.authenticator is None and self.auth_class:
     277            proto.authenticator = self.auth_class(username)
     278        self.digest_modes = c.get("digest", ("hmac", ))
     279
    281280        #client may have requested encryption:
    282281        cipher = c.strget("cipher")
    283282        cipher_iv = c.strget("cipher.iv")
    284283        key_salt = c.strget("cipher.key_salt")
    285284        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
    294285        auth_caps = {}
    295286        if cipher and cipher_iv:
    296287            if cipher not in ENCRYPTION_CIPHERS:
    297288                log.warn("unsupported cipher: %s", cipher)
    298289                self.send_disconnect(proto, "unsupported cipher")
    299290                return False
    300             proto.set_cipher_out(cipher, cipher_iv, password, key_salt, iterations)
     291            encryption_key = self.get_encryption_key(proto.authenticator)
     292            if encryption_key is None:
     293                self.send_disconnect(proto, "encryption key is missing")
     294                return False
     295            proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations)
    301296            #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                          }
     297            auth_caps = new_cipher_caps(proto, cipher, encryption_key)
    312298            log("server cipher=%s", auth_caps)
     299        else:
     300            auth_caps = None
    313301
    314         if self.password_file:
    315             log("password auth required")
     302        #verify authentication if required:
     303        if proto.authenticator:
     304            log("processing authentication with %s", proto.authenticator)
    316305            #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 "")
     306            challenge_response = c.strget("challenge_response")
     307            if not challenge_response:
     308                challenge = proto.authenticator.get_challenge()
     309                if challenge is None:
     310                    self.timeout_add(1000, self.send_disconnect, proto, "invalid authentication state: unexpected challenge response")
     311                    return False
     312                log.info("Authentication required, sending challenge")
     313                salt, digest = challenge
     314                if digest not in self.digest_modes:
     315                    self.send_disconnect(proto, "cannot proceed without %s digest support" % digest)
     316                    return False
     317                proto.send_now(("challenge", salt, auth_caps or "", digest))
    320318                return False
    321             if not self._verify_password(proto, client_hash, password):
     319            if not proto.authenticator.authenticate(challenge_response):
     320                self.timeout_add(1000, self.send_disconnect, proto, "authentication failed")
    322321                return False
     322            log("authentication challenge passed")
    323323        return auth_caps
    324324
     325    def get_encryption_key(self, authenticator=None):
     326        #if we have a keyfile specified, use that:
     327        v = None
     328        if self.encryption_keyfile:
     329            log("trying to load encryption key from keyfile: %s", self.encryption_keyfile)
     330            v = load_binary_file(self.encryption_keyfile)
     331        if v is None and authenticator:
     332            log("trying to get encryption key from: %s", authenticator)
     333            v = authenticator.get_password()
     334        if v is None and self.password_file:
     335            log("trying to load encryption key from password file: %s", self.password_file)
     336            v = load_binary_file(self.password_file)
     337        if v is None:
     338            return None
     339        return v.strip("\n\r")
     340
    325341    def hello_oked(self, proto, packet, c, auth_caps):
    326342        pass
    327343
     
    350366        capabilities["elapsed_time"] = int(now - self.start_time)
    351367        capabilities["raw_packets"] = True
    352368        capabilities["chunked_compression"] = True
     369        capabilities["digest"] = ("hmac", "xor")
    353370        capabilities["lz4"] = has_lz4
    354371        capabilities["rencode"] = has_rencode
    355372        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))