Skip to content

Commit

Permalink
Issue 152 - RFE - Add support for LDAP alias entries
Browse files Browse the repository at this point in the history
Description:  Per RFC rfc4512#section-2.6 add support for Alias Entries.
              Currently this is only designed to work with "base" searches.

              Thanks for @anilech for the code contribution!!!

relates: 389ds#152

Reviewed by: spichugi, tbordaz, and progier(Thanks!!!)
  • Loading branch information
mreynolds389 committed May 11, 2023
1 parent 05528f6 commit 5304d4f
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 5 deletions.
13 changes: 12 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ serverplugin_LTLIBRARIES = libacl-plugin.la \
libroles-plugin.la libstatechange-plugin.la libsyntax-plugin.la \
libviews-plugin.la libschemareload-plugin.la libusn-plugin.la \
libacctusability-plugin.la librootdn-access-plugin.la \
libwhoami-plugin.la $(LIBACCTPOLICY_PLUGIN) \
libwhoami-plugin.la libalias-entries-plugin.la $(LIBACCTPOLICY_PLUGIN) \
$(LIBPAM_PASSTHRU_PLUGIN) $(LIBDNA_PLUGIN) \
$(LIBBITWISE_PLUGIN) $(LIBPRESENCE_PLUGIN) $(LIBPOSIX_WINSYNC_PLUGIN) \
libentryuuid-plugin.la libentryuuid-syntax-plugin.la libpwdchan-plugin.la
Expand Down Expand Up @@ -468,6 +468,7 @@ dist_noinst_HEADERS = \
ldap/servers/plugins/passthru/passthru.h \
ldap/servers/plugins/rever/rever.h \
ldap/servers/plugins/automember/automember.h \
ldap/servers/plugins/alias_entries/alias-entries.h \
ldap/servers/plugins/mep/mep.h \
ldap/servers/slapd/agtmmap.h \
ldap/servers/slapd/auth.h \
Expand Down Expand Up @@ -1319,6 +1320,16 @@ libacl_plugin_la_LIBADD = libslapd.la libns-dshttpd.la $(LDAPSDK_LINK) $(NSPR_LI
libacl_plugin_la_LDFLAGS = -avoid-version
# libacl_plugin_la_LINK = $(CXXLINK) -avoid-version

#------------------------
# libalias-entries-plugin
#------------------------
libalias_entries_plugin_la_SOURCES = ldap/servers/plugins/alias_entries/alias-entries.c

libalias_entries_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS)
libalias_entries_plugin_la_LIBADD = libslapd.la $(NSPR_LINK)
libalias_entries_plugin_la_DEPENDENCIES = libslapd.la
libalias_entries_plugin_la_LDFLAGS = -avoid-version

#------------------------
# libaddn-plugin
#------------------------
Expand Down
111 changes: 111 additions & 0 deletions dirsrvtests/tests/suites/plugins/alias_entries_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# --- BEGIN COPYRIGHT BLOCK ---
# Copyright (C) 2023 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
#
import logging
import pytest
import os
import ldap
import time
from lib389.utils import ensure_str
from lib389.plugins import AliasEntriesPlugin
from lib389.idm.user import UserAccount
from lib389._constants import DEFAULT_SUFFIX
from lib389.topologies import topology_st as topo

log = logging.getLogger(__name__)

TEST_ENTRY_NAME = "entry"
TEST_ENTRY_DN = "cn=entry," + DEFAULT_SUFFIX
TEST_ALIAS_NAME = "alias entry"
TEST_ALIAS_DN = "cn=alias_entry," + DEFAULT_SUFFIX
TEST_ALIAS_DN_WRONG = "cn=alias_entry_not_there," + DEFAULT_SUFFIX
EXPECTED_UIDNUM = "1000"


def test_entry_alias(topo):
"""Test that deref search for alias entry works
:id: 454e85af-0e20-4a36-9b3a-02562b1db53d
:setup: Standalone Instance
:steps:
1. Enable alias entry plugin
2. Create entry and alias entry
3. Set deref option and do a base search
4. Test non-base scope ssearch returns error
5. Test invalid alias DN returns error
:expectedresults:
1. Success
2. Success
3. Success
4. Success
5. Success
"""
inst = topo.standalone

# Enable Alias Entries plugin
alias_plugin = AliasEntriesPlugin(inst)
alias_plugin.enable()
inst.restart()

# Add entry
test_user = UserAccount(inst, TEST_ENTRY_DN)
test_user.create(properties={
'uid': TEST_ENTRY_NAME,
'cn': TEST_ENTRY_NAME,
'sn': TEST_ENTRY_NAME,
'userPassword': TEST_ENTRY_NAME,
'uidNumber': '1000',
'gidNumber': '2000',
'homeDirectory': '/home/alias_test',
})

