/* variant.c - variant overlay */
/* This work is part of OpenLDAP Software .
*
* Copyright 2016-2021 Symas Corporation.
* 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 2016-2017 by Ondřej Kuzník for Symas Corp.
*/
#include "portable.h"
#ifdef SLAPD_OVER_VARIANT
#include "slap.h"
#include "slap-config.h"
#include "ldap_queue.h"
typedef enum variant_type_t {
VARIANT_INFO_PLAIN = 1 << 0,
VARIANT_INFO_REGEX = 1 << 1,
VARIANT_INFO_ALL = ~0
} variant_type_t;
typedef struct variant_info_t {
int passReplication;
LDAP_STAILQ_HEAD(variant_list, variantEntry_info) variants, regex_variants;
} variant_info_t;
typedef struct variantEntry_info {
variant_info_t *ov;
struct berval dn;
variant_type_t type;
regex_t *regex;
LDAP_SLIST_HEAD(attribute_list, variantAttr_info) attributes;
LDAP_STAILQ_ENTRY(variantEntry_info) next;
} variantEntry_info;
typedef struct variantAttr_info {
variantEntry_info *variant;
struct berval dn;
AttributeDescription *attr, *alternative;
LDAP_SLIST_ENTRY(variantAttr_info) next;
} variantAttr_info;
static int
variant_build_dn(
Operation *op,
variantAttr_info *vai,
int nmatch,
regmatch_t *pmatch,
struct berval *out )
{
struct berval dn, *ndn = &op->o_req_ndn;
char *dest, *p, *prev, *end = vai->dn.bv_val + vai->dn.bv_len;
size_t len = vai->dn.bv_len;
int rc;
p = vai->dn.bv_val;
while ( (p = memchr( p, '$', end - p )) != NULL ) {
len -= 1;
p += 1;
if ( ( *p >= '0' ) && ( *p <= '9' ) ) {
int i = *p - '0';
len += ( pmatch[i].rm_eo - pmatch[i].rm_so );
} else if ( *p != '$' ) {
/* Should have been checked at configuration time */
assert(0);
}
len -= 1;
p += 1;
}
dest = dn.bv_val = ch_realloc( out->bv_val, len + 1 );
dn.bv_len = len;
prev = vai->dn.bv_val;
while ( (p = memchr( prev, '$', end - prev )) != NULL ) {
len = p - prev;
AC_MEMCPY( dest, prev, len );
dest += len;
p += 1;
if ( ( *p >= '0' ) && ( *p <= '9' ) ) {
int i = *p - '0';
len = pmatch[i].rm_eo - pmatch[i].rm_so;
AC_MEMCPY( dest, ndn->bv_val + pmatch[i].rm_so, len );
dest += len;
} else if ( *p == '$' ) {
*dest++ = *p;
}
prev = p + 1;
}
len = end - prev;
AC_MEMCPY( dest, prev, len );
dest += len;
*dest = '\0';
rc = dnNormalize( 0, NULL, NULL, &dn, out, NULL );
ch_free( dn.bv_val );
return rc;
}
static int
variant_build_entry(
Operation *op,
variantEntry_info *vei,
struct berval *dn,
Entry **ep,
int nmatch,
regmatch_t *pmatch )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
BackendDB *be_orig = op->o_bd, *db;
struct berval ndn = BER_BVNULL;
variantAttr_info *vai;
Attribute *a;
BerVarray nvals;
Entry *e;
unsigned int i;
int rc;
assert( ep );
assert( !*ep );
rc = overlay_entry_get_ov( op, dn, NULL, NULL, 0, &e, on );
if ( rc == LDAP_SUCCESS && is_entry_referral( e ) ) {
overlay_entry_release_ov( op, e, 0, on );
rc = LDAP_REFERRAL;
}
if ( rc != LDAP_SUCCESS ) {
goto done;
}
*ep = entry_dup( e );
overlay_entry_release_ov( op, e, 0, on );
LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
if ( vei->type == VARIANT_INFO_REGEX ) {
rc = variant_build_dn( op, vai, nmatch, pmatch, &ndn );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
} else {
ndn = vai->dn;
}
(void)attr_delete( &(*ep)->e_attrs, vai->attr );
op->o_bd = be_orig;
/* only select backend if not served by ours, would retrace all
* overlays again */
db = select_backend( &ndn, 0 );
if ( db && db != be_orig->bd_self ) {
op->o_bd = db;
rc = be_entry_get_rw( op, &ndn, NULL, vai->alternative, 0, &e );
} else {
rc = overlay_entry_get_ov(
op, &ndn, NULL, vai->alternative, 0, &e, on );
}
switch ( rc ) {
case LDAP_SUCCESS:
break;
case LDAP_INSUFFICIENT_ACCESS:
case LDAP_NO_SUCH_ATTRIBUTE:
case LDAP_NO_SUCH_OBJECT:
rc = LDAP_SUCCESS;
continue;
break;
default:
goto done;
break;
}
a = attr_find( e->e_attrs, vai->alternative );
/* back-ldif doesn't check the attribute exists in the entry before
* returning it */
if ( a ) {
if ( a->a_nvals ) {
nvals = a->a_nvals;
} else {
nvals = a->a_vals;
}
for ( i = 0; i < a->a_numvals; i++ ) {
if ( backend_access( op, e, &ndn, vai->alternative, &nvals[i],
ACL_READ, NULL ) != LDAP_SUCCESS ) {
continue;
}
rc = attr_merge_one( *ep, vai->attr, &a->a_vals[i], &nvals[i] );
if ( rc != LDAP_SUCCESS ) {
break;
}
}
}
if ( db && db != be_orig->bd_self ) {
be_entry_release_rw( op, e, 0 );
} else {
overlay_entry_release_ov( op, e, 0, on );
}
if ( rc != LDAP_SUCCESS ) {
goto done;
}
}
done:
op->o_bd = be_orig;
if ( rc != LDAP_SUCCESS && *ep ) {
entry_free( *ep );
*ep = NULL;
}
if ( vei->type == VARIANT_INFO_REGEX ) {
ch_free( ndn.bv_val );
}
return rc;
}
static int
variant_find_config(
Operation *op,
variant_info_t *ov,
struct berval *ndn,
int which,
variantEntry_info **veip,
size_t nmatch,
regmatch_t *pmatch )
{
variantEntry_info *vei;
assert( veip );
if ( which & VARIANT_INFO_PLAIN ) {
int diff;
LDAP_STAILQ_FOREACH( vei, &ov->variants, next ) {
dnMatch( &diff, 0, NULL, NULL, ndn, &vei->dn );
if ( diff ) continue;
*veip = vei;
return LDAP_SUCCESS;
}
}
if ( which & VARIANT_INFO_REGEX ) {
LDAP_STAILQ_FOREACH( vei, &ov->regex_variants, next ) {
if ( regexec( vei->regex, ndn->bv_val, nmatch, pmatch, 0 ) ) {
continue;
}
*veip = vei;
return LDAP_SUCCESS;
}
}
return SLAP_CB_CONTINUE;
}
static int
variant_op_add( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei;
int rc;
if ( ov->passReplication && be_shadow_update( op ) ) {
return SLAP_CB_CONTINUE;
}
Debug( LDAP_DEBUG_TRACE, "variant_op_add: "
"dn=%s\n", op->o_req_ndn.bv_val );
rc = variant_find_config(
op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei, 0, NULL );
if ( rc == LDAP_SUCCESS ) {
variantAttr_info *vai;
LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
Attribute *a;
for ( a = op->ora_e->e_attrs; a; a = a->a_next ) {
if ( a->a_desc == vai->attr ) {
rc = LDAP_CONSTRAINT_VIOLATION;
send_ldap_error( op, rs, rc,
"variant: trying to add variant attributes" );
goto done;
}
}
}
}
rc = SLAP_CB_CONTINUE;
done:
Debug( LDAP_DEBUG_TRACE, "variant_op_add: "
"finished with %d\n",
rc );
return rc;
}
static int
variant_op_compare( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei;
regmatch_t pmatch[10];
int rc, nmatch = sizeof(pmatch) / sizeof(regmatch_t);
Debug( LDAP_DEBUG_TRACE, "variant_op_compare: "
"dn=%s\n", op->o_req_ndn.bv_val );
rc = variant_find_config(
op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei, nmatch, pmatch );
if ( rc == LDAP_SUCCESS ) {
Entry *e = NULL;
rc = variant_build_entry( op, vei, &op->o_req_ndn, &e, nmatch, pmatch );
/* in case of error, just let the backend deal with the mod and the
* client should get a meaningful error back */
if ( rc != LDAP_SUCCESS ) {
rc = SLAP_CB_CONTINUE;
} else {
rc = slap_compare_entry( op, e, op->orc_ava );
entry_free( e );
e = NULL;
}
}
if ( rc != SLAP_CB_CONTINUE ) {
rs->sr_err = rc;
send_ldap_result( op, rs );
}
Debug( LDAP_DEBUG_TRACE, "variant_op_compare: "
"finished with %d\n", rc );
return rc;
}
static int
variant_cmp_op( const void *l, const void *r )
{
const Operation *left = l, *right = r;
int diff;
dnMatch( &diff, 0, NULL, NULL, (struct berval *)&left->o_req_ndn,
(void *)&right->o_req_ndn );
return diff;
}
static int
variant_run_mod( void *nop, void *arg )
{
SlapReply nrs = { REP_RESULT };
slap_callback cb = { 0 };
Operation *op = nop;
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
int *rc = arg;
cb.sc_response = slap_null_cb;
op->o_callback = &cb;
Debug( LDAP_DEBUG_TRACE, "variant_run_mod: "
"running mod on dn=%s\n",
op->o_req_ndn.bv_val );
*rc = on->on_info->oi_orig->bi_op_modify( op, &nrs );
Debug( LDAP_DEBUG_TRACE, "variant_run_mod: "
"finished with %d\n", *rc );
return ( *rc != LDAP_SUCCESS );
}
/** Move the Modifications back to the original Op so that they can be disposed
* of by the original creator
*/
static int
variant_reassign_mods( void *nop, void *arg )
{
Operation *op = nop, *orig_op = arg;
Modifications *mod;
assert( op->orm_modlist );
for ( mod = op->orm_modlist; mod->sml_next; mod = mod->sml_next )
/* get the tail mod */;
mod->sml_next = orig_op->orm_modlist;
orig_op->orm_modlist = op->orm_modlist;
return LDAP_SUCCESS;
}
void
variant_free_op( void *op )
{
ch_free( ((Operation *)op)->o_req_ndn.bv_val );
ch_free( op );
}
static int
variant_op_mod( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei;
variantAttr_info *vai;
Avlnode *ops = NULL;
Entry *e = NULL;
Modifications *mod, *nextmod;
regmatch_t pmatch[10];
int rc, nmatch = sizeof(pmatch) / sizeof(regmatch_t);
if ( ov->passReplication && be_shadow_update( op ) ) {
return SLAP_CB_CONTINUE;
}
Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
"dn=%s\n", op->o_req_ndn.bv_val );
rc = variant_find_config(
op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei, nmatch, pmatch );
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
"not a variant\n" );
rc = SLAP_CB_CONTINUE;
goto done;
}
rc = variant_build_entry( op, vei, &op->o_req_ndn, &e, nmatch, pmatch );
/* in case of error, just let the backend deal with the mod and the client
* should get a meaningful error back */
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
"failed to retrieve entry\n" );
rc = SLAP_CB_CONTINUE;
goto done;
}
rc = acl_check_modlist( op, e, op->orm_modlist );
entry_free( e );
if ( !rc ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
send_ldap_error( op, rs, rc, "" );
return rc;
}
for ( mod = op->orm_modlist; mod; mod = nextmod ) {
Operation needle = { .o_req_ndn = BER_BVNULL }, *nop;
nextmod = mod->sml_next;
LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
if ( vai->attr == mod->sml_desc ) {
break;
}
}
if ( vai ) {
if ( vei->type == VARIANT_INFO_REGEX ) {
rc = variant_build_dn(
op, vai, nmatch, pmatch, &needle.o_req_ndn );
if ( rc != LDAP_SUCCESS ) {
continue;
}
} else {
needle.o_req_ndn = vai->dn;
}
nop = ldap_avl_find( ops, &needle, variant_cmp_op );
if ( nop == NULL ) {
nop = ch_calloc( 1, sizeof(Operation) );
*nop = *op;
ber_dupbv( &nop->o_req_ndn, &needle.o_req_ndn );
nop->o_req_dn = nop->o_req_ndn;
nop->orm_modlist = NULL;
rc = ldap_avl_insert( &ops, nop, variant_cmp_op, ldap_avl_dup_error );
assert( rc == 0 );
}
mod->sml_desc = vai->alternative;
op->orm_modlist = nextmod;
mod->sml_next = nop->orm_modlist;
nop->orm_modlist = mod;
if ( vei->type == VARIANT_INFO_REGEX ) {
ch_free( needle.o_req_ndn.bv_val );
}
}
}
if ( !ops ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
"no variant attributes in mod\n" );
return SLAP_CB_CONTINUE;
}
/*
* First run original Operation
* This will take care of making sure the entry exists as well.
*
* FIXME?
* Since we cannot make the subsequent Ops atomic wrt. this one, we just
* let it send the response as well. After all, the changes on the main DN
* have finished by then
*/
rc = on->on_info->oi_orig->bi_op_modify( op, rs );
if ( rc == LDAP_SUCCESS ) {
/* FIXME: if a mod fails, should we attempt to apply the rest? */
ldap_avl_apply( ops, variant_run_mod, &rc, -1, AVL_INORDER );
}
ldap_avl_apply( ops, variant_reassign_mods, op, -1, AVL_INORDER );
ldap_avl_free( ops, variant_free_op );
done:
Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
"finished with %d\n", rc );
return rc;
}
static int
variant_search_response( Operation *op, SlapReply *rs )
{
slap_overinst *on = op->o_callback->sc_private;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei;
int rc;
if ( rs->sr_type == REP_RESULT ) {
ch_free( op->o_callback );
op->o_callback = NULL;
}
if ( rs->sr_type != REP_SEARCH ) {
return SLAP_CB_CONTINUE;
}
rc = variant_find_config(
op, ov, &rs->sr_entry->e_nname, VARIANT_INFO_PLAIN, &vei, 0, NULL );
if ( rc == LDAP_SUCCESS ) {
rs->sr_nentries--;
return rc;
}
return SLAP_CB_CONTINUE;
}
static int
variant_op_search( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei;
slap_callback *cb;
Entry *e = NULL;
regmatch_t pmatch[10];
int variantInScope = 0, rc = SLAP_CB_CONTINUE,
nmatch = sizeof(pmatch) / sizeof(regmatch_t);
if ( ov->passReplication && ( op->o_sync > SLAP_CONTROL_IGNORED ) ) {
return SLAP_CB_CONTINUE;
}
Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
"dn=%s, scope=%d\n",
op->o_req_ndn.bv_val, op->ors_scope );
LDAP_STAILQ_FOREACH( vei, &ov->variants, next ) {
if ( !dnIsSuffixScope( &vei->dn, &op->o_req_ndn, op->ors_scope ) )
continue;
variantInScope = 1;
rc = variant_build_entry( op, vei, &vei->dn, &e, 0, NULL );
if ( rc == LDAP_NO_SUCH_OBJECT || rc == LDAP_REFERRAL ) {
rc = SLAP_CB_CONTINUE;
continue;
} else if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
"failed to retrieve entry: dn=%s\n",
vei->dn.bv_val );
goto done;
}
if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
"entry matched: dn=%s\n",
vei->dn.bv_val );
rs->sr_entry = e;
rs->sr_attrs = op->ors_attrs;
rc = send_search_entry( op, rs );
}
entry_free( e );
e = NULL;
}
/* Three options:
* - the entry has been handled above, in that case vei->type is VARIANT_INFO_PLAIN
* - the entry matches a regex, use the first one and we're finished
* - no configuration matches entry - do nothing
*/
if ( op->ors_scope == LDAP_SCOPE_BASE &&
variant_find_config( op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei,
nmatch, pmatch ) == LDAP_SUCCESS &&
vei->type == VARIANT_INFO_REGEX ) {
rc = variant_build_entry( op, vei, &op->o_req_ndn, &e, nmatch, pmatch );
if ( rc == LDAP_NO_SUCH_OBJECT || rc == LDAP_REFERRAL ) {
rc = SLAP_CB_CONTINUE;
} else if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
"failed to retrieve entry: dn=%s\n",
vei->dn.bv_val );
goto done;
} else {
if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
"entry matched: dn=%s\n",
vei->dn.bv_val );
rs->sr_entry = e;
rs->sr_attrs = op->ors_attrs;
rc = send_search_entry( op, rs );
}
entry_free( e );
e = NULL;
goto done;
}
}
rc = SLAP_CB_CONTINUE;
if ( variantInScope ) {
cb = ch_calloc( 1, sizeof(slap_callback) );
cb->sc_private = on;
cb->sc_response = variant_search_response;
cb->sc_next = op->o_callback;
op->o_callback = cb;
}
done:
if ( rc != SLAP_CB_CONTINUE ) {
rs->sr_err = (rc == LDAP_SUCCESS) ? rc : LDAP_OTHER;
send_ldap_result( op, rs );
}
Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
"finished with %d\n", rc );
return rc;
}
/* Configuration */
static ConfigLDAPadd variant_ldadd;
static ConfigLDAPadd variant_regex_ldadd;
static ConfigLDAPadd variant_attr_ldadd;
static ConfigDriver variant_set_dn;
static ConfigDriver variant_set_regex;
static ConfigDriver variant_set_alt_dn;
static ConfigDriver variant_set_alt_pattern;
static ConfigDriver variant_set_attribute;
static ConfigDriver variant_add_alt_attr;
static ConfigDriver variant_add_alt_attr_regex;
static ConfigCfAdd variant_cfadd;
enum
{
VARIANT_ATTR = 1,
VARIANT_ATTR_ALT,
VARIANT_LAST,
};
static ConfigTable variant_cfg[] = {
{ "passReplication", "on|off", 2, 2, 0,
ARG_ON_OFF|ARG_OFFSET,
(void *)offsetof( variant_info_t, passReplication ),
"( OLcfgOvAt:FIXME.1 NAME 'olcVariantPassReplication' "
"DESC 'Whether to let searches with replication control "
"pass unmodified' "
"SYNTAX OMsBoolean "
"SINGLE-VALUE )",
NULL, NULL
},
{ "variantDN", "dn", 2, 2, 0,
ARG_DN|ARG_QUOTE|ARG_MAGIC,
variant_set_dn,
"( OLcfgOvAt:FIXME.2 NAME 'olcVariantEntry' "
"DESC 'DN of the variant entry' "
"EQUALITY distinguishedNameMatch "
"SYNTAX OMsDN "
"SINGLE-VALUE )",
NULL, NULL
},
{ "variantRegex", "regex", 2, 2, 0,
ARG_BERVAL|ARG_QUOTE|ARG_MAGIC,
variant_set_regex,
"( OLcfgOvAt:FIXME.6 NAME 'olcVariantEntryRegex' "
"DESC 'Pattern for the variant entry' "
"EQUALITY caseExactMatch "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, NULL
},
/* These have no equivalent in slapd.conf */
{ "", NULL, 2, 2, 0,
ARG_STRING|ARG_MAGIC|VARIANT_ATTR,
variant_set_attribute,
"( OLcfgOvAt:FIXME.3 NAME 'olcVariantVariantAttribute' "
"DESC 'Attribute to fill in the entry' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, NULL
},
{ "", NULL, 2, 2, 0,
ARG_STRING|ARG_MAGIC|VARIANT_ATTR_ALT,
variant_set_attribute,
"( OLcfgOvAt:FIXME.4 NAME 'olcVariantAlternativeAttribute' "
"DESC 'Attribute to take from the alternative entry' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, NULL
},
{ "", NULL, 2, 2, 0,
ARG_DN|ARG_QUOTE|ARG_MAGIC,
variant_set_alt_dn,
"( OLcfgOvAt:FIXME.5 NAME 'olcVariantAlternativeEntry' "
"DESC 'DN of the alternative entry' "
"EQUALITY distinguishedNameMatch "
"SYNTAX OMsDN "
"SINGLE-VALUE )",
NULL, NULL
},
{ "", NULL, 2, 2, 0,
ARG_BERVAL|ARG_QUOTE|ARG_MAGIC,
variant_set_alt_pattern,
"( OLcfgOvAt:FIXME.7 NAME 'olcVariantAlternativeEntryPattern' "
"DESC 'Replacement pattern to locate the alternative entry' "
"EQUALITY caseExactMatch "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, NULL
},
/* slapd.conf alternatives for the four above */
{ "variantSpec", "attr attr2 dn", 4, 4, 0,
ARG_QUOTE|ARG_MAGIC,
variant_add_alt_attr,
NULL, NULL, NULL
},
{ "variantRegexSpec", "attr attr2 pattern", 4, 4, 0,
ARG_QUOTE|ARG_MAGIC,
variant_add_alt_attr_regex,
NULL, NULL, NULL
},
{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};
static ConfigOCs variant_ocs[] = {
{ "( OLcfgOvOc:FIXME.1 "
"NAME 'olcVariantConfig' "
"DESC 'Variant overlay configuration' "
"SUP olcOverlayConfig "
"MAY ( olcVariantPassReplication ) )",
Cft_Overlay, variant_cfg, NULL, variant_cfadd },
{ "( OLcfgOvOc:FIXME.2 "
"NAME 'olcVariantVariant' "
"DESC 'Variant configuration' "
"MUST ( olcVariantEntry ) "
"MAY ( name ) "
"SUP top "
"STRUCTURAL )",
Cft_Misc, variant_cfg, variant_ldadd },
{ "( OLcfgOvOc:FIXME.3 "
"NAME 'olcVariantAttribute' "
"DESC 'Variant attribute description' "
"MUST ( olcVariantVariantAttribute $ "
"olcVariantAlternativeAttribute $ "
"olcVariantAlternativeEntry "
") "
"MAY name "
"SUP top "
"STRUCTURAL )",
Cft_Misc, variant_cfg, variant_attr_ldadd },
{ "( OLcfgOvOc:FIXME.4 "
"NAME 'olcVariantRegex' "
"DESC 'Variant configuration' "
"MUST ( olcVariantEntryRegex ) "
"MAY ( name ) "
"SUP top "
"STRUCTURAL )",
Cft_Misc, variant_cfg, variant_regex_ldadd },
{ "( OLcfgOvOc:FIXME.5 "
"NAME 'olcVariantAttributePattern' "
"DESC 'Variant attribute description' "
"MUST ( olcVariantVariantAttribute $ "
"olcVariantAlternativeAttribute $ "
"olcVariantAlternativeEntryPattern "
") "
"MAY name "
"SUP top "
"STRUCTURAL )",
Cft_Misc, variant_cfg, variant_attr_ldadd },
{ NULL, 0, NULL }
};
static int
variant_set_dn( ConfigArgs *ca )
{
variantEntry_info *vei2, *vei = ca->ca_private;
slap_overinst *on = (slap_overinst *)ca->bi;
variant_info_t *ov = on->on_bi.bi_private;
int diff;
if ( ca->op == SLAP_CONFIG_EMIT ) {
value_add_one( &ca->rvalue_vals, &vei->dn );
return LDAP_SUCCESS;
} else if ( ca->op == LDAP_MOD_DELETE ) {
ber_memfree( vei->dn.bv_val );
BER_BVZERO( &vei->dn );
return LDAP_SUCCESS;
}
if ( !vei ) {
vei = ch_calloc( 1, sizeof(variantEntry_info) );
vei->ov = ov;
vei->type = VARIANT_INFO_PLAIN;
LDAP_SLIST_INIT(&vei->attributes);
LDAP_STAILQ_ENTRY_INIT(vei, next);
LDAP_STAILQ_INSERT_TAIL(&ov->variants, vei, next);
ca->ca_private = vei;
}
vei->dn = ca->value_ndn;
ber_memfree( ca->value_dn.bv_val );
/* Each DN should only be listed once */
LDAP_STAILQ_FOREACH( vei2, &vei->ov->variants, next ) {
if ( vei == vei2 ) continue;
dnMatch( &diff, 0, NULL, NULL, &vei->dn, &vei2->dn );
if ( !diff ) {
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"duplicate variant dn: %s", ca->value_ndn.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return LDAP_CONSTRAINT_VIOLATION;
}
}
return LDAP_SUCCESS;
}
static int
variant_set_regex( ConfigArgs *ca )
{
variantEntry_info *vei2, *vei = ca->ca_private;
slap_overinst *on = (slap_overinst *)ca->bi;
variant_info_t *ov = on->on_bi.bi_private;
if ( ca->op == SLAP_CONFIG_EMIT ) {
ca->value_bv = vei->dn;
return LDAP_SUCCESS;
} else if ( ca->op == LDAP_MOD_DELETE ) {
ber_memfree( vei->dn.bv_val );
BER_BVZERO( &vei->dn );
if ( vei->regex ) {
regfree( vei->regex );
ch_free( vei->regex );
vei->regex = NULL;
}
return LDAP_SUCCESS;
}
if ( !vei ) {
vei = ch_calloc( 1, sizeof(variantEntry_info) );
vei->ov = ov;
vei->type = VARIANT_INFO_REGEX;
LDAP_SLIST_INIT(&vei->attributes);
LDAP_STAILQ_ENTRY_INIT(vei, next);
LDAP_STAILQ_INSERT_TAIL(&ov->regex_variants, vei, next);
ca->ca_private = vei;
}
vei->dn = ca->value_bv;
/* Each regex should only be listed once */
LDAP_STAILQ_FOREACH( vei2, &vei->ov->regex_variants, next ) {
if ( vei == vei2 ) continue;
if ( !ber_bvcmp( &ca->value_bv, &vei2->dn ) ) {
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"duplicate variant regex: %s", ca->value_dn.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return LDAP_CONSTRAINT_VIOLATION;
}
}
vei->regex = ch_calloc( 1, sizeof(regex_t) );
if ( regcomp( vei->regex, vei->dn.bv_val, REG_EXTENDED ) ) {
ch_free( vei->regex );
vei->regex = NULL;
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"cannot process regex: %s", vei->dn.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return LDAP_CONSTRAINT_VIOLATION;
}
return LDAP_SUCCESS;
}
static int
variant_set_alt_dn( ConfigArgs *ca )
{
variantAttr_info *vai = ca->ca_private;
if ( ca->op == SLAP_CONFIG_EMIT ) {
value_add_one( &ca->rvalue_vals, &vai->dn );
return LDAP_SUCCESS;
} else if ( ca->op == LDAP_MOD_DELETE ) {
ber_memfree( vai->dn.bv_val );
BER_BVZERO( &vai->dn );
return LDAP_SUCCESS;
}
vai->dn = ca->value_ndn;
ber_memfree( ca->value_dn.bv_val );
return LDAP_SUCCESS;
}
static int
variant_set_alt_pattern( ConfigArgs *ca )
{
variantAttr_info *vai = ca->ca_private;
char *p = ca->value_bv.bv_val,
*end = ca->value_bv.bv_val + ca->value_bv.bv_len;
if ( ca->op == SLAP_CONFIG_EMIT ) {
ca->value_bv = vai->dn;
return LDAP_SUCCESS;
} else if ( ca->op == LDAP_MOD_DELETE ) {
ber_memfree( vai->dn.bv_val );
BER_BVZERO( &vai->dn );
return LDAP_SUCCESS;
}
while ( (p = memchr( p, '$', end - p )) != NULL ) {
p += 1;
if ( ( ( *p >= '0' ) && ( *p <= '9' ) ) || ( *p == '$' ) ) {
p += 1;
} else {
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"invalid replacement pattern supplied '%s'",
ca->value_bv.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return LDAP_CONSTRAINT_VIOLATION;
}
}
vai->dn = ca->value_bv;
return LDAP_SUCCESS;
}
static int
variant_set_attribute( ConfigArgs *ca )
{
variantAttr_info *vai2, *vai = ca->ca_private;
char *s = ca->value_string;
const char *text;
AttributeDescription **ad;
int rc;
if ( ca->type == VARIANT_ATTR ) {
ad = &vai->attr;
} else {
ad = &vai->alternative;
}
if ( ca->op == SLAP_CONFIG_EMIT ) {
ca->value_string = ch_strdup( (*ad)->ad_cname.bv_val );
return LDAP_SUCCESS;
} else if ( ca->op == LDAP_MOD_DELETE ) {
*ad = NULL;
return LDAP_SUCCESS;
}
if ( *s == '{' ) {
s = strchr( s, '}' );
if ( !s ) {
return LDAP_UNDEFINED_TYPE;
}
s += 1;
}
rc = slap_str2ad( s, ad, &text );
ber_memfree( ca->value_string );
if ( rc ) {
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"attribute %s invalid: %s", s, text );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return rc;
}
/* Both attributes have to share the same syntax */
if ( vai->attr && vai->alternative &&
vai->attr->ad_type->sat_syntax !=
vai->alternative->ad_type->sat_syntax ) {
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"attribute '%s' syntax doesn't match alternative attribute '%s'",
vai->attr->ad_cname.bv_val, vai->alternative->ad_cname.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return LDAP_CONSTRAINT_VIOLATION;
}
if ( ca->type == VARIANT_ATTR ) {
/* Each attribute should only be listed once */
LDAP_SLIST_FOREACH( vai2, &vai->variant->attributes, next ) {
if ( vai == vai2 ) continue;
if ( vai->attr == vai2->attr ) {
snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"duplicate attribute '%s'", vai->attr->ad_cname.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
return LDAP_CONSTRAINT_VIOLATION;
}
}
}
return LDAP_SUCCESS;
}
static int
variant_add_alt_attr( ConfigArgs *ca )
{
slap_overinst *on = (slap_overinst *)ca->bi;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei =
LDAP_STAILQ_LAST( &ov->variants, variantEntry_info, next );
variantAttr_info *vai;
struct berval dn, ndn;
int rc;
vai = ch_calloc( 1, sizeof(variantAttr_info) );
vai->variant = vei;
LDAP_SLIST_ENTRY_INIT( vai, next );
ca->ca_private = vai;
ca->value_string = ch_strdup( ca->argv[1] );
ca->type = VARIANT_ATTR;
rc = variant_set_attribute( ca );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
ca->value_string = ch_strdup( ca->argv[2] );
ca->type = VARIANT_ATTR_ALT;
rc = variant_set_attribute( ca );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
dn.bv_val = ca->argv[3];
dn.bv_len = strlen( dn.bv_val );
rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
ca->type = 0;
BER_BVZERO( &ca->value_dn );
ca->value_ndn = ndn;
rc = variant_set_alt_dn( ca );
if ( rc != LDAP_SUCCESS ) {
ch_free( ndn.bv_val );
goto done;
}
done:
if ( rc == LDAP_SUCCESS ) {
LDAP_SLIST_INSERT_HEAD( &vei->attributes, vai, next );
}
return rc;
}
static int
variant_add_alt_attr_regex( ConfigArgs *ca )
{
slap_overinst *on = (slap_overinst *)ca->bi;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei =
LDAP_STAILQ_LAST( &ov->regex_variants, variantEntry_info, next );
variantAttr_info *vai;
int rc;
vai = ch_calloc( 1, sizeof(variantAttr_info) );
vai->variant = vei;
LDAP_SLIST_ENTRY_INIT( vai, next );
ca->ca_private = vai;
ca->value_string = ch_strdup( ca->argv[1] );
ca->type = VARIANT_ATTR;
rc = variant_set_attribute( ca );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
ca->value_string = ch_strdup( ca->argv[2] );
ca->type = VARIANT_ATTR_ALT;
rc = variant_set_attribute( ca );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
ca->type = 0;
ber_str2bv( ca->argv[3], 0, 1, &ca->value_bv );
rc = variant_set_alt_pattern( ca );
if ( rc != LDAP_SUCCESS ) {
goto done;
}
done:
if ( rc == LDAP_SUCCESS ) {
LDAP_SLIST_INSERT_HEAD( &vei->attributes, vai, next );
}
return rc;
}
static int
variant_ldadd_cleanup( ConfigArgs *ca )
{
variantEntry_info *vei = ca->ca_private;
slap_overinst *on = (slap_overinst *)ca->bi;
variant_info_t *ov = on->on_bi.bi_private;
if ( ca->reply.err != LDAP_SUCCESS ) {
assert( LDAP_SLIST_EMPTY(&vei->attributes) );
ch_free( vei->dn.bv_val );
ch_free( vei );
return LDAP_SUCCESS;
}
if ( vei->type == VARIANT_INFO_PLAIN ) {
LDAP_STAILQ_INSERT_TAIL(&ov->variants, vei, next);
} else {
LDAP_STAILQ_INSERT_TAIL(&ov->regex_variants, vei, next);
}
return LDAP_SUCCESS;
}
static int
variant_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
{
slap_overinst *on;
variant_info_t *ov;
variantEntry_info *vei;
if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
cei->ce_bi->bi_cf_ocs != variant_ocs )
return LDAP_CONSTRAINT_VIOLATION;
on = (slap_overinst *)cei->ce_bi;
ov = on->on_bi.bi_private;
vei = ch_calloc( 1, sizeof(variantEntry_info) );
vei->ov = ov;
vei->type = VARIANT_INFO_PLAIN;
LDAP_SLIST_INIT(&vei->attributes);
LDAP_STAILQ_ENTRY_INIT(vei, next);
ca->bi = cei->ce_bi;
ca->ca_private = vei;
config_push_cleanup( ca, variant_ldadd_cleanup );
/* config_push_cleanup is only run in the case of online config but we use it to
* save the new config when done with the entry */
ca->lineno = 0;
return LDAP_SUCCESS;
}
static int
variant_regex_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
{
slap_overinst *on;
variant_info_t *ov;
variantEntry_info *vei;
if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
cei->ce_bi->bi_cf_ocs != variant_ocs )
return LDAP_CONSTRAINT_VIOLATION;
on = (slap_overinst *)cei->ce_bi;
ov = on->on_bi.bi_private;
vei = ch_calloc( 1, sizeof(variantEntry_info) );
vei->ov = ov;
vei->type = VARIANT_INFO_REGEX;
LDAP_SLIST_INIT(&vei->attributes);
LDAP_STAILQ_ENTRY_INIT(vei, next);
ca->bi = cei->ce_bi;
ca->ca_private = vei;
config_push_cleanup( ca, variant_ldadd_cleanup );
/* config_push_cleanup is only run in the case of online config but we use it to
* save the new config when done with the entry */
ca->lineno = 0;
return LDAP_SUCCESS;
}
static int
variant_attr_ldadd_cleanup( ConfigArgs *ca )
{
variantAttr_info *vai = ca->ca_private;
variantEntry_info *vei = vai->variant;
if ( ca->reply.err != LDAP_SUCCESS ) {
ch_free( vai->dn.bv_val );
ch_free( vai );
return LDAP_SUCCESS;
}
LDAP_SLIST_INSERT_HEAD(&vei->attributes, vai, next);
return LDAP_SUCCESS;
}
static int
variant_attr_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
{
variantEntry_info *vei;
variantAttr_info *vai;
CfEntryInfo *parent = cei->ce_parent;
if ( cei->ce_type != Cft_Misc || !parent || !parent->ce_bi ||
parent->ce_bi->bi_cf_ocs != variant_ocs )
return LDAP_CONSTRAINT_VIOLATION;
vei = (variantEntry_info *)cei->ce_private;
vai = ch_calloc( 1, sizeof(variantAttr_info) );
vai->variant = vei;
LDAP_SLIST_ENTRY_INIT(vai, next);
ca->ca_private = vai;
config_push_cleanup( ca, variant_attr_ldadd_cleanup );
/* config_push_cleanup is only run in the case of online config but we use it to
* save the new config when done with the entry */
ca->lineno = 0;
return LDAP_SUCCESS;
}
static int
variant_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
{
slap_overinst *on = (slap_overinst *)ca->bi;
variant_info_t *ov = on->on_bi.bi_private;
variantEntry_info *vei;
variantAttr_info *vai;
Entry *e;
struct berval rdn;
int i = 0;
LDAP_STAILQ_FOREACH( vei, &ov->variants, next ) {
int j = 0;
rdn.bv_len = snprintf(
ca->cr_msg, sizeof(ca->cr_msg), "name={%d}variant", i++ );
rdn.bv_val = ca->cr_msg;
ca->ca_private = vei;
e = config_build_entry(
op, rs, p->e_private, ca, &rdn, &variant_ocs[1], NULL );
assert( e );
LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
rdn.bv_len = snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"olcVariantVariantAttribute={%d}%s", j++,
vai->attr->ad_cname.bv_val );
rdn.bv_val = ca->cr_msg;
ca->ca_private = vai;
config_build_entry(
op, rs, e->e_private, ca, &rdn, &variant_ocs[2], NULL );
}
}
LDAP_STAILQ_FOREACH( vei, &ov->regex_variants, next ) {
int j = 0;
rdn.bv_len = snprintf(
ca->cr_msg, sizeof(ca->cr_msg), "name={%d}regex", i++ );
rdn.bv_val = ca->cr_msg;
ca->ca_private = vei;
e = config_build_entry(
op, rs, p->e_private, ca, &rdn, &variant_ocs[3], NULL );
assert( e );
LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
rdn.bv_len = snprintf( ca->cr_msg, sizeof(ca->cr_msg),
"olcVariantVariantAttribute={%d}%s", j++,
vai->attr->ad_cname.bv_val );
rdn.bv_val = ca->cr_msg;
ca->ca_private = vai;
config_build_entry(
op, rs, e->e_private, ca, &rdn, &variant_ocs[4], NULL );
}
}
return LDAP_SUCCESS;
}
static slap_overinst variant;
static int
variant_db_init( BackendDB *be, ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
variant_info_t *ov;
if ( SLAP_ISGLOBALOVERLAY(be) ) {
Debug( LDAP_DEBUG_ANY, "variant overlay must be instantiated within "
"a database.\n" );
return 1;
}
ov = ch_calloc( 1, sizeof(variant_info_t) );
LDAP_STAILQ_INIT(&ov->variants);
LDAP_STAILQ_INIT(&ov->regex_variants);
on->on_bi.bi_private = ov;
return LDAP_SUCCESS;
}
static int
variant_db_destroy( BackendDB *be, ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
variant_info_t *ov = on->on_bi.bi_private;
if ( ov ) {
while ( !LDAP_STAILQ_EMPTY( &ov->variants ) ) {
variantEntry_info *vei = LDAP_STAILQ_FIRST( &ov->variants );
LDAP_STAILQ_REMOVE_HEAD( &ov->variants, next );
while ( !LDAP_SLIST_EMPTY( &vei->attributes ) ) {
variantAttr_info *vai = LDAP_SLIST_FIRST( &vei->attributes );
LDAP_SLIST_REMOVE_HEAD( &vei->attributes, next );
ber_memfree( vai->dn.bv_val );
ch_free( vai );
}
ber_memfree( vei->dn.bv_val );
ch_free( vei );
}
while ( !LDAP_STAILQ_EMPTY( &ov->regex_variants ) ) {
variantEntry_info *vei = LDAP_STAILQ_FIRST( &ov->regex_variants );
LDAP_STAILQ_REMOVE_HEAD( &ov->regex_variants, next );
while ( !LDAP_SLIST_EMPTY( &vei->attributes ) ) {
variantAttr_info *vai = LDAP_SLIST_FIRST( &vei->attributes );
LDAP_SLIST_REMOVE_HEAD( &vei->attributes, next );
ber_memfree( vai->dn.bv_val );
ch_free( vai );
}
ber_memfree( vei->dn.bv_val );
regfree( vei->regex );
ch_free( vei->regex );
ch_free( vei );
}
ch_free( ov );
}
return LDAP_SUCCESS;
}
int
variant_initialize()
{
int rc;
variant.on_bi.bi_type = "variant";
variant.on_bi.bi_db_init = variant_db_init;
variant.on_bi.bi_db_destroy = variant_db_destroy;
variant.on_bi.bi_op_add = variant_op_add;
variant.on_bi.bi_op_compare = variant_op_compare;
variant.on_bi.bi_op_modify = variant_op_mod;
variant.on_bi.bi_op_search = variant_op_search;
variant.on_bi.bi_cf_ocs = variant_ocs;
rc = config_register_schema( variant_cfg, variant_ocs );
if ( rc ) return rc;
return overlay_register( &variant );
}
#if SLAPD_OVER_VARIANT == SLAPD_MOD_DYNAMIC
int
init_module( int argc, char *argv[] )
{
return variant_initialize();
}
#endif
#endif /* SLAPD_OVER_VARIANT */