/** 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. * * * Author: Pete Rowley * * Copyright (C) 2007 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif /** * Distributed Numeric Assignment plug-in */ #include #include #include #include #include /*#include "portable.h"*/ #include "nspr.h" /*#include "slapi-private.h"*/ /*#include "dirlite_strings.h"*/ /*#include "dirver.h"*/ #include "prclist.h" #include "ldif.h" /* get file mode flags for unix */ #ifndef _WIN32 #include #endif #define DNA_PLUGIN_SUBSYSTEM "ipa-dna-plugin" #define DNA_PLUGIN_VERSION 0x00020000 /* temporary */ #define DNA_DN "cn=ipa-dna,cn=plugins,cn=config" #define DNA_SUCCESS 0 #define DNA_FAILURE -1 /** * DNA config types */ #define DNA_TYPE "dnaType" #define DNA_PREFIX "dnaPrefix" #define DNA_NEXTVAL "dnaNextValue" #define DNA_INTERVAL "dnaInterval" #define DNA_GENERATE "dnaMagicRegen" #define DNA_FILTER "dnaFilter" #define DNA_SCOPE "dnaScope" /* since v2 */ #define DNA_MAXVAL "dnaMaxValue" #define DNA_SHARED_CFG_DN "dnaSharedCfgDN" /* Shared Config */ #define DNA_GLOBAL_RANGE "dnaGlobalRange" #define DNA_RANGE "dnaRange" #define DNA_MAX_RANGE_SIZE "dnaMaxRangeSize" #define DNA_CHUNK_SIZE "dnaChunkSize" #define FEATURE_DESC "IPA Distributed Numeric Assignment" #define PLUGIN_DESC "IPA Distributed Numeric Assignment plugin" #define PLUGIN_DESC_INT_PREOP PLUGIN_DESC " preop internal" #define PLUGIN_DESC_POSTOP PLUGIN_DESC " postop" #define PLUGIN_DESC_INT_POSTOP PLUGIN_DESC " postop internal" static Slapi_PluginDesc pdesc = { FEATURE_DESC, "FreeIPA project", "FreeIPA/1.0", PLUGIN_DESC }; /** * linked list of config entries */ struct configEntry { PRCList list; char *dn; char *type; char *prefix; PRUint64 nextval; PRUint64 interval; PRUint64 maxval; char *filter; struct slapi_filter *slapi_filter; char *generate; char *scope; }; static PRCList *dna_global_config = NULL; static PRRWLock *g_dna_cache_lock; static void *_PluginID = NULL; static char *_PluginDN = NULL; static int g_plugin_started = 0; /* * new value lock */ static Slapi_Mutex *g_new_value_lock; /** * * DNA plug-in management functions * */ int ipa_dna_init(Slapi_PBlock * pb); static int dna_start(Slapi_PBlock * pb); static int dna_close(Slapi_PBlock * pb); static int dna_internal_preop_init(Slapi_PBlock *pb); static int dna_postop_init(Slapi_PBlock * pb); /** * * Local operation functions * */ static int loadPluginConfig(); static int parseConfigEntry(Slapi_Entry * e); static void deleteConfig(); static void freeConfigEntry(struct configEntry ** entry); /** * * helpers * */ static char *dna_get_dn(Slapi_PBlock * pb); static int dna_dn_is_config(char *dn); static int dna_get_next_value(struct configEntry * config_entry, char **next_value_ret); /** * * the ops (where the real work is done) * */ static int dna_config_check_post_op(Slapi_PBlock * pb); static int dna_pre_op(Slapi_PBlock * pb, int modtype); static int dna_mod_pre_op(Slapi_PBlock * pb); static int dna_add_pre_op(Slapi_PBlock * pb); /** * debug functions - global, for the debugger */ void dnaDumpConfig(); void dnaDumpConfigEntry(struct configEntry *); /** * set the debug level */ #ifdef _WIN32 int *module_ldap_debug = 0; void plugin_init_debug_level(int *level_ptr) { module_ldap_debug = level_ptr; } #endif /** * * Deal with cache locking * */ void dna_read_lock() { PR_RWLock_Rlock(g_dna_cache_lock); } void dna_write_lock() { PR_RWLock_Wlock(g_dna_cache_lock); } void dna_unlock() { PR_RWLock_Unlock(g_dna_cache_lock); } /** * * Get the dna plug-in version * */ int dna_version() { return DNA_PLUGIN_VERSION; } /** * Plugin identity mgmt */ void setPluginID(void *pluginID) { _PluginID = pluginID; } void *getPluginID() { return _PluginID; } void setPluginDN(char *pluginDN) { _PluginDN = pluginDN; } char *getPluginDN() { return _PluginDN; } /* dna_init ------------- adds our callbacks to the list */ int ipa_dna_init(Slapi_PBlock * pb) { int status = DNA_SUCCESS; char *plugin_identity = NULL; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> ipa_dna_init\n"); /** * Store the plugin identity for later use. * Used for internal operations */ slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); PR_ASSERT(plugin_identity); setPluginID(plugin_identity); if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *) dna_start) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *) dna_close) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *) &pdesc) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) dna_mod_pre_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) dna_add_pre_op) != 0 || /* internal preoperation */ slapi_register_plugin("internalpreoperation", /* op type */ 1, /* Enabled */ "dna_internal_preop_init", /* this function desc */ dna_internal_preop_init, /* init func */ PLUGIN_DESC_INT_PREOP, /* plugin desc */ NULL, /* ? */ plugin_identity /* access control */ ) || /* the config change checking post op */ slapi_register_plugin("postoperation", /* op type */ 1, /* Enabled */ "dna_postop_init", /* this function desc */ dna_postop_init, /* init func for post op */ PLUGIN_DESC_POSTOP, /* plugin desc */ NULL, /* ? */ plugin_identity /* access control */ ) ) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "ipa_dna_init: failed to register plugin\n"); status = DNA_FAILURE; } slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- ipa_dna_init\n"); return status; } static int dna_internal_preop_init(Slapi_PBlock *pb) { int status = DNA_SUCCESS; if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *) &pdesc) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) dna_mod_pre_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) dna_add_pre_op) != 0) { status = DNA_FAILURE; } return status; } static int dna_postop_init(Slapi_PBlock * pb) { int status = DNA_SUCCESS; if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *) &pdesc) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) dna_config_check_post_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) dna_config_check_post_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) dna_config_check_post_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) dna_config_check_post_op) != 0) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_postop_init: failed to register plugin\n"); status = DNA_FAILURE; } return status; } /* dna_start -------------- Kicks off the config cache. It is called after dna_init. */ static int dna_start(Slapi_PBlock * pb) { char *plugindn = NULL; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_start\n"); /* Check if we're already started */ if (g_plugin_started) { goto done; } g_dna_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "dna"); g_new_value_lock = slapi_new_mutex(); if (!g_dna_cache_lock || !g_new_value_lock) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_start: lock creation failed\n"); return DNA_FAILURE; } /** * Get the plug-in target dn from the system * and store it for future use. This should avoid * hardcoding of DN's in the code. */ slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn); if (NULL == plugindn || 0 == strlen(plugindn)) { slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM, "dna_start: had to use hard coded config dn\n"); plugindn = DNA_DN; } else { slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM, "dna_start: config at %s\n", plugindn); } setPluginDN(plugindn); /** * Load the config for our plug-in */ dna_global_config = (PRCList *) slapi_ch_calloc(1, sizeof(struct configEntry)); PR_INIT_CLIST(dna_global_config); if (loadPluginConfig() != DNA_SUCCESS) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_start: unable to load plug-in configuration\n"); return DNA_FAILURE; } g_plugin_started = 1; slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM, "dna: ready for service\n"); slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_start\n"); done: return DNA_SUCCESS; } /* dna_close -------------- closes down the cache */ static int dna_close(Slapi_PBlock * pb) { slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_close\n"); deleteConfig(); slapi_ch_free((void **)&dna_global_config); slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_close\n"); return DNA_SUCCESS; } /* * config looks like this * - cn=myplugin * --- cn=posix * ------ cn=accounts * ------ cn=groups * --- cn=samba * --- cn=etc * ------ cn=etc etc */ static int loadPluginConfig() { int status = DNA_SUCCESS; int result; int i; Slapi_PBlock *search_pb; Slapi_Entry **entries = NULL; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> loadPluginConfig\n"); dna_write_lock(); deleteConfig(); search_pb = slapi_pblock_new(); slapi_search_internal_set_pb(search_pb, getPluginDN(), LDAP_SCOPE_SUBTREE, "objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0); slapi_search_internal_pb(search_pb); slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); if (LDAP_SUCCESS != result) { status = DNA_FAILURE; goto cleanup; } slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); if (NULL == entries || NULL == entries[0]) { status = DNA_SUCCESS; goto cleanup; } for (i = 0; (entries[i] != NULL); i++) { status = parseConfigEntry(entries[i]); if (DNA_SUCCESS != status) break; } cleanup: slapi_free_search_results_internal(search_pb); slapi_pblock_destroy(search_pb); dna_unlock(); slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- loadPluginConfig\n"); return status; } static int parseConfigEntry(Slapi_Entry * e) { char *value; struct configEntry *entry; struct configEntry *config_entry; PRCList *list; int entry_added = 0; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> parseConfigEntry\n"); entry = (struct configEntry *) slapi_ch_calloc(1, sizeof(struct configEntry)); if (NULL == entry) goto bail; value = slapi_entry_get_ndn(e); if (value) { entry->dn = strdup(value); } slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dn [%s]\n", entry->dn, 0, 0); value = slapi_entry_attr_get_charptr(e, DNA_TYPE); if (value) { entry->type = value; } else goto bail; slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaType [%s]\n", entry->type, 0, 0); /* FIXME: check the attribute type, it must suport matching rules and be * indexed, these are requirements and failure to meet them should result in * the configuration to be disarded and an ERROR logged prominently */ value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL); if (value) { entry->nextval = strtoul(value, 0, 0); slapi_ch_free_string(&value); } else goto bail; slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaNextValue [%d]\n", entry->nextval, 0, 0); value = slapi_entry_attr_get_charptr(e, DNA_PREFIX); if (value && value[0]) { entry->prefix = value; } slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaPrefix [%s]\n", entry->prefix, 0, 0); value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL); if (value) { entry->interval = strtoul(value, 0, 0); } else goto bail; slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaInterval [%s]\n", value, 0, 0); slapi_ch_free_string(&value); value = slapi_entry_attr_get_charptr(e, DNA_GENERATE); if (value) { entry->generate = value; } slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaMagicRegen [%s]\n", entry->generate, 0, 0); value = slapi_entry_attr_get_charptr(e, DNA_FILTER); if (value) { entry->filter = value; entry->slapi_filter = slapi_str2filter(value); } else goto bail; slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaFilter [%s]\n", value, 0, 0); value = slapi_entry_attr_get_charptr(e, DNA_SCOPE); if (value) { entry->scope = slapi_dn_normalize(value); } slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaScope [%s]\n", entry->scope, 0, 0); /* optional, if not specified set -1 which is converted to the max unisgnee * value */ value = slapi_entry_attr_get_charptr(e, DNA_MAXVAL); if (value) { entry->maxval = strtoul(value, 0, 0); slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "----------> dnaMaxValue [%ld]\n", value, 0, 0); slapi_ch_free_string(&value); } else entry->maxval = -1; /** * Finally add the entry to the list * we group by type then by filter * and finally sort by dn length with longer dn's * first - this allows the scope checking * code to be simple and quick and * cunningly linear */ if (!PR_CLIST_IS_EMPTY(dna_global_config)) { list = PR_LIST_HEAD(dna_global_config); while (list != dna_global_config) { config_entry = (struct configEntry *) list; if (slapi_attr_type_cmp(config_entry->type, entry->type, 1)) goto next; if (slapi_filter_compare(config_entry->slapi_filter, entry->slapi_filter)) goto next; if (slapi_dn_issuffix(entry->scope, config_entry->scope)) { PR_INSERT_BEFORE(&(entry->list), list); slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "store [%s] before [%s] \n", entry->scope, config_entry->scope, 0); entry_added = 1; break; } next: list = PR_NEXT_LINK(list); if (dna_global_config == list) { /* add to tail */ PR_INSERT_BEFORE(&(entry->list), list); slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "store [%s] at tail\n", entry->scope, 0, 0); entry_added = 1; break; } } } else { /* first entry */ PR_INSERT_LINK(&(entry->list), dna_global_config); slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "store [%s] at head \n", entry->scope, 0, 0); entry_added = 1; } bail: if (0 == entry_added) { slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "config entry [%s] skipped\n", entry->dn, 0, 0); freeConfigEntry(&entry); } slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- parseConfigEntry\n"); return DNA_SUCCESS; } static void freeConfigEntry(struct configEntry ** entry) { struct configEntry *e = *entry; if (e->dn) { slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, "freeing config entry [%s]\n", e->dn, 0, 0); slapi_ch_free_string(&e->dn); } if (e->type) slapi_ch_free_string(&e->type); if (e->prefix) slapi_ch_free_string(&e->prefix); if (e->filter) slapi_ch_free_string(&e->filter); if (e->slapi_filter) slapi_filter_free(e->slapi_filter, 1); if (e->generate) slapi_ch_free_string(&e->generate); if (e->scope) slapi_ch_free_string(&e->scope); slapi_ch_free((void **) entry); } static void deleteConfigEntry(PRCList * entry) { PR_REMOVE_LINK(entry); freeConfigEntry((struct configEntry **) & entry); } static void deleteConfig() { PRCList *list; while (!PR_CLIST_IS_EMPTY(dna_global_config)) { list = PR_LIST_HEAD(dna_global_config); deleteConfigEntry(list); } return; } /**************************************************** Distributed ranges Helpers ****************************************************/ static int dna_fix_maxval(Slapi_DN *dn, PRUint64 *cur, PRUint64 *max) { /* TODO: check the main partition to see if another range * is available, and set the new local configuration * accordingly. * If a new range is not available run the retrieval task * and simply return error */ return LDAP_OPERATIONS_ERROR; } static void dna_notice_allocation(Slapi_DN *dn, PRUint64 new) { /* TODO: check if we passed a new chunk threshold and update * the shared configuration on the public partition. */ return; } /**************************************************** Helpers ****************************************************/ static char *dna_get_dn(Slapi_PBlock * pb) { char *dn = 0; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_get_dn\n"); if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn)) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_get_dn: failed to get dn of changed entry"); goto bail; } /* slapi_dn_normalize( dn ); */ bail: slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_get_dn\n"); return dn; } /* config check matching config dn or a descendent reloads config */ static int dna_dn_is_config(char *dn) { int ret = 0; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_is_config\n"); if (slapi_dn_issuffix(dn, getPluginDN())) { ret = 1; } slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_is_config\n"); return ret; } #define DNA_LDAP_TAG_SK_REVERSE 0x81L static LDAPControl *dna_build_sort_control(const char *attr) { LDAPControl *ctrl; BerElement *ber; int rc; ber = ber_alloc(); if (NULL == ber) return NULL; rc = ber_printf(ber, "{{stb}}", attr, DNA_LDAP_TAG_SK_REVERSE, 1); if (-1 == rc) { ber_free(ber, 1); return NULL; } rc = slapi_build_control(LDAP_CONTROL_SORTREQUEST, ber, 1, &ctrl); ber_free(ber, 1); if (LDAP_SUCCESS != rc) return NULL; return ctrl; } /**************************************************** Functions that actually do things other than config and startup ****************************************************/ /* we do search all values between newval and maxval asking the * server to sort them, then we check the first free spot and * use it as newval */ static int dna_first_free_value(struct configEntry *config_entry, PRUint64 *newval, PRUint64 maxval, PRUint64 increment) { Slapi_Entry **entries = NULL; Slapi_PBlock *pb = NULL; LDAPControl **ctrls; char *attrs[2]; char *filter; char *prefix; char *type; int preflen; int result, status; PRUint64 tmpval, sval, i; char *strval = NULL; prefix = config_entry->prefix; type = config_entry->type; tmpval = *newval; attrs[0] = type; attrs[1] = NULL; ctrls = (LDAPControl **)slapi_ch_calloc(2, sizeof(LDAPControl)); if (NULL == ctrls) return LDAP_OPERATIONS_ERROR; ctrls[0] = dna_build_sort_control(config_entry->type); if (NULL == ctrls[0]) { slapi_ch_free((void **)&ctrls); return LDAP_OPERATIONS_ERROR; } filter = slapi_ch_smprintf("(&%s(&(%s>=%s%llu)(%s<=%s%llu)))", config_entry->filter, type, prefix?prefix:"", tmpval, type, prefix?prefix:"", maxval); if (NULL == filter) { ldap_control_free(ctrls[0]); slapi_ch_free((void **)&ctrls); return LDAP_OPERATIONS_ERROR; } pb = slapi_pblock_new(); if (NULL == pb) { ldap_control_free(ctrls[0]); slapi_ch_free((void **)&ctrls); slapi_ch_free_string(&filter); return LDAP_OPERATIONS_ERROR; } slapi_search_internal_set_pb(pb, config_entry->scope, LDAP_SCOPE_SUBTREE, filter, attrs, 0, ctrls, NULL, getPluginID(), 0); slapi_search_internal_pb(pb); /* ldap_control_free(ctrls[0]); */ slapi_ch_free_string(&filter); slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); if (LDAP_SUCCESS != result) { status = LDAP_OPERATIONS_ERROR; goto cleanup; } slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); if (NULL == entries || NULL == entries[0]) { /* no values means we already have a good value */ status = LDAP_SUCCESS; goto cleanup; } /* entries are sorted and filtered for value >= tval therefore if the * first one does not match tval it means that the value is free, * otherwise we need to cycle through values until we find a mismatch, * the first mismatch is the first free pit */ preflen = prefix?strlen(prefix):0; sval = 0; for (i = 0; NULL != entries[i]; i++) { strval = slapi_entry_attr_get_charptr(entries[i], type); if (preflen) { if (strlen(strval) <= preflen) { /* something very wrong here ... */ status = LDAP_OPERATIONS_ERROR; goto cleanup; } strval = &strval[preflen-1]; } errno = 0; sval = strtoul(strval, 0, 0); if (errno) { /* something very wrong here ... */ status = LDAP_OPERATIONS_ERROR; goto cleanup; } slapi_ch_free_string(&strval); if (tmpval != sval) break; if (maxval < sval) break; tmpval += increment; } *newval = tmpval; status = LDAP_SUCCESS; cleanup: slapi_ch_free_string(&strval); slapi_free_search_results_internal(pb); slapi_pblock_destroy(pb); return status; } /* * Perform ldap operationally atomic increment * Return the next value to be assigned * Method: * 1. retrieve entry * 2. do increment operations * 3. remove current value, add new value in one operation * 4. if failed, and less than 3 times, goto 1 */ static int dna_get_next_value(struct configEntry *config_entry, char **next_value_ret) { Slapi_PBlock *pb = NULL; char *old_value = NULL; Slapi_Entry *e = NULL; Slapi_DN *dn = NULL; char *attrlist[4]; int attempts; int ret; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_get_next_value\n"); /* get pre-requisites to search */ dn = slapi_sdn_new_dn_byref(config_entry->dn); attrlist[0] = DNA_NEXTVAL; attrlist[1] = DNA_MAXVAL; attrlist[2] = DNA_INTERVAL; attrlist[3] = NULL; /* the operation is constructed such that race conditions * to increment the value are detected and avoided - one wins, * one loses - however, there is no need for the server to compete * with itself so we lock here */ slapi_lock_mutex(g_new_value_lock); for (attempts = 0; attempts < 3; attempts++) { LDAPMod mod_add; LDAPMod mod_delete; LDAPMod *mods[3]; char *delete_val[2]; char *add_val[2]; char new_value[16]; char *interval; char *max_value; PRUint64 increment = 1; /* default increment */ PRUint64 setval = 0; PRUint64 newval = 0; PRUint64 maxval = -1; /* do update */ ret = slapi_search_internal_get_entry(dn, attrlist, &e, getPluginID()); if (LDAP_SUCCESS != ret) { ret = LDAP_OPERATIONS_ERROR; goto done; } old_value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL); if (NULL == old_value) { ret = LDAP_OPERATIONS_ERROR; goto done; } setval = strtoul(old_value, 0, 0); max_value = slapi_entry_attr_get_charptr(e, DNA_MAXVAL); if (max_value) { maxval = strtoul(max_value, 0, 0); slapi_ch_free_string(&max_value); } /* if not present the default is 1 */ interval = slapi_entry_attr_get_charptr(e, DNA_INTERVAL); if (NULL != interval) { increment = strtoul(interval, 0, 0); } slapi_entry_free(e); e = NULL; /* check the value is actually in range */ /* verify the new value is actually free and get the first * one free if not*/ ret = dna_first_free_value(config_entry, &setval, maxval, increment); if (LDAP_SUCCESS != ret) goto done; /* try for a new range or fail */ if (setval > maxval) { ret = dna_fix_maxval(dn, &setval, &maxval); if (LDAP_SUCCESS != ret) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_get_next_value: no more IDs available!!\n"); goto done; } /* verify the new value is actually free and get the first * one free if not */ ret = dna_first_free_value(config_entry, &setval, maxval, increment); if (LDAP_SUCCESS != ret) goto done; } if (setval > maxval) { ret = LDAP_OPERATIONS_ERROR; goto done; } newval = setval + increment; /* try for a new range or fail */ if (newval > maxval) { ret = dna_fix_maxval(dn, &newval, &maxval); if (LDAP_SUCCESS != ret) { slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_get_next_value: no more IDs available!!\n"); goto done; } } /* try to set the new value */ sprintf(new_value, "%llu", newval); delete_val[0] = old_value; delete_val[1] = 0; mod_delete.mod_op = LDAP_MOD_DELETE; mod_delete.mod_type = DNA_NEXTVAL; mod_delete.mod_values = delete_val; add_val[0] = new_value; add_val[1] = 0; mod_add.mod_op = LDAP_MOD_ADD; mod_add.mod_type = DNA_NEXTVAL; mod_add.mod_values = add_val; mods[0] = &mod_delete; mods[1] = &mod_add; mods[2] = 0; pb = slapi_pblock_new(); if (NULL == pb) { ret = LDAP_OPERATIONS_ERROR; goto done; } slapi_modify_internal_set_pb(pb, config_entry->dn, mods, 0, 0, getPluginID(), 0); slapi_modify_internal_pb(pb); slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret); slapi_pblock_destroy(pb); pb = NULL; slapi_ch_free_string(&interval); slapi_ch_free_string(&old_value); if (LDAP_SUCCESS == ret) { *next_value_ret = slapi_ch_smprintf("%llu", setval); if (NULL == *next_value_ret) { ret = LDAP_OPERATIONS_ERROR; goto done; } dna_notice_allocation(dn, newval); goto done; } if (LDAP_NO_SUCH_ATTRIBUTE != ret) { /* not the result of a race to change the value */ goto done; } } done: slapi_unlock_mutex(g_new_value_lock); if (LDAP_SUCCESS != ret) slapi_ch_free_string(&old_value); if (dn) slapi_sdn_free(&dn); if (e) slapi_entry_free(e); if (pb) slapi_pblock_destroy(pb); slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_get_next_value\n"); return ret; } /* for mods and adds: where dn's are supplied, the closest in scope is used as long as the type and filter are identical - otherwise all matches count */ static int dna_pre_op(Slapi_PBlock * pb, int modtype) { char *dn = 0; PRCList *list = 0; struct configEntry *config_entry = 0; struct slapi_entry *e = 0; char *last_type = 0; char *value = 0; int generate = 0; Slapi_Mods *smods = 0; Slapi_Mod *smod = 0; LDAPMod **mods; int free_entry = 0; char *errstr = NULL; int ret = 0; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_pre_op\n"); /* Just bail if we aren't ready to service requests yet. */ if (!g_plugin_started) goto bail; if (0 == (dn = dna_get_dn(pb))) goto bail; if (dna_dn_is_config(dn)) goto bail; if (LDAP_CHANGETYPE_ADD == modtype) { slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); } else { /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be * available but it turns out that is only true if you are * a dbm backend pre-op plugin - lucky dbm backend pre-op * plugins. * I think that is wrong since the entry is useful for filter * tests and schema checks and this plugin shouldn't be limited * to a single backend type, but I don't want that fight right * now so we go get the entry here * slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); */ Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(dn); if (tmp_dn) { slapi_search_internal_get_entry(tmp_dn, 0, &e, getPluginID()); slapi_sdn_free(&tmp_dn); free_entry = 1; } /* grab the mods - we'll put them back later with * our modifications appended */ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); smods = slapi_mods_new(); slapi_mods_init_passin(smods, mods); } if (0 == e) goto bailmod; dna_read_lock(); if (!PR_CLIST_IS_EMPTY(dna_global_config)) { list = PR_LIST_HEAD(dna_global_config); while (list != dna_global_config && LDAP_SUCCESS == ret) { config_entry = (struct configEntry *) list; /* did we already service this type? */ if (last_type) { if (!slapi_attr_type_cmp(config_entry->type, last_type, 1)) goto next; } /* is the entry in scope? */ if (config_entry->scope) { if (!slapi_dn_issuffix(dn, config_entry->scope)) goto next; } /* does the entry match the filter? */ if (config_entry->slapi_filter) { if (LDAP_SUCCESS != slapi_vattr_filter_test(pb, e, config_entry-> slapi_filter, 0)) goto next; } if (LDAP_CHANGETYPE_ADD == modtype) { /* does attribute contain the magic value or is the type not there? */ value = slapi_entry_attr_get_charptr(e, config_entry->type); if ((value && !slapi_UTF8CASECMP(config_entry->generate, value)) || 0 == value) { generate = 1; } } else { /* check mods for magic value */ Slapi_Mod *next_mod = slapi_mod_new(); smod = slapi_mods_get_first_smod(smods, next_mod); while (smod) { char *type = (char *) slapi_mod_get_type(smod); if (slapi_attr_types_equivalent(type, config_entry->type)) { struct berval *bv = slapi_mod_get_first_value(smod); int len = strlen(config_entry->generate); if (len == bv->bv_len) { if (!slapi_UTF8NCASECMP(bv->bv_val, config_entry->generate, len)) generate = 1; break; } } slapi_mod_done(next_mod); smod = slapi_mods_get_next_smod(smods, next_mod); } slapi_mod_free(&next_mod); } if (generate) { char *new_value; int len; /* create the value to add */ ret = dna_get_next_value(config_entry, &value); if (DNA_SUCCESS != ret) { errstr = slapi_ch_smprintf("Allocation of a new value for" " %s failed! Unable to proceed.", config_entry->type); break; } len = strlen(value) + 1; if (config_entry->prefix) { len += strlen(config_entry->prefix); } new_value = slapi_ch_malloc(len); if (config_entry->prefix) { strcpy(new_value, config_entry->prefix); strcat(new_value, value); } else strcpy(new_value, value); /* do the mod */ if (LDAP_CHANGETYPE_ADD == modtype) { /* add - add to entry */ slapi_entry_attr_set_charptr(e, config_entry->type, new_value); } else { /* mod - add to mods */ slapi_mods_add_string(smods, LDAP_MOD_REPLACE, config_entry->type, new_value); } /* free up */ slapi_ch_free_string(&value); slapi_ch_free_string(&new_value); /* make sure we don't generate for this * type again */ if (LDAP_SUCCESS == ret) { last_type = config_entry->type; } generate = 0; } next: list = PR_NEXT_LINK(list); } } dna_unlock(); bailmod: if (LDAP_CHANGETYPE_MODIFY == modtype) { /* these are the mods you made, really, * I didn't change them, honest, just had a quick look */ mods = slapi_mods_get_ldapmods_passout(smods); slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods); slapi_mods_free(&smods); } bail: if (free_entry && e) slapi_entry_free(e); if (ret) { slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM, "dna_pre_op: operation failure [%d]\n", ret); slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL); slapi_ch_free((void **)&errstr); ret = DNA_FAILURE; } slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_pre_op\n"); return ret; } static int dna_add_pre_op(Slapi_PBlock * pb) { return dna_pre_op(pb, LDAP_CHANGETYPE_ADD); } static int dna_mod_pre_op(Slapi_PBlock * pb) { return dna_pre_op(pb, LDAP_CHANGETYPE_MODIFY); } static int dna_config_check_post_op(Slapi_PBlock * pb) { char *dn; slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "--> dna_config_check_post_op\n"); if ((dn = dna_get_dn(pb))) { if (dna_dn_is_config(dn)) loadPluginConfig(); } slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM, "<-- dna_config_check_post_op\n"); return 0; } /**************************************************** End of Functions that actually do things other than config and startup ****************************************************/ /** * debug functions to print config */ void dnaDumpConfig() { PRCList *list; dna_read_lock(); if (!PR_CLIST_IS_EMPTY(dna_global_config)) { list = PR_LIST_HEAD(dna_global_config); while (list != dna_global_config) { dnaDumpConfigEntry((struct configEntry *) list); list = PR_NEXT_LINK(list); } } dna_unlock(); } void dnaDumpConfigEntry(struct configEntry * entry) { printf("<- type --------------> %s\n", entry->type); printf("<---- prefix ---------> %s\n", entry->prefix); printf("<---- next value -----> %lu\n", entry->nextval); printf("<---- interval -------> %lu\n", entry->interval); printf("<---- generate flag --> %s\n", entry->generate); }