# Add entry that has an alias set to the first entry
test_alias = UserAccount(inst, TEST_ALIAS_DN)
test_alias.create(properties={
'uid': TEST_ALIAS_NAME,
'cn': TEST_ALIAS_NAME,
'sn': TEST_ALIAS_NAME,
'userPassword': TEST_ALIAS_NAME,
'uidNumber': '1001',
'gidNumber': '2001',
'homeDirectory': '/home/alias_test',
'objectclass': ['alias', 'extensibleObject'],
'aliasedObjectName': TEST_ENTRY_DN,
})

# Set the deref "finding" option
inst.set_option(ldap.OPT_DEREF, ldap.DEREF_FINDING)

# Do base search which could map entry to the aliased one
log.info("Test alias")
deref_user = UserAccount(inst, TEST_ALIAS_DN)
result = deref_user.search(scope="base")
assert result[0].dn == TEST_ENTRY_DN
assert ensure_str(result[0].getValue('uidNumber')) == EXPECTED_UIDNUM

# Do non-base search which could raise an error
log.info("Test unsupported search scope")
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
deref_user.search(scope="subtree")

# Reset the aliasObjectname to a DN that does not exist, and try again
log.info("Test invalid alias")
test_alias.replace('aliasedObjectName', TEST_ALIAS_DN_WRONG)
try:
deref_user.search(scope="base")
assert False
except ldap.LDAPError as e:
msg = e.args[0]['info']
assert msg.startswith("Failed to dereference alias object")


if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
CURRENT_FILE = os.path.realpath(__file__)
pytest.main(["-s", CURRENT_FILE])
14 changes: 14 additions & 0 deletions ldap/ldif/template-dse-minimal.ldif.in
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,20 @@ nsslapd-plugintype: matchingRule
nsslapd-pluginenabled: on
nsslapd-pluginarg0: %config_dir%/slapd-collations.conf

dn: cn=Alias Entries,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: Alias Entries
nsslapd-pluginpath: libalias-entries-plugin
nsslapd-plugininitfunc: alias_entry_init
nsslapd-plugintype: preoperation
nsslapd-pluginenabled: off
nsslapd-pluginDescription: alias entries plugin [base search only]
nsslapd-pluginVendor: anilech
nsslapd-pluginVersion: 0.1
nsslapd-pluginId: alias-entries

dn: cn=ldbm database,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
Expand Down
13 changes: 13 additions & 0 deletions ldap/ldif/template-dse.ldif.in
Original file line number Diff line number Diff line change
Expand Up @@ -1255,3 +1255,16 @@ nsSaslMapRegexString: ^dn:\(.*\)
nsSaslMapBaseDNTemplate: \1
nsSaslMapFilterTemplate: (objectclass=*)

dn: cn=Alias Entries,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: Alias Entries
nsslapd-pluginpath: libalias-entries-plugin
nsslapd-plugininitfunc: alias_entry_init
nsslapd-plugintype: preoperation
nsslapd-pluginenabled: off
nsslapd-pluginDescription: alias entries plugin [base search only]
nsslapd-pluginVendor: anilech
nsslapd-pluginVersion: 0.1
nsslapd-pluginId: alias-entries
145 changes: 145 additions & 0 deletions ldap/servers/plugins/alias_entries/alias-entries.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2023 anilech
* Copyright (C) 2023 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
* See LICENSE for details.
* END COPYRIGHT BLOCK **/

#include "alias-entries.h"

static Slapi_ComponentId *plugin_id = NULL;
Slapi_PluginDesc srchpdesc = { PLUGINNAME, PLUGINVNDR, PLUGINVERS, PLUGINDESC };

static Slapi_DN*
alias_get_next_dn(Slapi_DN *sdn, int *rc)
{
Slapi_PBlock *pb = NULL;
Slapi_Entry **e;
Slapi_DN *alias_dn = NULL;
int derefNever = LDAP_DEREF_NEVER;
char *attrs[] = { "aliasedObjectName", NULL };

/* search dn to check if it is an alias */
pb = slapi_pblock_new();
slapi_search_internal_set_pb_ext(pb, sdn, LDAP_SCOPE_BASE, ALIASFILTER, attrs, 0, NULL, NULL, plugin_id, 0);
slapi_pblock_set(pb, SLAPI_SEARCH_DEREF, (void *)&derefNever);

if (slapi_search_internal_pb(pb) == 0) {
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, rc);
if (*rc == LDAP_SUCCESS) {
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &e);
if (e && e[0]) {
alias_dn = slapi_sdn_new_dn_byval(slapi_entry_attr_get_ref(e[0], attrs[0]));
}
}
}
slapi_free_search_results_internal(pb);
slapi_pblock_destroy(pb);

return alias_dn;
}

int
alias_entry_init(Slapi_PBlock *pb)
{
if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&srchpdesc) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *)alias_entry_srch) != 0 ||
slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id) != 0)
{
slapi_log_error(SLAPI_LOG_ERR, PLUGINNAME,
"alias_entry_init: Error registering the plug-in.\n");
return -1;
}

