summaryrefslogtreecommitdiffstats
path: root/contrib/slapd-modules/alias/alias.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--contrib/slapd-modules/alias/alias.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/contrib/slapd-modules/alias/alias.c b/contrib/slapd-modules/alias/alias.c
new file mode 100644
index 0000000..c5707ff
--- /dev/null
+++ b/contrib/slapd-modules/alias/alias.c
@@ -0,0 +1,673 @@
+/* alias.c - expose an attribute under a different name */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2023 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed in 2023 by Ondřej Kuzník for Symas Corp.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_ALIAS
+
+#include <inttypes.h>
+#include <ac/stdlib.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldap_queue.h"
+
+typedef struct alias_mapping_t {
+ AttributeDescription *source;
+ AttributeDescription *alias;
+} alias_mapping;
+
+typedef struct alias_info_t {
+ alias_mapping *mappings;
+} alias_info;
+
+typedef struct alias_sc_private_t {
+ slap_overinst *on;
+ AttributeName *attrs_orig, *attrs_new;
+} alias_sc_private;
+
+static alias_mapping *
+attribute_mapped( alias_info *ov, AttributeDescription *ad )
+{
+ alias_mapping *m;
+
+ for ( m = ov->mappings; m && m->source; m++ ) {
+ if ( ad == m->alias ) return m;
+ }
+
+ return NULL;
+}
+
+static int
+alias_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ alias_info *ov = on->on_bi.bi_private;
+ Entry *e = op->ora_e;
+ Attribute *a;
+ int rc = LDAP_SUCCESS;
+
+ if ( !BER_BVISEMPTY( &e->e_nname ) ) {
+ LDAPRDN rDN;
+ const char *p;
+ int i;
+
+ rc = ldap_bv2rdn_x( &e->e_nname, &rDN, (char **)&p, LDAP_DN_FORMAT_LDAP,
+ op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "alias_op_add: "
+ "can't parse rdn: dn=%s\n",
+ op->o_req_ndn.bv_val );
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( i = 0; rDN[i]; i++ ) {
+ AttributeDescription *ad = NULL;
+
+ /* If we can't resolve the attribute, ignore it */
+ if ( slap_bv2ad( &rDN[i]->la_attr, &ad, &p ) ) {
+ continue;
+ }
+
+ if ( attribute_mapped( ov, ad ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ }
+ }
+
+ ldap_rdnfree_x( rDN, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_error( op, rs, rc,
+ "trying to add a virtual attribute in RDN" );
+ return rc;
+ }
+ }
+
+ for ( a = e->e_attrs; a; a = a->a_next ) {
+ if ( attribute_mapped( ov, a->a_desc ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ send_ldap_error( op, rs, rc,
+ "trying to add a virtual attribute" );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+alias_op_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ alias_info *ov = on->on_bi.bi_private;
+ alias_mapping *alias = attribute_mapped( ov, op->orc_ava->aa_desc );
+
+ if ( alias )
+ op->orc_ava->aa_desc = alias->source;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+alias_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ alias_info *ov = on->on_bi.bi_private;
+ Modifications *mod;
+ int rc = LDAP_CONSTRAINT_VIOLATION;
+
+ for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) {
+ if ( attribute_mapped( ov, mod->sml_desc ) ) {
+ send_ldap_error( op, rs, rc,
+ "trying to modify a virtual attribute" );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+alias_op_modrdn( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ alias_info *ov = on->on_bi.bi_private;
+ LDAPRDN rDN;
+ const char *p;
+ int i, rc = SLAP_CB_CONTINUE;
+
+ rc = ldap_bv2rdn_x( &op->orr_nnewrdn, &rDN, (char **)&p,
+ LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "alias_op_modrdn: "
+ "can't parse rdn for dn=%s\n",
+ op->o_req_ndn.bv_val );
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( i = 0; rDN[i]; i++ ) {
+ AttributeDescription *ad = NULL;
+
+ /* If we can't resolve the attribute, ignore it */
+ if ( slap_bv2ad( &rDN[i]->la_attr, &ad, &p ) ) {
+ continue;
+ }
+
+ if ( attribute_mapped( ov, ad ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ }
+ }
+
+ ldap_rdnfree_x( rDN, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_error( op, rs, rc,
+ "trying to add a virtual attribute in RDN" );
+ return rc;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+alias_response_cleanup( Operation *op, SlapReply *rs )
+{
+ alias_sc_private *data = op->o_callback->sc_private;
+
+ if ( rs->sr_type == REP_RESULT || op->o_abandon ||
+ rs->sr_err == SLAPD_ABANDON )
+ {
+ if ( op->ors_attrs == data->attrs_new )
+ op->ors_attrs = data->attrs_orig;
+
+ ch_free( data->attrs_new );
+ ch_free( op->o_callback );
+ op->o_callback = NULL;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+alias_response( Operation *op, SlapReply *rs )
+{
+ alias_sc_private *data = op->o_callback->sc_private;
+ slap_overinst *on = data->on;
+ alias_info *ov = on->on_bi.bi_private;
+ Entry *e = NULL, *e_orig = rs->sr_entry;
+ alias_mapping *mapping;
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( rs->sr_type != REP_SEARCH || !e_orig ) {
+ return rc;
+ }
+
+ for ( mapping = ov->mappings; mapping && mapping->source; mapping++ ) {
+ Attribute *source, *a;
+ int operational = is_at_operational( mapping->source->ad_type ),
+ keep_source = 0;
+ slap_mask_t requested = operational ?
+ SLAP_OPATTRS_YES : SLAP_USERATTRS_YES;
+
+ if ( !(requested & rs->sr_attr_flags) &&
+ !ad_inlist( mapping->alias, rs->sr_attrs ) )
+ continue;
+
+ /* TODO: deal with multiple aliases from the same source */
+ if ( (requested & rs->sr_attr_flags) ||
+ ad_inlist( mapping->source, data->attrs_orig ) ) {
+ keep_source = 1;
+ }
+
+ if ( operational ) {
+ source = attr_find( rs->sr_operational_attrs, mapping->source );
+ }
+ if ( !source ) {
+ operational = 0;
+ source = attr_find( e_orig->e_attrs, mapping->source );
+ }
+ if ( !source )
+ continue;
+
+ if ( operational ) {
+ if ( !keep_source ) {
+ source->a_desc = mapping->alias;
+ } else {
+ Attribute **ap;
+
+ a = attr_dup( source );
+ a->a_desc = mapping->alias;
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap=&(*ap)->a_next );
+ *ap = a;
+ }
+ continue;
+ }
+
+ if ( !e ) {
+ if ( rs->sr_flags & REP_ENTRY_MODIFIABLE ) {
+ e = e_orig;
+ } else {
+ e = entry_dup( e_orig );
+ }
+ }
+
+ a = attr_find( e->e_attrs, mapping->source );
+ if ( !keep_source ) {
+ a->a_desc = mapping->alias;
+ } else {
+ attr_merge( e, mapping->alias, a->a_vals, a->a_nvals );
+ }
+ }
+
+ if ( e && e != e_orig ) {
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags &= ~REP_ENTRY_MASK;
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+
+ return rc;
+}
+
+static int
+alias_filter( alias_info *ov, Filter *f )
+{
+ int changed = 0;
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR: {
+ for ( f = f->f_and; f; f = f->f_next ) {
+ int result = alias_filter( ov, f );
+ if ( result < 0 ) {
+ return result;
+ }
+ changed += result;
+ }
+ } break;
+
+ case LDAP_FILTER_NOT:
+ return alias_filter( ov, f->f_not );
+
+ case LDAP_FILTER_PRESENT: {
+ alias_mapping *alias = attribute_mapped( ov, f->f_desc );
+ if ( alias ) {
+ f->f_desc = alias->source;
+ changed = 1;
+ }
+ } break;
+
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE: {
+ alias_mapping *alias = attribute_mapped( ov, f->f_av_desc );
+ if ( alias ) {
+ f->f_av_desc = alias->source;
+ changed = 1;
+ }
+ } break;
+
+ case LDAP_FILTER_SUBSTRINGS: {
+ alias_mapping *alias = attribute_mapped( ov, f->f_sub_desc );
+ if ( alias ) {
+ f->f_sub_desc = alias->source;
+ changed = 1;
+ }
+ } break;
+
+ case LDAP_FILTER_EXT: {
+ alias_mapping *alias = attribute_mapped( ov, f->f_mr_desc );
+ if ( alias ) {
+ f->f_mr_desc = alias->source;
+ changed = 1;
+ }
+ } break;
+
+ default:
+ return -1;
+ }
+ return changed;
+}
+
+static int
+alias_op_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ alias_info *ov = on->on_bi.bi_private;
+ alias_mapping *mapping;
+ AttributeName *an_orig = NULL, *an_new = NULL;
+ int mapped, an_length = 0;
+
+ if ( get_manageDSAit( op ) )
+ return SLAP_CB_CONTINUE;
+
+ /*
+ * 1. check filter: traverse, map aliased attributes
+ * 2. unparse filter
+ * 3. check all requested attributes -> register callback if one matches
+ */
+ if ( (mapped = alias_filter( ov, op->ors_filter )) < 0 ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "alias_op_search: failed to process filter" );
+ return LDAP_OTHER;
+ }
+
+ if ( mapped ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+ }
+
+ mapped = 0;
+ for ( mapping = ov->mappings; mapping && mapping->source; mapping++ ) {
+ int operational = is_at_operational( mapping->source->ad_type );
+ slap_mask_t requested = operational ?
+ SLAP_OPATTRS_YES : SLAP_USERATTRS_YES;
+
+ if ( requested & slap_attr_flags( op->ors_attrs ) ) {
+ mapped = 1;
+ } else if ( ad_inlist( mapping->alias, op->ors_attrs ) ) {
+ mapped = 1;
+ if ( !an_length ) {
+ for ( ; !BER_BVISNULL( &op->ors_attrs[an_length].an_name ); an_length++ )
+ /* Count */;
+ }
+
+ an_new = ch_realloc( an_new, (an_length+2)*sizeof(AttributeName) );
+ if ( !an_orig ) {
+ int i;
+ an_orig = op->ors_attrs;
+ for ( i=0; i < an_length; i++ ) {
+ an_new[i] = an_orig[i];
+ }
+ }
+
+ an_new[an_length].an_name = mapping->source->ad_cname;
+ an_new[an_length].an_desc = mapping->source;
+ an_length++;
+
+ BER_BVZERO( &an_new[an_length].an_name );
+ }
+ }
+
+ if ( mapped ) {
+ /* We have something to map back */
+ slap_callback *cb = op->o_tmpcalloc( 1,
+ sizeof(slap_callback)+sizeof(alias_sc_private),
+ op->o_tmpmemctx );
+ alias_sc_private *data = (alias_sc_private *)(cb+1);
+
+ data->on = on;
+
+ cb->sc_response = alias_response;
+ cb->sc_private = data;
+ cb->sc_next = op->o_callback;
+ cb->sc_cleanup = alias_response_cleanup;
+
+ if ( an_new ) {
+ data->attrs_orig = an_orig;
+ data->attrs_new = an_new;
+ op->ors_attrs = an_new;
+ }
+
+ op->o_callback = cb;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* Configuration */
+
+static ConfigDriver alias_config_mapping;
+
+static ConfigTable alias_cfg[] = {
+ { "alias_attribute", "attr> <attr", 3, 3, 0,
+ ARG_MAGIC,
+ alias_config_mapping,
+ "( OLcfgCtAt:10.1 NAME 'olcAliasMapping' "
+ "DESC 'Alias definition' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+/*
+ * FIXME: There is no reason to keep olcAliasMapping MAY (making this overlay
+ * a noop) except we can't enforce a MUST with slaptest+slapd.conf.
+ */
+static ConfigOCs alias_ocs[] = {
+ { "( OLcfgCtOc:10.1 "
+ "NAME 'olcAliasConfig' "
+ "DESC 'Alias overlay configuration' "
+ "MAY ( olcAliasMapping ) "
+ "SUP olcOverlayConfig )",
+ Cft_Overlay, alias_cfg },
+
+ { NULL, 0, NULL }
+};
+
+static int
+alias_config_mapping( ConfigArgs *ca )
+{
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ alias_info *ov = on->on_bi.bi_private;
+ AttributeDescription *source = NULL, *alias = NULL;
+ AttributeType *sat, *aat;
+ const char *text;
+ int i, rc = LDAP_CONSTRAINT_VIOLATION;
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ alias_mapping *mapping;
+
+ for ( mapping = ov->mappings; mapping && mapping->source; mapping++ ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ struct berval bv = { .bv_val = buf, .bv_len = SLAP_TEXT_BUFLEN };
+ bv.bv_len = snprintf( buf, bv.bv_len, "%s %s",
+ mapping->source->ad_cname.bv_val,
+ mapping->alias->ad_cname.bv_val );
+ value_add_one( &ca->rvalue_vals, &bv );
+ }
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ if ( ca->valx < 0 ) {
+ ch_free( ov->mappings );
+ ov->mappings = NULL;
+ } else {
+ i = ca->valx;
+ do {
+ ov->mappings[i] = ov->mappings[i+1];
+ i++;
+ } while ( ov->mappings[i].source );
+ }
+ return LDAP_SUCCESS;
+ }
+
+ rc = slap_str2ad( ca->argv[1], &source, &text );
+ if ( rc ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "cannot resolve attribute '%s': \"%s\"",
+ ca->argv[1], text );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ goto done;
+ }
+
+ rc = slap_str2ad( ca->argv[2], &alias, &text );
+ if ( rc ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "cannot resolve attribute '%s': \"%s\"",
+ ca->argv[2], text );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ goto done;
+ }
+
+ sat = source->ad_type;
+ aat = alias->ad_type;
+ if ( sat == aat ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "cannot map attribute %s to itself",
+ source->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ /* The types have to match */
+ if ( is_at_operational( sat ) != is_at_operational( aat ) ||
+ is_at_single_value( sat ) != is_at_single_value( aat ) ||
+ sat->sat_syntax != aat->sat_syntax ||
+ sat->sat_equality != aat->sat_equality ||
+ sat->sat_approx != aat->sat_approx ||
+ sat->sat_ordering != aat->sat_ordering ||
+ sat->sat_substr != aat->sat_substr ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "attributes %s and %s syntax and/or "
+ "default matching rules don't match",
+ source->ad_cname.bv_val,
+ alias->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( !ov->mappings ) {
+ ov->mappings = ch_calloc( 2, sizeof(alias_mapping) );
+ ov->mappings[0].source = source;
+ ov->mappings[0].alias = alias;
+ } else {
+ int i;
+
+ for ( i = 0; ov->mappings[i].source; i++ ) {
+ if ( alias == ov->mappings[i].alias ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "attribute %s already mapped from %s",
+ alias->ad_cname.bv_val,
+ ov->mappings[i].source->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ if ( alias == ov->mappings[i].source ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "cannot use %s as alias source, already mapped from %s",
+ source->ad_cname.bv_val,
+ ov->mappings[i].source->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ if ( source == ov->mappings[i].alias ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "cannot use %s as alias, it is aliased to %s",
+ alias->ad_cname.bv_val,
+ ov->mappings[i].alias->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ }
+
+ if ( ca->valx < 0 || ca->valx > i )
+ ca->valx = i;
+
+ i++;
+ ov->mappings = ch_realloc( ov->mappings, (i + 1) * sizeof(alias_mapping) );
+ do {
+ ov->mappings[i] = ov->mappings[i-1];
+ } while ( --i > ca->valx );
+ ov->mappings[i].source = source;
+ ov->mappings[i].alias = alias;
+ }
+
+ rc = LDAP_SUCCESS;
+done:
+ ca->reply.err = rc;
+ return rc;
+}
+
+static slap_overinst alias;
+
+static int
+alias_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ alias_info *ov;
+
+ /* TODO: can this be global? */
+ if ( SLAP_ISGLOBALOVERLAY(be) ) {
+ Debug( LDAP_DEBUG_ANY, "alias overlay must be instantiated "
+ "within a database.\n" );
+ return 1;
+ }
+
+ ov = ch_calloc( 1, sizeof(alias_info) );
+ on->on_bi.bi_private = ov;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+alias_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ alias_info *ov = on->on_bi.bi_private;
+
+ if ( ov && ov->mappings ) {
+ ch_free( ov->mappings );
+ }
+ ch_free( ov );
+
+ return LDAP_SUCCESS;
+}
+
+int
+alias_initialize()
+{
+ int rc;
+
+ alias.on_bi.bi_type = "alias";
+ alias.on_bi.bi_db_init = alias_db_init;
+ alias.on_bi.bi_db_destroy = alias_db_destroy;
+
+ alias.on_bi.bi_op_add = alias_op_add;
+ alias.on_bi.bi_op_compare = alias_op_compare;
+ alias.on_bi.bi_op_modify = alias_op_mod;
+ alias.on_bi.bi_op_modrdn = alias_op_modrdn;
+ alias.on_bi.bi_op_search = alias_op_search;
+
+ alias.on_bi.bi_cf_ocs = alias_ocs;
+
+ rc = config_register_schema( alias_cfg, alias_ocs );
+ if ( rc ) return rc;
+
+ return overlay_register( &alias );
+}
+
+#if SLAPD_OVER_ALIAS == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return alias_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_ALIAS */