/* $NetBSD: dupent.c,v 1.3 2021/08/14 16:14:51 christos Exp $ */ /* dupent.c - LDAP Control for a Duplicate Entry Representation of Search Results */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2006-2021 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 initially developed by Pierangelo Masarati for inclusion * in OpenLDAP Software. */ /* * LDAP Control for a Duplicate Entry Representation of Search Results * (EXPIRED) * */ #include __RCSID("$NetBSD: dupent.c,v 1.3 2021/08/14 16:14:51 christos Exp $"); #include "portable.h" /* define SLAPD_OVER_DUPENT=2 to build as run-time loadable module */ #ifdef SLAPD_OVER_DUPENT /* * The macros * * LDAP_CONTROL_DUPENT_REQUEST "2.16.840.1.113719.1.27.101.1" * LDAP_CONTROL_DUPENT_RESPONSE "2.16.840.1.113719.1.27.101.2" * LDAP_CONTROL_DUPENT_ENTRY "2.16.840.1.113719.1.27.101.3" * * are already defined in */ /* * support for no attrs and "*" in AttributeDescriptionList is missing */ #include "slap.h" #include "ac/string.h" #define o_dupent o_ctrlflag[dupent_cid] #define o_ctrldupent o_controls[dupent_cid] static int dupent_cid; static slap_overinst dupent; typedef struct dupent_t { AttributeName *ds_an; ber_len_t ds_nattrs; slap_mask_t ds_flags; ber_int_t ds_paa; } dupent_t; static int dupent_parseCtrl ( Operation *op, SlapReply *rs, LDAPControl *ctrl ) { ber_tag_t tag; BerElementBuffer berbuf; BerElement *ber = (BerElement *)&berbuf; ber_len_t len; BerVarray AttributeDescriptionList = NULL; ber_len_t cnt = sizeof(struct berval); ber_len_t off = 0; ber_int_t PartialApplicationAllowed = 1; dupent_t *ds = NULL; int i; if ( op->o_dupent != SLAP_CONTROL_NONE ) { rs->sr_text = "Dupent control specified multiple times"; return LDAP_PROTOCOL_ERROR; } if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { rs->sr_text = "Dupent control value is absent"; return LDAP_PROTOCOL_ERROR; } if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { rs->sr_text = "Dupent control value is empty"; return LDAP_PROTOCOL_ERROR; } ber_init2( ber, &ctrl->ldctl_value, 0 ); /* DuplicateEntryRequest ::= SEQUENCE { AttributeDescriptionList, -- from [RFC2251] PartialApplicationAllowed BOOLEAN DEFAULT TRUE } AttributeDescriptionList ::= SEQUENCE OF AttributeDescription AttributeDescription ::= LDAPString attributeDescription = AttributeType [ ";" ] */ tag = ber_skip_tag( ber, &len ); if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; if ( ber_scanf( ber, "{M}", &AttributeDescriptionList, &cnt, off ) == LBER_ERROR ) { rs->sr_text = "Dupent control: dupentSpec decoding error"; rs->sr_err = LDAP_PROTOCOL_ERROR; goto done; } tag = ber_skip_tag( ber, &len ); if ( tag == LBER_BOOLEAN ) { /* NOTE: PartialApplicationAllowed is ignored, since the control * can always be honored */ if ( ber_scanf( ber, "b", &PartialApplicationAllowed ) == LBER_ERROR ) { rs->sr_text = "Dupent control: dupentSpec decoding error"; rs->sr_err = LDAP_PROTOCOL_ERROR; goto done; } tag = ber_skip_tag( ber, &len ); } if ( len || tag != LBER_DEFAULT ) { rs->sr_text = "Dupent control: dupentSpec decoding error"; rs->sr_err = LDAP_PROTOCOL_ERROR; goto done; } ds = (dupent_t *)op->o_tmpcalloc( 1, sizeof(dupent_t) + sizeof(AttributeName)*cnt, op->o_tmpmemctx ); ds->ds_paa = PartialApplicationAllowed; if ( cnt == 0 ) { ds->ds_flags |= SLAP_USERATTRS_YES; } else { int c; ds->ds_an = (AttributeName *)&ds[ 1 ]; for ( i = 0, c = 0; i < cnt; i++ ) { const char *text; int j; int rc; AttributeDescription *ad = NULL; if ( bvmatch( &AttributeDescriptionList[i], slap_bv_all_user_attrs ) ) { if ( ds->ds_flags & SLAP_USERATTRS_YES ) { rs->sr_text = "Dupent control: AttributeDescription decoding error"; rs->sr_err = LDAP_PROTOCOL_ERROR; goto done; } ds->ds_flags |= SLAP_USERATTRS_YES; continue; } rc = slap_bv2ad( &AttributeDescriptionList[i], &ad, &text ); if ( rc != LDAP_SUCCESS ) { continue; } ds->ds_an[c].an_desc = ad; ds->ds_an[c].an_name = ad->ad_cname; /* FIXME: not specified; consider this an error, just in case */ for ( j = 0; j < c; j++ ) { if ( ds->ds_an[c].an_desc == ds->ds_an[j].an_desc ) { rs->sr_text = "Dupent control: AttributeDescription must be unique within AttributeDescriptionList"; rs->sr_err = LDAP_PROTOCOL_ERROR; goto done; } } c++; } ds->ds_nattrs = c; if ( ds->ds_flags & SLAP_USERATTRS_YES ) { /* purge user attrs */ for ( i = 0; i < ds->ds_nattrs; ) { if ( is_at_operational( ds->ds_an[i].an_desc->ad_type ) ) { i++; continue; } ds->ds_nattrs--; if ( i < ds->ds_nattrs ) { ds->ds_an[i] = ds->ds_an[ds->ds_nattrs]; } } } } op->o_ctrldupent = (void *)ds; op->o_dupent = ctrl->ldctl_iscritical ? SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; rs->sr_err = LDAP_SUCCESS; done:; if ( rs->sr_err != LDAP_SUCCESS ) { op->o_tmpfree( ds, op->o_tmpmemctx ); } if ( AttributeDescriptionList != NULL ) { ber_memfree_x( AttributeDescriptionList, op->o_tmpmemctx ); } return rs->sr_err; } typedef struct dupent_cb_t { slap_overinst *dc_on; dupent_t *dc_ds; int dc_skip; } dupent_cb_t; typedef struct valnum_t { Attribute *ap; Attribute a; struct berval vals[2]; struct berval nvals[2]; int cnt; } valnum_t; static int dupent_response_done( Operation *op, SlapReply *rs ) { BerElementBuffer berbuf; BerElement *ber = (BerElement *) &berbuf; struct berval ctrlval; LDAPControl *ctrl, *ctrlsp[2]; ber_init2( ber, NULL, LBER_USE_DER ); /* DuplicateEntryResponseDone ::= SEQUENCE { resultCode, -- From [RFC2251] errorMessage [0] LDAPString OPTIONAL, attribute [1] AttributeDescription OPTIONAL } */ ber_printf( ber, "{i}", rs->sr_err ); if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) { ber_free_buf( ber ); if ( op->o_dupent == SLAP_CONTROL_CRITICAL ) { return LDAP_CONSTRAINT_VIOLATION; } return SLAP_CB_CONTINUE; } ctrl = op->o_tmpcalloc( 1, sizeof( LDAPControl ) + ctrlval.bv_len + 1, op->o_tmpmemctx ); ctrl->ldctl_value.bv_val = (char *)&ctrl[ 1 ]; ctrl->ldctl_oid = LDAP_CONTROL_DUPENT_RESPONSE; ctrl->ldctl_iscritical = 0; ctrl->ldctl_value.bv_len = ctrlval.bv_len; AC_MEMCPY( ctrl->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len ); ctrl->ldctl_value.bv_val[ ctrl->ldctl_value.bv_len ] = '\0'; ber_free_buf( ber ); ctrlsp[0] = ctrl; ctrlsp[1] = NULL; slap_add_ctrls( op, rs, ctrlsp ); return SLAP_CB_CONTINUE; } static int dupent_response_entry_1level( Operation *op, SlapReply *rs, Entry *e, valnum_t *valnum, int nattrs, int level ) { int i, rc = LDAP_SUCCESS; for ( i = 0; i < valnum[level].ap->a_numvals; i++ ) { LDAPControl *ctrl = NULL, *ctrlsp[2]; valnum[level].a.a_vals[0] = valnum[level].ap->a_vals[i]; if ( valnum[level].ap->a_nvals != valnum[level].ap->a_vals ) { valnum[level].a.a_nvals[0] = valnum[level].ap->a_nvals[i]; } if ( level < nattrs - 1 ) { rc = dupent_response_entry_1level( op, rs, e, valnum, nattrs, level + 1 ); if ( rc != LDAP_SUCCESS ) { break; } continue; } /* NOTE: add the control all times, under the assumption * send_search_entry() honors the REP_CTRLS_MUSTBEFREED * set by slap_add_ctrls(); this is not true (ITS#6629) */ ctrl = op->o_tmpcalloc( 1, sizeof( LDAPControl ), op->o_tmpmemctx ); ctrl->ldctl_oid = LDAP_CONTROL_DUPENT_ENTRY; ctrl->ldctl_iscritical = 0; ctrlsp[0] = ctrl; ctrlsp[1] = NULL; slap_add_ctrls( op, rs, ctrlsp ); /* do the real send */ rs->sr_entry = e; rc = send_search_entry( op, rs ); if ( rc != LDAP_SUCCESS ) { break; } } return rc; } static void dupent_attr_prepare( dupent_t *ds, Entry *e, valnum_t *valnum, int nattrs, int c, Attribute **app, Attribute **ap_listp ) { valnum[c].ap = *app; *app = (*app)->a_next; valnum[c].ap->a_next = *ap_listp; *ap_listp = valnum[c].ap; valnum[c].a = *valnum[c].ap; if ( c < nattrs - 1 ) { valnum[c].a.a_next = &valnum[c + 1].a; } else { valnum[c].a.a_next = NULL; } valnum[c].a.a_numvals = 1; valnum[c].a.a_vals = valnum[c].vals; BER_BVZERO( &valnum[c].vals[1] ); if ( valnum[c].ap->a_nvals != valnum[c].ap->a_vals ) { valnum[c].a.a_nvals = valnum[c].nvals; BER_BVZERO( &valnum[c].nvals[1] ); } else { valnum[c].a.a_nvals = valnum[c].a.a_vals; } } static int dupent_response_entry( Operation *op, SlapReply *rs ) { dupent_cb_t *dc = (dupent_cb_t *)op->o_callback->sc_private; int nattrs = 0; valnum_t *valnum = NULL; Attribute **app, *ap_list = NULL; int i, c; Entry *e = NULL; int rc; assert( rs->sr_type == REP_SEARCH ); for ( i = 0; i < dc->dc_ds->ds_nattrs; i++ ) { Attribute *ap; ap = attr_find( rs->sr_entry->e_attrs, dc->dc_ds->ds_an[ i ].an_desc ); if ( ap && ap->a_numvals > 1 ) { nattrs++; } } if ( dc->dc_ds->ds_flags & SLAP_USERATTRS_YES ) { Attribute *ap; for ( ap = rs->sr_entry->e_attrs; ap != NULL; ap = ap->a_next ) { if ( !is_at_operational( ap->a_desc->ad_type ) && ap->a_numvals > 1 ) { nattrs++; } } } if ( !nattrs ) { return SLAP_CB_CONTINUE; } rs_entry2modifiable( op, rs, dc->dc_on ); rs->sr_flags &= ~(REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED); e = rs->sr_entry; valnum = op->o_tmpcalloc( sizeof(valnum_t), nattrs, op->o_tmpmemctx ); for ( c = 0, i = 0; i < dc->dc_ds->ds_nattrs; i++ ) { for ( app = &e->e_attrs; *app != NULL; app = &(*app)->a_next ) { if ( (*app)->a_desc == dc->dc_ds->ds_an[ i ].an_desc ) { break; } } if ( *app != NULL && (*app)->a_numvals > 1 ) { assert( c < nattrs ); dupent_attr_prepare( dc->dc_ds, e, valnum, nattrs, c, app, &ap_list ); c++; } } if ( dc->dc_ds->ds_flags & SLAP_USERATTRS_YES ) { for ( app = &e->e_attrs; *app != NULL; app = &(*app)->a_next ) { if ( !is_at_operational( (*app)->a_desc->ad_type ) && (*app)->a_numvals > 1 ) { assert( c < nattrs ); dupent_attr_prepare( dc->dc_ds, e, valnum, nattrs, c, app, &ap_list ); c++; } } } for ( app = &e->e_attrs; *app != NULL; app = &(*app)->a_next ) /* goto tail */ ; *app = &valnum[0].a; /* NOTE: since send_search_entry() does not honor the * REP_CTRLS_MUSTBEFREED flag set by slap_add_ctrls(), * the control could be added here once for all (ITS#6629) */ dc->dc_skip = 1; rc = dupent_response_entry_1level( op, rs, e, valnum, nattrs, 0 ); dc->dc_skip = 0; *app = ap_list; entry_free( e ); op->o_tmpfree( valnum, op->o_tmpmemctx ); return rc; } static int dupent_response( Operation *op, SlapReply *rs ) { dupent_cb_t *dc = (dupent_cb_t *)op->o_callback->sc_private; if ( dc->dc_skip ) { return SLAP_CB_CONTINUE; } switch ( rs->sr_type ) { case REP_RESULT: return dupent_response_done( op, rs ); case REP_SEARCH: return dupent_response_entry( op, rs ); case REP_SEARCHREF: break; default: assert( 0 ); } return SLAP_CB_CONTINUE; } static int dupent_cleanup( Operation *op, SlapReply *rs ) { if ( rs->sr_type == REP_RESULT || rs->sr_err == SLAPD_ABANDON ) { op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); op->o_callback = NULL; op->o_tmpfree( op->o_ctrldupent, op->o_tmpmemctx ); op->o_ctrldupent = NULL; } return SLAP_CB_CONTINUE; } static int dupent_op_search( Operation *op, SlapReply *rs ) { if ( op->o_dupent != SLAP_CONTROL_NONE ) { slap_callback *sc; dupent_cb_t *dc; sc = op->o_tmpcalloc( 1, sizeof( slap_callback ) + sizeof( dupent_cb_t ), op->o_tmpmemctx ); dc = (dupent_cb_t *)&sc[ 1 ]; dc->dc_on = (slap_overinst *)op->o_bd->bd_info; dc->dc_ds = (dupent_t *)op->o_ctrldupent; dc->dc_skip = 0; sc->sc_response = dupent_response; sc->sc_cleanup = dupent_cleanup; sc->sc_private = (void *)dc; sc->sc_next = op->o_callback->sc_next; op->o_callback->sc_next = sc; } return SLAP_CB_CONTINUE; } #if SLAPD_OVER_DUPENT == SLAPD_MOD_DYNAMIC static #endif /* SLAPD_OVER_DUPENT == SLAPD_MOD_DYNAMIC */ int dupent_initialize( void ) { int rc; rc = register_supported_control( LDAP_CONTROL_DUPENT, SLAP_CTRL_SEARCH, NULL, dupent_parseCtrl, &dupent_cid ); if ( rc != LDAP_SUCCESS ) { Debug( LDAP_DEBUG_ANY, "dupent_initialize: Failed to register control (%d)\n", rc ); return -1; } dupent.on_bi.bi_type = "dupent"; dupent.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; dupent.on_bi.bi_op_search = dupent_op_search; return overlay_register( &dupent ); } #if SLAPD_OVER_DUPENT == SLAPD_MOD_DYNAMIC int init_module( int argc, char *argv[] ) { return dupent_initialize(); } #endif /* SLAPD_OVER_DUPENT == SLAPD_MOD_DYNAMIC */ #endif /* SLAPD_OVER_DUPENT */