return 0;
}

int
alias_entry_srch(Slapi_PBlock *pb)
{
Slapi_DN *search_target = NULL;
Slapi_DN *dn1 = NULL;
Slapi_DN *dn2 = NULL;
char *str_filter = NULL;
int scope = 0;
int deref_enabled = 0;
size_t i = 0;
int rc = 0;

/* Skip reserved operations */
if (slapi_op_reserved(pb)) {
return 0;
}

/* Base dn should be provided */
if (slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &search_target) != 0 || search_target == NULL) {
return 0;
}

/* deref must be enabled */
if (slapi_pblock_get(pb, SLAPI_SEARCH_DEREF, &deref_enabled) != 0) {
return 0;
}
if (deref_enabled != LDAP_DEREF_FINDING && deref_enabled != LDAP_DEREF_ALWAYS) {
return 0;
}

/* Only base search is supported */
if (slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &scope) != 0 || scope != LDAP_SCOPE_BASE) {
char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE] = {0};
slapi_create_errormsg(errorbuf, sizeof(errorbuf),
"Only base level scoped searches are allowed to dereference alias entries");
rc = LDAP_UNWILLING_TO_PERFORM;
slapi_send_ldap_result(pb, rc, NULL, errorbuf, 0, NULL);
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &rc);
return SLAPI_PLUGIN_FAILURE;
}

/* Skip our own requests */
if (slapi_pblock_get(pb, SLAPI_SEARCH_STRFILTER, &str_filter) != 0 ||
str_filter == NULL || strcmp(str_filter, ALIASFILTER) == 0)
{
return 0;
}

/* Follow the alias chain */
dn2 = search_target;
do {
dn1 = dn2;
dn2 = alias_get_next_dn(dn1, &rc);
if (i > 0 && dn2 != NULL) {
slapi_sdn_free(&dn1);
}
if (rc != LDAP_SUCCESS) {
char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE] = {0};
slapi_create_errormsg(errorbuf, sizeof(errorbuf),
"Failed to dereference alias object name (%s) error %d",
slapi_sdn_get_dn(dn1), rc);
slapi_log_error(SLAPI_LOG_PLUGIN, PLUGINNAME,
"alias_entry_srch - %s\n", errorbuf);

slapi_send_ldap_result(pb, rc, NULL, errorbuf, 0, NULL);
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &rc);
return SLAPI_PLUGIN_FAILURE;
}
} while (dn2 != NULL && i++ < MAXALIASCHAIN);

if (dn1 == search_target) {
/* Source dn is not an alias */
return 0;
}

if (dn2 == NULL) {
/* alias resolved, set new base */
slapi_sdn_free(&search_target);
slapi_pblock_set(pb, SLAPI_SEARCH_TARGET_SDN, dn1);
} else {
/* Here we hit an alias chain longer than MAXALIASCHAIN */
slapi_sdn_free(&dn2);
}

return 0;
}
29 changes: 29 additions & 0 deletions ldap/servers/plugins/alias_entries/alias-entries.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (c) 2023 anilech
* Copyright (C) 2023 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
* See LICENSE for details.
* END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include "slapi-plugin.h"
#include "slapi-private.h"

#define PLUGINNAME "Alias Entries"
#define PLUGINVNDR "anilech"
#define PLUGINVERS "0.1"
#define PLUGINDESC "alias entries plugin [base search only]"

#define MAXALIASCHAIN 8
#define ALIASFILTER "(&(objectClass=alias)(aliasedObjectName=*))"

// function prototypes
int alias_entry_init(Slapi_PBlock *pb);
int alias_entry_srch(Slapi_PBlock *pb);
4 changes: 4 additions & 0 deletions ldap/servers/slapd/opshared.c
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,10 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
} else if (be_single) {
slapi_be_Unlock(be_single);
}
if (rc > 0) {
/* rc > 0 means a plugin generated an error and we should abort */
send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
}

free_and_return_nolock:
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &rc);
Expand Down
13 changes: 13 additions & 0 deletions src/lib389/lib389/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2411,6 +2411,7 @@ def fixup(self, basedn, _filter=None):

return task


class ContentSyncPlugin(Plugin):
"""A single instance of Content Sync (aka syncrepl) plugin entry
Expand All @@ -2423,3 +2424,15 @@ class ContentSyncPlugin(Plugin):
def __init__(self, instance, dn="cn=Content Synchronization,cn=plugins,cn=config"):
super(ContentSyncPlugin, self).__init__(instance, dn)


class AliasEntriesPlugin(Plugin):
"""A single instance of Alias Entries plugin entry
:param instance: An instance
:type instance: lib389.DirSrv
:param dn: Entry DN
:type dn: str
"""

def __init__(self, instance, dn="cn=Alias Entries,cn=plugins,cn=config"):
super(AliasEntriesPlugin, self).__init__(instance, dn)
Loading

0 comments on commit 5304d4f

Please sign in to comment.