def start_server(authkey='PublicKey', address=None, port=0, prefix='server',
                 allowed_hosts=None, allowed_users=None, allow_shell=False,
                 allowed_types=None, timeout=None, tunnel=False,
                 resources=None, log_prefix=None):
    """
    Start an :class:`ObjServerFactory` service in a separate process
    in the current directory.

    authkey: string
        Authorization key, must be matched by clients.

    address: string
        IPv4 address, hostname, or pipe name.
        Default is the host's default IPv4 address.

    port: int
        Server port (default of 0 implies next available port).
        Note that ports below 1024 typically require special privileges.
        If port is negative, then a local pipe is used for communication.

    prefix: string
        Prefix for server config file and stdout/stderr file.

    allowed_hosts: list(string)
        Host address patterns to check against. Required if `port` >= 0.
        Ignored if `allowed_users` is specified.

    allowed_users: dict
        Dictionary of users and corresponding public keys allowed access.
        If None, *any* user may access. If empty, no user may access.
        The host portions of user strings are used for address patterns.

    allow_shell: bool
        If True, :meth:`execute_command` and :meth:`load_model` are allowed.
        Use with caution!

    allowed_types: list(string)
        Names of types which may be created. If None, then allow types listed
        by :meth:`get_available_types`. If empty, no types are allowed.

    timeout: int
        Seconds to wait for server to start. Note that public key generation
        can take a while. The default value of None will use an internally
        computed value based on host type (and for Windows, the availability
        of pyWin32).

    tunnel: bool
        If True, report host IP address but listen for connections from a
        local SSH tunnel.

    resources: string
        Filename for resource configuration.

    log_prefix: string
        Name used to identify remote remote logging messages from server.
        Implies that the local process will be receiving the messages.

    Returns ``(server_proc, config_filename)``.
    """
    if timeout is None:
        if sys.platform == 'win32' and not HAVE_PYWIN32:  #pragma no cover
            timeout = 120
        else:
            timeout = 30

    server_key = prefix+'.key'
    server_cfg = prefix+'.cfg'
    server_out = prefix+'.out'
    for path in (server_cfg, server_out):
        if os.path.exists(path):
            os.remove(path)

    with open(server_key, 'w') as out:
        out.write('%s\n' % authkey)

    factory_path = pkg_resources.resource_filename('openmdao.main',
                                                   'objserverfactory.py')
    args = ['python', factory_path, '--port', str(port), '--prefix', prefix]

    if address is not None:
        args.extend(['--address', address])

    if tunnel:
        args.append('--tunnel')

    if resources is not None:
        args.append('--resources')
        args.append(resources)

    if allowed_users is not None:
        write_authorized_keys(allowed_users, 'users.allow', logging.getLogger())
        args.extend(['--users', 'users.allow'])
    else:
        args.append('--allow-public')
        if port >= 0:
            if allowed_hosts is None:
                allowed_hosts = [socket.gethostbyname(socket.gethostname())]
                if allowed_hosts[0].startswith('127.') and \
                   '127.0.0.1' not in allowed_hosts:
                    allowed_hosts.append('127.0.0.1')
            with open('hosts.allow', 'w') as out:
                for pattern in allowed_hosts:
                    out.write('%s\n' % pattern)
            if sys.platform != 'win32' or HAVE_PYWIN32:
                make_private('hosts.allow')
            else:  #pragma no cover
                logging.warning("Can't make hosts.allow private")

    if allow_shell:
        args.append('--allow-shell')

    if allowed_types is not None:
        with open('types.allow', 'w') as out:
            for typname in allowed_types:
                out.write('%s\n' % typname)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('types.allow')
        else:  #pragma no cover
            logging.warning("Can't make types.allow private")
        args.extend(['--types', 'types.allow'])

    if log_prefix is not None:
        log_host = socket.gethostname()
        log_port = logging_port(log_host, log_host)
        args.extend(['--log-host', log_host, '--log-port', str(log_port)])
        if log_prefix:  # Could be null (for default).
            args.extend(['--log-prefix', log_prefix])

    proc = ShellProc(args, stdout=server_out, stderr=STDOUT)

    try:
        # Wait for valid server_cfg file.
        retry = 0
        while (not os.path.exists(server_cfg)) or \
              (os.path.getsize(server_cfg) == 0):
            return_code = proc.poll()
            if return_code:
                error_msg = proc.error_message(return_code)
                raise RuntimeError('Server startup failed %s' % error_msg)
            retry += 1
            if retry < 10*timeout:
                time.sleep(.1)
            # Hard to cause a startup timeout.
            else:  #pragma no cover
                proc.terminate(timeout)
                raise RuntimeError('Server startup timeout')
        return (proc, server_cfg)
    finally:
        if os.path.exists(server_key):
            os.remove(server_key)
    def test_allowed_hosts(self):
        logging.debug('')
        logging.debug('test_allowed_hosts')

        hostname = socket.gethostname()
        host_ipv4 = socket.gethostbyname(hostname)
        dot = host_ipv4.rfind('.')
        domain_ipv4 = host_ipv4[:dot+1]

        good_data = """
# Local host IPv4.
%s

# Local domain IPv4.
%s

# Local host name.
%s

""" % (host_ipv4, domain_ipv4, hostname)

        gibberish = '$^&*'
        bad_data = good_data + """
# Gibberish.
%s
""" % gibberish

        # Try good data.
        with open('hosts.allow', 'w') as out:
            out.write(good_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('hosts.allow')
        try:
            allowed_hosts = read_allowed_hosts('hosts.allow')
        finally:
            os.remove('hosts.allow')
        self.assertEqual(len(allowed_hosts), 3)
        self.assertEqual(allowed_hosts[0], host_ipv4)
        self.assertEqual(allowed_hosts[1], domain_ipv4)
        self.assertEqual(allowed_hosts[2], host_ipv4)

        # Try bad data.
        with open('hosts.allow', 'w') as out:
            out.write(bad_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('hosts.allow')
        try:
            allowed_hosts = read_allowed_hosts('hosts.allow')
        except RuntimeError as exc:
            self.assertEqual(str(exc),
                             "1 errors in 'hosts.allow', check log for details")
        else:
            expected = 3
            try:
                # This actally resolves in some environments.
                socket.gethostbyname(gibberish)
            except socket.gaierror:
                pass
            else:
                expected += 1
            self.assertEqual(len(allowed_hosts), expected)
            self.assertEqual(allowed_hosts[0], host_ipv4)
            self.assertEqual(allowed_hosts[1], domain_ipv4)
            self.assertEqual(allowed_hosts[2], host_ipv4)
        finally:
            os.remove('hosts.allow')

        # Check AF_INET addresses.
        logger = logging.getLogger()
        self.assertTrue(is_legal_connection((host_ipv4, 0),
                                            allowed_hosts, logger))
        domain_host = domain_ipv4 + '123'
        self.assertTrue(is_legal_connection((domain_host, 0),
                                            allowed_hosts, logger))
        self.assertFalse(is_legal_connection(('0.0.0.0', 0),
                                            allowed_hosts, logger))

        # Check AF_UNIX address.
        self.assertTrue(is_legal_connection('/tmp/pipe',
                                            allowed_hosts, logger))
        # Try nonexistant file.
        assert_raises(self, "read_allowed_hosts('no-such-file')",
                      globals(), locals(), RuntimeError,
                      "'no-such-file' does not exist")

        # Try insecure file.
        if sys.platform != 'win32' or HAVE_PYWIN32:
            with open('hosts.allow', 'w') as out:
                out.write('\n')
            os.chmod('hosts.allow', 0666)
            try:
                assert_raises(self, "read_allowed_hosts('hosts.allow')",
                              globals(), locals(), RuntimeError,
                              "'hosts.allow' is not private")
            finally:
                os.remove('hosts.allow')
def start_server(authkey='PublicKey',
                 address=None,
                 port=0,
                 prefix='server',
                 allowed_hosts=None,
                 allowed_users=None,
                 allow_shell=False,
                 allowed_types=None,
                 timeout=None,
                 tunnel=False,
                 resources=None,
                 log_prefix=None):
    """
    Start an :class:`ObjServerFactory` service in a separate process
    in the current directory.

    authkey: string
        Authorization key; must be matched by clients.

    address: string
        IPv4 address, hostname, or pipe name.
        Default is the host's default IPv4 address.

    port: int
        Server port (default of 0 implies next available port).
        Note that ports below 1024 typically require special privileges.
        If port is negative, then a local pipe is used for communication.

    prefix: string
        Prefix for server config file and stdout/stderr file.

    allowed_hosts: list(string)
        Host address patterns to check against. Required if `port` >= 0.
        Ignored if `allowed_users` is specified.

    allowed_users: dict
        Dictionary of users and corresponding public keys allowed access.
        If None, *any* user may access. If empty, no user may access.
        The host portions of user strings are used for address patterns.

    allow_shell: bool
        If True, :meth:`execute_command` and :meth:`load_model` are allowed.
        Use with caution!

    allowed_types: list(string)
        Names of types which may be created. If None, then allow types listed
        by :meth:`get_available_types`. If empty, no types are allowed.

    timeout: int
        Seconds to wait for server to start. Note that public key generation
        can take a while. The default value of None will use an internally
        computed value based on host type (and for Windows, the availability
        of pyWin32).

    tunnel: bool
        If True, report host IP address but listen for connections from a
        local SSH tunnel.

    resources: string
        Filename for resource configuration.

    log_prefix: string
        Name used to identify remote remote logging messages from server.
        Implies that the local process will be receiving the messages.

    Returns ``(server_proc, config_filename)``.
    """
    if timeout is None:
        if sys.platform == 'win32' and not HAVE_PYWIN32:  # pragma no cover
            timeout = 120
        else:
            timeout = 30

    server_key = prefix + '.key'
    server_cfg = prefix + '.cfg'
    server_out = prefix + '.out'
    for path in (server_cfg, server_out):
        if os.path.exists(path):
            os.remove(path)

    with open(server_key, 'w') as out:
        out.write('%s\n' % authkey)

    factory_path = pkg_resources.resource_filename('openmdao.main',
                                                   'objserverfactory.py')
    args = ['python', factory_path, '--port', str(port), '--prefix', prefix]

    if address is not None:
        args.extend(['--address', address])

    if tunnel:
        args.append('--tunnel')

    if resources is not None:
        args.append('--resources')
        args.append(resources)

    if allowed_users is not None:
        write_authorized_keys(allowed_users, 'users.allow',
                              logging.getLogger())
        args.extend(['--users', 'users.allow'])
    else:
        args.append('--allow-public')
        if port >= 0:
            if allowed_hosts is None:
                allowed_hosts = [socket.gethostbyname(socket.gethostname())]
                if allowed_hosts[0].startswith('127.') and \
                                '127.0.0.1' not in allowed_hosts:
                    allowed_hosts.append('127.0.0.1')
            with open('hosts.allow', 'w') as out:
                for pattern in allowed_hosts:
                    out.write('%s\n' % pattern)
            if sys.platform != 'win32' or HAVE_PYWIN32:
                make_private('hosts.allow')
            else:  # pragma no cover
                logging.warning("Can't make hosts.allow private")

    if allow_shell:
        args.append('--allow-shell')

    if allowed_types is not None:
        with open('types.allow', 'w') as out:
            for typname in allowed_types:
                out.write('%s\n' % typname)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('types.allow')
        else:  # pragma no cover
            logging.warning("Can't make types.allow private")
        args.extend(['--types', 'types.allow'])

    if log_prefix is not None:
        log_host = socket.gethostname()
        log_port = logging_port(log_host, log_host)
        args.extend(['--log-host', log_host, '--log-port', str(log_port)])
        if log_prefix:  # Could be null (for default).
            args.extend(['--log-prefix', log_prefix])

    proc = ShellProc(args, stdout=server_out, stderr=STDOUT)

    try:
        # Wait for valid server_cfg file.
        retry = 0
        while (not os.path.exists(server_cfg)) or \
                (os.path.getsize(server_cfg) == 0):
            return_code = proc.poll()
            if return_code:
                error_msg = proc.error_message(return_code)
                raise RuntimeError('Server startup failed %s' % error_msg)
            retry += 1
            if retry < 10 * timeout:
                time.sleep(.1)
            # Hard to cause a startup timeout.
            else:  # pragma no cover
                proc.terminate(timeout)
                raise RuntimeError('Server startup timeout')
        return (proc, server_cfg)
    finally:
        if os.path.exists(server_key):
            os.remove(server_key)
    def test_authorized_keys(self):
        logging.debug('')
        logging.debug('test_authorized_keys')

        # Try various line formats.
        hostname = socket.gethostname()

        good_key_data = """
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAt9gTm9qX3pKOvFbn8vdkWL/W4kAdtNxRQQXO6QXX7ihuYxv09ZMuqkFPCD1ZxwZNZG0BYstSytPyYQDAGbOglmsjfQ0PRtwDLvK4utGiGLuRsf8ig/cS8NDfSJ/I1B+DBlV1uMaGmzamsFDsavv4Qxf/X50Fl5JTBiPp9W17xkz+JyDCsNMaQd2iSx+GjLbxT/QG2xM9/qrF8bQAAMLdNoKHwVNW+lLXyww6YI9pPj7Tep/dg3xk5Ggf5L6eJGRzmJVMYSfFK+TIX4r49SNddo3Vy/K2H02Yxu6dIBXUTwa+AUC+Mfh5LisAJiM/Oj4NBngWVRgDjN9NH6nQD08R8Q== user1@%(host)s
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAv7QM8MwxkX9yCKIebEH0o14b6Uik3KZnkQo2uF0NyuzDeeZntFym7v0mx7HV4KncjA5Ix2UBw4VKB2virDInO/YKYOC3ZqEJH/CvJkBFggPaZyJyzrEname0+NRXg+PnB2yIDKH0dpwEKVDkwAhEaAqcb9xoahEgXmd4kOmNGylJcwAJhSNqAC9BJO+gAdukGmKodM3nkwKo1BJc2ozqoYar8MYH/FQK8GPBOp4w2LHlm2yXuPB/dqd9/b9N4/ivf5LEthNMn1AnLS37tZIbQ4rSaxLGb72p0iBHSM5oHh1JKDn3mGDKIGxR1cxQ6PuuH6wNB5giNU9U76M4y2QGvw== user2@%(host)s

# another user entry
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user3@%(host)s

""" % {'host': hostname}

        bad_key_data = good_key_data + """
# missing host
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user4

# insufficient fields
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBalxS0OHm1J3QB7WjtInEhqdMO7cqjqw0yVCfHqb8VU/nXJWQZPJAom8ey3uYWqrjVKuHPSgEaqqtJxwVIeJ5oBDOQbAT9WY4n7+mx8I+bhpdVsZQvtQE3BUgYh0/GUbRgSx+F/1efrwcRHCRb9QO+9DrIg1q2NeY6OR2bSiYA5Q==

# too many fields
command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user5@%(host)s

# unsupported key type
ssh-dsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user6@%(host)s

# munged data
ssh-rsa ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user7@%(host)s
""" % {'host': hostname}

        with open('key_data', 'w') as out:
            out.write(good_key_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('key_data')
        try:
            keys = read_authorized_keys('key_data', logging.getLogger())
            for name, key in keys.items():
                logging.debug('    %s: %r', name, key)
            self.assertEqual(sorted(keys.keys()),
                             ['user1@'+hostname,
                              'user2@'+hostname,
                              'user3@'+hostname])
        finally:
            os.remove('key_data')

        # Write and read-back.
        key_file = 'users.allow'
        try:
            write_authorized_keys(keys, key_file)
            if sys.platform != 'win32' or HAVE_PYWIN32:
                self.assertTrue(is_private(key_file))
                new_keys = read_authorized_keys(key_file)
                self.assertEqual(len(keys), len(new_keys))
                for user in sorted(keys.keys()):
                    pubkey = keys[user]
                    try:
                        new_pubkey = new_keys[user]
                    except KeyError:
                        self.fail('new_keys is missing %r', user)
                    self.assertEqual(new_pubkey.n, pubkey.n)
                    self.assertEqual(new_pubkey.e, pubkey.e)
        finally:
            if os.path.exists(key_file):
                os.remove(key_file)

        # Try default file, which may or may not exist.
        try: 
            keys = read_authorized_keys(logger=logging.getLogger())
        except RuntimeError:
            pass

        # Try nonexistent file.
        code = "read_authorized_keys('key_data', logging.getLogger())"
        assert_raises(self, code, globals(), locals(), RuntimeError,
                      "'key_data' does not exist")

        # Try insecure file.
        if sys.platform != 'win32':
            with open('key_data', 'w') as out:
                out.write(good_key_data)
            os.chmod('key_data', 0666)
            try: 
                code = "read_authorized_keys('key_data', logging.getLogger())"
                assert_raises(self, code, globals(), locals(), RuntimeError,
                              "'key_data' is not private")
            finally:
                os.remove('key_data')

        # Try bad file.
        with open('key_data', 'w') as out:
            out.write(bad_key_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('key_data')
        try:
            code = "read_authorized_keys('key_data', logging.getLogger())"
            assert_raises(self, code, globals(), locals(), RuntimeError,
                          "5 errors in 'key_data', check log for details")
        finally:
            os.remove('key_data')
    def test_authorized_keys(self):
        logging.debug('')
        logging.debug('test_authorized_keys')

        # Try various line formats.
        hostname = socket.gethostname()

        good_key_data = """
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAt9gTm9qX3pKOvFbn8vdkWL/W4kAdtNxRQQXO6QXX7ihuYxv09ZMuqkFPCD1ZxwZNZG0BYstSytPyYQDAGbOglmsjfQ0PRtwDLvK4utGiGLuRsf8ig/cS8NDfSJ/I1B+DBlV1uMaGmzamsFDsavv4Qxf/X50Fl5JTBiPp9W17xkz+JyDCsNMaQd2iSx+GjLbxT/QG2xM9/qrF8bQAAMLdNoKHwVNW+lLXyww6YI9pPj7Tep/dg3xk5Ggf5L6eJGRzmJVMYSfFK+TIX4r49SNddo3Vy/K2H02Yxu6dIBXUTwa+AUC+Mfh5LisAJiM/Oj4NBngWVRgDjN9NH6nQD08R8Q== user1@%(host)s
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAv7QM8MwxkX9yCKIebEH0o14b6Uik3KZnkQo2uF0NyuzDeeZntFym7v0mx7HV4KncjA5Ix2UBw4VKB2virDInO/YKYOC3ZqEJH/CvJkBFggPaZyJyzrEname0+NRXg+PnB2yIDKH0dpwEKVDkwAhEaAqcb9xoahEgXmd4kOmNGylJcwAJhSNqAC9BJO+gAdukGmKodM3nkwKo1BJc2ozqoYar8MYH/FQK8GPBOp4w2LHlm2yXuPB/dqd9/b9N4/ivf5LEthNMn1AnLS37tZIbQ4rSaxLGb72p0iBHSM5oHh1JKDn3mGDKIGxR1cxQ6PuuH6wNB5giNU9U76M4y2QGvw== user2@%(host)s

# another user entry
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user3@%(host)s

""" % {
            'host': hostname
        }

        bad_key_data = good_key_data + """
# missing host
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user4

# insufficient fields
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBalxS0OHm1J3QB7WjtInEhqdMO7cqjqw0yVCfHqb8VU/nXJWQZPJAom8ey3uYWqrjVKuHPSgEaqqtJxwVIeJ5oBDOQbAT9WY4n7+mx8I+bhpdVsZQvtQE3BUgYh0/GUbRgSx+F/1efrwcRHCRb9QO+9DrIg1q2NeY6OR2bSiYA5Q==

# too many fields
command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user5@%(host)s

# unsupported key type
ssh-dsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user6@%(host)s

# munged data
ssh-rsa ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user7@%(host)s
""" % {
            'host': hostname
        }

        with open('key_data', 'w') as out:
            out.write(good_key_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('key_data')
        try:
            keys = read_authorized_keys('key_data', logging.getLogger())
            for name, key in keys.items():
                logging.debug('    %s: %r', name, key)
            self.assertEqual(sorted(keys.keys()), [
                'user1@' + hostname, 'user2@' + hostname, 'user3@' + hostname
            ])
        finally:
            os.remove('key_data')

        # Write and read-back.
        key_file = 'users.allow'
        try:
            write_authorized_keys(keys, key_file)
            if sys.platform != 'win32' or HAVE_PYWIN32:
                self.assertTrue(is_private(key_file))
                new_keys = read_authorized_keys(key_file)
                self.assertEqual(len(keys), len(new_keys))
                for user in sorted(keys.keys()):
                    pubkey = keys[user]
                    try:
                        new_pubkey = new_keys[user]
                    except KeyError:
                        self.fail('new_keys is missing %r', user)
                    self.assertEqual(new_pubkey.n, pubkey.n)
                    self.assertEqual(new_pubkey.e, pubkey.e)
        finally:
            if os.path.exists(key_file):
                os.remove(key_file)

        # Try default file, which may or may not exist.
        try:
            keys = read_authorized_keys(logger=logging.getLogger())
        except RuntimeError:
            pass

        # Try nonexistent file.
        code = "read_authorized_keys('key_data', logging.getLogger())"
        assert_raises(self, code, globals(), locals(), RuntimeError,
                      "'key_data' does not exist")

        # Try insecure file.
        if sys.platform != 'win32':
            with open('key_data', 'w') as out:
                out.write(good_key_data)
            os.chmod('key_data', 0666)
            try:
                code = "read_authorized_keys('key_data', logging.getLogger())"
                assert_raises(self, code, globals(), locals(), RuntimeError,
                              "'key_data' is not private")
            finally:
                os.remove('key_data')

        # Try bad file.
        with open('key_data', 'w') as out:
            out.write(bad_key_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('key_data')
        try:
            code = "read_authorized_keys('key_data', logging.getLogger())"
            assert_raises(self, code, globals(), locals(), RuntimeError,
                          "5 errors in 'key_data', check log for details")
        finally:
            os.remove('key_data')
Exemple #6
0
    def test_allowed_hosts(self):
        logging.debug('')
        logging.debug('test_allowed_hosts')

        hostname = socket.gethostname()
        host_ipv4 = socket.gethostbyname(hostname)
        dot = host_ipv4.rfind('.')
        domain_ipv4 = host_ipv4[:dot + 1]

        good_data = """
# Local host IPv4.
%s

# Local domain IPv4.
%s

# Local host name.
%s

""" % (host_ipv4, domain_ipv4, hostname)

        gibberish = '$^&*'
        bad_data = good_data + """
# Gibberish.
%s
""" % gibberish

        # Try good data.
        with open('hosts.allow', 'w') as out:
            out.write(good_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('hosts.allow')
        try:
            allowed_hosts = read_allowed_hosts('hosts.allow')
        finally:
            os.remove('hosts.allow')
        self.assertEqual(len(allowed_hosts), 3)
        self.assertEqual(allowed_hosts[0], host_ipv4)
        self.assertEqual(allowed_hosts[1], domain_ipv4)
        self.assertEqual(allowed_hosts[2], host_ipv4)

        # Try bad data.
        with open('hosts.allow', 'w') as out:
            out.write(bad_data)
        if sys.platform != 'win32' or HAVE_PYWIN32:
            make_private('hosts.allow')
        try:
            allowed_hosts = read_allowed_hosts('hosts.allow')
        except RuntimeError as exc:
            self.assertEqual(
                str(exc), "1 errors in 'hosts.allow', check log for details")
        else:
            expected = 3
            try:
                # This actally resolves in some environments.
                socket.gethostbyname(gibberish)
            except socket.gaierror:
                pass
            else:
                expected += 1
            self.assertEqual(len(allowed_hosts), expected)
            self.assertEqual(allowed_hosts[0], host_ipv4)
            self.assertEqual(allowed_hosts[1], domain_ipv4)
            self.assertEqual(allowed_hosts[2], host_ipv4)
        finally:
            os.remove('hosts.allow')

        # Check AF_INET addresses.
        logger = logging.getLogger()
        self.assertTrue(
            is_legal_connection((host_ipv4, 0), allowed_hosts, logger))
        domain_host = domain_ipv4 + '123'
        self.assertTrue(
            is_legal_connection((domain_host, 0), allowed_hosts, logger))
        self.assertFalse(
            is_legal_connection(('0.0.0.0', 0), allowed_hosts, logger))

        # Check AF_UNIX address.
        self.assertTrue(is_legal_connection('/tmp/pipe', allowed_hosts,
                                            logger))
        # Try nonexistant file.
        assert_raises(self, "read_allowed_hosts('no-such-file')", globals(),
                      locals(), RuntimeError, "'no-such-file' does not exist")

        # Try insecure file.
        if sys.platform != 'win32' or HAVE_PYWIN32:
            with open('hosts.allow', 'w') as out:
                out.write('\n')
            os.chmod('hosts.allow', 0666)
            try:
                assert_raises(self, "read_allowed_hosts('hosts.allow')",
                              globals(), locals(), RuntimeError,
                              "'hosts.allow' is not private")
            finally:
                os.remove('hosts.allow')