diff options
Diffstat (limited to 'daemons/ipa-slapi-plugins/dna/dna.c')
-rw-r--r-- | daemons/ipa-slapi-plugins/dna/dna.c | 1462 |
1 files changed, 1462 insertions, 0 deletions
diff --git a/daemons/ipa-slapi-plugins/dna/dna.c b/daemons/ipa-slapi-plugins/dna/dna.c new file mode 100644 index 00000000..cb6a0629 --- /dev/null +++ b/daemons/ipa-slapi-plugins/dna/dna.c @@ -0,0 +1,1462 @@ +/** 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 <config.h> +#endif + + +/** + * Distributed Numeric Assignment plug-in + */ + +#include <dirsrv/slapi-plugin.h> + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +/*#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 <sys/stat.h> +#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); +} |