From 339b9c371ec07b5125fed23e4bac1cdca2790525 Mon Sep 17 00:00:00 2001 From: Pierangelo Masarati Date: Fri, 6 Jan 2006 17:46:52 +0000 Subject: [PATCH] rfc2589 support (ITS#4293) --- clients/tools/Makefile.in | 19 +- clients/tools/ldapexop.c | 308 +++++ configure.in | 17 + doc/man/man5/slapo-dds.5 | 272 ++++ include/ldap.h | 34 + libraries/libldap/Makefile.in | 4 +- libraries/libldap/dds.c | 158 +++ libraries/libldap_r/Makefile.in | 4 +- servers/slapd/bconfig.c | 1 + servers/slapd/controls.c | 2 + servers/slapd/overlays/Makefile.in | 4 + servers/slapd/overlays/dds.c | 1992 ++++++++++++++++++++++++++++ servers/slapd/schema_prep.c | 2 +- servers/slapd/slap.h | 3 +- tests/data/dds.out | 70 + tests/data/slapd-dds.conf | 88 ++ tests/run.in | 2 + tests/scripts/conf.sh | 1 + tests/scripts/defines.sh | 4 + tests/scripts/test046-dds | 529 ++++++++ 20 files changed, 3504 insertions(+), 10 deletions(-) create mode 100644 clients/tools/ldapexop.c create mode 100644 doc/man/man5/slapo-dds.5 create mode 100644 libraries/libldap/dds.c create mode 100644 servers/slapd/overlays/dds.c create mode 100644 tests/data/dds.out create mode 100644 tests/data/slapd-dds.conf create mode 100755 tests/scripts/test046-dds diff --git a/clients/tools/Makefile.in b/clients/tools/Makefile.in index a51a47af6c..2c10318337 100644 --- a/clients/tools/Makefile.in +++ b/clients/tools/Makefile.in @@ -14,9 +14,11 @@ ## . SRCS = ldapsearch.c ldapmodify.c ldapdelete.c ldapmodrdn.c \ - ldappasswd.c ldapwhoami.c ldapcompare.c common.c + ldappasswd.c ldapwhoami.c ldapcompare.c \ + ldapexop.c common.c OBJS = ldapsearch.o ldapmodify.o ldapdelete.o ldapmodrdn.o \ - ldappasswd.o ldapwhoami.o ldapcompare.o common.o + ldappasswd.o ldapwhoami.o ldapcompare.o \ + ldapexop.o common.o LDAP_INCDIR= ../../include LDAP_LIBDIR= ../../libraries @@ -27,10 +29,10 @@ XLIBS = $(LDAP_L) XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS) XSRCS = ldsversion.c ldmversion.c lddversion.c ldrversion.c \ - ldpversion.c ldwversion.c ldcversion.c + ldpversion.c ldwversion.c ldcversion.c ldeversion.c PROGRAMS = ldapsearch ldapmodify ldapdelete ldapmodrdn \ - ldappasswd ldapwhoami ldapcompare + ldappasswd ldapwhoami ldapcompare ldapexop ldapsearch: ldsversion.o @@ -54,6 +56,9 @@ ldapwhoami: ldwversion.o ldapcompare: ldcversion.o $(LTLINK) -o $@ ldapcompare.o common.o ldcversion.o $(LIBS) +ldapexop: ldeversion.o + $(LTLINK) -o $@ ldapexop.o common.o ldeversion.o $(LIBS) + ldsversion.c: Makefile @-$(RM) $@ $(MKVERSION) $(MKVOPTS) ldapsearch > $@ @@ -96,6 +101,12 @@ ldcversion.c: Makefile ldcversion.o: ldapcompare.o common.o $(XLIBS) +ldeversion.c: Makefile + @-$(RM) $@ + $(MKVERSION) $(MKVOPTS) ldapexop > $@ + +ldeversion.o: ldapexop.o common.o $(XLIBS) + install-local: FORCE -$(MKDIR) $(DESTDIR)$(bindir) @( \ diff --git a/clients/tools/ldapexop.c b/clients/tools/ldapexop.c new file mode 100644 index 0000000000..9886d2d01e --- /dev/null +++ b/clients/tools/ldapexop.c @@ -0,0 +1,308 @@ +/* ldapexop.c -- a tool for performing well-known extended operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2005 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 originally developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software based, in part, on other client tools. + */ + +#include "portable.h" + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include "lutil.h" +#include "lutil_ldap.h" +#include "ldap_defaults.h" + +#include "common.h" + + +void +usage( void ) +{ + fprintf( stderr, _("Issue LDAP extended operations\n\n")); + fprintf( stderr, _("usage: %s [options]\n"), prog); + tool_common_usage(); + exit( EXIT_FAILURE ); +} + + +const char options[] = "" + "d:D:e:h:H:InO:p:QR:U:vVw:WxX:y:Y:Z"; + +int +handle_private_option( int i ) +{ + switch ( i ) { + default: + return 0; + } + return 1; +} + + +int +main( int argc, char *argv[] ) +{ + int rc; + char *user = NULL; + + LDAP *ld = NULL; + + char *matcheddn = NULL, *text = NULL, **refs = NULL; + int id, code; + LDAPMessage *res; + + tool_init(); + prog = lutil_progname( "ldapexop", argc, argv ); + + /* LDAPv3 only */ + protocol = LDAP_VERSION3; + + tool_args( argc, argv ); + + if ( argc - optind < 1 ) { + usage(); + } + + if ( pw_file || want_bindpw ) { + if ( pw_file ) { + rc = lutil_get_filed_password( pw_file, &passwd ); + if( rc ) return EXIT_FAILURE; + } else { + passwd.bv_val = getpassphrase( _("Enter LDAP Password: ") ); + passwd.bv_len = passwd.bv_val ? strlen( passwd.bv_val ) : 0; + } + } + + ld = tool_conn_setup( 0, 0 ); + + tool_bind( ld ); + + argv += optind; + argc -= optind; + + if ( strcasecmp( argv[ 0 ], "whoami" ) == 0 ) { + switch ( argc ) { + case 2: + user = argv[ 1 ]; + + case 1: + break; + + default: + fprintf( stderr, "need [user]\n\n" ); + usage(); + } + + if ( assertion || manageDSAit || noop ) { + fprintf( stderr, _("controls incompatible with WhoAmI exop\n\n") ); + usage(); + } + + if ( authzid ) { + tool_server_controls( ld, NULL, 0 ); + } + + rc = ldap_whoami( ld, NULL, NULL, &id ); + if ( rc != LDAP_SUCCESS ) { + tool_perror( "ldap_extended_operation", rc, NULL, NULL, NULL, NULL ); + rc = EXIT_FAILURE; + goto skip; + } + + } else if ( strcasecmp( argv[ 0 ], "cancel" ) == 0 ) { + int cancelid; + + switch ( argc ) { + case 2: + if ( lutil_atoi( &cancelid, argv[ 1 ] ) != 0 || cancelid < 0 ) { + fprintf( stderr, "invalid cancelid=%s\n\n", argv[ 1 ] ); + usage(); + } + break; + + default: + fprintf( stderr, "need cancelid\n\n" ); + usage(); + } + + rc = ldap_cancel( ld, cancelid, NULL, NULL, &id ); + if ( rc != LDAP_SUCCESS ) { + tool_perror( "ldap_cancel", rc, NULL, NULL, NULL, NULL ); + rc = EXIT_FAILURE; + goto skip; + } + + } else if ( strcasecmp( argv[ 0 ], "passwd" ) == 0 ) { + /* do we need this? */ + + } else if ( strcasecmp( argv[ 0 ], "refresh" ) == 0 ) { + int ttl = 3600; + struct berval dn; + + switch ( argc ) { + case 3: + ttl = atoi( argv[ 2 ] ); + + case 2: + dn.bv_val = argv[ 1 ]; + dn.bv_len = strlen( dn.bv_val ); + + case 1: + break; + + default: + fprintf( stderr, _("need DN [ttl]\n\n") ); + usage(); + } + + if ( assertion || manageDSAit || noop || authzid ) { + tool_server_controls( ld, NULL, 0 ); + } + + rc = ldap_refresh( ld, &dn, ttl, NULL, NULL, &id ); + if ( rc != LDAP_SUCCESS ) { + tool_perror( "ldap_extended_operation", rc, NULL, NULL, NULL, NULL ); + rc = EXIT_FAILURE; + goto skip; + } + + } else if ( tool_is_oid( argv[ 0 ] ) ) { + + } else { + fprintf( stderr, "unknown exop \"%s\"\n\n", argv[ 0 ] ); + usage(); + } + + for ( ; ; ) { + struct timeval tv; + + if ( tool_check_abandon( ld, id ) ) { + return LDAP_CANCELLED; + } + + tv.tv_sec = 0; + tv.tv_usec = 100000; + + rc = ldap_result( ld, LDAP_RES_ANY, LDAP_MSG_ALL, &tv, &res ); + if ( rc < 0 ) { + tool_perror( "ldap_result", rc, NULL, NULL, NULL, NULL ); + rc = EXIT_FAILURE; + goto skip; + } + + if ( rc != 0 ) { + break; + } + } + + rc = ldap_parse_result( ld, res, + &code, &matcheddn, &text, &refs, NULL, 0 ); + if ( rc == LDAP_SUCCESS ) { + rc = code; + } + + if ( rc != LDAP_SUCCESS ) { + tool_perror( "ldap_parse_result", rc, NULL, matcheddn, text, refs ); + rc = EXIT_FAILURE; + goto skip; + } + + if ( strcasecmp( argv[ 0 ], "whoami" ) == 0 ) { + char *retoid = NULL; + struct berval *retdata = NULL; + + rc = ldap_parse_extended_result( ld, res, &retoid, &retdata, 1 ); + + if ( rc != LDAP_SUCCESS ) { + tool_perror( "ldap_parse_extended_result", rc, NULL, NULL, NULL, NULL ); + rc = EXIT_FAILURE; + goto skip; + } + + if ( retdata != NULL ) { + if ( retdata->bv_len == 0 ) { + printf(_("anonymous\n") ); + } else { + printf("%s\n", retdata->bv_val ); + } + } + + ber_memfree( retoid ); + ber_bvfree( retdata ); + + } else if ( strcasecmp( argv[ 0 ], "cancel" ) == 0 ) { + /* no extended response; returns specific errors */ + assert( 0 ); + + } else if ( strcasecmp( argv[ 0 ], "passwd" ) == 0 ) { + + } else if ( strcasecmp( argv[ 0 ], "refresh" ) == 0 ) { + int newttl; + + rc = ldap_parse_refresh( ld, res, &newttl ); + + if ( rc != LDAP_SUCCESS ) { + tool_perror( "ldap_parse_refresh", rc, NULL, NULL, NULL, NULL ); + rc = EXIT_FAILURE; + goto skip; + } + + printf( "newttl=%d\n", newttl ); + + } else if ( tool_is_oid( argv[ 0 ] ) ) { + /* ... */ + } + + if( verbose || ( code != LDAP_SUCCESS ) || matcheddn || text || refs ) { + printf( _("Result: %s (%d)\n"), ldap_err2string( code ), code ); + + if( text && *text ) { + printf( _("Additional info: %s\n"), text ); + } + + if( matcheddn && *matcheddn ) { + printf( _("Matched DN: %s\n"), matcheddn ); + } + + if( refs ) { + int i; + for( i=0; refs[i]; i++ ) { + printf(_("Referral: %s\n"), refs[i] ); + } + } + } + + ber_memfree( text ); + ber_memfree( matcheddn ); + ber_memvfree( (void **) refs ); + +skip: + /* disconnect from server */ + tool_unbind( ld ); + tool_destroy(); + + return code == LDAP_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/configure.in b/configure.in index d942521e2f..712ab422c9 100644 --- a/configure.in +++ b/configure.in @@ -339,6 +339,7 @@ OL_ARG_ENABLE(sql,[ --enable-sql enable sql backend], dnl ---------------------------------------------------------------- dnl SLAPD Overlay Options Overlays="accesslog \ + dds \ denyop \ dyngroup \ dynlist \ @@ -360,6 +361,8 @@ OL_ARG_ENABLE(overlays,[ --enable-overlays enable all available overlays], --, [no yes mod])dnl OL_ARG_ENABLE(accesslog,[ --enable-accesslog In-Directory Access Logging overlay], no, [no yes mod], ol_enable_overlays) +OL_ARG_ENABLE(dds,[ --enable-dds Dynamic Directory Services overlay], + no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(denyop,[ --enable-denyop Deny Operation overlay], no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(dyngroup,[ --enable-dyngroup Dynamic Group overlay], @@ -614,6 +617,7 @@ BUILD_SHELL=no BUILD_SQL=no BUILD_ACCESSLOG=no +BUILD_DDS=no BUILD_DENYOP=no BUILD_DYNGROUP=no BUILD_DYNLIST=no @@ -2913,6 +2917,18 @@ if test "$ol_enable_accesslog" != no ; then AC_DEFINE_UNQUOTED(SLAPD_OVER_ACCESSLOG,$MFLAG,[define for In-Directory Access Logging overlay]) fi +if test "$ol_enable_dds" != no ; then + BUILD_DDS=$ol_enable_dds + if test "$ol_enable_dds" = mod ; then + MFLAG=SLAPD_MOD_DYNAMIC + SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS dds.la" + else + MFLAG=SLAPD_MOD_STATIC + SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS dds.o" + fi + AC_DEFINE_UNQUOTED(SLAPD_OVER_DDS,$MFLAG,[define for Dynamic Directory Services overlay]) +fi + if test "$ol_enable_denyop" != no ; then BUILD_DENYOP=$ol_enable_denyop if test "$ol_enable_denyop" = mod ; then @@ -3130,6 +3146,7 @@ dnl backends AC_SUBST(BUILD_SQL) dnl overlays AC_SUBST(BUILD_ACCESSLOG) + AC_SUBST(BUILD_DDS) AC_SUBST(BUILD_DENYOP) AC_SUBST(BUILD_DYNGROUP) AC_SUBST(BUILD_DYNLIST) diff --git a/doc/man/man5/slapo-dds.5 b/doc/man/man5/slapo-dds.5 new file mode 100644 index 0000000000..836a9032a5 --- /dev/null +++ b/doc/man/man5/slapo-dds.5 @@ -0,0 +1,272 @@ +.TH SLAPO-DDS 5 "RELEASEDATE" "OpenLDAP LDVERSION" +.\" Copyright 2005-2006 The OpenLDAP Foundation, All Rights Reserved. +.\" Copying restrictions apply. See the COPYRIGHT file. +.\" $OpenLDAP$ +.SH NAME +slapo-dds \- dds overlay +.SH SYNOPSIS +ETCDIR/slapd.conf +.SH DESCRIPTION +The +.B dds +overlay to +.BR slapd (8) +implements dynamic objects as per RFC 2589. +The name +.B dds +stands for +Dynamic Dyrectory Services. +It allows to define dynamic objects, characterized by the +.B dynamicObject +objectClass. +Dynamic objects have a limited life, determined by a time-to-live (TTL) +that can be refreshed by means of a specific +.B refresh +extended operation. +This operation allows to set the Client Refresh Period (CRP), +namely the period between refreshes that is required to preserve the +dynamic object from expiration. +The expiration time is computed by adding the requested TTL to the +current time. +When dynamic objects reach the end of their life without being +further refreshed, they are automatically deleted; there is no guarantee +of immediate deletion, but clients should not count over it. +Dynamic objects can have subordinates, provided they also are dynamic +objects. +RFC 2589 does not specify what should the behavior of a dynamic +directory service be when a dynamic object with (dynamic) subordinates +expires. +In this implementation, the life of dynamic objects with subordinates +is prolonged until all the dynamic subordinates expired. + + +This +.BR slapd.conf (5) +directive adds the +.B dds +overlay to the current database: + +.TP +.B overlay dds + +.LP +The +.B dds +overlay may be used with any backend that implements the +.BR add , +.BR modify , +.BR search , +and +.BR delete +operations. +Since its use may result in many internal entry lookups, adds +and deletes, it should be best used in conjunction with backends +that have resonably good write performances. + +.LP +The config directives that are specific to the +.B dds +overlay are prefixed by +.BR dds\- , +to avoid potential conflicts with directives specific to the underlying +database or to other stacked overlays. + +.TP +.B dds\-max\-ttl +Specifies the max TTL value; this is the default TTL newly created +dynamic objects receive, unless +.B dds\-default\-ttl +is set. +When the client with a refresh exop requests a TTL higher than it, +sizeLimitExceeded is returned. +This value must be between 86400 (1 day, the default) and 31557600 +(1 year plus 6 hours, as per RFC 2589). + +.TP +.B dds\-min\-ttl +Specifies the min TTL value; clients requesting a lower TTL by means +of the refresh exop actually obtain this value as CRP. +If set to 0 (the default), no lower limit is set. + +.TP +.B dds\-default\-ttl +Specifies the default TTL value that newly created dynamic objects get. +If set to 0 (the default), the +.B dds\-max\-ttl +is used. + +.TP +.B dds\-interval +Specifies the interval between expiration checks; efaults to 1 hour. + +.TP +.B dds\-tolerance +Specifies an extra time that is added to the timer that actually wakes up +the thread that will delete an expired dynamic object. +So the nominal life of the entry is that specified in the +.B entryTtl +attribute, but its life will actually be +.BR " entryTtl + tolerance " . +Note that there is no guarantee that the life of a dynamic object will be +.I exactly +the requested TTL; due to implementation details, it may be longer, which +is allowed by RFC 2589. +By default, tolerance is 0. + +.TP +.B dds\-max\-dynamicObjects +Specifies the maximum number of dynamic objects that can simultaneously exist +within a naming context. +This allows to limit the amount of resources (mostly in terms of runqueue size) +that are used by dynamic objects. +By default, no limit is set. + +.TP +.B dds-state {TRUE|false} +Specifies if the Dynamic Directory Services feature is enabled or not. +By default it is; however, a proxy does not need to keep track of dynamic +objects itself, it only needs to inform the frontend that support for +dynamic objects is available. + +.SH ACCESS CONTROL +The +.B dds +overlay restricts the refresh operation by requiring +.B manage +access to the +.B entryTtl +attribute (see +.BR slapd.access (5) +for details about the +.B manage +access privilege). +Since the +.B entryTtl +is an operational, NO-USER-MODIFICATION attribute, no direct write access +to it is possible. +So the +.B dds +overlay turns refresh exops into an internal modification to the value +of the +.B entryTtl +attribute with the +.B manageDIT +control set. + +RFC 2589 recommends that anonymous clients should not be allowed to refresh +a dynamic object. +This cn be implemented by appropriately crafting access control to obtain +the desired effect. + +Example: restrict refresh to authenticated clients + +.RS +.nf +access to attrs=entryTtl + by users manage + by * read + +.fi +.RE +Example: restrict refresh to the creator of the dynamic object + +.RS +.nf +access to attrs=entryTtl + by dnattr=creatorsName manage + by * read + +.fi +.RE +Another suggested usage of dynamic objects is to implement dynamic meetings; +in this case, all the participants to the meeting are allowed to refresh +the meeting object, but only the creator can delete it (otherwise it will +be deleted when the TTL expires) + +Example: assuming \fIparticipant\fP is a valid DN-valued attribute, +allow users to start a meeting and to join it; restrict refresh +to the participants; restrict delete to the creator + +.RS +.nf +access to dn.base="cn=Meetings" + attrs=children + by users write + +access to dn.onelevel="cn=Meetings" + attrs=entry + by dnattr=creatorsName write + by * read + +access to dn.onelevel="cn=Meetings" + attrs=participant + by dnattr=creatorsName write + by users selfwrite + by * read + +access to dn.onelevel="cn=Meetings" + attrs=entryTtl + by dnattr=participant manage + by * read + +.fi +.RE + +.SH REPLICATION +This implementation of RFC 2589 provides a restricted interpretation of how +dynamic objects replicate. Only the master takes care of handling dynamic +object expiration, while replicas simply see the dynamic object as a plain +object. + +When using slurpd replication, one needs to explicitly exclude the +.B dynamicObject +class and the +.B entryTtl +attribute. +This implementation of RFC 2589 introduces a new operational attribute, +.BR entryExpireTimestamp , +that contains the expiration timestamp. This must be excluded from +replication as well. +In +.BR slapd.conf (5), +add the following \fIexclusion list\fP to each +.B replica +statement: + +.RS +.nf +replica ... + attrs!=@dynamicObject,entryTtl,entryExpireTimestamp +.fi +.RE + +When using syncrepl, the quick and dirty solution is to set +.B schemacheck=off +and, optionally, exclude the operational attributes from replication, using + +.RS +.nf +syncrepl ... + exattrs=entryTtl,entryExpireTimestamp +.fi +.RE + +In any case the overlay must be either statically built in or run-time loaded +by the consumer, so that it is aware of the +.B entryExpireTimestamp +operational attribute; however, it must not be configured in the shadow +database. +Currently, there is no means to remove the +.B dynamicObject +class from the entry; this may be seen as a feature, since it allows to see +the dynamic properties of the object. + +.SH FILES +.TP +ETCDIR/slapd.conf +default slapd configuration file +.SH SEE ALSO +.BR slapd.conf (5), +.BR slapd (8). +.SH AUTHOR +Implemented by Pierangelo Masarati. diff --git a/include/ldap.h b/include/ldap.h index 79bc849421..e34b5576ca 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -323,6 +323,11 @@ typedef struct ldapcontrol { #define LDAP_EXOP_CANCEL "1.3.6.1.1.8" /* RFC 3909 */ #define LDAP_EXOP_X_CANCEL LDAP_EXOP_CANCEL +#define LDAP_EXOP_REFRESH "1.3.6.1.4.1.1466.101.119.1" /* RFC 2589 */ +#define LDAP_TAG_EXOP_REFRESH_REQ_DN ((ber_tag_t) 0x80U) +#define LDAP_TAG_EXOP_REFRESH_REQ_TTL ((ber_tag_t) 0x81U) +#define LDAP_TAG_EXOP_REFRESH_RES_TTL ((ber_tag_t) 0x80U) + /* various works in progress */ #define LDAP_EXOP_WHO_AM_I "1.3.6.1.4.1.4203.1.11.3" #define LDAP_EXOP_X_WHO_AM_I LDAP_EXOP_WHO_AM_I @@ -2137,5 +2142,34 @@ LDAP_F( const char * ) ldap_passwordpolicy_err2txt LDAP_P(( LDAPPasswordPolicyError )); #endif /* LDAP_CONTROL_PASSWORDPOLICYREQUEST */ +/* + * LDAP Dynamic Directory Services Refresh RFC2589 + * in dds.c + */ +#define LDAP_API_FEATURE_REFRESH 1000 + +LDAP_F( int ) +ldap_parse_refresh LDAP_P(( + LDAP *ld, + LDAPMessage *res, + int *newttl )); + +LDAP_F( int ) +ldap_refresh LDAP_P(( LDAP *ld, + struct berval *dn, + int ttl, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp )); + +LDAP_F( int ) +ldap_refresh_s LDAP_P(( + LDAP *ld, + struct berval *dn, + int ttl, + int *newttl, + LDAPControl **sctrls, + LDAPControl **cctrls )); + LDAP_END_DECL #endif /* _LDAP_H */ diff --git a/libraries/libldap/Makefile.in b/libraries/libldap/Makefile.in index 8166aaa90d..3e5028cf5b 100644 --- a/libraries/libldap/Makefile.in +++ b/libraries/libldap/Makefile.in @@ -26,7 +26,7 @@ SRCS = bind.c open.c result.c error.c compare.c search.c \ request.c os-ip.c url.c sortctrl.c vlvctrl.c \ init.c options.c print.c string.c util-int.c schema.c \ charray.c tls.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \ - turn.c groupings.c txn.c ppolicy.c + turn.c groupings.c txn.c ppolicy.c dds.c OBJS = bind.lo open.lo result.lo error.lo compare.lo search.lo \ controls.lo messages.lo references.lo extended.lo cyrus.lo \ @@ -37,7 +37,7 @@ OBJS = bind.lo open.lo result.lo error.lo compare.lo search.lo \ request.lo os-ip.lo url.lo sortctrl.lo vlvctrl.lo \ init.lo options.lo print.lo string.lo util-int.lo schema.lo \ charray.lo tls.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \ - turn.lo groupings.lo txn.lo ppolicy.lo + turn.lo groupings.lo txn.lo ppolicy.lo dds.lo LDAP_INCDIR= ../../include LDAP_LIBDIR= ../../libraries diff --git a/libraries/libldap/dds.c b/libraries/libldap/dds.c new file mode 100644 index 0000000000..3ec8d65150 --- /dev/null +++ b/libraries/libldap/dds.c @@ -0,0 +1,158 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2005-2006 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 developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software */ + +#include "portable.h" + +#include +#include +#include +#include + +#include "ldap-int.h" + +int +ldap_parse_refresh( LDAP *ld, LDAPMessage *res, int *newttl ) +{ + int rc; + struct berval *retdata = NULL; + ber_tag_t tag; + BerElement *ber; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + assert( newttl != NULL ); + + *newttl = 0; + + rc = ldap_parse_extended_result( ld, res, NULL, &retdata, 0 ); + + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( ld->ld_errno != LDAP_SUCCESS ) { + return ld->ld_errno; + } + + if ( retdata == NULL ) { + rc = ld->ld_errno = LDAP_DECODING_ERROR; + return rc; + } + + ber = ber_init( retdata ); + if ( ber == NULL ) { + rc = ld->ld_errno = LDAP_NO_MEMORY; + goto done; + } + + /* check the tag */ + tag = ber_scanf( ber, "{i}", newttl ); + ber_free( ber, 1 ); + + if ( tag != LDAP_TAG_EXOP_REFRESH_RES_TTL ) { + *newttl = 0; + rc = ld->ld_errno = LDAP_DECODING_ERROR; + } + +done:; + if ( retdata ) { + ber_bvfree( retdata ); + } + + return rc; +} + +int +ldap_refresh( + LDAP *ld, + struct berval *dn, + int ttl, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + struct berval bv = { 0, NULL }; + BerElement *ber = NULL; + int rc; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( dn != NULL ); + assert( msgidp != NULL ); + + *msgidp = -1; + + ber = ber_alloc_t( LBER_USE_DER ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + ber_printf( ber, "{tOtiN}", + LDAP_TAG_EXOP_REFRESH_REQ_DN, dn, + LDAP_TAG_EXOP_REFRESH_REQ_TTL, ttl ); + + rc = ber_flatten2( ber, &bv, 0 ); + + if ( rc < 0 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + return ld->ld_errno; + } + + rc = ldap_extended_operation( ld, LDAP_EXOP_REFRESH, &bv, + sctrls, cctrls, msgidp ); + + ber_free( ber, 1 ); + + return rc; +} + +int +ldap_refresh_s( + LDAP *ld, + struct berval *dn, + int ttl, + int *newttl, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_refresh( ld, dn, ttl, sctrls, cctrls, &msgid ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *)NULL, &res ) == -1 ) { + return ld->ld_errno; + } + + rc = ldap_parse_refresh( ld, res, newttl ); + if( rc != LDAP_SUCCESS ) { + ldap_msgfree( res ); + return rc; + } + + return( ldap_result2error( ld, res, 1 ) ); +} + diff --git a/libraries/libldap_r/Makefile.in b/libraries/libldap_r/Makefile.in index 42929ae8cf..93f725a23a 100644 --- a/libraries/libldap_r/Makefile.in +++ b/libraries/libldap_r/Makefile.in @@ -28,7 +28,7 @@ XXSRCS = apitest.c test.c \ request.c os-ip.c url.c sortctrl.c vlvctrl.c \ init.c options.c print.c string.c util-int.c schema.c \ charray.c tls.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \ - turn.c groupings.c txn.c ppolicy.c + turn.c groupings.c txn.c ppolicy.c dds.c SRCS = threads.c rdwr.c tpool.c rq.c \ thr_posix.c thr_cthreads.c thr_thr.c thr_lwp.c thr_nt.c \ thr_pth.c thr_stub.c thr_debug.c @@ -44,7 +44,7 @@ OBJS = threads.lo rdwr.lo tpool.lo rq.lo \ request.lo os-ip.lo url.lo sortctrl.lo vlvctrl.lo \ init.lo options.lo print.lo string.lo util-int.lo schema.lo \ charray.lo tls.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \ - turn.lo groupings.lo txn.lo ppolicy.lo + turn.lo groupings.lo txn.lo ppolicy.lo dds.lo LDAP_INCDIR= ../../include LDAP_LIBDIR= ../../libraries diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index 639eca1f66..9392830613 100644 --- a/servers/slapd/bconfig.c +++ b/servers/slapd/bconfig.c @@ -211,6 +211,7 @@ static OidRec OidMacros[] = { * OLcfgOv{Oc|At}:6 -> smbk5pwd * OLcfgOv{Oc|At}:7 -> distproc * OLcfgOv{Oc|At}:8 -> dynlist + * OLcfgOv{Oc|At}:9 -> dds */ /* alphabetical ordering */ diff --git a/servers/slapd/controls.c b/servers/slapd/controls.c index 08003bee1c..280b4f14b0 100644 --- a/servers/slapd/controls.c +++ b/servers/slapd/controls.c @@ -97,10 +97,12 @@ static int num_known_controls = 1; static char *proxy_authz_extops[] = { LDAP_EXOP_MODIFY_PASSWD, LDAP_EXOP_X_WHO_AM_I, + LDAP_EXOP_REFRESH, NULL }; static char *manageDSAit_extops[] = { + LDAP_EXOP_REFRESH, NULL }; diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in index f30e4c25af..068b65fc05 100644 --- a/servers/slapd/overlays/Makefile.in +++ b/servers/slapd/overlays/Makefile.in @@ -15,6 +15,7 @@ SRCS = overlays.c \ accesslog.c \ + dds.c \ denyop.c \ dyngroup.c \ dynlist.c \ @@ -59,6 +60,9 @@ dynamic: $(PROGRAMS) accesslog.la : accesslog.lo $(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS) +dds.la : dds.lo + $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS) + denyop.la : denyop.lo $(LTLINK_MOD) -module -o $@ denyop.lo version.lo $(LINK_LIBS) diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c new file mode 100644 index 0000000000..7e0dabf83e --- /dev/null +++ b/servers/slapd/overlays/dds.c @@ -0,0 +1,1992 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2005-2006 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 "portable.h" + +#ifdef SLAPD_OVER_DDS + +#include + +#include +#include + +#include "slap.h" +#include "lutil.h" +#include "ldap_rq.h" + +#include "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 advertize 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 }, sc2 = { 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; + + op = (Operation *)&opbuf; + connection_fake_init( &conn, op, ctx ); + + 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, 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; + + 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 ); + + if ( rs.sr_err != LDAP_SUCCESS ) { + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS expired objects lookup failed err=%d\n", + rs.sr_err ); + goto done; + } + + op->o_tag = LDAP_REQ_DELETE; + op->o_callback = ≻ + sc.sc_response = slap_replog_cb; + sc.sc_private = NULL; + sc.sc_next = &sc2; + sc2.sc_response = slap_null_cb; + + 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: + Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS dn=\"%s\" expired.\n", + de->de_ndn.bv_val ); + ndeletes++; + break; + + case LDAP_NOT_ALLOWED_ON_NONLEAF: + Log1( 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: + Log2( 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; + dep = &de->de_next; + op->o_tmpfree( de, op->o_tmpmemctx ); + } + } + + ntotdeletes += ndeletes; + } + + rs.sr_err = LDAP_SUCCESS; + + Log1( 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 ) { +#ifdef SLAP_ACL_HONOR_DISCLOSE + /* 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 +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + { + 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[] = "31557600"; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval bv; + + 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( ttl <= DDS_RF2589_MAX_TTL ); + + bv.bv_val = ttlbuf; + bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); + + /* 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; + + 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_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 ttl; + int rc; + + switch ( mod->sml_op ) { + case LDAP_MOD_DELETE: + if ( mod->sml_values != NULL ) { + if ( BER_BVISEMPTY( &bv_entryTtl ) + || !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) ) + { +#ifdef SLAP_ACL_HONOR_DISCLOSE + 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 +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + { + 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 SLAP_MOD_SOFTADD: /* FIXME? */ + case LDAP_MOD_ADD: + assert( mod->sml_values != NULL ); + assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) ); + + if ( !BER_BVISEMPTY( &bv_entryTtl ) ) { +#ifdef SLAP_ACL_HONOR_DISCLOSE + 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 +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + { + rs->sr_text = "attribute 'entryTtl' cannot have multiple values"; + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + } + goto done; + } + + rc = lutil_atoul( &ttl, mod->sml_values[ 0 ].bv_val ); + 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 = (time_t)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 ) ) { +#ifdef SLAP_ACL_HONOR_DISCLOSE + 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 +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + { + 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 ) { +#ifdef SLAP_ACL_HONOR_DISCLOSE + 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; + + } +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + 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 manageDIT 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_manageDIT( 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; + } + +#ifdef SLAP_ACL_HONOR_DISCLOSE + 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; + } + } +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + } + } + + 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 ); + } + } + + 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 ) { +#ifdef SLAP_ACL_HONOR_DISCLOSE + /* 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 +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + { + 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; +} + +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 ) { + Log0( 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 ) { + Log0( 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 ) { + Log0( 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 ) { + Log0( 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 ) { + Log0( 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 ( len != 0 ) { +decoding_error:; + Log1( 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 }; + slap_callback sc2 = { 0 }; + Modifications ttlmod = { { 0 } }; + struct berval ttlvalues[ 2 ]; + char ttlbuf[] = "31557600"; + + 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; + } + +#ifndef SLAPD_MULTIMASTER + /* This does not apply to multi-master case */ + if ( !( !SLAP_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; + } +#endif /* !SLAPD_MULTIMASTER */ + + 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 ) { +#ifdef SLAP_ACL_HONOR_DISCLOSE + /* 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 +#endif /* SLAP_ACL_HONOR_DISCLOSE */ + { + 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 manageDIT 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_replog_cb; + sc.sc_next = &sc2; + sc2.sc_response = slap_null_cb; + op2.o_managedit = 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; + 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; + + if ( rs->sr_err == LDAP_SUCCESS ) { + 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 ); + + Log3( 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' " + "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' " + "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' " + "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' " + "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' " + "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' " + "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' " + "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->msg, sizeof( c->msg), + "DDS unable to parse dds-max-ttl \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) { + snprintf( c->msg, sizeof( c->msg ), + "DDS invalid dds-max-ttl=%ld; must be between %d and %d", + t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->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->msg, sizeof( c->msg), + "DDS unable to parse dds-min-ttl \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + if ( t < 0 || t > DDS_RF2589_MAX_TTL ) { + snprintf( c->msg, sizeof( c->msg ), + "DDS invalid dds-min-ttl=%ld", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->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->msg, sizeof( c->msg), + "DDS unable to parse dds-default-ttl \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + if ( t < 0 || t > DDS_RF2589_MAX_TTL ) { + snprintf( c->msg, sizeof( c->msg ), + "DDS invalid dds-default-ttl=%ld", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->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->msg, sizeof( c->msg), + "DDS unable to parse dds-interval \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + if ( t <= 0 ) { + snprintf( c->msg, sizeof( c->msg ), + "DDS invalid dds-interval=%ld", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + if ( t < 60 ) { + Log2( 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->msg, sizeof( c->msg), + "DDS unable to parse dds-tolerance \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + if ( t < 0 || t > DDS_RF2589_MAX_TTL ) { + snprintf( c->msg, sizeof( c->msg ), + "DDS invalid dds-tolerance=%ld", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + + di->di_tolerance = (time_t)t; + break; + + case DDS_MAXDYNAMICOBJS: + if ( c->value_int < 0 ) { + snprintf( c->msg, sizeof( c->msg ), + "DDS invalid dds-max-dynamicObjects=%d", c->value_int ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->msg ); + return 1; + } + di->di_max_dynamicObjects = c->value_int; + break; + + default: + rc = 1; + break; + } + + return rc; +} + +static int +dds_db_init( + BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di; + BackendInfo *bi = on->on_info->oi_orig; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + Log0( 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 */ + { + Log1( 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 }; + + op = (Operation *)&opbuf; + connection_fake_init( &conn, op, ctx ); + + 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->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; + + 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 ); + + if ( rs.sr_err == LDAP_SUCCESS ) { + Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS non-expired=%d\n", + di->di_num_dynamicObjects ); + + } else { + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS non-expired objects lookup failed err=%d\n", + rs.sr_err ); + } + + return rs.sr_err; +} + +static int +dds_db_open( + BackendDB *be ) +{ + 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 ( DDS_OFF( di ) ) { + goto done; + } + +#ifndef SLAPD_MULTIMASTER + if ( SLAP_SHADOW( be ) ) { + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS incompatible with shadow database \"%s\".\n", + be->be_suffix[ 0 ].bv_val ); + return 1; + } +#endif /* ! SLAPD_MULTIMASTER */ + + 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; + + /* force deletion of expired entries... */ + be->bd_info = (BackendInfo *)on->on_info; + rc = dds_expire( thrctx, di ); + be->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + rc = 1; + goto done; + } + + /* ... so that count, if required, is accurate */ + if ( di->di_max_dynamicObjects > 0 ) { + 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:; + ldap_pvt_thread_pool_context_reset( thrctx ); + + return rc; +} + +static int +dds_db_close( + BackendDB *be ) +{ + 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 ); + } + + (void)entry_info_unregister( dds_entry_info, (void *)di ); + + return 0; +} + +static int +dds_db_destroy( + BackendDB *be ) +{ + 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; + } + + Log2( 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, 0 ); + 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; + const char *err; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( DDS_LAST ); + + if ( !do_not_load_schema ) { + static struct { + char *name; + char *desc; + AttributeDescription **ad; + } s_at[] = { +#warning "FIXME: register OID!!!" + { "entryExpireTimestamp", "( 1.3.6.1.4.1.4203.666.999999.0 " + "NAME ( 'entryExpireTimestamp' ) " + "DESC 'RFC2589 extension: expire time of a dynamic object, " + "computed as modifyTimestamp + entryTtl' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_entryExpireTimestamp }, + { NULL } + }; + + for ( i = 0; s_at[ i ].name != NULL; i++ ) { + LDAPAttributeType *at; + + at = ldap_str2attributetype( s_at[ i ].desc, + &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !at ) { + fprintf( stderr, "dds_initialize: " + "AttributeType load failed: %s %s\n", + ldap_scherr2str( code ), err ); + return code; + } + + code = at_add( at, 0, NULL, &err ); + ldap_memfree( at ); + if ( code != LDAP_SUCCESS ) { + fprintf( stderr, "dds_initialize: " + "AttributeType load failed: %s %s\n", + scherr2str( code ), err ); + return code; + } + + code = slap_str2ad( s_at[ i ].name, s_at[ i ].ad, &err ); + if ( code != LDAP_SUCCESS ) { + fprintf( stderr, "dds_initialize: " + "unable to find AttributeDescription " + "\"%s\": %d (%s)\n", + s_at[ i ].name, code, err ); + return 1; + } + } + } + + 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 ) { + Log1( 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_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_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) */ diff --git a/servers/slapd/schema_prep.c b/servers/slapd/schema_prep.c index a703f1cb11..001af17ef5 100644 --- a/servers/slapd/schema_prep.c +++ b/servers/slapd/schema_prep.c @@ -894,7 +894,7 @@ static struct slap_schema_ad_map { "DESC 'RFC2589: entry time-to-live' " "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE " "NO-USER-MODIFICATION USAGE dSAOperation )", - dynamicAttribute, 0, + dynamicAttribute, SLAP_AT_MANAGEABLE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, offsetof(struct slap_internal_schema, si_ad_entryTtl) }, diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index f880a0bd27..2b310fcd17 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -1713,6 +1713,7 @@ struct slap_backend_db { #define SLAP_DBFLAG_GLUE_ADVERTISE 0x0080U /* advertise in rootDSE */ #define SLAP_DBFLAG_OVERLAY 0x0100U /* this db struct is an overlay */ #define SLAP_DBFLAG_GLOBAL_OVERLAY 0x0200U /* this db struct is a global overlay */ +#define SLAP_DBFLAG_DYNAMIC 0x0400U /* this db allows dynamicObjects */ #define SLAP_DBFLAG_SHADOW 0x8000U /* a shadow */ #define SLAP_DBFLAG_SYNC_SHADOW 0x1000U /* a sync shadow */ #define SLAP_DBFLAG_SLURP_SHADOW 0x2000U /* a slurp shadow */ @@ -2165,7 +2166,7 @@ struct slap_backend_info { #define SLAP_ALIASES(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_ALIASES) #define SLAP_REFERRALS(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_REFERRALS) #define SLAP_SUBENTRIES(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_SUBENTRIES) -#define SLAP_DYNAMIC(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_DYNAMIC) +#define SLAP_DYNAMIC(be) ((SLAP_BFLAGS(be) & SLAP_BFLAG_DYNAMIC) || (SLAP_DBFLAGS(be) & SLAP_DBFLAG_DYNAMIC)) #define SLAP_NOLASTMODCMD(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_NOLASTMODCMD) #define SLAP_LASTMODCMD(be) (!SLAP_NOLASTMODCMD(be)) diff --git a/tests/data/dds.out b/tests/data/dds.out new file mode 100644 index 0000000000..1f580b4c10 --- /dev/null +++ b/tests/data/dds.out @@ -0,0 +1,70 @@ +# [1] Searching the dynamic portion of the database... +dn: cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +cn: Dynamic Object +sn: Object +entryTtl: 120 +userPassword:: ZHluYW1pYw== + +dn: cn=Subordinate Dynamic Object,cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +cn: Subordinate Dynamic Object +sn: Object +userPassword:: ZHluYW1pYw== +entryTtl: 3600 + +# [2] Searching the dynamic portion of the database... +dn: cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +cn: Dynamic Object +sn: Object +entryTtl: 120 +userPassword:: ZHluYW1pYw== + +dn: cn=Renamed Dynamic Object,cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +sn: Object +userPassword:: ZHluYW1pYw== +entryTtl: 3600 +cn: Renamed Dynamic Object + +# [3] Searching the dynamic portion of the database... +dn: cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +cn: Dynamic Object +sn: Object +userPassword:: ZHluYW1pYw== +entryTtl: 120 + +dn: cn=Renamed Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +sn: Object +userPassword:: ZHluYW1pYw== +entryTtl: 3600 +cn: Renamed Dynamic Object + +# [4] Searching the dynamic portion of the database... +dn: cn=Renamed Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +sn: Object +userPassword:: ZHluYW1pYw== +entryTtl: 3600 +cn: Renamed Dynamic Object + +# [5] Searching the dynamic portion of the database... +dn: cn=Renamed Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +sn: Object +userPassword:: ZHluYW1pYw== +cn: Renamed Dynamic Object +entryTtl: 10 + +# [6] Searching the dynamic portion of the database... diff --git a/tests/data/slapd-dds.conf b/tests/data/slapd-dds.conf new file mode 100644 index 0000000000..19e583e02d --- /dev/null +++ b/tests/data/slapd-dds.conf @@ -0,0 +1,88 @@ +# stand-alone slapd config -- for testing (with indexing) +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2005 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 +## . + +include @SCHEMADIR@/core.schema +include @SCHEMADIR@/cosine.schema +include @SCHEMADIR@/inetorgperson.schema +include @SCHEMADIR@/openldap.schema +include @SCHEMADIR@/nis.schema +include @DATADIR@/test.schema + +# +pidfile @TESTDIR@/slapd.1.pid +argsfile @TESTDIR@/slapd.1.args + +#mod#modulepath ../servers/slapd/back-@BACKEND@/ +#mod#moduleload back_@BACKEND@.la +#monitormod#modulepath ../servers/slapd/back-monitor/ +#monitormod#moduleload back_monitor.la +#ddsmod#modulepath ../servers/slapd/overlays/ +#ddsmod#moduleload dds.la + +####################################################################### +# database definitions +####################################################################### + +database @BACKEND@ +suffix "dc=example,dc=com" +directory @TESTDIR@/db.1.a +rootdn "cn=Manager,dc=example,dc=com" +rootpw secret +#bdb#index objectClass eq +#bdb#index cn,sn,uid pres,eq,sub +#bdb#index entryExpireTimestamp eq +#hdb#index objectClass eq +#hdb#index cn,sn,uid pres,eq,sub +#hdb#index entryExpireTimestamp eq +#ldbm#index objectClass eq +#ldbm#index cn,sn,uid pres,eq,sub +#ldbm#index entryExpireTimestamp eq + +overlay dds +dds-max-ttl 1d +dds-min-ttl 10s +dds-default-ttl 1h +dds-interval 5s +dds-tolerance 1s + +# This is to test the meeting feature +access to attrs=userPassword + by self write + by * read + +access to dn.base="ou=Groups,dc=example,dc=com" + attrs=children + by users write + +access to dn.onelevel="ou=Groups,dc=example,dc=com" + attrs=entry + by dnattr=creatorsName write + by * read + +access to dn.onelevel="ou=Groups,dc=example,dc=com" + attrs=member + by dnattr=creatorsName write + by users selfwrite + by * read + +access to dn.onelevel="ou=Groups,dc=example,dc=com" + attrs=entryTtl + by dnattr=member manage + by * read + +access to * + by * read + +#monitor#database monitor diff --git a/tests/run.in b/tests/run.in index 7bd775a0b5..6faeed7fd8 100644 --- a/tests/run.in +++ b/tests/run.in @@ -35,6 +35,7 @@ AC_sql=sql@BUILD_SQL@ # overlays AC_accesslog=accesslog@BUILD_ACCESSLOG@ +AC_dds=dds@BUILD_DDS@ AC_dynlist=dynlist@BUILD_DYNLIST@ AC_pcache=pcache@BUILD_PROXYCACHE@ AC_ppolicy=ppolicy@BUILD_PPOLICY@ @@ -56,6 +57,7 @@ AC_THREADS=threads@BUILD_THREAD@ export AC_bdb AC_hdb AC_ldap AC_ldbm AC_meta AC_monitor AC_relay AC_sql \ AC_accesslog AC_dynlist AC_pcache AC_ppolicy AC_refint AC_retcode \ AC_rwm AC_unique AC_syncprov AC_translucent AC_valsort \ + AC_dds \ AC_WITH_SASL AC_WITH_TLS AC_WITH_MODULES_ENABLED AC_ACI_ENABLED \ AC_THREADS diff --git a/tests/scripts/conf.sh b/tests/scripts/conf.sh index ddce73cdb7..7bef25a2a5 100755 --- a/tests/scripts/conf.sh +++ b/tests/scripts/conf.sh @@ -42,6 +42,7 @@ sed -e "s/@BACKEND@/${BACKEND}/" \ -e "s/^#${AC_sql}#//" \ -e "s/^#${RDBMS}#//" \ -e "s/^#${AC_accesslog}#//" \ + -e "s/^#${AC_dds}#//" \ -e "s/^#${AC_dynlist}#//" \ -e "s/^#${AC_pcache}#//" \ -e "s/^#${AC_ppolicy}#//" \ diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh index d3f3254651..a77439c2b7 100755 --- a/tests/scripts/defines.sh +++ b/tests/scripts/defines.sh @@ -21,6 +21,7 @@ BACKSQL=${AC_sql-sqlno} RDBMS=${SLAPD_USE_SQL-rdbmsno} RDBMSWRITE=${SLAPD_USE_SQLWRITE-no} ACCESSLOG=${AC_accesslog-accesslogno} +DDS=${AC_dds-ddsno} DYNLIST=${AC_dynlist-dynlistno} PROXYCACHE=${AC_pcache-pcacheno} PPOLICY=${AC_ppolicy-ppolicyno} @@ -107,6 +108,7 @@ VALSORTCONF=$DATADIR/slapd-valsort.conf DYNLISTCONF=$DATADIR/slapd-dynlist.conf RSLAVECONF=$DATADIR/slapd-repl-slave-remote.conf PLSRSLAVECONF=$DATADIR/slapd-syncrepl-slave-persist-ldap.conf +DDSCONF=$DATADIR/slapd-dds.conf CONF1=$TESTDIR/slapd.1.conf CONF2=$TESTDIR/slapd.2.conf @@ -149,6 +151,7 @@ LDAPADD="$CLIENTDIR/ldapmodify -a $TOOLPROTO $TOOLARGS" LDAPMODRDN="$CLIENTDIR/ldapmodrdn $TOOLPROTO $TOOLARGS" LDAPWHOAMI="$CLIENTDIR/ldapwhoami $TOOLARGS" LDAPCOMPARE="$CLIENTDIR/ldapcompare $TOOLARGS" +LDAPEXOP="$CLIENTDIR/ldapexop $TOOLARGS" SLAPDTESTER=$PROGDIR/slapd-tester LVL=${SLAPD_DEBUG-261} LOCALHOST=localhost @@ -297,6 +300,7 @@ MANAGEOUT=$DATADIR/manage.out SUBTREERENAMEOUT=$DATADIR/subtree-rename.out ACIOUT=$DATADIR/aci.out DYNLISTOUT=$DATADIR/dynlist.out +DDSOUT=$DATADIR/dds.out # Just in case we linked the binaries dynamically LD_LIBRARY_PATH=`pwd`/../libraries:${LD_LIBRARY_PATH} export LD_LIBRARY_PATH diff --git a/tests/scripts/test046-dds b/tests/scripts/test046-dds new file mode 100755 index 0000000000..34d96d9bff --- /dev/null +++ b/tests/scripts/test046-dds @@ -0,0 +1,529 @@ +#! /bin/sh +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2005 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 +## . + +echo "running defines.sh" +. $SRCDIR/scripts/defines.sh + +if test $DDS = ddsno; then + echo "Dynamic Directory Services overlay not available, test skipped" + exit 0 +fi + +mkdir -p $TESTDIR $DBDIR1 + +echo "Running slapadd to build slapd database..." +. $CONFFILTER $BACKEND $MONITORDB < $MCONF > $ADDCONF +$SLAPADD -f $ADDCONF -l $LDIFORDERED +RC=$? +if test $RC != 0 ; then + echo "slapadd failed ($RC)!" + exit $RC +fi + +echo "Running slapindex to index slapd database..." +. $CONFFILTER $BACKEND $MONITORDB < $DDSCONF > $CONF1 +$SLAPINDEX -f $CONF1 +RC=$? +if test $RC != 0 ; then + echo "warning: slapindex failed ($RC)" + echo " assuming no indexing support" +fi + +echo "Starting slapd on TCP/IP port $PORT1..." +$SLAPD -f $CONF1 -h $URI1 -d $LVL $TIMING > $LOG1 2>&1 & +PID=$! +if test $WAIT != 0 ; then + echo PID $PID + read foo +fi +KILLPIDS="$PID" + +sleep 1 + +echo "Testing slapd searching..." +for i in 0 1 2 3 4 5; do + $LDAPSEARCH -s base -b "$MONITOR" -h $LOCALHOST -p $PORT1 \ + '(objectclass=*)' > /dev/null 2>&1 + RC=$? + if test $RC = 0 ; then + break + fi + echo "Waiting 5 seconds for slapd to start..." + sleep 5 +done + +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +cat /dev/null > $SEARCHOUT + +echo "Creating a dynamic entry..." +$LDAPADD -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +cn: Dynamic Object +sn: Object +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Refreshing the newly created dynamic entry..." +$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + "refresh" "cn=Dynamic Object,dc=example,dc=com" "120" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapexop failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Modifying the newly created dynamic entry..." +$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Dynamic Object,dc=example,dc=com +changetype: modify +add: userPassword +userPassword: dynamic +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Binding as the newly created dynamic entry..." +$LDAPWHOAMI -h $LOCALHOST -p $PORT1 \ + -D "cn=Dynamic Object,dc=example,dc=com" -w dynamic +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Creating a dynamic entry subordinate to another..." +$LDAPADD -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Subordinate Dynamic Object,cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: dynamicObject +cn: Subordinate Dynamic Object +sn: Object +userPassword: dynamic +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SEARCH=0 + +SEARCH=`expr $SEARCH + 1` +echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \ + '(objectClass=dynamicObject)' '*' entryTtl \ + >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Creating a static entry subordinate to a dynamic one (should fail)..." +$LDAPADD -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Subordinate Static Object,cn=Dynamic Object,dc=example,dc=com +objectClass: inetOrgPerson +cn: Subordinate Static Object +sn: Object +userPassword: static +EOMODS +RC=$? +case $RC in +0) + echo "ldapadd should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit -1 + ;; +19) + echo "ldapadd failed ($RC)" + ;; +*) + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +echo "Turning a static into a dynamic entry (should fail)..." +$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: ou=People,dc=example,dc=com +changetype: modify +add: objectClass +objectClass: dynamicObject +EOMODS +RC=$? +case $RC in +0) + echo "ldapmodify should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit -1 + ;; +65) + echo "ldapmodify failed ($RC)" + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +echo "Turning a dynamic into a static entry (should fail)..." +$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Dynamic Object,dc=example,dc=com +changetype: modify +delete: objectClass +objectClass: dynamicObject +EOMODS +RC=$? +case $RC in +0) + echo "ldapmodify should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit -1 + ;; +65) + echo "ldapmodify failed ($RC)" + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +echo "Renaming a dynamic entry..." +$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Subordinate Dynamic Object,cn=Dynamic Object,dc=example,dc=com +changetype: modrdn +newrdn: cn=Renamed Dynamic Object +deleteoldrdn: 1 +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapmodrdn failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SEARCH=`expr $SEARCH + 1` +echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \ + '(objectClass=dynamicObject)' '*' entryTtl \ + >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Refreshing the initial dynamic entry to make it expire earlier than the subordinate..." +$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + "refresh" "cn=Dynamic Object,dc=example,dc=com" "1" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapexop failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SLEEP=10 +echo "Waiting $SLEEP seconds to force an expiration conflict..." +sleep $SLEEP + +echo "Re-vitalizing the initial dynamic entry..." +$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + "refresh" "cn=Dynamic Object,dc=example,dc=com" "120" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapexop failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Re-renaming the subordinate dynamic entry (new superior)..." +$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Renamed Dynamic Object,cn=Dynamic Object,dc=example,dc=com +changetype: modrdn +newrdn: cn=Renamed Dynamic Object +deleteoldrdn: 1 +newsuperior: dc=example,dc=com +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapmodrdn failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SEARCH=`expr $SEARCH + 1` +echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \ + '(objectClass=dynamicObject)' '*' entryTtl \ + >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Deleting a dynamic entry..." +$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: cn=Dynamic Object,dc=example,dc=com +changetype: delete +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapdelete failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SEARCH=`expr $SEARCH + 1` +echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \ + '(objectClass=dynamicObject)' '*' entryTtl \ + >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Refreshing the remaining dynamic entry..." +$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \ + "refresh" "cn=Renamed Dynamic Object,dc=example,dc=com" "1" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapexop failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SEARCH=`expr $SEARCH + 1` +echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \ + '(objectClass=dynamicObject)' '*' entryTtl \ + >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +SLEEP=15 +echo "Waiting $SLEEP seconds for remaining entry to expire..." +sleep $SLEEP + +SEARCH=`expr $SEARCH + 1` +echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \ + '(objectClass=dynamicObject)' '*' entryTtl \ + >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +# Meeting +MEETINGDN="cn=Meeting,ou=Groups,dc=example,dc=com" +echo "Creating a meeting as $BJORNSDN..." +$LDAPMODIFY -D "$BJORNSDN" -w bjorn -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: $MEETINGDN +changetype: add +objectClass: groupOfNames +objectClass: dynamicObject +cn: Meeting +member: $BJORNSDN + +dn: $MEETINGDN +changetype: modify +add: member +member: $JAJDN +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Refreshing the meeting as $BJORNSDN..." +$LDAPEXOP -D "$BJORNSDN" -w bjorn -h $LOCALHOST -p $PORT1 \ + "refresh" "$MEETINGDN" "120" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapexop failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Joining the meeting as $BABSDN..." +$LDAPMODIFY -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: $MEETINGDN +changetype: modify +add: member +member: $BABSDN +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Trying to add a member as $BABSDN (should fail)..." +$LDAPMODIFY -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: $MEETINGDN +changetype: modify +add: member +member: $MELLIOTDN +EOMODS +RC=$? +case $RC in +0) + echo "ldapmodify should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +50) + echo "ldapmodify failed ($RC)" + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +echo "Refreshing the meeting as $BABSDN..." +$LDAPEXOP -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \ + "refresh" "$MEETINGDN" "180" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapexop failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Trying to refresh the meeting anonymously (should fail)..." +$LDAPEXOP -h $LOCALHOST -p $PORT1 \ + "refresh" "$MEETINGDN" "240" \ + >> $TESTOUT 2>&1 +RC=$? +if test $RC = 0 ; then + echo "ldapexop should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Trying to delete the meeting as $BABSDN (should fail)..." +$LDAPMODIFY -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: $MEETINGDN +changetype: delete +EOMODS +RC=$? +case $RC in +0) + echo "ldapdelete should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +50) + echo "ldapdelete failed ($RC)" + ;; +*) + echo "ldapdelete failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +echo "Deleting the meeting as $BJORNSDN..." +$LDAPMODIFY -D "$BJORNSDN" -w bjorn -h $LOCALHOST -p $PORT1 \ + >> $TESTOUT 2>&1 << EOMODS +dn: $MEETINGDN +changetype: delete +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapdelete failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +test $KILLSERVERS != no && kill -HUP $KILLPIDS + +LDIF=$DDSOUT + +echo "Filtering ldapsearch results..." +. $LDIFFILTER < $SEARCHOUT > $SEARCHFLT +echo "Filtering original ldif used to create database..." +. $LDIFFILTER < $LDIF > $LDIFFLT +echo "Comparing filter output..." +$CMP $SEARCHFLT $LDIFFLT > $CMPOUT + +if test $? != 0 ; then + echo "Comparison failed" + exit 1 +fi + +echo ">>>>> Test succeeded" +exit 0 -- 2.39.5