/** BEGIN COPYRIGHT BLOCK * 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. * * In addition, as a special exception, Red Hat, Inc. gives You the additional * right to link the code of this Program with code not covered under the GNU * General Public License ("Non-GPL Code") and to distribute linked combinations * including the two, subject to the limitations in this paragraph. Non-GPL Code * permitted under this exception must only link to the code of this Program * through those well defined interfaces identified in the file named EXCEPTION * found in the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline functions from * the Approved Interfaces without causing the resulting work to be covered by * the GNU General Public License. Only Red Hat, Inc. may make changes or * additions to the list of Approved Interfaces. You must obey the GNU General * Public License in all respects for all of the Program code and other code used * in conjunction with the Program except the Non-GPL Code covered by this * exception. If you modify this file, you may extend this exception to your * version of the file, but you are not obligated to do so. If you do not wish to * provide this exception without modification, you must delete this exception * statement from your version and license this file solely under the GPL without * exception. * * * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2005 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif /* * uid.c * * Implements a directory server pre-operation plugin to test * attributes for uniqueness within a defined subtree in the * directory. * * Called uid.c since the original purpose of the plugin was to * check the uid attribute in user entries. */ #include #include #include #include "plugin-utils.h" #include "nspr.h" #if defined( LDAP_DEBUG ) && !defined( DEBUG ) #define DEBUG #endif #define UNTAGGED_PARAMETER 12 /* Quoting routine - this should be in a library somewhere (slapi?) */ int ldap_quote_filter_value( char *value, int len, char *out, int maxLen, int *outLen); static int search_one_berval(Slapi_DN *baseDN, const char **attrNames, const struct berval *value, const char *requiredObjectClass, Slapi_DN *target, Slapi_DN **excludes); /* * ISSUES: * How should this plugin handle ACL issues? It seems wrong to reject * adds and modifies because there is already a conflicting UID, when * the request would have failed because of an ACL check anyway. * * This code currently defines a maximum filter string size of 512. Is * this large enough? * * This code currently does not quote the value portion of the filter as * it is created. This is a bug. */ /* */ #define BEGIN do { #define END } while(0); /* * Slapi plugin descriptor */ static char *plugin_name = "NSUniqueAttr"; static Slapi_PluginDesc pluginDesc = { "NSUniqueAttr", VENDOR, DS_PACKAGE_VERSION, "Enforce unique attribute values" }; static void* plugin_identity = NULL; typedef struct attr_uniqueness_config { const char **attrs; char *attr_friendly; Slapi_DN **subtrees; Slapi_DN **exclude_subtrees; PRBool unique_in_all_subtrees; char *top_entry_oc; char *subtree_entries_oc; struct attr_uniqueness_config *next; } attr_uniqueness_config_t; #define ATTR_UNIQUENESS_ATTRIBUTE_NAME "uniqueness-attribute-name" #define ATTR_UNIQUENESS_SUBTREES "uniqueness-subtrees" #define ATTR_UNIQUENESS_EXCLUDE_SUBTREES "uniqueness-exclude-subtrees" #define ATTR_UNIQUENESS_ACROSS_ALL_SUBTREES "uniqueness-across-all-subtrees" #define ATTR_UNIQUENESS_TOP_ENTRY_OC "uniqueness-top-entry-oc" #define ATTR_UNIQUENESS_SUBTREE_ENTRIES_OC "uniqueness-subtree-entries-oc" static int getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass, char **requiredObjectClass); static struct attr_uniqueness_config *uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry); /* * More information about constraint failure */ static char *moreInfo = "Another entry with the same attribute value already exists (attribute: \"%s\")"; static void free_uniqueness_config(struct attr_uniqueness_config *config) { int i; for (i = 0; config->attrs && config->attrs[i]; i++) { slapi_ch_free_string((char **) &(config->attrs[i])); } for (i = 0; config->subtrees && config->subtrees[i]; i++) { slapi_sdn_free(&config->subtrees[i]); } for (i = 0; config->exclude_subtrees && config->exclude_subtrees[i]; i++) { slapi_sdn_free(&config->exclude_subtrees[i]); } slapi_ch_free((void **) &config->subtrees); slapi_ch_free((void **) &config->exclude_subtrees); slapi_ch_free_string((char **) &config->top_entry_oc); slapi_ch_free_string((char **) &config->subtree_entries_oc); } /* * New styles: * ---------- * * uniqueness-attribute-name: uid * uniqueness-subtrees: dc=people,dc=example,dc=com * uniqueness-subtrees: dc=sales, dc=example,dc=com * uniqueness-exclude-subtrees: dc=machines, dc=examples, dc=com * uniqueness-across-all-subtrees: on * * or * * uniqueness-attribute-name: uid * uniqueness-top-entry-oc: organizationalUnit * uniqueness-subtree-entries-oc: person * * If both are present: * - uniqueness-subtrees * - uniqueness-top-entry-oc/uniqueness-subtree-entries-oc * Then uniqueness-subtrees has the priority * * Old styles: * ---------- * * nsslapd-pluginarg0: uid * nsslapd-pluginarg1: dc=people,dc=example,dc=com * nsslapd-pluginarg2: dc=sales, dc=example,dc=com * * or * * nsslapd-pluginarg0: attribute=uid * nsslapd-pluginarg1: markerobjectclass=organizationalUnit * nsslapd-pluginarg2: requiredobjectclass=person * * From a Slapi_Entry of the config entry, it creates a attr_uniqueness_config. * It returns a (attr_uniqueness_config *) if the configuration is valid * Else it returns NULL */ static struct attr_uniqueness_config * uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry) { attr_uniqueness_config_t *tmp_config = NULL; char **values = NULL; int argc; char **argv = NULL; int rc = SLAPI_PLUGIN_SUCCESS; int i; int attrLen = 0; char *fp; int nb_subtrees = 0; if (config_entry == NULL) { rc = SLAPI_PLUGIN_FAILURE; goto done; } /* We are going to fill tmp_config in a first phase */ if ((tmp_config = (attr_uniqueness_config_t *) slapi_ch_calloc(1, sizeof (attr_uniqueness_config_t))) == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "load_config failed to allocate configuration\n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } else { /* set these to -1 for config validation */ } /* Check if this is new/old config style */ slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); if (argc == 0) { /* This is new config style * uniqueness-attribute-name: uid * uniqueness-subtrees: dc=people,dc=example,dc=com * uniqueness-subtrees: dc=sales, dc=example,dc=com * uniqueness-across-all-subtrees: on * * or * * uniqueness-attribute-name: uid * uniqueness-top-entry-oc: organizationalUnit * uniqueness-subtree-entries-oc: person */ /* Attribute name of the attribute we are going to check value uniqueness */ values = slapi_entry_attr_get_charray(config_entry, ATTR_UNIQUENESS_ATTRIBUTE_NAME); if (values) { for (i = 0; values && values[i]; i++); tmp_config->attrs = (const char **) slapi_ch_calloc(i + 1, sizeof(char *)); for (i = 0; values && values[i]; i++) { tmp_config->attrs[i] = slapi_ch_strdup(values[i]); slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "Adding attribute %s to uniqueness set\n", tmp_config->attrs[i]); } slapi_ch_array_free(values); values = NULL; } /* Subtrees where uniqueness is tested */ values = slapi_entry_attr_get_charray(config_entry, ATTR_UNIQUENESS_SUBTREES); if (values) { for (i = 0; values && values[i]; i++); /* slapi_ch_calloc never returns NULL unless the 2 args are 0 or negative. */ tmp_config->subtrees = (Slapi_DN **) slapi_ch_calloc(i + 1, sizeof (Slapi_DN *)); /* copy the valid subtree DN into the config */ for (i = 0, nb_subtrees = 0; values && values[i]; i++) { if (slapi_dn_syntax_check(pb, values[i], 1)) { /* syntax check failed */ slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Config info: Invalid DN (skipped): %s\n", values[i]); continue; } tmp_config->subtrees[nb_subtrees] = slapi_sdn_new_dn_byval(values[i]); nb_subtrees++; } slapi_ch_array_free(values); values = NULL; } /* Subtrees where uniqueness is explicitly ignored */ values = slapi_entry_attr_get_charray(config_entry, ATTR_UNIQUENESS_EXCLUDE_SUBTREES); if (values) { for (i = 0; values && values[i]; i++); /* slapi_ch_calloc never returns NULL unless the 2 args are 0 or negative. */ tmp_config->exclude_subtrees = (Slapi_DN **) slapi_ch_calloc(i + 1, sizeof (Slapi_DN *)); /* copy the valid subtree DN into the config */ for (i = 0, nb_subtrees = 0; values && values[i]; i++) { if (slapi_dn_syntax_check(pb, values[i], 1)) { /* syntax check failed */ slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Config info: Invalid DN (skipped): %s\n", values[i]); continue; } tmp_config->exclude_subtrees[nb_subtrees] = slapi_sdn_new_dn_byval(values[i]); nb_subtrees++; } slapi_ch_array_free(values); values = NULL; } /* Uniqueness may be enforced accross all the subtrees, by default it is not */ tmp_config->unique_in_all_subtrees = slapi_entry_attr_get_bool(config_entry, ATTR_UNIQUENESS_ACROSS_ALL_SUBTREES); /* enforce uniqueness only if the modified entry has this objectclass */ tmp_config->top_entry_oc = slapi_entry_attr_get_charptr(config_entry, ATTR_UNIQUENESS_TOP_ENTRY_OC); /* enforce uniqueness, in the modified entry subtree, only to entries having this objectclass */ tmp_config->subtree_entries_oc = slapi_entry_attr_get_charptr(config_entry, ATTR_UNIQUENESS_SUBTREE_ENTRIES_OC); } else { int result; char *attrName = NULL; char *markerObjectClass = NULL; char *requiredObjectClass = NULL; /* using the old style of configuration */ result = getArguments(pb, &attrName, &markerObjectClass, &requiredObjectClass); if (LDAP_OPERATIONS_ERROR == result) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "Config fail: unable to parse old style\n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } if (UNTAGGED_PARAMETER == result) { /* This is * nsslapd-pluginarg0: uid * nsslapd-pluginarg1: dc=people,dc=example,dc=com * nsslapd-pluginarg2: dc=sales, dc=example,dc=com * * config attribute are in argc/argv * * attrName is set * markerObjectClass/requiredObjectClass are NOT set */ if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc) || slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv)) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "Config fail: Only attribute name is valid\n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } /* Store attrName in the config */ tmp_config->attrs = (const char **) slapi_ch_calloc(1, sizeof(char *)); tmp_config->attrs[0] = slapi_ch_strdup(attrName); argc--; argv++; /* First argument was attribute name and remaining are subtrees */ /* Store the subtrees */ nb_subtrees = 0; if ((tmp_config->subtrees = (Slapi_DN **) slapi_ch_calloc(argc + 1, sizeof (Slapi_DN *))) == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Config info: Fail to allocate subtree array\n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } for (; argc > 0; argc--, argv++) { if (slapi_dn_syntax_check(pb, *argv, 1)) { /* syntax check failed */ slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Config info: Invalid DN (skipped): %s\n", *argv); continue; } tmp_config->subtrees[nb_subtrees] = slapi_sdn_new_dn_byval(*argv); nb_subtrees++; } /* this interface does not configure accross subtree uniqueness*/ tmp_config->unique_in_all_subtrees = PR_FALSE; /* Not really usefull, but it clarifies the config */ tmp_config->subtree_entries_oc = NULL; tmp_config->top_entry_oc = NULL; } else { /* This is * nsslapd-pluginarg0: attribute=uid * nsslapd-pluginarg1: markerobjectclass=organizationalUnit * nsslapd-pluginarg2: requiredobjectclass=person * * config attributes are in * - attrName * - markerObjectClass * - requiredObjectClass */ /* Store attrName in the config */ tmp_config->attrs = (const char **) slapi_ch_calloc(1, sizeof(char *)); tmp_config->attrs[0] = slapi_ch_strdup(attrName); /* There is no subtrees */ tmp_config->subtrees = NULL; /* this interface does not configure accross subtree uniqueness*/ tmp_config->unique_in_all_subtrees = PR_FALSE; /* set the objectclasses retrieved by getArgument */ tmp_config->subtree_entries_oc = slapi_ch_strdup(requiredObjectClass); tmp_config->top_entry_oc = slapi_ch_strdup(markerObjectClass); } } /* Time to check that the new configuration is valid */ /* Check that we have 1 or more value */ if (tmp_config->attrs == NULL) { slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Config info: attribute name not defined \n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } /* If the config is valid, prepare the friendly string for error messages */ for (i = 0; tmp_config->attrs && (tmp_config->attrs)[i]; i++) { attrLen += strlen((tmp_config->attrs)[i]) + 1; } tmp_config->attr_friendly = (char *) slapi_ch_calloc(attrLen, sizeof(char)); fp = tmp_config->attr_friendly; for (i = 0; tmp_config->attrs && (tmp_config->attrs)[i]; i++) { strcpy(fp, (tmp_config->attrs)[i] ); fp += strlen((tmp_config->attrs)[i]); strcpy(fp, " "); fp++; } /* Only ensure subtrees are set, no need to check excluded subtrees as setting exclusion without actual subtrees make little sense */ if (tmp_config->subtrees == NULL) { /* Uniqueness is enforced on entries matching objectclass */ if (tmp_config->subtree_entries_oc == NULL) { slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Config info: objectclass for subtree entries is not defined\n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } } else if (tmp_config->subtrees[0] == NULL) { /* Uniqueness is enforced on subtrees but none are defined */ slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Config info: No valid subtree is defined \n"); rc = SLAPI_PLUGIN_FAILURE; goto done; } done: if (rc != SLAPI_PLUGIN_SUCCESS) { if (tmp_config) { free_uniqueness_config(tmp_config); slapi_ch_free((void **) &tmp_config); } return NULL; } else { return tmp_config; } } static void freePblock( Slapi_PBlock *spb ) { if ( spb ) { slapi_free_search_results_internal( spb ); slapi_pblock_destroy( spb ); } } /* ------------------------------------------------------------ */ /* * op_error - Record (and report) an operational error. * name changed to uid_op_error so as not to conflict with the external function * of the same name thereby preventing compiler warnings. */ static int uid_op_error(int internal_error) { slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "Internal error: %d\n", internal_error); return LDAP_OPERATIONS_ERROR; } /* ------------------------------------------------------------ */ /* * Create an LDAP search filter from the attribute * name and value supplied. */ static char * create_filter(const char **attributes, const struct berval *value, const char *requiredObjectClass) { char *filter; char *fp; char *max; int *attrLen; int totalAttrLen = 0; int attrCount = 0; int valueLen; int classLen = 0; int filterLen; int i = 0; PR_ASSERT(attributes); /* Compute the length of the required buffer */ for (attrCount = 0; attributes && attributes[attrCount]; attrCount++); attrCount++; attrLen = (int *) slapi_ch_calloc(attrCount, sizeof(int)); for (i = 0; attributes && attributes[i]; i++) { attrLen[i] += strlen(attributes[i]); totalAttrLen += attrLen[i]; } /* if attrCount is 1, attrLen is already corect for usage.*/ if (attrCount > 1) { /* Filter will be (|(attr=value)(attr=value)) */ /* 3 for the (| ) */ /* 3 for each attr for (=) not in attr or value */ totalAttrLen += 3 + (attrCount * 3); } else { totalAttrLen += 3; } if (ldap_quote_filter_value(value->bv_val, value->bv_len, 0, 0, &valueLen)) return 0; if (requiredObjectClass) { classLen = strlen(requiredObjectClass); /* "(&(objectClass=))" == 17 */ filterLen = totalAttrLen + 1 + (valueLen * attrCount) + classLen + 17 + 1; } else { filterLen = totalAttrLen + 1 + (valueLen * attrCount) + 1; } /* Allocate the buffer */ filter = slapi_ch_malloc(filterLen); fp = filter; max = &filter[filterLen]; /* Place AND expression and objectClass in filter */ if (requiredObjectClass) { strcpy(fp, "(&(objectClass="); fp += 15; strcpy(fp, requiredObjectClass); fp += classLen; *fp++ = ')'; } if (attrCount == 1) { *fp++ = '('; /* Place attribute name in filter */ strcpy(fp, attributes[0]); fp += attrLen[0]; /* Place comparison operator */ *fp++ = '='; /* Place value in filter */ if (ldap_quote_filter_value(value->bv_val, value->bv_len, fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; } fp += valueLen; *fp++ = ')'; } else { strcpy(fp, "(|"); fp += 2; for (i = 0; attributes && attributes[i]; i++) { strcpy(fp, "("); fp += 1; /* Place attribute name in filter */ strcpy(fp, attributes[i]); fp += attrLen[i]; /* Place comparison operator */ *fp++ = '='; /* Place value in filter */ if (ldap_quote_filter_value(value->bv_val, value->bv_len, fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; } fp += valueLen; strcpy(fp, ")"); fp += 1; } strcpy(fp, ")"); fp += 1; } /* Close AND expression if a requiredObjectClass was set */ if (requiredObjectClass) { *fp++ = ')'; } /* Terminate */ *fp = 0; slapi_ch_free((void **) &attrLen); return filter; } /* ------------------------------------------------------------ */ /* * search - search a subtree for entries with a named attribute matching * the list of values. An entry matching the 'target' DN is * not considered in the search. * * If 'attr' is NULL, the values are taken from 'values'. * If 'attr' is non-NULL, the values are taken from 'attr'. * * Return: * LDAP_SUCCESS - no matches, or the attribute matches the * target dn. * LDAP_CONSTRAINT_VIOLATION - an entry was found that already * contains the attribute value. * LDAP_OPERATIONS_ERROR - a server failure. */ static int search(Slapi_DN *baseDN, const char **attrNames, Slapi_Attr *attr, struct berval **values, const char *requiredObjectClass, Slapi_DN *target, Slapi_DN **excludes) { int result; #ifdef DEBUG /* Fix this later to print all the attr names */ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "SEARCH baseDN=%s attr=%s target=%s\n", slapi_sdn_get_dn(baseDN), attrNames[0], target?slapi_sdn_get_dn(target):"None"); #endif result = LDAP_SUCCESS; /* If no values, can't possibly be a conflict */ if ( (Slapi_Attr *)NULL == attr && (struct berval **)NULL == values ) return result; /* * Perform the search for each value provided * * Another possibility would be to search for all the values at once. * However, this is more complex (for filter creation) and unique * attributes values are probably only changed one at a time anyway. */ if ( (Slapi_Attr *)NULL != attr ) { Slapi_Value *v = NULL; int vhint = -1; for ( vhint = slapi_attr_first_value( attr, &v ); vhint != -1 && LDAP_SUCCESS == result; vhint = slapi_attr_next_value( attr, vhint, &v )) { result = search_one_berval(baseDN, attrNames, slapi_value_get_berval(v), requiredObjectClass, target, excludes); } } else { for (;*values != NULL && LDAP_SUCCESS == result; values++) { result = search_one_berval(baseDN, attrNames, *values, requiredObjectClass, target, excludes); } } #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "SEARCH result = %d\n", result); #endif return( result ); } static int search_one_berval(Slapi_DN *baseDN, const char **attrNames, const struct berval *value, const char *requiredObjectClass, Slapi_DN *target, Slapi_DN **excludes) { int result; char *filter; Slapi_PBlock *spb; result = LDAP_SUCCESS; /* If no value, can't possibly be a conflict */ if ( (struct berval *)NULL == value ) return result; filter = 0; spb = 0; BEGIN int err; int sres; Slapi_Entry **entries; static char *attrs[] = { "1.1", 0 }; /* Create the filter - this needs to be freed */ filter = create_filter(attrNames, value, requiredObjectClass); #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "SEARCH filter=%s\n", filter); #endif /* Perform the search using the new internal API */ spb = slapi_pblock_new(); if (!spb) { result = uid_op_error(2); break; } slapi_search_internal_set_pb_ext(spb, baseDN, LDAP_SCOPE_SUBTREE, filter, attrs, 0 /* attrs only */, NULL, NULL, plugin_identity, 0 /* actions */); slapi_search_internal_pb(spb); err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_RESULT, &sres); if (err) { result = uid_op_error(3); break; } /* Allow search to report that there is nothing in the subtree */ if (sres == LDAP_NO_SUCH_OBJECT) break; /* Other errors are bad */ if (sres) { result = uid_op_error(3); break; } err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); if (err) { result = uid_op_error(4); break; } /* * Look at entries returned. Any entry found must be the * target entry or the constraint fails. */ for(;*entries;entries++) { #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "SEARCH entry dn=%s\n", slapi_entry_get_dn(*entries)); #endif /* * It is a Constraint Violation if any entry is found, unless * the entry is the target entry (if any). */ if (!target || slapi_sdn_compare(slapi_entry_get_sdn(*entries), target) != 0) { int i; result = LDAP_CONSTRAINT_VIOLATION; if (excludes == NULL || *excludes == NULL) { break; } /* Do the same check for excluded subtrees as resulted entries may have matched them */ for (i = 0;excludes && excludes[i]; i++) { Slapi_DN *entry_dn = slapi_entry_get_sdn(*entries); if (slapi_sdn_issuffix(entry_dn, excludes[i])) { result = LDAP_SUCCESS; break; } } if (result == LDAP_CONSTRAINT_VIOLATION) { break; } } } #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "SEARCH complete result=%d\n", result); #endif END /* Clean-up */ if (spb) { slapi_free_search_results_internal(spb); slapi_pblock_destroy(spb); } slapi_ch_free((void**)&filter); return result; } /* ------------------------------------------------------------ */ /* * searchAllSubtrees - search all subtrees in argv for entries * with a named attribute matching the list of values, by * calling search for each one. * * If 'attr' is NULL, the values are taken from 'values'. * If 'attr' is non-NULL, the values are taken from 'attr'. * * Return: * LDAP_SUCCESS - no matches, or the attribute matches the * target dn. * LDAP_CONSTRAINT_VIOLATION - an entry was found that already * contains the attribute value. * LDAP_OPERATIONS_ERROR - a server failure. */ static int searchAllSubtrees(Slapi_DN **subtrees, Slapi_DN **exclude_subtrees, const char **attrNames, Slapi_Attr *attr, struct berval **values, const char *requiredObjectClass, Slapi_DN *dn, PRBool unique_in_all_subtrees) { int result = LDAP_SUCCESS; int i; if (unique_in_all_subtrees) { PRBool in_a_subtree = PR_FALSE; /* we need to check that the added values of this attribute * are unique in all the monitored subtrees */ /* First check the target entry is in one of * the monitored subtree, so adding 'values' would * violate constraint */ for (i = 0;subtrees && subtrees[i]; i++) { if (slapi_sdn_issuffix(dn, subtrees[i])) { in_a_subtree = PR_TRUE; break; } } if (! in_a_subtree) { return result; } } /* If DN is in the excluded subtrees, we should ignore it in any case, not only * in the case of uniqueness in all subtrees. */ if (exclude_subtrees != NULL) { PRBool in_a_subtree = PR_FALSE; for (i = 0;exclude_subtrees && exclude_subtrees[i]; i++) { if (slapi_sdn_issuffix(dn, exclude_subtrees[i])) { in_a_subtree = PR_TRUE; break; } } if (in_a_subtree) { return result; } } /* * For each DN in the managed list, do uniqueness checking if * the target DN is a subnode in the tree. */ for(i = 0;subtrees && subtrees[i]; i++) { Slapi_DN *sufdn = subtrees[i]; /* * The DN should already be normalized, so we don't have to * worry about that here. */ if (unique_in_all_subtrees || slapi_sdn_issuffix(dn, sufdn)) { result = search(sufdn, attrNames, attr, values, requiredObjectClass, dn, exclude_subtrees); if (result) break; } } return result; } /* ------------------------------------------------------------ */ /* * getArguments - parse invocation parameters * Return: * 0 - success * >0 - error parsing parameters */ static int getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass, char **requiredObjectClass) { int argc; char **argv; /* * Get the arguments */ if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc)) { return uid_op_error(10); } if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv)) { return uid_op_error(11); } /* * Required arguments: attribute and markerObjectClass * Optional argument: requiredObjectClass */ for(;argc > 0;argc--,argv++) { char *param = *argv; char *delimiter = strchr(param, '='); if (NULL == delimiter) { /* Old style untagged parameter */ *attrName = *argv; return UNTAGGED_PARAMETER; } if (strncasecmp(param, "attribute", delimiter-param) == 0) { /* It's OK to set a pointer here, because ultimately it points * inside the argv array of the pblock, which will be staying * arround. */ *attrName = delimiter+1; } else if (strncasecmp(param, "markerobjectclass", delimiter-param) == 0) { *markerObjectClass = delimiter+1; } else if (strncasecmp(param, "requiredobjectclass", delimiter-param) == 0) { *requiredObjectClass = delimiter+1; } } if (!*attrName || !*markerObjectClass) { return uid_op_error(13); } return 0; } /* ------------------------------------------------------------ */ /* * findSubtreeAndSearch - walk up the tree to find an entry with * the marker object class; if found, call search from there and * return the result it returns * * If 'attr' is NULL, the values are taken from 'values'. * If 'attr' is non-NULL, the values are taken from 'attr'. * * Return: * LDAP_SUCCESS - no matches, or the attribute matches the * target dn. * LDAP_CONSTRAINT_VIOLATION - an entry was found that already * contains the attribute value. * LDAP_OPERATIONS_ERROR - a server failure. */ static int findSubtreeAndSearch(Slapi_DN *parentDN, const char **attrNames, Slapi_Attr *attr, struct berval **values, const char *requiredObjectClass, Slapi_DN *target, const char *markerObjectClass, Slapi_DN **excludes) { int result = LDAP_SUCCESS; Slapi_PBlock *spb = NULL; Slapi_DN *curpar = slapi_sdn_new(); Slapi_DN *newpar = NULL; slapi_sdn_get_parent(parentDN, curpar); while (slapi_sdn_get_dn(curpar) != NULL) { if ((spb = dnHasObjectClass(curpar, markerObjectClass))) { freePblock(spb); /* * Do the search. There is no entry that is allowed * to have the attribute already. */ result = search(curpar, attrNames, attr, values, requiredObjectClass, target, excludes); break; } newpar = slapi_sdn_new(); slapi_sdn_copy(curpar, newpar); slapi_sdn_get_parent(newpar, curpar); slapi_sdn_free(&newpar); } slapi_sdn_free(&curpar); return result; } /* ------------------------------------------------------------ */ /* * preop_add - pre-operation plug-in for add */ static int preop_add(Slapi_PBlock *pb) { int result; char *errtext = NULL; const char **attrNames = NULL; char * attr_friendly = NULL; #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n"); #endif result = LDAP_SUCCESS; /* * Do constraint check on the added entry. Set result. */ BEGIN int err; char *markerObjectClass = NULL; char *requiredObjectClass = NULL; Slapi_DN *sdn = NULL; int isupdatedn; Slapi_Entry *e; Slapi_Attr *attr; struct attr_uniqueness_config *config = NULL; int i = 0; /* * If this is a replication update, just be a noop. */ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn); if (err) { result = uid_op_error(50); break; } if (isupdatedn) { break; } slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &config); if (config == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "preop_modrdn fail to retrieve the config\n"); result = LDAP_OPERATIONS_ERROR; break; } /* * Get the arguments */ attrNames = config->attrs; markerObjectClass = config->top_entry_oc; requiredObjectClass = config->subtree_entries_oc; attr_friendly = config->attr_friendly; /* * Get the target DN for this add operation */ err = slapi_pblock_get(pb, SLAPI_ADD_TARGET_SDN, &sdn); if (err) { result = uid_op_error(51); break; } #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD target=%s\n", slapi_sdn_get_dn(sdn)); #endif /* * Get the entry data for this add. Check whether it * contains a value for the unique attribute */ err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); if (err) { result = uid_op_error(52); break; } /* * Check if it contains the required object class */ if (NULL != requiredObjectClass) { if (!entryHasObjectClass(pb, e, requiredObjectClass)) { /* No, so we don't have to do anything */ break; } } for (i = 0; attrNames && attrNames[i]; i++) { err = slapi_entry_attr_find(e, attrNames[i], &attr); if (!err) { /* * Passed all the requirements - this is an operation we * need to enforce uniqueness on. Now find all parent entries * with the marker object class, and do a search for each one. */ if (NULL != markerObjectClass) { /* Subtree defined by location of marker object class */ result = findSubtreeAndSearch(sdn, attrNames, attr, NULL, requiredObjectClass, sdn, markerObjectClass, config->exclude_subtrees); } else { /* Subtrees listed on invocation line */ result = searchAllSubtrees(config->subtrees, config->exclude_subtrees, attrNames, attr, NULL, requiredObjectClass, sdn, config->unique_in_all_subtrees); } if (result != LDAP_SUCCESS) { break; } } } END if (result) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD result %d\n", result); if (result == LDAP_CONSTRAINT_VIOLATION) { errtext = slapi_ch_smprintf(moreInfo, attr_friendly); } else { errtext = slapi_ch_strdup("Error checking for attribute uniqueness."); } /* Send failure to the client */ slapi_send_ldap_result(pb, result, 0, errtext, 0, 0); slapi_ch_free_string(&errtext); } return (result==LDAP_SUCCESS)?0:-1; } static void addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd) { if (*nmods == *capacity) { *capacity += 4; if (*modary) { *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *)); } else { *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *)); } } (*modary)[*nmods] = toadd; (*nmods)++; } /* ------------------------------------------------------------ */ /* * preop_modify - pre-operation plug-in for modify */ static int preop_modify(Slapi_PBlock *pb) { int result = LDAP_SUCCESS; Slapi_PBlock *spb = NULL; LDAPMod **checkmods = NULL; int checkmodsCapacity = 0; char *errtext = NULL; const char **attrNames = NULL; struct attr_uniqueness_config *config = NULL; char *attr_friendly = NULL; #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "MODIFY begin\n"); #endif BEGIN int err; char *markerObjectClass=NULL; char *requiredObjectClass=NULL; LDAPMod **mods; int modcount = 0; int ii; LDAPMod *mod; Slapi_DN *sdn = NULL; int isupdatedn; int i = 0; /* * If this is a replication update, just be a noop. */ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn); if (err) { result = uid_op_error(60); break; } if (isupdatedn) { break; } slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &config); if (config == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "preop_modrdn fail to retrieve the config\n"); result = LDAP_OPERATIONS_ERROR; break; } /* * Get the arguments */ attrNames = config->attrs; markerObjectClass = config->top_entry_oc; requiredObjectClass = config->subtree_entries_oc; attr_friendly = config->attr_friendly; err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); if (err) { result = uid_op_error(61); break; } /* There may be more than one mod that matches e.g. changetype: modify delete: uid uid: balster1950 - add: uid uid: scottg So, we need to first find all mods that contain the attribute which are add or replace ops and are bvalue encoded */ /* find out how many mods meet this criteria */ for(;mods && *mods;mods++) { mod = *mods; for (i = 0; attrNames && attrNames[i]; i++) { if ((slapi_attr_type_cmp(mod->mod_type, attrNames[i], 1) == 0) && /* mod contains target attr */ (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */ (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */ (SLAPI_IS_MOD_ADD(mod->mod_op) || /* mod is add */ SLAPI_IS_MOD_REPLACE(mod->mod_op))) /* mod is replace */ { addMod(&checkmods, &checkmodsCapacity, &modcount, mod); } } } if (modcount == 0) { break; /* no mods to check, we are done */ } /* Get the target DN */ err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET_SDN, &sdn); if (err) { result = uid_op_error(11); break; } /* * Check if it has the required object class */ if (requiredObjectClass && !(spb = dnHasObjectClass(sdn, requiredObjectClass))) { break; } /* * Passed all the requirements - this is an operation we * need to enforce uniqueness on. Now find all parent entries * with the marker object class, and do a search for each one. */ /* * stop checking at first mod that fails the check */ for (ii = 0; (result == 0) && (ii < modcount); ++ii) { mod = checkmods[ii]; if (NULL != markerObjectClass) { /* Subtree defined by location of marker object class */ result = findSubtreeAndSearch(sdn, attrNames, NULL, mod->mod_bvalues, requiredObjectClass, sdn, markerObjectClass, config->exclude_subtrees); } else { /* Subtrees listed on invocation line */ result = searchAllSubtrees(config->subtrees, config->exclude_subtrees, attrNames, NULL, mod->mod_bvalues, requiredObjectClass, sdn, config->unique_in_all_subtrees); } } END slapi_ch_free((void **)&checkmods); freePblock(spb); if (result) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "MODIFY result %d\n", result); if (result == LDAP_CONSTRAINT_VIOLATION) { errtext = slapi_ch_smprintf(moreInfo, attr_friendly); } else { errtext = slapi_ch_strdup("Error checking for attribute uniqueness."); } slapi_send_ldap_result(pb, result, 0, errtext, 0, 0); slapi_ch_free_string(&errtext); } return (result==LDAP_SUCCESS)?0:-1; } /* ------------------------------------------------------------ */ /* * preop_modrdn - Pre-operation call for modify RDN * * Check that the new RDN does not include attributes that * cause a constraint violation */ static int preop_modrdn(Slapi_PBlock *pb) { int result = LDAP_SUCCESS; Slapi_Entry *e = NULL; Slapi_Value *sv_requiredObjectClass = NULL; char *errtext = NULL; const char **attrNames = NULL; struct attr_uniqueness_config *config = NULL; #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "MODRDN begin\n"); #endif BEGIN int err; char *markerObjectClass=NULL; char *requiredObjectClass=NULL; Slapi_DN *sdn = NULL; Slapi_DN *superior; char *rdn; int deloldrdn = 0; int isupdatedn; Slapi_Attr *attr; int i = 0; /* * If this is a replication update, just be a noop. */ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn); if (err) { result = uid_op_error(30); break; } if (isupdatedn) { break; } slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &config); if (config == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "preop_modrdn fail to retrieve the config\n"); result = LDAP_OPERATIONS_ERROR; break; } /* * Get the arguments */ attrNames = config->attrs; markerObjectClass = config->top_entry_oc; requiredObjectClass = config->subtree_entries_oc; /* Create a Slapi_Value for the requiredObjectClass to use * for checking the entry. */ if (requiredObjectClass) { sv_requiredObjectClass = slapi_value_new_string(requiredObjectClass); } /* Get the DN of the entry being renamed */ err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET_SDN, &sdn); if (err) { result = uid_op_error(31); break; } /* Get superior value - unimplemented in 3.0/4.0/5.0 DS */ err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR_SDN, &superior); if (err) { result = uid_op_error(32); break; } /* * No superior means the entry is just renamed at * its current level in the tree. Use the target DN for * determining which managed tree this belongs to */ if (!superior) superior = sdn; /* Get the new RDN - this has the attribute values */ err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn); if (err) { result = uid_op_error(33); break; } #ifdef DEBUG slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "MODRDN newrdn=%s\n", rdn); #endif /* See if the old RDN value is being deleted. */ err = slapi_pblock_get(pb, SLAPI_MODRDN_DELOLDRDN, &deloldrdn); if (err) { result = uid_op_error(34); break; } /* Get the entry that is being renamed so we can make a dummy copy * of what it will look like after the rename. */ err = slapi_search_internal_get_entry(sdn, NULL, &e, plugin_identity); if (err != LDAP_SUCCESS) { result = uid_op_error(35); /* We want to return a no such object error if the target doesn't exist. */ if (err == LDAP_NO_SUCH_OBJECT) { result = err; } break; } /* Apply the rename operation to the dummy entry. */ /* slapi_entry_rename does not expect rdn normalized */ err = slapi_entry_rename(e, rdn, deloldrdn, superior); if (err != LDAP_SUCCESS) { result = uid_op_error(36); break; } /* * Check if it has the required object class */ if (requiredObjectClass && !slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sv_requiredObjectClass)) { break; } /* * Find any unique attribute data in the new RDN */ for (i = 0; attrNames && attrNames[i]; i++) { err = slapi_entry_attr_find(e, attrNames[i], &attr); if (!err) { /* * Passed all the requirements - this is an operation we * need to enforce uniqueness on. Now find all parent entries * with the marker object class, and do a search for each one. */ if (NULL != markerObjectClass) { /* Subtree defined by location of marker object class */ result = findSubtreeAndSearch(slapi_entry_get_sdn(e), attrNames, attr, NULL, requiredObjectClass, sdn, markerObjectClass, config->exclude_subtrees); } else { /* Subtrees listed on invocation line */ result = searchAllSubtrees(config->subtrees, config->exclude_subtrees, attrNames, attr, NULL, requiredObjectClass, sdn, config->unique_in_all_subtrees); } if (result != LDAP_SUCCESS) { break; } } } END /* Clean-up */ slapi_value_free(&sv_requiredObjectClass); if (e) slapi_entry_free(e); if (result) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "MODRDN result %d\n", result); if (result == LDAP_CONSTRAINT_VIOLATION) { errtext = slapi_ch_smprintf(moreInfo, config->attr_friendly); } else { errtext = slapi_ch_strdup("Error checking for attribute uniqueness."); } slapi_send_ldap_result(pb, result, 0, errtext, 0, 0); slapi_ch_free_string(&errtext); } return (result==LDAP_SUCCESS)?0:-1; } static int uiduniq_start(Slapi_PBlock *pb) { Slapi_Entry *plugin_entry = NULL; struct attr_uniqueness_config *config = NULL; if (slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &plugin_entry) == 0){ /* load the config into the config list */ if ((config = uniqueness_entry_to_config(pb, plugin_entry)) == NULL) { return SLAPI_PLUGIN_FAILURE; } slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, (void*) config); } return 0; } static int uiduniq_close(Slapi_PBlock *pb) { struct attr_uniqueness_config *config = NULL; slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &config); if (config) { slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, NULL); free_uniqueness_config(config); slapi_ch_free((void **) &config); } return 0; } /* ------------------------------------------------------------ */ /* * Initialize the plugin * * uidunique_init (the old name) is deprecated */ int NSUniqueAttr_Init(Slapi_PBlock *pb) { int err = 0; Slapi_Entry *plugin_entry = NULL; char *plugin_type = NULL; int preadd = SLAPI_PLUGIN_PRE_ADD_FN; int premod = SLAPI_PLUGIN_PRE_MODIFY_FN; int premdn = SLAPI_PLUGIN_PRE_MODRDN_FN; BEGIN /* Declare plugin version */ err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); if (err) break; /* * Get plugin identity and store it for later use * Used for internal operations */ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); /* PR_ASSERT (plugin_identity); */ if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) && plugin_entry && (plugin_type = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-plugintype")) && plugin_type && strstr(plugin_type, "betxn")) { preadd = SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN; premod = SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN; premdn = SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN; } slapi_ch_free_string(&plugin_type); /* Provide descriptive information */ err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void*)&pluginDesc); if (err) break; /* Register functions */ err = slapi_pblock_set(pb, preadd, (void*)preop_add); if (err) break; err = slapi_pblock_set(pb, premod, (void*)preop_modify); if (err) break; err = slapi_pblock_set(pb, premdn, (void*)preop_modrdn); if (err) break; err = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *) uiduniq_start); if (err) break; err = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *) uiduniq_close); if (err) break; END if (err) { slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init", "Error: %d\n", err); err = -1; } else slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init", "plugin loaded\n"); return err; } /* ------------------------------------------------------------ */ /* * ldap_quote_filter_value * * Quote the filter value according to RFC 2254 (Dec 1997) * * value - a UTF8 string containing the value. It may contain * any of the chars needing quotes ( '(' ')' '*' '/' and NUL ). * len - the length of the UTF8 value * out - a buffer to recieve the converted value. May be NULL, in * which case, only the length of the output is computed (and placed in * outLen). * maxLen - the size of the output buffer. It is an error if this length * is exceeded. Ignored if out is NULL. * outLen - recieves the size of the output. If an error occurs, this * result is not available. * * Returns * 0 - success * -1 - failure (usually a buffer overflow) */ int /* Error value */ ldap_quote_filter_value( char *value, int len, char *out, int maxLen, int *outLen) { int err; char *eValue; int resLen; #ifdef SLAPI_SUPPORTS_V3_ESCAPING static char hexchars[16] = "0123456789abcdef"; #endif err = 0; eValue = &value[len]; resLen = 0; /* * Convert each character in the input string */ while(value < eValue) { switch(*value) { case '(': case ')': case '*': case '\\': #ifdef SLAPI_SUPPORTS_V3_ESCAPING case 0: #endif /* Handle characters needing special escape sequences */ /* Compute size of output */ #ifdef SLAPI_SUPPORTS_V3_ESCAPING resLen += 3; #else resLen += 2; #endif /* Generate output if requested */ if (out) { /* Check for overflow */ if (resLen > maxLen) { err = -1; break; } *out++ = '\\'; #ifdef SLAPI_SUPPORTS_V3_ESCAPING *out++ = hexchars[(*value >> 4) & 0xF]; *out++ = hexchars[*value & 0xF]; #else *out++ = *value; #endif } break; default: /* Compute size of output */ resLen += 1; /* Generate output if requested */ if (out) { if (resLen > maxLen) { err = -1; break; } *out++ = *value; } break; } if (err) break; value++; } if (!err) *outLen = resLen; return err; }