From bdb995194c2439751c6969cce589414164b1ffa2 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Mon, 18 Jun 2012 21:25:31 +0200 Subject: Add range check preop plugin To make sure that ID ranges do not overlap this plugin checks new additions and changes for conflicts with existing ranges. https://fedorahosted.org/freeipa/ticket/2185 --- daemons/configure.ac | 1 + daemons/ipa-slapi-plugins/Makefile.am | 1 + .../ipa-slapi-plugins/ipa-range-check/Makefile.am | 46 +++ .../ipa-range-check/ipa_range_check.c | 440 +++++++++++++++++++++ .../ipa-range-check/range-check-conf.ldif | 16 + 5 files changed, 504 insertions(+) create mode 100644 daemons/ipa-slapi-plugins/ipa-range-check/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c create mode 100644 daemons/ipa-slapi-plugins/ipa-range-check/range-check-conf.ldif (limited to 'daemons') diff --git a/daemons/configure.ac b/daemons/configure.ac index 76ebaa67f..b94673026 100644 --- a/daemons/configure.ac +++ b/daemons/configure.ac @@ -342,6 +342,7 @@ AC_CONFIG_FILES([ ipa-slapi-plugins/ipa-uuid/Makefile ipa-slapi-plugins/ipa-modrdn/Makefile ipa-slapi-plugins/ipa-sidgen/Makefile + ipa-slapi-plugins/ipa-range-check/Makefile ]) AC_OUTPUT diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am index 5a3c9e703..c79e68db1 100644 --- a/daemons/ipa-slapi-plugins/Makefile.am +++ b/daemons/ipa-slapi-plugins/Makefile.am @@ -11,6 +11,7 @@ SUBDIRS = \ ipa-version \ ipa-winsync \ ipa-sidgen \ + ipa-range-check \ $(NULL) EXTRA_DIST = \ diff --git a/daemons/ipa-slapi-plugins/ipa-range-check/Makefile.am b/daemons/ipa-slapi-plugins/ipa-range-check/Makefile.am new file mode 100644 index 000000000..f284b42ff --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-range-check/Makefile.am @@ -0,0 +1,46 @@ +NULL = + +PLUGIN_COMMON_DIR=../common + +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -I$(PLUGIN_COMMON_DIR) \ + -I/usr/include/dirsrv \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(AM_CFLAGS) \ + $(LDAP_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +plugindir = $(libdir)/dirsrv/plugins +plugin_LTLIBRARIES = \ + libipa_range_check.la \ + $(NULL) + +libipa_range_check_la_SOURCES = \ + ipa_range_check.c \ + $(NULL) + +libipa_range_check_la_LDFLAGS = -avoid-version + +libipa_range_check_la_LIBADD = \ + $(LDAP_LIBS) \ + $(NULL) + +appdir = $(IPA_DATA_DIR) +app_DATA = \ + range-check-conf.ldif \ + $(NULL) + +EXTRA_DIST = \ + $(app_DATA) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c b/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c new file mode 100644 index 000000000..499e54a9c --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c @@ -0,0 +1,440 @@ +/** 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 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. + * + * Authors: + * Sumit Bose + * + * Copyright (C) 2011 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include +#include +#include +#include + +#include "util.h" + +#define IPA_CN "cn" +#define IPA_BASE_ID "ipaBaseID" +#define IPA_ID_RANGE_SIZE "ipaIDRangeSize" +#define IPA_BASE_RID "ipaBaseRID" +#define IPA_SECONDARY_BASE_RID "ipaSecondaryBaseRID" +#define RANGES_FILTER "objectclass=ipaIDRange" + +#define IPA_PLUGIN_NAME "ipa-range-check" +#define IPA_RANGE_CHECK_FEATURE_DESC "IPA ID range check plugin" +#define IPA_RANGE_CHECK_PLUGIN_DESC "Check if newly added or modified " \ + "ID ranges do not overlap with existing ones" + +Slapi_PluginDesc ipa_range_check_plugin_desc = { + IPA_RANGE_CHECK_FEATURE_DESC, + "FreeIPA project", + "FreeIPA/1.0", + IPA_RANGE_CHECK_PLUGIN_DESC +}; + +struct ipa_range_check_ctx { + Slapi_ComponentId *plugin_id; + const char *base_dn; +}; + +struct range_info { + char *name; + uint32_t base_id; + uint32_t id_range_size; + uint32_t base_rid; + uint32_t secondary_base_rid; +}; + +static int slapi_entry_to_range_info(struct slapi_entry *entry, + struct range_info **_range) +{ + int ret; + unsigned long ul_val; + struct range_info *range = NULL; + + range = calloc(1, sizeof(struct range_info)); + if (range == NULL) { + return ENOMEM; + } + + range->name = slapi_entry_attr_get_charptr(entry, IPA_CN); + if (range->name == NULL) { + return EINVAL; + } + + ul_val = slapi_entry_attr_get_ulong(entry, IPA_BASE_ID); + if (ul_val == 0 || ul_val >= UINT32_MAX) { + ret = ERANGE; + goto done; + } + range->base_id = ul_val; + + ul_val = slapi_entry_attr_get_ulong(entry, IPA_ID_RANGE_SIZE); + if (ul_val == 0 || ul_val >= UINT32_MAX) { + ret = ERANGE; + goto done; + } + range->id_range_size = ul_val; + + ul_val = slapi_entry_attr_get_ulong(entry, IPA_BASE_RID); + if (ul_val >= UINT32_MAX) { + ret = ERANGE; + goto done; + } + range->base_rid = ul_val; + + ul_val = slapi_entry_attr_get_ulong(entry, IPA_SECONDARY_BASE_RID); + if (ul_val >= UINT32_MAX) { + ret = ERANGE; + goto done; + } + range->secondary_base_rid = ul_val; + + *_range = range; + ret = 0; + +done: + if (ret != 0) { + free(range); + } + + return ret; +} + +#define IN_RANGE(x,base,size) ( (x) >= (base) && ((x) - (base)) < (size) ) +static bool ranges_overlap(struct range_info *r1, struct range_info *r2) +{ + if (r1->name != NULL && r2->name != NULL && + strcasecmp(r1->name, r2->name) == 0) { + return false; + } + + if (IN_RANGE(r1->base_id, r2->base_id, r2->id_range_size) || + IN_RANGE((r1->base_id + r1->id_range_size - 1), r2->base_id, r2->id_range_size) || + IN_RANGE(r2->base_id, r1->base_id, r1->id_range_size) || + IN_RANGE((r2->base_id + r2->id_range_size - 1), r1->base_id, r1->id_range_size)) { + return true; + } + + return false; +} + +static int ipa_range_check_start(Slapi_PBlock *pb) +{ + return 0; +} + +static int ipa_range_check_close(Slapi_PBlock *pb) +{ + return 0; +} + +static int ipa_range_check_pre_op(Slapi_PBlock *pb, int modtype) +{ + int ret; + int is_repl_op; + struct slapi_entry *entry = NULL; + bool free_entry = false; + struct range_info *new_range = NULL; + struct range_info *old_range = NULL; + const char *dn_str; + Slapi_DN *dn = NULL; + struct ipa_range_check_ctx *ctx; + LDAPMod **mods = NULL; + Slapi_PBlock *search_pb = NULL; + int search_result; + Slapi_Entry **search_entries = NULL; + size_t c; + bool overlap = true; + const char *check_attr; + char *errmsg = NULL; + + ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); + if (ret != 0) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + return LDAP_OPERATIONS_ERROR; + } + + if (is_repl_op) { + LOG("Is replicated operation, nothing to do.\n"); + return LDAP_SUCCESS; + } + + ret = slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &ctx); + if (ret != 0) { + LOG_FATAL("Missing private plugin context.\n"); + goto done; + } + + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn_str); + if (ret != 0) { + LOG_FATAL("Missing target DN.\n"); + goto done; + } + + dn = slapi_sdn_new_dn_byref(dn_str); + if (dn == NULL) { + LOG_FATAL("Failed to convert target DN.\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + + switch (modtype) { + case LDAP_CHANGETYPE_ADD: + ret = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &entry); + if (ret != 0) { + LOG_FATAL("Missing entry to add.\n"); + goto done; + } + + /* Check if this is a range object */ + check_attr = slapi_entry_attr_get_charptr(entry, IPA_BASE_ID); + if (check_attr == NULL) { + LOG("Not an ID range object, nothing to do.\n"); + ret = 0; + goto done; + } + + break; + case LDAP_CHANGETYPE_MODIFY: + ret = slapi_search_internal_get_entry(dn, NULL, &entry, + ctx->plugin_id); + if (ret != 0 || entry == NULL) { + LOG_FATAL("Missing entry to modify.\n"); + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + free_entry = true; + + /* Check if this is a range object */ + check_attr = slapi_entry_attr_get_charptr(entry, IPA_BASE_ID); + if (check_attr == NULL) { + LOG("Not an ID range object, nothing to do.\n"); + ret = 0; + goto done; + } + + ret = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + if (ret != 0) { + LOG_FATAL("Missing modify values.\n"); + goto done; + } + + ret = slapi_entry_apply_mods(entry, mods); + if (ret != 0) { + LOG_FATAL("Failed to apply modifications.\n"); + goto done; + } + + break; + default: + ret = LDAP_OPERATIONS_ERROR; + LOG_FATAL("Unsupported LDAP operation.\n"); + goto done; + } + + ret = slapi_entry_to_range_info(entry, &new_range); + if (ret != 0) { + LOG_FATAL("Failed to convert LDAP entry to range struct.\n"); + goto done; + } + + search_pb = slapi_pblock_new(); + if (search_pb == NULL) { + LOG_FATAL("Failed to create new pblock.\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + slapi_search_internal_set_pb(search_pb, ctx->base_dn, + LDAP_SCOPE_SUBTREE, RANGES_FILTER, + NULL, 0, NULL, NULL, ctx->plugin_id, 0); + + ret = slapi_search_internal_pb(search_pb); + if (ret != 0) { + LOG_FATAL("Starting internal search failed.\n"); + goto done; + } + + ret = slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &search_result); + if (ret != 0 || search_result != LDAP_SUCCESS) { + LOG_FATAL("Internal search failed.\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &search_entries); + if (ret != 0) { + LOG_FATAL("Failed to read searched entries.\n"); + goto done; + } + + if (search_entries == NULL || search_entries[0] == NULL) { + LOG("No existing entries.\n"); + ret = 0; + goto done; + } + + for (c = 0; search_entries[c] != NULL; c++) { + ret = slapi_entry_to_range_info(search_entries[c], &old_range); + if (ret != 0) { + LOG_FATAL("Failed to convert LDAP entry to range struct.\n"); + goto done; + } + + overlap = ranges_overlap(old_range, new_range); + free(old_range); + old_range = NULL; + if (overlap) { + LOG_FATAL("New range overlaps with existing one.\n"); + ret = LDAP_CONSTRAINT_VIOLATION; + errmsg = "New range overlaps with existing one."; + goto done; + } + } + LOG("No overlaps found.\n"); + + ret = 0; + +done: + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + slapi_sdn_free(&dn); + free(old_range); + free(new_range); + if (free_entry) { + slapi_entry_free(entry); + } + + if (ret != 0) { + if (errmsg == NULL) { + errmsg = "Range Check error"; + } + slapi_send_ldap_result(pb, ret, NULL, errmsg, 0, NULL); + } + + return ret; +} + +static int ipa_range_check_mod_pre_op(Slapi_PBlock * pb) +{ + return ipa_range_check_pre_op(pb, LDAP_CHANGETYPE_MODIFY); +} + +static int ipa_range_check_add_pre_op(Slapi_PBlock *pb) +{ + return ipa_range_check_pre_op(pb, LDAP_CHANGETYPE_ADD); +} + +static int ipa_range_check_init_ctx(Slapi_PBlock *pb, + struct ipa_range_check_ctx **_ctx) +{ + struct ipa_range_check_ctx *ctx; + Slapi_Entry *entry; + int ret; + + ctx = calloc(1, sizeof(struct ipa_range_check_ctx)); + if (ctx == NULL) { + return LDAP_OPERATIONS_ERROR; + } + + ret = slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ctx->plugin_id); + if ((ret != 0) || (ctx->plugin_id == NULL)) { + LOG_FATAL("Could not get identity or identity was NULL\n"); + if (ret == 0) { + ret = -1; + } + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &entry); + if (entry == NULL) { + LOG_FATAL("Plugin configuration not found!\n"); + ret = EINVAL; + goto done; + } + + ctx->base_dn = slapi_entry_attr_get_charptr(entry, "nsslapd-basedn"); + if (ctx->base_dn == NULL) { + LOG_FATAL("Base DN not found in plugin configuration!\n"); + ret = EINVAL; + goto done; + } + + ret = 0; + +done: + if (ret != 0) { + free(ctx); + } else { + *_ctx = ctx; + } + + return ret; +} + +int ipa_range_check_init(Slapi_PBlock *pb) +{ + int ret; + struct ipa_range_check_ctx *rc_ctx; + + ret = ipa_range_check_init_ctx(pb, &rc_ctx); + if (ret != 0) { + LOG_FATAL("Failed ot initialize range check plugin.\n"); + /* do not cause DS to stop, simply do nothing */ + return 0; + } + + ret = 0; + if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) ipa_range_check_start) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) ipa_range_check_close) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void *) &ipa_range_check_plugin_desc) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, + (void *) ipa_range_check_mod_pre_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, + (void *) ipa_range_check_add_pre_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, rc_ctx) != 0) { + LOG_FATAL("failed to register plugin\n"); + ret = EFAIL; + } + + return ret; +} diff --git a/daemons/ipa-slapi-plugins/ipa-range-check/range-check-conf.ldif b/daemons/ipa-slapi-plugins/ipa-range-check/range-check-conf.ldif new file mode 100644 index 000000000..6452304f4 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-range-check/range-check-conf.ldif @@ -0,0 +1,16 @@ +dn: cn=IPA Range-Check,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: IPA Range-Check +nsslapd-pluginpath: libipa_range_check +nsslapd-plugininitfunc: ipa_range_check_init +nsslapd-plugintype: preoperation +nsslapd-pluginenabled: on +nsslapd-pluginid: ipa_range_check_version +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: Red Hat, Inc. +nsslapd-plugindescription: IPA Range-Check plugin +nsslapd-plugin-depends-on-type: database +nsslapd-basedn: $SUFFIX -- cgit