/* $NetBSD: dds.c,v 1.3 2021/08/14 16:15:02 christos Exp $ */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2005-2021 The OpenLDAP Foundation. * Portions Copyright 2005-2006 SysNet s.n.c. * 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, sponsored by SysNet s.n.c. */ #include __RCSID("$NetBSD: dds.c,v 1.3 2021/08/14 16:15:02 christos Exp $"); #include "portable.h" #ifdef SLAPD_OVER_DDS #include #include #include #include "slap.h" #include "lutil.h" #include "ldap_rq.h" #include "slap-config.h" #define DDS_RF2589_MAX_TTL (31557600) /* 1 year + 6 hours */ #define DDS_RF2589_DEFAULT_TTL (86400) /* 1 day */ #define DDS_DEFAULT_INTERVAL (3600) /* 1 hour */ typedef struct dds_info_t { unsigned di_flags; #define DDS_FOFF (0x1U) /* is this really needed? */ #define DDS_SET(di, f) ( (di)->di_flags & (f) ) #define DDS_OFF(di) DDS_SET( (di), DDS_FOFF ) time_t di_max_ttl; time_t di_min_ttl; time_t di_default_ttl; #define DDS_DEFAULT_TTL(di) \ ( (di)->di_default_ttl ? (di)->di_default_ttl : (di)->di_max_ttl ) time_t di_tolerance; /* expire check interval and task */ time_t di_interval; #define DDS_INTERVAL(di) \ ( (di)->di_interval ? (di)->di_interval : DDS_DEFAULT_INTERVAL ) struct re_s *di_expire_task; /* allows to limit the maximum number of dynamic objects */ ldap_pvt_thread_mutex_t di_mutex; int di_num_dynamicObjects; int di_max_dynamicObjects; /* used to advertise the dynamicSubtrees in the root DSE, * and to select the database in the expiration task */ BerVarray di_suffix; BerVarray di_nsuffix; } dds_info_t; static struct berval slap_EXOP_REFRESH = BER_BVC( LDAP_EXOP_REFRESH ); static AttributeDescription *ad_entryExpireTimestamp; /* list of expired DNs */ typedef struct dds_expire_t { struct berval de_ndn; struct dds_expire_t *de_next; } dds_expire_t; typedef struct dds_cb_t { dds_expire_t *dc_ndnlist; } dds_cb_t; static int dds_expire_cb( Operation *op, SlapReply *rs ) { dds_cb_t *dc = (dds_cb_t *)op->o_callback->sc_private; dds_expire_t *de; int rc; switch ( rs->sr_type ) { case REP_SEARCH: /* alloc list and buffer for berval all in one */ de = op->o_tmpalloc( sizeof( dds_expire_t ) + rs->sr_entry->e_nname.bv_len + 1, op->o_tmpmemctx ); de->de_next = dc->dc_ndnlist; dc->dc_ndnlist = de; de->de_ndn.bv_len = rs->sr_entry->e_nname.bv_len; de->de_ndn.bv_val = (char *)&de[ 1 ]; AC_MEMCPY( de->de_ndn.bv_val, rs->sr_entry->e_nname.bv_val, rs->sr_entry->e_nname.bv_len + 1 ); rc = 0; break; case REP_SEARCHREF: case REP_RESULT: rc = rs->sr_err; break; default: assert( 0 ); } return rc; } static int dds_expire( void *ctx, dds_info_t *di ) { Connection conn = { 0 }; OperationBuffer opbuf; Operation *op; slap_callback sc = { 0 }; dds_cb_t dc = { 0 }; dds_expire_t *de = NULL, **dep; SlapReply rs = { REP_RESULT }; time_t expire; char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; struct berval ts; int ndeletes, ntotdeletes; int rc; char *extra = ""; connection_fake_init2( &conn, &opbuf, ctx, 0 ); op = &opbuf.ob_op; op->o_tag = LDAP_REQ_SEARCH; memset( &op->oq_search, 0, sizeof( op->oq_search ) ); op->o_bd = select_backend( &di->di_nsuffix[ 0 ], 0 ); op->o_req_dn = op->o_bd->be_suffix[ 0 ]; op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; op->o_dn = op->o_bd->be_rootdn; op->o_ndn = op->o_bd->be_rootndn; op->ors_scope = LDAP_SCOPE_SUBTREE; op->ors_tlimit = DDS_INTERVAL( di )/2 + 1; op->ors_slimit = SLAP_NO_LIMIT; op->ors_attrs = slap_anlist_no_attrs; op->o_do_not_cache = 1; expire = slap_get_time() - di->di_tolerance; ts.bv_val = tsbuf; ts.bv_len = sizeof( tsbuf ); slap_timestamp( &expire, &ts ); op->ors_filterstr.bv_len = STRLENOF( "(&(objectClass=" ")(" "<=" "))" ) + slap_schema.si_oc_dynamicObject->soc_cname.bv_len + ad_entryExpireTimestamp->ad_cname.bv_len + ts.bv_len; op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1, "(&(objectClass=%s)(%s<=%s))", slap_schema.si_oc_dynamicObject->soc_cname.bv_val, ad_entryExpireTimestamp->ad_cname.bv_val, ts.bv_val ); op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); if ( op->ors_filter == NULL ) { rs.sr_err = LDAP_OTHER; goto done_search; } op->o_callback = ≻ sc.sc_response = dds_expire_cb; sc.sc_private = &dc; (void)op->o_bd->bd_info->bi_op_search( op, &rs ); done_search:; op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); filter_free_x( op, op->ors_filter, 1 ); rc = rs.sr_err; switch ( rs.sr_err ) { case LDAP_SUCCESS: break; case LDAP_NO_SUCH_OBJECT: /* (ITS#5267) database not created yet? */ rs.sr_err = LDAP_SUCCESS; extra = " (ignored)"; /* fallthru */ default: Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS expired objects lookup failed err=%d%s\n", rc, extra ); goto done; } op->o_tag = LDAP_REQ_DELETE; op->o_callback = ≻ sc.sc_response = slap_null_cb; sc.sc_private = NULL; for ( ntotdeletes = 0, ndeletes = 1; dc.dc_ndnlist != NULL && ndeletes > 0; ) { ndeletes = 0; for ( dep = &dc.dc_ndnlist; *dep != NULL; ) { de = *dep; op->o_req_dn = de->de_ndn; op->o_req_ndn = de->de_ndn; (void)op->o_bd->bd_info->bi_op_delete( op, &rs ); switch ( rs.sr_err ) { case LDAP_SUCCESS: Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, "DDS dn=\"%s\" expired.\n", de->de_ndn.bv_val ); ndeletes++; break; case LDAP_NOT_ALLOWED_ON_NONLEAF: Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, "DDS dn=\"%s\" is non-leaf; " "deferring.\n", de->de_ndn.bv_val ); dep = &de->de_next; de = NULL; break; default: Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, "DDS dn=\"%s\" err=%d; " "deferring.\n", de->de_ndn.bv_val, rs.sr_err ); break; } if ( de != NULL ) { *dep = de->de_next; op->o_tmpfree( de, op->o_tmpmemctx ); } } ntotdeletes += ndeletes; } rs.sr_err = LDAP_SUCCESS; Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, "DDS expired=%d\n", ntotdeletes ); done:; return rs.sr_err; } static void * dds_expire_fn( void *ctx, void *arg ) { struct re_s *rtask = arg; dds_info_t *di = rtask->arg; assert( di->di_expire_task == rtask ); (void)dds_expire( ctx, di ); ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) { ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); } ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); return NULL; } /* frees the callback */ static int dds_freeit_cb( Operation *op, SlapReply *rs ) { op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); op->o_callback = NULL; return SLAP_CB_CONTINUE; } /* updates counter - installed on add/delete only if required */ static int dds_counter_cb( Operation *op, SlapReply *rs ) { assert( rs->sr_type == REP_RESULT ); if ( rs->sr_err == LDAP_SUCCESS ) { dds_info_t *di = op->o_callback->sc_private; ldap_pvt_thread_mutex_lock( &di->di_mutex ); switch ( op->o_tag ) { case LDAP_REQ_DELETE: assert( di->di_num_dynamicObjects > 0 ); di->di_num_dynamicObjects--; break; case LDAP_REQ_ADD: assert( di->di_num_dynamicObjects < di->di_max_dynamicObjects ); di->di_num_dynamicObjects++; break; default: assert( 0 ); } ldap_pvt_thread_mutex_unlock( &di->di_mutex ); } return dds_freeit_cb( op, rs ); } static int dds_op_add( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; dds_info_t *di = on->on_bi.bi_private; int is_dynamicObject; if ( DDS_OFF( di ) ) { return SLAP_CB_CONTINUE; } is_dynamicObject = is_entry_dynamicObject( op->ora_e ); /* FIXME: do not allow this right now, pending clarification */ if ( is_dynamicObject ) { rs->sr_err = LDAP_SUCCESS; if ( is_entry_referral( op->ora_e ) ) { rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; rs->sr_text = "a referral cannot be a dynamicObject"; } else if ( is_entry_alias( op->ora_e ) ) { rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; rs->sr_text = "an alias cannot be a dynamicObject"; } if ( rs->sr_err != LDAP_SUCCESS ) { op->o_bd->bd_info = (BackendInfo *)on->on_info; send_ldap_result( op, rs ); return rs->sr_err; } } /* we don't allow dynamicObjects to have static subordinates */ if ( !dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) { struct berval p_ndn; Entry *e = NULL; int rc; BackendInfo *bi = op->o_bd->bd_info; dnParent( &op->o_req_ndn, &p_ndn ); op->o_bd->bd_info = (BackendInfo *)on->on_info; rc = be_entry_get_rw( op, &p_ndn, slap_schema.si_oc_dynamicObject, NULL, 0, &e ); if ( rc == LDAP_SUCCESS && e != NULL ) { if ( !is_dynamicObject ) { /* return referral only if "disclose" * is granted on the object */ if ( ! access_allowed( op, e, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) ) { rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; send_ldap_result( op, rs ); } else { rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; send_ldap_error( op, rs, rc, "no static subordinate entries allowed for dynamicObject" ); } } be_entry_release_r( op, e ); if ( rc != LDAP_SUCCESS ) { return rc; } } op->o_bd->bd_info = bi; } /* handle dynamic object operational attr(s) */ if ( is_dynamicObject ) { time_t ttl, expire; char ttlbuf[STRLENOF("31557600") + 1]; char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; struct berval bv; if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) { ldap_pvt_thread_mutex_lock( &di->di_mutex ); rs->sr_err = ( di->di_max_dynamicObjects && di->di_num_dynamicObjects >= di->di_max_dynamicObjects ); ldap_pvt_thread_mutex_unlock( &di->di_mutex ); if ( rs->sr_err ) { op->o_bd->bd_info = (BackendInfo *)on->on_info; send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, "too many dynamicObjects in context" ); return rs->sr_err; } } ttl = DDS_DEFAULT_TTL( di ); /* assert because should be checked at configure */ assert( ttl <= DDS_RF2589_MAX_TTL ); bv.bv_val = ttlbuf; bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); assert( bv.bv_len < sizeof( ttlbuf ) ); /* FIXME: apparently, values in op->ora_e are malloc'ed * on the thread's slab; works fine by chance, * only because the attribute doesn't exist yet. */ assert( attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryTtl ) == NULL ); attr_merge_one( op->ora_e, slap_schema.si_ad_entryTtl, &bv, &bv ); expire = slap_get_time() + ttl; bv.bv_val = tsbuf; bv.bv_len = sizeof( tsbuf ); slap_timestamp( &expire, &bv ); assert( attr_find( op->ora_e->e_attrs, ad_entryExpireTimestamp ) == NULL ); attr_merge_one( op->ora_e, ad_entryExpireTimestamp, &bv, &bv ); /* if required, install counter callback */ if ( di->di_max_dynamicObjects > 0) { slap_callback *sc; sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); sc->sc_cleanup = dds_freeit_cb; sc->sc_response = dds_counter_cb; sc->sc_private = di; sc->sc_next = op->o_callback; sc->sc_writewait = 0; op->o_callback = sc; } } return SLAP_CB_CONTINUE; } static int dds_op_delete( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; dds_info_t *di = on->on_bi.bi_private; /* if required, install counter callback */ if ( !DDS_OFF( di ) && di->di_max_dynamicObjects > 0 ) { Entry *e = NULL; BackendInfo *bi = op->o_bd->bd_info; op->o_bd->bd_info = (BackendInfo *)on->on_info; rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, slap_schema.si_oc_dynamicObject, NULL, 0, &e ); /* FIXME: couldn't the entry be added before deletion? */ if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { slap_callback *sc; be_entry_release_r( op, e ); e = NULL; sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); sc->sc_cleanup = dds_freeit_cb; sc->sc_response = dds_counter_cb; sc->sc_private = di; sc->sc_writewait = 0; sc->sc_next = op->o_callback; op->o_callback = sc; } op->o_bd->bd_info = bi; } return SLAP_CB_CONTINUE; } static int dds_op_modify( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; dds_info_t *di = (dds_info_t *)on->on_bi.bi_private; Modifications *mod; Entry *e = NULL; BackendInfo *bi = op->o_bd->bd_info; int was_dynamicObject = 0, is_dynamicObject = 0; struct berval bv_entryTtl = BER_BVNULL; time_t entryTtl = 0; char textbuf[ SLAP_TEXT_BUFLEN ]; if ( DDS_OFF( di ) ) { return SLAP_CB_CONTINUE; } /* bv_entryTtl stores the string representation of the entryTtl * across modifies for consistency checks of the final value; * the bv_val points to a static buffer; the bv_len is zero when * the attribute is deleted. * entryTtl stores the integer representation of the entryTtl; * its value is -1 when the attribute is deleted; it is 0 only * if no modifications of the entryTtl occurred, as an entryTtl * of 0 is invalid. */ bv_entryTtl.bv_val = textbuf; op->o_bd->bd_info = (BackendInfo *)on->on_info; rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, slap_schema.si_oc_dynamicObject, slap_schema.si_ad_entryTtl, 0, &e ); if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryTtl ); /* the value of the entryTtl is saved for later checks */ if ( a != NULL ) { unsigned long ttl; int rc; bv_entryTtl.bv_len = a->a_nvals[ 0 ].bv_len; AC_MEMCPY( bv_entryTtl.bv_val, a->a_nvals[ 0 ].bv_val, bv_entryTtl.bv_len ); bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0'; rc = lutil_atoul( &ttl, bv_entryTtl.bv_val ); assert( rc == 0 ); entryTtl = (time_t)ttl; } be_entry_release_r( op, e ); e = NULL; was_dynamicObject = is_dynamicObject = 1; } op->o_bd->bd_info = bi; rs->sr_err = LDAP_SUCCESS; for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) { if ( mod->sml_desc == slap_schema.si_ad_objectClass ) { int i; ObjectClass *oc; switch ( mod->sml_op ) { case LDAP_MOD_DELETE: if ( mod->sml_values == NULL ) { is_dynamicObject = 0; break; } for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) { oc = oc_bvfind( &mod->sml_values[ i ] ); if ( oc == slap_schema.si_oc_dynamicObject ) { is_dynamicObject = 0; break; } } break; case LDAP_MOD_REPLACE: if ( mod->sml_values == NULL ) { is_dynamicObject = 0; break; } /* fallthru */ case LDAP_MOD_ADD: for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) { oc = oc_bvfind( &mod->sml_values[ i ] ); if ( oc == slap_schema.si_oc_dynamicObject ) { is_dynamicObject = 1; break; } } break; } } else if ( mod->sml_desc == slap_schema.si_ad_entryTtl ) { unsigned long uttl; time_t ttl; int rc; switch ( mod->sml_op ) { case LDAP_MOD_DELETE: case SLAP_MOD_SOFTDEL: /* FIXME? */ if ( mod->sml_values != NULL ) { if ( BER_BVISEMPTY( &bv_entryTtl ) || !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) ) { rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { rs->sr_err = LDAP_NO_SUCH_OBJECT; } else { rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; } goto done; } } bv_entryTtl.bv_len = 0; entryTtl = -1; break; case LDAP_MOD_REPLACE: bv_entryTtl.bv_len = 0; entryTtl = -1; /* fallthru */ case LDAP_MOD_ADD: case SLAP_MOD_SOFTADD: /* FIXME? */ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* FIXME? */ assert( mod->sml_values != NULL ); assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) ); if ( !BER_BVISEMPTY( &bv_entryTtl ) ) { rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { rs->sr_err = LDAP_NO_SUCH_OBJECT; } else { rs->sr_text = "attribute 'entryTtl' cannot have multiple values"; rs->sr_err = LDAP_CONSTRAINT_VIOLATION; } goto done; } rc = lutil_atoul( &uttl, mod->sml_values[ 0 ].bv_val ); ttl = (time_t)uttl; assert( rc == 0 ); if ( ttl > DDS_RF2589_MAX_TTL ) { rs->sr_err = LDAP_PROTOCOL_ERROR; rs->sr_text = "invalid time-to-live for dynamicObject"; goto done; } if ( ttl <= 0 || ttl > di->di_max_ttl ) { /* FIXME: I don't understand if this has to be an error, * or an indication that the requested Ttl has been * shortened to di->di_max_ttl >= 1 day */ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit"; goto done; } entryTtl = ttl; bv_entryTtl.bv_len = mod->sml_values[ 0 ].bv_len; AC_MEMCPY( bv_entryTtl.bv_val, mod->sml_values[ 0 ].bv_val, bv_entryTtl.bv_len ); bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0'; break; case LDAP_MOD_INCREMENT: if ( BER_BVISEMPTY( &bv_entryTtl ) ) { rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { rs->sr_err = LDAP_NO_SUCH_OBJECT; } else { rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; rs->sr_text = "modify/increment: entryTtl: no such attribute"; } goto done; } entryTtl++; if ( entryTtl > DDS_RF2589_MAX_TTL ) { rs->sr_err = LDAP_PROTOCOL_ERROR; rs->sr_text = "invalid time-to-live for dynamicObject"; } else if ( entryTtl <= 0 || entryTtl > di->di_max_ttl ) { /* FIXME: I don't understand if this has to be an error, * or an indication that the requested Ttl has been * shortened to di->di_max_ttl >= 1 day */ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit"; } if ( rs->sr_err != LDAP_SUCCESS ) { rc = backend_attribute( op, NULL, &op->o_req_ndn, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); if ( rc == LDAP_INSUFFICIENT_ACCESS ) { rs->sr_text = NULL; rs->sr_err = LDAP_NO_SUCH_OBJECT; } goto done; } bv_entryTtl.bv_len = snprintf( textbuf, sizeof( textbuf ), "%ld", entryTtl ); break; default: assert( 0 ); break; } } else if ( mod->sml_desc == ad_entryExpireTimestamp ) { /* should have been trapped earlier */ assert( mod->sml_flags & SLAP_MOD_INTERNAL ); } } done:; if ( rs->sr_err == LDAP_SUCCESS ) { int rc; /* FIXME: this could be allowed when the Relax control is used... * in that case: * * TODO * * static => dynamic: * entryTtl must be provided; add * entryExpireTimestamp accordingly * * dynamic => static: * entryTtl must be removed; remove * entryTimestamp accordingly * * ... but we need to make sure that there are no subordinate * issues... */ rc = is_dynamicObject - was_dynamicObject; if ( rc ) { #if 0 /* fix subordinate issues first */ if ( get_relax( op ) ) { switch ( rc ) { case -1: /* need to delete entryTtl to have a consistent entry */ if ( entryTtl != -1 ) { rs->sr_text = "objectClass modification from dynamicObject to static entry requires entryTtl deletion"; rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; } break; case 1: /* need to add entryTtl to have a consistent entry */ if ( entryTtl <= 0 ) { rs->sr_text = "objectClass modification from static entry to dynamicObject requires entryTtl addition"; rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; } break; } } else #endif { switch ( rc ) { case -1: rs->sr_text = "objectClass modification cannot turn dynamicObject into static entry"; break; case 1: rs->sr_text = "objectClass modification cannot turn static entry into dynamicObject"; break; } rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; } if ( rc != LDAP_SUCCESS ) { rc = backend_attribute( op, NULL, &op->o_req_ndn, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); if ( rc == LDAP_INSUFFICIENT_ACCESS ) { rs->sr_text = NULL; rs->sr_err = LDAP_NO_SUCH_OBJECT; } } } } if ( rs->sr_err == LDAP_SUCCESS && entryTtl != 0 ) { Modifications *tmpmod = NULL, **modp; for ( modp = &op->orm_modlist; *modp; modp = &(*modp)->sml_next ) ; tmpmod = ch_calloc( 1, sizeof( Modifications ) ); tmpmod->sml_flags = SLAP_MOD_INTERNAL; tmpmod->sml_type = ad_entryExpireTimestamp->ad_cname; tmpmod->sml_desc = ad_entryExpireTimestamp; *modp = tmpmod; if ( entryTtl == -1 ) { /* delete entryExpireTimestamp */ tmpmod->sml_op = LDAP_MOD_DELETE; } else { time_t expire; char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; struct berval bv; /* keep entryExpireTimestamp consistent * with entryTtl */ expire = slap_get_time() + entryTtl; bv.bv_val = tsbuf; bv.bv_len = sizeof( tsbuf ); slap_timestamp( &expire, &bv ); tmpmod->sml_op = LDAP_MOD_REPLACE; value_add_one( &tmpmod->sml_values, &bv ); value_add_one( &tmpmod->sml_nvalues, &bv ); tmpmod->sml_numvals = 1; } } if ( rs->sr_err ) { op->o_bd->bd_info = (BackendInfo *)on->on_info; send_ldap_result( op, rs ); return rs->sr_err; } return SLAP_CB_CONTINUE; } static int dds_op_rename( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; dds_info_t *di = on->on_bi.bi_private; if ( DDS_OFF( di ) ) { return SLAP_CB_CONTINUE; } /* we don't allow dynamicObjects to have static subordinates */ if ( op->orr_nnewSup != NULL ) { Entry *e = NULL; BackendInfo *bi = op->o_bd->bd_info; int is_dynamicObject = 0, rc; rs->sr_err = LDAP_SUCCESS; op->o_bd->bd_info = (BackendInfo *)on->on_info; rc = be_entry_get_rw( op, &op->o_req_ndn, slap_schema.si_oc_dynamicObject, NULL, 0, &e ); if ( rc == LDAP_SUCCESS && e != NULL ) { be_entry_release_r( op, e ); e = NULL; is_dynamicObject = 1; } rc = be_entry_get_rw( op, op->orr_nnewSup, slap_schema.si_oc_dynamicObject, NULL, 0, &e ); if ( rc == LDAP_SUCCESS && e != NULL ) { if ( !is_dynamicObject ) { /* return referral only if "disclose" * is granted on the object */ if ( ! access_allowed( op, e, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) ) { rs->sr_err = LDAP_NO_SUCH_OBJECT; send_ldap_result( op, rs ); } else { send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, "static entry cannot have dynamicObject as newSuperior" ); } } be_entry_release_r( op, e ); } op->o_bd->bd_info = bi; if ( rs->sr_err != LDAP_SUCCESS ) { return rs->sr_err; } } return SLAP_CB_CONTINUE; } /* entryTtl update for client */ static int dds_response( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; dds_info_t *di = on->on_bi.bi_private; int rc; if ( !DDS_OFF( di ) && rs->sr_type == REP_SEARCH && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryTtl ) ) { BerVarray vals = NULL; struct lutil_tm tm; struct lutil_timet tt; char ttlbuf[STRLENOF("31557600") + 1]; struct berval ttlvalue; time_t ttl; int len; /* User already has access to entryTtl, skip ACL checks on * entryExpireTimestamp */ rc = backend_attribute( op, NULL, &rs->sr_entry->e_nname, ad_entryExpireTimestamp, &vals, ACL_NONE ); if ( rc != LDAP_SUCCESS ) { return rc; } assert( vals[0].bv_val[vals[0].bv_len] == '\0' ); if ( lutil_parsetime( vals[0].bv_val, &tm ) ) { goto done; } lutil_tm2time( &tm, &tt ); ttl = tt.tt_sec - op->o_time; ttl = (ttl < 0) ? 0 : ttl; assert( ttl <= DDS_RF2589_MAX_TTL ); len = snprintf( ttlbuf, sizeof(ttlbuf), "%ld", ttl ); if ( len < 0 ) { goto done; } ttlvalue.bv_val = ttlbuf; ttlvalue.bv_len = len; rs_entry2modifiable( op, rs, on ); if ( attr_delete( &rs->sr_entry->e_attrs, slap_schema.si_ad_entryTtl ) ) { goto done; } if ( attr_merge_normalize_one( rs->sr_entry, slap_schema.si_ad_entryTtl, &ttlvalue, op->o_tmpmemctx ) ) { goto done; } done:; ber_bvarray_free_x( vals, op->o_tmpmemctx ); } return SLAP_CB_CONTINUE; } static int slap_parse_refresh( struct berval *in, struct berval *ndn, time_t *ttl, const char **text, void *ctx ) { int rc = LDAP_SUCCESS; ber_tag_t tag; ber_len_t len = -1; BerElementBuffer berbuf; BerElement *ber = (BerElement *)&berbuf; struct berval reqdata = BER_BVNULL; int tmp; *text = NULL; if ( ndn ) { BER_BVZERO( ndn ); } if ( in == NULL || in->bv_len == 0 ) { *text = "empty request data field in refresh exop"; return LDAP_PROTOCOL_ERROR; } ber_dupbv_x( &reqdata, in, ctx ); /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ ber_init2( ber, &reqdata, 0 ); tag = ber_scanf( ber, "{" /*}*/ ); if ( tag == LBER_ERROR ) { Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, "slap_parse_refresh: decoding error.\n" ); goto decoding_error; } tag = ber_peek_tag( ber, &len ); if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_DN ) { Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, "slap_parse_refresh: decoding error.\n" ); goto decoding_error; } if ( ndn ) { struct berval dn; tag = ber_scanf( ber, "m", &dn ); if ( tag == LBER_ERROR ) { Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, "slap_parse_refresh: DN parse failed.\n" ); goto decoding_error; } rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx ); if ( rc != LDAP_SUCCESS ) { *text = "invalid DN in refresh exop request data"; goto done; } } else { tag = ber_scanf( ber, "x" /* "m" */ ); if ( tag == LBER_DEFAULT ) { goto decoding_error; } } tag = ber_peek_tag( ber, &len ); if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_TTL ) { Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, "slap_parse_refresh: decoding error.\n" ); goto decoding_error; } tag = ber_scanf( ber, "i", &tmp ); if ( tag == LBER_ERROR ) { Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, "slap_parse_refresh: TTL parse failed.\n" ); goto decoding_error; } if ( ttl ) { *ttl = tmp; } tag = ber_peek_tag( ber, &len ); if ( tag != LBER_DEFAULT || len != 0 ) { decoding_error:; Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, "slap_parse_refresh: decoding error, len=%ld\n", (long)len ); rc = LDAP_PROTOCOL_ERROR; *text = "data decoding error"; done:; if ( ndn && !BER_BVISNULL( ndn ) ) { slap_sl_free( ndn->bv_val, ctx ); BER_BVZERO( ndn ); } } if ( !BER_BVISNULL( &reqdata ) ) { ber_memfree_x( reqdata.bv_val, ctx ); } return rc; } static int dds_op_extended( Operation *op, SlapReply *rs ) { slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; dds_info_t *di = on->on_bi.bi_private; if ( DDS_OFF( di ) ) { return SLAP_CB_CONTINUE; } if ( bvmatch( &op->ore_reqoid, &slap_EXOP_REFRESH ) ) { Entry *e = NULL; time_t ttl; BackendDB db = *op->o_bd; SlapReply rs2 = { REP_RESULT }; Operation op2 = *op; slap_callback sc = { 0 }; Modifications ttlmod = { { 0 } }; struct berval ttlvalues[ 2 ]; char ttlbuf[STRLENOF("31557600") + 1]; rs->sr_err = slap_parse_refresh( op->ore_reqdata, NULL, &ttl, &rs->sr_text, NULL ); assert( rs->sr_err == LDAP_SUCCESS ); if ( ttl <= 0 || ttl > DDS_RF2589_MAX_TTL ) { rs->sr_err = LDAP_PROTOCOL_ERROR; rs->sr_text = "invalid time-to-live for dynamicObject"; return rs->sr_err; } if ( ttl > di->di_max_ttl ) { /* FIXME: I don't understand if this has to be an error, * or an indication that the requested Ttl has been * shortened to di->di_max_ttl >= 1 day */ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; rs->sr_text = "time-to-live for dynamicObject exceeds limit"; return rs->sr_err; } if ( di->di_min_ttl && ttl < di->di_min_ttl ) { ttl = di->di_min_ttl; } /* This does not apply to multi-provider case */ if ( !( !SLAP_SINGLE_SHADOW( op->o_bd ) || be_isupdate( op ) ) ) { /* we SHOULD return a referral in this case */ BerVarray defref = op->o_bd->be_update_refs ? op->o_bd->be_update_refs : default_referral; if ( defref != NULL ) { rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs, NULL, NULL, LDAP_SCOPE_DEFAULT ); if ( rs->sr_ref ) { rs->sr_flags |= REP_REF_MUSTBEFREED; } else { rs->sr_ref = defref; } rs->sr_err = LDAP_REFERRAL; } else { rs->sr_text = "shadow context; no update referral"; rs->sr_err = LDAP_UNWILLING_TO_PERFORM; } return rs->sr_err; } assert( !BER_BVISNULL( &op->o_req_ndn ) ); /* check if exists but not dynamicObject */ op->o_bd->bd_info = (BackendInfo *)on->on_info; rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, slap_schema.si_oc_dynamicObject, NULL, 0, &e ); if ( rs->sr_err != LDAP_SUCCESS ) { rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { /* return referral only if "disclose" * is granted on the object */ if ( ! access_allowed( op, e, slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) ) { rs->sr_err = LDAP_NO_SUCH_OBJECT; } else { rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; rs->sr_text = "refresh operation only applies to dynamic objects"; } be_entry_release_r( op, e ); } else { rs->sr_err = LDAP_NO_SUCH_OBJECT; } return rs->sr_err; } else if ( e != NULL ) { be_entry_release_r( op, e ); } /* we require manage privileges on the entryTtl, * and fake a Relax control */ op2.o_tag = LDAP_REQ_MODIFY; op2.o_bd = &db; db.bd_info = (BackendInfo *)on->on_info; op2.o_callback = ≻ sc.sc_response = slap_null_cb; op2.o_relax = SLAP_CONTROL_CRITICAL; op2.orm_modlist = &ttlmod; ttlmod.sml_op = LDAP_MOD_REPLACE; ttlmod.sml_flags = SLAP_MOD_MANAGING; ttlmod.sml_desc = slap_schema.si_ad_entryTtl; ttlmod.sml_values = ttlvalues; ttlmod.sml_numvals = 1; ttlvalues[ 0 ].bv_val = ttlbuf; ttlvalues[ 0 ].bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); BER_BVZERO( &ttlvalues[ 1 ] ); /* the entryExpireTimestamp is added by modify */ rs->sr_err = op2.o_bd->be_modify( &op2, &rs2 ); if ( ttlmod.sml_next != NULL ) { slap_mods_free( ttlmod.sml_next, 1 ); } if ( rs->sr_err == LDAP_SUCCESS ) { int rc; BerElementBuffer berbuf; BerElement *ber = (BerElement *)&berbuf; ber_init_w_nullc( ber, LBER_USE_DER ); rc = ber_printf( ber, "{tiN}", LDAP_TAG_EXOP_REFRESH_RES_TTL, (int)ttl ); if ( rc < 0 ) { rs->sr_err = LDAP_OTHER; rs->sr_text = "internal error"; } else { (void)ber_flatten( ber, &rs->sr_rspdata ); rs->sr_rspoid = ch_strdup( slap_EXOP_REFRESH.bv_val ); Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_INFO, "%s REFRESH dn=\"%s\" TTL=%ld\n", op->o_log_prefix, op->o_req_ndn.bv_val, ttl ); } ber_free_buf( ber ); } return rs->sr_err; } return SLAP_CB_CONTINUE; } enum { DDS_STATE = 1, DDS_MAXTTL, DDS_MINTTL, DDS_DEFAULTTTL, DDS_INTERVAL, DDS_TOLERANCE, DDS_MAXDYNAMICOBJS, DDS_LAST }; static ConfigDriver dds_cfgen; #if 0 static ConfigLDAPadd dds_ldadd; static ConfigCfAdd dds_cfadd; #endif static ConfigTable dds_cfg[] = { { "dds-state", "on|off", 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DDS_STATE, dds_cfgen, "( OLcfgOvAt:9.1 NAME 'olcDDSstate' " "DESC 'RFC2589 Dynamic directory services state' " "EQUALITY booleanMatch " "SYNTAX OMsBoolean " "SINGLE-VALUE )", NULL, NULL }, { "dds-max-ttl", "ttl", 2, 2, 0, ARG_MAGIC|DDS_MAXTTL, dds_cfgen, "( OLcfgOvAt:9.2 NAME 'olcDDSmaxTtl' " "DESC 'RFC2589 Dynamic directory services max TTL' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, { "dds-min-ttl", "ttl", 2, 2, 0, ARG_MAGIC|DDS_MINTTL, dds_cfgen, "( OLcfgOvAt:9.3 NAME 'olcDDSminTtl' " "DESC 'RFC2589 Dynamic directory services min TTL' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, { "dds-default-ttl", "ttl", 2, 2, 0, ARG_MAGIC|DDS_DEFAULTTTL, dds_cfgen, "( OLcfgOvAt:9.4 NAME 'olcDDSdefaultTtl' " "DESC 'RFC2589 Dynamic directory services default TTL' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, { "dds-interval", "interval", 2, 2, 0, ARG_MAGIC|DDS_INTERVAL, dds_cfgen, "( OLcfgOvAt:9.5 NAME 'olcDDSinterval' " "DESC 'RFC2589 Dynamic directory services expiration " "task run interval' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, { "dds-tolerance", "ttl", 2, 2, 0, ARG_MAGIC|DDS_TOLERANCE, dds_cfgen, "( OLcfgOvAt:9.6 NAME 'olcDDStolerance' " "DESC 'RFC2589 Dynamic directory services additional " "TTL in expiration scheduling' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString " "SINGLE-VALUE )", NULL, NULL }, { "dds-max-dynamicObjects", "num", 2, 2, 0, ARG_MAGIC|ARG_INT|DDS_MAXDYNAMICOBJS, dds_cfgen, "( OLcfgOvAt:9.7 NAME 'olcDDSmaxDynamicObjects' " "DESC 'RFC2589 Dynamic directory services max number of dynamic objects' " "EQUALITY integerMatch " "SYNTAX OMsInteger " "SINGLE-VALUE )", NULL, NULL }, { NULL, NULL, 0, 0, 0, ARG_IGNORED } }; static ConfigOCs dds_ocs[] = { { "( OLcfgOvOc:9.1 " "NAME 'olcDDSConfig' " "DESC 'RFC2589 Dynamic directory services configuration' " "SUP olcOverlayConfig " "MAY ( " "olcDDSstate " "$ olcDDSmaxTtl " "$ olcDDSminTtl " "$ olcDDSdefaultTtl " "$ olcDDSinterval " "$ olcDDStolerance " "$ olcDDSmaxDynamicObjects " " ) " ")", Cft_Overlay, dds_cfg, NULL, NULL /* dds_cfadd */ }, { NULL, 0, NULL } }; #if 0 static int dds_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) { return LDAP_SUCCESS; } static int dds_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) { return 0; } #endif static int dds_cfgen( ConfigArgs *c ) { slap_overinst *on = (slap_overinst *)c->bi; dds_info_t *di = on->on_bi.bi_private; int rc = 0; unsigned long t; if ( c->op == SLAP_CONFIG_EMIT ) { char buf[ SLAP_TEXT_BUFLEN ]; struct berval bv; switch( c->type ) { case DDS_STATE: c->value_int = !DDS_OFF( di ); break; case DDS_MAXTTL: lutil_unparse_time( buf, sizeof( buf ), di->di_max_ttl ); ber_str2bv( buf, 0, 0, &bv ); value_add_one( &c->rvalue_vals, &bv ); break; case DDS_MINTTL: if ( di->di_min_ttl ) { lutil_unparse_time( buf, sizeof( buf ), di->di_min_ttl ); ber_str2bv( buf, 0, 0, &bv ); value_add_one( &c->rvalue_vals, &bv ); } else { rc = 1; } break; case DDS_DEFAULTTTL: if ( di->di_default_ttl ) { lutil_unparse_time( buf, sizeof( buf ), di->di_default_ttl ); ber_str2bv( buf, 0, 0, &bv ); value_add_one( &c->rvalue_vals, &bv ); } else { rc = 1; } break; case DDS_INTERVAL: if ( di->di_interval ) { lutil_unparse_time( buf, sizeof( buf ), di->di_interval ); ber_str2bv( buf, 0, 0, &bv ); value_add_one( &c->rvalue_vals, &bv ); } else { rc = 1; } break; case DDS_TOLERANCE: if ( di->di_tolerance ) { lutil_unparse_time( buf, sizeof( buf ), di->di_tolerance ); ber_str2bv( buf, 0, 0, &bv ); value_add_one( &c->rvalue_vals, &bv ); } else { rc = 1; } break; case DDS_MAXDYNAMICOBJS: if ( di->di_max_dynamicObjects > 0 ) { c->value_int = di->di_max_dynamicObjects; } else { rc = 1; } break; default: rc = 1; break; } return rc; } else if ( c->op == LDAP_MOD_DELETE ) { switch( c->type ) { case DDS_STATE: di->di_flags &= ~DDS_FOFF; break; case DDS_MAXTTL: di->di_min_ttl = DDS_RF2589_DEFAULT_TTL; break; case DDS_MINTTL: di->di_min_ttl = 0; break; case DDS_DEFAULTTTL: di->di_default_ttl = 0; break; case DDS_INTERVAL: di->di_interval = 0; break; case DDS_TOLERANCE: di->di_tolerance = 0; break; case DDS_MAXDYNAMICOBJS: di->di_max_dynamicObjects = 0; break; default: rc = 1; break; } return rc; } switch ( c->type ) { case DDS_STATE: if ( c->value_int ) { di->di_flags &= ~DDS_FOFF; } else { di->di_flags |= DDS_FOFF; } break; case DDS_MAXTTL: if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg), "DDS unable to parse dds-max-ttl \"%s\"", c->argv[ 1 ] ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DDS invalid dds-max-ttl=%lu; must be between %d and %d", t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } di->di_max_ttl = (time_t)t; break; case DDS_MINTTL: if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg), "DDS unable to parse dds-min-ttl \"%s\"", c->argv[ 1 ] ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t > DDS_RF2589_MAX_TTL ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DDS invalid dds-min-ttl=%lu", t ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t == 0 ) { di->di_min_ttl = DDS_RF2589_DEFAULT_TTL; } else { di->di_min_ttl = (time_t)t; } break; case DDS_DEFAULTTTL: if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg), "DDS unable to parse dds-default-ttl \"%s\"", c->argv[ 1 ] ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t > DDS_RF2589_MAX_TTL ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DDS invalid dds-default-ttl=%lu", t ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t == 0 ) { di->di_default_ttl = DDS_RF2589_DEFAULT_TTL; } else { di->di_default_ttl = (time_t)t; } break; case DDS_INTERVAL: if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg), "DDS unable to parse dds-interval \"%s\"", c->argv[ 1 ] ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t <= 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DDS invalid dds-interval=%lu", t ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t < 60 ) { Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, "%s: dds-interval=%lu may be too small.\n", c->log, t ); } di->di_interval = (time_t)t; if ( di->di_expire_task ) { ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) { ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task ); } di->di_expire_task->interval.tv_sec = DDS_INTERVAL( di ); ldap_pvt_runqueue_resched( &slapd_rq, di->di_expire_task, 0 ); ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); } break; case DDS_TOLERANCE: if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg), "DDS unable to parse dds-tolerance \"%s\"", c->argv[ 1 ] ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( t > DDS_RF2589_MAX_TTL ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DDS invalid dds-tolerance=%lu", t ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } di->di_tolerance = (time_t)t; break; case DDS_MAXDYNAMICOBJS: if ( c->value_int < 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DDS invalid dds-max-dynamicObjects=%d", c->value_int ); Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "%s: %s.\n", c->log, c->cr_msg ); return 1; } di->di_max_dynamicObjects = c->value_int; break; default: rc = 1; break; } return rc; } static int dds_db_init( BackendDB *be, ConfigReply *cr) { slap_overinst *on = (slap_overinst *)be->bd_info; dds_info_t *di; BackendInfo *bi = on->on_info->oi_orig; if ( SLAP_ISGLOBALOVERLAY( be ) ) { Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS cannot be used as global overlay.\n" ); return 1; } /* check support for required functions */ /* FIXME: some could be provided by other overlays in between */ if ( bi->bi_op_add == NULL /* object creation */ || bi->bi_op_delete == NULL /* object deletion */ || bi->bi_op_modify == NULL /* object refresh */ || bi->bi_op_search == NULL /* object expiration */ || bi->bi_entry_get_rw == NULL ) /* object type/existence checking */ { Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS backend \"%s\" does not provide " "required functionality.\n", bi->bi_type ); return 1; } di = (dds_info_t *)ch_calloc( 1, sizeof( dds_info_t ) ); on->on_bi.bi_private = di; di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; ldap_pvt_thread_mutex_init( &di->di_mutex ); SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_DYNAMIC; return 0; } /* adds dynamicSubtrees to root DSE */ static int dds_entry_info( void *arg, Entry *e ) { dds_info_t *di = (dds_info_t *)arg; attr_merge( e, slap_schema.si_ad_dynamicSubtrees, di->di_suffix, di->di_nsuffix ); return 0; } /* callback that counts the returned entries, since the search * does not get to the point in slap_send_search_entries where * the actual count occurs */ static int dds_count_cb( Operation *op, SlapReply *rs ) { int *nump = (int *)op->o_callback->sc_private; switch ( rs->sr_type ) { case REP_SEARCH: (*nump)++; break; case REP_SEARCHREF: case REP_RESULT: break; default: assert( 0 ); } return 0; } /* count dynamic objects existing in the database at startup */ static int dds_count( void *ctx, BackendDB *be ) { slap_overinst *on = (slap_overinst *)be->bd_info; dds_info_t *di = (dds_info_t *)on->on_bi.bi_private; Connection conn = { 0 }; OperationBuffer opbuf; Operation *op; slap_callback sc = { 0 }; SlapReply rs = { REP_RESULT }; int rc; char *extra = ""; connection_fake_init2( &conn, &opbuf, ctx, 0 ); op = &opbuf.ob_op; op->o_tag = LDAP_REQ_SEARCH; memset( &op->oq_search, 0, sizeof( op->oq_search ) ); op->o_bd = be; op->o_req_dn = op->o_bd->be_suffix[ 0 ]; op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; op->o_dn = op->o_bd->be_rootdn; op->o_ndn = op->o_bd->be_rootndn; op->ors_scope = LDAP_SCOPE_SUBTREE; op->ors_tlimit = SLAP_NO_LIMIT; op->ors_slimit = SLAP_NO_LIMIT; op->ors_attrs = slap_anlist_no_attrs; op->o_do_not_cache = 1; op->ors_filterstr.bv_len = STRLENOF( "(objectClass=" ")" ) + slap_schema.si_oc_dynamicObject->soc_cname.bv_len; op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1, "(objectClass=%s)", slap_schema.si_oc_dynamicObject->soc_cname.bv_val ); op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); if ( op->ors_filter == NULL ) { rs.sr_err = LDAP_OTHER; goto done_search; } op->o_callback = ≻ sc.sc_response = dds_count_cb; sc.sc_private = &di->di_num_dynamicObjects; di->di_num_dynamicObjects = 0; op->o_bd->bd_info = (BackendInfo *)on->on_info; (void)op->o_bd->bd_info->bi_op_search( op, &rs ); op->o_bd->bd_info = (BackendInfo *)on; done_search:; op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); filter_free_x( op, op->ors_filter, 1 ); rc = rs.sr_err; switch ( rs.sr_err ) { case LDAP_SUCCESS: Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, "DDS non-expired=%d\n", di->di_num_dynamicObjects ); break; case LDAP_NO_SUCH_OBJECT: /* (ITS#5267) database not created yet? */ rs.sr_err = LDAP_SUCCESS; extra = " (ignored)"; /* fallthru */ default: Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS non-expired objects lookup failed err=%d%s\n", rc, extra ); break; } return rs.sr_err; } static int dds_db_open( BackendDB *be, ConfigReply *cr ) { slap_overinst *on = (slap_overinst *)be->bd_info; dds_info_t *di = on->on_bi.bi_private; int rc = 0; void *thrctx = ldap_pvt_thread_pool_context(); if ( slapMode & SLAP_TOOL_MODE ) return 0; if ( DDS_OFF( di ) ) { goto done; } if ( SLAP_SINGLE_SHADOW( be ) ) { Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS incompatible with shadow database \"%s\".\n", be->be_suffix[ 0 ].bv_val ); return 1; } if ( di->di_max_ttl == 0 ) { di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; } if ( di->di_min_ttl == 0 ) { di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; } di->di_suffix = be->be_suffix; di->di_nsuffix = be->be_nsuffix; /* count the dynamic objects first */ rc = dds_count( thrctx, be ); if ( rc != LDAP_SUCCESS ) { rc = 1; goto done; } /* start expire task */ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); di->di_expire_task = ldap_pvt_runqueue_insert( &slapd_rq, DDS_INTERVAL( di ), dds_expire_fn, di, "dds_expire_fn", be->be_suffix[ 0 ].bv_val ); ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); /* register dinamicSubtrees root DSE info support */ rc = entry_info_register( dds_entry_info, (void *)di ); done:; return rc; } static int dds_db_close( BackendDB *be, ConfigReply *cr ) { slap_overinst *on = (slap_overinst *)be->bd_info; dds_info_t *di = on->on_bi.bi_private; /* stop expire task */ if ( di && di->di_expire_task ) { ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) { ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task ); } ldap_pvt_runqueue_remove( &slapd_rq, di->di_expire_task ); ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); di->di_expire_task = NULL; } (void)entry_info_unregister( dds_entry_info, (void *)di ); return 0; } static int dds_db_destroy( BackendDB *be, ConfigReply *cr ) { slap_overinst *on = (slap_overinst *)be->bd_info; dds_info_t *di = on->on_bi.bi_private; if ( di != NULL ) { ldap_pvt_thread_mutex_destroy( &di->di_mutex ); free( di ); } return 0; } static int slap_exop_refresh( Operation *op, SlapReply *rs ) { BackendDB *bd = op->o_bd; rs->sr_err = slap_parse_refresh( op->ore_reqdata, &op->o_req_ndn, NULL, &rs->sr_text, op->o_tmpmemctx ); if ( rs->sr_err != LDAP_SUCCESS ) { return rs->sr_err; } Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, "%s REFRESH dn=\"%s\"\n", op->o_log_prefix, op->o_req_ndn.bv_val ); op->o_req_dn = op->o_req_ndn; op->o_bd = select_backend( &op->o_req_ndn, 0 ); if ( op->o_bd == NULL ) { send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT, "no global superior knowledge" ); goto done; } if ( !SLAP_DYNAMIC( op->o_bd ) ) { send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, "backend does not support dynamic directory services" ); goto done; } rs->sr_err = backend_check_restrictions( op, rs, (struct berval *)&slap_EXOP_REFRESH ); if ( rs->sr_err != LDAP_SUCCESS ) { goto done; } if ( op->o_bd->be_extended == NULL ) { send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, "backend does not support extended operations" ); goto done; } op->o_bd->be_extended( op, rs ); done:; if ( !BER_BVISNULL( &op->o_req_ndn ) ) { op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); BER_BVZERO( &op->o_req_ndn ); BER_BVZERO( &op->o_req_dn ); } op->o_bd = bd; return rs->sr_err; } static slap_overinst dds; static int do_not_load_exop; static int do_not_replace_exop; static int do_not_load_schema; #if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC static #endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */ int dds_initialize() { int rc = 0; int i, code; /* Make sure we don't exceed the bits reserved for userland */ config_check_userland( DDS_LAST ); if ( !do_not_load_schema ) { static struct { char *desc; slap_mask_t flags; AttributeDescription **ad; } s_at[] = { { "( 1.3.6.1.4.1.4203.666.1.57 " "NAME ( 'entryExpireTimestamp' ) " "DESC 'RFC2589 OpenLDAP extension: expire time of a dynamic object, " "computed as now + entryTtl' " "EQUALITY generalizedTimeMatch " "ORDERING generalizedTimeOrderingMatch " "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " "SINGLE-VALUE " "NO-USER-MODIFICATION " "USAGE dSAOperation )", SLAP_AT_HIDE, &ad_entryExpireTimestamp }, { NULL } }; for ( i = 0; s_at[ i ].desc != NULL; i++ ) { code = register_at( s_at[ i ].desc, s_at[ i ].ad, 0 ); if ( code ) { Debug( LDAP_DEBUG_ANY, "dds_initialize: register_at failed\n" ); return code; } (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; } } if ( !do_not_load_exop ) { rc = load_extop2( (struct berval *)&slap_EXOP_REFRESH, SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, slap_exop_refresh, !do_not_replace_exop ); if ( rc != LDAP_SUCCESS ) { Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS unable to register refresh exop: %d.\n", rc ); return rc; } } dds.on_bi.bi_type = "dds"; dds.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; dds.on_bi.bi_db_init = dds_db_init; dds.on_bi.bi_db_open = dds_db_open; dds.on_bi.bi_db_close = dds_db_close; dds.on_bi.bi_db_destroy = dds_db_destroy; dds.on_bi.bi_op_add = dds_op_add; dds.on_bi.bi_op_delete = dds_op_delete; dds.on_bi.bi_op_modify = dds_op_modify; dds.on_bi.bi_op_modrdn = dds_op_rename; dds.on_bi.bi_extended = dds_op_extended; dds.on_response = dds_response; dds.on_bi.bi_cf_ocs = dds_ocs; rc = config_register_schema( dds_cfg, dds_ocs ); if ( rc ) { return rc; } return overlay_register( &dds ); } #if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC int init_module( int argc, char *argv[] ) { int i; for ( i = 0; i < argc; i++ ) { char *arg = argv[ i ]; int no = 0; if ( strncasecmp( arg, "no-", STRLENOF( "no-" ) ) == 0 ) { arg += STRLENOF( "no-" ); no = 1; } if ( strcasecmp( arg, "exop" ) == 0 ) { do_not_load_exop = no; } else if ( strcasecmp( arg, "replace" ) == 0 ) { do_not_replace_exop = no; } else if ( strcasecmp( arg, "schema" ) == 0 ) { do_not_load_schema = no; } else { Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "DDS unknown module arg[#%d]=\"%s\".\n", i, argv[ i ] ); return 1; } } return dds_initialize(); } #endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */ #endif /* defined(SLAPD_OVER_DDS) */