From 83e9f05f54012eea8fd1794ce317cfe9087bb73d Mon Sep 17 00:00:00 2001 From: Viktor Ashirov Date: Jun 29 2020 18:32:58 +0000 Subject: Issue 50396 - segfault when using pam passthru and addn plugins together Description: * Add missing test for addn plugin in acceptance test module * Add regression test for #50396 Relates: https://pagure.io/389-ds-base/issue/50396 Reviewed by: mreynolds (Thanks!) --- diff --git a/dirsrvtests/tests/suites/plugins/acceptance_test.py b/dirsrvtests/tests/suites/plugins/acceptance_test.py index e9e830d..29b5d6a 100644 --- a/dirsrvtests/tests/suites/plugins/acceptance_test.py +++ b/dirsrvtests/tests/suites/plugins/acceptance_test.py @@ -29,6 +29,7 @@ pytestmark = pytest.mark.tier1 log = logging.getLogger(__name__) USER_DN = 'uid=test_user_1001,ou=people,dc=example,dc=com' +USER_ADDN = 'test_user_1001' USER_PW = 'password' GROUP_DN = 'cn=group,' + DEFAULT_SUFFIX CONFIG_AREA = 'nsslapd-pluginConfigArea' @@ -1792,10 +1793,97 @@ def test_rootdn(topo, args=None): return +def test_addn(topo, args=None): + """Test AD DN plugin basic functionality + + :id: 106ee95e-93ab-11e9-9dd6-3497f624ea11 + :setup: Standalone Instance + :steps: + 1. Create a test user + 2. Bind using only the uid + 3. Bind using uid@example.com + 4. Bind using uid@example.org (second domain, non-default) + 5. Bind using uid@nonexistent.domain + 6. Bind as Root DN + 7. Configure plugin + 8. Create a default domain config entry + 9. Create a second domain config entry + 10. Enable plugin and restart the instance + 11. Bind using only the uid + 12. Bind using uid@example.com + 13. Bind using uid@example.org (second domain, non-default) + 14. Bind using uid@nonexistent.domain + 15. Clean up + :expectedresults: + 1. Success + 2. Failure + 3. Failure + 4. Failure + 5. Failure + 6. Success + 7. Success + 8. Success + 9. Success + 10. Success + 11. Success + 12. Success + 13. Success + 14. Failure + 15. Success + """ + + inst = topo[0] + + users = UserAccounts(inst, DEFAULT_SUFFIX) + user1 = users.create_test_user(uid=1001) + user1.replace('userPassword', USER_PW) + + with pytest.raises(ldap.LDAPError): + assert inst.simple_bind_s(USER_ADDN, USER_PW) + with pytest.raises(ldap.LDAPError): + assert inst.simple_bind_s(f'{USER_ADDN}@example.com', USER_PW) + with pytest.raises(ldap.LDAPError): + assert inst.simple_bind_s(f'{USER_ADDN}@example.org', USER_PW) + with pytest.raises(ldap.LDAPError): + assert inst.simple_bind_s(f'{USER_ADDN}@nonexistent.domain', USER_PW) + inst.simple_bind_s(DN_DM, PASSWORD) + + # stop the plugin, and start it + plugin = AddnPlugin(inst) + plugin.create(rdn='addn') + plugin.set_default_domain('example.com') + addn_domains = AddnDomains(inst) + addn_domains.create( + properties={'cn': 'example.com', + 'addn_base': 'ou=people,dc=example,dc=com', + 'addn_filter': '(&(objectClass=account)(uid=%s))',}) + addn_domains.create( + properties={'cn': 'example.org', + 'addn_base': 'ou=people,dc=example,dc=com', + 'addn_filter': '(&(objectClass=account)(uid=%s))',}) + plugin.enable() + + if args == "restart": + return + + if args is None: + inst.restart() + + assert inst.simple_bind_s(USER_ADDN, USER_PW) + assert inst.simple_bind_s(f'{USER_ADDN}@example.com', USER_PW) + assert inst.simple_bind_s(f'{USER_ADDN}@example.org', USER_PW) + with pytest.raises(ldap.LDAPError): + assert inst.simple_bind_s(f'{USER_ADDN}@nonexistent.domain', USER_PW) + + # Delete user + inst.simple_bind_s(DN_DM, PASSWORD) + user1.delete() + + # Array of test functions func_tests = [test_acctpolicy, test_attruniq, test_automember, test_dna, test_linkedattrs, test_memberof, test_mep, test_passthru, - test_referint, test_retrocl, test_rootdn] + test_referint, test_retrocl, test_rootdn, test_addn] def check_all_plugins(topo, args="online"): diff --git a/dirsrvtests/tests/suites/plugins/regression_test.py b/dirsrvtests/tests/suites/plugins/regression_test.py new file mode 100644 index 0000000..942c2de --- /dev/null +++ b/dirsrvtests/tests/suites/plugins/regression_test.py @@ -0,0 +1,60 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2019 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# +import ldap +import logging +import pytest +from lib389.plugins import PAMPassThroughAuthPlugin, AddnPlugin, AddnDomains +from lib389.topologies import topology_st as topo + +pytestmark = pytest.mark.tier1 + +log = logging.getLogger(__name__) + +@pytest.mark.ds50396 +@pytest.mark.bz1701092 +def test_crash_in_pam_pta_plugin_when_user_doesnt_exist(topo): + """Test that the server doesn't crash when user doesn't exist + + :id: cb362b70-9424-11e9-a56a-54e1ad30572c + :setup: Standalone Instance + :steps: + 1. Enable PAM PTA plugin + 2. Enable and configure AD DN plugin + 3. Restart the instance + 4. Bind as non-existent user + :expectedresults: + 1. Success + 2. Success + 3. Success + 4. Server shouldn't crash + """ + + inst = topo.standalone + + # Enable PAM PTA plugin + pampta_plugin = PAMPassThroughAuthPlugin(inst) + pampta_plugin.enable() + + # Enable and configure AD DN plugin + addn_plugin = AddnPlugin(inst) + addn_plugin.create(rdn='addn') + addn_plugin.set_default_domain('example.com') + addn_domains = AddnDomains(inst) + addn_domains.create( + properties={'cn': 'example.com', + 'addn_base': 'ou=people,dc=example,dc=com', + 'addn_filter': '(&(objectClass=account)(uid=%s))',}) + addn_plugin.enable() + + # Restart the instance to enable plugins + inst.restart() + + # Expect the following bind to fail, but not crash the server (ldap.SERVER_DOWN) + with pytest.raises(ldap.OPERATIONS_ERROR): + assert inst.simple_bind_s('bob@example.com', 'password') diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py index c702a96..2772515 100644 --- a/src/lib389/lib389/plugins.py +++ b/src/lib389/lib389/plugins.py @@ -100,17 +100,100 @@ class Plugin(DSLdapObject): class AddnPlugin(Plugin): - """An instance of addn plugin entry + """An instance of ADDN plugin entry :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str """ + _plugin_properties = { + 'nsslapd-pluginEnabled': 'off', + 'nsslapd-pluginPath': 'libaddn-plugin', + 'nsslapd-pluginInitfunc': 'addn_init', + 'nsslapd-pluginType': 'preoperation', + 'nsslapd-pluginId': 'addn', + 'nsslapd-pluginVendor': '389 Project', + 'nsslapd-pluginVersion': 'none', + 'nsslapd-pluginDescription': 'Allow AD DN style bind names to LDAP', + } def __init__(self, instance, dn="cn=addn,cn=plugins,cn=config"): super(AddnPlugin, self).__init__(instance, dn) - # Need to add wrappers to add domains to this. + self._create_objectclasses = ['top', 'nsslapdplugin', 'extensibleObject'] + + def set_default_domain(self, attr): + """Add addn_default_domain attribute""" + + self.set('addn_default_domain', attr) + + def get_default_domain(self): + """Get addn_default_domain attribute""" + + return self.get_attr_val_utf8('addn_default_domain') + +class AddnDomain(DSLdapObject): + """A single instance of ADDN plugin domain config entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + """ + + def __init__(self, instance, dn): + super(AddnDomain, self).__init__(instance, dn) + self._rdn_attribute = 'cn' + self._must_attributes = ['cn','addn_base', 'addn_filter'] + self._create_objectclasses = ['top', 'extensibleObject'] + self._protected = False + +class AddnDomains(DSLdapObjects): + """A DSLdapObjects entity which represents ADDN plugin domain config entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all account entries below + :type basedn: str + """ + + def __init__(self, instance, basedn=None): + super(AddnDomains, self).__init__(instance) + self._objectclasses = ['top', 'extensibleObject'] + self._filterattrs = ['cn'] + self._childobject = AddnDomain + # So we can set the configArea easily + if basedn is None: + basedn = "cn=addn,cn=plugins,cn=config" + self._basedn = basedn + + def list(self): + """Get a list of children entries (DSLdapObject, Replica, etc.) using a base DN + and objectClasses of our object (DSLdapObjects, Replicas, etc.) + + :returns: A list of children entries + """ + + # Filter based on the objectclasses and the basedn + insts = None + # This will yield and & filter for objectClass with as many terms as needed. + filterstr = self._get_objectclass_filter() + self._log.debug('list filter = %s' % filterstr) + try: + results = self._instance.search_ext_s( + base=self._basedn, + scope=self._scope, + filterstr=filterstr, + attrlist=self._list_attrlist, + serverctrls=self._server_controls, clientctrls=self._client_controls, + escapehatch='i am sure' + ) + # def __init__(self, instance, dn=None): + insts = [self._entry_to_instance(dn=r.dn, entry=r) for r in results] + except ldap.NO_SUCH_OBJECT: + # There are no objects to select from, se we return an empty array + insts = [] + return insts class AttributeUniquenessPlugin(Plugin): @@ -1016,7 +1099,7 @@ class ViewsPlugin(Plugin): class SevenBitCheckPlugin(Plugin): - """An instance of addn plugin entry + """An instance of 7-bit check plugin entry :param instance: An instance :type instance: lib389.DirSrv