/* $NetBSD: datamorph.c,v 1.2 2021/08/14 16:14:51 christos Exp $ */ /* datamorph.c - enumerated and native integer value support */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2016-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 developed in 2016 by Ondřej Kuzník for Symas Corp. */ #include __RCSID("$NetBSD: datamorph.c,v 1.2 2021/08/14 16:14:51 christos Exp $"); #include "portable.h" #ifdef SLAPD_OVER_DATAMORPH #include #include #if defined(__linux__) #include #elif defined(sun) #define be16toh(x) BE_16(x) #define le16toh(x) LE_16(x) #define htobe16(x) BE_16(x) #define htole16(x) LE_16(x) #define be32toh(x) BE_32(x) #define le32toh(x) LE_32(x) #define htobe32(x) BE_32(x) #define htole32(x) LE_32(x) #define be64toh(x) BE_64(x) #define le64toh(x) LE_64(x) #define htobe64(x) BE_64(x) #define htole64(x) LE_64(x) #elif defined(__NetBSD__) || defined(__FreeBSD__) #include #elif defined(__OpenBSD__) #include #define be16toh(x) betoh16(x) #define le16toh(x) letoh16(x) #define be32toh(x) betoh32(x) #define le32toh(x) letoh32(x) #define be64toh(x) betoh64(x) #define le64toh(x) letoh64(x) #elif defined(__BYTE_ORDER__) && \ ( defined(__GNUC__) || defined(__clang__) ) #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define be16toh(x) __builtin_bswap16(x) #define le16toh(x) (x) #define htobe16(x) __builtin_bswap16(x) #define htole16(x) (x) #define be32toh(x) __builtin_bswap32(x) #define le32toh(x) (x) #define htobe32(x) __builtin_bswap32(x) #define htole32(x) (x) #define be64toh(x) __builtin_bswap64(x) #define le64toh(x) (x) #define htobe64(x) __builtin_bswap64(x) #define htole64(x) (x) #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define be16toh(x) (x) #define le16toh(x) __builtin_bswap16(x) #define htobe16(x) (x) #define htole16(x) __builtin_bswap16(x) #define be32toh(x) (x) #define le32toh(x) __builtin_bswap32(x) #define htobe32(x) (x) #define htole32(x) __builtin_bswap32(x) #define be64toh(x) (x) #define le64toh(x) __builtin_bswap64(x) #define htobe64(x) (x) #define htole64(x) __builtin_bswap64(x) #else #error "Only support pure big and little endian at the moment" #endif #else #error "I lack the way to check my endianness and convert to/from big-endian" #endif #include "slap.h" #include "slap-config.h" #include "lutil.h" #include "ldap_queue.h" typedef enum datamorph_type_t { DATAMORPH_UNSET, DATAMORPH_ENUM, DATAMORPH_INT, } datamorph_type; typedef enum datamorph_flags_t { DATAMORPH_FLAG_SIGNED = 1 << 0, DATAMORPH_FLAG_LOWER = 1 << 1, DATAMORPH_FLAG_UPPER = 1 << 2, } datamorph_flags; typedef union datamorph_interval_bound_t { int64_t i; uint64_t u; } datamorph_interval_bound; typedef struct transformation_info_t { AttributeDescription *attr; datamorph_type type; union { struct { Avlnode *to_db; struct berval from_db[256]; } maps; #define ti_enum info.maps struct { datamorph_flags flags; unsigned int size; datamorph_interval_bound lower, upper; } interval; #define ti_int info.interval } info; } transformation_info; typedef struct datamorph_enum_mapping_t { struct berval wire_value; uint8_t db_value; transformation_info *transformation; } datamorph_enum_mapping; typedef struct datamorph_info_t { Avlnode *transformations; transformation_info *wip_transformation; } datamorph_info; static int transformation_mapping_cmp( const void *l, const void *r ) { const datamorph_enum_mapping *left = l, *right = r; return ber_bvcmp( &left->wire_value, &right->wire_value ); } static int transformation_info_cmp( const void *l, const void *r ) { const transformation_info *left = l, *right = r; return ( left->attr == right->attr ) ? 0 : ( left->attr < right->attr ) ? -1 : 1; } static int transform_to_db_format_one( Operation *op, transformation_info *definition, struct berval *value, struct berval *outval ) { switch ( definition->type ) { case DATAMORPH_ENUM: { datamorph_enum_mapping *mapping, needle = { .wire_value = *value }; struct berval db_value = { .bv_len = 1 }; mapping = ldap_avl_find( definition->ti_enum.to_db, &needle, transformation_mapping_cmp ); if ( !mapping ) { Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: " "value '%s' not mapped\n", value->bv_val ); return LDAP_CONSTRAINT_VIOLATION; } db_value.bv_val = (char *)&mapping->db_value; ber_dupbv( outval, &db_value ); assert( outval->bv_val ); break; } case DATAMORPH_INT: { union { char s[8]; uint8_t be8; uint16_t be16; uint32_t be32; uint64_t be64; } buf; struct berval db_value = { .bv_val = buf.s }; char *ptr = value->bv_val + value->bv_len; uint64_t unsigned_value; int64_t signed_value; assert( definition->ti_int.size == 1 || definition->ti_int.size == 2 || definition->ti_int.size == 4 || definition->ti_int.size == 8 ); /* Read number */ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { signed_value = strtoll( value->bv_val, &ptr, 10 ); } else { unsigned_value = strtoull( value->bv_val, &ptr, 10 ); } if ( *value->bv_val == '\0' || *ptr != '\0' ) { Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: " "value '%s' not an integer\n", value->bv_val ); return LDAP_CONSTRAINT_VIOLATION; } /* Check it's within configured bounds */ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { if ( signed_value < definition->ti_int.lower.i || signed_value > definition->ti_int.upper.i ) { Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: " "value '%s' doesn't fit configured constraints\n", value->bv_val ); return LDAP_CONSTRAINT_VIOLATION; } } else { if ( unsigned_value < definition->ti_int.lower.u || unsigned_value > definition->ti_int.upper.u ) { Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: " "value '%s' doesn't fit configured constraints\n", value->bv_val ); return LDAP_CONSTRAINT_VIOLATION; } } db_value.bv_len = definition->ti_int.size; switch ( definition->ti_int.size ) { case 1: { if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { buf.be8 = (unsigned char)((char)signed_value); } else { buf.be8 = unsigned_value; } break; } case 2: { uint16_t h16; if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { h16 = signed_value; } else { h16 = unsigned_value; } buf.be16 = htobe16( h16 ); break; } case 4: { uint32_t h32; if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { h32 = signed_value; } else { h32 = unsigned_value; } buf.be32 = htobe32( h32 ); break; } case 8: { uint64_t h64; if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { h64 = signed_value; } else { h64 = unsigned_value; } buf.be64 = htobe64( h64 ); break; } } ber_dupbv( outval, &db_value ); assert( outval->bv_val ); break; } default: assert(0); } return LDAP_SUCCESS; } static int transform_to_db_format( Operation *op, transformation_info *definition, BerVarray values, int numvals, BerVarray *out ) { struct berval *value; int i, rc = LDAP_SUCCESS; if ( numvals == 0 ) { for ( value = values; value; value++, numvals++ ) ; /* Count them */ } assert( out ); *out = ch_calloc( numvals + 1, sizeof(struct berval) ); for ( i = 0; i < numvals; i++ ) { rc = transform_to_db_format_one( op, definition, &values[i], &(*out)[i] ); if ( rc ) { break; } } if ( rc ) { for ( ; i >= 0; i-- ) { ch_free((*out)[i].bv_val); } ch_free(*out); } return rc; } static int transform_from_db_format_one( Operation *op, transformation_info *definition, struct berval *value, struct berval *outval ) { switch ( definition->type ) { case DATAMORPH_ENUM: { uint8_t index = value->bv_val[0]; struct berval *val = &definition->info.maps.from_db[index]; if ( !BER_BVISNULL( val ) ) { ber_dupbv( outval, val ); assert( outval->bv_val ); } else { Debug( LDAP_DEBUG_ANY, "transform_from_db_format_one: " "DB value %d has no mapping!\n", index ); /* FIXME: probably still need to return an error */ BER_BVZERO( outval ); } break; } case DATAMORPH_INT: { char buf[24]; struct berval wire_value = { .bv_val = buf }; union lens_t { uint8_t be8; uint16_t be16; uint32_t be32; uint64_t be64; } *lens = (union lens_t *)value->bv_val; uint64_t unsigned_value; int64_t signed_value; if ( value->bv_len != definition->ti_int.size ) { Debug( LDAP_DEBUG_ANY, "transform_from_db_format_one(%s): " "unexpected DB value of length %lu when configured " "for %u!\n", definition->attr->ad_cname.bv_val, value->bv_len, definition->ti_int.size ); /* FIXME: probably still need to return an error */ BER_BVZERO( outval ); break; } assert( definition->ti_int.size == 1 || definition->ti_int.size == 2 || definition->ti_int.size == 4 || definition->ti_int.size == 8 ); switch ( definition->ti_int.size ) { case 1: { if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { signed_value = (int8_t)lens->be8; } else { unsigned_value = (uint8_t)lens->be8; } break; } case 2: { uint16_t h16 = be16toh( lens->be16 ); if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { signed_value = (int16_t)h16; } else { unsigned_value = (uint16_t)h16; } break; } case 4: { uint32_t h32 = be32toh( lens->be32 ); if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { signed_value = (int32_t)h32; } else { unsigned_value = (uint32_t)h32; } break; } case 8: { uint64_t h64 = be64toh( lens->be64 ); if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { signed_value = (int64_t)h64; } else { unsigned_value = (uint64_t)h64; } break; } } if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { wire_value.bv_len = sprintf( buf, "%" PRId64, signed_value ); } else { wire_value.bv_len = sprintf( buf, "%" PRIu64, unsigned_value ); } ber_dupbv( outval, &wire_value ); assert( outval->bv_val ); break; } default: assert(0); } return LDAP_SUCCESS; } static int transform_from_db_format( Operation *op, transformation_info *definition, BerVarray values, int numvals, BerVarray *out ) { struct berval *value; int i, rc = LDAP_SUCCESS; if ( numvals == 0 ) { for ( value = values; value; value++, numvals++ ) ; /* Count them */ } assert( out ); *out = ch_calloc( numvals + 1, sizeof(struct berval) ); for ( i = 0; i < numvals; i++ ) { struct berval bv; rc = transform_from_db_format_one( op, definition, &values[i], &bv ); if ( !BER_BVISNULL( &bv ) ) { ber_bvarray_add( out, &bv ); } if ( rc ) { break; } } if ( rc ) { for ( ; i >= 0; i-- ) { ch_free( (*out)[i].bv_val ); } ch_free( *out ); } return rc; } static int datamorph_filter( Operation *op, datamorph_info *ov, Filter *f ) { switch ( f->f_choice ) { case LDAP_FILTER_PRESENT: /* The matching rules are not in place, * so the filter will be ignored */ case LDAP_FILTER_APPROX: case LDAP_FILTER_SUBSTRINGS: default: break; return LDAP_SUCCESS; case LDAP_FILTER_AND: case LDAP_FILTER_OR: { for ( f = f->f_and; f; f = f->f_next ) { int rc = datamorph_filter( op, ov, f ); if ( rc != LDAP_SUCCESS ) { return rc; } } } break; case LDAP_FILTER_NOT: return datamorph_filter( op, ov, f->f_not ); case LDAP_FILTER_EQUALITY: case LDAP_FILTER_GE: case LDAP_FILTER_LE: { transformation_info *t, needle = { .attr = f->f_ava->aa_desc }; t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ); if ( t ) { struct berval new_val; int rc = transform_to_db_format_one( op, t, &f->f_ava->aa_value, &new_val ); ch_free( f->f_ava->aa_value.bv_val ); if ( rc != LDAP_SUCCESS ) { f->f_choice = SLAPD_FILTER_COMPUTED; f->f_result = SLAPD_COMPARE_UNDEFINED; } else { f->f_ava->aa_value = new_val; } } } break; } return LDAP_SUCCESS; } static int datamorph_op_add( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; Entry *e = op->ora_e; Attribute *a, *next; AttributeDescription *stop = NULL; int rc = LDAP_SUCCESS; if ( !BER_BVISNULL( &e->e_nname ) && !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, "datamorph_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++ ) { transformation_info needle = {}; /* If we can't resolve the attribute, ignore it */ if ( slap_bv2ad( &rDN[i]->la_attr, &needle.attr, &p ) ) { continue; } if ( ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ) ) { rc = LDAP_CONSTRAINT_VIOLATION; Debug( LDAP_DEBUG_TRACE, "datamorph_op_add: " "attempted to add transformed attribute in RDN\n" ); break; } } ldap_rdnfree_x( rDN, op->o_tmpmemctx ); if ( rc != LDAP_SUCCESS ) { send_ldap_error( op, rs, rc, "datamorph: trying to add transformed attribute in RDN" ); return rc; } } for ( a = e->e_attrs; a && a->a_desc != stop; a = next ) { transformation_info *t, needle = { .attr = a->a_desc }; BerVarray new_vals; next = a->a_next; t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ); if ( !t ) continue; rc = transform_to_db_format( op, t, a->a_vals, a->a_numvals, &new_vals ); if ( rc != LDAP_SUCCESS ) { goto fail; } (void)attr_delete( &e->e_attrs, needle.attr ); rc = attr_merge( e, needle.attr, new_vals, NULL ); ber_bvarray_free( new_vals ); if ( rc != LDAP_SUCCESS ) { goto fail; } if ( !stop ) { stop = needle.attr; } } return SLAP_CB_CONTINUE; fail: send_ldap_error( op, rs, rc, "datamorph: trying to add values outside definitions" ); return rc; } static int datamorph_op_compare( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; transformation_info *t, needle = { .attr = op->orc_ava->aa_desc }; int rc = SLAP_CB_CONTINUE; t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ); if ( t ) { struct berval new_val; rc = transform_to_db_format_one( op, t, &op->orc_ava->aa_value, &new_val ); if ( rc != LDAP_SUCCESS ) { Debug( LDAP_DEBUG_TRACE, "datamorph_op_compare: " "transformation failed for '%s', rc=%d\n", op->orc_ava->aa_value.bv_val, rc ); rs->sr_err = rc = LDAP_COMPARE_FALSE; send_ldap_result( op, rs ); return rc; } ch_free( op->orc_ava->aa_value.bv_val ); op->orc_ava->aa_value = new_val; } return SLAP_CB_CONTINUE; } static int datamorph_op_mod( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; Modifications *mod; int rc = SLAP_CB_CONTINUE; for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) { transformation_info *t, needle = { .attr = mod->sml_desc }; BerVarray new_vals = NULL; if ( mod->sml_numvals == 0 ) continue; /* Nothing to transform */ t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ); if ( !t ) continue; assert( !mod->sml_nvalues ); rc = transform_to_db_format( op, t, mod->sml_values, mod->sml_numvals, &new_vals ); if ( rc != LDAP_SUCCESS ) { goto fail; } ber_bvarray_free( mod->sml_values ); mod->sml_values = new_vals; } return SLAP_CB_CONTINUE; fail: Debug( LDAP_DEBUG_TRACE, "datamorph_op_mod: " "dn=%s failed rc=%d\n", op->o_req_ndn.bv_val, rc ); send_ldap_error( op, rs, rc, "datamorph: trying to operate on values outside definitions" ); return rc; } static int datamorph_op_modrdn( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; LDAPRDN rDN; const char *p; int i, rc; 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, "datamorph_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++ ) { transformation_info needle = {}; /* If we can't resolve the attribute, ignore it */ if ( slap_bv2ad( &rDN[i]->la_attr, &needle.attr, &p ) ) { continue; } if ( ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ) ) { rc = LDAP_CONSTRAINT_VIOLATION; Debug( LDAP_DEBUG_TRACE, "datamorph_op_modrdn: " "attempted to add transformed values in RDN\n" ); break; } } ldap_rdnfree_x( rDN, op->o_tmpmemctx ); if ( rc != LDAP_SUCCESS ) { send_ldap_error( op, rs, rc, "datamorph: trying to put transformed values in RDN" ); return rc; } return SLAP_CB_CONTINUE; } static int datamorph_response( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; Entry *e = NULL, *e_orig = rs->sr_entry; AttributeDescription *stop = NULL; Attribute *a, *next = NULL; int rc = SLAP_CB_CONTINUE; if ( rs->sr_type != REP_SEARCH ) { return rc; } for ( a = e_orig->e_attrs; a && a->a_desc != stop; a = next ) { transformation_info *t, needle = { .attr = a->a_desc }; BerVarray new_vals; next = a->a_next; t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ); if ( !t ) continue; rc = transform_from_db_format( op, t, a->a_vals, a->a_numvals, &new_vals ); if ( rc != LDAP_SUCCESS ) { break; } if ( !e ) { if ( rs->sr_flags & REP_ENTRY_MODIFIABLE ) { e = e_orig; } else { e = entry_dup( e_orig ); } } (void)attr_delete( &e->e_attrs, needle.attr ); rc = attr_merge( e, needle.attr, new_vals, NULL ); ber_bvarray_free( new_vals ); if ( rc != LDAP_SUCCESS ) { break; } if ( !stop ) { stop = needle.attr; } } if ( rc == LDAP_SUCCESS ) { rc = SLAP_CB_CONTINUE; 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; } } else if ( e && e != e_orig ) { entry_free( e ); } return rc; } static int datamorph_op_search( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; int rc = SLAP_CB_CONTINUE; /* * 1. check all requested attributes -> register callback if one matches * 2. check filter: parse filter, traverse, for configured attributes: * - presence -> do not touch * - ava -> replace assertion value with db value if possible, assertion with undefined otherwise * - inequality -> ??? * - anything else -> undefined * - might just check for equality and leave the rest to syntax? * 3. unparse filter */ if ( datamorph_filter( op, ov, op->ors_filter ) ) { send_ldap_error( op, rs, LDAP_OTHER, "datamorph: failed to process filter" ); return LDAP_OTHER; } return rc; } static int datamorph_entry_release_rw( Operation *op, Entry *e, int rw ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; int rc = LDAP_SUCCESS; if ( on->on_next ) { rc = overlay_entry_release_ov( op, e, rw, on->on_next ); } else if ( on->on_info->oi_orig->bi_entry_release_rw ) { /* FIXME: there should be a better way */ rc = on->on_info->oi_orig->bi_entry_release_rw( op, e, rw ); } else { entry_free( e ); } return rc; } static int datamorph_entry_get_rw( Operation *op, struct berval *ndn, ObjectClass *oc, AttributeDescription *at, int rw, Entry **ep ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; datamorph_info *ov = on->on_bi.bi_private; Entry *e_orig, *e = NULL; int rc; if ( on->on_next ) { rc = overlay_entry_get_ov( op, ndn, oc, at, rw, ep, on->on_next ); } else { /* FIXME: there should be a better way */ rc = on->on_info->oi_orig->bi_entry_get_rw( op, ndn, oc, at, rw, ep ); } e_orig = *ep; if ( rc == LDAP_SUCCESS && e_orig ) { AttributeDescription *stop = NULL; Attribute *a; for ( a = e_orig->e_attrs; a; a = a->a_next ) { transformation_info *t, needle = { .attr = a->a_desc }; BerVarray new_vals; t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ); if ( !t ) continue; rc = transform_from_db_format( op, t, a->a_vals, a->a_numvals, &new_vals ); if ( rc != LDAP_SUCCESS ) { goto fail; } if ( !e ) { e = entry_dup( e_orig ); } (void)attr_delete( &e->e_attrs, needle.attr ); rc = attr_merge( e, needle.attr, new_vals, NULL ); ber_bvarray_free( new_vals ); if ( rc != LDAP_SUCCESS ) { goto fail; } if ( !stop ) { stop = needle.attr; } } } if ( e ) { datamorph_entry_release_rw( op, e_orig, rw ); *ep = e; } return rc; fail: if ( e ) { entry_free( e ); } (void)datamorph_entry_release_rw( op, *ep, rw ); return rc; } /* Schema */ static int datamorphBlobValidate( Syntax *syntax, struct berval *in ) { /* any value allowed */ return LDAP_SUCCESS; } int datamorphBinarySignedOrderingMatch( int *matchp, slap_mask_t flags, Syntax *syntax, MatchingRule *mr, struct berval *value, void *assertedValue ) { struct berval *asserted = assertedValue; ber_len_t v_len = value->bv_len; ber_len_t av_len = asserted->bv_len; /* Ordering: * 1. Negative always before non-negative * 2. Shorter before longer * 3. Rest ordered by memory contents (they are big-endian numbers) */ int match = ( *value->bv_val >= 0 ) - ( *asserted->bv_val >= 0 ); if ( match == 0 ) match = (int)v_len - (int)av_len; if ( match == 0 ) match = memcmp( value->bv_val, asserted->bv_val, v_len ); /* If used in extensible match filter, match if value < asserted */ if ( flags & SLAP_MR_EXT ) match = ( match >= 0 ); *matchp = match; return LDAP_SUCCESS; } /* Index generation function: Ordered index */ int datamorphUnsignedIndexer( slap_mask_t use, slap_mask_t flags, Syntax *syntax, MatchingRule *mr, struct berval *prefix, BerVarray values, BerVarray *keysp, void *ctx ) { int i; BerVarray keys; for ( i = 0; values[i].bv_val != NULL; i++ ) { /* just count them */ } /* we should have at least one value at this point */ assert( i > 0 ); keys = slap_sl_malloc( sizeof(struct berval) * ( i + 1 ), ctx ); for ( i = 0; values[i].bv_val != NULL; i++ ) { ber_dupbv_x( &keys[i], &values[i], ctx ); } BER_BVZERO( &keys[i] ); *keysp = keys; return LDAP_SUCCESS; } /* Index generation function: Ordered index */ int datamorphUnsignedFilter( slap_mask_t use, slap_mask_t flags, Syntax *syntax, MatchingRule *mr, struct berval *prefix, void *assertedValue, BerVarray *keysp, void *ctx ) { BerVarray keys; BerValue *value = assertedValue; keys = slap_sl_malloc( sizeof(struct berval) * 2, ctx ); ber_dupbv_x( &keys[0], value, ctx ); BER_BVZERO( &keys[1] ); *keysp = keys; return LDAP_SUCCESS; } /* Index generation function: Ordered index */ int datamorphSignedIndexer( slap_mask_t use, slap_mask_t flags, Syntax *syntax, MatchingRule *mr, struct berval *prefix, BerVarray values, BerVarray *keysp, void *ctx ) { int i; BerVarray keys; for ( i = 0; values[i].bv_val != NULL; i++ ) { /* just count them */ } /* we should have at least one value at this point */ assert( i > 0 ); keys = slap_sl_malloc( sizeof(struct berval) * ( i + 1 ), ctx ); for ( i = 0; values[i].bv_val != NULL; i++ ) { keys[i].bv_len = values[i].bv_len + 1; keys[i].bv_val = slap_sl_malloc( keys[i].bv_len, ctx ); /* if positive (highest bit is not set), note that in the first byte */ *keys[i].bv_val = ~( *values[i].bv_val & 0x80 ); AC_MEMCPY( keys[i].bv_val + 1, values[i].bv_val, values[i].bv_len ); } BER_BVZERO( &keys[i] ); *keysp = keys; return LDAP_SUCCESS; } /* Index generation function: Ordered index */ int datamorphSignedFilter( slap_mask_t use, slap_mask_t flags, Syntax *syntax, MatchingRule *mr, struct berval *prefix, void *assertedValue, BerVarray *keysp, void *ctx ) { BerVarray keys; BerValue *value = assertedValue; keys = slap_sl_malloc( sizeof(struct berval) * 2, ctx ); keys[0].bv_len = value->bv_len + 1; keys[0].bv_val = slap_sl_malloc( keys[0].bv_len, ctx ); /* if positive (highest bit is not set), note that in the first byte */ *keys[0].bv_val = ~( *value->bv_val & 0x80 ); AC_MEMCPY( keys[0].bv_val + 1, value->bv_val, value->bv_len ); BER_BVZERO( &keys[1] ); *keysp = keys; return LDAP_SUCCESS; } #define DATAMORPH_ARC "1.3.6.1.4.1.4203.666.11.12" #define DATAMORPH_SYNTAXES DATAMORPH_ARC ".1" #define DATAMORPH_SYNTAX_BASE DATAMORPH_SYNTAXES ".1" #define DATAMORPH_SYNTAX_ENUM DATAMORPH_SYNTAXES ".2" #define DATAMORPH_SYNTAX_INT DATAMORPH_SYNTAXES ".3" #define DATAMORPH_SYNTAX_SIGNED_INT DATAMORPH_SYNTAXES ".4" #define DATAMORPH_MATCHES DATAMORPH_ARC ".2" #define DATAMORPH_MATCH_EQUALITY DATAMORPH_MATCHES ".1" #define DATAMORPH_MATCH_SIGNED_EQUALITY DATAMORPH_MATCHES ".2" #define DATAMORPH_MATCH_ORDERING DATAMORPH_MATCHES ".3" #define DATAMORPH_MATCH_SIGNED_ORDERING DATAMORPH_MATCHES ".4" static char *datamorph_sups[] = { DATAMORPH_SYNTAX_BASE, NULL }; static char *datamorphSyntaxes[] = { DATAMORPH_SYNTAX_SIGNED_INT, DATAMORPH_SYNTAX_ENUM, DATAMORPH_SYNTAX_INT, NULL }; static slap_syntax_defs_rec datamorph_syntax_defs[] = { { "( " DATAMORPH_SYNTAX_BASE " DESC 'Fixed size value' )", 0, NULL, NULL, NULL }, { "( " DATAMORPH_SYNTAX_ENUM " DESC 'Enumerated value' )", 0, datamorph_sups, datamorphBlobValidate, NULL }, { "( " DATAMORPH_SYNTAX_INT " DESC 'Fixed-size integer' )", 0, datamorph_sups, datamorphBlobValidate, NULL }, { "( " DATAMORPH_SYNTAX_SIGNED_INT " DESC 'Fixed-size signed integer' )", 0, datamorph_sups, datamorphBlobValidate, NULL }, { NULL, 0, NULL, NULL, NULL } }; static Syntax *datamorph_base_syntax; static slap_mrule_defs_rec datamorph_mrule_defs[] = { { "( " DATAMORPH_MATCH_EQUALITY " NAME 'fixedSizeIntegerMatch'" " SYNTAX " DATAMORPH_SYNTAX_INT " )", SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX, datamorphSyntaxes + 1, NULL, NULL, octetStringOrderingMatch, datamorphUnsignedIndexer, datamorphUnsignedFilter, NULL }, { "( " DATAMORPH_MATCH_SIGNED_EQUALITY " NAME 'fixedSizeSignedIntegerMatch'" " SYNTAX " DATAMORPH_SYNTAX_SIGNED_INT " )", SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX, NULL, NULL, NULL, datamorphBinarySignedOrderingMatch, datamorphSignedIndexer, datamorphSignedFilter, NULL }, { "( " DATAMORPH_MATCH_ORDERING " NAME 'fixedSizeIntegerOrderingMatch'" " SYNTAX " DATAMORPH_SYNTAX_INT " )", SLAP_MR_ORDERING|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX, datamorphSyntaxes + 1, NULL, NULL, octetStringOrderingMatch, datamorphUnsignedIndexer, datamorphUnsignedFilter, "octetStringMatch" }, { "( " DATAMORPH_MATCH_SIGNED_ORDERING " NAME 'fixedSizeSignedIntegerOrderingMatch'" " SYNTAX " DATAMORPH_SYNTAX_SIGNED_INT " )", SLAP_MR_ORDERING|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX, NULL, NULL, NULL, datamorphBinarySignedOrderingMatch, datamorphSignedIndexer, datamorphSignedFilter, "octetStringMatch" }, { NULL, SLAP_MR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; /* Configuration */ static ConfigLDAPadd datamorph_ldadd_enum; static ConfigLDAPadd datamorph_ldadd_interval; static ConfigLDAPadd datamorph_ldadd_mapping; static ConfigDriver datamorph_set_attribute; static ConfigDriver datamorph_set_size; static ConfigDriver datamorph_set_signed; static ConfigDriver datamorph_set_bounds; static ConfigDriver datamorph_set_index; static ConfigDriver datamorph_set_value; static ConfigDriver datamorph_add_mapping; static ConfigDriver datamorph_add_transformation; static ConfigCfAdd datamorph_cfadd; enum { DATAMORPH_INT_SIZE = 1, DATAMORPH_INT_SIGNED, DATAMORPH_INT_LOWER, DATAMORPH_INT_UPPER, DATAMORPH_INT_LAST, }; static ConfigTable datamorph_cfg[] = { { "datamorph_attribute", "attr", 2, 2, 0, ARG_STRING|ARG_QUOTE|ARG_MAGIC, datamorph_set_attribute, "( OLcfgCtAt:7.1 NAME 'olcDatamorphAttribute' " "DESC 'Attribute to transform' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, { "datamorph_size", "<1|2|4|8>", 2, 2, 0, ARG_INT|ARG_MAGIC|DATAMORPH_INT_SIZE, datamorph_set_size, "( OLcfgCtAt:7.2 NAME 'olcDatamorphIntegerBytes' " "DESC 'Integer size in bytes' " "EQUALITY integerMatch " "SYNTAX OMsInteger " "SINGLE-VALUE )", NULL, NULL }, { "datamorph_signed", "TRUE|FALSE", 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|DATAMORPH_INT_SIGNED, datamorph_set_signed, "( OLcfgCtAt:7.3 NAME 'olcDatamorphIntegerSigned' " "DESC 'Whether integers maintain sign' " "EQUALITY booleanMatch " "SYNTAX OMsBoolean " "SINGLE-VALUE )", NULL, NULL }, { "datamorph_lower_bound", "int", 2, 2, 0, ARG_BERVAL|ARG_MAGIC|DATAMORPH_INT_LOWER, datamorph_set_bounds, "( OLcfgCtAt:7.4 NAME 'olcDatamorphIntegerLowerBound' " "DESC 'Lowest valid value for the attribute' " "EQUALITY integerMatch " "SYNTAX OMsInteger " "SINGLE-VALUE )", NULL, NULL }, { "datamorph_upper_bound", "int", 2, 2, 0, ARG_BERVAL|ARG_MAGIC|DATAMORPH_INT_UPPER, datamorph_set_bounds, "( OLcfgCtAt:7.5 NAME 'olcDatamorphIntegerUpperBound' " "DESC 'Highest valid value for the attribute' " "EQUALITY integerMatch " "SYNTAX OMsInteger " "SINGLE-VALUE )", NULL, NULL }, /* These have no equivalent in slapd.conf */ { "", NULL, 2, 2, 0, ARG_INT|ARG_MAGIC, datamorph_set_index, "( OLcfgCtAt:7.6 NAME 'olcDatamorphIndex' " "DESC 'Internal DB value' " "EQUALITY integerMatch " "SYNTAX OMsInteger " "SINGLE-VALUE )", NULL, NULL }, { "", NULL, 2, 2, 0, ARG_BERVAL|ARG_QUOTE|ARG_MAGIC, datamorph_set_value, "( OLcfgCtAt:7.7 NAME 'olcDatamorphValue' " "DESC 'Wire value' " "EQUALITY caseExactMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, /* slapd.conf alternative for the two above */ { "datamorph_value", "int> wire_value.bv_val ); ch_free( mapping ); } static void datamorph_info_free( void *arg ) { transformation_info *info = arg; if ( info->type == DATAMORPH_ENUM ) { ldap_avl_free( info->ti_enum.to_db, datamorph_mapping_free ); } ch_free( info ); } static int datamorph_set_attribute( ConfigArgs *ca ) { transformation_info needle = {}, *info = ca->ca_private; slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; char *s = ca->value_string; const char *text; int rc = LDAP_SUCCESS; if ( ca->op == SLAP_CONFIG_EMIT ) { ca->value_string = info->attr->ad_cname.bv_val; return LDAP_SUCCESS; } else if ( ca->op == LDAP_MOD_DELETE ) { info = ldap_avl_delete( &ov->transformations, info, transformation_info_cmp ); assert( info ); info->attr = NULL; return LDAP_SUCCESS; } if ( *s == '{' ) { s = strchr( s, '}' ); if ( !s ) { rc = LDAP_UNDEFINED_TYPE; goto done; } s += 1; } rc = slap_str2ad( s, &info->attr, &text ); ch_free( ca->value_string ); if ( rc ) { goto done; } /* The type has to be set appropriately */ if ( !info->attr->ad_type->sat_syntax->ssyn_sups || info->attr->ad_type->sat_syntax->ssyn_sups[0] != datamorph_base_syntax ) { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "improper syntax for attribute %s", info->attr->ad_cname.bv_val ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); rc = LDAP_CONSTRAINT_VIOLATION; goto done; } needle.attr = info->attr; if ( ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ) ) { rc = LDAP_CONSTRAINT_VIOLATION; goto done; } done: if ( rc ) { ca->reply.err = rc; } return rc; } static int datamorph_set_size( ConfigArgs *ca ) { transformation_info *info = ca->ca_private; if ( !info ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; info = ov->wip_transformation; assert( ca->op == SLAP_CONFIG_ADD ); } if ( ca->op == SLAP_CONFIG_EMIT ) { ca->value_int = info->ti_int.size; return LDAP_SUCCESS; } else if ( ca->op == LDAP_MOD_DELETE ) { info->ti_int.size = 0; return LDAP_SUCCESS; } if ( ca->value_int != 1 && ca->value_int != 2 && ca->value_int != 4 && ca->value_int != 8 ) { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "invalid size %d", ca->value_int ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); ca->reply.err = LDAP_CONSTRAINT_VIOLATION; return ca->reply.err; } info->ti_int.size = ca->value_int; return LDAP_SUCCESS; } static int datamorph_set_signed( ConfigArgs *ca ) { transformation_info *info = ca->ca_private; if ( !info ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; info = ov->wip_transformation; assert( ca->op == SLAP_CONFIG_ADD ); } if ( ca->op == SLAP_CONFIG_EMIT ) { ca->value_int = info->ti_int.flags & DATAMORPH_FLAG_SIGNED; return LDAP_SUCCESS; } else if ( ca->op == LDAP_MOD_DELETE ) { info->ti_int.flags &= ~DATAMORPH_FLAG_SIGNED; return LDAP_SUCCESS; } info->ti_int.flags &= ~DATAMORPH_FLAG_SIGNED; if ( ca->value_int ) { info->ti_int.flags |= DATAMORPH_FLAG_SIGNED; } return LDAP_SUCCESS; } static int datamorph_set_bounds( ConfigArgs *ca ) { transformation_info *info = ca->ca_private; datamorph_interval_bound *bound; uint64_t unsigned_bound; int64_t signed_bound; char *ptr = ca->value_bv.bv_val + ca->value_bv.bv_len; int flag; if ( !info ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; info = ov->wip_transformation; assert( ca->op == SLAP_CONFIG_ADD ); } switch ( ca->type ) { case DATAMORPH_INT_LOWER: bound = &info->ti_int.lower; flag = DATAMORPH_FLAG_LOWER; break; case DATAMORPH_INT_UPPER: bound = &info->ti_int.upper; flag = DATAMORPH_FLAG_UPPER; break; default: assert(0); } if ( ca->op == SLAP_CONFIG_EMIT ) { char buf[24]; struct berval bv = { .bv_val = buf }; if ( !(info->ti_int.flags & flag) ) { /* Bound not set, do not emit */ return LDAP_SUCCESS; } if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { bv.bv_len = sprintf( buf, "%" PRId64, bound->i ); } else { bv.bv_len = sprintf( buf, "%" PRIu64, bound->u ); } ber_dupbv_x( &ca->value_bv, &bv, ca->ca_op->o_tmpmemctx ); return LDAP_SUCCESS; } else if ( ca->op == LDAP_MOD_DELETE ) { info->ti_int.flags &= ~flag; if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { bound->i = (flag == DATAMORPH_FLAG_LOWER) ? INT64_MIN : INT64_MAX; } else { bound->u = (flag == DATAMORPH_FLAG_LOWER) ? 0 : UINT64_MAX; } return LDAP_SUCCESS; } /* FIXME: if attributes in the Add operation come in the wrong order * (signed=true after the bound definition), we can't check the interval * sanity. */ /* if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { signed_bound = strtoll( ca->value_bv.bv_val, &ptr, 10 ); } else { unsigned_bound = strtoull( ca->value_bv.bv_val, &ptr, 10 ); } */ /* Also, no idea what happens in the case of big-endian, hopefully, * it behaves the same */ unsigned_bound = strtoull( ca->value_bv.bv_val, &ptr, 10 ); signed_bound = (int64_t)unsigned_bound; if ( *ca->value_bv.bv_val == '\0' || *ptr != '\0' ) { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "failed to parse '%s' as integer", ca->value_bv.bv_val ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); ca->reply.err = LDAP_CONSTRAINT_VIOLATION; return ca->reply.err; } ch_free( ca->value_bv.bv_val ); info->ti_int.flags |= flag; switch ( info->ti_int.size ) { case 1: if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { /* See FIXME above if ( signed_bound < INT8_MIN || signed_bound > INT8_MAX ) { goto fail; } */ } else { /* See FIXME above if ( unsigned_bound > UINT8_MAX ) { goto fail; } */ } break; case 2: if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { /* See FIXME above if ( signed_bound < INT16_MIN || signed_bound > INT16_MAX ) { goto fail; } */ } else { /* See FIXME above if ( unsigned_bound > UINT16_MAX ) { goto fail; } */ } break; case 4: if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { /* See FIXME above if ( signed_bound < INT32_MIN || signed_bound > INT32_MAX ) { goto fail; } */ } else { /* See FIXME above if ( unsigned_bound > UINT32_MAX ) { goto fail; } */ } break; case 8: break; default: /* Should only happen in these two cases: * 1. datamorph_size not yet encountered for this one (when * processing slapd.conf) * 2. When someone runs a fun modification on the config entry * messing with more attributes at once * * The error message is expected to be helpful only for the former, * so use the slapd.conf name. */ snprintf( ca->cr_msg, sizeof(ca->cr_msg), "datamorph_size has to be set first!" ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); ca->reply.err = LDAP_CONSTRAINT_VIOLATION; return ca->reply.err; } if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) { bound->i = signed_bound; } else { bound->u = unsigned_bound; } return LDAP_SUCCESS; } static int datamorph_set_value( ConfigArgs *ca ) { datamorph_enum_mapping *mapping = ca->ca_private; char *s = ca->value_bv.bv_val; if ( ca->op == SLAP_CONFIG_EMIT ) { /* We generate the value as part of the RDN, don't add anything */ return LDAP_SUCCESS; } else if ( ca->op == LDAP_MOD_DELETE ) { ch_free( mapping->wire_value.bv_val ); BER_BVZERO( &mapping->wire_value ); /* TODO: remove from info->ti_enum.to_db? */ return LDAP_SUCCESS; } /* As long as this attribute can be in the RDN, * we have to expect the '{n}' prefix */ if ( *s == '{' ) { ber_len_t len; s = memchr( s, '}', ca->value_bv.bv_len ); if ( !s ) { ca->reply.err = LDAP_UNDEFINED_TYPE; return ca->reply.err; } s += 1; len = ca->value_bv.bv_len - ( s - ca->value_bv.bv_val ); ber_str2bv( s, len, 1, &mapping->wire_value ); ch_free( ca->value_bv.bv_val ); } else { mapping->wire_value = ca->value_bv; } return LDAP_SUCCESS; } static int datamorph_set_index( ConfigArgs *ca ) { datamorph_enum_mapping *mapping = ca->ca_private; struct berval *from_db = mapping->transformation->ti_enum.from_db; if ( ca->op == SLAP_CONFIG_EMIT ) { ca->value_int = mapping->db_value; return LDAP_SUCCESS; } else if ( ca->op == LDAP_MOD_DELETE ) { BER_BVZERO( &from_db[mapping->db_value] ); return LDAP_SUCCESS; } if ( ca->value_int < 0 || ca->value_int >= 256 ) { ca->reply.err = LDAP_CONSTRAINT_VIOLATION; return ca->reply.err; } else if ( !BER_BVISNULL( &from_db[ca->value_int] ) ) { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "duplicate index %d", ca->value_int ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); ca->reply.err = LDAP_CONSTRAINT_VIOLATION; return ca->reply.err; } mapping->db_value = ca->value_int; from_db[ca->value_int] = mapping->wire_value; return LDAP_SUCCESS; } /* Called when processing slapd.conf only, * cn=config uses the objectclass to decide which type we're dealing with. */ static int datamorph_add_transformation( ConfigArgs *ca ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; transformation_info *info; if ( ov->wip_transformation ) { /* We checked everything as were processing the lines */ int rc = ldap_avl_insert( &ov->transformations, ov->wip_transformation, transformation_info_cmp, ldap_avl_dup_error ); assert( rc == LDAP_SUCCESS ); } info = ch_calloc( 1, sizeof(transformation_info) ); ov->wip_transformation = ca->ca_private = info; if ( !strcasecmp( ca->argv[1], "enum" ) ) { info->type = DATAMORPH_ENUM; } else if ( !strcasecmp( ca->argv[1], "int" ) ) { info->type = DATAMORPH_INT; } else { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "unknown transformation type '%s'", ca->argv[1] ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); ca->reply.err = LDAP_CONSTRAINT_VIOLATION; return ca->reply.err; } ca->value_string = strdup( ca->argv[2] ); return datamorph_set_attribute( ca ); } static int datamorph_add_mapping( ConfigArgs *ca ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; transformation_info *info = ov->wip_transformation; datamorph_enum_mapping *mapping; int rc = LDAP_CONSTRAINT_VIOLATION; if ( !info ) { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "no attribute configured" ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); goto done; } mapping = ch_calloc( 1, sizeof(datamorph_enum_mapping) ); mapping->transformation = info; ca->ca_private = mapping; ber_str2bv( ca->argv[2], 0, 1, &ca->value_bv ); rc = datamorph_set_value( ca ); if ( rc != LDAP_SUCCESS ) { goto done; } rc = lutil_atoix( &ca->value_int, ca->argv[1], 0 ); if ( rc != LDAP_SUCCESS ) { snprintf( ca->cr_msg, sizeof(ca->cr_msg), "invalid integer %s", ca->argv[1] ); Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg ); goto done; } rc = datamorph_set_index( ca ); if ( rc != LDAP_SUCCESS ) { goto done; } done: if ( rc == LDAP_SUCCESS ) { rc = ldap_avl_insert( &info->ti_enum.to_db, mapping, transformation_mapping_cmp, ldap_avl_dup_error ); } if ( rc ) { ca->reply.err = rc; } return rc; } static int datamorph_ldadd_info_cleanup( ConfigArgs *ca ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; transformation_info *info = ca->ca_private; if ( ca->reply.err != LDAP_SUCCESS ) { /* Not reached since cleanup is only called on success */ fail: ch_free( info ); return LDAP_SUCCESS; } if ( ldap_avl_insert( &ov->transformations, info, transformation_info_cmp, ldap_avl_dup_error ) ) { goto fail; } return LDAP_SUCCESS; } static int datamorph_ldadd_transformation( CfEntryInfo *cei, Entry *e, ConfigArgs *ca, datamorph_type type ) { transformation_info *info; if ( cei->ce_type != Cft_Overlay || !cei->ce_bi || cei->ce_bi->bi_cf_ocs != datamorph_ocs ) return LDAP_CONSTRAINT_VIOLATION; info = ch_calloc( 1, sizeof(transformation_info) ); info->type = type; ca->bi = cei->ce_bi; ca->ca_private = info; config_push_cleanup( ca, datamorph_ldadd_info_cleanup ); /* config_push_cleanup is only run in the case of online config but we use it to * enable the new config when done with the entry */ ca->lineno = 0; return LDAP_SUCCESS; } static int datamorph_ldadd_enum( CfEntryInfo *cei, Entry *e, ConfigArgs *ca ) { return datamorph_ldadd_transformation( cei, e, ca, DATAMORPH_ENUM ); } static int datamorph_ldadd_interval( CfEntryInfo *cei, Entry *e, ConfigArgs *ca ) { return datamorph_ldadd_transformation( cei, e, ca, DATAMORPH_INT ); } static int datamorph_ldadd_mapping_cleanup( ConfigArgs *ca ) { datamorph_enum_mapping *mapping = ca->ca_private; transformation_info *info = mapping->transformation; if ( ca->reply.err != LDAP_SUCCESS ) { /* Not reached since cleanup is only called on success */ fail: datamorph_mapping_free( mapping ); return LDAP_SUCCESS; } if ( ldap_avl_insert( &info->ti_enum.to_db, mapping, transformation_mapping_cmp, ldap_avl_dup_error ) ) { goto fail; } info->ti_enum.from_db[mapping->db_value] = mapping->wire_value; return LDAP_SUCCESS; } static int datamorph_ldadd_mapping( CfEntryInfo *cei, Entry *e, ConfigArgs *ca ) { transformation_info *info; datamorph_enum_mapping *mapping; CfEntryInfo *parent = cei->ce_parent; if ( cei->ce_type != Cft_Misc || !parent || !parent->ce_bi || parent->ce_bi->bi_cf_ocs != datamorph_ocs ) return LDAP_CONSTRAINT_VIOLATION; info = cei->ce_private; mapping = ch_calloc( 1, sizeof(datamorph_enum_mapping) ); mapping->transformation = info; ca->ca_private = mapping; config_push_cleanup( ca, datamorph_ldadd_mapping_cleanup ); /* config_push_cleanup is only run in the case of online config but we use it to * enable the new config when done with the entry */ ca->lineno = 0; return LDAP_SUCCESS; } struct datamorph_cfadd_args { Operation *op; SlapReply *rs; Entry *p; ConfigArgs *ca; int index; }; static int datamorph_config_build_enum( void *item, void *arg ) { datamorph_enum_mapping *mapping = item; struct datamorph_cfadd_args *args = arg; struct berval rdn; Entry *e; char *p; ber_len_t index; rdn.bv_len = snprintf( args->ca->cr_msg, sizeof(args->ca->cr_msg), "olcDatamorphValue={%d}", args->index++ ); rdn.bv_val = args->ca->cr_msg; p = rdn.bv_val + rdn.bv_len; rdn.bv_len += mapping->wire_value.bv_len; for ( index = 0; index < mapping->wire_value.bv_len; index++ ) { if ( RDN_NEEDSESCAPE(mapping->wire_value.bv_val[index]) ) { rdn.bv_len++; *p++ = '\\'; } *p++ = mapping->wire_value.bv_val[index]; } *p = '\0'; args->ca->ca_private = mapping; args->ca->ca_op = args->op; e = config_build_entry( args->op, args->rs, args->p->e_private, args->ca, &rdn, &datamorph_ocs[4], NULL ); assert( e ); return LDAP_SUCCESS; } static int datamorph_config_build_attr( void *item, void *arg ) { transformation_info *info = item; struct datamorph_cfadd_args *args = arg; struct berval rdn; ConfigOCs *oc; Entry *e; rdn.bv_len = snprintf( args->ca->cr_msg, sizeof(args->ca->cr_msg), "olcDatamorphAttribute={%d}%s", args->index++, info->attr->ad_cname.bv_val ); rdn.bv_val = args->ca->cr_msg; switch ( info->type ) { case DATAMORPH_ENUM: oc = &datamorph_ocs[2]; break; case DATAMORPH_INT: oc = &datamorph_ocs[3]; break; default: assert(0); break; } args->ca->ca_private = info; args->ca->ca_op = args->op; e = config_build_entry( args->op, args->rs, args->p->e_private, args->ca, &rdn, oc, NULL ); assert( e ); if ( info->type == DATAMORPH_ENUM ) { struct datamorph_cfadd_args new_args = *args; new_args.p = e; new_args.index = 0; return ldap_avl_apply( info->ti_enum.to_db, datamorph_config_build_enum, &new_args, 1, AVL_PREORDER ); } return LDAP_SUCCESS; } static int datamorph_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) { slap_overinst *on = (slap_overinst *)ca->bi; datamorph_info *ov = on->on_bi.bi_private; struct datamorph_cfadd_args args = { .op = op, .rs = rs, .p = p, .ca = ca, .index = 0, }; if ( ov->wip_transformation ) { /* There is one last item that is unfinished */ int rc = ldap_avl_insert( &ov->transformations, ov->wip_transformation, transformation_info_cmp, ldap_avl_dup_error ); assert( rc == LDAP_SUCCESS ); } return ldap_avl_apply( ov->transformations, &datamorph_config_build_attr, &args, 1, AVL_PREORDER ); } static slap_overinst datamorph; static int datamorph_db_init( BackendDB *be, ConfigReply *cr ) { slap_overinst *on = (slap_overinst *)be->bd_info; datamorph_info *ov; /* TODO: can this be global? */ if ( SLAP_ISGLOBALOVERLAY(be) ) { Debug( LDAP_DEBUG_ANY, "datamorph overlay must be instantiated " "within a database.\n" ); return 1; } ov = ch_calloc( 1, sizeof(datamorph_info) ); on->on_bi.bi_private = ov; return LDAP_SUCCESS; } static int datamorph_db_destroy( BackendDB *be, ConfigReply *cr ) { slap_overinst *on = (slap_overinst *)be->bd_info; datamorph_info *ov = on->on_bi.bi_private; if ( ov ) { ldap_avl_free( ov->transformations, datamorph_info_free ); } ch_free( ov ); return LDAP_SUCCESS; } int datamorph_initialize() { int rc, i; datamorph.on_bi.bi_type = "datamorph"; datamorph.on_bi.bi_db_init = datamorph_db_init; datamorph.on_bi.bi_db_destroy = datamorph_db_destroy; datamorph.on_bi.bi_op_add = datamorph_op_add; datamorph.on_bi.bi_op_compare = datamorph_op_compare; datamorph.on_bi.bi_op_modify = datamorph_op_mod; datamorph.on_bi.bi_op_modrdn = datamorph_op_modrdn; datamorph.on_bi.bi_op_search = datamorph_op_search; datamorph.on_response = datamorph_response; datamorph.on_bi.bi_entry_release_rw = datamorph_entry_release_rw; datamorph.on_bi.bi_entry_get_rw = datamorph_entry_get_rw; datamorph.on_bi.bi_cf_ocs = datamorph_ocs; for ( i = 0; datamorph_syntax_defs[i].sd_desc != NULL; i++ ) { rc = register_syntax( &datamorph_syntax_defs[i] ); if ( rc ) { Debug( LDAP_DEBUG_ANY, "datamorph_initialize: " "error registering syntax %s\n", datamorph_syntax_defs[i].sd_desc ); return rc; } } datamorph_base_syntax = syn_find( DATAMORPH_SYNTAX_BASE ); assert( datamorph_base_syntax ); for ( i = 0; datamorph_mrule_defs[i].mrd_desc != NULL; i++ ) { rc = register_matching_rule( &datamorph_mrule_defs[i] ); if ( rc ) { Debug( LDAP_DEBUG_ANY, "datamorph_initialize: " "error registering matching rule %s\n", datamorph_mrule_defs[i].mrd_desc ); return rc; } } rc = config_register_schema( datamorph_cfg, datamorph_ocs ); if ( rc ) return rc; return overlay_register( &datamorph ); } #if SLAPD_OVER_DATAMORPH == SLAPD_MOD_DYNAMIC int init_module( int argc, char *argv[] ) { return datamorph_initialize(); } #endif #endif /* SLAPD_OVER_DATAMORPH */