/** 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, either version 3 of the License, or * (at your option) any later version. * * 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, see . * * Additional permission under GPLv3 section 7: * * In the following paragraph, "GPL" means the GNU General Public * License, version 3 or any later version, and "Non-GPL Code" means * code that is governed neither by the the GPL nor a license * compatible with the GPL. * * You may link the code of this Program with Non-GPL Code and convey * linked combinations including the two, provided that such Non-GPL * Code only links 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 GPL. Only the copyright holders of this * Program may make changes or additions to the list of Approved * Interfaces. * * Copyright (C) 2010 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ /** * IPA MODRDN plug-in */ #include #include #include "slapi-plugin.h" #include "nspr.h" #include "prclist.h" #include #include "util.h" #define IPA_PLUGIN_NAME "ipa-modrdn-plugin" #define IPAMODRDN_PLUGIN_VERSION 0x00010000 #define IPAMODRDN_DN "cn=IPA MODRDN,cn=plugins,cn=config" /* temporary */ /** * IPA MODRDN config types */ #define IPAMODRDN_SATTR "ipaModRDNsourceAttr" #define IPAMODRDN_TATTR "ipaModRDNtargetAttr" #define IPAMODRDN_PREFIX "ipaModRDNprefix" #define IPAMODRDN_SUFFIX "ipaModRDNsuffix" #define IPAMODRDN_FILTER "ipaModRDNfilter" #define IPAMODRDN_SCOPE "ipaModRDNscope" #define IPAMODRDN_FEATURE_DESC "IPA MODRDN" #define IPAMODRDN_PLUGIN_DESC "IPA MODRDN plugin" #define IPAMODRDN_POSTOP_DESC "IPA MODRDN postop plugin" static Slapi_PluginDesc pdesc = { IPAMODRDN_FEATURE_DESC, "Red Hat, Inc.", "1.0", IPAMODRDN_PLUGIN_DESC }; /** * linked list of config entries */ struct configEntry { PRCList list; char *dn; char *sattr; char *tattr; char *prefix; char *suffix; char *filter; Slapi_Filter *slapi_filter; char *scope; }; static PRCList *ipamodrdn_global_config = NULL; static pthread_rwlock_t g_ipamodrdn_cache_lock; static void *_PluginID = NULL; static char *_PluginDN = NULL; static int g_plugin_started = 0; /** * * management functions * */ int ipamodrdn_init(Slapi_PBlock * pb); static int ipamodrdn_start(Slapi_PBlock * pb); static int ipamodrdn_close(Slapi_PBlock * pb); /** * * Local operation functions * */ static int ipamodrdn_load_plugin_config(void); static int ipamodrdn_parse_config_entry(Slapi_Entry * e, bool apply); static void ipamodrdn_delete_config(void); static void ipamodrdn_free_config_entry(struct configEntry ** entry); /** * * helpers * */ static char *ipamodrdn_get_dn(Slapi_PBlock * pb); static int ipamodrdn_dn_is_config(char *dn); /** * * the ops (where the real work is done) * */ static int ipamodrdn_config_check_post_op(Slapi_PBlock * pb); static int ipamodrdn_post_op(Slapi_PBlock * pb); /** * debug functions - global, for the debugger */ void ipamodrdn_dump_config(void); void ipamodrdn_dump_config_entry(struct configEntry *); /** * * Deal with cache locking * */ void ipamodrdn_read_lock(void) { pthread_rwlock_rdlock(&g_ipamodrdn_cache_lock); } void ipamodrdn_write_lock(void) { pthread_rwlock_wrlock(&g_ipamodrdn_cache_lock); } void ipamodrdn_unlock(void) { pthread_rwlock_unlock(&g_ipamodrdn_cache_lock); } /** * * Get the plug-in version * */ int ipamodrdn_version(void) { return IPAMODRDN_PLUGIN_VERSION; } /** * Plugin identity mgmt */ void setPluginID(void *pluginID) { _PluginID = pluginID; } void *getPluginID(void) { return _PluginID; } void setPluginDN(char *pluginDN) { _PluginDN = pluginDN; } char *getPluginDN(void) { return _PluginDN; } /* ipamodrdn_init ------------- adds our callbacks to the list */ int ipamodrdn_init(Slapi_PBlock *pb) { int status = EOK; char *plugin_identity = NULL; LOG_TRACE("--in-->\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_DESCRIPTION, (void *) &pdesc) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *) ipamodrdn_start) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *) ipamodrdn_close) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) ipamodrdn_config_check_post_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) ipamodrdn_post_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) ipamodrdn_config_check_post_op) != 0 || slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) ipamodrdn_config_check_post_op) != 0) { LOG_FATAL("failed to register plugin\n"); status = EFAIL; } LOG_TRACE("<--out--\n"); return status; } /* ipamodrdn_start -------------- Kicks off the config cache. It is called after ipamodrdn_init. */ static int ipamodrdn_start(Slapi_PBlock * pb) { char *plugindn = NULL; LOG_TRACE("--in-->\n"); /* Check if we're already started */ if (g_plugin_started) { goto done; } if (pthread_rwlock_init(&g_ipamodrdn_cache_lock, NULL) != 0) { LOG_FATAL("lock creation failed\n"); return EFAIL; } /** * 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)) { LOG("had to use hard coded config dn\n"); plugindn = IPAMODRDN_DN; } else { LOG("config at %s\n", plugindn); } setPluginDN(plugindn); /* * Load the config for our plug-in */ ipamodrdn_global_config = (PRCList *) slapi_ch_calloc(1, sizeof(struct configEntry)); PR_INIT_CLIST(ipamodrdn_global_config); if (ipamodrdn_load_plugin_config() != EOK) { LOG_FATAL("unable to load plug-in configuration\n"); return EFAIL; } g_plugin_started = 1; LOG("ready for service\n"); LOG_TRACE("<--out--\n"); done: return EOK; } /* ipamodrdn_close -------------- closes down the cache */ static int ipamodrdn_close(Slapi_PBlock * pb) { LOG_TRACE( "--in-->\n"); ipamodrdn_delete_config(); slapi_ch_free((void **)&ipamodrdn_global_config); LOG_TRACE("<--out--\n"); return EOK; } /* * config looks like this * - cn=myplugin * --- cn=posix * ------ cn=accounts * ------ cn=groups * --- cn=samba * --- cn=etc * ------ cn=etc etc */ static int ipamodrdn_load_plugin_config(void) { int status = EOK; int result; int i; Slapi_PBlock *search_pb; Slapi_Entry **entries = NULL; LOG_TRACE("--in-->\n"); ipamodrdn_write_lock(); ipamodrdn_delete_config(); 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 = EFAIL; goto cleanup; } slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); if (NULL == entries || NULL == entries[0]) { status = EOK; goto cleanup; } for (i = 0; (entries[i] != NULL); i++) { /* We don't care about the status here because we may have * some invalid config entries, but we just want to continue * looking for valid ones. */ ipamodrdn_parse_config_entry(entries[i], true); } cleanup: slapi_free_search_results_internal(search_pb); slapi_pblock_destroy(search_pb); ipamodrdn_unlock(); LOG_TRACE("<--out--\n"); return status; } /* * ipamodrdn_parse_config_entry() * * Parses a single config entry. If apply is non-zero, then * we will load and start using the new config. You can simply * validate config without making any changes by setting apply * to 0. * * Returns EOK if the entry is valid and EFAIL * if it is invalid. */ static int ipamodrdn_parse_config_entry(Slapi_Entry * e, bool apply) { char *value; struct configEntry *entry = NULL; struct configEntry *config_entry; PRCList *list; int entry_added = 0; int ret = EOK; LOG_TRACE("--in-->\n"); /* If this is the main MODRDN plug-in config entry, just bail. */ if (strcasecmp(getPluginDN(), slapi_entry_get_ndn(e)) == 0) { ret = EFAIL; goto bail; } entry = (struct configEntry *) slapi_ch_calloc(1, sizeof(struct configEntry)); if (NULL == entry) { ret = EFAIL; goto bail; } value = slapi_entry_get_ndn(e); if (value) { entry->dn = slapi_ch_strdup(value); } LOG_CONFIG("----------> dn [%s]\n", entry->dn); entry->sattr = slapi_entry_attr_get_charptr(e, IPAMODRDN_SATTR); if (!entry->sattr) { LOG_FATAL("The %s config setting is required for %s.\n", IPAMODRDN_SATTR, entry->dn); ret = EFAIL; goto bail; } LOG_CONFIG("----------> %s [%s]\n", IPAMODRDN_SATTR, entry->sattr); entry->tattr = slapi_entry_attr_get_charptr(e, IPAMODRDN_TATTR); if (!entry->tattr) { LOG_FATAL("The %s config setting is required for %s.\n", IPAMODRDN_TATTR, entry->dn); ret = EFAIL; goto bail; } LOG_CONFIG("----------> %s [%s]\n", IPAMODRDN_TATTR, entry->tattr); value = slapi_entry_attr_get_charptr(e, IPAMODRDN_PREFIX); if (value && value[0]) { entry->prefix = value; } else { entry->prefix = slapi_ch_strdup(""); } LOG_CONFIG("----------> %s [%s]\n", IPAMODRDN_PREFIX, entry->prefix); value = slapi_entry_attr_get_charptr(e, IPAMODRDN_SUFFIX); if (value && value[0]) { entry->suffix = value; } else { entry->suffix = slapi_ch_strdup(""); } LOG_CONFIG("----------> %s [%s]\n", IPAMODRDN_SUFFIX, entry->suffix); value = slapi_entry_attr_get_charptr(e, IPAMODRDN_FILTER); if (value) { entry->filter = value; if (NULL == (entry->slapi_filter = slapi_str2filter(value))) { LOG_FATAL("Error: Invalid search filter in entry [%s]: [%s]\n", entry->dn, value); ret = EFAIL; goto bail; } } else { LOG_FATAL("The %s config setting is required for %s.\n", IPAMODRDN_FILTER, entry->dn); ret = EFAIL; goto bail; } LOG_CONFIG("----------> %s [%s]\n", IPAMODRDN_FILTER, value); value = slapi_entry_attr_get_charptr(e, IPAMODRDN_SCOPE); if (value) { entry->scope = value; } else { LOG_FATAL("The %s config config setting is required for %s.\n", IPAMODRDN_SCOPE, entry->dn); ret = EFAIL; goto bail; } LOG_CONFIG("----------> %s [%s]\n", IPAMODRDN_SCOPE, entry->scope); /* If we were only called to validate config, we can * just bail out before applying the config changes */ if (!apply) { goto bail; } /** * Finally add the entry to the list. * We sort by scope 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(ipamodrdn_global_config)) { list = PR_LIST_HEAD(ipamodrdn_global_config); while (list != ipamodrdn_global_config) { config_entry = (struct configEntry *) list; if (slapi_dn_issuffix(entry->scope, config_entry->scope)) { PR_INSERT_BEFORE(&(entry->list), list); LOG_CONFIG("store [%s] before [%s] \n", entry->scope, config_entry->scope); entry_added = 1; break; } list = PR_NEXT_LINK(list); if (ipamodrdn_global_config == list) { /* add to tail */ PR_INSERT_BEFORE(&(entry->list), list); LOG_CONFIG("store [%s] at tail\n", entry->scope); entry_added = 1; break; } } } else { /* first entry */ PR_INSERT_LINK(&(entry->list), ipamodrdn_global_config); LOG_CONFIG("store [%s] at head \n", entry->scope); entry_added = 1; } bail: if (0 == entry_added) { /* Don't log error if we weren't asked to apply config */ if (apply && (entry != NULL)) { LOG_FATAL("Invalid config entry [%s] skipped\n", entry->dn); } ipamodrdn_free_config_entry(&entry); } else { ret = EOK; } LOG_TRACE("<--out--\n"); return ret; } static void ipamodrdn_free_config_entry(struct configEntry **entry) { struct configEntry *e; if (!entry || !*entry) { return; } e = *entry; if (e->dn) { LOG_CONFIG("freeing config entry [%s]\n", e->dn); } slapi_ch_free_string(&e->dn); slapi_ch_free_string(&e->sattr); slapi_ch_free_string(&e->tattr); slapi_ch_free_string(&e->prefix); slapi_ch_free_string(&e->suffix); slapi_ch_free_string(&e->filter); slapi_filter_free(e->slapi_filter, 1); slapi_ch_free_string(&e->scope); slapi_ch_free((void **)entry); } static void ipamodrdn_delete_configEntry(PRCList *entry) { PR_REMOVE_LINK(entry); ipamodrdn_free_config_entry((struct configEntry **) &entry); } static void ipamodrdn_delete_config(void) { PRCList *list; while (!PR_CLIST_IS_EMPTY(ipamodrdn_global_config)) { list = PR_LIST_HEAD(ipamodrdn_global_config); ipamodrdn_delete_configEntry(list); } return; } /**************************************************** Helpers ****************************************************/ static char *ipamodrdn_get_dn(Slapi_PBlock * pb) { char *dn = NULL; LOG_TRACE("--in-->\n"); if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn)) { LOG_FATAL("failed to get dn of changed entry"); } LOG_TRACE("<--out--\n"); return dn; } /* config check matching config dn or a descendent reloads config */ static int ipamodrdn_dn_is_config(char *dn) { int ret = 0; LOG_TRACE("--in-->\n"); if (slapi_dn_issuffix(dn, getPluginDN())) { ret = 1; } LOG_TRACE("<--out--\n"); return ret; } /**************************************************** Functions that actually do things other than config and startup ****************************************************/ static int ipamodrdn_change_attr(struct configEntry *cfgentry, char *targetdn, const char *value) { Slapi_PBlock *mod_pb = slapi_pblock_new(); LDAPMod mod; LDAPMod *mods[2]; char *val[2] = { NULL }; int ret; val[0] = slapi_ch_smprintf("%s%s%s", cfgentry->prefix, value, cfgentry->suffix); if (!val[0]) { LOG_OOM(); ret = EFAIL; goto done; } val[1] = 0; mod.mod_op = LDAP_MOD_REPLACE; mod.mod_type = cfgentry->tattr; mod.mod_values = val; mods[0] = &mod; mods[1] = 0; LOG("Setting %s to %s in entry (%s)\n", cfgentry->tattr, value, targetdn); /* Perform the modify operation. */ slapi_modify_internal_set_pb(mod_pb, targetdn, mods, 0, 0, getPluginID(), 0); slapi_modify_internal_pb(mod_pb); slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &ret); if (ret != LDAP_SUCCESS) { LOG_FATAL("Failed to change attribute with error %d\n", ret); ret = EFAIL; } ret = EOK; done: if (val[0]) slapi_ch_free_string(&(val[0])); slapi_pblock_destroy(mod_pb); return ret; } /* for mods and adds: where dn's are supplied, the closest in scope is used as long as the type filter matches and the attr value has not been generated yet. */ static int ipamodrdn_post_op(Slapi_PBlock *pb) { char *dn = NULL; PRCList *list = NULL; struct configEntry *cfgentry = NULL; struct slapi_entry *e = NULL; Slapi_Attr *sattr = NULL; Slapi_Attr *tattr = NULL; int ret = LDAP_SUCCESS; LOG_TRACE("--in-->\n"); /* Just bail if we aren't ready to service requests yet. */ if (!g_plugin_started) { goto done; } slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e); if (NULL == e) { goto done; } dn = slapi_entry_get_ndn(e); if (NULL == dn) { goto done; } ipamodrdn_read_lock(); if (!PR_CLIST_IS_EMPTY(ipamodrdn_global_config)) { list = PR_LIST_HEAD(ipamodrdn_global_config); for(list = PR_LIST_HEAD(ipamodrdn_global_config); list != ipamodrdn_global_config; list = PR_NEXT_LINK(list)) { cfgentry = (struct configEntry *) list; /* is the entry in scope? */ if (cfgentry->scope) { if (!slapi_dn_issuffix(dn, cfgentry->scope)) { continue; } } /* does the entry match the filter? */ if (cfgentry->slapi_filter) { ret = slapi_vattr_filter_test(pb, e, cfgentry->slapi_filter, 0); if (ret != LDAP_SUCCESS) { continue; } } if (slapi_entry_attr_find(e, cfgentry->sattr, &sattr) != 0) { LOG_TRACE("Source attr %s not found for %s\n", cfgentry->sattr, dn); continue; } if (slapi_entry_attr_find(e, cfgentry->tattr, &tattr) != 0) { LOG_TRACE("Target attr %s not found for %s\n", cfgentry->tattr, dn); } else { Slapi_Value *val; const char *strval; ret = slapi_attr_first_value(sattr, &val); if (ret == -1 || !val) { LOG_FATAL("Source attr %s is empty\n", cfgentry->sattr); continue; } strval = slapi_value_get_string(val); ret = ipamodrdn_change_attr(cfgentry, dn, strval); if (ret != EOK) { LOG_FATAL("Failed to set target attr %s for %s\n", cfgentry->tattr, dn); } } } } ipamodrdn_unlock(); ret = LDAP_SUCCESS; done: if (ret) { LOG("operation failure [%d]\n", ret); ret = EFAIL; } LOG_TRACE("<--out--\n"); return ret; } static int ipamodrdn_config_check_post_op(Slapi_PBlock * pb) { char *dn; LOG_TRACE("--in-->\n"); if ((dn = ipamodrdn_get_dn(pb))) { if (ipamodrdn_dn_is_config(dn)) { ipamodrdn_load_plugin_config(); } } LOG_TRACE("<--out--\n"); return 0; }