/* alias.c - expose an attribute under a different name */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * 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 * . */ /* ACKNOWLEDGEMENTS: * This work was developed in 2023 by Ondřej Kuzník for Symas Corp. */ #include "portable.h" #ifdef SLAPD_OVER_ALIAS #include #include #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> 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 */