diff options
-rw-r--r-- | configure.ac | 14 | ||||
-rw-r--r-- | doc/ipa/sch-ipa.txt | 93 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/back-sch-idview.c | 392 | ||||
-rw-r--r-- | src/back-sch-nss.c | 111 | ||||
-rw-r--r-- | src/back-sch.c | 71 | ||||
-rw-r--r-- | src/back-sch.h | 38 |
7 files changed, 692 insertions, 31 deletions
diff --git a/configure.ac b/configure.ac index 84b84d1..71dbdc7 100644 --- a/configure.ac +++ b/configure.ac @@ -383,6 +383,20 @@ if test "x$use_nsswitch" != xno ; then AC_DEFINE(USE_NSSWITCH,1,[Use nsswitch API to lookup users and groups not found in the LDAP tree]) fi +use_idviews=true +AC_ARG_WITH(idviews, + AS_HELP_STRING([--with-idviews], [Use FreeIPA ID views to override POSIX IDs of users and groups]), + use_idviews=$withval,use_idviews=yes) +if test "x$use_idviews" = xyes ; then + AC_MSG_RESULT([FreeIPA ID views support is enabled]) + AC_DEFINE(USE_IPA_IDVIEWS,1,[Use FreeIPA ID views to override POSIX attributes of users and groups per view.]) + AC_DEFINE(IPA_IDVIEWS_ATTR_ANCHORUUID, ["ipaAnchorUUID"],[FreeIPA attr unique pointer for id overrides]) + AC_DEFINE(IPA_IDVIEWS_ATTR_ORIGINALUID, ["ipaOriginalUid"],[FreeIPA attr original uid value for user id overrides]) +else + AC_MSG_RESULT([FreeIPA ID views support is disabled]) +fi +AM_CONDITIONAL([USE_IPA_IDVIEWS], [test "x$use_idviews" != xno]) + mylibdir=`eval echo "$libdir" | sed "s,NONE,${ac_default_prefix},g"` mylibdir=`eval echo "$mylibdir" | sed "s,NONE,${ac_prefix},g"` case "$server" in diff --git a/doc/ipa/sch-ipa.txt b/doc/ipa/sch-ipa.txt index b5a585b..f560580 100644 --- a/doc/ipa/sch-ipa.txt +++ b/doc/ipa/sch-ipa.txt @@ -87,3 +87,96 @@ on IPA masters. As 'system-auth' PAM service is not used directly by any other application, it is safe to use it for trusted domain users via compatibility path. + +== Support for ID views == + +When FreeIPA 4.1 is in use, Schema compatibility plugin can be configured to +override POSIX attributes according to an identity view (ID View) which +contains overrides for users and groups. + +The overrides are managed by FreeIPA separately for users and groups: + +* management of ID views: + ipa idview-add 'my view' + +* management of an override for a user: + ipa idoverrideuser-add 'my view' auser --login=foo --shell=/bin/ksh \ + --homedir=/home/foo --uid=12345 + +* management of an override for a group: + ipa idoverridegroup-add 'my view' agroup --group-name=bgroup --gid=54321 + +FreeIPA transparently supports overrides for users and groups from trusted AD +domains. This means that for trusted domains, IPA doesn't require to place +POSIX attributes in Active Directory. Instead, a global ID view named 'Default +Trust View' is used to contain POSIX attributes for existing AD users. +Additionally, specialized ID views can be added on top of the 'Default Trust +View' and then assigned to specific hosts. + +Schema compatibility plugin does support ID overrides from a single view. The +feature is designed to allow host-specific ID view, where the view is specified +through the search base. + +For native IPA users default POSIX attributes are stored natively, thus only a +host-specific ID view is applied, if any. For trusted AD users 'Default Trust +View' ID view is applied automatically by SSSD running on the IPA master, thus +Schema compatibility plugin only applies a host-specific ID view, if specified. + +In FreeIPA Schema compatibility is configured to serve entries through the +host-specific ID view with base DN of cn=<ID view>,cn=views,cn=compat,$SUFFIX. + +=== ID views implementation === +Detailed design of ID views in FreeIPA can be seen here: +http://www.freeipa.org/page/V4/Migrating_existing_environments_to_Trust + +In Schema compatibility plugin support for ID views is done on top of existing +map cache. It is expected that there are less overrides than non-overridden +entries for IPA users and groups. For trusted AD users POSIX attributes from +'Default Trust View' are applied by SSSD on IPA master. Thus, if there are no +host-specific overrides, trusted AD users treated by Schema compatibility +plugin as before -- as entries which content comes from nssswitch API. + +This approach allows to only keep original entry in the memory and apply +host-specific override only at the time when entry with explicitly requested ID +view is returned as part of a search result. + +In order to map original entry to an override, FreeIPA configuration for Schema +compatibility plugin adds ipaAnchorUUID attribute and ipaOverrideTarget object +class to every generated entry. ipaAnchorUUID is based on ipaUniqueID for IPA +objects and on SID for trusted AD objects: + +* ipaAnchorUUID=:IPA:<domain>:<ipaUniqueID> for IPA object (user, group)_ + +* ipaAnchorUUID=:SID:<SID> for trusted AD user or group + +For ID overrides FreeIPA maintains ipaAnchorUUID with the same value so that an +override can be found by simple matching of the ipaAnchorUUID attribute's +value. FreeIPA also stores original uid value for user objects in ID override +as ipaOriginalUid attribute, to allow mapping back memberUid values for groups. + +When a query request comes, the view in the base DN is detected and remembered. +Base DN is rewritten to exclude the cn=<ID view>,cn=views so that a normal +search can be performed against cached entries. Additionally, search filter is +analyzed to replace references to rewritten uid (for user) and cn (for group) +attributes by references to the original objects. The references come from the +ID view overrides, if they exist. + +Once search results are gathered for the map, they are processed in order to +apply an override. For users entry attributes overridden with the values from +an override. For groups additional processing is performed on values of +memberUid attribute. + +As opposed to member attribute, memberUid attribute contains only values of uid +attribute of the original member entry. Given that an ID override may redefine +uid value, corresponding memberUid value of a group needs to be rewritten to +include redefined uid value. In order to do that, original memberUid value is +compared with ipaOriginalUid attribute's value to find an override +corresponding to the original user object. If such override is detected, memberUid +value is replaced by the uid value of the override. + +When attributes of the entry are processed and optionally amended with overridden +values, DN of the entry is rewritten as well, to reflect the fact that entry is +served through the view. + +For all returned entries ipaAnchorUUID attribute and ipaOverrideTarget objectclass +are removed. Resulting entry is sent to the client. diff --git a/src/Makefile.am b/src/Makefile.am index e4fe1a9..6f4926e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,6 +78,10 @@ schemacompat_plugin_la_SOURCES += back-sch-pam.c schemacompat_plugin_la_LIBADD += $(PAM_LIBS) endif +if USE_IPA_IDVIEWS +schemacompat_plugin_la_SOURCES += back-sch-idview.c +endif + noinst_LTLIBRARIES = dummy-nis-plugin.la dummy_nis_plugin_la_SOURCES = \ disp-nis.c \ diff --git a/src/back-sch-idview.c b/src/back-sch-idview.c new file mode 100644 index 0000000..5a2b450 --- /dev/null +++ b/src/back-sch-idview.c @@ -0,0 +1,392 @@ +/* + * Copyright 2014 Red Hat, Inc. + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This Program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; if not, write to the + * + * Free Software Foundation, Inc. + * 59 Temple Place, Suite 330 + * Boston, MA 02111-1307 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +#ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H +#include <nspr.h> +#include <nss.h> +#include <dirsrv/slapi-plugin.h> +#else +#include <slapi-plugin.h> +#endif + +#include <rpc/xdr.h> +#include "../yp/yp.h" + +#ifdef HAVE_TCPD_H +#include <tcpd.h> +#endif + +#include "backend.h" +#include "back-shr.h" +#include "format.h" +#include "plugin.h" +#include "map.h" +#include "back-sch.h" + +void +idview_get_overrides(struct backend_search_cbdata *cbdata) +{ + char *dn = NULL; + int ret = 0, result = 0; + const Slapi_DN *suffix = NULL; + Slapi_PBlock *pb; + + if (cbdata->idview == NULL) + return; + + pb = wrap_pblock_new(cbdata->pb); + if (pb == NULL) + return; + + wrap_inc_call_level(); + + suffix = slapi_get_suffix_by_dn(cbdata->target_dn); + dn = slapi_ch_smprintf("cn=%s,cn=views,cn=accounts,%s", cbdata->idview, slapi_sdn_get_dn(suffix)); + /* Fetch all attributes; there is a bug in 389-ds: it gives out all attributes for the entry anyway + * when search returns Slapi_Entry* objects. Instead, we'll do removal later */ + slapi_search_internal_set_pb(pb, dn, LDAP_SCOPE_SUBTREE, + "(objectclass=ipaOverrideAnchor)", NULL, 0, + NULL, NULL, cbdata->state->plugin_identity, 0); + ret = slapi_search_internal_pb(pb); + slapi_ch_free_string(&dn); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if (result == 0) { + /* Steal search result entries to avoid re-allocating them */ + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &(cbdata->overrides)); + slapi_pblock_set(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, NULL); + } + + wrap_dec_call_level(); + slapi_pblock_destroy(pb); +} + +void +idview_free_overrides(struct backend_search_cbdata *cbdata) +{ + int i = 0; + if (cbdata->overrides != NULL) { + for(i=0; cbdata->overrides[i] != NULL; i++) { + slapi_entry_free(cbdata->overrides[i]); + } + slapi_ch_free((void**)&(cbdata->overrides)); + } +} + +void +idview_process_overrides(struct backend_search_cbdata *cbdata, + const char *key, const char *map, const char *domain, + Slapi_Entry *entry) +{ +#define VIEW_TEMPLATE_KEY_MAP_DOMAIN 0 +#define VIEW_TEMPLATE_KEY_MAP_DOMAIN_NEWKEY 3 +#define VIEW_TEMPLATE_MAP_DOMAIN 1 +#define VIEW_TEMPLATE_DOMAIN 2 + /* After view was applied, entry's DN needs to reflect the view */ + const char *dn_template[] = {"%s,%s,cn=%s,cn=views,%s", /* an entry for user or group */ + "%s,cn=%s,cn=views,%s", /* an entry for a map (container for users or groups) */ + "cn=%s,cn=views,%s", /* an entry is a base of the compat tree */ + "%s=%s,%s,cn=%s,cn=views,%s", /* an entry for user or group which RDN was overridden with new value */ + }; + const char *filterout_attrs[] = {"objectclass", "creatorsname", "modifiersname", + "createtimestamp", "modifytimestamp", "parentid", + "entryusn", "entryid", "entrydn", "ipaoriginaluid", + "ipaanchoruuid", "nsuniqueid", "ipasshpubkey", NULL }; + char *new_dn = NULL, *new_key = NULL, *sep = NULL, *new_val = NULL; + char *override_type = NULL; + Slapi_Entry *override_entry = NULL; + Slapi_Attr *anchor = NULL, *id_attr = NULL; + Slapi_Value *anchor_value = NULL, *id_value = NULL; + int i, result, dn_template_id; + + if (cbdata->overrides == NULL) { + /* Only retrieve overrides for the view first time when neccessary */ + idview_get_overrides(cbdata); + if (cbdata->overrides == NULL) + return; + } + + /* 1. See if the entry has ipaAnchorUUID and selected idview has an override for it */ + /* The code below intentionally uses Slapi_Value instead of comparing string values to + * avoid allocating additional memory */ + result = slapi_entry_attr_find(entry, IPA_IDVIEWS_ATTR_ANCHORUUID, &anchor); + if ((result == 0) && (anchor != NULL) && (cbdata->overrides != NULL)) { + result = slapi_attr_first_value(anchor, &anchor_value); + for(i = 0; cbdata->overrides[i] != NULL; i++) { + result = slapi_entry_attr_find(cbdata->overrides[i], IPA_IDVIEWS_ATTR_ANCHORUUID, &id_attr); + if ((result == 0) && (id_attr != NULL)) { + result = slapi_attr_first_value(id_attr, &id_value); + result = slapi_value_compare(id_attr, anchor_value, id_value); + if (result == 0) { + override_entry = cbdata->overrides[i]; + break; + } + } + } + } + + /* 2. If there is indeed an override, replace attribute values except for the ones that should be ignored */ + if (override_entry != NULL) { + Slapi_Attr *override_attr = NULL; + + result = slapi_entry_first_attr(override_entry, &override_attr); + while (result == 0) { + Slapi_ValueSet *override_valueset = NULL; + + /* Filter out override attributes that we don't care about */ + result = slapi_attr_get_type(override_attr, &override_type); + for (i = 0; filterout_attrs[i] != NULL; i++) { + if (strcasecmp(override_type, filterout_attrs[i]) == 0) { + break; + } + } + + if (filterout_attrs[i] == NULL) { + /* Replace the attribute's value with the override or + * add an override value if the attribute didn't exist */ + result = slapi_entry_attr_exists(entry, override_type); + if (result == 1) { + result = slapi_entry_attr_delete(entry, override_type); + } + result = slapi_attr_get_valueset(override_attr, &override_valueset); + result = slapi_entry_add_valueset(entry, override_type, override_valueset); + } + result = slapi_entry_next_attr(override_entry, override_attr, &override_attr); + } + } + + /* 3. If entry has memberUid, we need to replace memberUid values too, if they were overridden */ + override_type = "memberUid"; + result = slapi_entry_attr_find(entry, override_type, &anchor); + if ((result == 0) && (anchor != NULL) && (cbdata->overrides != NULL)) { + int value_idx = 0; + Slapi_ValueSet *new_valueset = slapi_valueset_new(); + + if (new_valueset != NULL) { + /* For each memberUid value, find an override with ipaOriginalUid attribute of the same value */ + value_idx = slapi_attr_first_value(anchor, &anchor_value); + while (value_idx != -1) { + bool_t value_found = FALSE; + for(i = 0; cbdata->overrides[i] != NULL; i++) { + result = slapi_entry_attr_find(cbdata->overrides[i], IPA_IDVIEWS_ATTR_ORIGINALUID, &id_attr); + if ((result == 0) && (id_attr != NULL)) { + result = slapi_attr_first_value(id_attr, &id_value); + result = slapi_value_compare(id_attr, anchor_value, id_value); + if (result == 0) { + /* If there is an override with ipaOriginalUid: <memberUid value>, use its 'uid' value to override */ + result = slapi_entry_attr_find(cbdata->overrides[i], "uid", &id_attr); + if ((result == 0) && (id_attr != NULL)) { + result = slapi_attr_first_value(id_attr, &id_value); + if (result == 0) { + /* finally: we have an override with ipaOriginalUid: <memberUid value> _and_ + * this override is changing the 'uid' attribute so we have something to replace */ + slapi_valueset_add_value(new_valueset, id_value); + value_found = TRUE; + break; + } + } + } + } + } + + if (value_found == FALSE) { + slapi_valueset_add_value(new_valueset, anchor_value); + } + value_idx = slapi_attr_next_value(anchor, value_idx, &anchor_value); + } + + result = slapi_entry_attr_delete(entry, override_type); + result = slapi_entry_add_valueset(entry, override_type, new_valueset); + slapi_valueset_free(new_valueset); + } + } + + /* 4. Even if there were no overrides, since we are serving throught the view, replace DN value */ + dn_template_id = (key == NULL ? 1 : 0) + (map == NULL ? 1 : 0); + switch (dn_template_id) { + case VIEW_TEMPLATE_KEY_MAP_DOMAIN: + /* update RDN with proper value from the entry after overrides were applied */ + sep = strchr(key, '='); + if (sep != NULL) { + sep[0] = '\0'; + new_val = slapi_entry_attr_get_charptr(entry, key); + new_dn = slapi_ch_smprintf(dn_template[VIEW_TEMPLATE_KEY_MAP_DOMAIN_NEWKEY], key, new_val, map, cbdata->idview, domain); + slapi_ch_free_string(&new_val); + sep[0] = '='; + } else { + new_dn = slapi_ch_smprintf(dn_template[dn_template_id], key, map, cbdata->idview, domain); + } + break; + case VIEW_TEMPLATE_MAP_DOMAIN: + new_dn = slapi_ch_smprintf(dn_template[dn_template_id], map, cbdata->idview, domain); + break; + case VIEW_TEMPLATE_DOMAIN: + new_dn = slapi_ch_smprintf(dn_template[dn_template_id], cbdata->idview, domain); + break; + }; + slapi_entry_set_dn(entry, new_dn); +} + +void +idview_replace_target_dn(char **target, char **idview) +{ + char *idview_p = NULL; + char *cnviews = NULL; + char *new_target = NULL; + + cnviews = strstr(*target, ",cn=views,"); + if (cnviews != NULL && cnviews != *target) { + cnviews[0] = '\0'; + idview_p = strrchr(*target, ','); + if (idview_p == NULL) { + idview_p = *target; + } else { + idview_p++; + } + if (strstr(idview_p, "cn=") != idview_p) { + cnviews[0] = ','; + return; + } + *idview = slapi_ch_strdup(&idview_p[3]); + if (idview_p != *target) { + idview_p[0] = '\0'; + new_target = slapi_ch_smprintf("%s%s", *target, cnviews+10); + idview_p--; + idview_p[0] = ','; + } else { + new_target = slapi_ch_smprintf("%s", cnviews+10); + } + cnviews[0] = ','; + *target = new_target; + } +} + +static int +idview_process_filter_cb(Slapi_Filter *filter, const char *filter_type, struct berval *bval, struct backend_search_filter_config *config) +{ + int res, i; + Slapi_Value *filter_val, *value, *anchor_val; + Slapi_Attr *anchor, *attr = NULL; + struct backend_search_cbdata *cbdata = (struct backend_search_cbdata *) config->callback_data; + + if (cbdata == NULL || cbdata->idview == NULL) { + return SLAPI_FILTER_SCAN_CONTINUE; + } + + if (filter_type == NULL || config->name == NULL) { + return SLAPI_FILTER_SCAN_CONTINUE; + } + + if (cbdata->overrides == NULL) { + /* Only retrieve overrides for the view first time when neccessary */ + idview_get_overrides(cbdata); + } + + if (cbdata->overrides == NULL) { + return SLAPI_FILTER_SCAN_CONTINUE; + } + + filter_val = slapi_value_new_berval(bval); + + /* If filter contains an attribute name which is overridden in the view and filter value + * corresponds to the override, replace the filter by (ipaAnchorUUID=...) from the override + * to point to the original because otherwise an entry will not be found in the slapi-nis map */ + for(i=0; cbdata->overrides[i] != NULL; i++) { + res = slapi_entry_attr_find(cbdata->overrides[i], filter_type, &attr); + if ((res == 0) && (attr != NULL)) { + res = slapi_attr_first_value(attr, &value); + res = slapi_value_compare(attr, value, filter_val); + if (res == 0) { + /* For uid overrides we should have ipaOriginalUID in the override */ + if (strcasecmp(filter_type, "uid") == 0) { + res = slapi_entry_attr_find(cbdata->overrides[i], IPA_IDVIEWS_ATTR_ORIGINALUID, &anchor); + if (res == 0) { + res = slapi_attr_first_value(anchor, &anchor_val); + slapi_ber_bvdone(bval); + slapi_ber_bvcpy(bval, slapi_value_get_berval(anchor_val)); + config->override_found = TRUE; + break; + } + } + + /* otherwise, use ipaAnchorUUID value */ + res = slapi_entry_attr_find(cbdata->overrides[i], IPA_IDVIEWS_ATTR_ANCHORUUID, &anchor); + if (res == 0) { + res = slapi_attr_first_value(anchor, &anchor_val); + slapi_filter_changetype(filter, IPA_IDVIEWS_ATTR_ANCHORUUID); + slapi_ber_bvdone(bval); + slapi_ber_bvcpy(bval, slapi_value_get_berval(anchor_val)); + config->override_found = TRUE; + break; + } + + } + } + } + + slapi_value_free(&filter_val); + + return SLAPI_FILTER_SCAN_CONTINUE; + +} + +/* Traverse through the filter and replace overridden attribute/value pairs with references to the original + * entries. This allows to properly handle overrides of uid and cn attributes where searches look like + * (&(objectclass=posixAccount)(uid=foobar)) -- if uid=foobar is part of an override for uid=admin, we need + * to point back to uid=admin to be able to find original entry in the slapi-nis cache. + * + * Note that in reality we don't use original value of the uid/cn attribue. Instead, we use ipaAnchorUUID + * to refer to the original entry. */ +extern char * +slapi_filter_to_string( const struct slapi_filter *f, char *buf, size_t bufsize ); +void +idview_replace_filter(struct backend_search_cbdata *cbdata) +{ + struct backend_search_filter_config config = + {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, NULL, NULL}; + int res = 0; + + if (cbdata->idview == NULL) { + return; + } + + config.callback = idview_process_filter_cb; + config.callback_data = cbdata; + + /* Ignore the return code as it will always be SLAPI_FILTER_SCAN_NO_MORE */ + res = backend_analyze_search_filter(cbdata->filter, &config); + + if (config.name != NULL) { + slapi_ch_free_string(&config.name); + } + +} diff --git a/src/back-sch-nss.c b/src/back-sch-nss.c index f192bb9..26d4b8c 100644 --- a/src/back-sch-nss.c +++ b/src/back-sch-nss.c @@ -52,16 +52,20 @@ #include "back-sch.h" #include "format.h" -struct backend_search_filter_config { - bool_t search_user; - bool_t search_group; - bool_t search_uid; - bool_t search_gid; - bool_t search_members; - bool_t name_set; - bool_t wrong_search; - char *name; -}; +static int +bvstrprefix(const struct berval *bval, const char *s) +{ + size_t len; + int c; + + len = strlen(s); + if (len < bval->bv_len) { + return strncasecmp(bval->bv_val, s, len) != 0; + } + + return 1; + +} static int bvstrcasecmp(const struct berval *bval, const char *s) @@ -115,6 +119,14 @@ backend_search_filter_has_cn_uid(Slapi_Filter *filter, void *arg) } else if ((0 == strcasecmp(filter_type, "objectClass")) && (0 == bvstrcasecmp(bval, "shadowAccount"))) { config->wrong_search = TRUE; +#ifdef HAVE_SSS_NSS_IDMAP +#ifdef USE_IPA_IDVIEWS + } else if ((0 == strcasecmp(filter_type, "ipaAnchorUUID")) && + (0 == bvstrprefix(bval, ":SID:S-"))) { + config->search_sid = TRUE; + config->name_set = TRUE; +#endif +#endif } if ((NULL == config->name) && config->name_set) { @@ -127,10 +139,15 @@ backend_search_filter_has_cn_uid(Slapi_Filter *filter, void *arg) } } + if (config->callback != NULL) { + return config->callback(filter, filter_type, bval, config); + } + if ((config->search_uid || config->search_gid || config->search_user || - config->search_group) && (config->name != NULL)) { + config->search_group || + config->search_sid) && (config->name != NULL)) { return SLAPI_FILTER_SCAN_STOP; } return SLAPI_FILTER_SCAN_CONTINUE; @@ -211,21 +228,21 @@ backend_make_user_entry_from_nsswitch_passwd(struct passwd *pwd, slapi_entry_add_string(entry, "objectClass", "posixAccount"); slapi_entry_add_string(entry, - "objectClass", "extensibleObject"); - slapi_entry_add_string(entry, "uid", pwd->pw_name); slapi_entry_attr_set_uint(entry, "uidNumber", pwd->pw_uid); slapi_entry_attr_set_uint(entry, "gidNumber", pwd->pw_gid); - slapi_entry_add_string(entry, - "gecos", pwd->pw_gecos); if (strlen(pwd->pw_gecos) > 0) { slapi_entry_add_string(entry, "cn", pwd->pw_gecos); + slapi_entry_add_string(entry, + "gecos", pwd->pw_gecos); } else { slapi_entry_add_string(entry, "cn", pwd->pw_name); + slapi_entry_add_string(entry, + "gecos", pwd->pw_name); } slapi_entry_add_string(entry, @@ -240,7 +257,20 @@ backend_make_user_entry_from_nsswitch_passwd(struct passwd *pwd, #ifdef HAVE_SSS_NSS_IDMAP rc = sss_nss_getsidbyid(pwd->pw_uid, &sid_str, &id_type); if ((rc == 0) && (sid_str != NULL)) { +#ifdef USE_IPA_IDVIEWS + char *anchor = NULL; + /* For overrides of AD users to work correctly, we need to generate + * ipaAnchorUUID value so that idviews can be properly searched for the override */ + anchor = slapi_ch_smprintf(":SID:%s", sid_str); + if (anchor != NULL) { + slapi_entry_add_string(entry, "objectClass", "ipaOverrideTarget"); + slapi_entry_add_string(entry, "ipaAnchorUUID", anchor); + slapi_ch_free_string(&anchor); + } +#else + slapi_entry_add_string(entry, "objectClass", "extensibleObject"); slapi_entry_add_string(entry, "ipaNTSecurityIdentifier", sid_str); +#endif free(sid_str); } #endif @@ -335,8 +365,6 @@ backend_make_group_entry_from_nsswitch_group(struct group *grp, slapi_entry_add_string(entry, "objectClass", "posixGroup"); slapi_entry_add_string(entry, - "objectClass", "extensibleObject"); - slapi_entry_add_string(entry, "cn", grp->gr_name); slapi_entry_attr_set_uint(entry, "gidNumber", grp->gr_gid); @@ -352,7 +380,20 @@ backend_make_group_entry_from_nsswitch_group(struct group *grp, #ifdef HAVE_SSS_NSS_IDMAP rc = sss_nss_getsidbyid(grp->gr_gid, &sid_str, &id_type); if ((rc == 0) && (sid_str != NULL)) { +#ifdef USE_IPA_IDVIEWS + char *anchor = NULL; + /* For overrides of AD users to work correctly, we need to generate + * ipaAnchorUUID value so that idviews can be properly searched for the override */ + anchor = slapi_ch_smprintf(":SID:%s", sid_str); + if (anchor != NULL) { + slapi_entry_add_string(entry, "objectClass", "ipaOverrideTarget"); + slapi_entry_add_string(entry, "ipaAnchorUUID", anchor); + slapi_ch_free_string(&anchor); + } +#else + slapi_entry_add_string(entry, "objectClass", "extensibleObject"); slapi_entry_add_string(entry, "ipaNTSecurityIdentifier", sid_str); +#endif free(sid_str); } #endif @@ -558,6 +599,16 @@ nsswitch_type_to_name(enum sch_search_nsswitch_t type) return "(unknown)"; } +int +backend_analyze_search_filter(Slapi_Filter *filter, struct backend_search_filter_config *config) +{ + int result, rc; + result = slapi_filter_apply(filter, + backend_search_filter_has_cn_uid, + config, &rc); + return (result != SLAPI_FILTER_SCAN_STOP) ? 1 : 0; +} + /* Check if the filter is one (like uid=<value>) that should trigger an * nsswitch lookup, and if it is, make a note that we should perform such a * lookup. */ @@ -566,15 +617,15 @@ backend_search_nsswitch(struct backend_set_data *set_data, struct backend_search_cbdata *cbdata) { int result, rc; - struct backend_search_filter_config config = {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL}; + struct backend_search_filter_config config = + {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, NULL, NULL}; struct backend_staged_search *staged = NULL; char *idptr = NULL; unsigned long id; /* First, we search the filter to see if it includes a cn|uid=<value> test. */ - result = slapi_filter_apply(cbdata->filter, - backend_search_filter_has_cn_uid, &config, &rc); - if ((result != SLAPI_FILTER_SCAN_STOP)) { + result = backend_analyze_search_filter(cbdata->filter, &config); + if (result != 0) { return; } @@ -624,6 +675,7 @@ backend_search_nsswitch(struct backend_set_data *set_data, staged->type = cbdata->check_nsswitch; staged->name = config.name; /* takes ownership */ staged->is_id = config.search_gid || config.search_uid; + staged->is_sid = config.search_sid; staged->search_members = config.search_members; staged->next = cbdata->staged; @@ -649,6 +701,23 @@ backend_retrieve_from_nsswitch(struct backend_staged_search *staged, { Slapi_Entry **entries; +#ifdef HAVE_SSS_NSS_IDMAP + if (staged->is_sid) { + char *name = NULL; + enum sss_id_type id_type; + /* we expect name to be a SID prefixed with :SID: */ + int result = sss_nss_getnamebysid(staged->name+5, &name, &id_type); + if (result == 0) { + staged->is_sid = FALSE; + staged->is_id = FALSE; + + slapi_ch_free_string(&staged->name); + staged->name = slapi_ch_strdup(name); + free(name); + } + } +#endif + if (((staged->type == SCH_NSSWITCH_GROUP) && staged->search_members) && (NULL != staged->name)) { entries = backend_retrieve_group_list_from_nsswitch(staged->name, staged->container_sdn, diff --git a/src/back-sch.c b/src/back-sch.c index 78f2627..27d5101 100644 --- a/src/back-sch.c +++ b/src/back-sch.c @@ -259,15 +259,6 @@ backend_set_config_read_config(struct plugin_state *state, Slapi_Entry *e, free(nsswitch_min_id); } - if (ret.check_nsswitch != SCH_NSSWITCH_NONE) { - /* If we're adding nsswitch-based entries to this map, make - * sure that we copy the schema-compat-origin and SID - * attributes, so that we can read the former during the BIND - * callback. */ - backend_shr_add_strlist(&ret.attribute_format, "objectClass=%ifeq(\"%{ipaNTSecurityIdentifier}\",\"\",\"\",\"extensibleObject\")"); - backend_shr_add_strlist(&ret.attribute_format, "ipaNTSecurityIdentifier=%{ipaNTSecurityIdentifier}"); - } - *pret = backend_copy_set_config(&ret); if (*pret == NULL) { if (strlen(container) > 0) { @@ -1005,6 +996,7 @@ backend_search_entry_cb(const char *domain, const char *map, bool_t secure, void *backend_data, void *cb_data) { Slapi_DN *sdn; + Slapi_Entry *entry; struct backend_search_cbdata *cbdata; struct backend_entry_data *entry_data; int result; @@ -1043,9 +1035,25 @@ backend_search_entry_cb(const char *domain, const char *map, bool_t secure, cbdata->state->plugin_desc->spd_id, "search matched %s\n", slapi_sdn_get_ndn(sdn)); - slapi_send_ldap_search_entry(cbdata->pb, entry_data->e, NULL, + entry = entry_data->e; +#ifdef USE_IPA_IDVIEWS + entry = slapi_entry_dup(entry_data->e); + if (cbdata->idview != NULL) { + idview_process_overrides(cbdata, key, map, domain, entry); + } + + if (slapi_entry_attr_exists(entry, IPA_IDVIEWS_ATTR_ANCHORUUID) == 1) { + slapi_entry_attr_delete(entry, IPA_IDVIEWS_ATTR_ANCHORUUID); + slapi_entry_delete_string(entry, "objectClass", "ipaOverrideTarget"); + } +#endif + slapi_send_ldap_search_entry(cbdata->pb, entry, NULL, cbdata->attrs, cbdata->attrsonly); cbdata->n_entries++; + + if (entry != entry_data->e) { + slapi_entry_free(entry); + } break; } @@ -1104,6 +1112,13 @@ backend_search_set_cb(const char *group, const char *set, bool_t flag, slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "search matched %s\n", ndn); +#ifdef USE_IPA_IDVIEWS + if (cbdata->idview != NULL) { + idview_process_overrides(cbdata, NULL, + set_data->common.set, + set_data->common.group, set_entry); + } +#endif slapi_send_ldap_search_entry(cbdata->pb, set_entry, NULL, cbdata->attrs, cbdata->attrsonly); @@ -1216,6 +1231,11 @@ backend_search_group_cb(const char *group, void *cb_data) slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "search matched %s\n", group); +#ifdef USE_IPA_IDVIEWS + if (cbdata->idview != NULL) { + idview_process_overrides(cbdata, NULL, NULL, group, group_entry); + } +#endif slapi_send_ldap_search_entry(cbdata->pb, group_entry, NULL, cbdata->attrs, cbdata->attrsonly); @@ -1313,11 +1333,16 @@ backend_search_cb(Slapi_PBlock *pb) cbdata.n_entries = 0; cbdata.staged = NULL; cbdata.cur_staged = NULL; + cbdata.idview = NULL; + cbdata.overrides = NULL; /* Okay, we can search. */ slapi_log_error(SLAPI_LOG_PLUGIN, cbdata.state->plugin_desc->spd_id, "searching from \"%s\" for \"%s\" with scope %d%s\n", cbdata.target, cbdata.strfilter, cbdata.scope, backend_sch_scope_as_string(cbdata.scope)); +#ifdef USE_IPA_IDVIEWS + idview_replace_target_dn(&cbdata.target, &cbdata.idview); +#endif cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target); /* Check if there's a backend handling this search. */ if (!slapi_be_exist(cbdata.target_dn)) { @@ -1326,10 +1351,21 @@ backend_search_cb(Slapi_PBlock *pb) "slapi_be_exists(\"%s\") = 0, " "ignoring search\n", cbdata.target); slapi_sdn_free(&cbdata.target_dn); + if (cbdata.idview != NULL) { + slapi_ch_free_string(&cbdata.target); + } + slapi_ch_free_string(&cbdata.idview); +#ifdef USE_IPA_IDVIEWS + idview_free_overrides(&cbdata); +#endif return 0; } + /* Walk the list of groups. */ wrap_inc_call_level(); +#ifdef USE_IPA_IDVIEWS + idview_replace_filter(&cbdata); +#endif if (map_rdlock() == 0) { map_data_foreach_domain(cbdata.state, backend_search_group_cb, &cbdata); @@ -1468,6 +1504,13 @@ backend_search_cb(Slapi_PBlock *pb) cbdata.n_entries, NULL); } slapi_sdn_free(&cbdata.target_dn); + if (cbdata.idview != NULL) { + slapi_ch_free_string(&cbdata.target); + } + slapi_ch_free_string(&cbdata.idview); +#ifdef USE_IPA_IDVIEWS + idview_free_overrides(&cbdata); +#endif free(cbdata.closest_match); free(cbdata.text); return cbdata.answer ? -1 : 0; @@ -1525,6 +1568,7 @@ static void backend_locate(Slapi_PBlock *pb, struct backend_entry_data **data, const char **group, const char**set) { struct backend_locate_cbdata cbdata; + char *idview = NULL; slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &cbdata.state); if (cbdata.state->plugin_base == NULL) { @@ -1533,6 +1577,9 @@ backend_locate(Slapi_PBlock *pb, struct backend_entry_data **data, const char ** return; } slapi_pblock_get(pb, SLAPI_TARGET_DN, &cbdata.target); +#ifdef USE_IPA_IDVIEWS + idview_replace_target_dn(&cbdata.target, &idview); +#endif cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target); cbdata.entry_data = NULL; cbdata.entry_group = NULL; @@ -1542,6 +1589,10 @@ backend_locate(Slapi_PBlock *pb, struct backend_entry_data **data, const char ** *group = cbdata.entry_group; *set = cbdata.entry_set; slapi_sdn_free(&cbdata.target_dn); + if (idview != NULL) { + slapi_ch_free_string(&cbdata.target); + } + slapi_ch_free_string(&idview); } /* Check if the target DN is part of this group's tree. If it is, return an diff --git a/src/back-sch.h b/src/back-sch.h index 2f4a3df..9f0b201 100644 --- a/src/back-sch.h +++ b/src/back-sch.h @@ -55,6 +55,7 @@ struct backend_staged_search { struct backend_set_data *set_data; enum sch_search_nsswitch_t type; bool_t is_id; + bool_t is_sid; /* if search is by ipaAnchorUUID beginning with :SID:S-... */ bool_t search_members; char *name; char *container_sdn; @@ -69,6 +70,8 @@ struct backend_search_cbdata { Slapi_PBlock *pb; struct plugin_state *state; char *target, *strfilter, **attrs; + char *idview; + Slapi_Entry **overrides; int scope, sizelimit, timelimit, attrsonly; bool_t check_access; enum sch_search_nsswitch_t check_nsswitch; @@ -87,6 +90,31 @@ struct backend_search_cbdata { struct backend_staged_search *cur_staged; }; +struct backend_search_filter_config { + bool_t search_user; + bool_t search_group; + bool_t search_uid; + bool_t search_gid; + bool_t search_sid; + bool_t search_members; + bool_t name_set; + bool_t wrong_search; + bool_t override_found; + char *name; + /* If callback is defined, it is called on each filter after analyzing it. + * Return code of the callback is directly returned to slapi_filter_apply() */ + int (*callback)(Slapi_Filter *filter, const char *filter_type, struct berval *bval, struct backend_search_filter_config *config); + void *callback_data; +}; + +/* Analyzes the filter to decide what kind of NSS search is it + * Returns 0 on success, 1 on failure + * struct backend_search_filter_config is populated with information about the filter + * config.name should be freed with slapi_ch_free_string() + */ + +int backend_analyze_search_filter(Slapi_Filter *filter, struct backend_search_filter_config *config); + void backend_search_nsswitch(struct backend_set_data *set_data, struct backend_search_cbdata *cbdata); @@ -95,4 +123,14 @@ bool_t backend_retrieve_from_nsswitch(struct backend_staged_search *staged, int backend_sch_do_pam_auth(Slapi_PBlock *pb, const char *username); +#ifdef USE_IPA_IDVIEWS +void idview_get_overrides(struct backend_search_cbdata *cbdata); +void idview_free_overrides(struct backend_search_cbdata *cbdata); +void idview_process_overrides(struct backend_search_cbdata *cbdata, + const char *key, const char *map, const char *domain, + Slapi_Entry *entry); +void idview_replace_target_dn(char **target, char **idview); +void idview_replace_filter(struct backend_search_cbdata *cbdata); +#endif + #endif |