diff options
Diffstat (limited to 'contrib/slapd-modules/alias/alias.c')
-rw-r--r-- | contrib/slapd-modules/alias/alias.c | 673 |
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 */ |