示例#1
0
文件: ssh.py 项目: gitmirrors2/xpra
def do_ssh_paramiko_connect_to(transport, host, username, password, host_config=None, keyfiles=None, paramiko_config=None):
    from paramiko import SSHException, PasswordRequiredException
    from paramiko.agent import Agent
    from paramiko.hostkeys import HostKeys
    log("do_ssh_paramiko_connect_to%s", (transport, host, username, password, host_config, keyfiles, paramiko_config))
    log("SSH transport %s", transport)

    def configvalue(key):
        #if the paramiko config has a setting, honour it:
        if paramiko_config and key in paramiko_config:
            return paramiko_config.get(key)
        #fallback to the value from the host config:
        return (host_config or {}).get(key)
    def configbool(key, default_value=True):
        return parse_bool(key, configvalue(key), default_value)
    def configint(key, default_value=0):
        v = configvalue(key)
        if v is None:
            return default_value
        return int(v)

    host_key = transport.get_remote_server_key()
    assert host_key, "no remote server key"
    log("remote_server_key=%s", keymd5(host_key))
    if configbool("verify-hostkey", VERIFY_HOSTKEY):
        host_keys = HostKeys()
        host_keys_filename = None
        KNOWN_HOSTS = get_ssh_known_hosts_files()
        for known_hosts in KNOWN_HOSTS:
            host_keys.clear()
            try:
                path = os.path.expanduser(known_hosts)
                if os.path.exists(path):
                    host_keys.load(path)
                    log("HostKeys.load(%s) successful", path)
                    host_keys_filename = path
                    break
            except IOError:
                log("HostKeys.load(%s)", known_hosts, exc_info=True)

        log("host keys=%s", host_keys)
        keys = host_keys.lookup(host)
        known_host_key = (keys or {}).get(host_key.get_name())
        def keyname():
            return host_key.get_name().replace("ssh-", "")
        if host_key==known_host_key:
            assert host_key
            log("%s host key '%s' OK for host '%s'", keyname(), keymd5(host_key), host)
        else:
            dnscheck = ""
            if configbool("verifyhostkeydns"):
                try:
                    from xpra.net.sshfp import check_host_key
                    dnscheck = check_host_key(host, host_key)
                except ImportError as e:
                    log("verifyhostkeydns failed", exc_info=True)
                    log.warn("Warning: cannot check SSHFP DNS records")
                    log.warn(" %s", e)
            log("dnscheck=%s", dnscheck)
            def adddnscheckinfo(q):
                if dnscheck is not True:
                    if dnscheck:
                        q += [
                            "SSHFP validation failed:",
                            dnscheck
                            ]
                    else:
                        q += [
                            "SSHFP validation failed"
                            ]
            if dnscheck is True:
                #DNSSEC provided a matching record
                log.info("found a valid SSHFP record for host %s", host)
            elif known_host_key:
                log.warn("Warning: SSH server key mismatch")
                qinfo = [
"WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!",
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!",
"It is also possible that a host key has just been changed.",
"The fingerprint for the %s key sent by the remote host is" % keyname(),
keymd5(host_key),
]
                adddnscheckinfo(qinfo)
                if configbool("stricthostkeychecking", VERIFY_STRICT):
                    log.warn("Host key verification failed.")
                    #TODO: show alert with no option to accept key
                    qinfo += [
                        "Please contact your system administrator.",
                        "Add correct host key in %s to get rid of this message.",
                        "Offending %s key in %s" % (keyname(), host_keys_filename),
                        "ECDSA host key for %s has changed and you have requested strict checking." % keyname(),
                        ]
                    sys.stderr.write(os.linesep.join(qinfo))
                    transport.close()
                    raise InitExit(EXIT_SSH_KEY_FAILURE, "SSH Host key has changed")
                if not confirm_key(qinfo):
                    transport.close()
                    raise InitExit(EXIT_SSH_KEY_FAILURE, "SSH Host key has changed")

            else:
                assert (not keys) or (host_key.get_name() not in keys)
                if not keys:
                    log.warn("Warning: unknown SSH host")
                else:
                    log.warn("Warning: unknown %s SSH host key", keyname())
                qinfo = [
                    "The authenticity of host '%s' can't be established." % (host,),
                    "%s key fingerprint is" % keyname(),
                    keymd5(host_key),
                    ]
                adddnscheckinfo(qinfo)
                if not confirm_key(qinfo):
                    transport.close()
                    raise InitExit(EXIT_SSH_KEY_FAILURE, "Unknown SSH host '%s'" % host)

            if configbool("addkey", ADD_KEY):
                try:
                    if not host_keys_filename:
                        #the first one is the default,
                        #ie: ~/.ssh/known_hosts on posix
                        host_keys_filename = os.path.expanduser(KNOWN_HOSTS[0])
                    log("adding %s key for host '%s' to '%s'", keyname(), host, host_keys_filename)
                    if not os.path.exists(host_keys_filename):
                        keys_dir = os.path.dirname(host_keys_filename)
                        if not os.path.exists(keys_dir):
                            log("creating keys directory '%s'", keys_dir)
                            os.mkdir(keys_dir, 0o700)
                        elif not os.path.isdir(keys_dir):
                            log.warn("Warning: '%s' is not a directory")
                            log.warn(" key not saved")
                        if os.path.exists(keys_dir) and os.path.isdir(keys_dir):
                            log("creating known host file '%s'", host_keys_filename)
                            with umask_context(0o133):
                                with open(host_keys_filename, 'a+'):
                                    pass
                    host_keys.add(host, host_key.get_name(), host_key)
                    host_keys.save(host_keys_filename)
                except OSError as e:
                    log("failed to add key to '%s'", host_keys_filename)
                    log.error("Error adding key to '%s'", host_keys_filename)
                    log.error(" %s", e)
                except Exception as e:
                    log.error("cannot add key", exc_info=True)
    else:
        log("ssh host key verification skipped")


    def auth_agent():
        agent = Agent()
        agent_keys = agent.get_keys()
        log("agent keys: %s", agent_keys)
        if agent_keys:
            for agent_key in agent_keys:
                log("trying ssh-agent key '%s'", keymd5(agent_key))
                try:
                    transport.auth_publickey(username, agent_key)
                    if transport.is_authenticated():
                        log("authenticated using agent and key '%s'", keymd5(agent_key))
                        break
                except SSHException:
                    log("agent key '%s' rejected", keymd5(agent_key), exc_info=True)
            if not transport.is_authenticated():
                log.info("agent authentication failed, tried %i key%s", len(agent_keys), engs(agent_keys))

    def auth_publickey():
        log("trying public key authentication using %s", keyfiles)
        for keyfile_path in keyfiles:
            if not os.path.exists(keyfile_path):
                log("no keyfile at '%s'", keyfile_path)
                continue
            log("trying '%s'", keyfile_path)
            key = None
            import paramiko
            for pkey_classname in ("RSA", "DSS", "ECDSA", "Ed25519"):
                pkey_class = getattr(paramiko, "%sKey" % pkey_classname, None)
                if pkey_class is None:
                    log("no %s key type", pkey_classname)
                    continue
                log("trying to load as %s", pkey_classname)
                try:
                    key = pkey_class.from_private_key_file(keyfile_path)
                    log.info("loaded %s private key from '%s'", pkey_classname, keyfile_path)
                    break
                except PasswordRequiredException as e:
                    log("%s keyfile requires a passphrase; %s", keyfile_path, e)
                    passphrase = input_pass("please enter the passphrase for %s:" % (keyfile_path,))
                    if passphrase:
                        try:
                            key = pkey_class.from_private_key_file(keyfile_path, passphrase)
                            log.info("loaded %s private key from '%s'", pkey_classname, keyfile_path)
                        except SSHException as e:
                            log("from_private_key_file", exc_info=True)
                            log.info("cannot load key from file '%s':", keyfile_path)
                            log.info(" %s", e)
                    break
                except Exception as e:
                    log("auth_publickey() loading as %s", pkey_classname, exc_info=True)
                    key_data = load_binary_file(keyfile_path)
                    if key_data and key_data.find(b"BEGIN OPENSSH PRIVATE KEY")>=0 and paramiko.__version__<"2.7":
                        log.warn("Warning: private key '%s'", keyfile_path)
                        log.warn(" this file seems to be using OpenSSH's own format")
                        log.warn(" please convert it to something more standard (ie: PEM)")
                        log.warn(" so it can be used with the paramiko backend")
                        log.warn(" or switch to the OpenSSH backend with '--ssh=ssh'")
            if key:
                log("auth_publickey using %s as %s: %s", keyfile_path, pkey_classname, keymd5(key))
                try:
                    transport.auth_publickey(username, key)
                except SSHException as e:
                    log("key '%s' rejected", keyfile_path, exc_info=True)
                    log.info("SSH authentication using key '%s' failed:", keyfile_path)
                    log.info(" %s", e)
                else:
                    if transport.is_authenticated():
                        break
            else:
                log.error("Error: cannot load private key '%s'", keyfile_path)

    def auth_none():
        log("trying none authentication")
        try:
            transport.auth_none(username)
        except SSHException:
            log("auth_none()", exc_info=True)

    def auth_password():
        log("trying password authentication")
        try:
            transport.auth_password(username, password)
        except SSHException as e:
            log("auth_password(..)", exc_info=True)
            log.info("SSH password authentication failed:")
            log.info(" %s", getattr(e, "message", e))

    def auth_interactive():
        log("trying interactive authentication")
        class iauthhandler:
            def __init__(self):
                self.authcount = 0
            def handlestuff(self, _title, _instructions, prompt_list):
                p = []
                for pent in prompt_list:
                    if self.authcount==0 and password:
                        p.append(password)
                    else:
                        p.append(input_pass(pent[0]))
                    self.authcount += 1
                return p
        try:
            myiauthhandler = iauthhandler()
            transport.auth_interactive(username, myiauthhandler.handlestuff, "")
        except SSHException as e:
            log("auth_interactive(..)", exc_info=True)
            log.info("SSH password authentication failed:")
            log.info(" %s", getattr(e, "message", e))

    banner = transport.get_banner()
    if banner:
        log.info("SSH server banner:")
        for x in banner.splitlines():
            log.info(" %s", x)

    if paramiko_config and "auth" in paramiko_config:
        auth = paramiko_config.get("auth", "").split("+")
        AUTH_OPTIONS = ("none", "agent", "key", "password")
        if any(a for a in auth if a not in AUTH_OPTIONS):
            raise InitExit(EXIT_SSH_FAILURE, "invalid ssh authentication module specified: %s" %
                           csv(a for a in auth if a not in AUTH_OPTIONS))
    else:
        auth = []
        if configbool("noneauthentication", NONE_AUTH):
            auth.append("none")
        if password and configbool("passwordauthentication", PASSWORD_AUTH):
            auth.append("password")
        if configbool("agentauthentication", AGENT_AUTH):
            auth.append("agent")
        # Some people do two-factor using KEY_AUTH to kick things off, so this happens first
        if configbool("keyauthentication", KEY_AUTH):
            auth.append("key")
        if not password and configbool("passwordauthentication", PASSWORD_AUTH):
            auth.append("password")
    #def doauth(authtype):
    #    return authtype in auth and not transport.is_authenticated()

    log("starting authentication, authentication methods: %s", auth)
    # per the RFC we probably should do none first always and read off the supported
    # methods, however, the current code seems to work fine with OpenSSH
    while not transport.is_authenticated() and auth:
        a = auth.pop(0)
        log("auth=%s", a)
        if a=="none":
            auth_none()
        elif a=="agent":
            auth_agent()
        elif a=="key":
            auth_publickey()
        elif a=="password":
            auth_interactive()
            if not transport.is_authenticated():
                if password:
                    auth_password()
                else:
                    tries = configint("numberofpasswordprompts", PASSWORD_RETRY)
                    for _ in range(tries):
                        password = input_pass("please enter the SSH password for %s@%s:" % (username, host))
                        if not password:
                            break
                        auth_password()
                        if transport.is_authenticated():
                            break
    if not transport.is_authenticated():
        transport.close()
        raise InitExit(EXIT_CONNECTION_FAILED, "SSH Authentication on %s failed" % host)
示例#2
0
def do_ssh_paramiko_connect_to(transport,
                               host,
                               username,
                               password,
                               host_config=None):
    from paramiko import SSHException, RSAKey, PasswordRequiredException
    from paramiko.agent import Agent
    from paramiko.hostkeys import HostKeys
    log("do_ssh_paramiko_connect_to%s",
        (transport, host, username, password, host_config))
    log("SSH transport %s", transport)

    host_key = transport.get_remote_server_key()
    assert host_key, "no remote server key"
    log("remote_server_key=%s", keymd5(host_key))
    if VERIFY_HOSTKEY:
        host_keys = HostKeys()
        host_keys_filename = None
        KNOWN_HOSTS = get_ssh_known_hosts_files()
        for known_hosts in KNOWN_HOSTS:
            host_keys.clear()
            try:
                path = os.path.expanduser(known_hosts)
                if os.path.exists(path):
                    host_keys.load(path)
                    log("HostKeys.load(%s) successful", path)
                    host_keys_filename = path
                    break
            except IOError:
                log("HostKeys.load(%s)", known_hosts, exc_info=True)

        log("host keys=%s", host_keys)
        keys = host_keys.lookup(host)
        known_host_key = (keys or {}).get(host_key.get_name())

        def keyname():
            return host_key.get_name().replace("ssh-", "")

        if host_key == known_host_key:
            assert host_key
            log("%s host key '%s' OK for host '%s'", keyname(),
                keymd5(host_key), host)
        else:
            dnscheck = ""
            if host_config:
                verifyhostkeydns = host_config.get("verifyhostkeydns")
                if verifyhostkeydns and verifyhostkeydns.lower(
                ) in TRUE_OPTIONS:
                    try:
                        from xpra.net.sshfp import check_host_key
                        dnscheck = check_host_key(host, host_key)
                    except ImportError as e:
                        log("verifyhostkeydns failed", exc_info=True)
                        log.warn("Warning: cannot check SSHFP DNS records")
                        log.warn(" %s", e)
            log.info("dnscheck=%s", dnscheck)

            def adddnscheckinfo(q):
                if dnscheck is not True:
                    q += ["SSHFP validation failed:", dnscheck]

            if dnscheck is True:
                #DNSSEC provided a matching record
                log.info("found a valid SSHFP record for host %s", host)
            elif known_host_key:
                log.warn("Warning: SSH server key mismatch")
                qinfo = [
                    "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
                    "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!",
                    "Someone could be eavesdropping on you right now (man-in-the-middle attack)!",
                    "It is also possible that a host key has just been changed.",
                    "The fingerprint for the %s key sent by the remote host is"
                    % keyname(),
                    keymd5(host_key),
                ]
                adddnscheckinfo(qinfo)
                if VERIFY_STRICT:
                    log.warn("Host key verification failed.")
                    #TODO: show alert with no option to accept key
                    qinfo += [
                        "Please contact your system administrator.",
                        "Add correct host key in %s to get rid of this message.",
                        "Offending %s key in %s" %
                        (keyname(), host_keys_filename),
                        "ECDSA host key for %s has changed and you have requested strict checking."
                        % keyname(),
                    ]
                    sys.stderr.write(os.linesep.join(qinfo))
                    transport.close()
                    raise InitExit(EXIT_SSH_KEY_FAILURE,
                                   "SSH Host key has changed")
                if not confirm_key(qinfo):
                    transport.close()
                    raise InitExit(EXIT_SSH_KEY_FAILURE,
                                   "SSH Host key has changed")

            else:
                assert (not keys) or (host_key.get_name() not in keys)
                if not keys:
                    log.warn("Warning: unknown SSH host")
                else:
                    log.warn("Warning: unknown %s SSH host key", keyname())
                qinfo = [
                    "The authenticity of host '%s' can't be established." %
                    (host, ),
                    "%s key fingerprint is" % keyname(),
                    keymd5(host_key),
                ]
                adddnscheckinfo(qinfo)
                if not confirm_key(qinfo):
                    transport.close()
                    raise InitExit(EXIT_SSH_KEY_FAILURE,
                                   "Unknown SSH host '%s'" % host)

            if ADD_KEY:
                try:
                    if not host_keys_filename:
                        #the first one is the default,
                        #ie: ~/.ssh/known_hosts on posix
                        host_keys_filename = os.path.expanduser(KNOWN_HOSTS[0])
                    log("adding %s key for host '%s' to '%s'", keyname(), host,
                        host_keys_filename)
                    if not os.path.exists(host_keys_filename):
                        keys_dir = os.path.dirname(host_keys_filename)
                        if not os.path.exists(keys_dir):
                            log("creating keys directory '%s'", keys_dir)
                            os.mkdir(keys_dir, 0o700)
                        elif not os.path.isdir(keys_dir):
                            log.warn("Warning: '%s' is not a directory")
                            log.warn(" key not saved")
                        if os.path.exists(keys_dir) and os.path.isdir(
                                keys_dir):
                            log("creating known host file '%s'",
                                host_keys_filename)
                            with umask_context(0o133):
                                with open(host_keys_filename, 'a+'):
                                    pass
                    host_keys.add(host, host_key.get_name(), host_key)
                    host_keys.save(host_keys_filename)
                except OSError as e:
                    log("failed to add key to '%s'", host_keys_filename)
                    log.error("Error adding key to '%s'", host_keys_filename)
                    log.error(" %s", e)
                except Exception as e:
                    log.error("cannot add key", exc_info=True)

    def auth_agent():
        agent = Agent()
        agent_keys = agent.get_keys()
        log("agent keys: %s", agent_keys)
        if agent_keys:
            for agent_key in agent_keys:
                log("trying ssh-agent key '%s'", keymd5(agent_key))
                try:
                    transport.auth_publickey(username, agent_key)
                    if transport.is_authenticated():
                        log("authenticated using agent and key '%s'",
                            keymd5(agent_key))
                        break
                except SSHException:
                    log("agent key '%s' rejected",
                        keymd5(agent_key),
                        exc_info=True)
            if not transport.is_authenticated():
                log.info("agent authentication failed, tried %i key%s",
                         len(agent_keys), engs(agent_keys))

    def auth_publickey():
        log("trying public key authentication")
        for keyfile in ("id_rsa", "id_dsa"):
            keyfile_path = osexpand(os.path.join("~/", ".ssh", keyfile))
            if not os.path.exists(keyfile_path):
                log("no keyfile at '%s'", keyfile_path)
                continue
            key = None
            try:
                key = RSAKey.from_private_key_file(keyfile_path)
            except PasswordRequiredException:
                log("%s keyfile requires a passphrase", keyfile_path)
                passphrase = input_pass("please enter the passphrase for %s:" %
                                        (keyfile_path, ))
                if passphrase:
                    try:
                        key = RSAKey.from_private_key_file(
                            keyfile_path, passphrase)
                    except SSHException as e:
                        log("from_private_key_file", exc_info=True)
                        log.info("cannot load key from file '%s':",
                                 keyfile_path)
                        log.info(" %s", e)
            if key:
                log("auth_publickey using %s: %s", keyfile_path, keymd5(key))
                try:
                    transport.auth_publickey(username, key)
                except SSHException as e:
                    log("key '%s' rejected", keyfile_path, exc_info=True)
                    log.info("SSH authentication using key '%s' failed:",
                             keyfile_path)
                    log.info(" %s", e)
                else:
                    if transport.is_authenticated():
                        break

    def auth_none():
        log("trying none authentication")
        try:
            transport.auth_none(username)
        except SSHException as e:
            log("auth_none()", exc_info=True)

    def auth_password():
        log("trying password authentication")
        try:
            transport.auth_password(username, password)
        except SSHException as e:
            log("auth_password(..)", exc_info=True)
            log.info("SSH password authentication failed: %s", e)

    def auth_interactive():
        log("trying interactive authentication")

        class iauthhandler:
            def __init__(self):
                self.authcount = 0

            def handlestuff(self, title, instructions, prompt_list):
                p = []
                for pent in prompt_list:
                    if self.authcount == 0 and password:
                        p.append(password)
                    else:
                        p.append(input_pass(pent[0]))
                    self.authcount += 1
                return p

        try:
            myiauthhandler = iauthhandler()
            transport.auth_interactive(username, myiauthhandler.handlestuff,
                                       "")
        except SSHException as e:
            log("auth_interactive(..)", exc_info=True)
            log.info("SSH password authentication failed: %s", e)

    banner = transport.get_banner()
    if banner:
        log.info("SSH server banner:")
        for x in banner.splitlines():
            log.info(" %s", x)

    log("starting authentication")
    # per the RFC we probably should do none first always and read off the supported
    # methods, however, the current code seems to work fine with OpenSSH
    if not transport.is_authenticated() and NONE_AUTH:
        auth_none()

    # Some people do two-factor using KEY_AUTH to kick things off, so this happens first
    if not transport.is_authenticated() and KEY_AUTH:
        auth_publickey()

    if not transport.is_authenticated() and PASSWORD_AUTH:
        auth_interactive()

    if not transport.is_authenticated() and PASSWORD_AUTH and password:
        auth_password()

    if not transport.is_authenticated() and AGENT_AUTH:
        auth_agent()

    if not transport.is_authenticated() and PASSWORD_AUTH and not password:
        for _ in range(1 + PASSWORD_RETRY):
            password = input_pass("please enter the SSH password for %s@%s" %
                                  (username, host))
            if not password:
                break
            auth_password()
            if transport.is_authenticated():
                break

    if not transport.is_authenticated():
        transport.close()
        raise InitException("SSH Authentication on %s failed" % host)