diff options
author | cvsadm <cvsadm> | 2005-01-21 00:44:34 +0000 |
---|---|---|
committer | cvsadm <cvsadm> | 2005-01-21 00:44:34 +0000 |
commit | b2093e3016027d6b5cf06b3f91f30769bfc099e2 (patch) | |
tree | cf58939393a9032182c4fbc4441164a9456e82f8 /ldap/servers/plugins | |
download | ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.tar.gz ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.tar.xz ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.zip |
Moving NSCP Directory Server from DirectoryBranch to TRUNK, initial drop. (foxworth)ldapserver7x
Diffstat (limited to 'ldap/servers/plugins')
267 files changed, 96477 insertions, 0 deletions
diff --git a/ldap/servers/plugins/Makefile b/ldap/servers/plugins/Makefile new file mode 100644 index 00000000..051f3b70 --- /dev/null +++ b/ldap/servers/plugins/Makefile @@ -0,0 +1,101 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server and LDAP SDK libraries +# + +MCOM_ROOT = ../../../.. +LDAP_SRC = $(MCOM_ROOT)/ldapserver/ldap + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +all: _referint _collation _syntaxes _passthru _utils _uiduniq _roles _acl _replication _cos _pwdstorage _rever _chainingdb _distrib _retrocl _statechange _http _presence _views + +_utils: + cd shared; $(MAKE) $(MFLAGS) all +_rever: + cd rever; $(MAKE) $(MFLAGS) all + +_chainingdb: + cd chainingdb; $(MAKE) $(MFLAGS) all + +_referint: + cd referint; $(MAKE) $(MFLAGS) all + +_collation: + cd collation; $(MAKE) $(MFLAGS) all + +_syntaxes: + cd syntaxes; $(MAKE) $(MFLAGS) all + +_passthru: + cd passthru; $(MAKE) $(MFLAGS) all + +_uiduniq: + cd uiduniq; $(MAKE) $(MFLAGS) all + +_replication: + cd replication; $(MAKE) $(MFLAGS) all + +_acl: + cd acl; $(MAKE) $(MFLAGS) all + +_pwdstorage: + cd pwdstorage; $(MAKE) $(MFLAGS) all + +_distrib: + cd distrib; $(MAKE) $(MFLAGS) all + +_roles: + cd roles; $(MAKE) $(MFLAGS) all + +_cos: + cd cos; $(MAKE) $(MFLAGS) all + +_statechange: + cd statechange; $(MAKE) $(MFLAGS) all + +_retrocl: + cd retrocl; $(MAKE) $(MFLAGS) all + +_http: + cd http; $(MAKE) $(MFLAGS) all + +_presence: + cd presence; $(MAKE) $(MFLAGS) all + +_views: + cd views; $(MAKE) $(MFLAGS) all + +clean: + cd rever; $(MAKE) $(MFLAGS) clean + cd referint; $(MAKE) $(MFLAGS) clean + cd collation; $(MAKE) $(MFLAGS) clean + cd syntaxes; $(MAKE) $(MFLAGS) clean + cd passthru; $(MAKE) $(MFLAGS) clean + cd shared; $(MAKE) $(MFLAGS) clean + cd uiduniq; $(MAKE) $(MFLAGS) clean + cd replication; $(MAKE) $(MFLAGS) clean + cd acl; $(MAKE) $(MFLAGS) clean + cd cos; $(MAKE) $(MFLAGS) clean + cd pwdstorage; $(MAKE) $(MFLAGS) clean + cd roles; $(MAKE) $(MFLAGS) clean + cd chainingdb; $(MAKE) $(MFLAGS) clean + cd distrib; $(MAKE) $(MFLAGS) clean + cd retrocl; $(MAKE) $(MFLAGS) clean + cd statechange; $(MAKE) $(MFLAGS) clean + cd presence; $(MAKE) $(MFLAGS) clean + cd http; $(MAKE) $(MFLAGS) clean + cd views; $(MAKE) $(MFLAGS) clean + +veryclean: clean diff --git a/ldap/servers/plugins/acl/ACL-Notes b/ldap/servers/plugins/acl/ACL-Notes new file mode 100644 index 00000000..e275c967 --- /dev/null +++ b/ldap/servers/plugins/acl/ACL-Notes @@ -0,0 +1,215 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# + + +Date What ? +=================================== +10/15/98 - Created the ACL plugin + - Created a new file aclplugin.c and split the old + acl.c to acl.c & aclparse.c files. + - Merged changes made upt 4.0B2 +10/21/98 - Added USERATTR rule. + + +02/01/99 - Cleanup needed to be done in 5.0 to make it a real plugin +===================================================================================== +1. Do not use slap.h but use slapi-plugin.h. This will require + some work. Work involves + 1) Making the ACLCB an extensible object of CONN struct + 2) Remove reference of Connection & operation struct + 3) Need slapi plugin apis to get the IP and DNS so that + we can evaluate it in the LASes. + 4) Need new option to get values of conn , op & pb stuct like + cert, authtype, + +2. Make ACLPB hang from the Operation struct instead of the PBlock. +3. Make ACLCB an extensible object of CONN struct and remove any reference + about acl private info. + +4. I implemented the Userattr rule before even deciding if we need in 5.0 + or not. I think it is useful. The documents those were based on are + in http://jazz/users/prasanta/acl_manage_filter + +5. Move acllas_dn_parent to the libslapd. This is duplicated code and is + BAAAD. + +6. Use the new normalized dn code so that we don't have to it over and over again. + We have to very careful ins slapi_access_allowed() as we keep the dn around and + free it later ( we can use dn by ref ). + +7. Merge from DS4.1 ( proxy auth) to DS 5.0. + +8. Miscs + a) can we use the SDK URL parsing code ? + b) Merge teh printing routines ( it's all over ). + +My estimate for doing the above cleanup will require anywhere between 5 to 8 days. +Run the ACL tests after all the changes -- that is a MUST. +=============================== +04/28/99 + + -- All the work descibed above is done. + -- Also + a) Created a Pool pf ACLPB one of which is grabed at the init time. + b) Created a global lockarary which takes care of the concurreny issue between + aclpb & aclcb + c) Fixed plugin init. + + +I think the userattr rule should be made generic + + useAttr = "attrName#Type" + + <Type> :== DN | GROUP | ROLE | URL | <value> + <value> :== < any printable String> + +Example: + userAttr = "manager#DN" --- similar to userdnattr + userAttr = "owner#GROUP" --- similar to groupdnattr + userAttr = "attr#ROLE" --- The value of attr contains a role definition + userAttr = "myattr#URL" --- The value contains a URL or filter + userAttr = "OU#Directory Server" + --- In this case the client's OU and the + resource entry's OU must have + "Directory Server" value. + + This way we can get rid of userdnattr and groupdnattr and accomplish a + lot with a single rule. + +At this point, we are done with the changes and waiting for what needs to be +done in 5.0. +================================= +06/01/1999 + -- Split the code into smaller modules + ( aclanom, aclgroup, aclinit, ...) + --- The ACLs are read and kept in a AVL tree. + --- Few bugs fixed in the acl_scan_match code. + +================================================ +07/02/99 + + -- Added support for parameterized bind rules. + -- Added support for caching of ATTR rules using recompute.S + + What's left for 5.0 + ------------------- + 1. Support for roles + 2. Re-architect user/group cache + 3. startup in multiple threads ( low priority) + 4. look at add/delete/modrdn operations. + 5. cleanup: + - revist all the debug statements + - new tests etc. + 6. UI work + +============ +commit:14/12/99 rbyrne + +. Added targattrfilters keyword for value based acls. + Required also slapi_filter_apply(), slapi_get_attribute_type() + and slapi_attr_syntax_normalize() in slapd (filter.c and attrsyntax.c). +. Memory leak fix in acl.c for PListInit() call--see comments in code. +. made access an int on it's own to give room for expansion + (see aci_access and aclpb_access) +. files: ACL-Notes, acl.c acl.h acl-ext.c aclanom.c acllas.c acllist.c aclparse.c aclutil.c slapd/attrsyntax.c slapd/slapi-plugin.h slapd/filter.c slapd/libslapd.def + +=== +commit: Mon 20th Dec 199 +. aclparse.c: add proxy back to acl_access2str +. filter.c: get_filter() does not recurse anymore--get_fitler_internal(), get_filter_list() +do the recursion...this way testing for ldapsubentry works. +. aclinit.c: now have filter (|(aci=*)(objectclass=ldapsubentry)) in +aclinit_search_and_insert_aci(). This means that when slapi_search_internal_callback() +stops returning subentries by default, we will still get them as we have the correct filter. + +=== +commit: 12/01/2000: +. aclplugin.c: fix for proxyauth bug in aclplugin_preop_search() and +acl_plugin_preop_modify()--the proxy_dn and dn were swapped. +. acl_ext.c: Also, when we PListAssignValue() on DS_ATTR_USERDN in acl_init_aclpb(), +we should pass it a dn from aclpb_sdn, NOT the dn passed into acl_init_aclpb() which +gets freed after the call to acl_init_acpb(). JAlso here need to be careful thatif dn contains NULL that we indicate this in aclpb_sdn by setting dn to a non-NULL empty string ("") which the code takes to be anon. +. checked that none of the PList objects (DS_PROP_ACLPB, DS_ATTR_USERDN, DS_ATTR_ENTRY) have mem leak problems. +. acl.c, acllas.c, aclproxy.c: removed some #ifdef 0 and comments--tidy up but +no code changes. +. acl_ext.c: in acl__done_aclpb() we need to PListDleteProp() on ACL_ATTR_IP +and ACL_ATTR_DNS. This is because if LASIpEval/ACL_GetAttribute() and +LASDnsEval/ACL_GetAttribute() see that these properties exist, they do +not bother calling the respective Getter() function. So, everytime +the aclpb is reused and ip or dns eval is required, the old value is used ( +or whatever hjappens to be in the memory.). Tested--works fine now with ip and dns keywords. ALso tested that when the same user tries an a non-allowed machine he is not allowed by accident (as he was before). +. in schema.c/oc_find(): normalize the objectclass name before looking for it. Otherwise +if there's a trailing space in the oc name, you won't dfind it. + +=== +commit: + +. aclparse.c: fix for syntax.ksh tp6 test: if there is no "version" in an aci item, reject it. +. acllas.c: in DS_UserDnEval() now call slapi_normalize_dn() when comparing param strings and + ordinary dns. +. acl_ext.c: when seeting DS_USER_DN_ATTR, get the ndn, the normalized form. + +==== +commit: 7/02/2000 +anom profile and groupdn != don't work together! Bug 381830 in 4.X +. acl.h: new bit in aci_type to mark as below. +. aclparse.c: mark an aci if it's like deny() groupdn != blah +. aclanom.c: if marked like that cancel anom profile (just like userdn !=) +== +. removed these for the mo... +commit: +. acllas.c: now get the vattrs via slapi_vattr_merge_copy() when testing the client entry. +. vattr.c: assign i the length of the list:i = type_context.list_length; +. entry.c: slapi_entry_add_valueset() + +== + +commit: 03/03/2000 +. support for roledn in acis. +=== +. acllist: in slapi_sdn_free(&aciListHead->acic_sdn); gbeelato's mem leak fix. +commited + +===== + +committed: 17/008/00 +. support for $dn: aclutil.c, aclparse.c, acllist.c, acllas.c, acl.c, acl.h +. acl_ext.c:Make sure aclpb_search_base is initialized to NULL in aclpb__malloc() +. acl.c: set_result_status: wrong bit masks were being used in a_eval->attrEval_s_astatus etc. + acl__attr_cached_result(): in the attr==NULL case, need to test for potential +"recompute" case of attribute--this happens if it's a param or attr style aci. + +======== +commited +Support for dynamic backends: +. acllist.c, aclinit.c, libslapd.def, control.c, slapi-plugin.h: + acl_be_state_change_fnc(), slapi_build_control_from_berval() etc. +. aclanom.c: logical error in aclanom_match_profile() was causing misctest4 to fail. +. acl_ext.c:fix mem leak by calling acl_clean_aclEval_control() in acl_ext_conn_desctructor() +. +=== +committed:24 Aug 2000 +now SLAPI_ACL_ALL (allow(all)) does NOT include proxy right + +== +committed: 30 Aug 2000 +. acl.c: new print_access_control_Summary() routine to display final acl status. Gets the proxy + stuff right too. + in acl__resource_match_aci() always test the TARGET_FILTER case, the old cod ethere was wrong. +== +. add support for macros to userdn ldapurl keyword. + + +== +Committed: +. Sep 07 2000: Support for $attr in macros. +. Sep 15 2000: Support for aci macros in targetfilter keyword. +. Sep 18 2000: improve ret code handling in __aclinit_handler--stops spurious error message. + + +--eof diff --git a/ldap/servers/plugins/acl/Makefile b/ldap/servers/plugins/acl/Makefile new file mode 100644 index 00000000..bdfe2dc0 --- /dev/null +++ b/ldap/servers/plugins/acl/Makefile @@ -0,0 +1,96 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server acl-plugin.so acl plugins +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libacl +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +# ACL plugin depends on libadminutil +MCC_INCLUDE += $(ADMINUTIL_INCLUDE) + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libacl.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(ACLINC) + +ACL_OBJS= acl.o acllas.o aclutil.o aclplugin.o aclparse.o acl_ext.o aclproxy.o \ + aclinit.o aclgroup.o aclanom.o acllist.o acleffectiverights.o + +OBJS = $(addprefix $(OBJDEST)/, $(ACL_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBACL_DLL_OBJ = $(addprefix $(OBJDEST)/, acldllmain.o) +endif + +LIBACL= $(addprefix $(LIBDIR)/, $(ACL_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(NSPRLINK) $(LDAP_LIBAVL) $(LDAP_SDK_LIBLDAP_DLL) +endif + +# ACL plugin depends on libadminutil (through libns-httpd) +EXTRA_LIBS_DEP += $(NSHTTPD_DEP) $(ADMINUTIL_DEP) $(DBM_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(DBMLINK) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBACCESS_DEP) +EXTRA_LIBS += $(LIBACCESS) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libacl.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_LIBAVL) $(LDAP_SDK_LIBLDAP_DLL) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBACL) + +$(LIBACL): $(OBJS) $(LIBACL_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBACL_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBACL_DLL_OBJ) +endif + $(RM) $(LIBACL) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c new file mode 100644 index 00000000..8dfbd52a --- /dev/null +++ b/ldap/servers/plugins/acl/acl.c @@ -0,0 +1,4118 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/**************************************************************************** +* +* acl.c +* +* +* This file contains the functions related to Access Control List (ACL) +* checking. The ACL checking is based on the ONE ACL design implemented +* in the Web server 2.0. For more information on the ACL design look +* into the barracuda home page. +* +* +******************************************************************************/ + + +/****************************************************************************/ +/* Globals. Must be protected by Mutex. */ +/****************************************************************************/ +/* Signatures to see if things have changed */ +static short acl_signature = 0; + +/****************************************************************************/ +/* Defines, Constants, ande Declarations */ +/****************************************************************************/ +static char *ds_map_generic[2] = { NULL, NULL }; + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int acl__resource_match_aci(struct acl_pblock *aclpb, aci_t *aci , + int skip_attrEval, int *a_matched); +static acl__TestRights(Acl_PBlock *aclpb,int access, char **right, + char ** map_generic, aclResultReason_t *result_reason); +static int acl__scan_for_acis(struct acl_pblock *aclpb, int *err); +static void acl__reset_cached_result (struct acl_pblock *aclpb ); +static int acl__scan_match_handles ( struct acl_pblock *aclpb, int type); +static int acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access ); +static int acl__match_handlesFromCache (struct acl_pblock *aclpb, char *attr, int access); +static int acl__get_attrEval ( struct acl_pblock *aclpb, char *attr ); +static int acl__config_get_readonly (); +static int acl__recompute_acl (Acl_PBlock *aclpb, AclAttrEval *a_eval, + int access, int aciIndex); +static void __acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, + int access, int index ); +static int acl__make_filter_test_entry ( Slapi_Entry **entry, + char *attr_type, struct berval *attr_val); +static int acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, + int filter_sense); +static void print_access_control_summary( char * source, + int ret_val, char *clientDn, + struct acl_pblock *aclpb, + char *right, + char *attr, + const char *edn, + aclResultReason_t *acl_reason); +static int check_rdn_access( Slapi_PBlock *pb,Slapi_Entry *e, char * newrdn, + int access); + + +/* + * Check the rdn permissions for this entry: + * require: write access to the entry, write (add) access to the new + * naming attribute, write (del) access to the old naming attribute if + * deleteoldrdn set. + * + * Valid only for the modrdn operation. +*/ +int +acl_access_allowed_modrdn( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* requested access rights */ + ) +{ + int retCode ; + char *newrdn, *oldrdn; + int deleteoldrdn = 0; + + /* + * First check write permission on the entry--this is actually + * specially for modrdn. + */ + retCode = acl_access_allowed ( pb, e, NULL /* attr */, NULL /* val */, + SLAPI_ACL_WRITE); + + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to entry not allowed\n"); + return(retCode); + } + + /* Now get the new rdn attribute name and value */ + + slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &oldrdn ); + slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ); + + /* Check can add the new naming attribute */ + retCode = check_rdn_access( pb, e, newrdn, ACLPB_SLAPI_ACL_WRITE_ADD) ; + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to add new naming attribute not allowed\n"); + return(retCode); + } + + /* Check can delete the new naming attribute--if required */ + slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn ); + if ( deleteoldrdn ) { + retCode = check_rdn_access( pb, e, oldrdn, ACLPB_SLAPI_ACL_WRITE_DEL) ; + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to delete old naming attribute not allowed\n"); + return(retCode); + } + } + + return(retCode); + +} +/* + * Test if have access to make the first rdn of dn in entry e. +*/ + +static int check_rdn_access( Slapi_PBlock *pb, Slapi_Entry *e, char *dn, + int access) { + + char **dns; + char **rdns; + int retCode = LDAP_INSUFFICIENT_ACCESS; + int i; + + if ( (dns = ldap_explode_dn( dn, 0 )) != NULL ) { + + if ( (rdns = ldap_explode_rdn( dns[0], 0 )) != NULL ) { + + for ( i = 0; rdns[i] != NULL; i++ ) { + char *type; + struct berval bv; + + if ( slapi_rdn2typeval( rdns[i], &type, &bv ) != 0 ) { + char ebuf[ BUFSIZ ]; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn: rdn2typeval (%s) failed\n", + escape_string( rdns[i], ebuf )); + retCode = LDAP_INSUFFICIENT_ACCESS; + break; + } else { + if ( (retCode = acl_access_allowed ( pb, e, type /* attr */, + &bv /* val */, + access)) != LDAP_SUCCESS) { + break; + } + } + } + ldap_value_free( rdns ); + } + ldap_value_free( dns ); + } + + return(retCode); +} + +/*************************************************************************** +* +* acl_access_allowed +* Determines if access to the resource is allowed or not. +* +* Input: +* +* +* Returns: +* +* Returns success/Denied/error condition +* +* LDAP_SUCCESS -- access allowed +* LDAP_INSUFFICIENT_ACCESS -- access denied +* +* Errors returned: +* +* Some of the definition of the return values used copied from +* "ldap.h" for convienience. +* LDAP_OPERATIONS_ERROR +* LDAP_PROTOCOL_ERROR +* LDAP_UNWILLING_TO_PERFORM +* +* +* Error Handling: +* Returned error code. +**************************************************************************/ +int +acl_access_allowed( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* requested access rights */ + ) +{ + char *n_edn; /* Normalized DN of the entry */ + int rv; + int err; + int ret_val; + char *right; + int num_handle; + struct acl_pblock *aclpb = NULL; + AclAttrEval *c_attrEval = NULL; + int got_reader_locked = 0; + int deallocate_attrEval = 0; + char ebuf [ BUFSIZ ]; + char *clientDn; + Slapi_DN *e_sdn; + Slapi_Operation *op = NULL; + aclResultReason_t decision_reason; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + slapi_pblock_get(pb, SLAPI_OPERATION, &op); /* for logging */ + + TNF_PROBE_1_DEBUG(acl_access_allowed_start,"ACL","", + tnf_int,access,access); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + /** + * First, if the acl private write/delete on attribute right + * is requested, turn this into SLAPI_ACL_WRITE + * and record the original value. + * Need to make sure that these rights do not clash with the SLAPI + * public rights. This should be easy as the requested rights + * in the aclpb are stored in the bottom byte of aclpb_res_type, + * so if we keep the ACL private bits here too we make sure + * not to clash. + * + */ + + if ( access & (ACLPB_SLAPI_ACL_WRITE_ADD | ACLPB_SLAPI_ACL_WRITE_DEL) ) { + access |= SLAPI_ACL_WRITE; + } + + n_edn = slapi_entry_get_ndn ( e ); + e_sdn = slapi_entry_get_sdn ( e ); + + /* Check if this is a write operation and the database is readonly */ + /* No one, even the rootdn should be allowed to write to the database */ + /* jcm: ReadOnly only applies to the public backends, the private ones */ + /* (the DSEs) should still be writable for configuration. */ + if ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE )) { + int be_readonly, privateBackend; + Slapi_Backend *be; + + slapi_pblock_get ( pb, SLAPI_BE_READONLY, &be_readonly ); + slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + privateBackend = slapi_be_private ( be ); + + if ( !privateBackend && (be_readonly || slapi_config_get_readonly () )){ + slapi_log_error (loglevel, plugin_name, + "conn=%d op=%d (main): Deny %s on entry(%s)" + ": readonly backend\n", + op->o_connid, op->o_opid, + acl_access2str(access), + escape_string_with_punctuation(n_edn,ebuf)); + return LDAP_UNWILLING_TO_PERFORM; + } + } + + /* Check for things we need to skip */ + TNF_PROBE_0_DEBUG(acl_skipaccess_start,"ACL",""); + if ( acl_skip_access_check ( pb, e )) { + slapi_log_error (loglevel, plugin_name, + "conn=%d op=%d (main): Allow %s on entry(%s)" + ": root user\n", + op->o_connid, op->o_opid, + acl_access2str(access), + escape_string_with_punctuation(n_edn,ebuf)); + return(LDAP_SUCCESS); + } + TNF_PROBE_0_DEBUG(acl_skipaccess_end,"ACL",""); + + + /* Get the bindDN */ + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + + /* get the right acl pblock to work with */ + if ( access & SLAPI_ACL_PROXY ) + aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK ); + else + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 1 \n" ); + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + /* check if aclpb is initialized or not */ + TNF_PROBE_0_DEBUG(acl_aclpbinit_start,"ACL",""); + acl_init_aclpb ( pb, aclpb, clientDn, 0 ); + TNF_PROBE_0_DEBUG(acl_aclpbinit_end,"ACL",""); + + + /* Here we mean if "I am trying to add/delete "myself" ? " */ + if (val && (access & SLAPI_ACL_WRITE) && (val->bv_len > 0) ) { + /* should use slapi_sdn_compare() but that'a an extra malloc/free */ + + char *dn_val_to_write = + slapi_dn_normalize(slapi_ch_strdup(val->bv_val)); + + if ( aclpb->aclpb_authorization_sdn && + slapi_utf8casecmp((ACLUCHP)dn_val_to_write, (ACLUCHP) + slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn)) == 0) { + access |= SLAPI_ACL_SELF; + } + + slapi_ch_free( (void **)&dn_val_to_write); + } + + /* Convert access to string of rights eg SLAPI_ACL_ADD->"add". */ + if ((right= acl_access2str(access)) == NULL) { + /* ERROR: unknown rights */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl_access_allowed unknown rights:%d\n", access); + + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + + /* + * Am I a anonymous dude ? then we can use our anonymous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + TNF_PROBE_0_DEBUG(acl_anon_test_start,"ACL",""); + if ( (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ )) && + (clientDn && *clientDn == '\0')) { + aclanom_get_suffix_info(e, aclpb); + ret_val = aclanom_match_profile ( pb, aclpb, e, attr, access ); + if (ret_val != -1 ) { + if (ret_val == LDAP_SUCCESS ) { + decision_reason.reason = ACL_REASON_ANON_ALLOWED; + } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + decision_reason.reason = ACL_REASON_ANON_DENIED; + } + goto cleanup_and_ret; + } + } + TNF_PROBE_0_DEBUG(acl_anon_test_end,"ACL",""); + + /* copy the value into the aclpb for later checking by the value acl code */ + + aclpb->aclpb_curr_attrVal = val; + + if (!(aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) && + (access & SLAPI_ACL_SEARCH)) { + /* We are evaluating SEARCH right for the entry. After that + ** we will eval the READ right. We need to refresh the + ** list of acls selected for evaluation for the entry. + ** Initialize the array so that we indicate nothing has been + ** selected. + */ + aclpb->aclpb_handles_index[0] = -1; + /* access is not allowed on entry for search -- it's for + ** read only. + */ + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_ENTRY; + } + + /* set that this is a new entry */ + aclpb->aclpb_res_type |= ACLPB_NEW_ENTRY; + aclpb->aclpb_access = 0; + aclpb->aclpb_access |= access; + + /* + * stub the Slapi_Entry info first time and only it has changed + * or if the pblock is a psearch pblock--in this case the lifetime + * of entries associated with psearches is such that we cannot cache + * pointers to them--we must always start afresh (see psearch.c). + */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if ( operation_is_flag_set(op, OP_FLAG_PS) || + (aclpb->aclpb_curr_entry_sdn == NULL) || + (slapi_sdn_compare ( aclpb->aclpb_curr_entry_sdn, e_sdn) != 0)) { + + TNF_PROBE_0_DEBUG(acl_entry_first_touch_start,"ACL",""); + + slapi_log_error(loglevel, plugin_name, + "#### conn=%d op=%d binddn=\"%s\"\n", + op->o_connid, op->o_opid, clientDn); + aclpb->aclpb_stat_total_entries++; + + if (!(access & SLAPI_ACL_PROXY) && + !( aclpb->aclpb_state & ACLPB_DONOT_EVALUATE_PROXY )) { + Acl_PBlock *proxy_pb; + + proxy_pb = acl_get_aclpb( pb, ACLPB_PROXYDN_PBLOCK ); + if (proxy_pb) { + TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_start,"ACL",""); + ret_val = acl_access_allowed( pb, e, attr, val, SLAPI_ACL_PROXY ); + TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_end,"ACL",""); + + if (ret_val != LDAP_SUCCESS) goto cleanup_and_ret; + } + } + if ( access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_num_entries++; + + if ( aclpb->aclpb_num_entries == 1) { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + } else if ( aclpb->aclpb_state & ACLPB_COPY_EVALCONTEXT ) { + /* We need to copy the evalContext */ + acl_copyEval_context ( aclpb, &aclpb->aclpb_curr_entryEval_context, + &aclpb->aclpb_prev_entryEval_context, 0 ); + aclpb->aclpb_state &= ~ACLPB_COPY_EVALCONTEXT; + } + acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 1 /*scrub */); + } + + /* reset the cached result based on the scope */ + acl__reset_cached_result (aclpb ); + + /* Find all the candidate aci's that apply by scanning up the DIT tree from edn. */ + + TNF_PROBE_0_DEBUG(acl_aciscan_start,"ACL",""); + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + slapi_sdn_set_dn_byval ( aclpb->aclpb_curr_entry_sdn, n_edn ); + acllist_aciscan_update_scan ( aclpb, n_edn ); + TNF_PROBE_0_DEBUG(acl_aciscan_end,"ACL",""); + + /* Keep the ptr to the current entry */ + aclpb->aclpb_curr_entry = (Slapi_Entry *) e; + + /* Get the attr info */ + deallocate_attrEval = acl__get_attrEval ( aclpb, attr ); + + aclutil_print_resource ( aclpb, right, attr, clientDn ); + + /* + * Used to be PListInitProp(aclpb->aclpb_proplist, 0, + * DS_ATTR_ENTRY, e, 0); + * + * The difference is that PListInitProp() allocates a new property + * every time it's called, overwriting the old name in the PList hash + * table, but not freeing the original property. + * Now, we just create the property at aclpb_malloc() time and + * Assign a new value each time. + */ + + rv = PListAssignValue(aclpb->aclpb_proplist, + DS_ATTR_ENTRY, e, 0); + + if (rv < 0) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Unable to set the Slapi_Entry in the Plist\n",0,0,0); + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + TNF_PROBE_0_DEBUG(acl_entry_first_touch_end,"ACL",""); + + } else { + /* we are processing the same entry but for a different + ** attribute. If access is already allowed on that, entry, then + ** it's not a new entry anymore. It's the same old one. + */ + + TNF_PROBE_0_DEBUG(acl_entry_subs_touch_start,"ACL",""); + + aclpb->aclpb_res_type &= ~ACLPB_NEW_ENTRY; + + /* Get the attr info */ + deallocate_attrEval = acl__get_attrEval ( aclpb, attr ); + + TNF_PROBE_0_DEBUG(acl_entry_subs_touch_end,"ACL",""); + + } + + /* get a lock for the reader */ + acllist_acicache_READ_LOCK(); + got_reader_locked = 1; + + /* + ** Check if we can use any cached information to determine + ** access to this resource + */ + if ( (access & SLAPI_ACL_SEARCH) && + (ret_val = acl__match_handlesFromCache ( aclpb , attr, access)) != -1) { + /* means got a result: allowed or not*/ + + if (ret_val == LDAP_SUCCESS ) { + decision_reason.reason = ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + goto cleanup_and_ret; + } + + /* + ** Now we have all the information about the resource. Now we need to + ** figure out if there are any ACLs which can be applied. + ** If no ACLs are there, then it's a DENY as default. + */ + if (!(num_handle = acl__scan_for_acis(aclpb, &err))) { + + /* We might have accessed the ACL first time which could + ** have caused syntax error. + */ + if ( err == ACL_ONEACL_TEXT_ERR) + ret_val = LDAP_INVALID_SYNTAX; + else { + ret_val = LDAP_INSUFFICIENT_ACCESS; + decision_reason.reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS; + } + goto cleanup_and_ret; + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Processed attr:%s for entry:%s\n", attr ? attr : "NULL", + ACL_ESCAPE_STRING_WITH_PUNCTUATION ( n_edn, ebuf), 0); + + /* + ** Now evaluate the rights. + ** This is what we have been waiting for. + ** The return value should be ACL_RES_DENY or ACL_RES_ALLOW. + */ + rv = acl__TestRights(aclpb, access, &right, ds_map_generic, + &decision_reason); + if ( rv != ACL_RES_ALLOW && (0 == strcasecmp ( right, "selfwrite") ) ) { + /* If I am adding myself to a group, we don't need selfwrite always, + ** write priv is good enough. Since libaccess doesn't provide me a nice + ** way to evaluate OR rights, I have to try again with wite priv. + ** bug: 339051 + */ + right = access_str_write; + rv = acl__TestRights(aclpb, access, &right, ds_map_generic, + &decision_reason); + } + + if (rv == ACL_RES_ALLOW) { + ret_val = LDAP_SUCCESS; + } else { + ret_val = LDAP_INSUFFICIENT_ACCESS; + } + +cleanup_and_ret: + + TNF_PROBE_0_DEBUG(acl_cleanup_start,"ACL",""); + + /* I am ready to get out. */ + if ( got_reader_locked ) acllist_acicache_READ_UNLOCK(); + + /* Store the status of the evaluation for this attr */ + if ( aclpb && (c_attrEval = aclpb->aclpb_curr_attrEval )) { + if ( deallocate_attrEval ) { + /* In this case we are not caching the result as + ** we have too many attrs. we have malloced space. + ** Get rid of it. + */ + slapi_ch_free ( (void **) &c_attrEval->attrEval_name ); + slapi_ch_free ( (void **) &c_attrEval ); + } else if (ret_val == LDAP_SUCCESS ) { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_status |= ACL_ATTREVAL_SUCCESS; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_SUCCESS; + else + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID; + } else { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_status |= ACL_ATTREVAL_FAIL; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_FAIL; + else + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID; + } + } + + if ( aclpb ) aclpb->aclpb_curr_attrEval = NULL; + + print_access_control_summary( "main", ret_val, clientDn, aclpb, right, + (attr ? attr : "NULL"), + escape_string_with_punctuation (n_edn, ebuf), + &decision_reason); + TNF_PROBE_0_DEBUG(acl_cleanup_end,"ACL",""); + + TNF_PROBE_0_DEBUG(acl_access_allowed_end,"ACL",""); + + return(ret_val); + +} + +static void print_access_control_summary( char *source, int ret_val, char *clientDn, + struct acl_pblock *aclpb, + char *right, + char *attr, + const char *edn, + aclResultReason_t *acl_reason) +{ + struct codebook { + int code; + char *text; + }; + + static struct codebook reasonbook[] = { + {ACL_REASON_NO_ALLOWS, "no allow acis"}, + {ACL_REASON_RESULT_CACHED_DENY, "cached deny"}, + {ACL_REASON_RESULT_CACHED_ALLOW, "cached allow"}, + {ACL_REASON_EVALUATED_ALLOW, "allowed"}, + {ACL_REASON_EVALUATED_DENY, "denied"}, + {ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, "no aci matched the resource"}, + {ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS, "no aci matched the subject"}, + {ACL_REASON_ANON_ALLOWED, "allow anyone aci matched anon user"}, + {ACL_REASON_ANON_DENIED, "no matching anyone aci for anon user"}, + {ACL_REASON_EVALCONTEXT_CACHED_ALLOW, "cached context/parent allow"}, + {ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED, "cached context/parent deny"}, + {ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW, "cached context/parent allow any attr"}, + {ACL_REASON_NONE, "error occurred"}, + }; + + char *anon = "anonymous"; + char *null_user = "NULL"; /* bizare case */ + char *real_user = NULL; + char *proxy_user = NULL; + char *access_allowed_string = "Allow"; + char *access_not_allowed_string = "Deny"; + char *access_error_string = "access_error"; + char *access_status = NULL; + char *access_reason_none = "no reason available"; + char *access_reason = access_reason_none; + char acl_info[ BUFSIZ ]; + Slapi_Operation *op = NULL; + int loglevel; + int i; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + if ( !slapi_is_loglevel_set(loglevel) ) { + return; + } + + slapi_pblock_get(aclpb->aclpb_pblock, SLAPI_OPERATION, &op); /* for logging */ + + if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + access_status = access_not_allowed_string; + } else if ( ret_val == LDAP_SUCCESS) { + access_status = access_allowed_string; + } else { /* some kind of error */ + access_status = access_error_string; + } + + /* decode the reason */ + for (i = 0; i < sizeof(reasonbook) / sizeof(struct codebook); i++) { + if ( acl_reason->reason == reasonbook[i].code ) { + access_reason = reasonbook[i].text; + break; + } + } + + /* get the acl */ + acl_info[0] = '\0'; + if (acl_reason->deciding_aci) { + if (acl_reason->reason == ACL_REASON_RESULT_CACHED_DENY || + acl_reason->reason == ACL_REASON_RESULT_CACHED_ALLOW) { + /* acl is in cache. Its detail must have been printed before. + * So no need to print out acl detail this time. + */ + PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d)", + access_reason, + acl_reason->deciding_aci->aci_index); + } + else { + PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d): aciname=%s, acidn=\"%s\"", + access_reason, + acl_reason->deciding_aci->aci_index, + acl_reason->deciding_aci->aclName, + slapi_sdn_get_ndn (acl_reason->deciding_aci->aci_sdn) ); + } + } + + /* Say who was denied access */ + + if (clientDn) { + if (clientDn[0] == '\0') { + /* anon */ + real_user = anon; + } else { + real_user = clientDn; + } + } else { + real_user = null_user; + } + + /* Is there a proxy */ + + if ( aclpb != NULL && aclpb->aclpb_proxy != NULL) { + + if ( aclpb->aclpb_authorization_sdn != NULL ) { + + proxy_user = (char *)(aclpb->aclpb_authorization_sdn->ndn ? + aclpb->aclpb_authorization_sdn->ndn: + null_user); + + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + proxy_user, + acl_info[0] ? acl_info : access_reason); + } else { + proxy_user = null_user; + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + proxy_user, + acl_info[0] ? acl_info : access_reason); + } + } else{ + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + acl_info[0] ? acl_info : access_reason); + } + + +} +/*************************************************************************** +* +* acl_read_access_allowed_on_entry +* check read access control on the given entry. +* +* Only used during seearch to test for read access on the entry. +* (Could be generalized). +* +* attrs is the list of requested attributes passed with the search. +* If the entry has no attributes (weird case) then the routine survives. +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - access allowed +* LDAP_INSUFFICIENT_ACCESS - access denied +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_read_access_allowed_on_entry ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char **attrs, + int access /* access rights */ + ) +{ + + struct acl_pblock *aclpb; + Slapi_Attr *currAttr; + Slapi_Attr *nextAttr; + int len; + int attr_index = -1; + char *attr_type = NULL; + int rv, isRoot; + char *clientDn; + unsigned long flags; + aclResultReason_t decision_reason; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_start ,"ACL",""); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + + /* + ** If it's the root, or acl is off or the entry is a rootdse, + ** Then you have the privilege to read it. + */ + if ( acl_skip_access_check ( pb, e ) ) { + char *n_edn = slapi_entry_get_ndn ( e ); + char ebuf [ BUFSIZ ]; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Root access (%s) allowed on entry(%s)\n", + acl_access2str(access), + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf)); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,skip_access,""); + return LDAP_SUCCESS; + } + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 2 \n" ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,end,"aclpb error"); + return LDAP_OPERATIONS_ERROR; + } + + /* + * Am I a anonymous dude ? then we can use our anonympous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + if ( clientDn && *clientDn == '\0' ) { + int ret_val; + ret_val = aclanom_match_profile ( pb, aclpb, e, NULL, SLAPI_ACL_READ ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,end,"anon"); + + if (ret_val != -1 ) return ret_val; + } + + aclpb->aclpb_state &= ~ACLPB_RESET_MASK; + if (aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + int ret_val; + ret_val = acl__attr_cached_result (aclpb, NULL, SLAPI_ACL_READ); + if (ret_val != -1 ) { + /* print summary if loglevel set */ + if ( slapi_is_loglevel_set(loglevel) ) { + char *n_edn; + n_edn = slapi_entry_get_ndn ( e ); + if ( ret_val == LDAP_SUCCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + /* + * pass NULL as the attr as this routine is concerned with + * access at the entry level. + */ + print_access_control_summary( "on entry", + ret_val, clientDn, aclpb, + acl_access2str(SLAPI_ACL_READ), + NULL, n_edn, + &decision_reason); + } + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,eval_context_cached,""); + + return ret_val; + } + } + + /* + * Currently do not use this code--it results in confusing + * behaviour..see 529905 + */ +#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + + /* Do we have access to the entry by virtue of + ** having access to an attr. Before that, let's find out which attrs + ** the user want. If the user has specified certain attributes, then + ** we check aginst that set of attributes. + */ + if (!((aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS) || + (aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS))) { + int i; + if (attrs == NULL) { + aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS; + } else { + for ( i = 0; attrs != NULL && attrs[i] != NULL; i++ ) { + if ( strcmp( LDAP_ALL_USER_ATTRS, attrs[i] ) == 0 ) { + aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS; + break; + } + } + } + + if (!(aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS)) { + for (i = 0; attrs != NULL && attrs[i] != NULL; i++ ) { + if ( !slapi_entry_attr_find ( e, attrs[i], &currAttr ) ) { + aclpb->aclpb_state |= ACLPB_USER_SPECIFIED_ATTARS; + break; + } + } + } + } /* end of all user test*/ + + + /* + ** If user has specified a list of attrs, might as well use it + ** to determine access control. + */ + currAttr = NULL; + attr_index = -1; + if ( aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS) { + attr_index = 0; + attr_type = attrs[attr_index++]; + } else { + /* Skip the operational attributes -- if there are any in the front */ + slapi_entry_first_attr ( e, &currAttr ); + if (currAttr != NULL) { + slapi_attr_get_flags ( currAttr, &flags ); + while ( flags & SLAPI_ATTR_FLAG_OPATTR ) { + flags = 0; + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags ); + currAttr = nextAttr; + } + + /* Get the attr type */ + if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type ); + } + } + +#endif /*DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES*/ + +#ifndef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + + /* + * Here look at each attribute in the entry and see if + * we have read access to it--if we do + * and we are not denied access to the entry then this + * is taken as implying access to the entry. + */ + slapi_entry_first_attr ( e, &currAttr ); + if (currAttr != NULL) { + slapi_attr_get_type ( currAttr , &attr_type ); + } +#endif + aclpb->aclpb_state |= ACLPB_EVALUATING_FIRST_ATTR; + + while (attr_type) { + if (acl_access_allowed (pb, e,attr_type, NULL, + SLAPI_ACL_READ) == LDAP_SUCCESS) { + /* + ** We found a rule which requires us to test access + ** to the entry. + */ + if ( aclpb->aclpb_state & ACLPB_FOUND_A_ENTRY_TEST_RULE){ + /* Do I have access on the entry itself */ + if (acl_access_allowed (pb, e, NULL, + NULL, access) != LDAP_SUCCESS) { + /* How was I denied ? + ** I could be denied on a DENY rule or because + ** there is no allow rule. If it's a DENY from + ** a DENY rule, then we don't have access to + ** the entry ( nice trick to get in ) + */ + if ( aclpb->aclpb_state & + ACLPB_EXECUTING_DENY_HANDLES) + return LDAP_INSUFFICIENT_ACCESS; + + /* The other case is I don't have an + ** explicit allow rule -- which is fine. + ** Since, I am already here, it means that I have + ** an implicit allow to the entry. + */ + } + } + aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR; + + /* + ** If we are not sending all the attrs, then we must + ** make sure that we have right on a attr that we are + ** sending + */ + len = strlen(attr_type); + if ( len > ACLPB_MAX_ATTR_LEN) { + slapi_ch_free ( (void **) &aclpb->aclpb_Evalattr); + aclpb->aclpb_Evalattr = slapi_ch_malloc(len); + } + strncpy (aclpb->aclpb_Evalattr, attr_type, len); + aclpb->aclpb_Evalattr[len] = '\0'; + if ( attr_index >= 0 ) { + /* + * access was granted to one of the user specified attributes + * which was found in the entry and that attribute is + * now in aclpb_Evalattr + */ + aclpb->aclpb_state |= + ACLPB_ACCESS_ALLOWED_USERATTR; + } else { + /* + * Access was granted to _an_ attribute in the entry and that + * attribute is now in aclpb_Evalattr + */ + aclpb->aclpb_state |= + ACLPB_ACCESS_ALLOWED_ON_A_ATTR; + } + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end , "ACL","", + tnf_string,called_access_allowed,""); + + return LDAP_SUCCESS; + } else { + /* try the next one */ + attr_type = NULL; + if (attr_index >= 0) { + attr_type = attrs[attr_index++]; + } else { + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( rv != 0 ) break; + currAttr = nextAttr; + slapi_attr_get_flags ( currAttr, &flags ); + while ( flags & SLAPI_ATTR_FLAG_OPATTR ) { + flags = 0; + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags ); + currAttr = nextAttr; + } + /* Get the attr type */ + if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type ); + } + } + } + + /* + ** That means. we have searched thru all the attrs and found + ** access is denied on all attrs. + ** + ** If there were no attributes in the entry at all (can have + ** such entries thrown up by the b/e, then we do + ** not have such an implied access. + */ + aclpb->aclpb_state |= ACLPB_ACCESS_DENIED_ON_ALL_ATTRS; + aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR; + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL",""); + + return LDAP_INSUFFICIENT_ACCESS; +} + +/*************************************************************************** +* +* acl_read_access_allowed_on_attr +* check access control on the given attr. +* +* Only used during search to test for read access to an attr. +* (Could be generalized) +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - access allowed +* LDAP_INSUFFICIENT_ACCESS - access denied +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_read_access_allowed_on_attr ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* access rights */ + ) +{ + + struct acl_pblock *aclpb = NULL; + char ebuf [ BUFSIZ ]; + char *clientDn = NULL; + char *n_edn; + aclResultReason_t decision_reason; + int ret_val = -1; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_start ,"ACL",""); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + /* I am here, because I have access to the entry */ + + n_edn = slapi_entry_get_ndn ( e ); + + /* If it's the root or acl is off or rootdse, he has all the priv */ + if ( acl_skip_access_check ( pb, e ) ) { + char ebuf [ BUFSIZ ]; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Root access (%s) allowed on entry(%s)\n", + acl_access2str(access), + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,skip_aclcheck,""); + + return LDAP_SUCCESS; + } + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 3 \n" ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclpb_error,""); + + return LDAP_OPERATIONS_ERROR; + } + + /* + * Am I a anonymous dude ? then we can use our anonympous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + slapi_pblock_get (pb, SLAPI_REQUESTOR_DN ,&clientDn ); + if ( clientDn && *clientDn == '\0' ) { + ret_val = aclanom_match_profile ( pb, aclpb, e, attr, + SLAPI_ACL_READ ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,anon_decision,""); + if (ret_val != -1 ) return ret_val; + } + + /* Then I must have a access to the entry. */ + aclpb->aclpb_state |= ACLPB_ACCESS_ALLOWED_ON_ENTRY; + + if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + + ret_val = acl__attr_cached_result (aclpb, attr, SLAPI_ACL_READ); + if (ret_val != -1 ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "MATCHED HANDLE:dn:%s attr: %s val:%d\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), attr, + ret_val ); + if ( ret_val == LDAP_SUCCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + goto acl_access_allowed_on_attr_Exit; + } else { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + } + } + + if (aclpb->aclpb_state & ACLPB_ACCESS_DENIED_ON_ALL_ATTRS) { + /* access is denied on all the attributes */ + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,deny_all_attrs,""); + + return LDAP_INSUFFICIENT_ACCESS; + } + + /* do I have access to all the entries by virtue of having aci + ** rules with targetattr ="*". If yes, then allow access to + ** rest of the attributes. + */ + if (aclpb->aclpb_state & ACLPB_ATTR_STAR_MATCHED) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "STAR Access allowed on attr:%s; entry:%s \n", + attr, ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0); + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW; + ret_val = LDAP_SUCCESS; + goto acl_access_allowed_on_attr_Exit; + + } + + if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_ON_A_ATTR) { + + /* access is allowed on that attr. + ** for example: Slapi_Entry: cn, sn. phone, uid, passwd, address + ** We found that access is allowed on phone. That means the + ** -- access is denied on cn, sn + ** -- access is allowed on phone + ** -- Don't know about the rest. Need to evaluate. + */ + + if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) { + /* from now on we need to evaluate access on + ** rest of the attrs. + */ + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_A_ATTR; + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclp_Evalattr1,""); + + return LDAP_SUCCESS; + } else { + /* + * Here, the attr that implied access to the entry (aclpb_Evalattr), + * is not + * the one we currently want evaluated--so + * we need to evaluate access to attr--so fall through. + */ + } + + } else if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_USERATTR) { + /* Only skip evaluation on the user attr on which we have + ** evaluated before. + */ + if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) { + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_USERATTR; + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclp_Evalattr2,""); + return LDAP_SUCCESS; + } + } + + /* we need to evaluate the access on this attr */ + return ( acl_access_allowed(pb, e, attr, val, access) ); + + /* This exit point prints a summary and returns ret_val */ +acl_access_allowed_on_attr_Exit: + + /* print summary if loglevel set */ + if ( slapi_is_loglevel_set(loglevel) ) { + + print_access_control_summary( "on attr", + ret_val, clientDn, aclpb, + acl_access2str(SLAPI_ACL_READ), + attr, n_edn, &decision_reason); + } + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL",""); + + return(ret_val); +} +/*************************************************************************** +* +* acl_check_mods +* check access control on the given entry to see if +* it allows the given modifications by the user associated with op. +* +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - mods allowed ok +* <err> - same return value as acl_access_allowed() +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_check_mods( + Slapi_PBlock *pb, + Slapi_Entry *e, + LDAPMod **mods, + char **errbuf +) +{ + int i; + int rv, accessCheckDisabled; + int lastmod = 0; + Slapi_Attr *attr = NULL; + char *n_edn; + Slapi_Backend *be = NULL; + Slapi_DN *e_sdn; + Acl_PBlock *aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK ); + LDAPMod *mod; + Slapi_Mods smods; + + rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled ); + if ( rv != -1 && accessCheckDisabled ) return LDAP_SUCCESS; + + if ( NULL == aclpb ) + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + n_edn = slapi_entry_get_ndn ( e ); + e_sdn = slapi_entry_get_sdn ( e ); + + slapi_mods_init_byref(&smods,mods); + + for (mod = slapi_mods_get_first_mod(&smods); + mod != NULL; + mod = slapi_mods_get_next_mod(&smods)) { + switch (mod->mod_op & ~LDAP_MOD_BVALUES ) { + + case LDAP_MOD_DELETE: + if (mod->mod_bvalues != NULL ) { + break; + } + + /* + * Here, check that we have the right to delete all + * the values of the attribute in the entry. + */ + + case LDAP_MOD_REPLACE: + if ( !lastmod ) { + if (be == NULL) { + if (slapi_pblock_get( pb, SLAPI_BACKEND, &be )) { + be = NULL; + } + } + if (be != NULL) + slapi_pblock_get ( pb, SLAPI_BE_LASTMOD, &lastmod ); + } + if (lastmod && + (strcmp (mod->mod_type, "modifiersname")== 0 || + strcmp (mod->mod_type, "modifytimestamp")== 0)) { + continue; + } + + slapi_entry_attr_find (e, mod->mod_type, &attr); + if ( attr != NULL) { + Slapi_Value *sval=NULL; + const struct berval *attrVal=NULL; + int k= slapi_attr_first_value(attr,&sval); + while(k != -1) { + attrVal = slapi_value_get_berval(sval); + rv = slapi_access_allowed (pb, e, + mod->mod_type, + (struct berval *)attrVal, /* XXXggood had to cast away const - BAD */ + ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */ + if ( rv != LDAP_SUCCESS) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return(rv); + } + k= slapi_attr_next_value(attr, k, &sval); + } + } + else { + rv = slapi_access_allowed (pb, e, + mod->mod_type, + NULL, + ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */ + if ( rv != LDAP_SUCCESS) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return(rv); + } + } + break; + + default: + break; + } /* switch */ + + /* + * Check that we have add/delete writes on the specific values + * we are trying to add. + */ + + if ( aclpb && aclpb->aclpb_curr_entry_sdn ) + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + + if ( mod->mod_bvalues != NULL ) { + + /* + * Here, there are specific values specified. + * For add and replace--we need add rights for these values. + * For delete we need delete rights for these values. + */ + + for ( i = 0; mod->mod_bvalues[i] != NULL; i++ ) { + + if ( ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || + ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_REPLACE)) { + + rv = acl_access_allowed (pb,e, + mod->mod_type, + mod->mod_bvalues[i], + ACLPB_SLAPI_ACL_WRITE_ADD); /*was SLAPI_ACL_WRITE*/ + } else if ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + rv = acl_access_allowed (pb,e, + mod->mod_type, + mod->mod_bvalues[i], + ACLPB_SLAPI_ACL_WRITE_DEL); /*was SLAPI_ACL_WRITE*/ + } else { + rv = LDAP_INSUFFICIENT_ACCESS; + } + + if ( rv != LDAP_SUCCESS ) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return rv; + } + /* Need to check for all the values because + ** we may be modifying a "self<right>" value. + */ + + /* Are we adding/replacing a aci attribute + ** value. In that case, we need to make + ** sure that the new value has thr right + ** syntax + */ + if (strcmp(mod->mod_type, + aci_attr_type) == 0) { + if ( 0 != (rv = acl_verify_syntax( e_sdn, + mod->mod_bvalues[i]))) { + aclutil_print_err(rv, e_sdn, + mod->mod_bvalues[i], + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return LDAP_INVALID_SYNTAX; + } + } + } /* for */ + } + } /* end of big for */ + /* Cleanup */ + slapi_mods_done(&smods); + return( LDAP_SUCCESS ); +} +/*************************************************************************** +* +* acl_modified +* Modifies ( removed, add, changes) the ACI LIST. +* +* Input: +* int *optype - op code +* char *dn - DN of the entry +* void *change - The change struct which contais the +* - change value +* +* Returns: +* None. +* +* Error Handling: +* None. +* +**************************************************************************/ +extern void +acl_modified (Slapi_PBlock *pb, int optype, char *n_dn, void *change) +{ + struct berval **bvalue; + char **value; + int rv=0; /* returned value */ + char* new_RDN; + char* parent_DN; + char* new_DN; + LDAPMod **mods; + struct berval b; + int j; + Slapi_Attr *attr = NULL; + Slapi_Entry *e = NULL; + char ebuf [ BUFSIZ]; + Slapi_DN *e_sdn; + aclUserGroup *ugroup = NULL; + + e_sdn = slapi_sdn_new_ndn_byval ( n_dn ); + /* Before we proceed, Let's first check if we are changing any groups. + ** If we are, then we need to change the signature + */ + switch ( optype ) { + case SLAPI_OPERATION_MODIFY: + case SLAPI_OPERATION_DELETE: + slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, (void*)&e); + break; + case SLAPI_OPERATION_ADD: + e = (Slapi_Entry *)change; + break; + } + + /* e can be null for RDN */ + if ( e ) slapi_entry_attr_find( e, "objectclass", &attr); + + if ( attr ) { + int group_change = 0; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + i= slapi_attr_first_value ( attr,&sval ); + while(i != -1) { + attrVal = slapi_value_get_berval ( sval ); + if ( (strcasecmp (attrVal->bv_val, "groupOfNames") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfUniqueNames") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfCertificates") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfURLs") == 0 ) ) { + group_change= 1; + if ( optype == SLAPI_OPERATION_MODIFY ) { + Slapi_Attr *a = NULL; + int rv; + rv = slapi_entry_attr_find ( e, "uniqueMember", &a); + if ( rv != 0 ) break; + rv = slapi_entry_attr_find ( e, "Member", &a ); + if ( rv != 0 ) break; + rv = slapi_entry_attr_find ( e, "MemberURL", &a ); + if ( rv != 0 ) break; + /* That means we are not changing the member + ** list, so it's okay to let go this + ** change + */ + group_change = 0; + } + break; + } + i= slapi_attr_next_value ( attr, i, &sval ); + } + + /* + ** We can do better here XXX, i.e invalidate the cache for users who + ** use this group. for now just do the whole thing. + */ + if ( group_change ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Group Change: Invalidating entire UserGroup Cache\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf)); + aclg_regen_group_signature(); + if ( (optype == SLAPI_OPERATION_MODIFY) || (optype == SLAPI_OPERATION_DELETE ) ) { + /* Then we need to invalidate the acl signature also */ + acl_signature = aclutil_gen_signature ( acl_signature ); + } + } + } + + /* + * Here if the target entry is in the group cache + * as a user then, as it's being changed it may move out of any dynamic + * groups it belongs to. + * Just remove it for now--can do better XXX by checking to see if + * it really needs to be removed by testing to see if he's + * still in th group after the change--but this requires keeping + * the memberURL of the group which we don't currently do. + * Also, if we keep + * the attributes that are being used in dynamic + * groups then we could only remove the user if a sensitive + * attribute was being modified (rather than scanning the whole user cache + * all the time). Also could do a hash lookup. + * + * aclg_find_userGroup() incs a refcnt so we can still refer to ugroup. + * aclg_markUgroupForRemoval() decs it and marks it for removal + * , so after that you cannot refer to ugroup. + * + */ + + if ( (ugroup = aclg_find_userGroup(n_dn)) != NULL) { + /* + * Mark this for deletion next time round--try to impact + * this mainline code as little as possible. + */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Marking entry %s for removal from ACL user Group Cache\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf)); + aclg_markUgroupForRemoval (ugroup); + } + + /* + * Take the write lock around all the mods--so that + * other operations will see the acicache either before the whole mod + * or after but not, as it was before, during the mod. + * This is in line with the LDAP concept of the operation + * on the whole entry being the atomic unit. + * + */ + + switch(optype) { + case SLAPI_OPERATION_DELETE: + /* In this case we have already checked if the user has + ** right to delete the entry. Part of delete of entry is + ** remove all the ACLs also. + */ + + acllist_acicache_WRITE_LOCK(); + rv = acllist_remove_aci_needsLock(e_sdn, NULL); + acllist_acicache_WRITE_UNLOCK(); + + break; + case SLAPI_OPERATION_ADD: + slapi_entry_attr_find ( (Slapi_Entry *) change, aci_attr_type, &attr ); + + if ( attr ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + acllist_acicache_WRITE_LOCK(); + i= slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval(sval); + rv= acllist_insert_aci_needsLock(e_sdn, attrVal ); + if (rv <= ACL_ERR) + aclutil_print_err(rv, e_sdn, attrVal, NULL); + /* Print the aci list */ + i= slapi_attr_next_value ( attr, i, &sval ); + } + acllist_acicache_WRITE_UNLOCK(); + } + break; + + case SLAPI_OPERATION_MODIFY: + { + int got_write_lock = 0; + + mods = (LDAPMod **) change; + + for (j=0; mods[j] != NULL; j++) { + if (strcasecmp(mods[j]->mod_type, aci_attr_type) == 0) { + + /* Got an aci to mod in this list of mods, so + * take the acicache lock for the whole list of mods, + * remembering to free it below. + */ + if ( !got_write_lock) { + acllist_acicache_WRITE_LOCK(); + got_write_lock = 1; + } + + switch (mods[j]->mod_op & ~LDAP_MOD_BVALUES) { + case LDAP_MOD_REPLACE: + /* First remove the item */ + rv = acllist_remove_aci_needsLock(e_sdn, NULL); + + /* now fall thru to add the new one */ + case LDAP_MOD_ADD: + /* Add the new aci */ + if (mods[j]->mod_op & LDAP_MOD_BVALUES) { + bvalue = mods[j]->mod_bvalues; + if (bvalue == NULL) + break; + for (; *bvalue != NULL; ++bvalue) { + rv=acllist_insert_aci_needsLock( e_sdn, *bvalue); + if (rv <= ACL_ERR) { + aclutil_print_err(rv, e_sdn, + *bvalue, NULL); + } + } + } else { + value = mods[j]->mod_values; + if (value == NULL) + break; + for (; *value != NULL; ++value) { + b.bv_len = strlen (*value); + b.bv_val = *value; + rv=acllist_insert_aci_needsLock( e_sdn, &b); + if (rv <= ACL_ERR) { + aclutil_print_err(rv, e_sdn, + &b, NULL); + } + } + } + break; + case LDAP_MOD_DELETE: + if (mods[j]->mod_op & LDAP_MOD_BVALUES) { + bvalue = mods[j]->mod_bvalues; + if (bvalue == NULL || *bvalue == NULL) { + rv = acllist_remove_aci_needsLock( e_sdn, NULL); + } else { + for (; *bvalue != NULL; ++bvalue) + acllist_remove_aci_needsLock( e_sdn, *bvalue); + } + } else { + value = mods[j]->mod_values; + if (value == NULL || *value == NULL) { + acllist_remove_aci_needsLock( e_sdn,NULL); + } else { + for (; *value != NULL; ++value) { + b.bv_len = strlen (*value); + b.bv_val = *value; + acllist_remove_aci_needsLock( e_sdn, &b); + } + } + + } + break; + + default: + break; + }/* modtype switch */ + }/* attrtype is aci */ + } /* end of for */ + if ( got_write_lock ) { + acllist_acicache_WRITE_UNLOCK(); + got_write_lock = 0; + } + + break; + }/* case op is modify*/ + + case SLAPI_OPERATION_MODRDN: + + new_RDN = (char*) change; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl_modified (MODRDN %s => \"%s\"\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_dn, ebuf), new_RDN, 0); + + /* compute new_DN: */ + parent_DN = slapi_dn_parent (n_dn); + if (parent_DN == NULL) { + new_DN = new_RDN; + } else { + new_DN = (char*) slapi_ch_malloc (strlen (new_RDN) + 3 + + strlen (parent_DN)); + strcpy (new_DN, new_RDN); + strcat (new_DN, ","); + strcat (new_DN, parent_DN); + slapi_dn_normalize (new_DN); + } + + /* Change the acls */ + acllist_acicache_WRITE_LOCK(); + acllist_moddn_aci_needsLock ( e_sdn, new_DN ); + acllist_acicache_WRITE_UNLOCK(); + + /* deallocat the parent_DN */ + if (parent_DN != NULL) { + slapi_ch_free ( (void **) &new_DN ); + slapi_ch_free ( (void **) &parent_DN ); + } + break; + + default: + /* print ERROR */ + break; + } /*optype switch */ + + slapi_sdn_free ( &e_sdn ); + +} +/*************************************************************************** +* +* acl__scan_for_acis +* Scan the list and picup the correct acls for evaluation. +* +* Input: +* Acl_PBlock *aclpb - Main ACL pblock +* int *err; - Any error status +* Returns: +* num_handles - Number of handles matched to the +* - resource + 1. +* Error Handling: +* None. +* +**************************************************************************/ +static int +acl__scan_for_acis(Acl_PBlock *aclpb, int *err) +{ + aci_t *aci; + NSErr_t errp; + int attr_matched; + int deny_handle; + int allow_handle; + int gen_allow_handle = ACI_MAX_ELEVEL+1; + int gen_deny_handle = ACI_MAX_ELEVEL+1; + int i; + PRUint32 cookie; + + TNF_PROBE_0_DEBUG(acl__scan_for_acis_start,"ACL",""); + + /* + ** Determine if we are traversing via the list Vs. we have our own + ** generated list + */ + if ( aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST || + aclpb->aclpb_handles_index[0] != -1 ) { + int kk = 0; + while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] != -1 ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Using ACL Cointainer:%d for evaluation\n", kk); + kk++; + } + } + + memset (&errp, 0, sizeof(NSErr_t)); + *err = ACL_FALSE; + aclpb->aclpb_num_deny_handles = -1; + aclpb->aclpb_num_allow_handles = -1; + for (i=0; i <= ACI_MAX_ELEVEL; i++) { + aclpb->aclpb_deny_handles [i] = NULL; + aclpb->aclpb_allow_handles [i] = NULL; + } + + /* Check the signature. If it has changed, start fresh */ + if ( aclpb->aclpb_signature != acl_signature ) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Restart the scan -- due to acl changes\n"); + acllist_init_scan ( aclpb->aclpb_pblock, LDAP_SCOPE_BASE, NULL ); + } + + attr_matched = ACL_FALSE; + deny_handle = 0; + allow_handle = 0; + i = 0; + + aclpb->aclpb_stat_acllist_scanned++; + aci = acllist_get_first_aci ( aclpb, &cookie ); + + while( aci ) { + if (acl__resource_match_aci(aclpb, aci, 0, &attr_matched)) { + /* Generate the ACL list handle */ + if (aci->aci_handle == NULL) { + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + continue; + } + aclutil_print_aci (aci, acl_access2str (aclpb->aclpb_access)); + + if (aci->aci_type & ACI_HAS_DENY_RULE) { + if (aclpb->aclpb_deny_handles[aci->aci_elevel] == NULL ) { + aclpb->aclpb_deny_handles[aci->aci_elevel] = aci; + } else { + if ((gen_deny_handle + ACI_DEFAULT_ELEVEL + 1) == + aclpb->aclpb_deny_handles_size ) { + int num = ACLPB_INCR_LIST_HANDLES + + aclpb->aclpb_deny_handles_size; + /* allocate more space */ + aclpb->aclpb_deny_handles = + (aci_t **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_deny_handles, + num * sizeof (aci_t *)); + aclpb->aclpb_deny_handles_size = num; + } + aclpb->aclpb_deny_handles [gen_deny_handle] = aci; + gen_deny_handle++; + } + deny_handle++; + } + /* + ** It's possible that a single acl is in both the camps i.e + ** It has a allow and a deny rule + ** In that case we keep the same acl in both the camps. + */ + if (aci->aci_type & ACI_HAS_ALLOW_RULE) { + if (aclpb->aclpb_allow_handles[aci->aci_elevel] == NULL ) { + aclpb->aclpb_allow_handles[aci->aci_elevel] = aci; + } else { + if ((gen_allow_handle + ACI_DEFAULT_ELEVEL + 1) == + aclpb->aclpb_allow_handles_size) { + /* allocate more space */ + int num = ACLPB_INCR_LIST_HANDLES + + aclpb->aclpb_allow_handles_size; + + aclpb->aclpb_allow_handles = + (aci_t **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_allow_handles, + num * sizeof (aci_t *)); + aclpb->aclpb_allow_handles_size = num; + } + aclpb->aclpb_allow_handles [gen_allow_handle] = aci; + gen_allow_handle++; + } + allow_handle++; + } + } + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } /* end of while */ + + /* make the last one a null */ + aclpb->aclpb_deny_handles [gen_deny_handle] = NULL; + aclpb->aclpb_allow_handles [gen_allow_handle] = NULL; + + /* specify how many we found */ + aclpb->aclpb_num_deny_handles = deny_handle; + aclpb->aclpb_num_allow_handles = allow_handle; + + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Num of ALLOW Handles:%d, DENY handles:%d\n", + aclpb->aclpb_num_allow_handles, aclpb->aclpb_num_deny_handles, 0); + + TNF_PROBE_0_DEBUG(acl__scan_for_acis_end,"ACL",""); + + return(allow_handle + deny_handle); +} + +/*************************************************************************** +* +* acl__resource_match_aci +* +* This compares the ACI for the given resource and determines if +* the ACL applies to the resource or not. +* +* For read/search operation, we collect all the possible acls which +* will apply, We will be using this list for future acl evaluation +* for that entry. +* +* Input: +* struct acl_pblock *aclpb - Main acl private block +* aci_t *aci - The ACI item +* int skip_attrEval - DOn't check for attrs +* int *a_matched - Attribute matched +* +* Returns: +* +* ACL_TRUE - Yep, This ACL is applicable to the +* - the resource. +* ACL_FALSE - No it is not. +* +* Error Handling: +* None. +* +**************************************************************************/ +#define ACL_RIGHTS_TARGETATTR_NOT_NEEDED ( SLAPI_ACL_ADD | SLAPI_ACL_DELETE | SLAPI_ACL_PROXY) +static int +acl__resource_match_aci( Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a_matched) +{ + + struct slapi_filter *f; /* filter */ + int rv; /* return value */ + int matches; + int attr_matched; + int attr_matched_in_targetattrfilters = 0; + int dn_matched; + char *res_attr; + int aci_right = 0; + int res_right = 0; + int star_matched = ACL_FALSE; + int num_attrs = 0; + AclAttrEval *c_attrEval = NULL; + const char *res_ndn = NULL; + const char *aci_ndn = NULL; + char *matched_val = NULL; + int add_matched_val_to_ht = 0; + char res_right_str[128]; + + TNF_PROBE_0_DEBUG(acl__resource_match_aci_start,"ACL",""); + + aclpb->aclpb_stat_aclres_matched++; + + /* Assume that resource matches */ + matches = ACL_TRUE; + + /* Figure out if the acl has the correct rights or not */ + aci_right = aci->aci_access; + res_right = aclpb->aclpb_access; + if (!(aci_right & res_right)) { + /* If we are looking for read/search and the acl has read/search + ** then go further because if targets match we may keep that + ** acl in the entry cache list. + */ + if (!((res_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) && + (aci_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)))) + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + + /* first Let's see if the entry is under the subtree where the + ** ACL resides. We can't let somebody affect a target beyond the + ** scope of where the ACL resides + ** Example: ACL is located in "ou=engineering, o=ace industry, c=us + ** but if the target is "o=ace industry, c=us", then we are in trouble. + ** + ** If the aci is in the rootdse and the entry is not, then we do not + ** match--ie. acis in the rootdse do NOT apply below...for the moment. + ** + */ + res_ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn ); + aci_ndn = slapi_sdn_get_ndn ( aci->aci_sdn ); + if (!slapi_sdn_issuffix(aclpb->aclpb_curr_entry_sdn, aci->aci_sdn) + || (!slapi_is_rootdse(res_ndn) && slapi_is_rootdse(aci_ndn)) ) { + + /* cant' poke around */ + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* + ** We have a single ACI which we need to find if it applies to + ** the resource or not. + */ + if ((aci->aci_type & ACI_TARGET_DN) && + (aclpb->aclpb_curr_entry_sdn)) { + char *avaType; + struct berval *avaValue; + + f = aci->target; + dn_matched = ACL_TRUE; + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + if (!slapi_dn_issuffix( res_ndn, avaValue->bv_val)) { + dn_matched = ACL_FALSE; + } + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + } + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + if (aci->aci_type & ACI_TARGET_PATTERN) { + + f = aci->target; + dn_matched = ACL_TRUE; + + if ((rv = acl_match_substring(f, (char *)res_ndn, 0 /* match suffux */)) != ACL_TRUE) { + dn_matched = ACL_FALSE; + if(rv == ACL_ERR) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__resource_match_aci:pattern err\n", + 0,0,0); + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + } + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + } + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + /* + * Is it a (target="ldap://cn=*,($dn),o=sun.com") kind of thing. + */ + if (aci->aci_type & ACI_TARGET_MACRO_DN) { + /* + * See if the ($dn) component matches the string and + * retrieve the matched substring for later use + * in the userdn. + * The macro string is a function of the dn only, so if the + * entry is the same one don't recalculate it-- + * this flag only works for search right now, could + * also optimise for mods by making it work for mods. + */ + + if ( (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY) == 0 ) { + /* + * Here same entry so just look up the matched value, + * calculated from the targetdn and stored judiciously there + */ + matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index); + } + if ( matched_val == NULL && + (aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS))) { + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Evaluating macro aci(%d)%s for resource %s\n", + aci->aci_index, aci->aclName, + aclutil__access_str(res_right, res_right_str)); + matched_val = acl_match_macro_in_target( res_ndn, + aci->aci_macro->match_this, + aci->aci_macro->macro_ptr); + add_matched_val_to_ht = 1; /* may need to add matched value to ht */ + } + if (matched_val == NULL) { + dn_matched = ACL_FALSE; + } else { + dn_matched = ACL_TRUE; + } + + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + + if ( add_matched_val_to_ht ) { + if ( matches == ACL_TRUE && matched_val ) { + /* + * matched_val may be needed later for matching on + * other targets or on the subject--so optimistically + * put it in the hash table. + * If, at the end of this routine, we + * find that after all the resource did not match then + * that's ok--the value is freed at aclpb cleanup time. + * If there is already an entry for this aci in this + * aclpb then remove it--it's an old value for a + * different entry. + */ + + acl_ht_add_and_freeOld(aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index, + matched_val); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "-- Added aci(%d) and matched value (%s) to macro ht\n", + aci->aci_index, matched_val); + acl_ht_display_ht(aclpb->aclpb_macro_ht); + } else { + slapi_ch_free((void **)&matched_val); + if (matches == ACL_FALSE) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Evaluated ACL_FALSE\n"); + } + } + } + } /* MACRO_DN */ + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + /* + ** Here, if there's a targetfilter field, see if it matches. + ** + ** The commented out code below was an erroneous attempt to skip + ** this test. It is wrong because: 1. you need to store + ** whether the last test matched or not (you cannot just assume it did) + ** and 2. It may not be the same aci, so the previous matched + ** value is a function of the aci. + ** May be interesting to build such a cache...but no evidence for + ** for that right now. See Bug 383424. + ** + ** + ** && ((aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) || + ** (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY)) + */ + if (aci->aci_type & ACI_TARGET_FILTER ) { + int filter_matched = ACL_TRUE; + + /* + * Check for macros. + * For targetfilter we need to fake the lasinfo structure--it's + * created "naturally" for subjects but not targets. + */ + + + if ( aci->aci_type & ACI_TARGET_FILTER_MACRO_DN) { + + lasInfo *lasinfo = NULL; + + lasinfo = (lasInfo*) slapi_ch_malloc( sizeof(lasInfo) ); + + lasinfo->aclpb = aclpb; + lasinfo->resourceEntry = aclpb->aclpb_curr_entry; + aclpb->aclpb_curr_aci = aci; + filter_matched = aclutil_evaluate_macro( aci->targetFilterStr, + lasinfo, + ACL_EVAL_TARGET_FILTER); + slapi_ch_free((void**)&lasinfo); + } else { + + + if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry, + aci->targetFilter, + 0 /*don't do acess chk*/)!= 0) { + filter_matched = ACL_FALSE; + } + + } + + /* If it's a logical value we can do logic on it...otherwise we do not match */ + if ( filter_matched == ACL_TRUE || filter_matched == ACL_FALSE) { + if (aci->aci_type & ACI_TARGET_FILTER_NOT) { + matches = (filter_matched == ACL_TRUE ? ACL_FALSE : ACL_TRUE); + } else { + matches = (filter_matched == ACL_TRUE ? ACL_TRUE: ACL_FALSE); + } + } else { + matches = ACL_FALSE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for targetfilter evaluation.\n"); + } + + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + } + + /* + * Check to see if we need to evaluate any targetattrfilters. + * They look as follows: + * (targetattrfilters="add=sn:(sn=rob) && gn:(gn!=byrne), + * del=sn:(sn=rob) && gn:(gn=byrne)") + * + * For ADD/DELETE: + * If theres's a targetattrfilter then each add/del filter + * that applies to an attribute in the entry, must be satisfied + * by each value of the attribute in the entry. + * + * For MODIFY: + * If there's a targetattrfilter then the add/del filter + * must be satisfied by the attribute to be added/deleted. + * (MODIFY acl is evaluated one value at a time). + * + * + */ + + if ((aclpb->aclpb_access & SLAPI_ACL_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS )|| + (aclpb->aclpb_access & SLAPI_ACL_DELETE && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) { + + Targetattrfilter **attrFilterArray; + + Targetattrfilter *attrFilter = NULL; + + int found_applicable = 0; + Slapi_Attr *attr_ptr = NULL; + Slapi_Value *sval; + const struct berval *attrVal; + int k; + int done; + + + if (aclpb->aclpb_access & SLAPI_ACL_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + attrFilterArray = aci->targetAttrAddFilters; + + } else if (aclpb->aclpb_access & SLAPI_ACL_DELETE && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) { + + attrFilterArray = aci->targetAttrDelFilters; + + } + + attr_matched = ACL_TRUE; + num_attrs = 0; + + while (attrFilterArray[num_attrs] && attr_matched) { + attrFilter = attrFilterArray[num_attrs]; + + /* + * If this filter applies to an attribute in the entry, + * apply it to the entry. + * Otherwise just ignore it. + * + */ + + if (slapi_entry_attr_find ( aclpb->aclpb_curr_entry, + attrFilter->attr_str, + &attr_ptr) == 0) { + + /* + * This is an applicable filter. + * The filter is to be appplied to the entry being added + * or deleted. + * The filter needs to be satisfied by _each_ occurence + * of the attribute in the entry--otherwise you + * could satisfy the filter and then put loads of other + * values in on the back of it. + */ + + found_applicable = 1; + + sval=NULL; + attrVal=NULL; + k= slapi_attr_first_value(attr_ptr,&sval); + done = 0; + while(k != -1 && !done) { + attrVal = slapi_value_get_berval(sval); + + if ( acl__make_filter_test_entry( + &aclpb->aclpb_filter_test_entry, + attrFilter->attr_str, + (struct berval *)attrVal) == LDAP_SUCCESS ) { + + attr_matched= acl__test_filter( + aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ + ); + done = !attr_matched; + slapi_entry_free( aclpb->aclpb_filter_test_entry ); + } + + k= slapi_attr_next_value(attr_ptr, k, &sval); + }/* while */ + + /* + * Here, we applied an applicable filter to the entry. + * So if attr_matched is ACL_TRUE then every value + * of the attribute in the entry satisfied the filter. + * Otherwise, attr_matched is ACL_FALSE and not every + * value satisfied the filter, so we will teminate the + * scan of the filter list. + */ + + } + + num_attrs++; + } /* while */ + + /* + * Here, we've applied all the applicable filters to the entry. + * Each one must have been satisfied by all the values of the attribute. + * The result of this is stored in attr_matched. + */ + +#if 0 + /* + * Don't support a notion of "add != " or "del != " + * at the moment. + * To do this, need to test whether it's an add test or del test + * then if it's add and ACI_TARGET_ATTR_ADD_FILTERS_NOT then + * flip the bit. Same for del. + */ + + if (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS_NOT) { + matches = (matches ? ACL_FALSE : ACL_TRUE); + } else { + matches = (matches ? ACL_TRUE: ACL_FALSE); + } +#endif + + /* No need to look further */ + if (attr_matched == ACL_FALSE) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + } else if ( (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) || + (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) { + + + /* + * Here, it's a modify add/del and we have attr filters. + * So, we need to scan the add/del filter list to find the filter + * that applies to the current attribute. + * Then the (attribute,value) pair being added/deleted better + * match that filter. + * + * + */ + + Targetattrfilter **attrFilterArray = NULL; + Targetattrfilter *attrFilter; + int found = 0; + + if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + attrFilterArray = aci->targetAttrAddFilters; + + } else if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) { + + attrFilterArray = aci->targetAttrDelFilters; + + } + + + /* + * Scan this filter list for an applicable filter. + */ + + found = 0; + num_attrs = 0; + + while (attrFilterArray[num_attrs] && !found) { + attrFilter = attrFilterArray[num_attrs]; + + /* If this filter applies to the attribute, stop. */ + if ((aclpb->aclpb_curr_attrEval) && + slapi_attr_type_cmp ( aclpb->aclpb_curr_attrEval->attrEval_name, + attrFilter->attr_str, 1) == 0) { + found = 1; + } + num_attrs++; + } + + /* + * Here, if found an applicable filter, then apply the filter to the + * (attr,val) pair. + * Otherwise, ignore the targetattrfilters. + */ + + if (found) { + + if ( acl__make_filter_test_entry( + &aclpb->aclpb_filter_test_entry, + aclpb->aclpb_curr_attrEval->attrEval_name, + aclpb->aclpb_curr_attrVal) == LDAP_SUCCESS ) { + + attr_matched= acl__test_filter(aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ + ); + slapi_entry_free( aclpb->aclpb_filter_test_entry ); + } + + /* No need to look further */ + if (attr_matched == ACL_FALSE) { + matches = attr_matched; + goto acl__resource_match_aci_EXIT; + } + + /* + * Here this attribute appeared and was matched in a + * targetattrfilters list, so record this fact so we do + * not have to scan the targetattr list for the attribute. + */ + + attr_matched_in_targetattrfilters = 1; + + + } + } /* targetvaluefilters */ + + + /* There are 3 cases by which acis are selected. + ** 1) By scanning the whole list and picking based on the resource. + ** 2) By picking a subset of the list which will be used for the whole + ** acl evaluation. + ** 3) A finer granularity, i.e, a selected list of acls which will be + ** used for only that entry's evaluation. + */ + if ( !(skip_attrEval) && (aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_ENTRY_LIST) && + (res_right & SLAPI_ACL_SEARCH) && + ((aci->aci_access & SLAPI_ACL_READ) || (aci->aci_access & SLAPI_ACL_SEARCH))) { + int kk=0; + + while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] >=0 ) kk++; + if (kk >= ACLPB_MAX_SELECTED_ACLS) { + aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_ENTRY_LIST; + } else { + aclpb->aclpb_handles_index[kk++] = aci->aci_index; + aclpb->aclpb_handles_index[kk] = -1; + } + } + + + /* If we are suppose to skip attr eval, then let's skip it */ + if ( (aclpb->aclpb_access & SLAPI_ACL_SEARCH ) && ( ! skip_attrEval ) && + ( aclpb->aclpb_res_type & ACLPB_NEW_ENTRY )) { + aclEvalContext *c_evalContext = &aclpb->aclpb_curr_entryEval_context; + int nhandle = c_evalContext->acle_numof_tmatched_handles; + + if ( nhandle < ACLPB_MAX_SELECTED_ACLS) { + c_evalContext->acle_handles_matched_target[nhandle] = aci->aci_index; + c_evalContext->acle_numof_tmatched_handles++; + } + } + + if ( skip_attrEval ) { + goto acl__resource_match_aci_EXIT; + } + + /* We need to check again because we don't want to select this handle + ** if the right doesn't match for now. + */ + if (!(aci_right & res_right)) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* + * Here if the request is one that requires matching + * on a targetattr then do it here. + * If we have already matched an attribute in the targetattrfitlers list + * then we do not require a match in the targetattr so we can skip it. + * The operations that require targetattr are SLAPI_ACL_COMPARE, + * SLAPI_ACL_SEARCH, SLAPI_ACL_READ and SLAPI_ACL_WRITE, as long as + * c_attrEval is non-null (otherwise it's a modrdn op which + * does not require the targetattr list). + * + * rbyrneXXX if we had a proper permission for modrdn eg SLAPI_ACL_MODRDN + * then we would not need this crappy way of telling it was a MODRDN + * request ie. SLAPI_ACL_WRITE && !(c_attrEval). + */ + + c_attrEval = aclpb->aclpb_curr_attrEval; + + /* + * If we've already matched on targattrfilter then do not + * bother to look at the attrlist. + */ + + if (!attr_matched_in_targetattrfilters) { + + /* match target attr */ + if ((c_attrEval) && + (aci->aci_type & ACI_TARGET_ATTR)) { + /* there is a target ATTR */ + Targetattr **attrArray = aci->targetAttr; + Targetattr *attr = NULL; + + res_attr = c_attrEval->attrEval_name; + attr_matched = ACL_FALSE; + star_matched = ACL_FALSE; + num_attrs = 0; + + while (attrArray[num_attrs] && !attr_matched) { + attr = attrArray[num_attrs]; + if (attr->attr_type & ACL_ATTR_STRING) { + if (slapi_attr_type_cmp ( res_attr, + attr->u.attr_str, 1) == 0) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + } + } else if (attr->attr_type & ACL_ATTR_FILTER) { + if (ACL_TRUE == acl_match_substring ( + attr->u.attr_filter, + res_attr, 1)) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + } + } else if (attr->attr_type & ACL_ATTR_STAR) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + star_matched = ACL_TRUE; + } + num_attrs++; + } + + if (aci->aci_type & ACI_TARGET_ATTR_NOT) { + matches = (attr_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (attr_matched ? ACL_TRUE: ACL_FALSE); + } + + + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + /* figure out how it matched, i.e star matched */ + if (matches && star_matched && num_attrs == 1 && + !(aclpb->aclpb_state & ACLPB_FOUND_ATTR_RULE)) + aclpb->aclpb_state |= ACLPB_ATTR_STAR_MATCHED; + else { + /* we are here means that there is a specific + ** attr in the rule for this resource. + ** We need to avoid this case + ** Rule 1: (targetattr = "uid") + ** Rule 2: (targetattr = "*") + ** we cannot use STAR optimization + */ + aclpb->aclpb_state |= ACLPB_FOUND_ATTR_RULE; + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + } + } else if ( (c_attrEval) || + (aci->aci_type & ACI_TARGET_ATTR)) { + if ((aci_right & ACL_RIGHTS_TARGETATTR_NOT_NEEDED) && + (aclpb->aclpb_access & ACL_RIGHTS_TARGETATTR_NOT_NEEDED)) { + /* + ** Targetattr rule doesn't make any sense + ** in this case. So select this rule + ** default: matches = ACL_TRUE; + */ + ; + } else if (aci_right & SLAPI_ACL_WRITE && + (aci->aci_type & ACI_TARGET_ATTR) && + !(c_attrEval)) { + /* We need to handle modrdn operation. Modrdn doesn't + ** change any attrs but changes the RDN and so (attr=NULL). + ** Here we found an acl which has a targetattr but + ** the resource doesn't need one. In that case, we should + ** consider this acl. + ** default: matches = ACL_TRUE; + */ + ; + } else { + matches = ACL_FALSE; + } + } + }/* !attr_matched_in_targetattrfilters */ + + /* + ** Here we are testing if we find a entry test rule (which should + ** be rare). In that case, just remember it. An entry test rule + ** doesn't have "(targetattr)". + */ + if (aclpb && (aclpb->aclpb_state & ACLPB_EVALUATING_FIRST_ATTR) && + (!(aci->aci_type & ACI_TARGET_ATTR))) { + aclpb->aclpb_state |= ACLPB_FOUND_A_ENTRY_TEST_RULE; + } + + /* + * Generic exit point for this routine: + * matches is ACL_TRUE if the aci matches the target of the resource, + * ACL_FALSE othrewise. + * Apologies for the goto--this is a retro-fitted exit point. + */ +acl__resource_match_aci_EXIT: + + /* + * For macro acis, there may be a partial macro string + * placed in the aclpb_macro_ht + * even if the aci did not finally match. + * All the partial strings will be freed at aclpb + * cleanup time. + */ + + TNF_PROBE_0_DEBUG(acl__resource_match_aci_end,"ACL",""); + + return (matches); +} +/* Macro to determine if the cached result is valid or not. */ +#define ACL_CACHED_RESULT_VALID( result) \ + (((result & ACLPB_CACHE_READ_RES_ALLOW) && \ + (result & ACLPB_CACHE_READ_RES_SKIP)) || \ + ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) && \ + (result & ACLPB_CACHE_SEARCH_RES_SKIP))) ? 0 : 1 +/*************************************************************************** +* +* acl__TestRights +* +* Test the rights and find out if access is allowed or not. +* +* Processing Alogorithm: +* +* First, process the DENY rules one by one. If the user is not explicitly +* denied, then check if the user is allowed by processing the ALLOW handles +* one by one. +* The result of the evaluation is cached. Exceptions are +* -- If an acl happens to be in both DENY and ALLOW camp. +* -- Only interested for READ/SEARCH right. +* +* Input: +* struct acl_pblock *aclpb - main acl private block +* int access - The access bits +* char **right - The right we are looking for +* char ** generic - Generic rights +* +* Returns: +* +* ACL_RES_ALLOW - Access allowed +* ACL_RES_DENY - Access denied +* err - error condition +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +acl__TestRights(Acl_PBlock *aclpb,int access, char **right, char ** map_generic, + aclResultReason_t *result_reason) +{ + ACLEvalHandle_t *acleval; + int rights_rv = ACL_RES_DENY; + int rv, i,j, k; + int index; + char *deny = NULL; + char *deny_generic = NULL; + char *acl_tag; + int expr_num; + char *testRights[2]; + aci_t *aci; + int numHandles = 0; + aclEvalContext *c_evalContext = NULL; + + TNF_PROBE_0_DEBUG(acl__TestRights_start,"ACL",""); + + c_evalContext = &aclpb->aclpb_curr_entryEval_context; + + /* record the aci and reason for access decision */ + result_reason->deciding_aci = NULL; + result_reason->reason = ACL_REASON_NONE; + + /* If we don't have any ALLLOW handles, it's DENY by default */ + if (aclpb->aclpb_num_allow_handles <= 0) { + result_reason->deciding_aci = NULL; + result_reason->reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS; + + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,no_allows,""); + + return ACL_RES_DENY; + } + + /* Get the ACL evaluation Context */ + acleval = aclpb->aclpb_acleval; + + testRights[0] = *right; + testRights[1] = '\0'; + + /* + ** START PROCESSING DENY HANDLES + ** Process each handle at a time. Do not concatenate the handles or else + ** all the context information will be build again and we will pay a + ** lot of penalty. The context is built the first time the handle is + ** processed. + ** + ** First we set the default to INVALID so that if rules are not matched, then + ** we get INVALID and if a rule matched, the we get DENY. + */ + aclpb->aclpb_state &= ~ACLPB_EXECUTING_ALLOW_HANDLES; + aclpb->aclpb_state |= ACLPB_EXECUTING_DENY_HANDLES; + ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID); + + numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_deny_handles; + for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_deny_handles ; ++i) { + int skip_eval = 0; + + /* + ** If the handle has been evaluated before, we can + ** cache the result. + */ + if (((aci = aclpb->aclpb_deny_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL)) + continue; + k++; + index = aci->aci_index; + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Evaluating DENY aci(%d) \"%s\"\n", index, aci->aclName); + + if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + /* + * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is + * a cache of info about whether applicable acis + * allowed, did_not_allow or denied access + */ + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + short result; + + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if (access & SLAPI_ACL_SEARCH) { + if ( result & ACLPB_CACHE_SEARCH_RES_DENY){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found SEARCH DENY in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_deny,""); + return ACL_RES_DENY; + } else if ((result & ACLPB_CACHE_SEARCH_RES_SKIP) || + (result & ACLPB_CACHE_SEARCH_RES_ALLOW)) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found SEARCH SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } else { /* must be READ */ + if (result & ACLPB_CACHE_READ_RES_DENY) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found READ DENY in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_deny,""); + return ACL_RES_DENY; + } else if ( result & ACLPB_CACHE_READ_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found READ SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } + } + } + } + if (skip_eval) { + skip_eval = 0; + continue; + } + + rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle); + if ( rv < 0) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__TestRights:Unable to set the DENY acllist\n", + 0,0,0); + continue; + } + /* + ** Now we have all the information we need. We need to call + ** the ONE ACL to test the rights. + ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes + */ + aclpb->aclpb_curr_aci = aci; + rights_rv = ACL_EvalTestRights (NULL, acleval, testRights, + map_generic, &deny, + &deny_generic, + &acl_tag, &expr_num); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Processed:%d DENY handles Result:%d\n",index, rights_rv,0); + + if (rights_rv == ACL_RES_FAIL) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_NONE; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } + + /* have we executed an ATTR RULE */ + if ( aci->aci_ruleType & ACI_ATTR_RULES ) + aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED; + + if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS ) { + /* j == aclpb->aclpb_last_cache_result && + j < ACLPB_MAX_CACHE_RESULTS */ + aclpb->aclpb_last_cache_result++; + aclpb->aclpb_cache_result[j].aci_index = index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + + } else { /* cache overflow */ + if ( rights_rv == ACL_RES_DENY) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } else { + continue; + } + } + + __acl_set_aclIndex_inResult ( aclpb, access, index ); + if (rights_rv == ACL_RES_DENY) { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_DENY; + } else { /* MUST BE READ */ + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_DENY; + } + /* We are done -- return */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } else if (rights_rv == ACL_RES_ALLOW) { + /* This will happen, of we have an acl with both deny and allow + ** Since we may not have finished all the deny acl, go thru all + ** of them. We will use this cached result when we evaluate this + ** handle in the context of allow handles. + */ + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_ALLOW; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_ALLOW; + } + + } else { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_SKIP; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_SKIP; + } + continue; + } + } else { + if ( rights_rv == ACL_RES_DENY ) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } + } + } + + + /* + ** START PROCESSING ALLOW HANDLES. + ** Process each handle at a time. Do not concatenate the handles or else + ** all the context information will be build again and we will pay a + ** lot of penalty. The context is built the first time the handle is + ** processed. + ** + ** First we set the default to INVALID so that if rules are not matched, then + ** we get INVALID and if a rule matched, the we get ALLOW. + */ + aclpb->aclpb_state &= ~ACLPB_EXECUTING_DENY_HANDLES; + aclpb->aclpb_state |= ACLPB_EXECUTING_ALLOW_HANDLES; + ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID); + numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_allow_handles; + for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_allow_handles ; ++i) { + int skip_eval = 0; + /* + ** If the handle has been evaluated before, we can + ** cache the result. + */ + aci = aclpb->aclpb_allow_handles[i]; + if (((aci = aclpb->aclpb_allow_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL)) + continue; + k++; + index = aci->aci_index; + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "%d. Evaluating ALLOW aci(%d) \"%s\"\n", k, index, aci->aclName); + + if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + /* + * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is + * a cache of info about whether applicable acis + * allowed, did_not_allow or denied access + */ + + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + short result; + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if (access & SLAPI_ACL_SEARCH) { + if (result & ACLPB_CACHE_SEARCH_RES_ALLOW) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found SEARCH ALLOW in cache\n"); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_allow,""); + return ACL_RES_ALLOW; + } else if ( result & ACLPB_CACHE_SEARCH_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found SEARCH SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + /* need to evaluate */ + break; + } + } else { + if ( result & ACLPB_CACHE_READ_RES_ALLOW) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found READ ALLOW in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_allow,""); + return ACL_RES_ALLOW; + } else if ( result & ACLPB_CACHE_READ_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found READ SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } + } + } + } + if ( skip_eval) { + skip_eval = 0; + continue; + } + + TNF_PROBE_0_DEBUG(acl__libaccess_start,"ACL",""); + rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle); + if ( rv < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl__TestRights:Unable to set the acllist\n", + 0,0,0); + continue; + } + /* + ** Now we have all the information we need. We need to call + ** the ONE ACL to test the rights. + ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes + */ + aclpb->aclpb_curr_aci = aci; + rights_rv = ACL_EvalTestRights (NULL, acleval, testRights, + map_generic, &deny, + &deny_generic, + &acl_tag, &expr_num); + TNF_PROBE_0_DEBUG(acl__libaccess_end,"ACL",""); + + if (aci->aci_ruleType & ACI_ATTR_RULES) + aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED; + + if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS ) { + /* j == aclpb->aclpb_last_cache_result && + j < ACLPB_MAX_CACHE_RESULTS */ + aclpb->aclpb_last_cache_result++; + aclpb->aclpb_cache_result[j].aci_index = index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + } else { /* cache overflow */ + slapi_log_error (SLAPI_LOG_FATAL, "acl__TestRights", "cache overflown\n"); + if ( rights_rv == ACL_RES_ALLOW) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + } else { + continue; + } + } + + __acl_set_aclIndex_inResult ( aclpb, access, index ); + if (rights_rv == ACL_RES_ALLOW) { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_ALLOW; + } else { /* must be READ */ + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_ALLOW; + } + + /* We are done -- return */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + + } else { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_SKIP; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_SKIP; + } + continue; + } + } else { + if ( rights_rv == ACL_RES_ALLOW ) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + } + } + }/* for */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS; + + TNF_PROBE_0_DEBUG(acl__TestRights_end,"ACL",""); + + return (ACL_RES_DENY); +} +/*************************************************************************** +* +* acl_match_substring +* +* Compare the input string to the patteren in the filter +* +* Input: +* struct slapi_filter *f - Filter which has the patteren +* char *str - String to compare +* int exact_match - 1; match the pattern exactly +* - 0; match the pattern as a suffix +* +* Returns: +* ACL_TRUE - The sting matches with the patteren +* ACL_FALSE - No it doesn't +* ACL_ERR - Error +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_match_substring ( Slapi_Filter *f, char *str, int exact_match) +{ + int i, rc, len; + char *p; + char *end, *realval, *tmp; + char pat[BUFSIZ]; + char buf[BUFSIZ]; + char *type, *initial, *final; + char **any; + + if ( 0 != slapi_filter_get_subfilt ( f, &type, &initial, &any, &final ) ) { + return (ACL_FALSE); + } + + /* convert the input to lower. */ + for (p = str; *p; p++) + *p = TOLOWER ( *p ); + + /* construct a regular expression corresponding to the filter: */ + pat[0] = '\0'; + p = pat; + end = pat + sizeof(pat) - 2; /* leave room for null */ + + + if ( initial != NULL) { + strcpy (p, "^"); + p = strchr (p, '\0'); + + /* 2 * in case every char is special */ + if (p + 2 * strlen ( initial ) > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + + return (ACL_ERR); + } + + if (!exact_match) { + strcpy (p, ".*"); + p = strchr (p, '\0'); + } + acl_strcpy_special (p, initial); + p = strchr (p, '\0'); + } + + if ( any != NULL) { + for (i = 0; any && any[i] != NULL; i++) { + /* ".*" + value */ + if (p + 2 * strlen ( any[i]) + 2 > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + return (ACL_ERR); + } + + strcpy (p, ".*"); + p = strchr (p, '\0'); + acl_strcpy_special (p, any[i]); + p = strchr (p, '\0'); + } + } + + + if ( final != NULL) { + /* ".*" + value */ + if (p + 2 * strlen ( final ) + 2 > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + return (ACL_ERR); + } + + strcpy (p, ".*"); + p = strchr (p, '\0'); + acl_strcpy_special (p, final); + p = strchr (p, '\0'); + strcpy (p, "$"); + } + + /* see if regex matches with the input string */ + tmp = NULL; + len = strlen(str); + + if (len < sizeof(buf)) { + strcpy (buf, str); + realval = buf; + } else { + tmp = (char*) slapi_ch_malloc (len + 1); + strcpy (tmp, str); + realval = tmp; + } + + slapi_dn_normalize (realval); + + + /* What we have built is a regular pattaren expression. + ** Now we will compile the pattern and compare wth the string to + ** see if the input string matches with the patteren or not. + */ + slapd_re_lock(); + if ((p = slapd_re_comp (pat)) != 0) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl_match_substring:re_comp failed (%s)\n", p, 0, 0); + slapd_re_unlock(); + return (ACL_ERR); + } + + /* re_exec() returns 1 if the string p1 matches the last compiled + ** regular expression, 0 if the string p1 failed to match + ** (see man pages) + ** + ** IMPORTANT NOTE: If I use compile() and step() to do the patteren + ** matching, it seems that step() is leaking 1036 bytes/search + ** I couldn't figure out why it's leaking. + */ + rc = slapd_re_exec( realval ); + + slapd_re_unlock(); + + if (tmp != NULL) { + slapi_ch_free ( (void **) &tmp ); + } + + if (rc == 1) { + return ACL_TRUE; + } else { + return ACL_FALSE; + } +} +/*************************************************************************** +* +* acl__reset_cached_result +* +* Go thru the cached result and invlalidate the cached evaluation results for +* rules which can only be cached based on the scope. +* If we have scope ACI_CACHE_RESULT_PER_ENTRY, then we need to invalidate the +* cacched reult whenever we hit a new entry. +* +* Returns: +* Null +* +**************************************************************************/ +static void +acl__reset_cached_result (struct acl_pblock *aclpb ) +{ + + int j; + + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + /* Unless we have to clear the result, move on */ + if (!( aclpb->aclpb_cache_result[j].aci_ruleType & ACI_CACHE_RESULT_PER_ENTRY)) + continue; + aclpb->aclpb_cache_result[j].result = 0; + } +} +/* + * acl_access_allowed_disjoint_resource + * + * This is an internal module which can be used to verify + * access to a resource which may not be inside the search scope. + * + * Returns: + * - Same return val as acl_access_allowed(). + */ +int +acl_access_allowed_disjoint_resource( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* access rights */ + ) +{ + + int rv; + struct acl_pblock *aclpb = NULL; + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + /* + ** It's possible that we already have a context of ACLs. + ** However once in a while, we need to test + ** access to a resource which (like vlv, schema) which falls + ** outside the search base. In that case, we need to go + ** thru all the ACLs and not depend upon the acls which we have + ** gathered. + */ + + /* If you have the right to use the resource, then we don't need to check for + ** proxy right on that resource. + */ + if (aclpb) + aclpb->aclpb_state |= ( ACLPB_DONOT_USE_CONTEXT_ACLS| ACLPB_DONOT_EVALUATE_PROXY ); + + rv = acl_access_allowed(pb, e, attr, val, access); + + if (aclpb) aclpb->aclpb_state &= ~ACLPB_DONOT_USE_CONTEXT_ACLS; + if (aclpb ) aclpb->aclpb_state &= ~ACLPB_DONOT_EVALUATE_PROXY; + + return rv; +} + +/* + * acl__attr_cached_result + * Loops thru the cached result and determines if we can use the cached value. + * + * Inputs: + * Slapi_pblock *aclpb - acl private block + * char *attr - attribute name + * int access - access type + * Returns: + * LDAP_SUCCESS: - access is granted + * LDAP_INSUFFICIENT_ACCESS - access denied + * ACL_ERR - no cached info about this attr. + * - or the attr had multiple result and so + * - we can't determine the outcome. + * + */ +static int +acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access ) +{ + + int i, rc; + aclEvalContext *c_evalContext; + + if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ) )) + return ACL_ERR; + + if (aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) { + c_evalContext = &aclpb->aclpb_prev_opEval_context; + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acl__attr_cached_result:Using Context: ACLPB_ACLCB\n", 0,0,0 ); + } else { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acl__attr_cached_result:Using Context: ACLPB_PREV\n", 0,0,0 ); + } + + if ( attr == NULL ) { + int eval_read = 0; + /* + ** Do I have access to at least one attribute, then I have + ** access to the entry. + */ + for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i]; + + if ( (access & SLAPI_ACL_READ ) && a_eval->attrEval_r_status && + a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) { + eval_read++; + if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + /* rbyrne: recompute if we have to. + * How does this cached result get turned off for + * attr style acis which acannot be cached becuase entry + * can result in a diff value. + */ + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_r_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + if ( rc == LDAP_SUCCESS) { + return LDAP_SUCCESS; + } + } + } + }/* for */ + /* + * If we have scanned the whole list without success then + * we are not granting access to this entry through access + * to an attribute in the list--however this does not mean + * that we do not have access to the entry via another attribute + * not already in the list, so return -1 meaning + * "don't know". + */ + return(ACL_ERR); +#if 0 + if ( eval_read ) + return LDAP_INSUFFICIENT_ACCESS; + else + return ACL_ERR; +#endif + } + + for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i]; + + if ( a_eval == NULL ) continue; + + if (strcasecmp ( attr, a_eval->attrEval_name ) == 0 ) { + if ( access & SLAPI_ACL_SEARCH ) { + if (a_eval->attrEval_s_status < ACL_ATTREVAL_DETERMINISTIC ) { + if ( a_eval->attrEval_s_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_FAIL) + return LDAP_INSUFFICIENT_ACCESS; + else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_s_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + } else + return ACL_ERR; + } else { + /* This means that for the same attribute and same type of + ** access, we had different results at different time. + ** Since we are not caching per object, we can't + ** determine exactly. So, can't touch this + */ + return ACL_ERR; + } + } else { + if (a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) { + if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_FAIL) + return LDAP_INSUFFICIENT_ACCESS; + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_r_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + } else + return ACL_ERR; + } else { + /* Look above for explanation */ + return ACL_ERR; + } + } + } + } + return ACL_ERR; +} + +/* + * Had to do this juggling of casting to make + * both Nt & unix compiler happy. + */ +static int +acl__cmp(const void *a, const void *b) +{ + short *i = (short *) a; + short *j = (short *) b; + + if ( (short) *i > (short) *j ) + return (1); + if ( (short)*i < (short) *j) + return (-1); + return (0); +} + +/* + * acl__scan_match_handles + * Go thru the ACL list and determine if the list of acls selected matches + * what we have in the cache. + * + * Inputs: + * Acl_PBlock *pb - Main pblock ( blacvk hole) + * int type - Which context to look on + * + * Returns: + * 0 - matches all the acl handles + * ACL_ERR - sorry; no match + * + * ASSUMPTION: A READER LOCK ON ACL LIST + */ +static int +acl__scan_match_handles ( Acl_PBlock *aclpb, int type) +{ + + + int matched = 0; + aci_t *aci = NULL; + int index; + PRUint32 cookie; + aclEvalContext *c_evalContext = NULL; + + if (type == ACLPB_EVALCONTEXT_PREV ) { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } else if ( type == ACLPB_EVALCONTEXT_ACLCB ){ + c_evalContext = &aclpb->aclpb_prev_opEval_context; + } else { + return ACL_ERR; + } + + + if ( !c_evalContext->acle_numof_tmatched_handles ) + return ACL_ERR; + + aclpb->aclpb_stat_acllist_scanned++; + aci = acllist_get_first_aci ( aclpb, &cookie ); + + while ( aci ) { + index = aci->aci_index; + if (acl__resource_match_aci(aclpb, aci, 1 /* skip attr matching */, NULL )) { + int j; + int s_matched = matched; + + /* We have a sorted list of handles that matched the target */ + + for (j=0; j < c_evalContext->acle_numof_tmatched_handles ; j++ ) { + if ( c_evalContext->acle_handles_matched_target[j] > index ) + + break; + else if ( index == c_evalContext->acle_handles_matched_target[j] ) { + int jj; + matched++; + + /* See if this is a ATTR rule that matched -- in that case we have + ** to nullify the cached result + */ + if ( aci->aci_ruleType & ACI_ATTR_RULES ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Found an attr Rule [Name:%s Index:%d\n", aci->aclName, + aci->aci_index ); + for ( jj =0; jj < c_evalContext->acle_numof_attrs; jj++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[jj]; + if ( a_eval->attrEval_r_aciIndex == aci->aci_index ) + a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE; + if ( a_eval->attrEval_s_aciIndex == aci->aci_index ) + a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE; + } + } + break; + } + } + if ( s_matched == matched ) return ACL_ERR; + } + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } + if ( matched == c_evalContext->acle_numof_tmatched_handles ) + return 0; + + return ACL_ERR; +} +/* + * acl_copyEval_context + * Copy the context info which include attr info and handles. + * + * Inputs + * struct acl_pblock *aclpb - acl private main block + * aclEvalContext *src - src context + * aclEvalContext *dest - dest context + * Returns: + * None. + * + */ +void +acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src, + aclEvalContext *dest , int copy_attr_only ) +{ + + int d_slot, i; + + /* Do a CLEAN copy we have nothing or else do an incremental copy.*/ + if ( src->acle_numof_attrs < 1 ) + return; + + /* Copy the attr info */ + if ( dest->acle_numof_attrs < 1 ) + acl_clean_aclEval_context ( dest, 0 /*clean */ ); + + d_slot = dest->acle_numof_attrs; + for (i=0; i < src->acle_numof_attrs; i++ ) { + int j; + int attr_exists = 0; + int dd_slot = d_slot; + + if ( aclpb && (i == 0) ) aclpb->aclpb_stat_num_copycontext++; + + if ( src->acle_attrEval[i].attrEval_r_status == 0 && + src->acle_attrEval[i].attrEval_s_status == 0 ) + continue; + + for ( j = 0; j < dest->acle_numof_attrs; j++ ) { + if ( strcasecmp ( src->acle_attrEval[i].attrEval_name, + dest->acle_attrEval[j].attrEval_name ) == 0 ) { + /* We have it. skip it. */ + attr_exists = 1; + dd_slot = j; + break; + } + } + if ( !attr_exists ) { + if ( dd_slot >= ACLPB_MAX_ATTRS -1 ) + break; + + if ( aclpb) aclpb->aclpb_stat_num_copy_attrs++; + + if ( dest->acle_attrEval[dd_slot].attrEval_name ) + slapi_ch_free ( (void **) &dest->acle_attrEval[dd_slot].attrEval_name ); + + dest->acle_attrEval[dd_slot].attrEval_name = + slapi_ch_strdup ( src->acle_attrEval[i].attrEval_name ); + } + /* Copy the result status and the aci index */ + dest->acle_attrEval[dd_slot].attrEval_r_status = + src->acle_attrEval[i].attrEval_r_status; + dest->acle_attrEval[dd_slot].attrEval_r_aciIndex = + src->acle_attrEval[i].attrEval_r_aciIndex; + dest->acle_attrEval[dd_slot].attrEval_s_status = + src->acle_attrEval[i].attrEval_s_status; + dest->acle_attrEval[dd_slot].attrEval_s_aciIndex = + src->acle_attrEval[i].attrEval_s_aciIndex; + + if (!attr_exists ) d_slot++; + } + + dest->acle_numof_attrs = d_slot; + dest->acle_attrEval[d_slot].attrEval_name = NULL; + + if ( copy_attr_only ) + return; + + /* First sort the arrays which keeps the acl index numbers */ + qsort ( (char *) src->acle_handles_matched_target, + (size_t)src->acle_numof_tmatched_handles, sizeof( int ), acl__cmp ); + + for (i=0; i < src->acle_numof_tmatched_handles; i++ ) { + dest->acle_handles_matched_target[i] = + src->acle_handles_matched_target[i]; + } + + if ( src->acle_numof_tmatched_handles ) { + dest->acle_numof_tmatched_handles = src->acle_numof_tmatched_handles; + if ( aclpb) aclpb->aclpb_stat_num_tmatched_acls = src->acle_numof_tmatched_handles; + } +} + +/* + * acl_clean_aclEval_context + * Clean the eval context + * + * Inputs: + * aclEvalContext *clean_me - COntext to be cleaned + * int clean_type - 0: clean, 1 scrub + * + */ + +void +acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only ) +{ + int i; + + /* Copy the attr info */ + for (i=0; i < clean_me->acle_numof_attrs; i++ ) { + + char *a_name = clean_me->acle_attrEval[i].attrEval_name; + if ( a_name && !scrub_only) { + slapi_ch_free ( (void **) &a_name ); + clean_me->acle_attrEval[i].attrEval_name = NULL; + } + clean_me->acle_attrEval[i].attrEval_r_status = 0; + clean_me->acle_attrEval[i].attrEval_s_status = 0; + clean_me->acle_attrEval[i].attrEval_r_aciIndex = 0; + clean_me->acle_attrEval[i].attrEval_s_aciIndex = 0; + } + + if ( !scrub_only ) clean_me->acle_numof_attrs = 0; + clean_me->acle_numof_tmatched_handles = 0; +} +/* + * acl__match_handlesFromCache + * + * We have 2 cacheed information + * 1) cached info from the previous operation + * 2) cached info from the prev entry evaluation + * + * What we are doing here is going thru all the acls and see if the same + * set of acls apply to this resource or not. If it does, then do we have + * a cached info for the attr. If we don't for both of the cases then we need + * to evaluate all over again. + * + * Inputs: + * struct acl_pblock - ACL private block; + * char *attr - Attribute name + * int access - acces type + * + * returns: + * LDAP_SUCCESS (0) - The same acls apply and we have + * access ALLOWED on the attr + * LDAP_INSUFFICIENT_ACCESS - The same acls apply and we have + * access DENIED on the attr + * -1 - Acls doesn't match or we don't have + * cached info for this attr. + * + * ASSUMPTIONS: A reader lock has been obtained for the acl list. + */ +static int +acl__match_handlesFromCache ( Acl_PBlock *aclpb, char *attr, int access) +{ + + aclEvalContext *c_evalContext = NULL; + int context_type = 0; + int ret_val = -1; /* it doen't match by default */ + + /* Before we proceed, find out if we have evaluated any ATTR RULE. If we have + ** then we can't use any caching mechanism + */ + + if ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) { + context_type = ACLPB_EVALCONTEXT_ACLCB; + c_evalContext = &aclpb->aclpb_prev_opEval_context; + } else { + context_type = ACLPB_EVALCONTEXT_PREV; + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } + + + if ( aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS) ) { + aclpb->aclpb_state |= ACLPB_MATCHES_ALL_ACLS; + ret_val = acl__scan_match_handles ( aclpb, context_type ); + if ( -1 == ret_val ) { + aclpb->aclpb_state &= ~ACLPB_MATCHES_ALL_ACLS; + aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE; + /* Did not match */ + if ( context_type == ACLPB_HAS_ACLCB_EVALCONTEXT ) { + aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT; + } else { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + c_evalContext->acle_numof_tmatched_handles = 0; + } + } + } + if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + /* See if we have a cached result for this attr */ + ret_val = acl__attr_cached_result (aclpb, attr, access); + + /* It's not in the ACLCB context but we might have it in the + ** current/prev context. Take a look at it. we might have evaluated + ** this attribute already. + */ + if ( (-1 == ret_val ) && + ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT )) { + aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT ; + ret_val = acl__attr_cached_result (aclpb, attr, access); + aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT ; + + /* We need to do an incremental update */ + if ( !ret_val ) aclpb->aclpb_state |= ACLPB_INCR_ACLCB_CACHE; + } + } + return ret_val; +} +/* + * acl__get_attrEval + * Get the atteval from the current context and hold the ptr in aclpb. + * If we have too many attrs, then allocate a new one. In that case + * we let the caller know about that so that it will be deallocated. + * + * Returns: + * int - 0: The context was indexed. So, no allocations. + * - 1; context was allocated - deallocate it. + */ +static int +acl__get_attrEval ( struct acl_pblock *aclpb, char *attr ) +{ + + int j; + aclEvalContext *c_ContextEval = &aclpb->aclpb_curr_entryEval_context; + int deallocate_attrEval = 0; + AclAttrEval *c_attrEval = NULL; + + if ( !attr ) return deallocate_attrEval; + + aclpb->aclpb_curr_attrEval = NULL; + + /* Go thru and see if we have the attr already */ + for (j=0; j < c_ContextEval->acle_numof_attrs; j++) { + c_attrEval = &c_ContextEval->acle_attrEval[j]; + + if ( c_attrEval && + slapi_attr_type_cmp ( c_attrEval->attrEval_name, attr, 1) == 0 ) { + aclpb->aclpb_curr_attrEval = c_attrEval; + break; + } + } + + if ( !aclpb->aclpb_curr_attrEval) { + if ( c_ContextEval->acle_numof_attrs == ACLPB_MAX_ATTRS -1 ) { + /* Too many attrs. create a temp one */ + c_attrEval = (AclAttrEval * ) slapi_ch_calloc ( 1, sizeof ( AclAttrEval ) ); + deallocate_attrEval =1; + } else { + c_attrEval = &c_ContextEval->acle_attrEval[c_ContextEval->acle_numof_attrs++]; + c_attrEval->attrEval_r_status = 0; + c_attrEval->attrEval_s_status = 0; + c_attrEval->attrEval_r_aciIndex = 0; + c_attrEval->attrEval_s_aciIndex = 0; + } + /* clean it before use */ + c_attrEval->attrEval_name = slapi_ch_strdup ( attr ); + aclpb->aclpb_curr_attrEval = c_attrEval; + } + return deallocate_attrEval; +} +/* + * acl_skip_access_check + * + * See if we need to go thru the ACL check or not. We don't need to if I am root + * or internal operation or ... + * + * returns: + * ACL_TRUE - Yes; skip the ACL check + * ACL_FALSE - No; you have to go thru ACL check + * + */ +int +acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e ) +{ + int rv, isRoot, accessCheckDisabled; + void *conn = NULL; + Slapi_Backend *be; + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + if ( isRoot ) return ACL_TRUE; + + /* See if this is local request */ + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn); + + if ( NULL == conn ) return ACL_TRUE; + + /* + * Turn on access checking in the rootdse--this code used + * to skip the access check. + * + * check if the entry is the RootDSE entry + if ( e ) { + char * edn = slapi_entry_get_ndn ( e ); + if ( slapi_is_rootdse ( edn ) ) return ACL_TRUE; + } + */ + + /* GB : when referrals are directly set in the mappin tree + * we can reach this code without a backend in the pblock + * in such a case, allow access for now + * we may want to reconsider this is NULL DSE implementation happens + */ + rv = slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + if (be == NULL) + return ACL_TRUE; + + rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled ); + if ( rv != -1 && accessCheckDisabled ) return ACL_TRUE; + + return ACL_FALSE; +} +short +acl_get_aclsignature () +{ + return acl_signature; +} +void +acl_set_aclsignature ( short value) +{ + acl_signature = value; +} +void +acl_regen_aclsignature () +{ + acl_signature = aclutil_gen_signature ( acl_signature ); +} + + + +static int +acl__handle_config_entry (Slapi_Entry *e, void *callback_data ) +{ + + int *value = (int *) callback_data; + + *value = slapi_entry_attr_get_int( e, "nsslapd-readonly"); + + return 0; +} + +static int +acl__config_get_readonly () +{ + + int readonly = 0; + + slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL, 0 /* attrsonly */, + &readonly/* callback_data */, + NULL /* controls */, + NULL /* result_callback */, + acl__handle_config_entry, + NULL /* referral_callback */); + + return readonly; +} +/* +* +* Assumptions: +* 1) Called for read/search right. +*/ +static int +acl__recompute_acl ( Acl_PBlock *aclpb, + AclAttrEval *a_eval, + int access, + int aciIndex + ) +{ + + + char *unused_str1, *unused_str2; + char *acl_tag, *testRight[2]; + int j, expr_num; + int result_status, rv, cache_result; + PRUint32 cookie; + aci_t *aci; + + + PR_ASSERT ( aciIndex >= 0 ); + PR_ASSERT ( a_eval != NULL ); + PR_ASSERT (aclpb != NULL ); + + + /* We might have evaluated this acl just now, check it there first */ + + for ( j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) { + short result; + result_status =ACL_RES_INVALID; + + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + + + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) || + (result & ACLPB_CACHE_READ_RES_ALLOW) ) + result_status = ACL_RES_ALLOW; + else if ((result & ACLPB_CACHE_SEARCH_RES_DENY) || + (result & ACLPB_CACHE_READ_RES_DENY) ) + result_status = ACL_RES_DENY; + + } + } /* end of for */ + + if ( result_status != ACL_RES_INVALID ) { + goto set_result_status; + } + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Recomputing the ACL Index:%d for entry:%s\n", + aciIndex, slapi_entry_get_ndn ( aclpb->aclpb_curr_entry) ); + + /* First find this one ACL and then evaluate it. */ + + + aci = acllist_get_first_aci ( aclpb, &cookie ); + while ( aci && aci->aci_index != aciIndex ) { + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } + + if (NULL == aci) + return -1; + + + ACL_SetDefaultResult (NULL, aclpb->aclpb_acleval, ACL_RES_INVALID); + rv = ACL_EvalSetACL(NULL, aclpb->aclpb_acleval, aci->aci_handle); + + testRight[0] = acl_access2str ( access ); + testRight[1] = '\0'; + aclpb->aclpb_curr_aci = aci; + result_status = ACL_EvalTestRights (NULL, aclpb->aclpb_acleval, testRight, + ds_map_generic, &unused_str1, + &unused_str2, + &acl_tag, &expr_num); + + cache_result = 0; + if ( result_status == ACL_RES_DENY && aci->aci_type & ACI_HAS_DENY_RULE ) { + if ( access & SLAPI_ACL_SEARCH) + cache_result = ACLPB_CACHE_SEARCH_RES_DENY; + else + cache_result = ACLPB_CACHE_READ_RES_DENY; + } else if ( result_status == ACL_RES_ALLOW && aci->aci_type & ACI_HAS_ALLOW_RULE ) { + if ( access & SLAPI_ACL_SEARCH) + cache_result = ACLPB_CACHE_SEARCH_RES_ALLOW; + else + cache_result = ACLPB_CACHE_READ_RES_ALLOW; + + } else { + result_status = -1; + } + + /* Now we need to put the cached result in the aclpb */ + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS-1) { + /* rbyrneXXX: make this same as other last_cache_result code! */ + j = ++aclpb->aclpb_last_cache_result; + aclpb->aclpb_cache_result[j].aci_index = aci->aci_index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + + } else { /* No more space */ + goto set_result_status; + } + + /* Add the cached result status */ + aclpb->aclpb_cache_result[j].result |= cache_result; + + + +set_result_status: + if (result_status == ACL_RES_ALLOW) { + if (access & SLAPI_ACL_SEARCH) + /*wrong bit maskes were being used here-- + a_eval->attrEval_s_status = ACLPB_CACHE_SEARCH_RES_ALLOW;*/ + a_eval->attrEval_s_status = ACL_ATTREVAL_SUCCESS; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_SUCCESS; + + } else if ( result_status == ACL_RES_DENY) { + if (access & SLAPI_ACL_SEARCH) + a_eval->attrEval_s_status = ACL_ATTREVAL_FAIL; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_FAIL; + } else { + /* Here, set it to recompute--try again later */ + if (access & SLAPI_ACL_SEARCH) + a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE; + result_status = -1; + } + + return result_status; +} + +static void +__acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, int access, int index ) +{ + AclAttrEval *c_attrEval = aclpb->aclpb_curr_attrEval; + + if ( c_attrEval ) { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_aciIndex = index; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_aciIndex = index; + } +} + +/* + * If filter_sense is true then return (entry satisfies f). + * Otherwise, return !(entry satisfies f) +*/ + +static int +acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, int filter_sense) { + int filter_matched; + + /* slapi_vattr_filter_test() returns 0 for match */ + + filter_matched = !slapi_vattr_filter_test(NULL, entry, + f, + 0 /*don't do acess chk*/); + + if (filter_sense) { + return(filter_matched ? ACL_TRUE : ACL_FALSE); + } else { + return(filter_matched ? ACL_FALSE: ACL_TRUE); + } +} + +/* + * Make an entry consisting of attr_type and attr_val and put + * a pointer to it in *entry. + * We will use this afterwards to test for against a filter. +*/ + +static int +acl__make_filter_test_entry ( Slapi_Entry **entry, char *attr_type, + struct berval *attr_val) { + + struct berval *vals_array[2]; + + vals_array[0] = attr_val; + vals_array[1] = NULL; + + *entry = slapi_entry_alloc(); + slapi_entry_init(*entry, NULL, NULL); + + return (slapi_entry_add_values( *entry, (const char *)attr_type, + (struct berval**)&vals_array[0] )); + +} + +/*********************************************************************************/ +/* E N D */ +/*********************************************************************************/ diff --git a/ldap/servers/plugins/acl/acl.h b/ldap/servers/plugins/acl/acl.h new file mode 100644 index 00000000..5e16a0bd --- /dev/null +++ b/ldap/servers/plugins/acl/acl.h @@ -0,0 +1,867 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/***************************************************************************** +* acl.h +* +* Header file for ACL processing +* +*****************************************************************************/ +#ifndef _ACL_H_ +#define _ACL_H_ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#endif + +#include <ldap.h> +#include <las.h> +#include <aclproto.h> +#include <aclerror.h> +#include "prcvar.h" +#include "slapi-plugin.h" +#include "slap.h" +#include "slapi-private.h" +#include "portable.h" +#include "prrwlock.h" +#include "avl.h" + +#include "cert.h" + +#include <plhash.h> + +#ifdef SOLARIS + #include <tnf/probe.h> +#else + #define TNF_PROBE_0_DEBUG(a,b,c) + #define TNF_PROBE_1_DEBUG(a,b,c,d,e,f) +#endif + +#define ACL_PLUGIN_NAME "NSACLPlugin" +extern char *plugin_name; + +/* + * Define the OID for version 2 of the proxied authorization control if + * it is not already defined (it is in recent copies of ldap.h). + */ +#ifndef LDAP_CONTROL_PROXIEDAUTH +#define LDAP_CONTROL_PROXIEDAUTH "2.16.840.1.113730.3.4.18" +#endif + +#define ACLUCHP unsigned char * + +static char* const aci_attr_type = "aci"; +static char* const filter_string = "aci=*"; +static char* const aci_targetdn = "target"; +static char* const aci_targetattr = "targetattr"; +static char* const aci_targetattrfilters = "targattrfilters"; +static char* const aci_targetfilter = "targetfilter"; + +static char* const LDAP_URL_prefix = "ldap:///"; + +static char* const access_str_compare = "compare"; +static char* const access_str_search = "search"; +static char* const access_str_read = "read"; +static char* const access_str_write = "write"; +static char* const access_str_delete = "delete"; +static char* const access_str_add = "add"; +static char* const access_str_selfwrite = "selfwrite"; +static char* const access_str_proxy = "proxy"; + +#define ACL_INIT_ATTR_ARRAY 5 + +/* define the method */ +#define DS_METHOD "ds_method" + +#define ACL_ESCAPE_STRING_WITH_PUNCTUATION(x,y) (slapi_is_loglevel_set(SLAPI_LOG_ACL) ? escape_string_with_punctuation(x,y) : "") + +/* Lases */ +#define DS_LAS_USER "user" +#define DS_LAS_GROUP "group" +#define DS_LAS_USERDN "userdn" +#define DS_LAS_GROUPDN "groupdn" +#define DS_LAS_USERDNATTR "userdnattr" +#define DS_LAS_AUTHMETHOD "authmethod" +#define DS_LAS_GROUPDNATTR "groupdnattr" +#define DS_LAS_USERATTR "userattr" +#define DS_LAS_ROLEDN "roledn" +#define DS_LAS_ROLEDNATTR "rolednattr" + + +/* These define the things that aclutil_evaluate_macro() supports */ +typedef enum +{ + ACL_EVAL_USER, + ACL_EVAL_GROUP, + ACL_EVAL_ROLE, + ACL_EVAL_GROUPDNATTR, + ACL_EVAL_TARGET_FILTER +}acl_eval_types; + +typedef enum +{ + ACL_RULE_MACRO_DN_TYPE, + ACL_RULE_MACRO_DN_LEVELS_TYPE +}acl_rule_macro_types; + +#define ACL_TARGET_MACRO_DN_KEY "($dn)" +#define ACL_RULE_MACRO_DN_KEY "($dn)" +#define ACL_RULE_MACRO_DN_LEVELS_KEY "[$dn]" +#define ACL_RULE_MACRO_ATTR_KEY "($attr." + +#define ACL_EVAL_USER 0 +#define ACL_EVAL_GROUP 1 +#define ACL_EVAL_ROLE 2 + +/* The LASes are implemented in the libaccess library */ +#define DS_LAS_TIMEOFDAY "timeofday" +#define DS_LAS_DAYOFWEEK "dayofweek" + + +/* ACL function return codes */ +#define ACL_TRUE 1 /* evaluation results to TRUE */ +#define ACL_OK ACL_TRUE +#define ACL_FALSE 0 /* evaluation results to FALSE */ +#define ACL_ERR -1 /* generic error */ +#define ACL_TARGET_FILTER_ERR -2 /* Target filter not set properly */ +#define ACL_TARGETATTR_FILTER_ERR -3 /* TargetAttr filter not set properly */ +#define ACL_TARGETFILTER_ERR -4 /* Target filter not set properly */ +#define ACL_SYNTAX_ERR -5 /* Syntax error */ +#define ACL_ONEACL_TEXT_ERR -6 /* ONE ACL text error */ +#define ACL_ERR_CONCAT_HANDLES -7 /* unable to concat the handles */ +#define ACL_INVALID_TARGET -8 /* invalid target */ +#define ACL_INVALID_AUTHMETHOD -9 /* multiple client auth */ +#define ACL_INVALID_AUTHORIZATION -10 /* no authorization */ +#define ACL_INCORRECT_ACI_VERSION -11 /* incorrect version # */ +#define ACL_DONT_KNOW -12 /* the world is an uncertain place */ + +/* supported by the DS */ +#define DS_PROP_CONNECTION "connection" +#define DS_ATTR_USERDN "userdn" +#define DS_ATTR_ENTRY "entry" +#define DS_PROP_ACLPB "aclblock" +#define DS_ATTR_AUTHTYPE "authtype" +#define DS_ATTR_CERT "clientcert" + +#define ACL_ANOM_MAX_ACL 40 +struct scoped_entry_anominfo { + short anom_e_targetInfo[ACL_ANOM_MAX_ACL]; + short anom_e_nummatched; + short anom_e_isrootds; +}; + +typedef struct targetattr { + int attr_type; +#define ACL_ATTR_FILTER 0x01 +#define ACL_ATTR_STRING 0x02 +#define ACL_ATTR_STAR 0x04 /* attr is * only */ + + union { + char *attr_str; + struct slapi_filter *attr_filter; + }u; +}Targetattr; + +typedef struct targetattrfilter { + char *attr_str; + char *filterStr; + struct slapi_filter *filter; /* value filter */ + +}Targetattrfilter; + +typedef struct Aci_Macro { + char *match_this; + char *macro_ptr; /* ptr into match_this */ +}aciMacro; + +typedef PLHashTable acl_ht_t; + +/* Access Control Item (aci): Stores information about a particular ACL */ +typedef struct aci { + int aci_type; /* Type of resurce */ + +/* THE FIRST BYTE WAS USED TO KEEP THE RIGHTS. ITS BEEN MOVED TO +** aci_access and is now free. +** +** +** +*/ + +#define ACI_TARGET_MACRO_DN (int)0x000001 +#define ACI_TARGET_FILTER_MACRO_DN (int)0x000002 +#define ACI_TARGET_DN (int)0x000100 /* target has DN */ +#define ACI_TARGET_ATTR (int)0x000200 /* target is an attr */ +#define ACI_TARGET_PATTERN (int)0x000400 /* target has some patt */ +#define ACI_TARGET_FILTER (int)0x000800 /* target has a filter */ +#define ACI_ACLTXT (int)0x001000 /* ACI has text only */ +#define ACI_TARGET_NOT (int)0x002000 /* it's a != */ +#define ACI_TARGET_ATTR_NOT (int)0x004000 /* It's a != manager */ +#define ACI_TARGET_FILTER_NOT (int)0x008000 /* It's a != filter */ +#define ACI_UNUSED2 (int)0x010000 /* Unused */ +#define ACI_HAS_ALLOW_RULE (int)0x020000 /* allow (...) */ +#define ACI_HAS_DENY_RULE (int)0x040000 /* deny (...) */ +#define ACI_CONTAIN_NOT_USERDN (int)0x080000 /* userdn != blah */ +#define ACI_TARGET_ATTR_ADD_FILTERS (int)0x100000 +#define ACI_TARGET_ATTR_DEL_FILTERS (int)0x200000 +#define ACI_CONTAIN_NOT_GROUPDN (int)0x400000 /* groupdn != blah */ +#define ACI_CONTAIN_NOT_ROLEDN (int)0x800000 + + int aci_access; + +/* + * See also aclpb_access which is used to store rights too. +*/ + + short aci_ruleType; /* kinds of rules in the ACL */ + +#define ACI_USERDN_RULE (short) 0x0001 +#define ACI_USERDNATTR_RULE (short) 0x0002 +#define ACI_GROUPDN_RULE (short) 0x0004 +#define ACI_GROUPDNATTR_RULE (short) 0x0008 +#define ACI_AUTHMETHOD_RULE (short) 0x0010 +#define ACI_IP_RULE (short) 0x0020 +#define ACI_DNS_RULE (short) 0x0040 +#define ACI_TIMEOFDAY_RULE (short) 0x0080 +#define ACI_DAYOFWEEK_RULE (short) 0x0010 +#define ACI_USERATTR_RULE (short) 0x0200 +/* + * These are extension of USERDN/GROUPDN rule. However since the + * semantics are quite different, we classify them as different rules. + * ex: groupdn = "ldap:///cn=helpdesk, ou=$attr.dept, o=$dn.o, o=isp" + */ +#define ACI_PARAM_DNRULE (short) 0x0400 +#define ACI_PARAM_ATTRRULE (short) 0x0800 +#define ACI_USERDN_SELFRULE (short) 0x1000 +#define ACI_ROLEDN_RULE (short) 0x2000 + + + +#define ACI_ATTR_RULES ( ACI_USERDNATTR_RULE | ACI_GROUPDNATTR_RULE | ACI_USERATTR_RULE | ACI_PARAM_DNRULE | ACI_PARAM_ATTRRULE | ACI_USERDN_SELFRULE) +#define ACI_CACHE_RESULT_PER_ENTRY ACI_ATTR_RULES + + short aci_elevel; /* Based on the aci type some idea about the + ** execution flow + */ + int aci_index; /* index # */ + Slapi_DN *aci_sdn; /* location */ + Slapi_Filter *target; /* Target is a DN */ + Targetattr **targetAttr; + char *targetFilterStr; + struct slapi_filter *targetFilter; /* Target has a filter */ + Targetattrfilter **targetAttrAddFilters; + Targetattrfilter **targetAttrDelFilters; + char *aclName; /* ACL name */ + struct ACLListHandle *aci_handle; /*handle of the ACL */ + aciMacro *aci_macro; + struct aci *aci_next; /* next one */ +}aci_t; + +/* Aci excution level +** The idea is that for each handle types, we can prioritize which one to evaluate first. +** Evaluating the user before the group is better. +*/ +#define ACI_ELEVEL_USERDN_ANYONE 0 +#define ACI_ELEVEL_USERDN_ALL 1 +#define ACI_ELEVEL_USERDN 2 +#define ACI_ELEVEL_USERDNATTR 3 +#define ACI_ELEVEL_GROUPDNATTR_URL 4 +#define ACI_ELEVEL_GROUPDNATTR 5 +#define ACI_ELEVEL_GROUPDN 6 +#define ACI_MAX_ELEVEL ACI_ELEVEL_GROUPDN +1 +#define ACI_DEFAULT_ELEVEL ACI_MAX_ELEVEL + + +#define ACLPB_MAX_SELECTED_ACLS 200 + +typedef struct result_cache { + int aci_index; + short aci_ruleType; + short result; +#define ACLPB_CACHE_READ_RES_ALLOW (short)0x0001 /* used for ALLOW handles only */ +#define ACLPB_CACHE_READ_RES_DENY (short)0x0002 /* used for DENY handles only */ +#define ACLPB_CACHE_SEARCH_RES_ALLOW (short)0x0004 /* used for ALLOW handles only */ +#define ACLPB_CACHE_SEARCH_RES_DENY (short)0x0008 /* used for DENY handles only */ +#define ACLPB_CACHE_SEARCH_RES_SKIP (short)0x0010 /* used for both types */ +#define ACLPB_CACHE_READ_RES_SKIP (short)0x0020 /* used for both types */ +}r_cache_t; +#define ACLPB_MAX_CACHE_RESULTS ACLPB_MAX_SELECTED_ACLS + +/* + * This is use to keep the result of the evaluation of the attr. + * We are only intrested in read/searc only. + */ +struct acl_attrEval { + char *attrEval_name; /* Attribute Name */ + short attrEval_r_status; /* status of read evaluation */ + short attrEval_s_status; /* status of search evaluation */ + int attrEval_r_aciIndex; /* Index of the ACL which grants access*/ + int attrEval_s_aciIndex; /* Index of the ACL which grants access*/ + +#define ACL_ATTREVAL_SUCCESS 0x1 +#define ACL_ATTREVAL_FAIL 0x2 +#define ACL_ATTREVAL_RECOMPUTE 0x4 +#define ACL_ATTREVAL_DETERMINISTIC 7 +#define ACL_ATTREVAL_INVALID 0x8 + +}; +typedef struct acl_attrEval AclAttrEval; + + +/* + * Struct to keep the evaluation context information. This struct is + * used in multiple places ( different instance ) to keep the context for + * current entry evaluation, previous entry evaluation or previous operation + * evaluation status. + */ +#define ACLPB_MAX_ATTR_LEN 100 +#define ACLPB_MAX_ATTRS 100 +struct acleval_context { + + /* Information about the attrs */ + AclAttrEval acle_attrEval[ACLPB_MAX_ATTRS]; + short acle_numof_attrs; + + /* Handles information */ + short acle_numof_tmatched_handles; + int acle_handles_matched_target[ACLPB_MAX_SELECTED_ACLS]; +}; +typedef struct acleval_context aclEvalContext; + + +struct acl_usergroup { + short aclug_signature; +/* + * To modify refcnt you need either the write lock on the whole cache or + * the reader lock on the whole cache plus this refcnt mutex +*/ + short aclug_refcnt; + PRLock *aclug_refcnt_mutex; + + + char *aclug_ndn; /* Client's normalized DN */ + + char **aclug_member_groups; + short aclug_member_group_size; + short aclug_numof_member_group; + + char **aclug_notmember_groups; + short aclug_notmember_group_size; + short aclug_numof_notmember_group; + struct acl_usergroup *aclug_next; + struct acl_usergroup *aclug_prev; + +}; +typedef struct acl_usergroup aclUserGroup; + +#define ACLUG_INCR_GROUPS_LIST 20 + +struct aci_container { + Slapi_DN *acic_sdn; /* node DN */ + aci_t *acic_list; /* List of the ACLs for that node */ + int acic_index; /* index to the container array */ +}; +typedef struct aci_container AciContainer; + +struct acl_pblock { + int aclpb_state; + +#define ACLPB_ACCESS_ALLOWED_ON_A_ATTR 0x000001 +#define ACLPB_ACCESS_DENIED_ON_ALL_ATTRS 0x000002 +#define ACLPB_ACCESS_ALLOWED_ON_ENTRY 0x000004 +#define ACLPB_ATTR_STAR_MATCHED 0x000008 +#define ACLPB_FOUND_ATTR_RULE 0x000010 +#define ACLPB_SEARCH_BASED_ON_LIST 0x000020 +#define ACLPB_EXECUTING_DENY_HANDLES 0x000040 +#define ACLPB_EXECUTING_ALLOW_HANDLES 0x000080 +#define ACLPB_ACCESS_ALLOWED_USERATTR 0x000100 +#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + #define ACLPB_USER_SPECIFIED_ATTARS 0x000200 + #define ACLPB_USER_WANTS_ALL_ATTRS 0x000400 +#endif +#define ACLPB_EVALUATING_FIRST_ATTR 0x000800 +#define ACLPB_FOUND_A_ENTRY_TEST_RULE 0x001000 +#define ACLPB_SEARCH_BASED_ON_ENTRY_LIST 0x002000 +#define ACLPB_DONOT_USE_CONTEXT_ACLS 0x004000 +#define ACLPB_HAS_ACLCB_EVALCONTEXT 0x008000 +#define ACLPB_COPY_EVALCONTEXT 0x010000 +#define ACLPB_MATCHES_ALL_ACLS 0x020000 +#define ACLPB_INITIALIZED 0x040000 +#define ACLPB_INCR_ACLCB_CACHE 0x080000 +#define ACLPB_UPD_ACLCB_CACHE 0x100000 +#define ACLPB_ATTR_RULE_EVALUATED 0x200000 +#define ACLPB_DONOT_EVALUATE_PROXY 0x400000 + + +#define ACLPB_RESET_MASK ( ACLPB_ACCESS_ALLOWED_ON_A_ATTR | ACLPB_ACCESS_DENIED_ON_ALL_ATTRS | \ + ACLPB_ACCESS_ALLOWED_ON_ENTRY | ACLPB_ATTR_STAR_MATCHED | \ + ACLPB_FOUND_ATTR_RULE | ACLPB_EVALUATING_FIRST_ATTR | \ + ACLPB_FOUND_A_ENTRY_TEST_RULE ) +#define ACLPB_STATE_ALL 0x3fffff + + int aclpb_res_type; + + #define ACLPB_NEW_ENTRY 0x100 + #define ACLPB_EFFECTIVE_RIGHTS 0x200 + #define ACLPB_RESTYPE_ALL 0x7ff + + /* + * The bottom bye used to be for rights. It's free now as they have + * been moved to aclpb_access. + */ + + int aclpb_access; + +#define ACLPB_SLAPI_ACL_WRITE_ADD 0x200 +#define ACLPB_SLAPI_ACL_WRITE_DEL 0x400 + + /* stores the requested access during an operation */ + + short aclpb_signature; + short aclpb_type; +#define ACLPB_TYPE_MAIN 1 +#define ACLPB_TYPE_MAIN_STR "Main Block" +#define ACLPB_TYPE_PROXY 2 +#define ACLPB_TYPE_PROXY_STR "Proxy Block" + + Slapi_Entry *aclpb_client_entry; /* A copy of client's entry */ + Slapi_PBlock *aclpb_pblock; /* back to LDAP PBlock */ + int aclpb_optype; /* current optype from pb */ + + /* Current entry/dn/attr evaluation info */ + Slapi_Entry *aclpb_curr_entry; /* current Entry being processed */ + int aclpb_num_entries; + Slapi_DN *aclpb_curr_entry_sdn; /* Entry's SDN */ + Slapi_DN *aclpb_authorization_sdn; /* dn used for authorization */ + + AclAttrEval *aclpb_curr_attrEval; /* Current attr being evaluated */ + struct berval *aclpb_curr_attrVal; /* Value of Current attr */ + Slapi_Entry *aclpb_filter_test_entry; /* Scratch entry */ + aci_t *aclpb_curr_aci; + char *aclpb_Evalattr; /* The last attr evaluated */ + + /* Plist and eval info */ + ACLEvalHandle_t *aclpb_acleval; /* acleval handle for evaluation */ + struct PListStruct_s *aclpb_proplist;/* All the needed property */ + + /* DENY ACI HANDLES */ + aci_t **aclpb_deny_handles; + int aclpb_deny_handles_size; + int aclpb_num_deny_handles; + + /* ALLOW ACI HANDLES */ + aci_t **aclpb_allow_handles; + int aclpb_allow_handles_size; + int aclpb_num_allow_handles; + + /* This is used in the groupdnattr="URL" rule + ** Keep a list of base where searched has been done + */ + char **aclpb_grpsearchbase; + int aclpb_grpsearchbase_size; + int aclpb_numof_bases; + + aclUserGroup *aclpb_groupinfo; + + /* Keep the Group nesting level */ + int aclpb_max_nesting_level; + int aclpb_max_member_sizelimit; + + + /* To keep the results in the cache */ + + int aclpb_last_cache_result; + struct result_cache aclpb_cache_result[ACLPB_MAX_CACHE_RESULTS]; + + /* Index numbers of ACLs selected based on a locality search*/ + char *aclpb_search_base; + int aclpb_base_handles_index[ACLPB_MAX_SELECTED_ACLS]; + int aclpb_handles_index[ACLPB_MAX_SELECTED_ACLS]; + + /* Evaluation context info + ** 1) Context cached from aclcb ( from connection struct ) + ** 2) Context cached from previous entry evaluation + ** 3) current entry evaluation info + */ + aclEvalContext aclpb_curr_entryEval_context; + aclEvalContext aclpb_prev_entryEval_context; + aclEvalContext aclpb_prev_opEval_context; + + /* Currentry anom profile sumamry */ + struct scoped_entry_anominfo aclpb_scoped_entry_anominfo; + + /* Some Statistics gathering */ + PRUint16 aclpb_stat_acllist_scanned; + PRUint16 aclpb_stat_aclres_matched; + PRUint16 aclpb_stat_total_entries; + PRUint16 aclpb_stat_anom_list_scanned; + PRUint16 aclpb_stat_num_copycontext; + PRUint16 aclpb_stat_num_copy_attrs; + PRUint16 aclpb_stat_num_tmatched_acls; + PRUint16 aclpb_stat_unused; + CERTCertificate *aclpb_clientcert; + AciContainer *aclpb_aclContainer; + struct acl_pblock *aclpb_proxy; /* Child proxy block */ + acl_ht_t *aclpb_macro_ht; /* ht for partial macro strs */ + + struct acl_pblock *aclpb_prev; /* Previpous in the chain */ + struct acl_pblock *aclpb_next; /* Next in the chain */ +}; +typedef struct acl_pblock Acl_PBlock; + +/* PBLCOK TYPES */ +typedef enum +{ + ACLPB_BINDDN_PBLOCK, + ACLPB_PROXYDN_PBLOCK, + ACLPB_ALL_PBLOCK +}aclpb_types; + + +#define ACLPB_EVALCONTEXT_CURR 1 +#define ACLPB_EVALCONTEXT_PREV 2 +#define ACLPB_EVALCONTEXT_ACLCB 3 + + + +/* Cleaning/ deallocating/ ... acl_freeBlock() */ +#define ACL_CLEAN_ACLPB 1 +#define ACL_COPY_ACLCB 2 +#define ACL_CLEAN_ACLCB 3 + +/* used to differentiate acl plugins sharing the same lib */ +#define ACL_PLUGIN_IDENTITY 1 +#define ACL_PREOP_PLUGIN_IDENTITY 2 + + +/* start with 50 and then add 50 more as required + * The first ACI_MAX_ELEVEL slots are predefined. + */ +#define ACLPB_INCR_LIST_HANDLES ACI_MAX_ELEVEL + 43 + +#define ACLPB_INCR_BASES 5 + +/* + * acl private block which hangs from connection structure. + * This is allocated the first time an operation is done and freed when the + * connection are cleaned. + * + */ +struct acl_cblock { + + short aclcb_aclsignature; + short aclcb_state; +#define ACLCB_HAS_CACHED_EVALCONTEXT 0x1 + + Slapi_DN *aclcb_sdn; /* Contains bind SDN */ + aclEvalContext aclcb_eval_context; + PRLock *aclcb_lock; /* shared lock */ +}; + +struct acl_groupcache { + short aclg_state; /* status information */ + short aclg_signature; + int aclg_num_userGroups; + aclUserGroup *aclg_first; + aclUserGroup *aclg_last; + PRRWLock *aclg_rwlock; /* lock to monitor the group cache */ +}; +typedef struct acl_groupcache aclGroupCache; + + +/* Type of extensions that can be registered */ +typedef enum +{ + ACL_EXT_OPERATION, /* extension for Operation object */ + ACL_EXT_CONNECTION, /* extension for Connection object */ + ACL_EXT_ALL +}ext_type; + +/* Used to pass data around in acllas.c */ + +typedef struct { + char *clientDn; + char *authType; + int anomUser; + Acl_PBlock *aclpb; + Slapi_Entry *resourceEntry; + +}lasInfo; + + +/* reasons why the subject allowed/denied access--good for logs */ + +typedef enum{ +ACL_REASON_NO_ALLOWS, +ACL_REASON_RESULT_CACHED_DENY, +ACL_REASON_EVALUATED_DENY, /* evaluated deny */ +ACL_REASON_RESULT_CACHED_ALLOW, /* cached allow */ +ACL_REASON_EVALUATED_ALLOW, /* evalauted allow */ +ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, /* were allows/denies, but none matched */ +ACL_REASON_NONE, /* no reason available */ +ACL_REASON_ANON_ALLOWED, +ACL_REASON_ANON_DENIED, +ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS, +ACL_REASON_EVALCONTEXT_CACHED_ALLOW, +ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED, +ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW +}aclReasonCode_t; + +typedef struct{ + aci_t *deciding_aci; + aclReasonCode_t reason; +}aclResultReason_t; +#define ACL_NO_DECIDING_ACI_INDEX -10 + + +/* Extern declaration for backend state change fnc: acllist.c and aclinit.c */ + +void acl_be_state_change_fnc ( void *handle, char *be_name, int old_state, + int new_state); + + +/* Extern declaration for ATTRs */ + +extern int +DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASUserDnGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASGroupDnGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASEntryGetter(NSErr_t *errp, PList_t subject, PList_t resource, + PList_t auth_info, PList_t global_auth, void *arg); + +extern int +DS_LASCertGetter(NSErr_t *errp, PList_t subject, PList_t resource, + PList_t auth_info, PList_t global_auth, void *arg); + +/* function declartion for LAses supported by DS */ + +extern int DS_LASUserEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASGroupEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASUserDnEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASGroupDnEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASRoleDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASUserDnAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASAuthMethodEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASGroupDnAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASRoleDnAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASUserAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +/* other function declaration */ +int aclinit_main(); +int acl_match_substring (struct slapi_filter *f, char *str, int match); +void acl_print_acllib_err(NSErr_t *errp, char * str); +void acl_initBlock ( Slapi_PBlock *pb ); +void acl_freeBlock ( Slapi_PBlock *pb, int state ); +int acl_read_access_allowed_on_entry ( Slapi_PBlock *pb, Slapi_Entry *e, + char **attrs, int access); +int acl_access_allowed_modrdn ( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access); +int acl_read_access_allowed_on_attr ( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access); +void acl_set_acllist (Slapi_PBlock *pb, int scope, char *base); +void acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf); +void acl_modified ( Slapi_PBlock *pb, int optype, char *dn, void *change); +int acl_access_allowed_disjoint_resource( Slapi_PBlock *pb, Slapi_Entry *e, + char *attr, struct berval *val, int access ); +int acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char **attrs, + struct berval *val, int access , int flags, char **errbuf); +int acl_access_allowed( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access ); +int acl_verify_syntax(const Slapi_DN *e_sdn, const struct berval *bval); +aclUserGroup * acl_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn); +void acl_print_acllib_err (NSErr_t *errp , char * str); +int acl_check_mods( Slapi_PBlock *pb, Slapi_Entry *e, LDAPMod **mods, char **errbuf ); +int acl_verify_aci_syntax (Slapi_Entry *e, char **errbuf); +char * acl__access2str(int access); +void acl_strcpy_special (char *d, char *s); +int acl_parse(char * str, aci_t *aci_item); +char * acl_access2str ( int access ); +int acl_init_ext (); +void * acl_get_ext (ext_type type, void *object); +void acl_set_ext (ext_type type, void *object, void *data); +void acl_reset_ext_status (ext_type type, void *object); +void acl_init_op_ext ( Slapi_PBlock *pb , int type, char *dn, int copy); +void * acl_operation_ext_constructor (void *object, void *parent ); +void acl_operation_ext_destructor ( void *ext, void *object, void *parent ); +void * acl_conn_ext_constructor (void *object, void *parent ); +void acl_conn_ext_destructor ( void *ext, void *object, void *parent ); +void acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only ); +void acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src, + aclEvalContext *dest , int copy_attr_only ); +struct acl_pblock * acl_get_aclpb ( Slapi_PBlock *pb, int type ); +int acl_client_anonymous ( Slapi_PBlock *pb ); +short acl_get_aclsignature(); +void acl_set_aclsignature( short value); +void acl_regen_aclsignature(); +struct acl_pblock * acl_new_proxy_aclpb( Slapi_PBlock *pb ); +void acl_set_authorization_dn( Slapi_PBlock *pb, char *dn, int type ); +int acl_get_proxyauth_dn( Slapi_PBlock *pb, char **proxydnp, + char **errtextp ); +void acl_init_aclpb ( Slapi_PBlock *pb , Acl_PBlock *aclpb, + const char *dn, int copy_from_aclcb); +int acl_create_aclpb_pool (); +int acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e ); + +int aclext_alloc_lockarray (); + +int aclutil_str_appened(char **str1, const char *str2); +void aclutil_print_err (int rv , const Slapi_DN *sdn, + const struct berval* val, char **errbuf); +void aclutil_print_aci (aci_t *aci_item, char *type); +short aclutil_gen_signature ( short c_signature ); +void aclutil_print_resource( struct acl_pblock *aclpb, char *right , char *attr, char *clientdn ); +char * aclutil_expand_paramString ( char *str, Slapi_Entry *e ); + + +void acllist_init_scan (Slapi_PBlock *pb, int scope, char *base); +aci_t * acllist_get_first_aci (Acl_PBlock *aclpb, PRUint32 *cookie ); +aci_t * acllist_get_next_aci ( Acl_PBlock *aclpb, aci_t *curraci, PRUint32 *cookie ); +aci_t * acllist_get_aci_new (); +void acllist_free_aci (aci_t *item); +void acllist_acicache_READ_UNLOCK(void); +void acllist_acicache_READ_LOCK(void); +void acllist_acicache_WRITE_UNLOCK(void); +void acllist_acicache_WRITE_LOCK(void); +void acllist_aciscan_update_scan ( Acl_PBlock *aclpb, char *edn ); +int acllist_remove_aci_needsLock( const Slapi_DN *sdn, const struct berval *attr ); +int acllist_insert_aci_needsLock( const Slapi_DN *e_sdn, const struct berval* aci_attr); +int acllist_init (); +int acllist_moddn_aci_needsLock ( Slapi_DN *oldsdn, char *newdn ); +void acllist_print_tree ( Avlnode *root, int *depth, char *start, char *side); +AciContainer *acllist_get_aciContainer_new ( ); +void acllist_done_aciContainer ( AciContainer *); + +aclUserGroup* aclg_find_userGroup (char *n_dn); +void aclg_regen_ugroup_signature( aclUserGroup *ugroup); +void aclg_markUgroupForRemoval ( aclUserGroup *u_group ); +void aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group); +int aclg_numof_usergroups(void); +int aclgroup_init (); +void aclg_regen_group_signature (); +void aclg_reset_userGroup ( struct acl_pblock *aclpb ); +void aclg_init_userGroup ( struct acl_pblock *aclpb, const char *dn , int got_lock); +aclUserGroup * aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn); + +void aclg_lock_groupCache (int type ); +void aclg_unlock_groupCache (int type ); + +int aclanom_init(); +int aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb, + Slapi_Entry *e, char *attr, int access); +void aclanom_get_suffix_info(Slapi_Entry *e, struct acl_pblock *aclpb ); +void aclanom_invalidateProfile(); +typedef enum{ + DONT_TAKE_ACLCACHE_READLOCK, + DO_TAKE_ACLCACHE_READLOCK, + DONT_TAKE_ACLCACHE_WRITELOCK, + DO_TAKE_ACLCACHE_WRITELOCK +}acl_lock_flag_t; +void aclanom_gen_anomProfile (acl_lock_flag_t lock_flag); +int aclanom_is_client_anonymous ( Slapi_PBlock *pb ); +int aclinit_main (); +typedef struct aclinit_handler_callback_data { +#define ACL_ADD_ACIS 1 +#define ACL_REMOVE_ACIS 0 + int op; + int retCode; + acl_lock_flag_t lock_flag; +}aclinit_handler_callback_data_t; +int +aclinit_search_and_update_aci ( int thisbeonly, const Slapi_DN *base, + char *be_name, int scope, int op, + acl_lock_flag_t lock_flag); +void *aclplugin_get_identity(int plug); +int +acl_dn_component_match( const char *ndn, char *match_this, int component_number); +char * +acl_match_macro_in_target( const char *ndn, char *match_this, + char *macro_ptr); +char* get_next_component(char *dn, int *index); +int acl_match_prefix( char *macro_prefix, const char *ndn, + int *exact_match); +char * +get_this_component(char *dn, int *index); +int +acl_find_comp_end( char * s); +char * +acl_replace_str(char * s, char *substr, char* replace_with); +int acl_strstr(char * s, char *substr); +int aclutil_evaluate_macro( char * rule, lasInfo *lasinfo, + acl_eval_types evalType ); + +/* acl hash table functions */ +void acl_ht_add_and_freeOld(acl_ht_t * acl_ht, PLHashNumber key,char *value); +acl_ht_t *acl_ht_new(void); +void acl_ht_free_all_entries_and_values( acl_ht_t *acl_ht); +void acl_ht_remove( acl_ht_t *acl_ht, PLHashNumber key); +void *acl_ht_lookup( acl_ht_t *acl_ht, PLHashNumber key); +void acl_ht_display_ht( acl_ht_t *acl_ht); + +/* acl get effective rights */ +int +acl_get_effective_rights ( Slapi_PBlock *pb, Slapi_Entry *e, + char **attrs, struct berval *val, int access, char **errbuf ); + +char* aclutil__access_str (int type , char str[]); + +#endif /* _ACL_H_ */ diff --git a/ldap/servers/plugins/acl/acl_ext.c b/ldap/servers/plugins/acl/acl_ext.c new file mode 100644 index 00000000..28a9ce18 --- /dev/null +++ b/ldap/servers/plugins/acl/acl_ext.c @@ -0,0 +1,968 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +static void acl__done_aclpb ( struct acl_pblock *aclpb ); +static void acl__dump_stats ( struct acl_pblock *aclpb , const char *block_type); +static Acl_PBlock * acl__get_aclpb_from_pool ( ); +static int acl__put_aclpb_back_to_pool ( Acl_PBlock *aclpb ); +static Acl_PBlock * acl__malloc_aclpb ( ); +static char * acl__get_aclpb_type ( Acl_PBlock *aclpb ); +static PRLock *aclext_get_lock (); + + +struct acl_pbqueue { + Acl_PBlock *aclq_free; + Acl_PBlock *aclq_busy; + short aclq_nfree; + short aclq_nbusy; + PRLock *aclq_lock; +}; +typedef struct acl_pbqueue Acl_PBqueue; + +static Acl_PBqueue *aclQueue; + +/* structure with information for each extension */ +typedef struct acl_ext +{ + char *object_name; /* name of the object extended */ + int object_type; /* handle to the extended object */ + int handle; /* extension handle */ +} acl_ext; + +static acl_ext acl_ext_list [ACL_EXT_ALL]; + +/* + * EXTENSION INITIALIZATION, CONSTRUCTION, & DESTRUCTION + * + */ +int +acl_init_ext () +{ + int rc; + + acl_ext_list[ACL_EXT_OPERATION].object_name = SLAPI_EXT_OPERATION; + + rc = slapi_register_object_extension(plugin_name, SLAPI_EXT_OPERATION, + acl_operation_ext_constructor, + acl_operation_ext_destructor, + &acl_ext_list[ACL_EXT_OPERATION].object_type, + &acl_ext_list[ACL_EXT_OPERATION].handle); + + if ( rc != 0 ) return rc; + + acl_ext_list[ACL_EXT_CONNECTION].object_name = SLAPI_EXT_CONNECTION; + rc = slapi_register_object_extension(plugin_name, SLAPI_EXT_CONNECTION, + acl_conn_ext_constructor, + acl_conn_ext_destructor, + &acl_ext_list[ACL_EXT_CONNECTION].object_type, + &acl_ext_list[ACL_EXT_CONNECTION].handle); + + return rc; + +} + +/* Interface to get the extensions */ +void * +acl_get_ext (ext_type type, void *object) +{ + struct acl_ext ext; + void *data; + + if ( type >= ACL_EXT_ALL ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Invalid extension type:%d\n", type ); + return NULL; + } + + /* find the requested extension */ + ext = acl_ext_list [type]; + data = slapi_get_object_extension(ext.object_type, object, ext.handle); + + return data; +} + +void +acl_set_ext (ext_type type, void *object, void *data) +{ + if ( type >= 0 && type < ACL_EXT_ALL ) + { + struct acl_ext ext = acl_ext_list [type]; + slapi_set_object_extension ( ext.object_type, object, ext.handle, data ); + } +} + +/**************************************************************************** + * Global lock array so that private extension between connection and operation + * co-exist + * + ******************************************************************************/ +struct ext_lockArray { + PRLock **lockArray; + int numlocks; +}; + +static struct ext_lockArray extLockArray; + +/* PKBxxx: make this a configurable. Start with 2 * maxThreads */ +#define ACLEXT_MAX_LOCKS 40 + +int +aclext_alloc_lockarray ( ) +{ + + int i; + PRLock *lock; + + extLockArray.lockArray = + (PRLock **) slapi_ch_calloc ( ACLEXT_MAX_LOCKS, sizeof ( PRLock *) ); + + for ( i =0; i < ACLEXT_MAX_LOCKS; i++) { + if (NULL == (lock = PR_NewLock()) ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "Unable to allocate locks used for private extension\n"); + return 1; + } + extLockArray.lockArray[i] = lock; + } + extLockArray.numlocks = ACLEXT_MAX_LOCKS; + return 0; +} +static PRUint32 slot_id =0; +static PRLock * +aclext_get_lock () +{ + + PRUint16 slot = slot_id % ACLEXT_MAX_LOCKS; + slot_id++; + return ( extLockArray.lockArray[slot] ); + +} +/****************************************************************************/ +/* CONNECTION EXTENSION SPECIFIC */ +/****************************************************************************/ +void * +acl_conn_ext_constructor ( void *object, void *parent ) +{ + struct acl_cblock *ext = NULL; + + ext = (struct acl_cblock * ) slapi_ch_calloc (1, sizeof (struct acl_cblock ) ); + if (( ext->aclcb_lock = aclext_get_lock () ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "Unable to get Read/Write lock for CONNECTION extension\n"); + slapi_ch_free ( (void **) &ext ); + return NULL; + } + ext->aclcb_sdn = slapi_sdn_new (); + /* store the signatures */ + ext->aclcb_aclsignature = acl_get_aclsignature(); + ext->aclcb_state = -1; + return ext; + + +} + +void +acl_conn_ext_destructor ( void *ext, void *object, void *parent ) +{ + struct acl_cblock *aclcb = ext; + PRLock *shared_lock; + + if ( NULL == aclcb ) return; + PR_Lock ( aclcb->aclcb_lock ); + shared_lock = aclcb->aclcb_lock; + acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /* clean*/ ); + slapi_sdn_free ( &aclcb->aclcb_sdn ); + aclcb->aclcb_lock = NULL; + slapi_ch_free ( (void **) &aclcb ); + + PR_Unlock ( shared_lock ); +} + +/****************************************************************************/ +/* OPERATION EXTENSION SPECIFIC */ +/****************************************************************************/ +void * +acl_operation_ext_constructor ( void *object, void *parent ) +{ + Acl_PBlock *aclpb = NULL; + + TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_start ,"ACL",""); + + /* This means internal operations */ + if ( NULL == parent) { + + TNF_PROBE_1_DEBUG(acl_operation_ext_constructor_end ,"ACL","", + tnf_string,internal_op,""); + + return NULL; + } + + aclpb = acl__get_aclpb_from_pool(); + if ( NULL == aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Operation extension allocation Failed\n"); + } + + TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_end ,"ACL",""); + + return aclpb; + +} + +void +acl_operation_ext_destructor ( void *ext, void *object, void *parent ) +{ + + struct acl_cblock *aclcb = NULL; + struct acl_pblock *aclpb = NULL; + + TNF_PROBE_0_DEBUG(acl_operation_ext_destructor_start ,"ACL",""); + + if ( (NULL == parent ) || (NULL == ext)) { + TNF_PROBE_1_DEBUG(acl_operation_ext_destructor_end ,"ACL","", + tnf_string,internal_op,""); + + return; + } + + aclpb = (Acl_PBlock *) ext; + + if ( (NULL == aclpb) || + (NULL == aclpb->aclpb_pblock) || + (!(aclpb->aclpb_state & ACLPB_INITIALIZED))) + goto clean_aclpb; + + /* get the connection extension */ + aclcb = (struct acl_cblock *) acl_get_ext ( ACL_EXT_CONNECTION, parent ); + + /* We are about to get out of this connection. Move all the + ** cached information to the acl private block which hangs + ** from the connection struct. + */ + if ( aclcb && aclcb->aclcb_lock && + ( (aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE ) || + (aclpb->aclpb_state & ACLPB_INCR_ACLCB_CACHE ) ) ) { + + aclEvalContext *c_evalContext; + int attr_only = 0; + PRLock *shared_lock = aclcb->aclcb_lock; + + if (aclcb->aclcb_lock ) PR_Lock ( shared_lock ); + else { + goto clean_aclpb; + } + if ( !aclcb->aclcb_lock ) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "aclcb lock released! aclcb cache can't be refreshed\n"); + PR_Unlock ( shared_lock ); + goto clean_aclpb; + } + + /* We need to refresh the aclcb cache */ + if ( aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE ) + acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /* clean*/ ); + if ( aclpb->aclpb_prev_entryEval_context.acle_numof_attrs ) { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } else { + c_evalContext = &aclpb->aclpb_curr_entryEval_context; + } + + if (( aclpb->aclpb_state & ACLPB_INCR_ACLCB_CACHE ) && + ! ( aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE )) + attr_only = 1; + + acl_copyEval_context ( NULL, c_evalContext, &aclcb->aclcb_eval_context, attr_only ); + + aclcb->aclcb_aclsignature = aclpb->aclpb_signature; + if ( aclcb->aclcb_sdn && aclpb->aclpb_authorization_sdn && + (0 != slapi_sdn_compare ( aclcb->aclcb_sdn, + aclpb->aclpb_authorization_sdn ) ) ) { + slapi_sdn_set_ndn_byval( aclcb->aclcb_sdn, + slapi_sdn_get_ndn ( aclpb->aclpb_authorization_sdn ) ); + } + aclcb->aclcb_state = 0; + aclcb->aclcb_state |= ACLCB_HAS_CACHED_EVALCONTEXT; + + PR_Unlock ( shared_lock ); + } + +clean_aclpb: + if ( aclpb ) { + + if ( aclpb->aclpb_proxy ) { + TNF_PROBE_0_DEBUG(acl_proxy_aclpbdoneback_start ,"ACL",""); + + acl__done_aclpb( aclpb->aclpb_proxy ); + + /* Put back to the Pool */ + acl__put_aclpb_back_to_pool ( aclpb->aclpb_proxy ); + aclpb->aclpb_proxy = NULL; + TNF_PROBE_0_DEBUG(acl_proxy_aclpbdoneback_end ,"ACL",""); + + } + + TNF_PROBE_0_DEBUG(acl_aclpbdoneback_start ,"ACL",""); + + acl__done_aclpb( aclpb); + acl__put_aclpb_back_to_pool ( aclpb ); + + TNF_PROBE_0_DEBUG(acl_aclpbdoneback_end ,"ACL",""); + + } + + TNF_PROBE_0_DEBUG(acl_operation_ext_destructor_end ,"ACL",""); + +} + +/****************************************************************************/ +/* FUNCTIONS TO MANAGE THE ACLPB POOL */ +/****************************************************************************/ + +/* + * Get the right acl pblock + */ +struct acl_pblock * +acl_get_aclpb ( Slapi_PBlock *pb, int type ) +{ + Acl_PBlock *aclpb = NULL; + void *op = NULL; + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + aclpb = (Acl_PBlock *) acl_get_ext ( ACL_EXT_OPERATION, op ); + if (NULL == aclpb ) return NULL; + + if ( type == ACLPB_BINDDN_PBLOCK ) + return aclpb; + else if ( type == ACLPB_PROXYDN_PBLOCK ) + return aclpb->aclpb_proxy; + else + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "acl_get_aclpb: Invalid aclpb type %d\n", type ); + return NULL; +} +/* + * Create a new proxy acl pblock + * + */ +struct acl_pblock * +acl_new_proxy_aclpb( Slapi_PBlock *pb ) +{ + void *op; + Acl_PBlock *aclpb = NULL; + Acl_PBlock *proxy_aclpb = NULL; + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + aclpb = (Acl_PBlock *) acl_get_ext ( ACL_EXT_OPERATION, op ); + if (NULL == aclpb ) return NULL; + + proxy_aclpb = acl__get_aclpb_from_pool(); + if (NULL == proxy_aclpb) return NULL; + proxy_aclpb->aclpb_type = ACLPB_TYPE_PROXY; + + aclpb->aclpb_proxy = proxy_aclpb; + + return proxy_aclpb; + +} +static int +acl__handle_config_entry (Slapi_Entry *e, void *callback_data ) +{ + *(int * )callback_data = slapi_entry_attr_get_int( e, "nsslapd-threadnumber"); + + return 0; +} + +/* + * Create a pool of acl pblock. Created during the ACL plugin + * initialization. + */ +int +acl_create_aclpb_pool () +{ + + Acl_PBlock *aclpb; + Acl_PBlock *prev_aclpb; + Acl_PBlock *first_aclpb; + int i; + int maxThreads= 0; + + slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL, 0 /* attrsonly */, + &maxThreads/* callback_data */, + NULL /* controls */, + NULL /* result_callback */, + acl__handle_config_entry, + NULL /* referral_callback */); + + /* Create a pool pf aclpb */ + maxThreads = 2 * maxThreads; + + aclQueue = ( Acl_PBqueue *) slapi_ch_calloc ( 1, sizeof (Acl_PBqueue) ); + aclQueue->aclq_lock = PR_NewLock(); + + if ( NULL == aclQueue->aclq_lock ) { + /* ERROR */ + return 1; + } + + prev_aclpb = NULL; + first_aclpb = NULL; + for ( i = 0; i < maxThreads; i++ ) { + aclpb = acl__malloc_aclpb (); + if ( 0 == i) first_aclpb = aclpb; + + aclpb->aclpb_prev = prev_aclpb; + if ( prev_aclpb ) prev_aclpb->aclpb_next = aclpb; + prev_aclpb = aclpb; + } + + /* Since this is the begining, everybody is in free list */ + aclQueue->aclq_free = first_aclpb; + + aclQueue->aclq_nfree = maxThreads; + return 0; +} + +/* + * Get a FREE acl pblock from the pool. + * + */ +static Acl_PBlock * +acl__get_aclpb_from_pool ( ) +{ + Acl_PBlock *aclpb = NULL; + Acl_PBlock *t_aclpb = NULL; + + + PR_Lock (aclQueue->aclq_lock ); + + /* Get the first aclpb from the FREE List */ + aclpb = aclQueue->aclq_free; + if ( aclpb ) { + t_aclpb = aclpb->aclpb_next; + if ( t_aclpb ) t_aclpb->aclpb_prev = NULL; + aclQueue->aclq_free = t_aclpb; + + /* make the this an orphon */ + aclpb->aclpb_prev = aclpb->aclpb_next = NULL; + + aclQueue->aclq_nfree--; + } else { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Unable to find a free aclpb\n"); + aclpb = acl__malloc_aclpb (); + } + + + /* Now move it to the FRONT of busy list */ + t_aclpb = aclQueue->aclq_busy; + aclpb->aclpb_next = t_aclpb; + if ( t_aclpb ) t_aclpb->aclpb_prev = aclpb; + aclQueue->aclq_busy = aclpb; + aclQueue->aclq_nbusy++; + + PR_Unlock (aclQueue->aclq_lock ); + + return aclpb; +} +/* + * Put the acl pblock into the FREE pool. + * + */ +static int +acl__put_aclpb_back_to_pool ( Acl_PBlock *aclpb ) +{ + + Acl_PBlock *p_aclpb, *n_aclpb; + + PR_Lock (aclQueue->aclq_lock ); + + /* Remove it from the busy list */ + n_aclpb = aclpb->aclpb_next; + p_aclpb = aclpb->aclpb_prev; + + if ( p_aclpb ) { + p_aclpb->aclpb_next = n_aclpb; + if ( n_aclpb ) n_aclpb->aclpb_prev = p_aclpb; + } else { + aclQueue->aclq_busy = n_aclpb; + if ( n_aclpb ) n_aclpb->aclpb_prev = NULL; + } + aclQueue->aclq_nbusy--; + + + /* Put back to the FREE list */ + aclpb->aclpb_prev = NULL; + n_aclpb = aclQueue->aclq_free; + aclpb->aclpb_next = n_aclpb; + if ( n_aclpb ) n_aclpb->aclpb_prev = aclpb; + aclQueue->aclq_free = aclpb; + aclQueue->aclq_nfree++; + + PR_Unlock (aclQueue->aclq_lock ); + + return 0; +} + +/* + * Allocate the basic acl pb + * + */ +static Acl_PBlock * +acl__malloc_aclpb ( ) +{ + Acl_PBlock *aclpb = NULL; + + + aclpb = ( Acl_PBlock *) slapi_ch_calloc ( 1, sizeof ( Acl_PBlock) ); + + /* Now set the propert we need for ACL evaluations */ + if ((aclpb->aclpb_proplist = PListNew(NULL)) == NULL) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to allocate the aclprop PList\n"); + return NULL; + } + + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_PROP_ACLPB, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the ACL PBLOCK in the Plist\n"); + return NULL; + } + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_USERDN, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the USER DN in the Plist\n"); + return NULL; + } + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_AUTHTYPE, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the AUTH TYPE in the Plist\n"); + return NULL; + } + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_ENTRY, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the ENTRY TYPE in the Plist\n"); + return NULL; + } + + /* + * ACL_ATTR_IP and ACL_ATTR_DNS are initialized lazily in the + * IpGetter and DnsGetter functions. + * They are removed from the aclpb property list at acl__aclpb_done() + * time. + */ + + /* allocate the acleval struct */ + aclpb->aclpb_acleval = (ACLEvalHandle_t *) ACL_EvalNew(NULL, NULL); + if (aclpb->aclpb_acleval == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to allocate the acleval block\n"); + return NULL; + } + /* + * This is a libaccess routine. + * Need to setup subject and resource property information + */ + + ACL_EvalSetSubject(NULL, aclpb->aclpb_acleval, aclpb->aclpb_proplist); + + /* allocate some space for attr name */ + aclpb->aclpb_Evalattr = (char *) slapi_ch_malloc (ACLPB_MAX_ATTR_LEN); + + aclpb->aclpb_deny_handles = (aci_t **) slapi_ch_calloc (1, + ACLPB_INCR_LIST_HANDLES * sizeof (aci_t *)); + + aclpb->aclpb_allow_handles = (aci_t **) slapi_ch_calloc (1, + ACLPB_INCR_LIST_HANDLES * sizeof (aci_t *)); + + aclpb->aclpb_deny_handles_size = ACLPB_INCR_LIST_HANDLES; + aclpb->aclpb_allow_handles_size = ACLPB_INCR_LIST_HANDLES; + + /* allocate the array for bases */ + aclpb->aclpb_grpsearchbase = (char **) + slapi_ch_malloc (ACLPB_INCR_BASES * sizeof(char *)); + aclpb->aclpb_grpsearchbase_size = ACLPB_INCR_BASES; + aclpb->aclpb_numof_bases = 0; + + /* Make sure aclpb_search_base is initialized to NULL..tested elsewhere! */ + aclpb->aclpb_search_base = NULL; + + aclpb->aclpb_authorization_sdn = slapi_sdn_new (); + aclpb->aclpb_curr_entry_sdn = slapi_sdn_new(); + + aclpb->aclpb_aclContainer = acllist_get_aciContainer_new (); + + /* hash table to store macro matched values from targets */ + aclpb->aclpb_macro_ht = acl_ht_new(); + + return aclpb; + +} + +/* Initializes the aclpb */ +void +acl_init_aclpb ( Slapi_PBlock *pb , Acl_PBlock *aclpb, const char *dn, int copy_from_aclcb) +{ + struct acl_cblock *aclcb = NULL; + char *authType; + void *conn; + unsigned long op_type; + + + if ( NULL == aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "acl_init_aclpb:No ACLPB\n"); + return; + } + + /* See if we have initialized already */ + if (aclpb->aclpb_state & ACLPB_INITIALIZED) return; + + slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &op_type ); + if ( op_type == SLAPI_OPERATION_BIND || op_type == SLAPI_OPERATION_UNBIND ) + return; + + /* We indicate the initialize here becuase, if something goes wrong, it's cleaned up + ** properly. + */ + aclpb->aclpb_state = ACLPB_INITIALIZED; + + /* We make an anonymous user a non null dn which is empty */ + if (dn && *dn != '\0' ) + slapi_sdn_set_ndn_byval ( aclpb->aclpb_authorization_sdn, dn ); + else + slapi_sdn_set_ndn_byval ( aclpb->aclpb_authorization_sdn, "" ); + + /* reset scoped entry cache to be empty */ + aclpb->aclpb_scoped_entry_anominfo.anom_e_nummatched = 0; + + if (PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_USERDN, + slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn), 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the USER DN in the Plist\n"); + return; + } + slapi_pblock_get ( pb, SLAPI_OPERATION_AUTHTYPE, &authType ); + if (PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_AUTHTYPE, authType, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the AUTH TYPE in the Plist\n"); + return; + } + /* PKBxxx: We should be getting it from the OP struct */ + slapi_pblock_get ( pb, SLAPI_CONN_CERT, &aclpb->aclpb_clientcert ); + + /* See if the we have already a cached info about user's group */ + aclg_init_userGroup ( aclpb, dn, 0 /* get lock */ ); + + slapi_pblock_get( pb, SLAPI_BE_MAXNESTLEVEL, &aclpb->aclpb_max_nesting_level ); + slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &aclpb->aclpb_max_member_sizelimit ); + if ( aclpb->aclpb_max_member_sizelimit == 0 ) { + aclpb->aclpb_max_member_sizelimit = SLAPD_DEFAULT_LOOKTHROUGHLIMIT; + } + slapi_pblock_get( pb, SLAPI_OPERATION_TYPE, &aclpb->aclpb_optype ); + + aclpb->aclpb_signature = acl_get_aclsignature(); + aclpb->aclpb_last_cache_result = 0; + aclpb->aclpb_pblock = pb; + PR_ASSERT ( aclpb->aclpb_pblock != NULL ); + + /* get the connection */ + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn); + aclcb = (struct acl_cblock *) acl_get_ext ( ACL_EXT_CONNECTION, conn ); + + if (NULL == aclcb || NULL == aclcb->aclcb_lock) { + /* This could happen if the client is dead and we are in + ** process of abondoning this operation + */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "No CONNECTION extension\n"); + + } else if ( aclcb->aclcb_state == -1 ) { + /* indicate that we need to update the cache */ + aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE; + aclcb->aclcb_state = 0; /* Nore this is ACLCB and not ACLPB */ + + } else if ( copy_from_aclcb ){ + char *cdn; + Slapi_DN *c_sdn; /* client SDN */ + + /* check if the operation is abandoned or not.*/ + if ( slapi_op_abandoned ( pb ) ) { + return; + } + + slapi_pblock_get ( pb, SLAPI_CONN_DN, &cdn ); /* We *must* free cdn! */ + c_sdn = slapi_sdn_new_dn_passin( cdn ); + PR_Lock ( aclcb->aclcb_lock ); + /* + * since PR_Lock is taken, + * we can mark the connection extension ok to be destroyed. + */ + if ( (aclcb->aclcb_aclsignature != acl_get_aclsignature()) || + ( (NULL == cdn) && aclcb->aclcb_sdn ) || + (cdn && (NULL == aclcb->aclcb_sdn )) || + (cdn && aclcb->aclcb_sdn && ( 0 != slapi_sdn_compare ( c_sdn, aclcb->aclcb_sdn ) ))) { + + /* cleanup the aclcb cache */ + acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /*clean*/ ); + aclcb->aclcb_state = 0; + aclcb->aclcb_aclsignature = 0; + slapi_sdn_done ( aclcb->aclcb_sdn ); + } + slapi_sdn_free ( &c_sdn ); + + /* COPY the cached information from ACLCB --> ACLPB */ + if ( aclcb->aclcb_state & ACLCB_HAS_CACHED_EVALCONTEXT) { + acl_copyEval_context ( aclpb, &aclcb->aclcb_eval_context , + &aclpb->aclpb_prev_opEval_context, 0 ); + aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT; + } + PR_Unlock ( aclcb->aclcb_lock ); + } + +} + +/* Cleans up the aclpb */ +static void +acl__done_aclpb ( struct acl_pblock *aclpb ) +{ + + int i; + int dump_aclpb_info = 0; + char *ds_attr_userdn=NULL; /* for finding userdn for freeing */ + int rc=-1; + char *tmp_ptr=NULL; + + /* + ** First, let's do some sanity checks to see if we have everything what + ** it should be. + */ + + /* Nothing needs to be cleaned up in this case */ + if ( !aclpb->aclpb_state & ACLPB_INITIALIZED) + return; + + /* Check the state */ + if (aclpb->aclpb_state & ~ACLPB_STATE_ALL) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "The aclpb.state value (%d) is incorrect. Exceeded the limit (%d)\n", + aclpb->aclpb_state, ACLPB_STATE_ALL); + dump_aclpb_info = 1; + + } + + /* acl__dump_stats ( aclpb, acl__get_aclpb_type(aclpb)); */ + + /* reset the usergroup cache */ + aclg_reset_userGroup ( aclpb ); + + if ( aclpb->aclpb_res_type & ~ACLPB_RESTYPE_ALL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "The aclpb res_type value (%d) has exceeded. Limit is (%d)\n", + aclpb->aclpb_res_type, ACLPB_RESTYPE_ALL, 0 ); + dump_aclpb_info = 1; + } + + if ( dump_aclpb_info ) { + const char *ndn; + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "ACLPB value is:%p\n", aclpb, 0,0 ); + + ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn ); + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "curr_entry:%p num_entries:%d curr_dn:%p\n", + aclpb->aclpb_curr_entry ? (char *) aclpb->aclpb_curr_entry : "NULL", + aclpb->aclpb_num_entries, + ndn ? ndn : "NULL"); + + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Last attr:%p, Plist:%p acleval: %p\n", + aclpb->aclpb_Evalattr ? aclpb->aclpb_Evalattr : "NULL", + aclpb->aclpb_proplist ? (char *) aclpb->aclpb_proplist : "NULL", + aclpb->aclpb_acleval ? (char *) aclpb->aclpb_acleval : "NULL" ); + } + + /* Now Free the contents or clean it */ + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + if (aclpb->aclpb_Evalattr) + aclpb->aclpb_Evalattr[0] = '\0'; + + /* deallocate the contents of the base array */ + for (i=0; i < aclpb->aclpb_numof_bases; i++) { + if (aclpb->aclpb_grpsearchbase[i]) + slapi_ch_free ( (void **)&aclpb->aclpb_grpsearchbase[i] ); + } + aclpb->aclpb_numof_bases = 0; + + acl_clean_aclEval_context ( &aclpb->aclpb_prev_opEval_context, 0 /*claen*/ ); + acl_clean_aclEval_context ( &aclpb->aclpb_prev_entryEval_context, 0 /*clean*/ ); + acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 0/*clean*/ ); + + if ( aclpb->aclpb_client_entry ) slapi_entry_free ( aclpb->aclpb_client_entry ); + aclpb->aclpb_client_entry = NULL; + + slapi_sdn_done ( aclpb->aclpb_authorization_sdn ); + aclpb->aclpb_pblock = NULL; + + if ( aclpb->aclpb_search_base ) + slapi_ch_free ( (void **) &aclpb->aclpb_search_base ); + for ( i=0; i < aclpb->aclpb_num_deny_handles; i++ ) + aclpb->aclpb_deny_handles[i] = NULL; + aclpb->aclpb_num_deny_handles = 0; + + for ( i=0; i < aclpb->aclpb_num_allow_handles; i++ ) + aclpb->aclpb_allow_handles[i] = NULL; + aclpb->aclpb_num_allow_handles = 0; + + /* clear results cache */ + memset((char*)aclpb->aclpb_cache_result, 0, + sizeof(struct result_cache)*aclpb->aclpb_last_cache_result); + aclpb->aclpb_last_cache_result = 0; + aclpb->aclpb_handles_index[0] = -1; + aclpb->aclpb_base_handles_index[0] = -1; + + aclpb->aclpb_stat_acllist_scanned = 0; + aclpb->aclpb_stat_aclres_matched = 0; + aclpb->aclpb_stat_total_entries = 0; + aclpb->aclpb_stat_anom_list_scanned = 0; + aclpb->aclpb_stat_num_copycontext = 0; + aclpb->aclpb_stat_num_copy_attrs = 0; + aclpb->aclpb_stat_num_tmatched_acls = 0; + + aclpb->aclpb_clientcert = NULL; + aclpb->aclpb_proxy = NULL; + + acllist_done_aciContainer ( aclpb->aclpb_aclContainer ); + + /* + * Here, decide which things need to be freed/removed/whatever from the + * aclpb_proplist. + */ + + /* + * The DS_ATTR_DNS property contains the name of the client machine. + * + * The value pointed to by this property is stored in the pblock--it + * points to the SLAPI_CLIENT_DNS object. So, that memory will + * be freed elsewhere. + * + * It's removed here from the aclpb_proplist as it would be an error to + * allow it to persist in the aclpb which is an operation time thing. + * If we leave it here the next time this aclpb gets used, the DnsGetter + * is not called by LASDnsEval/ACL_GetAttribute() as it thinks the + * ACL_ATTR_DNS has already been initialized. + * + */ + + if ((rc = PListFindValue(aclpb->aclpb_proplist, ACL_ATTR_DNS, + (void **)&tmp_ptr, NULL)) > 0) { + + PListDeleteProp(aclpb->aclpb_proplist, rc, NULL); + } + + /* + * Remove the DS_ATTR_IP property from the property list. + * The value of this property is just the property pointer + * (an unsigned long) so that gets freed too when we delete the + * property. + * It's removed here from the aclpb_proplist as it would be an error to + * allow it to persist in the aclpb which is an operation time thing. + * If we leave it here the next time this aclpb gets used, the DnsGetter + * is not called by LASIpEval/ACL_GetAttribute() as it thinks the + * ACL_ATTR_IP has already been initialized. + */ + + if ((rc = PListFindValue(aclpb->aclpb_proplist, ACL_ATTR_IP, + (void **)&tmp_ptr, NULL)) > 0) { + + PListDeleteProp(aclpb->aclpb_proplist, rc, NULL); + } + + /* + * The DS_ATTR_USERDN value comes from aclpb_authorization_sdn. + * This memory + * is freed above using aclpb_authorization_sdn so we don't need to free it here + * before overwriting the old value. + */ + PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_USERDN, NULL, 0); + + /* + * The DS_ATTR_AUTHTYPE value is a pointer into the pblock, so + * we do not need to free that memory before overwriting the value. + */ + PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_AUTHTYPE, NULL, 0); + + /* + * DO NOT overwrite the aclpb pointer--it is initialized at malloc_aclpb + * time and is kept within the aclpb. + * + * PListAssignValue(aclpb->aclpb_proplist, DS_PROP_ACLPB, NULL, 0); + */ + + /* + * The DS_ATTR_ENTRY value was a pointer to the entry being evaluated + * by the ACL code. That entry comes from outside the context of + * the acl code and so is dealt with out there. Ergo, here we can just + * lose the pointer to that entry. + */ + PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_ENTRY, NULL, 0); + + aclpb->aclpb_signature = 0; + + /* reset scoped entry cache to be empty */ + aclpb->aclpb_scoped_entry_anominfo.anom_e_nummatched = 0; + + /* Free up any of the string values left in the macro ht and remove + * the entries.*/ + acl_ht_free_all_entries_and_values(aclpb->aclpb_macro_ht); + + /* Finally, set it to the no use state */ + aclpb->aclpb_state = 0; + +} + +static char * +acl__get_aclpb_type ( Acl_PBlock *aclpb ) +{ + + if (aclpb->aclpb_state & ACLPB_TYPE_PROXY) + return ACLPB_TYPE_PROXY_STR; + + return ACLPB_TYPE_MAIN_STR; +} +static void +acl__dump_stats ( struct acl_pblock *aclpb , const char *block_type) +{ + int connid = 0; + int opid = 0; + Slapi_PBlock *pb = NULL; + + pb = aclpb->aclpb_pblock; + if ( pb ) { + slapi_pblock_get ( pb, SLAPI_CONN_ID, &connid ); + slapi_pblock_get ( pb, SLAPI_OPERATION_ID, &opid ); + } + + /* DUMP STAT INFO */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "**** ACL OPERATION STAT BEGIN ( aclpb:%p Block type: %s): Conn:%d Operation:%d *******\n", + aclpb, block_type, connid, opid ); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of entries scanned: %d\n", + aclpb->aclpb_stat_total_entries); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times ACL List scanned: %d\n", + aclpb->aclpb_stat_acllist_scanned); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of ACLs with target matched:%d\n", + aclpb->aclpb_stat_num_tmatched_acls); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times acl resource matched:%d\n", + aclpb->aclpb_stat_aclres_matched); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times ANOM list scanned:%d\n", + aclpb->aclpb_stat_anom_list_scanned); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times Context was copied:%d\n", + aclpb->aclpb_stat_num_copycontext); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times Attrs was copied:%d\n", + aclpb->aclpb_stat_num_copy_attrs); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, " **** ACL OPERATION STAT END *******\n"); +} +/****************************************************************************/ +/* E N D */ +/****************************************************************************/ + diff --git a/ldap/servers/plugins/acl/aclanom.c b/ldap/servers/plugins/acl/aclanom.c new file mode 100644 index 00000000..71b0c68a --- /dev/null +++ b/ldap/servers/plugins/acl/aclanom.c @@ -0,0 +1,536 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/************************************************************************ +Anonymous profile +**************************************************************************/ + +struct anom_targetacl { + int anom_type; /* defines for anom types same as aci_type */ + int anom_access; + Slapi_DN *anom_target; /* target of the ACL */ + Slapi_Filter *anom_filter; /* targetfilter part */ + char **anom_targetAttrs; /* list of attrs */ +}; + + +struct anom_profile { + short anom_signature; + short anom_numacls; + struct anom_targetacl anom_targetinfo[ACL_ANOM_MAX_ACL]; +}; + +static struct anom_profile *acl_anom_profile = NULL; + +static PRRWLock *anom_rwlock = NULL; +#define ANOM_LOCK_READ() PR_RWLock_Rlock (anom_rwlock ) +#define ANOM_UNLOCK_READ() PR_RWLock_Unlock (anom_rwlock ) +#define ANOM_LOCK_WRITE() PR_RWLock_Wlock (anom_rwlock ) +#define ANOM_UNLOCK_WRITE() PR_RWLock_Unlock (anom_rwlock ) + + +static void __aclanom__del_profile (); + +/* + * aclanom_init (); + * Generate a profile for the anonymous user. We can use this profile + * later to determine what resources the client is allowed to. + * + * Dependency: + * Before calling this, it is assumed that all the ACLs have been read + * and parsed. + * + * We will go thru all the ACL and pick the ANYONE ACL and generate the anom + * profile. + * + */ +int +aclanom_init () +{ + + acl_anom_profile = (struct anom_profile * ) + slapi_ch_calloc (1, sizeof ( struct anom_profile ) ); + + if (( anom_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ANOM LOCK") ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "Failed in getting the ANOM rwlock\n" ); + return 1; + } + return 0; +} + +/* + * Depending on the context, this routine may need to take the + * acicache read lock. +*/ +void +aclanom_gen_anomProfile (acl_lock_flag_t lock_flag) +{ + aci_t *aci = NULL; + int i; + Targetattr **srcattrArray; + Targetattr *attr; + struct anom_profile *a_profile; + PRUint32 cookie; + + PR_ASSERT( lock_flag == DO_TAKE_ACLCACHE_READLOCK || + lock_flag == DONT_TAKE_ACLCACHE_READLOCK); + + /* + * This routine requires two locks: + * the one for the global cache in acllist_acicache_READ_LOCK() and + * the one for the anom profile. + * They _must_ be taken in the order presented here or there + * is a deadlock scenario with acllist_remove_aci_needsLock() which + * takes them is this order. + */ + + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_LOCK(); + } + ANOM_LOCK_WRITE (); + a_profile = acl_anom_profile; + + if ( (!acl_get_aclsignature()) || ( !a_profile) || + (a_profile->anom_signature == acl_get_aclsignature()) ) { + ANOM_UNLOCK_WRITE (); + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_UNLOCK(); + } + return; + } + + /* D0 we have one already. If we do, then clean it up */ + __aclanom__del_profile(); + + /* We have a new signature now */ + a_profile->anom_signature = acl_get_aclsignature(); + + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "GENERATING ANOM USER PROFILE\n", 0,0,0); + /* + ** Go thru the ACL list and find all the ACLs which apply to the + ** anonymous user i.e anyone. we can generate a profile for that. + ** We will llok at the simple case i.e it matches + ** cases not handled: + ** 1) When there is a mix if rule types ( allows & denies ) + ** + */ + + aci = acllist_get_first_aci ( NULL, &cookie ); + while ( aci ) { + int a_numacl; + struct slapi_filter *f; + char **destattrArray; + + + /* + * We must not have a rule like: deny ( all ) userdn != "xyz" + * or groupdn != + */ + if ( (aci->aci_type & ACI_HAS_DENY_RULE) && + ( (aci->aci_type & ACI_CONTAIN_NOT_USERDN ) || + (aci->aci_type & ACI_CONTAIN_NOT_GROUPDN) || + (aci->aci_type & ACI_CONTAIN_NOT_ROLEDN)) ){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE BECAUSE OF DENY RULE\n", 0,0,0); + goto cleanup; + } + + /* Must be a anyone rule */ + if ( aci->aci_elevel != ACI_ELEVEL_USERDN_ANYONE ) { + aci = acllist_get_next_aci ( NULL, aci, &cookie); + continue; + } + if (! (aci->aci_access & ( SLAPI_ACL_READ | SLAPI_ACL_SEARCH)) ) { + aci = acllist_get_next_aci ( NULL, aci, &cookie); + continue; + } + /* If the rule has anything other than userdn = "ldap:///anyone" + ** let's not consider complex rules - let's make this lean. + */ + if ( aci->aci_ruleType & ~ACI_USERDN_RULE ){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE BECAUSE OF COMPLEX RULE\n", 0,0,0); + goto cleanup; + } + + /* Must not be a or have a + ** 1 ) DENY RULE 2) targetfilter + ** 3) no target pattern ( skip monitor acl ) + */ + if ( aci->aci_type & ( ACI_HAS_DENY_RULE | ACI_TARGET_PATTERN | + ACI_TARGET_NOT | ACI_TARGET_FILTER_NOT )) { + const char *dn = slapi_sdn_get_dn ( aci->aci_sdn ); + + /* see if this is a monitor acl */ + if (( strcasecmp ( dn, "cn=monitor") == 0 ) || + ( strcasecmp ( dn, "cn=monitor,cn=ldbm") == 0 )) { + aci = acllist_get_next_aci ( NULL, aci, &cookie); + continue; + } else { + /* clean up before leaving */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE 1\n", 0,0,0); + goto cleanup; + } + + } + + /* Now we have an ALLOW ACL which applies to anyone */ + a_numacl = a_profile->anom_numacls++; + + if ( a_profile->anom_numacls == ACL_ANOM_MAX_ACL ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "CANCELLING ANOM USER PROFILE 2\n", 0,0,0); + goto cleanup; + } + + if ( (f = aci->target) != NULL ) { + char *avaType; + struct berval *avaValue; + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + a_profile->anom_targetinfo[a_numacl].anom_target = + slapi_sdn_new_dn_byval ( avaValue->bv_val ); + } else { + a_profile->anom_targetinfo[a_numacl].anom_target = + slapi_sdn_dup ( aci->aci_sdn ); + } + + a_profile->anom_targetinfo[a_numacl].anom_filter = NULL; + if ( aci->targetFilterStr ) + a_profile->anom_targetinfo[a_numacl].anom_filter = slapi_str2filter ( aci->targetFilterStr ); + + i = 0; + srcattrArray = aci->targetAttr; + while ( srcattrArray[i]) + i++; + + a_profile->anom_targetinfo[a_numacl].anom_targetAttrs = + (char **) slapi_ch_calloc ( 1, (i+1) * sizeof(char *)); + + srcattrArray = aci->targetAttr; + destattrArray = a_profile->anom_targetinfo[a_numacl].anom_targetAttrs; + + i = 0; + while ( srcattrArray[i] ) { + attr = srcattrArray[i]; + if ( attr->attr_type & ACL_ATTR_FILTER ) { + /* Do'nt want to support these kind now */ + destattrArray[i] = NULL; + /* clean up before leaving */ + __aclanom__del_profile (); + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE 3\n", 0,0,0); + goto cleanup; + } + + destattrArray[i] = slapi_ch_strdup ( attr->u.attr_str ); + i++; + } + + destattrArray[i] = NULL; + + aclutil_print_aci ( aci, "anom" ); + /* Here we are storing att the info from the acls. However + ** we are only interested in a few things like ACI_TARGETATTR_NOT. + */ + a_profile->anom_targetinfo[a_numacl].anom_type = aci->aci_type; + a_profile->anom_targetinfo[a_numacl].anom_access = aci->aci_access; + + aci = acllist_get_next_aci ( NULL, aci, &cookie); + } + + ANOM_UNLOCK_WRITE (); + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_UNLOCK(); + } + return; + +cleanup: + __aclanom__del_profile (); + ANOM_UNLOCK_WRITE (); + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_UNLOCK(); + } +} + + +void +aclanom_invalidateProfile () +{ + ANOM_LOCK_WRITE(); + if ( acl_anom_profile && acl_anom_profile->anom_numacls ) + acl_anom_profile->anom_signature = 0; + ANOM_UNLOCK_WRITE(); + + +} + +/* + * __aclanom_del_profile + * + * Cleanup the anonymous user's profile we have. + * + * ASSUMPTION: A WRITE LOCK HAS BEEN OBTAINED + * + */ +static void +__aclanom__del_profile (void) +{ + int i; + struct anom_profile *a_profile; + + + if ( (a_profile = acl_anom_profile) == NULL ) { + return; + } + + for ( i=0; i < a_profile->anom_numacls; i++ ) { + int j = 0; + char **destArray = a_profile->anom_targetinfo[i].anom_targetAttrs; + + /* Deallocate target */ + slapi_sdn_free ( &a_profile->anom_targetinfo[i].anom_target ); + + /* Deallocate filter */ + if ( a_profile->anom_targetinfo[i].anom_filter ) + slapi_filter_free ( a_profile->anom_targetinfo[i].anom_filter, 1 ); + + /* Deallocate attrs */ + if ( destArray ) { + while ( destArray[j] ) { + slapi_ch_free ( (void **) &destArray[j] ); + j++; + } + slapi_ch_free ( (void **) &destArray ); + } + a_profile->anom_targetinfo[i].anom_targetAttrs = NULL; + a_profile->anom_targetinfo[i].anom_type = 0; + a_profile->anom_targetinfo[i].anom_access = 0; + } + a_profile->anom_numacls = 0; + + /* Don't clean the signatue */ +} + +/* + * This routine sets up a "context" for evaluation of access control + * on a given entry for an anonymous user. + * It just factors out the scope and targetfilter info into a list + * of indices of the global anom profile list, that apply to this + * entry, and stores them in the aclpb. + * It's use relies on the way that access control is checked in the mailine search + * code in the core server, namely: check filter, check entry, then check each + * attribute. So, we call this in acl_access_allowed() before calling + * aclanom_match_profile()--therafter, aclanom_match_profile() uses the + * context to evaluate access to the entry and attributes. + * + * If there are no anom profiles, or the anom profiles get cancelled + * due to complex anon acis, then that's OK, aclanom_match_profile() + * returns -1 and the mainline acl code kicks in. + * + * The lifetime of this context info is the time it takes to check + * access control for all parts of this entry (filter, entry, attributes). + * So, if for an example an entry changes and a given anom profile entry + * no longer applies, we will not notice until the next round of access + * control checking on the entry--this is acceptable. + * + * The gain on doing this factoring in the following type of search + * was approx 6%: + * anon bind, 20 threads, exact match, ~20 attributes returned, + * (searchrate & DirectoryMark). + * +*/ +void +aclanom_get_suffix_info(Slapi_Entry *e, + struct acl_pblock *aclpb ) { + int i; + char *ndn = NULL; + Slapi_DN *e_sdn; + const char *aci_ndn; + int populate = 0; + struct scoped_entry_anominfo *s_e_anominfo = + &aclpb->aclpb_scoped_entry_anominfo; + + ANOM_LOCK_READ (); + + s_e_anominfo->anom_e_nummatched=0; + + ndn = slapi_entry_get_ndn ( e ) ; + e_sdn= slapi_entry_get_sdn ( e ) ; + for (i=acl_anom_profile->anom_numacls-1; i >= 0; i-- ) { + aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target); + if (!slapi_sdn_issuffix(e_sdn,acl_anom_profile->anom_targetinfo[i].anom_target) + || (!slapi_is_rootdse(ndn) && slapi_is_rootdse(aci_ndn))) + continue; + if ( acl_anom_profile->anom_targetinfo[i].anom_filter ) { + if ( slapi_vattr_filter_test( aclpb->aclpb_pblock, e, + acl_anom_profile->anom_targetinfo[i].anom_filter, + 0 /*don't do acess chk*/) != 0) + continue; + } + s_e_anominfo->anom_e_targetInfo[s_e_anominfo->anom_e_nummatched]=i; + s_e_anominfo->anom_e_nummatched++; + } + ANOM_UNLOCK_READ (); +} + + +/* + * aclanom_match_profile + * Look at the anonymous profile and see if we can use it or not. + * + * + * Inputs: + * Slapi_Pblock - The Pblock + * Slapi_Entry *e - The entry for which we are asking permission. + * char *attr - Attribute name + * int access - access type + * + * Return: + * LDAP_SUCCESS ( 0 ) - acess is allowed. + * LDAP_INSUFFICIENT_ACCESS (50 ) - access denied. + * -1 - didn't match the targets + * + * Assumptions: + * The caller of this module has to make sure that the client is + * an anonymous client. + */ +int +aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb, Slapi_Entry *e, + char *attr, int access ) +{ + + struct anom_profile *a_profile; + int result, i, k; + char **destArray; + int tmatched = 0; + char ebuf[ BUFSIZ ]; + int loglevel; + struct scoped_entry_anominfo *s_e_anominfo = + &aclpb->aclpb_scoped_entry_anominfo; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + /* WE are only interested for READ/SEARCH */ + if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) ) + return -1; + + /* If we are here means, the client is doing a anonymous read/search */ + if ((a_profile = acl_anom_profile) == NULL ) { + return -1; + } + + ANOM_LOCK_READ (); + /* Check the signature first */ + if ( a_profile->anom_signature != acl_get_aclsignature () ) { + /* Need to regenrate the signature. + * Need a WRITE lock to generate the anom profile - + * which is obtained in acl__gen_anom_user_profile (). Since + * I don't have upgrade lock -- I have to do this way. + */ + ANOM_UNLOCK_READ (); + aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK); + aclanom_get_suffix_info(e, aclpb ); + ANOM_LOCK_READ (); + } + + /* doing this early saves use a malloc/free/normalize cost */ + if ( !a_profile->anom_numacls ) { + ANOM_UNLOCK_READ (); + return -1; + } + + result = LDAP_INSUFFICIENT_ACCESS; + + for ( k=0; k<s_e_anominfo->anom_e_nummatched; k++ ) { + short matched = 0; + short j = 0; + + i = s_e_anominfo->anom_e_targetInfo[k]; + + /* Check for right */ + if ( !(a_profile->anom_targetinfo[i].anom_access & access) ) + continue; + + /* + * XXX rbyrne Don't really understand the role of this + * but not causing any obvious bugs...get back to it. + */ + tmatched++; + + if ( attr == NULL ) { + result = LDAP_SUCCESS; + break; + } + + destArray = a_profile->anom_targetinfo[i].anom_targetAttrs; + while ( destArray[j] ) { + if ( strcasecmp ( destArray[j], "*") == 0 || + slapi_attr_type_cmp ( attr, destArray[j], 1 ) == 0 ) { + matched = 1; + break; + } + j++; + } + + if ( a_profile->anom_targetinfo[i].anom_type & ACI_TARGET_ATTR_NOT ) + result = matched ? LDAP_INSUFFICIENT_ACCESS : LDAP_SUCCESS; + else + result = matched ? LDAP_SUCCESS : LDAP_INSUFFICIENT_ACCESS; + + if ( result == LDAP_SUCCESS ) + break; + } /* for */ + + if ( slapi_is_loglevel_set(loglevel) ) { + char *ndn = NULL; + Slapi_Operation *op = NULL; + + ndn = slapi_entry_get_ndn ( e ) ; + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + if ( result == LDAP_SUCCESS) { + const char *aci_ndn; + aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target); + + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d: Allow access on entry(%s).attr(%s) to anonymous: acidn=\"%s\"\n", + op->o_connid, op->o_opid, + escape_string_with_punctuation(ndn, ebuf), + attr ? attr:"NULL", + escape_string_with_punctuation(aci_ndn, ebuf)); + } else { + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d: Deny access on entry(%s).attr(%s) to anonymous\n", + op->o_connid, op->o_opid, + escape_string_with_punctuation(ndn, ebuf), attr ? attr:"NULL" ); + } + } + + ANOM_UNLOCK_READ (); + if ( tmatched == 0) + return -1; + else + return result; + +} +int +aclanom_is_client_anonymous ( Slapi_PBlock *pb ) +{ + char *clientDn; + + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + if (acl_anom_profile->anom_numacls && + acl_anom_profile->anom_signature && + (( NULL == clientDn) || (clientDn && *clientDn == '\0')) ) + return 1; + + return 0; +} + diff --git a/ldap/servers/plugins/acl/acldllmain.c b/ldap/servers/plugins/acl/acldllmain.c new file mode 100644 index 00000000..21ab60c4 --- /dev/null +++ b/ldap/servers/plugins/acl/acldllmain.c @@ -0,0 +1,128 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif + +#ifdef LDAP_DEBUG +#ifndef _WIN32 +#include <stdarg.h> +#include <stdio.h> + +void LDAPDebug( int level, char* fmt, ... ) +{ + static char debugBuf[1024]; + + if (module_ldap_debug && (*module_ldap_debug & level)) + { + va_list ap; + va_start (ap, fmt); + _snprintf (debugBuf, sizeof(debugBuf), fmt, ap); + va_end (ap); + + OutputDebugString (debugBuf); + } +} +#endif +#endif + +#ifndef _WIN32 + +/* The 16-bit version of the RTL does not implement perror() */ + +#include <stdio.h> + +void perror( const char *msg ) +{ + char buf[128]; + wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ; + OutputDebugString( buf ); +} + +#endif diff --git a/ldap/servers/plugins/acl/acleffectiverights.c b/ldap/servers/plugins/acl/acleffectiverights.c new file mode 100644 index 00000000..1e250e96 --- /dev/null +++ b/ldap/servers/plugins/acl/acleffectiverights.c @@ -0,0 +1,674 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2004 Netscape Communications Corporation + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "acl.h" + +static int +_ger_g_permission_granted ( Slapi_PBlock *pb, Slapi_Entry *e, char **errbuf ) +{ + char *proxydn = NULL; + Slapi_DN *requestor_sdn, *entry_sdn; + char *errtext = NULL; + int isroot; + int rc; + + /* + * Theorically, we should check if the entry has "g" + * permission granted to the requestor. If granted, + * allows the effective rights on that entry and its + * attributes within the entry to be returned for + * ANY subject. + * + * "G" permission granting has not been implemented yet, + * the current release assumes that "g" permission be + * granted to root and owner of any entry. + */ + + /* + * The requestor may be either the bind dn or a proxy dn + */ + acl_get_proxyauth_dn ( pb, &proxydn, &errtext ); + if ( proxydn != NULL ) + { + requestor_sdn = slapi_sdn_new_dn_passin ( proxydn ); + } + else + { + requestor_sdn = &(pb->pb_op->o_sdn); + } + if ( slapi_sdn_get_dn (requestor_sdn) == NULL ) + { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_g_permission_granted: anonymous has no g permission\n" ); + rc = LDAP_INSUFFICIENT_ACCESS; + goto bailout; + } + isroot = slapi_dn_isroot ( slapi_sdn_get_dn (requestor_sdn) ); + if ( isroot ) + { + /* Root has "g" permission on any entry */ + rc = LDAP_SUCCESS; + goto bailout; + } + + entry_sdn = slapi_entry_get_sdn ( e ); + if ( entry_sdn == NULL || slapi_sdn_get_dn (entry_sdn) == NULL ) + { + rc = LDAP_SUCCESS; + goto bailout; + } + + if ( slapi_sdn_compare ( requestor_sdn, entry_sdn ) == 0 ) + { + /* Owner has "g" permission on his own entry */ + rc = LDAP_SUCCESS; + goto bailout; + } + + aclutil_str_appened ( errbuf, "get-effective-rights: requestor has no g permission on the entry" ); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_g_permission_granted: %s\n", *errbuf); + rc = LDAP_INSUFFICIENT_ACCESS; + +bailout: + if ( proxydn ) + { + /* The ownership of proxydn has passed to requestor_sdn */ + slapi_sdn_free ( &requestor_sdn ); + } + return rc; +} + +static int +_ger_parse_control ( Slapi_PBlock *pb, char **subjectndn, int *iscritical, char **errbuf ) +{ + LDAPControl **requestcontrols; + struct berval *subjectber; + BerElement *ber; + + if (NULL == subjectndn) + { + return LDAP_OPERATIONS_ERROR; + } + + *subjectndn = NULL; + + /* + * Get the control + */ + slapi_pblock_get ( pb, SLAPI_REQCONTROLS, (void *) &requestcontrols ); + slapi_control_present ( requestcontrols, + LDAP_CONTROL_GET_EFFECTIVE_RIGHTS, + &subjectber, + iscritical ); + if ( subjectber == NULL || subjectber->bv_val == NULL || + subjectber->bv_len == 0 ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: missing subject" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + return LDAP_INVALID_SYNTAX; + } + + if ( strncasecmp ( "dn:", subjectber->bv_val, 3 ) == 0 ) + { + /* + * This is a non-standard support to allow the subject being a plain + * or base64 encoding string. Hence users using -J option in + * ldapsearch don't have to do BER encoding for the subject. + */ + *subjectndn = slapi_ch_malloc ( subjectber->bv_len + 1 ); + strncpy ( *subjectndn, subjectber->bv_val, subjectber->bv_len ); + *(*subjectndn + subjectber->bv_len) = '\0'; + } + else + { + ber = ber_init (subjectber); + if ( ber == NULL ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: ber_init failed for the subject" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + return LDAP_OPERATIONS_ERROR; + } + /* "a" means to allocate storage as needed for octet string */ + if ( ber_scanf (ber, "a", subjectndn) == LBER_ERROR ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: invalid ber tag in the subject" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + ber_free ( ber, 1 ); + return LDAP_INVALID_SYNTAX; + } + ber_free ( ber, 1 ); + } + + /* + * The current implementation limits the subject to authorization ID + * (see section 9 of RFC 2829) only. It also only supports the "dnAuthzId" + * flavor, which looks like "dn:<DN>" where null <DN> is for anonymous. + */ + if ( NULL == *subjectndn || strlen (*subjectndn) < 3 || + strncasecmp ( "dn:", *subjectndn, 3 ) != 0 ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: subject is not dnAuthzId" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + return LDAP_INVALID_SYNTAX; + } + + strcpy ( *subjectndn, *subjectndn + 3 ); + slapi_dn_normalize ( *subjectndn ); + return LDAP_SUCCESS; +} + +static void +_ger_release_gerpb ( + Slapi_PBlock **gerpb, + void **aclcb, /* original aclcb */ + Slapi_PBlock *pb /* original pb */ + ) +{ + if ( *gerpb ) + { + /* Return conn to pb */ + slapi_pblock_set ( *gerpb, SLAPI_CONNECTION, NULL ); + slapi_pblock_destroy ( *gerpb ); + *gerpb = NULL; + } + + /* Put the original aclcb back to pb */ + if ( *aclcb ) + { + Connection *conn = NULL; + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn ); + if (conn) + { + struct aclcb *geraclcb; + geraclcb = (struct aclcb *) acl_get_ext ( ACL_EXT_CONNECTION, conn ); + acl_conn_ext_destructor ( geraclcb, NULL, NULL ); + acl_set_ext ( ACL_EXT_CONNECTION, conn, *aclcb ); + *aclcb = NULL; + } + } +} + +static int +_ger_new_gerpb ( + Slapi_PBlock *pb, + Slapi_Entry *e, + const char *subjectndn, + Slapi_PBlock **gerpb, + void **aclcb, /* original aclcb */ + char **errbuf + ) +{ + Connection *conn; + struct acl_cblock *geraclcb; + Acl_PBlock *aclpb, *geraclpb; + Operation *op, *gerop; + int rc = LDAP_SUCCESS; + + *aclcb = NULL; + *gerpb = slapi_pblock_new (); + if ( *gerpb == NULL ) + { + rc = LDAP_NO_MEMORY; + goto bailout; + } + + { + /* aclpb initialization needs the backend */ + Slapi_Backend *be; + slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + slapi_pblock_set ( *gerpb, SLAPI_BACKEND, be ); + } + + { + int isroot = slapi_dn_isroot ( subjectndn ); + slapi_pblock_set ( *gerpb, SLAPI_REQUESTOR_ISROOT, &isroot ); + } + + /* Save requestor's aclcb and set subjectdn's one */ + { + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn ); + slapi_pblock_set ( *gerpb, SLAPI_CONNECTION, conn ); + + /* Can't share the conn->aclcb because of different context */ + geraclcb = (struct acl_cblock *) acl_conn_ext_constructor ( NULL, NULL); + if ( geraclcb == NULL ) + { + rc = LDAP_NO_MEMORY; + goto bailout; + } + slapi_sdn_set_ndn_byval ( geraclcb->aclcb_sdn, subjectndn ); + *aclcb = acl_get_ext ( ACL_EXT_CONNECTION, conn ); + acl_set_ext ( ACL_EXT_CONNECTION, conn, (void *) geraclcb ); + } + + { + gerop = operation_new ( OP_FLAG_INTERNAL ); + if ( gerop == NULL ) + { + rc = LDAP_NO_MEMORY; + goto bailout; + } + /* + * conn is a no-use parameter in the functions + * chained down from factory_create_extension + */ + gerop->o_extension = factory_create_extension ( get_operation_object_type(), (void *)gerop, (void *)conn ); + slapi_pblock_set ( *gerpb, SLAPI_OPERATION, gerop ); + slapi_sdn_set_dn_byval ( &gerop->o_sdn, subjectndn ); + geraclpb = acl_get_ext ( ACL_EXT_OPERATION, (void *)gerop); + acl_init_aclpb ( *gerpb, geraclpb, subjectndn, 0 ); + geraclpb->aclpb_res_type |= ACLPB_EFFECTIVE_RIGHTS; + } + + +bailout: + if ( rc != LDAP_SUCCESS ) + { + _ger_release_gerpb ( gerpb, aclcb, pb ); + } + + return rc; +} + +/* + * Callers should have already allocated *gerstr to hold at least + * "entryLevelRights: adnvxxx\n". + */ +unsigned long +_ger_get_entry_rights ( + Slapi_PBlock *gerpb, + Slapi_Entry *e, + const char *subjectndn, + char *gerstr, + char **errbuf + ) +{ + unsigned long entryrights = 0; + Slapi_RDN *rdn = NULL; + const char *rdnstr = NULL; + char *equalsign = NULL; + char *rdntype = NULL; + + strcpy ( gerstr, "entryLevelRights: " ); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_READ\n" ); + if (acl_access_allowed(gerpb, e, "*", NULL, SLAPI_ACL_READ) == LDAP_SUCCESS) + { + /* v - view e */ + entryrights |= SLAPI_ACL_READ; + strcat (gerstr, "v"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_ADD\n" ); + if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_ADD) == LDAP_SUCCESS) + { + /* a - add child entry below e */ + entryrights |= SLAPI_ACL_ADD; + strcat (gerstr, "a"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_DELETE\n" ); + if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_DELETE) == LDAP_SUCCESS) + { + /* d - delete e */ + entryrights |= SLAPI_ACL_DELETE; + strcat (gerstr, "d"); + } + /* + * Some limitation/simplification applied here: + * - The modrdn right requires the rights to delete the old rdn and + * the new one. However we have no knowledge of what the new rdn + * is going to be. + * - In multi-valued RDN case, we check the right on + * the first rdn type only for now. + */ + rdn = slapi_rdn_new_dn ( slapi_entry_get_ndn (e) ); + rdnstr = slapi_rdn_get_rdn ( rdn ); + if ( NULL != (equalsign = strchr ( rdnstr, '=' )) ) + { + rdntype = slapi_ch_malloc ( equalsign-rdnstr+1 ); + strncpy ( rdntype, rdnstr, equalsign-rdnstr ); + rdntype [ equalsign-rdnstr ] = '\0'; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_WRITE_DEL & _ADD %s\n", rdntype ); + if (acl_access_allowed(gerpb, e, rdntype, NULL, + ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS && + acl_access_allowed(gerpb, e, rdntype, NULL, + ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) + { + /* n - rename e */ + entryrights |= SLAPI_ACL_WRITE; + strcat (gerstr, "n"); + } + slapi_ch_free ( (void**) &rdntype ); + } + slapi_rdn_free ( &rdn ); + +done: + if ( entryrights == 0 ) + { + strcat (gerstr, "none"); + } + + strcat (gerstr, "\n"); + + return entryrights; +} + +/* + * *gerstr should point to a heap buffer since it may need + * to expand dynamically. + */ +unsigned long +_ger_get_attr_rights ( + Slapi_PBlock *gerpb, + Slapi_Entry *e, + const char *subjectndn, + char *type, + char **gerstr, + int *gerstrsize, + int isfirstattr, + char **errbuf + ) +{ + unsigned long attrrights = 0; + + /* Enough space for " $type:rwoscxx" ? */ + if ( (*gerstrsize - strlen(*gerstr)) < (strlen(type) + 16) ) + { + /* slapi_ch_realloc() exits if realloc() failed */ + *gerstrsize += 256; + *gerstr = slapi_ch_realloc ( *gerstr, *gerstrsize ); + } + if (!isfirstattr) + { + strcat ( *gerstr, ", " ); + } + sprintf ( *gerstr + strlen(*gerstr), "%s:", type ); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_READ %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_READ) == LDAP_SUCCESS) + { + /* r - read the values of type */ + attrrights |= SLAPI_ACL_READ; + strcat (*gerstr, "r"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_SEARCH %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_SEARCH) == LDAP_SUCCESS) + { + /* s - search the values of type */ + attrrights |= SLAPI_ACL_SEARCH; + strcat (*gerstr, "s"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_COMPARE %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_COMPARE) == LDAP_SUCCESS) + { + /* c - compare the values of type */ + attrrights |= SLAPI_ACL_COMPARE; + strcat (*gerstr, "c"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_WRITE_ADD %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) + { + /* w - add the values of type */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD; + strcat (*gerstr, "w"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_WRITE_DEL %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS) + { + /* o - delete the values of type */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL; + strcat (*gerstr, "o"); + } + /* If subjectdn has no general write right, check for self write */ + if ( 0 == (attrrights & (ACLPB_SLAPI_ACL_WRITE_DEL | ACLPB_SLAPI_ACL_WRITE_ADD)) ) + { + struct berval val; + + val.bv_val = (char *)subjectndn; + val.bv_len = strlen (subjectndn); + + if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) + { + /* W - add self to the attribute */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD; + strcat (*gerstr, "W"); + } + if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS) + { + /* O - delete self from the attribute */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL; + strcat (*gerstr, "O"); + } + } + + if ( attrrights == 0 ) + { + strcat (*gerstr, "none"); + } + + return attrrights; +} + +void +_ger_get_attrs_rights ( + Slapi_PBlock *gerpb, + Slapi_Entry *e, + const char *subjectndn, + char **attrs, + char **gerstr, + int *gerstrsize, + char **errbuf + ) +{ + int isfirstattr = 1; + + /* gerstr was initially allocated with enough space for one more line */ + strcat ( *gerstr, "attributeLevelRights: " ); + + if (attrs && *attrs) + { + int i; + for ( i = 0; attrs[i]; i++ ) + { + _ger_get_attr_rights ( gerpb, e, subjectndn, attrs[i], gerstr, gerstrsize, isfirstattr, errbuf ); + isfirstattr = 0; + } + } + else + { + Slapi_Attr *prevattr = NULL, *attr; + char *type; + + while ( slapi_entry_next_attr ( e, prevattr, &attr ) == 0 ) + { + if ( ! slapi_attr_flag_is_set (attr, SLAPI_ATTR_FLAG_OPATTR) ) + { + slapi_attr_get_type ( attr, &type ); + _ger_get_attr_rights ( gerpb, e, subjectndn, type, gerstr, gerstrsize, isfirstattr, errbuf ); + isfirstattr = 0; + } + prevattr = attr; + } + } + + if ( isfirstattr ) + { + /* not a single attribute was retrived or specified */ + strcat ( *gerstr, "*:none" ); + } + return; +} + +/* + * controlType = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS; + * criticality = n/a; + * controlValue = OCTET STRING of BER encoding of the SEQUENCE of + * ENUMERATED LDAP code + */ +void +_ger_set_response_control ( + Slapi_PBlock *pb, + int iscritical, + int rc + ) +{ + LDAPControl **resultctrls = NULL; + LDAPControl gerrespctrl; + BerElement *ber = NULL; + struct berval *berval = NULL; + int found = 0; + int i; + + if ( (ber = der_alloc ()) == NULL ) + { + goto bailout; + } + + /* begin sequence, enumeration, end sequence */ + ber_printf ( ber, "{e}", rc ); + if ( ber_flatten ( ber, &berval ) != LDAP_SUCCESS ) + { + goto bailout; + } + gerrespctrl.ldctl_oid = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS; + gerrespctrl.ldctl_iscritical = iscritical; + gerrespctrl.ldctl_value.bv_val = berval->bv_val; + gerrespctrl.ldctl_value.bv_len = berval->bv_len; + + slapi_pblock_get ( pb, SLAPI_RESCONTROLS, &resultctrls ); + for (i = 0; resultctrls && resultctrls[i]; i++) + { + if (strcmp(resultctrls[i]->ldctl_oid, LDAP_CONTROL_GET_EFFECTIVE_RIGHTS) == 0) + { + /* + * We get here if search returns more than one entry + * and this is not the first entry. + */ + ldap_control_free ( resultctrls[i] ); + resultctrls[i] = slapi_dup_control (&gerrespctrl); + found = 1; + break; + } + } + + if ( !found ) + { + /* slapi_pblock_set() will dup the control */ + slapi_pblock_set ( pb, SLAPI_ADD_RESCONTROL, &gerrespctrl ); + } + +bailout: + ber_free ( ber, 1 ); /* ber_free() checks for NULL param */ + ber_bvfree ( berval ); /* ber_bvfree() checks for NULL param */ +} + +int +acl_get_effective_rights ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* target entry */ + char **attrs, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access, /* requested access rights */ + char **errbuf + ) +{ + Slapi_PBlock *gerpb = NULL; + void *aclcb = NULL; + char *subjectndn = NULL; + char *gerstr = NULL; + int gerstrsize = 1024; + unsigned long entryrights; + int iscritical = 1; + int rc; + + *errbuf = '\0'; + gerstr = slapi_ch_malloc ( gerstrsize ); + + /* + * Get the subject + */ + rc = _ger_parse_control (pb, &subjectndn, &iscritical, errbuf ); + if ( rc != LDAP_SUCCESS ) + { + goto bailout; + } + + /* + * The requestor should have g permission on the entry + * to get the effective rights. + */ + rc = _ger_g_permission_granted (pb, e, errbuf); + if ( rc != LDAP_SUCCESS ) + { + goto bailout; + } + + /* + * Construct a new pb + */ + rc = _ger_new_gerpb ( pb, e, subjectndn, &gerpb, &aclcb, errbuf ); + if ( rc != LDAP_SUCCESS ) + { + goto bailout; + } + + /* Get entry level effective rights */ + entryrights = _ger_get_entry_rights ( gerpb, e, subjectndn, gerstr, errbuf ); + + /* + * Attribute level effective rights may not be NULL + * even if entry level's is. + */ + _ger_get_attrs_rights ( gerpb, e, subjectndn, attrs, &gerstr, &gerstrsize, errbuf ); + +bailout: + /* + * Now construct the response control + */ + _ger_set_response_control ( pb, iscritical, rc ); + + if ( rc != LDAP_SUCCESS ) + { + sprintf ( gerstr, "entryLevelRights: %d\nattributeLevelRights: *:%d", rc, rc ); + } + + slapi_log_error (SLAPI_LOG_ACLSUMMARY, plugin_name, + "###### Effective Rights on Entry (%s) for Subject (%s) ######\n", + slapi_entry_get_ndn (e), subjectndn); + slapi_log_error (SLAPI_LOG_ACLSUMMARY, plugin_name, "%s\n", gerstr); + + /* Restore pb */ + _ger_release_gerpb ( &gerpb, &aclcb, pb ); + + /* + * General plugin uses SLAPI_RESULT_TEXT for error text. Here + * SLAPI_PB_RESULT_TEXT is exclusively shared with add, dse and schema. + * slapi_pblock_set() will free any previous data, and + * pblock_done() will free SLAPI_PB_RESULT_TEXT. + */ + slapi_pblock_set (pb, SLAPI_PB_RESULT_TEXT, gerstr); + + if ( !iscritical ) + { + /* + * If return code is not LDAP_SUCCESS, the server would + * abort sending the data of the entry to the client. + */ + rc = LDAP_SUCCESS; + } + + slapi_ch_free ( (void **) &subjectndn ); + slapi_ch_free ( (void **) &gerstr ); + return rc; +} diff --git a/ldap/servers/plugins/acl/aclgroup.c b/ldap/servers/plugins/acl/aclgroup.c new file mode 100644 index 00000000..4cd7039f --- /dev/null +++ b/ldap/servers/plugins/acl/aclgroup.c @@ -0,0 +1,442 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/*************************************************************************** + * + * This module deals with the global user group cache. + * + * A LRU queue mechanism is used to maintain the groups the user currently in. + * At this moment the QUEUE is invalidated if there is a group change. A better + * way would have been to invalidate only the one which are effected. + * However to accomplish that will require quite a bit of work which may not be + * cost-efftive. + **************************************************************************/ +static aclGroupCache *aclUserGroups; +#define ACL_MAXCACHE_USERGROUPS 200 + +#define ACLG_LOCK_GROUPCACHE_READ() PR_RWLock_Rlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_LOCK_GROUPCACHE_WRITE() PR_RWLock_Wlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_ULOCK_GROUPCACHE_WRITE() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_ULOCK_GROUPCACHE_READ() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock ) + + +static void __aclg__delete_userGroup ( aclUserGroup *u_group ); + + +int +aclgroup_init () +{ + + aclUserGroups = ( aclGroupCache * ) slapi_ch_calloc (1, sizeof ( aclGroupCache ) ); + if ( NULL == (aclUserGroups->aclg_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"Group LOCK"))) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Unable to allocate RWLOCK for group cache\n"); + return 1; + } + return 0; +} + +/* + * aclg_init_userGroup + * + * Go thru the Global Group CACHE and see if we have group information for + * the user. The user's group cache is invalidated when a group is modified + * (in which case ALL usergroups are invalidated) or when the user's entry + * is modified in which case just his is invalidated. + * + * We need to scan the whole cache looking for a valid entry that matches + * this user. If we find invalid entries along the way. + * + * If we don't have anything it's fine. we will allocate a space when we + * need it i.e during the group evaluation. + * + * Inputs: + * struct acl_pblock - ACL private block + * char *dn - the client's dn + * int got_lock - 1: already obtained WRITE Lock + * - 0: Nope; get one + * Returns: + * None. + */ + +void +aclg_init_userGroup ( struct acl_pblock *aclpb, const char *n_dn , int got_lock ) +{ + aclUserGroup *u_group = NULL; + aclUserGroup *next_ugroup = NULL; + aclUserGroup *p_group, *n_group; + int found = 0; + + /* Check for Anonymous user */ + if ( n_dn && *n_dn == '\0') return; + + if ( !got_lock ) ACLG_LOCK_GROUPCACHE_WRITE (); + u_group = aclUserGroups->aclg_first; + aclpb->aclpb_groupinfo = NULL; + + while ( u_group != NULL ) { + next_ugroup = u_group->aclug_next; + if ( aclUserGroups->aclg_signature != u_group->aclug_signature) { + /* + * This means that this usergroup is no longer valid and + * this operation so delete this one if no one is using it. + */ + + if ( !u_group->aclug_refcnt ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "In traversal group deallocation\n", 0,0,0 ); + __aclg__delete_userGroup (u_group); + } + } else { + + /* + * Here, u_group is valid--if it matches then take it. + */ + if ( slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn, + (ACLUCHP)n_dn ) == 0 ) { + u_group->aclug_refcnt++; + aclpb->aclpb_groupinfo = u_group; + found = 1; + break; + } + } + u_group = next_ugroup; + } + + /* Move the new one to the top of the queue */ + if ( found ) { + p_group = u_group->aclug_prev; + n_group = u_group->aclug_next; + + if ( p_group ) { + aclUserGroup *t_group = NULL; + + p_group->aclug_next = n_group; + if ( n_group ) n_group->aclug_prev = p_group; + + t_group = aclUserGroups->aclg_first; + if ( t_group ) t_group->aclug_prev = u_group; + + u_group->aclug_prev = NULL; + u_group->aclug_next = t_group; + aclUserGroups->aclg_first = u_group; + + if ( u_group == aclUserGroups->aclg_last ) + aclUserGroups->aclg_last = p_group; + } + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "acl_init_userGroup: found in cache for dn:%s\n", n_dn,0,0); + } + if (!got_lock ) ACLG_ULOCK_GROUPCACHE_WRITE (); +} + + +/* + * + * aclg_reset_userGroup + * Reset the reference count to the user's group. + * + * Inputs: + * struct acl_pblock -- The acl private block. + * Returns: + * None. + * + * Note: A WRITE Lock on the GroupCache is obtained during the change: + */ +void +aclg_reset_userGroup ( struct acl_pblock *aclpb ) +{ + + aclUserGroup *u_group; + + ACLG_LOCK_GROUPCACHE_WRITE(); + + if ( (u_group = aclpb->aclpb_groupinfo) != NULL ) { + u_group->aclug_refcnt--; + + /* If I am the last one but I was using an invalid group cache + ** in the meantime, it is time now to get rid of it so that we will + ** not have duplicate cache. + */ + if ( !u_group->aclug_refcnt && + ( aclUserGroups->aclg_signature != u_group->aclug_signature )) { + __aclg__delete_userGroup ( u_group ); + } + } + ACLG_ULOCK_GROUPCACHE_WRITE(); + aclpb->aclpb_groupinfo = NULL; +} + +/* + * Find a user group in the global cache, returning a pointer to it, + * ensuring that the refcnt has been bumped to stop + * another thread freeing it underneath us. +*/ + +aclUserGroup* +aclg_find_userGroup(char *n_dn) +{ + aclUserGroup *u_group = NULL; + int i; + + /* Check for Anonymous user */ + if ( n_dn && *n_dn == '\0') return (NULL) ; + + ACLG_LOCK_GROUPCACHE_READ (); + u_group = aclUserGroups->aclg_first; + + for ( i=0; i < aclUserGroups->aclg_num_userGroups; i++ ) { + if ( aclUserGroups->aclg_signature == u_group->aclug_signature && + slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn, + (ACLUCHP)n_dn ) == 0 ) { + aclg_reader_incr_ugroup_refcnt(u_group); + break; + } + u_group = u_group->aclug_next; + } + + ACLG_ULOCK_GROUPCACHE_READ (); + return(u_group); +} + +/* + * Mark a usergroup for removal from the usergroup cache. + * It will be removed by the first operation traversing the cache + * that finds it. +*/ +void +aclg_markUgroupForRemoval ( aclUserGroup* u_group) { + + ACLG_LOCK_GROUPCACHE_WRITE (); + aclg_regen_ugroup_signature(u_group); + u_group->aclug_refcnt--; + ACLG_ULOCK_GROUPCACHE_WRITE (); +} + +/* + * + * aclg_get_usersGroup + * + * If we already have a the group info then we are done. If we + * don't, then allocate a new one and attach it. + * + * Inputs: + * struct acl_pblock -- The acl private block. + * char *n_dn - normalized client's DN + * + * Returns: + * aclUserGroup - The Group info block. + * + */ +aclUserGroup * +aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn) +{ + + aclUserGroup *u_group, *f_group; + + if ( aclpb && aclpb->aclpb_groupinfo ) + return aclpb->aclpb_groupinfo; + + ACLG_LOCK_GROUPCACHE_WRITE(); + + /* try it one more time. We might have one in the meantime */ + aclg_init_userGroup (aclpb, n_dn , 1 /* got the lock */); + if ( aclpb && aclpb->aclpb_groupinfo ) { + ACLG_ULOCK_GROUPCACHE_WRITE(); + return aclpb->aclpb_groupinfo; + } + + /* + * It is possible at this point that we already have a group cache for the user + * but is is invalid. We can't use it anayway. So, we march along and allocate a new one. + * That's fine as the invalid one will be deallocated when done. + */ + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "ALLOCATING GROUP FOR:%s\n", n_dn,0,0 ); + u_group = ( aclUserGroup * ) slapi_ch_calloc ( 1, sizeof ( aclUserGroup ) ); + + u_group->aclug_refcnt = 1; + if ( (u_group->aclug_refcnt_mutex = PR_NewLock()) == NULL ) { + slapi_ch_free((void **)&u_group); + ACLG_ULOCK_GROUPCACHE_WRITE(); + return(NULL); + } + + u_group->aclug_member_groups = (char **) + slapi_ch_calloc ( 1, + (ACLUG_INCR_GROUPS_LIST * sizeof (char *))); + u_group->aclug_member_group_size = ACLUG_INCR_GROUPS_LIST; + u_group->aclug_numof_member_group = 0; + + u_group->aclug_notmember_groups = (char **) + slapi_ch_calloc ( 1, + (ACLUG_INCR_GROUPS_LIST * sizeof (char *))); + u_group->aclug_notmember_group_size = ACLUG_INCR_GROUPS_LIST; + u_group->aclug_numof_notmember_group = 0; + + u_group->aclug_ndn = slapi_ch_strdup ( n_dn ) ; + + u_group->aclug_signature = aclUserGroups->aclg_signature; + + /* Do we have alreday the max number. If we have then delete the last one */ + if ( aclUserGroups->aclg_num_userGroups >= ACL_MAXCACHE_USERGROUPS - 5 ) { + aclUserGroup *d_group; + + /* We need to traverse thru backwards and delete the one with a refcnt = 0 */ + d_group = aclUserGroups->aclg_last; + while ( d_group ) { + if ( !d_group->aclug_refcnt ) { + __aclg__delete_userGroup ( d_group ); + break; + } else { + d_group = d_group->aclug_prev; + } + } + + /* If we didn't find any, which should be never, + ** we have 5 more tries to do it. + */ + } + f_group = aclUserGroups->aclg_first; + u_group->aclug_next = f_group; + if ( f_group ) f_group->aclug_prev = u_group; + + aclUserGroups->aclg_first = u_group; + if ( aclUserGroups->aclg_last == NULL ) + aclUserGroups->aclg_last = u_group; + + aclUserGroups->aclg_num_userGroups++; + + /* Put it in the queue */ + ACLG_ULOCK_GROUPCACHE_WRITE(); + + /* Now hang on to it */ + aclpb->aclpb_groupinfo = u_group; + return u_group; +} + +/* + * + * __aclg__delete_userGroup + * + * Delete the User's Group cache. + * + * Inputs: + * aclUserGroup - remove this one + * Returns: + * None. + * + * Note: A WRITE Lock on the GroupCache is obtained by the caller + */ +static void +__aclg__delete_userGroup ( aclUserGroup *u_group ) +{ + + aclUserGroup *next_group, *prev_group; + int i; + + if ( !u_group ) return; + + prev_group = u_group->aclug_prev; + next_group = u_group->aclug_next; + + /* + * At this point we must have a 0 refcnt or else we are in a bad shape. + * If we don't have one then at least remove the user's dn so that it will + * be in a condemned state and later deleted. + */ + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "DEALLOCATING GROUP FOR:%s\n", u_group->aclug_ndn,0,0 ); + + slapi_ch_free ( (void **) &u_group->aclug_ndn ); + + PR_DestroyLock(u_group->aclug_refcnt_mutex); + + /* Remove the member GROUPS */ + for (i=0; i < u_group->aclug_numof_member_group; i++ ) + slapi_ch_free ( (void **) &u_group->aclug_member_groups[i] ); + slapi_ch_free ( (void **) &u_group->aclug_member_groups ); + + /* Remove the NOT member GROUPS */ + for (i=0; i < u_group->aclug_numof_notmember_group; i++ ) + slapi_ch_free ( (void **) &u_group->aclug_notmember_groups[i] ); + slapi_ch_free ( (void **) &u_group->aclug_notmember_groups ); + + slapi_ch_free ( (void **) &u_group ); + + if ( prev_group == NULL && next_group == NULL ) { + aclUserGroups->aclg_first = NULL; + aclUserGroups->aclg_last = NULL; + } else if ( prev_group == NULL ) { + next_group->aclug_prev = NULL; + aclUserGroups->aclg_first = next_group; + } else { + prev_group->aclug_next = next_group; + if ( next_group ) + next_group->aclug_prev = prev_group; + else + aclUserGroups->aclg_last = prev_group; + } + aclUserGroups->aclg_num_userGroups--; +} + +void +aclg_regen_group_signature( ) +{ + aclUserGroups->aclg_signature = aclutil_gen_signature ( aclUserGroups->aclg_signature ); +} + +void +aclg_regen_ugroup_signature( aclUserGroup *ugroup) +{ + ugroup->aclug_signature = + aclutil_gen_signature ( ugroup->aclug_signature ); +} + +void +aclg_lock_groupCache ( int type /* 1 for reader and 2 for writer */) +{ + + if (type == 1 ) + ACLG_LOCK_GROUPCACHE_READ(); + else + ACLG_LOCK_GROUPCACHE_WRITE(); +} + +void +aclg_unlock_groupCache ( int type /* 1 for reader and 2 for writer */) +{ + + if (type == 1 ) + ACLG_ULOCK_GROUPCACHE_READ(); + else + ACLG_ULOCK_GROUPCACHE_WRITE(); +} + + +/* + * If you have the write lock on the group cache, you can + * increment the refcnt without taking the mutex. + * If you just have the reader lock on the refcnt then you need to + * take the mutex on the refcnt to increment it--which is what this routine is + * for. + * +*/ + +void +aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group) { + + PR_Lock(u_group->aclug_refcnt_mutex); + u_group->aclug_refcnt++; + PR_Unlock(u_group->aclug_refcnt_mutex); +} + +/* You need the usergroups read lock to call this routine*/ +int +aclg_numof_usergroups(void) { + + return(aclUserGroups->aclg_num_userGroups); +} + diff --git a/ldap/servers/plugins/acl/aclinit.c b/ldap/servers/plugins/acl/aclinit.c new file mode 100644 index 00000000..21e54337 --- /dev/null +++ b/ldap/servers/plugins/acl/aclinit.c @@ -0,0 +1,537 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +static int __aclinit__RegisterLases(void); +static int __aclinit__RegisterAttributes(void); +static int __aclinit_handler(Slapi_Entry *e, void *callback_data); + +/*************************************************************************** +* +* aclinit_main() +* Main routine which is called at the server boot up time. +* +* 1) Reads all the ACI entries from the database and creates +* the ACL list. +* 2) Registers all the LASes and the GetAttrs supported by the DS. +* 3) Generates anonymous profiles. +* 4) Registers proxy control +* 5) Creates aclpb pool +* +* Input: +* None. +* +* Returns: +* 0 -- no error +* 1 -- Error +* +* Error Handling: +* If any error found during the ACL generation, error is logged. +* +**************************************************************************/ +static int acl_initialized = 0; +int +aclinit_main() +{ + char *cookie = NULL; + Slapi_PBlock *pb; + int rv; + Slapi_DN *sdn; + void *node; + + if (acl_initialized) { + /* There is no need to do anything more */ + return 0; + } + + /* Initialize the LIBACCESS ACL library */ + if (ACL_Init() != 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "ACL Library Initialization failed\n",0,0,0); + return 1; + } + + /* register all the LASes supported by the DS */ + if (ACL_ERR == __aclinit__RegisterLases()) { + /* Error is already logged */ + return 1; + } + + /* Register all the Attrs */ + if (ACL_ERR == __aclinit__RegisterAttributes()) { + /* Error is already logged */ + return 1; + } + + /* + * Register to get backend state changes so we can add/remove + * acis from backends that come up and go down. + */ + + slapi_register_backend_state_change((void *) NULL, acl_be_state_change_fnc); + + + /* register the extensions */ + /* ONREPL Moved to the acl_init function because extensions + need to be registered before any operations are issued + if ( 0 != acl_init_ext() ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to initialize the extensions\n"); + return 1; + } */ + + /* create the mutex array */ + if ( 0 != aclext_alloc_lockarray ( ) ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to create the mutext array\n"); + return 1; + } + + /* Allocate the pool */ + if ( 0 != acl_create_aclpb_pool () ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to create the acl private pool\n"); + return 1; + } + + /* + * Now read all the ACLs from all the backends and put it + * in a list + */ + /* initialize the ACLLIST sub-system */ + if ( 0 != (rv = acllist_init ( ))) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to initialize the plugin:%d\n", rv ); + return 1; + } + + /* Initialize the anonymous profile i.e., generate it */ + rv = aclanom_init (); + + pb = slapi_pblock_new(); + + /* + * search for the aci_attr_type attributes of all entries. + * + * slapi_get_fist_suffix() and slapi_get_next_suffix() do not return the + * rootdse entry so we search for acis in there explicitly here. + */ + + sdn = slapi_sdn_new_dn_byval(""); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Searching for all acis(scope base) at suffix ''\n"); + aclinit_search_and_update_aci ( 0, /* thisbeonly */ + sdn, /* base */ + NULL, /* be name*/ + LDAP_SCOPE_BASE, ACL_ADD_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + slapi_sdn_free(&sdn); + + sdn = slapi_get_first_suffix( &node, 1 ); + while (sdn) + { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Searching for all acis(scope subtree) at suffix '%s'\n", + slapi_sdn_get_dn(sdn) ); + aclinit_search_and_update_aci ( 0, /* thisbeonly */ + sdn, /* base */ + NULL, /* be name*/ + LDAP_SCOPE_SUBTREE, ACL_ADD_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + sdn = slapi_get_next_suffix( &node, 1 ); + } + + /* Initialize it. */ + acl_initialized = 1; + + /* generate the signatures */ + acl_set_aclsignature ( aclutil_gen_signature ( 100 ) ); + + /* Initialize the user-group cache */ + rv = aclgroup_init ( ); + + aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK); + + /* Register both of the proxied authorization controls (version 1 and 2) */ + slapi_register_supported_control( LDAP_CONTROL_PROXYAUTH, + SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE + | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE + | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN + | SLAPI_OPERATION_EXTENDED ); + slapi_register_supported_control( LDAP_CONTROL_PROXIEDAUTH, + SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE + | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE + | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN + | SLAPI_OPERATION_EXTENDED ); + + slapi_pblock_destroy ( pb ); + return 0; +} +/* + * This routine is the one that scans for acis and either adds them + * to the internal cache (op==ACL_ADD_ACIS) or deletes them + * (op==ACL_REMOVE_ACIS). + * + * If thisbeonly is 0 the search + * is conducted on the base with the specifed scope and be_name is ignored. + * This is used at startup time where we iterate over all suffixes, searching + * for all the acis in the DIT to load the ACL cache. + * + * If thisbeonly is 1 then then a be_name must be specified. + * In this case we will search in that backend ONLY. + * This is used in the case where a backend is turned on and off--in this + * case we only want to add/remove the acis in that particular backend and + * not for example in any backends below that one. +*/ + +int +aclinit_search_and_update_aci ( int thisbeonly, const Slapi_DN *base, + char *be_name, int scope, int op, + acl_lock_flag_t lock_flag ) +{ + char *attrs[2] = { "aci", NULL }; + /* Tell __aclinit_handler whether it's an add or a delete */ + int any_error = op; + Slapi_PBlock *aPb; + LDAPControl **ctrls=NULL; + int retval; + struct berval *bval; + aclinit_handler_callback_data_t call_back_data; + + PR_ASSERT( lock_flag == DONT_TAKE_ACLCACHE_WRITELOCK || + lock_flag == DO_TAKE_ACLCACHE_WRITELOCK); + + if ( thisbeonly && be_name == NULL) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Error: This be_name must be specified.\n", 0, 0, 0); + return -1; + } + + + /* + * We need to explicitly request (objectclass=ldapsubentry) + * in order to get all the subentry acis too. + * Note that subentries can be added under subentries (although its not + * recommended) so that + * there may be non-trivial acis under a subentry. + */ + + /* Use new search internal API */ + /* and never retrieve aci from a remote server */ + aPb = slapi_pblock_new (); + + /* + * Set up the control to say "Only get acis from this Backend-- + * there may be more backends under this one. + */ + + if ( thisbeonly ) { + + bval = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + bval->bv_len = strlen(be_name) + 1; + bval->bv_val = slapi_ch_strdup(be_name); + + ctrls = (LDAPControl **)slapi_ch_calloc( 2, sizeof(LDAPControl *)); + ctrls[0] = NULL; + ctrls[1] = NULL; + + retval = slapi_build_control_from_berval( + MTN_CONTROL_USE_ONE_BACKEND_OID, + bval, + 1 /* is critical */, + ctrls); + + } + + slapi_search_internal_set_pb ( aPb, + slapi_sdn_get_dn(base), + scope, + "(|(aci=*)(objectclass=ldapsubentry))", + attrs, + 0 /* attrsonly */, + ctrls /* controls: SLAPI_ARGCONTROLS */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions : get local aci only */); + + if (thisbeonly) { + slapi_pblock_set(aPb, SLAPI_REQCONTROLS, ctrls); + } + + call_back_data.op = op; + call_back_data.retCode = 0; + call_back_data.lock_flag = lock_flag; + + slapi_search_internal_callback_pb(aPb, + &call_back_data /* callback_data */, + NULL/* result_callback */, + __aclinit_handler, + NULL /* referral_callback */); + + if (thisbeonly) { + slapi_ch_free((void **)&bval); + } + + /* + * This frees the control oid, the bv_val and the control itself and the + * ctrls array mem by caling ldap_controls_free()--so we + * don't need to do it ourselves. + */ + slapi_pblock_destroy (aPb); + + return call_back_data.retCode; + +} + +/*************************************************************************** +* +* __aclinit_handler +* +* For each entry, finds if there is any ACL in thet entry. If there is +* then the ACL is processed and stored in the ACL LIST. +* +* +* Input: +* +* +* Returns: +* None. +* +* Error Handling: +* If any error found during the ACL generation, the ACL is +* logged. Also, set in the callback_data so that caller can act upon it. +* +**************************************************************************/ +static int +__aclinit_handler ( Slapi_Entry *e, void *callback_data) +{ + Slapi_Attr *attr; + aclinit_handler_callback_data_t *call_back_data = + (aclinit_handler_callback_data_t*)callback_data; + Slapi_DN *e_sdn; + int rv; + Slapi_Value *sval=NULL; + + call_back_data->retCode = 0; /* assume success--if there's an error we overwrite it */ + if (e != NULL) { + + e_sdn = slapi_entry_get_sdn ( e ); + + /* + * Take the write lock around all the mods--so that + * other operations will see the acicache either before the whole mod + * or after but not, as it was before, during the mod. + * This is in line with the LDAP concept of the operation + * on the whole entry being the atomic unit. + * + */ + + if ( call_back_data->op == ACL_ADD_ACIS ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Adding acis for entry '%s'\n", slapi_sdn_get_dn(e_sdn)); + slapi_entry_attr_find ( e, aci_attr_type, &attr ); + + if ( attr ) { + + const struct berval *attrValue; + + int i; + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_LOCK(); + } + i= slapi_attr_first_value ( attr, &sval ); + while(i != -1) { + attrValue = slapi_value_get_berval(sval); + + if ( 0 != (rv=acllist_insert_aci_needsLock (e_sdn, attrValue))) { + aclutil_print_err(rv, e_sdn, attrValue, NULL); + + /* We got an error; Log it and then march along */ + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Error: This (%s) ACL will not be considered for evaluation" + " because of syntax errors.\n", + attrValue->bv_val ? attrValue->bv_val: "NULL", 0, 0); + call_back_data->retCode = rv; + } + i= slapi_attr_next_value( attr, i, &sval ); + }/* while */ + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_UNLOCK(); + } + } + } else if (call_back_data->op == ACL_REMOVE_ACIS) { + + /* Here we are deleting the acis. */ + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Removing acis\n"); + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_LOCK(); + } + if ( 0 != (rv=acllist_remove_aci_needsLock(e_sdn, NULL))) { + aclutil_print_err(rv, e_sdn, NULL, NULL); + + /* We got an error; Log it and then march along */ + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Error: ACls not deleted from %s\n", + e_sdn, 0, 0); + call_back_data->retCode = rv; + } + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_UNLOCK(); + } + } + + } + + /* + * If we get here it's success. + * The call_back_data->error is the error code that counts as it's the + * one that the original caller will see--this routine is called off a callbacl. + */ + + return ACL_FALSE; /* "local" error code--it's 0 */ +} +/*************************************************************************** +* +* __acl__RegisterAttributes +* +* Register all the attributes supported by the DS. +* +* Input: +* None. +* +* Returns: +* ACL_OK - No error +* ACL_ERR - in case of errror +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +__aclinit__RegisterAttributes(void) +{ + + ACLMethod_t methodinfo; + NSErr_t errp; + int rv; + + memset (&errp, 0, sizeof(NSErr_t)); + + rv = ACL_MethodRegister(&errp, DS_METHOD, &methodinfo); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Register the methods\n", 0,0,0); + return ACL_ERR; + } + rv = ACL_MethodSetDefault (&errp, methodinfo); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Set the default method\n", 0,0,0); + return ACL_ERR; + } + rv = ACL_AttrGetterRegister(&errp, ACL_ATTR_IP, DS_LASIpGetter, + methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Register Attr ip\n", 0,0,0); + return ACL_ERR; + } + rv = ACL_AttrGetterRegister(&errp, ACL_ATTR_DNS, DS_LASDnsGetter, + methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Register Attr dns\n", 0,0,0); + return ACL_ERR; + } + return ACL_OK; +} + +/*************************************************************************** +* +* __acl__RegisterLases +* Register all the LASes supported by the DS. +* +* The DS doesnot support user/group. We have defined our own LAS +* so that we can display/print an error when the LAS is invoked. +* Input: +* None. +* +* Returns: +* ACL_OK - No error +* ACL_ERR - in case of errror +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +__aclinit__RegisterLases(void) +{ + + if (ACL_LasRegister(NULL, DS_LAS_USER, (LASEvalFunc_t) DS_LASUserEval, + (LASFlushFunc_t) NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USER Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_GROUP, (LASEvalFunc_t) DS_LASGroupEval, + (LASFlushFunc_t) NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register GROUP Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_GROUPDN, (LASEvalFunc_t)DS_LASGroupDnEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register GROUPDN Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_ROLEDN, (LASEvalFunc_t)DS_LASRoleDnEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register ROLEDN Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_USERDN, (LASEvalFunc_t)DS_LASUserDnEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USERDN Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_USERDNATTR, + (LASEvalFunc_t)DS_LASUserDnAttrEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USERDNATTR Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_AUTHMETHOD, + (LASEvalFunc_t)DS_LASAuthMethodEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register CLIENTAUTHTYPE Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_GROUPDNATTR, + (LASEvalFunc_t)DS_LASGroupDnAttrEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register GROUPDNATTR Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_USERATTR, + (LASEvalFunc_t)DS_LASUserAttrEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USERATTR Las\n",0,0,0); + return ACL_ERR; + } + return ACL_OK; +} diff --git a/ldap/servers/plugins/acl/acllas.c b/ldap/servers/plugins/acl/acllas.c new file mode 100644 index 00000000..0179b0e6 --- /dev/null +++ b/ldap/servers/plugins/acl/acllas.c @@ -0,0 +1,3848 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <ipfstruct.h> +#include "acl.h" + +/* + A word on this file: + + The various routines here implement each component of the subject of an aci + eg. "groupdn", "userdn","roledn", "userattr" etc. + They are responsible for evaluating each individual keyword not for doing + the boolean combination of these keywords, nor for combining multiple + allow()/deny() statements--that's libaccess's job. + For example, for "groupdn", DS_LASGroupDnEval might have to evaluate + something like this: + + "groupdn = "ldap:///cn=G1,o=sun.com || ldap:///cn=G2,o=sun.com" + + The "=" here may be "!=" as well and these routines take care of the + comparator. + + These rotuines get called via acl__TestRights(), which calls + ACL_EvalTestRights() a libaccess routine (the immediately calling routine is + ACLEvalAce() in oneeval.cpp). + + They should return LAS_EVAL_TRUE, if that keyword component evaluates to + TRUE, LAS_EVAL_FALSE if it evaluates to FALSE and LAS_EVAL_FAIL if an + error occurrs during evaluation. Note that once any component of a subject + returns LAS_EVAL_FAIL, the evaluation in libaccess stops and the whole + subject does not match and that aci is not applied. +*/ + +/* + + A word on three-valued logic: + + In general when you do boolean combination of terms some of which + may evaluate to UNDEFINED then you need to define what the combination + means. + So, for example libaccess implements a scheme which once UNDEFINED + is returned for a term, it bales out of the + evaluation and the whole expression evaluates to UNDEFINED. + In this case the aci will not apply. + On the other hand LDAP filters (cf. rfc2251 4.5.1) say that for OR, + an expression will + evaluate to TRUE if any term is TRUE, even if some terms are UNDEFINED. + Other off the cuff options might be to redefine UNDEFINED to be FALSE, + or TRUE. + + Which is best ? + + Well it probably depends on exactly what is to decided based on the + evaluation of the logical expression. However, the final suggestion is + almost certainly + bad--you are unlikely to want to take an action based on an undefined + result and + defining UNDEFINED to be either TRUE or FALSE may result in the overall + expression + returning TRUE--a security hole. The only case this might work is if you + are dealing with restricted + expressions eg. terms may only be AND'ed togther--in this case defining + UNDEFINED to be FALSE would guarantee a result of FALSE. + + The libaccess approach of returning UNDEFINED once an UNDEFINED is + encountered during + evaluation is not too bad--at least it guarantees that no aci will apply + based on an + undefined value. However, with an aci like this "...allow(all) A or B" + where A returned UNDEFINED, you might be disappointed not to receive the + rights if it was B that + was granting you the rights and evaluation of A, which has nothing to do + with you, returns UNDEFINED. In the case of an aci like + "...deny(all) A or B" then the same + situation is arguably a security hole. Note that this scheme also makes + the final result + dependent on the evaluation order and so if the evaluation engine does + anything fancy internally (eg. reordering the terms in an OR so that fast + to evaluate ones came first) then + this would need to be documented so that a user (or a tool) could look at + the external syntax and figure out the result of the evaluation. + Also it breaks commutivity and De Morgans law. + + The LDAP filter scheme is starting to look good--it solves the problems of + the + libaccess approach, makes the final result of an expression independent of + the evaluation order and + gives you back commutivity of OR and AND. De Morgans is still broken, but + that's because of the asymmetry of behaviour of UNDEFINED with OR and AND. + + So...? + + For acis, in general it can look like this: + + "...allow(rights)(LogicalCombinationofBindRule); + deny(LogicalCombinationOfBindRule)...." + + A BindRule is one of the "userdn", "groupdn" or "userattr" things and it + can look like this: + + "groupdn = "ldap:///cn=G1,o=sun.com || ldap:///cn=G2,o=sun.com" + + The "=" here may be "!=" as well and these routines take care of the + comparator. + + For "userattr" keywords a mutilvalued attribute amounts a logical OR of the + individual values. There is also a logical OR over the different levels + as specified by the "parent" keyword. + + In fact there are three levels of logical combination: + + 1. In the aclplugin: + The "||" and "!=" combinator for BindRule keywords like userdn and + groupdn. + The fact that for the "userattr" keyword, a mutilvalued attribute is + evaluated as "||". Same for the different levels. + 2. In libaccess: + The logical combination of BindRules. + 3. In libaccess: + The evaluation of multiple BindRules seperated by ";", which means OR. + + The LDAP filter three-valued logic SHOULD be applied to each level but + here's the way it works right now: + + 1. At this level it depends.... + + DS_LASIpGetter - get attr for IP - + returns ip address or LAS_EVAL_FAIL for error. + no logical combination. + DS_LASDnsGetter - get attr for DNS- + returns dns name or LAS_EVAL_FAIL for error + no logical combination. + DS_LASUserDnEval - LAS Evaluation for USERDN - + three-valued logic + logical combination: || and != + DS_LASGroupDnEval - LAS Evaluation for GROUPDN - + three-valued logic + logical combination: || and != + DS_LASRoleDnEval - LAS Evaluation for ROLEDN - + three-valued logic + logical combination: || and != + DS_LASUserDnAttrEval - LAS Evaluation for USERDNATTR - + three-valued logic + logical combination || (over specified attribute values and + parent keyword levels), != + DS_LASAuthMethodEval - LAS Evaluation for AUTHMETHOD - + three-valued logic ( logical combinations: !=) + DS_LASGroupDnAttrEval - LAS Evaluation for GROUPDNATTR - + three-valued logic + logical combination || (over specified attribute values and + parent keyword levels), != + DS_LASUserAttrEval - LAS Evaluation for USERATTR - + USER, GROUPDN and ROLEDN as above. + LDAPURL -- three-valued logic (logical combinations: || over + specified attribute vales, !=) + attrname#attrvalue -- three-valued logic, logical combination:!= + + 2. The libaccess scheme applies at this level. + 3. The LDAP filter three-valued logic applies at this level. + + Example of realistic, non-bizarre things that cause evaluation of a + BindRule to be undefined are exceeding some resource limits (nesting level, + lookthrough limit) in group membership evaluation, or trying to get ADD + permission from the "userattr" keyword at "parent" level 0. + Note that not everything that might be construed as an error needs to be + taken as UNDEFINED. For example, things like not finding a user or an + attribute in an entry can be defined away as TRUE or FALSE. eg. in an + LDAP filter (cn=rob) applied to an entry where cn is not present is FALSE, + not UNDEFINED. Similarly, if the number of levels in a parent keyword + exceeds the allowed limit, we just ignore the rest--though this + is a syntax error which should be detected at parse time. + + +*/ + +/* To get around warning: declared in ldapserver/lib/ldaputil/ldaputili.h */ +extern int ldapu_member_certificate_match (void* cert, const char* desc); + +/****************************************************************************/ +/* Defines, Constants, ande Declarations */ +/****************************************************************************/ +static char* const type_objectClass = "objectclass"; +static char* const filter_groups = "(|(objectclass=groupOfNames) (objectclass=groupOfUniqueNames)(objectclass=groupOfCertificates)(objectclass=groupOfURLs))"; +static char* const type_member = "member"; +static char* const type_uniquemember = "uniquemember"; +static char* const type_memberURL = "memberURL"; +static char* const type_memberCert = "memberCertificateDescription"; + +/* cache strategy for groups */ +#define ACLLAS_CACHE_MEMBER_GROUPS 0x1 +#define ACLLAS_CACHE_NOT_MEMBER_GROUPS 0x2 +#define ACLLAS_CACHE_ALL_GROUPS 0x3 + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int acllas__handle_group_entry(Slapi_Entry *, void *); +static int acllas__user_ismember_of_group(struct acl_pblock *aclpb, + char* groupDN, + char* clientDN, + int cache_status, + CERTCertificate *clientCert); +static int acllas__user_has_role( struct acl_pblock *aclpb, + Slapi_DN *roleDN, Slapi_DN *clientDn); +static int acllas__add_allgroups (Slapi_Entry* e, void *callback_data); +static int acllas__eval_memberGroupDnAttr (char *attrName, + Slapi_Entry *e, + char *n_clientdn, + struct acl_pblock *aclpb); +static int acllas__verify_client (Slapi_Entry* e, void *callback_data); +static char* acllas__dn_parent( char *dn, int level); +static int acllas__get_members (Slapi_Entry* e, void *callback_data); +static int acllas__client_match_URL (struct acl_pblock *aclpb, + char *n_dn, char *url ); +static int acllas__handle_client_search (Slapi_Entry *e, void *callback_data); +static int __acllas_setup ( NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth, char *lasType, char *lasName, lasInfo *linfo); +int +aclutil_evaluate_macro( char * user, lasInfo *lasinfo, + acl_eval_types evalType ); +static int +acllas_eval_one_user( struct acl_pblock *aclpb, + char * clientDN, char *userKeyword); +static int +acllas_eval_one_group(char *group, lasInfo *lasinfo); +static int +acllas_eval_one_role(char *role, lasInfo *lasinfo); +static char ** +acllas_replace_dn_macro( char *rule, char *matched_val, lasInfo *lasinfo); +static char ** +acllas_replace_attr_macro( char *rule, lasInfo *lasinfo); +static int +acllas_eval_one_target_filter( char * str, Slapi_Entry *e); + +/****************************************************************************/ + +int +DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg) +{ + + struct acl_pblock *aclpb = NULL; + IPAddr_t ip=0; + PRNetAddr client_praddr; + struct in_addr client_addr; + int rv; + + + rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb, + subject, resource, auth_info, global_auth); + if ( rv != LAS_EVAL_TRUE || ( NULL == aclpb )) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASIpGetter:Unable to get the ACLPB(%d)\n", rv,0,0); + return LAS_EVAL_FAIL; + } + + if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CONN_CLIENTNETADDR, + &client_praddr ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" ); + return( LAS_EVAL_FAIL ); + } + + if ( !PR_IsNetAddrType(&client_praddr, PR_IpAddrV4Mapped) ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Client address is IPv6. ACLs only support IPv4 addresses so far.\n"); + return( LAS_EVAL_FAIL ); + } + + client_addr.s_addr = client_praddr.ipv6.ip.pr_s6_addr32[3]; + + ip = (IPAddr_t) ntohl( client_addr.s_addr ); + rv = PListInitProp(subject, 0, ACL_ATTR_IP, (void *)ip, NULL); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning client ip address '%s'\n", + (slapi_is_loglevel_set(SLAPI_LOG_ACL) ? inet_ntoa(client_addr) : "")); + + return LAS_EVAL_TRUE; + +} + +/* + * This is called from the libaccess code when it needs to find a dns name. + * It's called from ACL_GetAttribute() when it finds that ACL_ATTR_DNS is + * not already part of the proplist. + * +*/ + +int +DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg) +{ + struct acl_pblock *aclpb = NULL; + PRNetAddr client_praddr; + PRHostEnt *hp; + char *dnsName = NULL; + int rv; + struct berval **clientDns; + + + rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb, + subject, resource, auth_info, global_auth); + if ( rv != LAS_EVAL_TRUE || ( NULL == aclpb )) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASDnsGetter:Unable to get the ACLPB(%d)\n", rv,0,0); + return LAS_EVAL_FAIL; + } + + if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CLIENT_DNS, &clientDns ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" ); + return( LAS_EVAL_FAIL ); + } + + /* + * If the client hostname has already been put into the pblock then + * use that. Otherwise we work it out and add it ourselves. + * This info is connection-lifetime so with multiple operaitons on the same + * connection we will only do the calculation once. + * + * rbyrneXXX surely this code would be better in connection.c so + * the name would be just there waiting for us, and everyone else. + * + */ + + if ( clientDns && clientDns[0] != NULL && clientDns[0]->bv_val ) { + dnsName = clientDns[0]->bv_val; + } else { + struct berval **dnsList; + char buf[PR_NETDB_BUF_SIZE]; + + if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CONN_CLIENTNETADDR, &client_praddr ) != 0 ) { + + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" ); + return( LAS_EVAL_FAIL ); + } + hp = (PRHostEnt *)slapi_ch_malloc( sizeof(PRHostEnt) ); + if ( PR_GetHostByAddr( &(client_praddr), (char *)buf, sizeof(buf), hp ) == PR_SUCCESS ) { + if ( hp->h_name != NULL ) { + dnsList = (struct berval**) + slapi_ch_calloc (1, sizeof(struct berval*) * (1 + 1)); + *dnsList = (struct berval*) + slapi_ch_calloc ( 1, sizeof(struct berval)); + dnsName = (*dnsList)->bv_val = slapi_ch_strdup( hp->h_name ); + (*dnsList)->bv_len = strlen ( (*dnsList)->bv_val ); + slapi_pblock_set( aclpb->aclpb_pblock, SLAPI_CLIENT_DNS, &dnsList ); + } + } + slapi_ch_free( (void **)&hp ); + } + + if ( NULL == dnsName ) return LAS_EVAL_FAIL; + + rv = PListInitProp(subject, 0, ACL_ATTR_DNS, dnsName, NULL); + if (rv < 0) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASDnsGetter:Couldn't set the DNS property(%d)\n", rv ); + return LAS_EVAL_FAIL; + } + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "DNS name: %s\n", dnsName ); + return LAS_EVAL_TRUE; + +} +/***************************************************************************/ +/* New LASes */ +/* */ +/* 1. user, groups. -- stubs to report errors. Not supported. */ +/* 2. userdn */ +/* 3. groupdn */ +/* 4. userdnattr */ +/* 5. authmethod */ +/* 6. groupdnattr */ +/* 7. roledn */ +/* */ +/* */ +/***************************************************************************/ +int +DS_LASUserEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "User LAS is not supported in the ACL\n",0,0,0); + + return LAS_EVAL_INVALID; +} + +int +DS_LASGroupEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Group LAS is not supported in the ACL\n",0,0,0); + + return LAS_EVAL_INVALID; +} + +/*************************************************************************** +* +* DS_LASUserDnEval +* Evaluate the "userdn" LAS. See if the user has rights. +* +* Input: +* attr_name The string "userdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASUserDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *users = NULL; + char *s_user, *user = NULL; + char *ptr = NULL; + char *end_dn = NULL; + char *n_edn = NULL; + char *parent_dn = NULL; + int matched; + int rc; + short len; + char *s = NULL; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_USERDN, "DS_LASUserDnEval", &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + users = slapi_ch_strdup(attr_pattern); + user = users; + matched = ACL_FALSE; + + /* check if the clientdn is one of the users */ + while(user != 0 && *user != 0 && matched != ACL_TRUE ) { + + /* ignore leading whitespace */ + while(ldap_utf8isspace(user)) + LDAP_UTF8INC(user); + + /* Now we must see the userdn in the following + ** formats: + ** + ** The following formats are supported: + ** + ** 1. The DN itself: + ** allow (read) userdn = "ldap:///cn=prasanta, ..." + ** + ** 2. keyword SELF: + ** allow (write) + ** userdn = "ldap:///self" + ** + ** 3. Pattern: + ** deny (read) userdn = "ldap:///cn=*, o=netscape, c = us"; + ** + ** 4. Anonymous user + ** deny (read, write) userdn = "ldap:///anyone" + ** + ** 5. All users (All authenticated users) + ** allow (search) ** userdn = "ldap:///all" + ** 6. parent "ldap:///parent" + ** 7. Synamic users using the URL + ** + ** + ** DNs must be separated by "||". Ex: + ** allow (read) + ** userdn = "ldap:///DN1 || ldap:///DN2" + */ + + + /* The DN is now "ldap:///DN" + ** remove the "ldap:///" part + */ + if (strncasecmp (user, LDAP_URL_prefix, + LDAP_URL_prefix_len) == 0) { + s_user = user; + user += LDAP_URL_prefix_len; + + } else { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "DS_LASUserDnEval:Syntax error(%s)\n", + escape_string_with_punctuation( user, ebuf ), 0,0); + return LAS_EVAL_FAIL; + } + + /* Now we have the starting point of the "userdn" */ + if ((end_dn = strstr(user, "||")) != NULL) { + auto char *t = end_dn; + LDAP_UTF8INC(end_dn); + LDAP_UTF8INC(end_dn); + *t = 0; + } + + /* Now user is a null terminated string */ + + if (*user) { + while(ldap_utf8isspace(user)) + LDAP_UTF8INC(user); + /* ignore trailing whitespace */ + len = strlen(user); + ptr = user+len-1; + while(ldap_utf8isspace(ptr)){ *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } + + /* + ** Check , if the user is a anonymous user. In that case + ** We must find the rule "ldap:///anyone" + */ + if (lasinfo.anomUser) { + if (strcasecmp(user, "anyone") == 0 ) { + /* matches -- anonymous user */ + matched = ACL_TRUE; + break; + } + } else { + /* URL format */ + + if ((s = strstr (user, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (user, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (user, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( s_user, &lasinfo, + ACL_EVAL_USER); + if (matched == ACL_TRUE) { + break; + } + + } else if ((s = strchr (user, '?'))!= NULL) { + /* URL format */ + if (acllas__client_match_URL ( lasinfo.aclpb, lasinfo.clientDn, + s_user) == ACL_TRUE) { + matched = ACL_TRUE; + break; + } + } else if (strcasecmp(user, "anyone") == 0 ) { + /* Anyone means anyone in the world */ + matched = ACL_TRUE; + break; + } else if (strcasecmp(user, "self") == 0) { + if (n_edn == NULL) { + n_edn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + } + if (slapi_utf8casecmp((ACLUCHP)lasinfo.clientDn, (ACLUCHP)n_edn) == 0) + matched = ACL_TRUE; + break; + } else if (strcasecmp(user, "parent") == 0) { + if (n_edn == NULL) { + n_edn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + } + /* get the parent */ + parent_dn = slapi_dn_parent(n_edn); + if (parent_dn && + slapi_utf8casecmp ((ACLUCHP)lasinfo.clientDn, (ACLUCHP)parent_dn) == 0) + matched = ACL_TRUE; + + if (parent_dn) slapi_ch_free ( (void **) &parent_dn ); + break; + } else if (strcasecmp(user, "all") == 0) { + /* matches -- */ + matched = ACL_TRUE; + break; + } else if (strchr(user, '*')) { + char line[200]; + char *lineptr = &line[0]; + char *newline = NULL; + int lenu = 0; + Slapi_Filter *f = NULL; + char *tt; + int filterChoice; + + /* + ** what we are doing is faking the str2simple() + ** function with a "userdn = "user") + */ + for (tt = user; *tt; tt++) + *tt = TOLOWER ( *tt ); + + if ((lenu = strlen(user)) > 190) { /* 200 - 9 for "(userdn=%s)" */ + newline = slapi_ch_malloc(lenu + 10); + lineptr = newline; + } + + sprintf (lineptr, "(userdn=%s)", user); + if ((f = slapi_str2filter (lineptr)) == NULL) { + if (newline) slapi_ch_free((void **) &newline); + /* try the next one */ + break; + } + if (newline) slapi_ch_free((void **) &newline); + filterChoice = slapi_filter_get_choice ( f ); + + if (( filterChoice != LDAP_FILTER_SUBSTRINGS) && + ( filterChoice != LDAP_FILTER_PRESENT)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserDnEval:Error in gen. filter(%s)\n", user); + } + if ((rc = acl_match_substring( f, + lasinfo.clientDn, + 1 /*exact match */) + ) == ACL_TRUE) { + matched = ACL_TRUE; + slapi_filter_free(f,1); + break; + } + if (rc == ACL_ERR) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserDnEval:Error in matching patteren(%s)\n", + user,0,0); + } + slapi_filter_free(f,1); + } else { + /* Must be a simple dn then */ + if (slapi_utf8casecmp((ACLUCHP)lasinfo.clientDn, + (ACLUCHP)slapi_dn_normalize(user)) == 0) { + matched = ACL_TRUE; + break; + } + } + } + + if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another user will evaluate to TRUE */ + got_undefined = 1; + } + /* Nothing matched -- try the next DN */ + user = end_dn; + } /* end of while */ + + slapi_ch_free ( (void **) &users); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for userdn evaluation.\n"); + } + + return rc; +} + +/*************************************************************************** +* +* DS_LASGroupDnEval +* +* +* Input: +* attr_name The string "userdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return code +* If the client is in any of the groups mentioned this groupdn keywrod +* then returns LAS_EVAL_TRUE, if he's not in any LAS_EVAL_FALSE. +* If any of the membership evaluations fail, then it goes on to evaluate the +* others. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASGroupDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *groups; + char *groupName; + char *ptr; + char *end_dn; + int matched; + int rc; + int len; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + int any_group = 0; + lasInfo lasinfo; + int got_undefined = 0; + + /* the setup should not fail under normal operation */ + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_GROUPDN, "DS_LASGroupDnEval", &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + groups = slapi_ch_strdup(attr_pattern); + groupName = groups; + matched = ACL_FALSE; + + /* check if the groupdn is one of the users */ + while(groupName != 0 && *groupName != 0 && matched != ACL_TRUE) { + + /* ignore leading whitespace */ + while(ldap_utf8isspace(groupName)) + LDAP_UTF8INC(groupName); + + /* + ** The syntax allowed for the groupdn is + ** + ** Example: + ** groupdn = "ldap:///dn1 || ldap:///dn2"; + ** + */ + + if (strncasecmp (groupName, LDAP_URL_prefix, + LDAP_URL_prefix_len) == 0) { + groupName += LDAP_URL_prefix_len; + } else { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "DS_LASGroupDnEval:Syntax error(%s)\n", + escape_string_with_punctuation( groupName, ebuf ),0,0); + } + + /* Now we have the starting point of the "groupdn" */ + if ((end_dn = strstr(groupName, "||")) != NULL) { + auto char *t = end_dn; + LDAP_UTF8INC(end_dn); + LDAP_UTF8INC(end_dn); + *t = 0; + } + + if (*groupName) { + while(ldap_utf8isspace(groupName)) + LDAP_UTF8INC(groupName); + /* ignore trailing whitespace */ + len = strlen(groupName); + ptr = groupName+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } + + /* + ** Now we have the DN of the group. Evaluate the "clientdn" + ** and see if the user is a member of the group. + */ + if (0 == (strcasecmp(groupName, "anyone"))) { + any_group = 1; + } + + if (any_group) { + /* anyone in the world */ + matched = ACL_TRUE; + break; + } else if ( lasinfo.anomUser && + (lasinfo.aclpb->aclpb_clientcert == NULL) && (!any_group)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Group not evaluated(%s)\n", groupName); + break; + } else { + char *s; + + if ((s = strstr (groupName, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (groupName, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (groupName, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( groupName, &lasinfo, + ACL_EVAL_GROUP); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASGroupDnEval: Param group name:%s\n", + groupName); + } else {/* normal evaluation */ + + matched = acllas_eval_one_group( groupName, &lasinfo); + + } + + if ( matched == ACL_TRUE ) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + } + /* Nothing matched -- try the next DN */ + groupName = end_dn; + + } /* end of while */ + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for groupdn evaluation.\n"); + } + + slapi_ch_free ((void**) &groups); + return rc; +} +/*************************************************************************** +* +* DS_LASRoleDnEval +* +* +* Input: +* attr_name The string "roledn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A "||" sperated list of roles +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASRoleDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *roles; + char *role; + char *ptr; + char *end_dn; + int matched; + int rc; + int len; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + int any_role = 0; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_ROLEDN, "DS_LASRoleDnEval", + &lasinfo )) ) { + return LAS_EVAL_FALSE; + } + + + roles = slapi_ch_strdup(attr_pattern); + role = roles; + matched = ACL_FALSE; + + /* check if the roledn is one of the users */ + while(role != 0 && *role != 0 && matched != ACL_TRUE) { + + /* ignore leading whitespace */ + while(ldap_utf8isspace(role)) + LDAP_UTF8INC(role); + + /* + ** The syntax allowed for the roledn is + ** + ** Example: + ** roledn = "ldap:///roledn1 || ldap:///roledn2"; + ** + */ + + if (strncasecmp (role, LDAP_URL_prefix, + LDAP_URL_prefix_len) == 0) { + role += LDAP_URL_prefix_len; + } else { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "DS_LASRoleDnEval:Syntax error(%s)\n", + escape_string_with_punctuation( role, ebuf ),0,0); + } + + /* Now we have the starting point of the "roledn" */ + if ((end_dn = strstr(role, "||")) != NULL) { + auto char *t = end_dn; + LDAP_UTF8INC(end_dn); + LDAP_UTF8INC(end_dn); + *t = 0; + } + + + if (*role) { + while(ldap_utf8isspace(role)) + LDAP_UTF8INC(role); + /* ignore trailing whitespace */ + len = strlen(role); + ptr = role+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } + + /* + ** Now we have the DN of the role. Evaluate the "clientdn" + ** and see if the user has this role. + */ + if (0 == (strcasecmp(role, "anyone"))) { + any_role = 1; + } + + if (any_role) { + /* anyone in the world */ + matched = ACL_TRUE; + break; + } else if ( lasinfo.anomUser && + (lasinfo.aclpb->aclpb_clientcert == NULL) && (!any_role)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Role not evaluated(%s) for anon user\n", role); + break; + } else { + + /* Take care of param strings */ + + char *s; + + if ((s = strstr (role, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (role, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (role, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( role, &lasinfo, + ACL_EVAL_ROLE); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASRoleDnEval: Param role name:%s\n", + role); + } else {/* normal evaluation */ + + matched = acllas_eval_one_role( role, &lasinfo); + + } + + if ( matched == ACL_TRUE ) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another role will evaluate to TRUE */ + got_undefined = 1; + } + } + /* Nothing matched -- try the next DN */ + role = end_dn; + + } /* end of while */ + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for roledn evaluation.\n"); + } + + slapi_ch_free ((void**) &roles); + return rc; +} +/*************************************************************************** +* +* DS_LASUserDnAttrEval +* +* +* Input: +* attr_name The string "userdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +struct userdnattr_info { + char *attr; + int result; + char *clientdn; +}; +#define ACLLAS_MAX_LEVELS 10 +int +DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *n_currEntryDn = NULL; + char *s_attrName, *attrName; + char *ptr; + int matched; + int rc, len, i; + char *val; + Slapi_Attr *a; + int levels[ACLLAS_MAX_LEVELS]; + int numOflevels =0; + struct userdnattr_info info; + char *attrs[2] = { LDAP_ALL_USER_ATTRS, NULL }; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_USERDNATTR, "DS_LASUserDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* + ** The userdnAttr syntax is + ** userdnattr = <attribute> or + ** userdnattr = parent[0,2,4].attribute" + ** Ex: + ** userdnattr = manager; or + ** userdnattr = "parent[0,2,4].manager"; + ** + ** Here 0 means current level, 2 means grandfather and + ** 4 (great great grandfather) + ** + ** The function of this LAS is to compare the value of the + ** attribute in the Slapi_Entry with the "userdn". + ** + ** Ex: userdn: "cn=prasanta, o= netscape, c= us" + ** and in the Slapi_Entry the manager attribute has + ** manager = <value>. Compare the userdn with manager.value to + ** determine the result. + ** + */ + s_attrName = attrName = slapi_ch_strdup (attr_pattern); + + /* ignore leading/trailing whitespace */ + while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName); + len = strlen(attrName); + ptr = attrName+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + + + /* See if we have a parent[2].attr" rule */ + if ( (ptr = strstr(attrName, "parent[")) != NULL) { + char *word, *str, *next; + + numOflevels = 0; + n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + str = attrName; + + word = ldap_utf8strtok_r(str, "[],. ",&next); + /* The first word is "parent[" and so it's not important */ + + while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) { + if (ldap_utf8isdigit(word)) { + while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word); + if (numOflevels < ACLLAS_MAX_LEVELS) + levels[numOflevels++] = atoi (word); + else { + /* + * Here, ignore the extra levels..it's really + * a syntax error which should have been ruled out at parse time + */ + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "DS_LASUserDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n", + ACLLAS_MAX_LEVELS); + } + } else { + /* Must be the attr name. We can goof of by + ** having parent[1,2,a] but then you have to be + ** stupid to do that. + */ + char *p = word; + if (*--p == '.') { + attrName = word; + break; + } + } + } + info.attr = attrName; + info.clientdn = lasinfo.clientDn; + info.result = 0; + } else { + levels[0] = 0; + numOflevels = 1; + + } + + /* No attribute name specified--it's a syntax error and so undefined */ + if (attrName == NULL ) { + slapi_ch_free ( (void**) &s_attrName); + return LAS_EVAL_FAIL; + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName, 0,0); + matched = ACL_FALSE; + for (i=0; i < numOflevels; i++) { + if ( levels[i] == 0 ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int j; + + /* + * For the add operation, the resource itself (level 0) + * must never be allowed to grant access-- + * This is because access would be granted based on a value + * of an attribute in the new entry--security hole. + * + */ + + if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL info: userdnAttr does not allow ADD permission at level 0.\n"); + got_undefined = 1; + continue; + } + slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &a); + if ( NULL == a ) continue; + j= slapi_attr_first_value ( a,&sval ); + while ( j != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + /* Here if atleast 1 value matches then we are done.*/ + val = slapi_dn_normalize ( + slapi_ch_strdup( attrVal->bv_val)); + + if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)lasinfo.clientDn ) == 0) { + char ebuf [ BUFSIZ ]; + /* Wow it matches */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "userdnAttr matches(%s, %s) level (%d)\n", + val, + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo.clientDn, ebuf), + 0); + matched = ACL_TRUE; + slapi_ch_free ( (void **) &val); + break; + } + slapi_ch_free ( (void**) &val); + j = slapi_attr_next_value ( a, j, &sval ); + } + } else { + char *p_dn; /* parent dn */ + + p_dn = acllas__dn_parent (n_currEntryDn, levels[i]); + if (p_dn == NULL) continue; + + /* use new search internal API */ + { + Slapi_PBlock *aPb = slapi_pblock_new (); + + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + p_dn, + LDAP_SCOPE_BASE, + "objectclass=*", + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__verify_client, + NULL /* referral_callback */); + slapi_pblock_destroy(aPb); + } + + /* + * Currently info.result is boolean so + * we do not need to check for ACL_DONT_KNOW + */ + if (info.result) { + matched = ACL_TRUE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "userdnAttr matches at level (%d)\n", levels[i]); + } + } + if (matched == ACL_TRUE) { + break; + } + } + + slapi_ch_free ( (void **) &s_attrName); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for userdnattr evaluation.\n"); + } + + return rc; +} +/*************************************************************************** +* +* DS_LASAuthMethodEval +* +* +* Input: +* attr_name The string "authmethod" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASAuthMethodEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *attr; + char *ptr; + int len; + int matched; + int rc; + char *s = NULL; + lasInfo lasinfo; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_AUTHMETHOD, "DS_LASAuthMethodEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + attr = attr_pattern; + + matched = ACL_FALSE; + /* ignore leading whitespace */ + s = strstr (attr, SLAPD_AUTH_SASL); + if ( s) { + s +=4; + attr = s; + } + + while(ldap_utf8isspace(attr)) LDAP_UTF8INC(attr); + len = strlen(attr); + ptr = attr+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASAuthMethodEval:authtype:%s authmethod:%s\n", + lasinfo.authType, attr); + + /* None method means, we don't care -- otherwise we care */ + if ((strcasecmp(attr, "none") == 0) || + (strcasecmp(attr, lasinfo.authType) == 0)) { + matched = ACL_TRUE; + } + + if ( matched == ACL_TRUE || matched == ACL_FALSE) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for authmethod evaluation.\n"); + } + + return rc; +} + +/**************************************************************************** +* Struct to evaluate and keep the current members being evaluated +* +* 0 1 2 3 4 5 +* member: [a,b,c,d,e,f] +* c_idx may point to 2 i.e to "c" if "c" is being evaluated to +* see if any of "c" members is the clientDN. +* lu_idx points to the last used spot i.e 5. +* lu_idx++ is the next free spot. +* +* We allocate ACLLAS_MAX_GRP_MEMBER ptr first and then we add if it +* is required. +* +***************************************************************************/ +#define ACLLAS_MAX_GRP_MEMBER 50 +struct member_info +{ + char *member; /* member DN */ + struct member_info *parent; /* parent of this member */ +} member_info; + +struct eval_info +{ + int result; /* result status */ + char *userDN; /* client's normalized DN */ + int c_idx; /* Index to the current member being processed */ + int lu_idx; /* Index to the slot where the last member is stored */ + char **member; /* mmebers list */ + struct member_info **memberInfo;/* array of memberInfo */ + CERTCertificate *clientCert; /* ptr to cert */ + struct acl_pblock *aclpb; /*aclpblock */ +} eval_info; + +static void +dump_member_info ( struct member_info *minfo, char *buf ) +{ + if ( minfo ) + { + if ( minfo->parent ) + { + dump_member_info ( minfo->parent, buf ); + } + else + { + strcat ( buf, "<nil>" ); + } + strcat ( buf, "->" ); + strcat ( buf, minfo->member ); + } +} + +static void +dump_eval_info (char *caller, struct eval_info *info, int idx) +{ + char buf[1024]; + int len; + int i; + + if ( idx < 0 ) + { + sprintf ( buf, "\nuserDN=\"%s\"\nmember=", info->userDN); + if (info->member) + { + len = strlen (buf); + sprintf ( &(buf[len]), "\"%s\"", info->member ); + } + len = strlen (buf); + sprintf ( &(buf[len]), "\nmemberinfo[%d]-[%d]:", info->c_idx, info->lu_idx ); + if ( info->memberInfo ) + for (i = 0; i <= info->lu_idx; i++) + { + len = strlen(buf); + sprintf ( &buf[len], "\n [%d]: ", i ); + dump_member_info ( info->memberInfo[i], buf ); + } + slapi_log_error ( SLAPI_LOG_FATAL, NULL, "\n======== candidate member info in eval_info ========%s\n\n", buf ); + } + else + { + sprintf (buf, "evaluated candidate [%d]=", idx); + switch (info->result) + { + case ACL_TRUE: + strcat (buf, "ACL_TRUE\n"); + break; + case ACL_FALSE: + strcat (buf, "ACL_FALSE\n"); + break; + case ACL_DONT_KNOW: + strcat (buf, "ACL_DONT_KNOW\n"); + break; + default: + len = strlen (buf); + sprintf ( &(buf[len]), "%d\n", info->result ); + break; + } + dump_member_info ( info->memberInfo[idx], buf ); + slapi_log_error ( SLAPI_LOG_FATAL, NULL, "%s\n", buf ); + } +} + + +/*************************************************************************** +* +* acllas__user_ismember_of_group +* +* Check if the user is a member of the group and nested groups.. +* +* Input: +* char *groupdn - DN of the group +* char *clientDN - Dn of the client +* +* Returns: +* ACL_TRUE - the user is a member of the group. +* ACL_FALSE - Not a member +* ACL_DONT_KNOW - Any errors eg. resource limits exceeded and we could +* not compelte the evaluation. +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +acllas__user_ismember_of_group( struct acl_pblock *aclpb, + char* groupDN, + char* clientDN, + int cache_status, + CERTCertificate *clientCert) +{ + + + char *attrs[5]; + char *currDN; + int i,j; + int result = ACL_FALSE; + struct eval_info info; + int nesting_level; + int numOfMembersAtCurrentLevel; + int numOfMembersVisited; + int totalMembersVisited; + int numOfMembers; + int max_nestlevel; + int max_memberlimit; + aclUserGroup *u_group; + char ebuf [ BUFSIZ ]; + struct member_info *groupMember = NULL; + struct member_info *parentGroup = NULL; + + /* + ** First, Let's look thru the cached list and determine if the client is + ** a member of the cached list of groups. + */ + if ( (u_group = aclg_get_usersGroup ( aclpb , clientDN )) == NULL) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Failed to find/allocate a usergroup--aborting evaluation\n", 0, 0); + return(ACL_DONT_KNOW); + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluating user %s in group %s?\n", + clientDN, groupDN ); + + /* Before I start using, get a reader lock on the group cache */ + aclg_lock_groupCache ( 1 /* reader */ ); + for ( i= 0; i < u_group->aclug_numof_member_group; i++) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "-- In %s\n", + u_group->aclug_member_groups[i] ); + if ( slapi_utf8casecmp((ACLUCHP)groupDN, (ACLUCHP)u_group->aclug_member_groups[i]) == 0){ + aclg_unlock_groupCache ( 1 /* reader */ ); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_TRUE\n"); + return ACL_TRUE; + } + } + + /* see if we know the client is not a member of a group. */ + for ( i= 0; i < u_group->aclug_numof_notmember_group; i++) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "-- Not in %s\n", + u_group->aclug_notmember_groups[i] ); + if ( slapi_utf8casecmp((ACLUCHP)groupDN, (ACLUCHP)u_group->aclug_notmember_groups[i]) == 0){ + aclg_unlock_groupCache ( 1 /* reader */ ); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_FALSE\n"); + return ACL_FALSE; + } + } + + /* + ** That means we didn't find the the group in the cache. -- we have to add it + ** so no need for READ lock - need to get a WRITE lock. We will get it just before + ** modifying it. + */ + aclg_unlock_groupCache ( 1 /* reader */ ); + + /* Indicate the initialization handler -- this module will be + ** called by the backend to evaluate the entry. + */ + info.result = ACL_FALSE; + if (clientDN && *clientDN != '\0') + info.userDN = clientDN; + else + info.userDN = NULL; + + info.c_idx = 0; + info.memberInfo = (struct member_info **) slapi_ch_malloc (ACLLAS_MAX_GRP_MEMBER * sizeof(struct member_info *)); + groupMember = (struct member_info *) slapi_ch_malloc ( sizeof (struct member_info) ); + groupMember->member = slapi_ch_strdup(groupDN); + groupMember->parent = NULL; + info.memberInfo[0] = groupMember; + info.lu_idx = 0; + + attrs[0] = type_member; + attrs[1] = type_uniquemember; + attrs[2] = type_memberURL; + attrs[3] = type_memberCert; + attrs[4] = NULL; + + currDN = groupMember->member; + + /* nesting level is 0 to begin with */ + nesting_level = 0; + numOfMembersVisited = 0; + totalMembersVisited = 0; + numOfMembersAtCurrentLevel = 1; + + if (clientCert) + info.clientCert = clientCert; + else + info.clientCert = NULL; + info.aclpb = aclpb; + + max_memberlimit = aclpb->aclpb_max_member_sizelimit; + max_nestlevel = aclpb->aclpb_max_nesting_level; + + /* dump_eval_info ( "acllas__user_ismember_of_group", &info, -1 ); */ + +eval_another_member: + + numOfMembers = info.lu_idx - info.c_idx; + + /* Use new search internal API */ + { + Slapi_PBlock * aPb = slapi_pblock_new (); + + /* + * This search may NOT be chained--we demand that group + * definition be local. + */ + slapi_search_internal_set_pb ( aPb, + currDN, + LDAP_SCOPE_BASE, + filter_groups, + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__handle_group_entry, + NULL /* referral_callback */); + + if ( info.result == ACL_TRUE ) + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"-- In %s\n", info.memberInfo[info.c_idx]->member ); + else if ( info.result == ACL_FALSE ) + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"-- Not in %s\n", info.memberInfo[info.c_idx]->member ); + + slapi_pblock_destroy (aPb); + } + + if (info.result == ACL_TRUE) { + /* + ** that means the client is a member of the + ** group or one of the nested groups. We are done. + */ + result = ACL_TRUE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_TRUE\n"); + goto free_and_return; + } + numOfMembersVisited++; + + if (numOfMembersVisited == numOfMembersAtCurrentLevel) { + /* This means we have looked at all the members for this level */ + numOfMembersVisited = 0; + + /* Now we are ready to look at the next level */ + nesting_level++; + + /* So, far we have visited ... */ + totalMembersVisited += numOfMembersAtCurrentLevel; + + /* How many members in the next level ? */ + numOfMembersAtCurrentLevel = + info.lu_idx - totalMembersVisited +1; + } + + if ((nesting_level > max_nestlevel)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "GroupEval:Member not found within the allowed nesting level (Allowed:%d Looked at:%d)\n", + max_nestlevel, nesting_level, 0); + + result = ACL_DONT_KNOW; /* don't try to cache info based on this result */ + goto free_and_return; + } + + /* limit of -1 means "no limit */ + if (info.c_idx > max_memberlimit && + max_memberlimit != -1 ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "GroupEval:Looked at too many entries:(%d, %d)\n", + info.c_idx, info.lu_idx,0); + result = ACL_DONT_KNOW; /* don't try to cache info based on this result */ + goto free_and_return; + } + if (info.lu_idx > info.c_idx) { + if (numOfMembers == (info.lu_idx - info.c_idx)) { + /* That means it's not a GROUP. It is just another + ** useless member which doesn't match. Remove the BAD dude. + */ + groupMember = info.memberInfo[info.c_idx]; + + if (groupMember ) { + if ( groupMember->member ) slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + info.memberInfo[info.c_idx] = NULL; + } + } + info.c_idx++; + + /* Go thru the stack and see if we have already + ** evaluated this group. If we have, then skip it. + */ + while (1) { + int evalNext=0; + int j; + if (info.c_idx > info.lu_idx) { + /* That means we have crossed the limit. We + ** may end of in this situation if we + ** have circular groups + */ + info.c_idx = info.lu_idx; + goto free_and_return; + } + + /* Break out of the loop if we have searched to the end */ + groupMember = info.memberInfo[info.c_idx]; + if ( (NULL == groupMember) || ((currDN = groupMember->member)!= NULL)) + break; + + for (j = 0; j < info.c_idx; j++) { + groupMember = info.memberInfo[j]; + if (groupMember->member && + (slapi_utf8casecmp((ACLUCHP)currDN, (ACLUCHP)groupMember->member) == 0)) { + /* Don't need the duplicate */ + groupMember = info.memberInfo[info.c_idx]; + slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + info.memberInfo[info.c_idx] = NULL; + info.c_idx++; + evalNext=1; + break; + } + } + if (!evalNext) break; + } + /* Make sure that we have a valid DN to chug along */ + groupMember = info.memberInfo[info.c_idx]; + if ((info.c_idx <= info.lu_idx) && ((currDN = groupMember->member) != NULL)) + goto eval_another_member; + } + +free_and_return: + /* Remove the unnecessary members from the list which + ** we might have accumulated during the last execution + ** and we don't need to look at them. + */ + i = info.c_idx; + i++; + while (i <= info.lu_idx) { + groupMember = info.memberInfo[i]; + slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + info.memberInfo[i] = NULL; + i++; + } + + /* + ** Now we have a list which has all the groups + ** which we need to cache + */ + info.lu_idx = info.c_idx; + + /* since we are updating the groupcache, get a write lock */ + aclg_lock_groupCache ( 2 /* writer */ ); + + /* + ** Keep the result of the evaluation in the cache. + ** We have 2 lists: member_of and not_member_of. We can use this + ** cached information next time we evaluate groups. + */ + if (result == ACL_TRUE && + (cache_status & ACLLAS_CACHE_MEMBER_GROUPS)) { + int ngr = 0; + + /* get the last group which the user is a member of */ + groupMember = info.memberInfo[info.c_idx]; + + while ( groupMember ) { + int already_cached = 0; + + parentGroup = groupMember->parent; + for (j=0; j < u_group->aclug_numof_member_group;j++){ + if (slapi_utf8casecmp( (ACLUCHP)groupMember->member, + (ACLUCHP)u_group->aclug_member_groups[j]) == 0) { + already_cached = 1; + break; + } + } + if (already_cached) { + groupMember = parentGroup; + parentGroup = NULL; + continue; + } + + ngr = u_group->aclug_numof_member_group++; + if (u_group->aclug_numof_member_group >= + u_group->aclug_member_group_size){ + u_group->aclug_member_groups = + (char **) slapi_ch_realloc ( + (void *) u_group->aclug_member_groups, + (u_group->aclug_member_group_size + + ACLUG_INCR_GROUPS_LIST) * + sizeof (char *)); + u_group->aclug_member_group_size += + ACLUG_INCR_GROUPS_LIST; + } + u_group->aclug_member_groups[ngr] = slapi_ch_strdup ( groupMember->member ); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Adding Group (%s) ParentGroup (%s) to the IN GROUP List\n", + groupMember->member , parentGroup ? parentGroup->member: "NULL"); + + groupMember = parentGroup; + parentGroup = NULL; + } + } else if (result == ACL_FALSE && + (cache_status & ACLLAS_CACHE_NOT_MEMBER_GROUPS)) { + int ngr = 0; + + /* NOT IN THE GROUP LIST */ + /* get the last group which the user is a member of */ + groupMember = info.memberInfo[info.c_idx]; + + while ( groupMember ) { + int already_cached = 0; + + parentGroup = groupMember->parent; + for (j=0; j < u_group->aclug_numof_notmember_group;j++){ + if (slapi_utf8casecmp( (ACLUCHP)groupMember->member, + (ACLUCHP)u_group->aclug_notmember_groups[j]) == 0) { + already_cached = 1; + break; + } + } + if (already_cached) { + groupMember = parentGroup; + parentGroup = NULL; + continue; + } + + ngr = u_group->aclug_numof_notmember_group++; + if (u_group->aclug_numof_notmember_group >= + u_group->aclug_notmember_group_size){ + u_group->aclug_notmember_groups = + (char **) slapi_ch_realloc ( + (void *) u_group->aclug_notmember_groups, + (u_group->aclug_notmember_group_size + + ACLUG_INCR_GROUPS_LIST) * + sizeof (char *)); + u_group->aclug_notmember_group_size += + ACLUG_INCR_GROUPS_LIST; + } + u_group->aclug_notmember_groups[ngr] = slapi_ch_strdup ( groupMember->member ); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Adding Group (%s) ParentGroup (%s) to the NOT IN GROUP List\n", + groupMember->member , parentGroup ? parentGroup->member: "NULL"); + + groupMember = parentGroup; + parentGroup = NULL; + } + } else if ( result == ACL_DONT_KNOW ) { + + /* + * We terminated the search without reaching a conclusion--so + * don't cache any info based on this evaluation. + */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_DONT_KNOW\n"); + } + + /* Unlock the group cache, we are done with updating */ + aclg_unlock_groupCache ( 2 /* writer */ ); + + for (i=0; i <= info.lu_idx; i++) { + groupMember = info.memberInfo[i]; + if ( NULL == groupMember ) continue; + + slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + } + + /* free the pointer array.*/ + slapi_ch_free ( (void **) &info.memberInfo); + return result; +} + +/*************************************************************************** +* +* acllas__handle_group_entry +* +* handler called. Compares the userdn value and determines if it's +* a member of not. +* +* Input: +* +* +* Returns: +* +* Error Handling: +* +**************************************************************************/ +static int +acllas__handle_group_entry (Slapi_Entry* e, void *callback_data) +{ + struct eval_info *info; + Slapi_Attr *currAttr, *nextAttr; + char *n_dn, *attrType; + short n; + int i; + + info = (struct eval_info *) callback_data; + info->result = ACL_FALSE; + + if (e == NULL) { + return 0; + } + + slapi_entry_first_attr ( e, &currAttr); + if ( NULL == currAttr ) return 0; + + slapi_attr_get_type ( currAttr, &attrType ); + if (NULL == attrType ) return 0; + + do { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + + if ((strcasecmp (attrType, type_member) == 0) || + (strcasecmp (attrType, type_uniquemember) == 0 )) { + + i = slapi_attr_first_value ( currAttr,&sval ); + while ( i != -1 ) { + struct member_info *groupMember = NULL; + attrVal = slapi_value_get_berval ( sval ); + n_dn = slapi_dn_normalize ( slapi_ch_strdup( attrVal->bv_val)); + info->lu_idx++; + n = info->lu_idx; + if (!(n % ACLLAS_MAX_GRP_MEMBER)) { + info->memberInfo = (struct member_info **) slapi_ch_realloc( + (void *) info->memberInfo, + (n+ACLLAS_MAX_GRP_MEMBER) * + sizeof(struct eval_info *)); + } + + /* allocate the space for the member and attch it to the list */ + groupMember = (struct member_info *) slapi_ch_malloc ( sizeof ( struct member_info ) ); + groupMember->member = n_dn; + groupMember->parent = info->memberInfo[info->c_idx]; + info->memberInfo[n] = groupMember; + + if (info->userDN && + slapi_utf8casecmp((ACLUCHP)n_dn, (ACLUCHP)info->userDN) == 0) { + info->result = ACL_TRUE; + return 0; + } + i = slapi_attr_next_value ( currAttr, i, &sval ); + } + /* Evaluate Dynamic groups */ + } else if (strcasecmp ( attrType, type_memberURL) == 0) { + char *memberURL, *savURL; + + if (!info->userDN) continue; + + i= slapi_attr_first_value ( currAttr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + /* + * memberURL may start with "ldap:///" or "ldap://host:port" + * ldap://localhost:11000/o=ace industry,c=us?? + * or + * ldap:///o=ace industry,c=us?? + */ + if (strncasecmp( attrVal->bv_val, "ldap://",7) == 0 || + strncasecmp( attrVal->bv_val, "ldaps://",8) == 0) { + savURL = memberURL = slapi_ch_strdup ( attrVal->bv_val); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL Group Eval:MemberURL:%s\n", memberURL); + info->result = acllas__client_match_URL ( + info->aclpb, + info->userDN, + memberURL); + slapi_ch_free ( (void**) &savURL); + if (info->result == ACL_TRUE) + return 0; + } else { + /* This means that the URL is ill-formed */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL Group Eval:Badly Formed MemberURL:%s\n", attrVal->bv_val); + } + i = slapi_attr_next_value ( currAttr, i, &sval ); + } + /* Evaluate Fortezza groups */ + } else if ((strcasecmp (attrType, type_memberCert) == 0) ) { + /* Do we have the certificate around */ + if (!info->clientCert) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + " acllas__handle_group_entry:Client Cert missing\n" ); + continue; + } + i = slapi_attr_first_value ( currAttr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + if (ldapu_member_certificate_match ( + info->clientCert, + attrVal->bv_val) == LDAP_SUCCESS) { + info->result = ACL_TRUE; + return 0; + } + i = slapi_attr_next_value ( currAttr, i, &sval ); + } + } + + attrType = NULL; + /* get the next attr */ + slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( NULL == nextAttr ) break; + + currAttr = nextAttr; + slapi_attr_get_type ( currAttr, &attrType ); + + } while ( NULL != attrType ); + + return 0; +} +/*************************************************************************** +* +* DS_LASGroupDnAttrEval +* +* +* Input: +* attr_name The string "groupdnattr" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +struct groupdnattr_info +{ + char *attrName; /* name of the attribute */ + int numofGroups; /* number of groups */ + char **member; +}; +int +DS_LASGroupDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *s_attrName = NULL; + char *attrName; + char *ptr; + int matched; + int rc; + int len; + Slapi_Attr *attr; + int levels[ACLLAS_MAX_LEVELS]; + int numOflevels = 0; + char *n_currEntryDn = NULL; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_GROUPDNATTR, "DS_LASGroupDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* For anonymous client, the answer is XXX come back to this */ + if ( lasinfo.anomUser ) + return LAS_EVAL_FALSE; + + /* + ** The groupdnAttr syntax is + ** groupdnattr = <attribute> + ** Ex: + ** groupdnattr = SIEmanager; + ** + ** The function of this LAS is to find out if the client belongs + ** to any group that is specified in the attr. + */ + attrName = attr_pattern; + if (strstr(attrName, LDAP_URL_prefix)) { + char *s; + + + /* In this case "grppupdnattr="ldap:///base??attr" */ + + if ((s = strstr (attrName, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (attrName, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (attrName, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( attrName, &lasinfo, + ACL_EVAL_GROUPDNATTR); + } else{ + + matched = acllas__eval_memberGroupDnAttr(attrName, + lasinfo.resourceEntry, + lasinfo.clientDn, + lasinfo.aclpb); + } + if ( matched == ACL_DONT_KNOW) { + got_undefined = 1; + } + } else { + int i; + char *n_groupdn; + + /* ignore leading/trailing whitespace */ + while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName); + len = strlen(attrName); + ptr = attrName+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName, 0,0); + + /* See if we have a parent[2].attr" rule */ + if ( (ptr = strstr(attrName, "parent[")) != NULL) { + char *word, *str, *next; + + numOflevels = 0; + n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry ) ; + s_attrName = attrName = slapi_ch_strdup ( attr_pattern ); + str = attrName; + + word = ldap_utf8strtok_r(str, "[],. ",&next); + /* The first word is "parent[" and so it's not important */ + + while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) { + if (ldap_utf8isdigit(word)) { + while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word); + if (numOflevels < ACLLAS_MAX_LEVELS) + levels[numOflevels++] = atoi (word); + else { + /* + * Here, ignore the extra levels..it's really + * a syntax error which should have been ruled out at parse time + */ + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "DS_LASGroupDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n", + ACLLAS_MAX_LEVELS,0,0); + } + } else { + /* Must be the attr name. We can goof of by + ** having parent[1,2,a] but then you have to be + ** stupid to do that. + */ + char *p = word; + if (*--p == '.') { + attrName = word; + break; + } + } + } + } else { + levels[0] = 0; + numOflevels = 1; + } + + matched = ACL_FALSE; + for (i=0; i < numOflevels; i++) { + if ( levels[i] == 0 ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int attr_i; + + /* + * For the add operation, the resource itself (level 0) + * must never be allowed to grant access-- + * This is because access would be granted based on a value + * of an attribute in the new entry--security hole. + * XXX is this therefore FALSE or DONT_KNOW ? + */ + + if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL info: groupdnAttr does not allow ADD permission at level 0.\n"); + got_undefined = 1; + continue; + } + slapi_entry_attr_find ( lasinfo.resourceEntry, attrName, &attr); + if ( !attr) continue; + attr_i= slapi_attr_first_value ( attr,&sval ); + while ( attr_i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + n_groupdn = slapi_dn_normalize( + slapi_ch_strdup( attrVal->bv_val)); + matched = acllas__user_ismember_of_group ( + lasinfo.aclpb, n_groupdn, lasinfo.clientDn, + ACLLAS_CACHE_MEMBER_GROUPS, + lasinfo.aclpb->aclpb_clientcert); + slapi_ch_free ( (void **) &n_groupdn); + if (matched == ACL_TRUE ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "groupdnattr matches at level (%d)\n", levels[i]); + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + attr_i= slapi_attr_next_value ( attr, attr_i, &sval ); + } + } else { + char *p_dn; + struct groupdnattr_info info; + char *attrs[2]; + int j; + + info.numofGroups = 0; + attrs[0] = info.attrName = attrName; + attrs[1] = NULL; + + p_dn = acllas__dn_parent (n_currEntryDn, levels[i]); + + if (p_dn == NULL) continue; + + /* Use new search internal API */ + { + + Slapi_PBlock *aPb = slapi_pblock_new (); + /* + * This search may NOT be chained--if the user's definition is + * remote and the group is dynamic and the user entry + * changes then we would not notice--so don't go + * find the user entry in the first place. + */ + slapi_search_internal_set_pb ( aPb, + p_dn, + LDAP_SCOPE_BASE, + "objectclass=*", + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__get_members, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if (info.numofGroups <= 0) { + continue; + } + for (j=0; j <info.numofGroups; j++) { + if (slapi_utf8casecmp((ACLUCHP)info.member[j], + (ACLUCHP)lasinfo.clientDn) == 0) { + matched = ACL_TRUE; + break; + } + matched = acllas__user_ismember_of_group ( + lasinfo.aclpb, info.member[j], + lasinfo.clientDn, ACLLAS_CACHE_ALL_GROUPS, + lasinfo.aclpb->aclpb_clientcert); + if (matched == ACL_TRUE) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + } + /* Deallocate the member array and the member struct */ + for (j=0; j < info.numofGroups; j++) + slapi_ch_free ((void **) &info.member[j]); + slapi_ch_free ((void **) &info.member); + } + if (matched == ACL_TRUE) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "groupdnattr matches at level (%d)\n", levels[i]); + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group at another level + * will evaluate to TRUE. + */ + got_undefined = 1; + } + + } /* NumofLevels */ + } + if (s_attrName) slapi_ch_free ((void**) &s_attrName ); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for groupdnattr evaluation.\n"); + } + + return rc; +} + +/* + * acllas__eval_memberGroupDnAttr + * + * return ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW + * + * Inverse group evaluation. Find all the groups that the user is a + * member of. Find all teh groups that contain those groups. Do an + * upward nested level search. By the end of it, we will know all the + * groups that the clinet is a member of under that search scope. + * + * This model seems to be very fast if we have few groups at the + * leaf level. + * + */ +static int +acllas__eval_memberGroupDnAttr (char *attrName, Slapi_Entry *e, + char *n_clientdn, struct acl_pblock *aclpb) +{ + + Slapi_Attr *attr; + char *s, *p; + char *str, *s_str, *base, *groupattr; + int i,j,k,matched, enumerate_groups; + aclUserGroup *u_group; + char ebuf [ BUFSIZ ]; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + + /* Parse the URL -- We can't use the ldap_url_parse() + ** we don't follow thw complete url naming scheme + */ + s_str = str = slapi_ch_strdup(attrName); + while (str && ldap_utf8isspace(str)) LDAP_UTF8INC( str ); + str +=8; + s = strchr (str, '?'); + if (s) { + p = s; + p++; + *s = '\0'; + base = str; + s = strchr (p, '?'); + if (s) *s = '\0'; + + groupattr = p; + } else { + slapi_ch_free ( (void **)&s_str ); + return ACL_FALSE; + } + + if ( (u_group = aclg_get_usersGroup ( aclpb , n_clientdn )) == NULL) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Failed to find/allocate a usergroup--aborting evaluation\n", 0, 0); + slapi_ch_free ( (void **)&s_str ); + return(ACL_DONT_KNOW); + } + + /* + ** First find out if we have already searched this base or + ** if we are searching a subtree to an already enumerated base. + */ + enumerate_groups = 1; + for (j=0; j < aclpb->aclpb_numof_bases; j++) { + if (slapi_dn_issuffix(aclpb->aclpb_grpsearchbase[j], base)) { + enumerate_groups = 0; + break; + } + } + + + /* See if we have already enumerated all the groups which the + ** client is a member of. + */ + if (enumerate_groups) { + char filter_str[BUFSIZ]; + char *attrs[3]; + struct eval_info info; + char *curMemberDn; + int Done = 0; + int ngr, tt; + + /* Add the scope to the list of scopes */ + if (aclpb->aclpb_numof_bases >= (aclpb->aclpb_grpsearchbase_size-1)) { + aclpb->aclpb_grpsearchbase = (char **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_grpsearchbase, + (aclpb->aclpb_grpsearchbase_size + + ACLPB_INCR_BASES) * + sizeof (char *)); + aclpb->aclpb_grpsearchbase_size += ACLPB_INCR_BASES; + } + aclpb->aclpb_grpsearchbase[aclpb->aclpb_numof_bases++] = + slapi_dn_normalize(slapi_ch_strdup(base)); + + /* Set up info to do a search */ + attrs[0] = type_member; + attrs[1] = type_uniquemember; + attrs[2] = NULL; + + info.c_idx = info.lu_idx = 0; + info.member = + (char **) slapi_ch_malloc (ACLLAS_MAX_GRP_MEMBER * sizeof(char *)); + curMemberDn = n_clientdn; + + while (!Done) { + char *filter_str_ptr = &filter_str[0]; + char *new_filter_str = NULL; + int lenf = strlen(curMemberDn)<<1; + + if (lenf > (BUFSIZ - 28)) { /* 28 for "(|(uniquemember=%s)(member=%s))" */ + new_filter_str = slapi_ch_malloc(lenf + 28); + filter_str_ptr = new_filter_str; + } + + /* + ** Search the db for groups that the client is a member of. + ** Once found cache it. cache only unique groups. + */ + tt = info.lu_idx; + sprintf (filter_str_ptr,"(|(uniquemember=%s)(member=%s))", + curMemberDn, curMemberDn); + + /* Use new search internal API */ + { + Slapi_PBlock *aPb = slapi_pblock_new (); + /* + * This search may NOT be chained--we demand that group + * definition be local. + */ + slapi_search_internal_set_pb ( aPb, + base, + LDAP_SCOPE_SUBTREE, + filter_str_ptr, + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__add_allgroups, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if (new_filter_str) slapi_ch_free((void **) &new_filter_str); + + if (tt == info.lu_idx) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "currDn:(%s) \n\tNO MEMBER ADDED\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (curMemberDn, ebuf) , 0,0); + } else { + for (i=tt; i < info.lu_idx; i++) + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "currDn:(%s) \n\tADDED MEMBER[%d]=%s\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (curMemberDn, ebuf), i, info.member[i]); + } + + if (info.c_idx >= info.lu_idx) { + for (i=0; i < info.lu_idx; i++) { + int already_cached = 0; + for (j=0; j < u_group->aclug_numof_member_group; + j++){ + if (slapi_utf8casecmp( + (ACLUCHP)info.member[i], + (ACLUCHP)u_group->aclug_member_groups[j]) == 0) { + slapi_ch_free ((void **) &info.member[i] ); + info.member[i] = NULL; + already_cached = 1; + break; + } + + } + + if (already_cached) continue; + + ngr = u_group->aclug_numof_member_group++; + if (u_group->aclug_numof_member_group >= + u_group->aclug_member_group_size){ + u_group->aclug_member_groups = + (char **) slapi_ch_realloc ( + (void *) u_group->aclug_member_groups, + (u_group->aclug_member_group_size + + ACLUG_INCR_GROUPS_LIST) * sizeof(char *)); + + u_group->aclug_member_group_size += + ACLUG_INCR_GROUPS_LIST; + } + u_group->aclug_member_groups[ngr] = info.member[i]; + info.member[i] = NULL; + } + slapi_ch_free ((void **) &info.member); + Done = 1; + } else { + curMemberDn = info.member[info.c_idx]; + info.c_idx++; + } + } + } + + for (j=0; j < u_group->aclug_numof_member_group; j++) + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "acllas__eval_memberGroupDnAttr:GROUP[%d] IN CACHE:%s\n", + j, ACL_ESCAPE_STRING_WITH_PUNCTUATION (u_group->aclug_member_groups[j], ebuf),0); + + matched = ACL_FALSE; + slapi_entry_attr_find( e, groupattr, &attr); + if (attr == NULL) { + slapi_ch_free ( (void **)&s_str ); + return ACL_FALSE; + } + { + k = slapi_attr_first_value ( attr,&sval ); + while ( k != -1 ) { + char *n_attrval; + attrVal = slapi_value_get_berval ( sval ); + n_attrval = slapi_ch_strdup( attrVal->bv_val); + n_attrval = slapi_dn_normalize (n_attrval); + + /* We support: The attribute value can be a USER or a GROUP. + ** Let's compare with the client, thi might be just an user. If it is not + ** then we test it against the list of groups. + */ + if (slapi_utf8casecmp ((ACLUCHP)n_attrval, (ACLUCHP)n_clientdn) == 0 ) { + matched = ACL_TRUE; + slapi_ch_free ( (void **)&n_attrval ); + break; + } + for (j=0; j <u_group->aclug_numof_member_group; j++) { + if ( slapi_utf8casecmp((ACLUCHP)n_attrval, + (ACLUCHP)u_group->aclug_member_groups[j]) == 0) { + matched = ACL_TRUE; + break; + } + } + slapi_ch_free ( (void **)&n_attrval ); + if (matched == ACL_TRUE) break; + k= slapi_attr_next_value ( attr, k, &sval ); + } + } + slapi_ch_free ( (void **)&s_str ); + return matched; +} + +static int +acllas__add_allgroups (Slapi_Entry* e, void *callback_data) +{ + int i, n, m; + struct eval_info *info; + char *n_dn; + + info = (struct eval_info *) callback_data; + + /* + ** Once we are here means this is a valid group. First see + ** If we have already seen this group. If not, add it to the + ** member list. + */ + n_dn = slapi_ch_strdup ( slapi_entry_get_ndn ( e ) ); + for (i=0; i < info->lu_idx; i++) { + if (slapi_utf8casecmp((ACLUCHP)n_dn, (ACLUCHP)info->member[i]) == 0) { + slapi_ch_free ( (void **) &n_dn); + return 0; + } + } + + m = info->lu_idx; + n = info->lu_idx++; + if (!(n % ACLLAS_MAX_GRP_MEMBER)) { + info->member = (char **) slapi_ch_realloc ( + (void *) info->member, + (n+ACLLAS_MAX_GRP_MEMBER) * sizeof(char *)); + } + info->member[m] = n_dn; + return 0; +} +/* + * + * acllas__dn_parent + * + * This code should belong to dn.c. However this is specific to acl and I had + * 2 choices 1) create a new API or 2) reuse the slapi_dN_parent + * + * Returns a ptr to the parent based on the level. + * + */ +#define DNSEPARATOR(c) (c == ',' || c == ';') +static char* +acllas__dn_parent( char *dn, int level) +{ + char *s, *dnstr; + int inquote; + int curLevel; + int lastLoop = 0; + + if ( dn == NULL || *dn == '\0' ) { + return( NULL ); + } + + /* An X.500-style name, which looks like foo=bar,sha=baz,... */ + /* Do we have any dn seprator or not */ + if ((strchr(dn,',') == NULL) && (strchr(dn,';') == NULL)) + return (NULL); + + inquote = 0; + curLevel = 1; + dnstr = dn; + while ( curLevel <= level) { + if (lastLoop) break; + if (curLevel == level) lastLoop = 1; + for ( s = dnstr; *s; s++ ) { + if ( *s == '\\' ) { + if ( *(s + 1) ) + s++; + continue; + } + if ( inquote ) { + if ( *s == '"' ) + inquote = 0; + } else { + if ( *s == '"' ) + inquote = 1; + else if ( DNSEPARATOR( *s ) ) { + if (curLevel == level) + return( s + 1 ); + dnstr = s + 1; + curLevel++; + break; + } + } + } + if ( *s == '\0') { + /* Got to the end of the string without reaching level, + * so return NULL. + */ + return(NULL); + } + } + + return( NULL ); +} +/* + * acllas__verify_client + * + * returns 1 if the attribute exists in the entry and + * it's value is equal to the client Dn. + * If the attribute is not in the entry, or it is and the + * value differs from the clientDn then returns FALSE. + * + * Verify if client's DN is stored in the attrbute or not. + * This is a handler from a search being done at + * DS_LASUserDnAttrEval(). + * + */ +static int +acllas__verify_client (Slapi_Entry* e, void *callback_data) +{ + + Slapi_Attr *attr; + char *val; + struct userdnattr_info *info; + Slapi_Value *sval; + const struct berval *attrVal; + int i; + + info = (struct userdnattr_info *) callback_data; + + slapi_entry_attr_find( e, info->attr, &attr); + if (attr == NULL) return 0; + + i = slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + val = slapi_dn_normalize ( + slapi_ch_strdup(attrVal->bv_val)); + + if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)info->clientdn ) == 0) { + info->result = 1; + slapi_ch_free ( (void **) &val); + return 0; + } + slapi_ch_free ( (void **) &val); + i = slapi_attr_next_value ( attr, i, &sval ); + } + return 0; +} +/* + * + * acllas__get_members + * + * Collects all the values of the specified attribute which should be group names. + */ +static int +acllas__get_members (Slapi_Entry* e, void *callback_data) +{ + + Slapi_Attr *attr; + struct groupdnattr_info *info; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + info = (struct groupdnattr_info *) callback_data; + slapi_entry_attr_find (e, info->attrName, &attr); + if ( !attr ) return 0; + + slapi_attr_get_numvalues ( attr, &info->numofGroups ); + + info->member = (char **) slapi_ch_malloc (info->numofGroups * sizeof(char *)); + i = slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal =slapi_value_get_berval ( sval ); + info->member[i] = slapi_dn_normalize ( slapi_ch_strdup(attrVal->bv_val)); + i = slapi_attr_next_value ( attr, i, &sval ); + } + return 0; +} + +/* + * DS_LASUserAttrEval + * LAS to evaluate the userattr rule + * + * userAttr = "attrName#Type" + * + * <Type> ::= "USERDN" | "GROUPDN" | "ROLEDN" | "LDAPURL" | <value> + * <value>::== <any printable String> + * + * Example: + * userAttr = "manager#USERDN" --- same as userdnattr + * userAttr = "owner#GROUPDN" --- same as groupdnattr + * = "ldap:///o=sun.com?owner#GROUPDN + * userAttr = "attr#ROLEDN" --- The value of attr contains a roledn + * userAttr = "myattr#LDAPURL" --- The value contains a LDAP URL + * which can have scope and filter + * bits. + * userAttr = "OU#Directory Server" + * --- In this case the client's OU and the + * resource entry's OU must have + * "Directory Server" value. + * + * Returns: + * retcode The usual LAS return codes. + */ +int +DS_LASUserAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *attrName; + char *attrValue = NULL; + int rc; + int matched = ACL_FALSE; + char *p; + int URLAttrRule = 0; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_USERATTR, "DS_LASUserAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* Which rule are we evaluating ? */ + attrName = slapi_ch_strdup (attr_pattern ); + if ( NULL == (p = strchr ( attrName, '#' ))) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserAttrEval:Invalid value(%s)\n", attr_pattern); + slapi_ch_free ( (void **) &attrName ); + return LAS_EVAL_FAIL; + } + attrValue = p; + attrValue++; /* skip the # */ + *p = '\0'; /* null terminate the attr name */ + + if ( 0 == strncasecmp ( attrValue, "USERDN", 6)) { + matched = DS_LASUserDnAttrEval (errp,DS_LAS_USERDNATTR, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } else if ( 0 == strncasecmp ( attrValue, "GROUPDN", 7)) { + matched = DS_LASGroupDnAttrEval (errp,DS_LAS_GROUPDNATTR, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } else if ( 0 == strncasecmp ( attrValue, "LDAPURL", 7) ) { + URLAttrRule = 1; + } else if ( 0 == strncasecmp ( attrValue, "ROLEDN", 6)) { + matched = DS_LASRoleDnAttrEval (errp,DS_LAS_ROLEDN, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } + + if ( lasinfo.aclpb && ( NULL == lasinfo.aclpb->aclpb_client_entry )) { + /* SD 00/16/03 pass NULL in case the req is chained */ + char **attrs=NULL; + + + /* Use new search internal API */ + Slapi_PBlock *aPb = slapi_pblock_new (); + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + lasinfo.clientDn, + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + slapi_search_internal_callback_pb(aPb, + lasinfo.aclpb /* callback_data */, + NULL/* result_callback */, + acllas__handle_client_search, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + + } + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserAttrEval: AttrName:%s, attrVal:%s\n", attrName, attrValue ); + + if ( URLAttrRule ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + Slapi_Attr *attrs; + int i; + + /* Get the attr from the resouce entry */ + if ( 0 == slapi_entry_attr_find (lasinfo.resourceEntry, attrName, &attrs) ) { + i= slapi_attr_first_value ( attrs, &sval ); + if ( i==-1 ) { + matched = ACL_FALSE; /* Attr val not there so it's value cannot equal other one */ + goto done_acl; + } + } else { + matched = ACL_FALSE; /* Not there so it cannot equal another one */ + goto done_acl; + } + + while( matched != ACL_TRUE && (sval != NULL)) { + attrVal = slapi_value_get_berval ( sval ); + matched = acllas__client_match_URL ( lasinfo.aclpb, + lasinfo.clientDn, + attrVal->bv_val); + if ( matched != ACL_TRUE ) + i = slapi_attr_next_value ( attrs, i, &sval ); + if ( matched == ACL_DONT_KNOW ) { + got_undefined = 1; + } + }/* while */ + } else { + /* + * Here it's the userAttr = "OU#Directory Server" case. + * Allocate the Slapi_Value on the stack and init it by reference + * to avoid having to malloc and free memory. + */ + Slapi_Value v; + + slapi_value_init_string_passin(&v, attrValue); + rc = slapi_entry_attr_has_syntax_value ( lasinfo.resourceEntry, attrName, + &v ); + if (rc) { + rc = slapi_entry_attr_has_syntax_value ( + lasinfo.aclpb->aclpb_client_entry, + attrName, &v ); + if (rc) matched = ACL_TRUE; + } + /* Nothing to free--cool */ + } + + /* + * Find out what the result is, in + * this case matched is one of ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW + * and got_undefined says whether a logical term evaluated to ACL_DONT_KNOW. + * + */ +done_acl: + if ( matched == ACL_TRUE || !got_undefined) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + } + + slapi_ch_free ( (void **) &attrName ); + return rc; + +done_las: + /* + * In this case matched is already LAS_EVAL_TRUE or LAS_EVAL_FALSE or + * LAS_EVAL_FAIL. + */ + if ( matched != LAS_EVAL_FAIL ) { + if (comparator == CMP_OP_EQ) { + rc = matched; + } else { + rc = (matched == LAS_EVAL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } + + slapi_ch_free ( (void **) &attrName ); + return rc; +} + +/* + * acllas__client_match_URL + * Match a client to a URL. + * + * Returns: + * ACL_TRUE - matched the URL + * ACL_FALSE - Sorry; no match + * + */ +static int +acllas__client_match_URL (struct acl_pblock *aclpb, char *n_clientdn, char *url ) +{ + + LDAPURLDesc *ludp; + int rc; + Slapi_Filter *f = NULL; + + + /* Get the client's entry if we don't have already */ + if ( aclpb && ( NULL == aclpb->aclpb_client_entry )) { + /* SD 00/16/03 Get every attr in case req chained */ + char **attrs=NULL; + + /* Use new search internal API */ + Slapi_PBlock * aPb = slapi_pblock_new (); + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + n_clientdn, + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + slapi_search_internal_callback_pb(aPb, + aclpb /* callback_data */, + NULL/* result_callback */, + acllas__handle_client_search, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if ( NULL == aclpb->aclpb_client_entry ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserAttrEval: Unable to get client's entry\n",0,0,0); + return ACL_FALSE; + } + + if (( rc = ldap_url_parse( url, &ludp)) != 0 ) { + return ACL_FALSE; + + } + if ( ( NULL == ludp->lud_dn) || ( NULL == ludp->lud_filter) ) { + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + + /* Normalize in place the dn */ + slapi_dn_normalize ( ludp->lud_dn ); + + /* Check the scope */ + if ( ludp->lud_scope == LDAP_SCOPE_SUBTREE ) { + if (!slapi_dn_issuffix(n_clientdn, ludp->lud_dn)) { + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + } else if ( ludp->lud_scope == LDAP_SCOPE_ONELEVEL ) { + char *parent = slapi_dn_parent (n_clientdn); + + if (slapi_utf8casecmp ((ACLUCHP)parent, (ACLUCHP)ludp->lud_dn) != 0 ) { + slapi_ch_free ( (void **) &parent); + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + slapi_ch_free ( (void **) &parent); + } else { /* default */ + if (slapi_utf8casecmp ( (ACLUCHP)n_clientdn, (ACLUCHP)ludp->lud_dn) != 0 ) { + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + + } + + + /* Convert the filter string */ + f = slapi_str2filter ( ludp->lud_filter ); + + rc = ACL_TRUE; + if (0 != slapi_vattr_filter_test ( aclpb->aclpb_pblock, + aclpb->aclpb_client_entry, f, 0 /* no acces chk */ )) + rc = ACL_FALSE; + + ldap_free_urldesc( ludp ); + slapi_filter_free ( f, 1 ) ; + + return rc; +} +static int +acllas__handle_client_search ( Slapi_Entry *e, void *callback_data ) +{ + struct acl_pblock *aclpb = (struct acl_pblock *) callback_data; + + /* If we are here means we have found the entry */ + if ( NULL == aclpb-> aclpb_client_entry) + aclpb->aclpb_client_entry = slapi_entry_dup ( e ); + return 0; +} +/* +* +* Do all the necessary setup for all the +* LASes. +* It will only fail if it's passed garbage (which should not happen) or +* if the data it needs to stock the lasinfo is not available, which +* also should not happen. +* +* +* Return value: 0 or one of these +* #define LAS_EVAL_TRUE -1 +* #define LAS_EVAL_FALSE -2 +* #define LAS_EVAL_DECLINE -3 +* #define LAS_EVAL_FAIL -4 +* #define LAS_EVAL_INVALID -5 +*/ + +static int +__acllas_setup ( NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth, char *lasType, char*lasName, lasInfo *linfo) +{ + + int rc; + memset ( linfo, 0, sizeof ( lasInfo) ); + + *cachable = 0; + *LAS_cookie = (void *)0; + + if (strcmp(attr_name, lasType) != 0) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Invalid LAS(%s)\n", lasName, attr_name); + return LAS_EVAL_INVALID; + } + + if ((comparator != CMP_OP_EQ) && (comparator != CMP_OP_NE)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Invalid comparator(%d)\n", lasName, (int)comparator); + return LAS_EVAL_INVALID; + } + + + /* Get the client DN */ + rc = ACL_GetAttribute(errp, DS_ATTR_USERDN, (void **)&linfo->clientDn, + subject, resource, auth_info, global_auth); + + if ( rc != LAS_EVAL_TRUE ) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the clientdn attribute(%d)\n",lasName, rc); + return LAS_EVAL_FAIL; + } + + /* Check if we have a user or not */ + if (linfo->clientDn) { + /* See if it's a anonymous user */ + if (*(linfo->clientDn) == '\0') + linfo->anomUser = ACL_TRUE; + } else { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s: No user\n",lasName); + return LAS_EVAL_FAIL; + } + + if ((rc = PListFindValue(subject, DS_ATTR_ENTRY, + (void **)&linfo->resourceEntry, NULL)) < 0) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the Slapi_Entry attr(%d)\n",lasName, rc); + return LAS_EVAL_FAIL; + } + + /* Get ACLPB */ + rc = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&linfo->aclpb, + subject, resource, auth_info, global_auth); + if ( rc != LAS_EVAL_TRUE ) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the ACLPB(%d)\n", lasName, rc); + return LAS_EVAL_FAIL; + } + if (NULL == attr_pattern ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:No rule value in the ACL\n", lasName); + + return LAS_EVAL_FAIL; + } + /* get the authentication type */ + if ((rc = PListFindValue(subject, DS_ATTR_AUTHTYPE, + (void **)&linfo->authType, NULL)) < 0) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the auth type(%d)\n", rc); + return LAS_EVAL_FAIL; + } + return 0; +} + +/* + * See if clientDN has role roleDN. + * Here we know the user is not anon and that the role + * is not the anyone role ie. it's actually worth invoking the roles code. +*/ + +static int acllas__user_has_role( struct acl_pblock *aclpb, + Slapi_DN *roleDN, Slapi_DN *clientDn) { + + int present = 0; + int rc = 0; + + /* Get the client's entry if we don't have already */ + if ( aclpb && ( NULL == aclpb->aclpb_client_entry )) { + /* SD 00/16/03 Get every attr in case req chained */ + char **attrs=NULL; + + /* Use new search internal API */ + Slapi_PBlock * aPb = slapi_pblock_new (); + /* + * This search may NOT be chained--the user and the role definition + * must be co-located (chaining is not supported for the roles + * plugin in 5.0 + */ + slapi_search_internal_set_pb ( aPb, + slapi_sdn_get_ndn(clientDn), + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + aclpb /* callback_data */, + NULL/* result_callback */, + acllas__handle_client_search, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if ( NULL == aclpb->aclpb_client_entry ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acllas__user_has_role: Unable to get client's entry\n",0,0,0); + return ACL_FALSE; + } + + /* If the client has the role then it's a match, otherwise no */ + + rc = slapi_role_check( aclpb->aclpb_client_entry, roleDN, &present); + if ( present ) { + return(ACL_TRUE); + } + + return(ACL_FALSE); +} + +int +DS_LASRoleDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *s_attrName = NULL; + char *attrName; + int matched; + int rc; + Slapi_Attr *attr; + int numOflevels = 0; + char *n_currEntryDn = NULL; + lasInfo lasinfo; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int k=0; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_ROLEDN, "DS_LASRoleDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* For anonymous client, they have no roles so the match is false. */ + if ( lasinfo.anomUser ) + return LAS_EVAL_FALSE; + + /* + ** + ** The function of this LAS is to find out if the client has + ** the role specified in the attr. + ** attr_pattern looks like: "ROLEDN cn=role1,o=sun.com" + */ + attrName = attr_pattern; + + matched = ACL_FALSE; + slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &attr); + if (attr == NULL) { + /* + * Here the entry does not contain the attribute so the user + * cannot have this "null" role + */ + return LAS_EVAL_FALSE; + } + + if (lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) { + /* + * Here the entry does not contain the attribute so the user + * cannot have this "null" role or + * For the add operation, the resource itself + * must never be allowed to grant access-- + * This is because access would be granted based on a value + * of an attribute in the new entry--security hole. + * XXX is this therefore FALSE or DONT_KNOW ? + * + * + */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL info: userattr=XXX#ROLEDN does not allow ADD permission.\n"); + got_undefined = 1; + } else { + + /* + * Got the first value. + * Test all the values of this attribute--if the client has _any_ + * of the roles then it's a match. + */ + k = slapi_attr_first_value ( attr,&sval ); + while ( k != -1 ) { + char *n_attrval; + Slapi_DN *roleDN; + + attrVal = slapi_value_get_berval ( sval ); + n_attrval = slapi_ch_strdup( attrVal->bv_val); + n_attrval = slapi_dn_normalize (n_attrval); + roleDN = slapi_sdn_new_dn_byval(n_attrval); + + /* We support: The attribute value can be a USER or a GROUP. + ** Let's compare with the client, thi might be just an user. If it is not + ** then we test it against the list of groups. + */ + if ((matched = acllas__user_has_role( + lasinfo.aclpb, + roleDN, + lasinfo.aclpb->aclpb_authorization_sdn)) == ACL_TRUE){ + slapi_ch_free ( (void **)&n_attrval ); + slapi_sdn_free(&roleDN ); + break; + } + slapi_ch_free ( (void **)&n_attrval ); + slapi_sdn_free(&roleDN ); + if (matched == ACL_TRUE) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + k= slapi_attr_next_value ( attr, k, &sval ); + }/* while */ + } + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + } + return (rc); +} + +/* + * Here, determine if lasinfo->clientDn matches user (which contains + * a ($dn) or a $attr component or both.) As defined in the aci + * lasinfo->aclpb->aclpb_curr_aci, + * which is the current aci being evaluated. + * + * returns: ACL_TRUE for matched, + * ACL_FALSE for matched. + * ACL_DONT_KNOW otherwise. + * + * +*/ + +int +aclutil_evaluate_macro( char * rule, lasInfo *lasinfo, + acl_eval_types evalType ) { + + int matched = 0; + aci_t *aci; + char *matched_val = NULL; + char **candidate_list = NULL; + char **inner_list = NULL; + char **sptr = NULL; + char **tptr = NULL; + char *t = NULL; + char *s = NULL; + char *target_dn = NULL; + struct acl_pblock *aclpb = lasinfo->aclpb; + int found_matched_val_in_ht = 0; + + aci = lasinfo->aclpb->aclpb_curr_aci; + /* Get a pointer to the ndn in the resouirce */ + target_dn = slapi_entry_get_ndn ( lasinfo->resourceEntry ); + + /* + * First, get the matched value from the target resource. + * We have alredy done this matching once beofer at tasrget match time. + */ + + LDAPDebug ( LDAP_DEBUG_ACL, "aclutil_evaluate_macro for aci '%s'" + "index '%d'\n", + aci->aclName, aci->aci_index,0); + + if ( aci->aci_macro == NULL ) { + /* No $dn in the target, it's a $attr type subject rule */ + matched_val = NULL; + } else { + + /* + * Look up the matched_val value calculated + * from the target and stored judiciously there for us. + */ + + if ( (matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index)) == NULL) { + LDAPDebug( LDAP_DEBUG_ACL, + "ACL info: failed to locate the calculated target" + "macro for aci '%s' index '%d'\n", + aci->aclName, aci->aci_index,0); + return(ACL_FALSE); /* Not a match */ + } else { + LDAPDebug( LDAP_DEBUG_ACL, + "ACL info: found matched_val (%s) for aci index %d" + "in macro ht\n", + aci->aclName, aci->aci_index,0); + + found_matched_val_in_ht = 1; + } + } + + /* + * Now, make a candidate + * list of strings to match against the client. + * This involves replacing ($dn) or [$dn] by either the matched + * value, or all the suffix substrings of matched_val. + * If there is no $dn then the candidate list is just + * user itself. + * + */ + + candidate_list = acllas_replace_dn_macro( rule, matched_val, lasinfo); + + sptr= candidate_list; + while( *sptr != NULL && !matched) { + + s = *sptr; + + /* + * Now s may contain some $attr macros. + * So, make a candidate list, got by replacing each occurence + * of $attr with all the values that attribute has in + * the resource entry. + */ + + inner_list = acllas_replace_attr_macro( s, lasinfo); + + tptr = inner_list; + while( *tptr != NULL && (matched != ACL_TRUE) ){ + + t = *tptr; + + /* + * Now, at last t is a candidate string we can + * match agains the client. + * + * $dn and $attr can appear in userdn, graoupdn and roledn + * rules, so we we need to decide which type we + * currently evaluating and evaluate that. + * + * If the string generated was undefined, eg it contained + * ($attr.ou) and the entry did not have an ou attribute,then + * the empty string is returned for this. So it we find + * an empty string in the list, skip it--it does not match. + */ + + if ( *t != '\0') { + if ( evalType == ACL_EVAL_USER ) { + + matched = acllas_eval_one_user( lasinfo->aclpb, + lasinfo->clientDn, t); + } else if (evalType == ACL_EVAL_GROUP) { + + matched = acllas_eval_one_group(t, lasinfo); + } else if (evalType == ACL_EVAL_ROLE) { + matched = acllas_eval_one_role(t, lasinfo); + } else if (evalType == ACL_EVAL_GROUPDNATTR) { + matched = acllas__eval_memberGroupDnAttr(t, + lasinfo->resourceEntry, + lasinfo->clientDn, + lasinfo->aclpb); + } else if ( evalType == ACL_EVAL_TARGET_FILTER) { + + matched = acllas_eval_one_target_filter(t, + lasinfo->resourceEntry); + + } + } + + tptr++; + + }/*inner while*/ + charray_free(inner_list); + + sptr++; + }/* outer while */ + + charray_free(candidate_list); + + return(matched); + +} + +/* + * Here, replace the first occurrence of $(dn) with matched_val. + * replace any occurrence of $[dn] with each of the suffix substrings + * of matched_val. + * Each of these strings is returned in a NULL terminated list of strings. + * + * If there is no $dn thing then the returned list just contains rule itself. + * + * eg. rule: cn=fred,ou=*, ($dn), o=sun.com + * matched_val: ou=People,o=icnc + * + * Then we return the list + * cn=fred,ou=*,ou=People,o=icnc,o=sun.com NULL + * + * eg. rule: cn=fred,ou=*,[$dn], o=sun.com + * matched_val: ou=People,o=icnc + * + * Then we return the list + * cn=fred,ou=*,ou=People,o=icnc,o=sun.com + * cn=fred,ou=*,o=icnc,o=sun.com + * NULL + * + * +*/ + +static char ** +acllas_replace_dn_macro( char *rule, char *matched_val, lasInfo *lasinfo) { + + char **a = NULL; + char *str = NULL; + char *patched_rule = NULL; + char *rule_to_use = NULL; + char *new_patched_rule = NULL; + char *rule_prefix = NULL; + char *rule_suffix = NULL; + int rule_suffix_len = 0; + char *comp = NULL; + int matched_val_len = 0; + int macro_len = 0; + int j = 0; + int has_macro_dn = 0; + int has_macro_levels = 0; + + /* Determine what the rule's got once */ + if ( strstr(rule, ACL_RULE_MACRO_DN_KEY) != NULL) { + has_macro_dn = 1; + } + + if ( strstr(rule, ACL_RULE_MACRO_DN_LEVELS_KEY) != NULL) { + has_macro_levels = 1; + } + + if ( !has_macro_dn && !has_macro_levels ) { + + /* + * No $dn thing, just return a list with two elements, rule and NULL. + * charray_add will create the list and null terminate it. + */ + + charray_add( &a, slapi_ch_strdup(rule)); + return(a); + } else { + + /* + * Have an occurrence of the macro rules + * + * First, replace all occurrencers of ($dn) with the matched_val + */ + + if ( has_macro_dn) { + patched_rule = + acl_replace_str(rule, ACL_RULE_MACRO_DN_KEY, matched_val); + } + + /* If there are no [$dn] we're done */ + + if ( !has_macro_levels ) { + charray_add( &a, patched_rule); + return(a); + } else { + + /* + * It's a [$dn] type, so walk matched_val, splicing in all + * the suffix substrings and adding each such string to + * to the returned list. + * get_next_component() does not return the commas--the + * prefix and suffix should come with their commas. + * + * All occurrences of each [$dn] are replaced with each level. + * + * If has_macro_dn then patched_rule is the rule to strart with, + * and this needs to be freed at the end, otherwise + * just use rule. + */ + + if (patched_rule) { + rule_to_use = patched_rule; + } else { + rule_to_use = rule; + } + + matched_val_len = strlen(matched_val); + j = 0; + + while( j < matched_val_len) { + + new_patched_rule = + acl_replace_str(rule_to_use, ACL_RULE_MACRO_DN_LEVELS_KEY, + &matched_val[j]); + charray_add( &a, new_patched_rule); + + j += acl_find_comp_end(&matched_val[j]); + } + + if (patched_rule) { + slapi_ch_free((void**)&patched_rule); + } + + return(a); + } + } +} + +/* + * Here, replace any occurrence of $attr.attrname with the + * value of attrname from lasinfo->resourceEntry. + * + * + * If there is no $attr thing then the returned list just contains rule + * itself. + * + * eg. rule: cn=fred,ou=*,ou=$attr.ou,o=sun.com + * ou: People + * ou: icnc + * + * Then we return the list + * cn=fred,ou=*,ou=People,o=sun.com + * cn=fred,ou=*,ou=icnc,o=sun.com + * +*/ + +static char ** +acllas_replace_attr_macro( char *rule, lasInfo *lasinfo) { + + char **a = NULL; + char **working_list = NULL; + Slapi_Entry *e = lasinfo->resourceEntry; + char *str, *working_rule; + char *macro_str, *macro_attr_name; + int l; + Slapi_Attr *attr = NULL; + + str = strstr(rule, ACL_RULE_MACRO_ATTR_KEY); + if ( str == NULL ) { + + charray_add(&a, slapi_ch_strdup(rule)); + return(a); + + } else { + + working_rule = slapi_ch_strdup(rule); + str = strstr(working_rule, ACL_RULE_MACRO_ATTR_KEY); + charray_add(&working_list, working_rule ); + + while( str != NULL) { + + /* + * working_rule is the first member of working_list. + * str points to the next $attr.attrName in working_rule. + * each member of working_list needs to have each occurence of + * $attr.atrName replaced with the value of attrName in e. + * If attrName is multi valued then this generates another + * list which replaces the old one. + */ + + l = acl_strstr(&str[0], ")"); + macro_str = slapi_ch_malloc(l+2); + strncpy( macro_str, &str[0], l+1); + macro_str[l+1] = '\0'; + + str = strstr(macro_str, "."); + str++; /* skip the . */ + l = acl_strstr(&str[0], ")"); + macro_attr_name = slapi_ch_malloc(l+1); + strncpy( macro_attr_name, &str[0], l); + macro_attr_name[l] = '\0'; + + slapi_entry_attr_find ( e, macro_attr_name, &attr ); + if ( NULL == attr ) { + + /* + * Here, if a $attr.attrName is such that the attrName + * does not occur in the entry then return a ""-- + * this will go back to the matching code in + * aclutil_evaluate_macro() where "" will + * be taken as the candidate. + */ + + slapi_ch_free((void **)¯o_str); + slapi_ch_free((void **)¯o_attr_name); + + charray_free(working_list); + charray_add(&a, slapi_ch_strdup("")); + return(a); + + } else{ + + const struct berval *attrValue; + Slapi_Value *sval; + int i, j; + char *patched_rule; + + a = NULL; + i= slapi_attr_first_value ( attr, &sval ); + while(i != -1) { + attrValue = slapi_value_get_berval(sval); + + j = 0; + while( working_list[j] != NULL) { + + patched_rule = + acl_replace_str(working_list[j], + macro_str, attrValue->bv_val); + charray_add(&a, patched_rule); + j++; + } + + i= slapi_attr_next_value( attr, i, &sval ); + }/* while */ + + /* + * Here, a is working_list, where each member has had + * macro_str replaced with attrVal. + */ + + charray_free(working_list); + working_list = a; + working_rule = a[0]; + } + slapi_ch_free((void **)¯o_str); + slapi_ch_free((void **)¯o_attr_name); + + str = strstr(working_rule, ACL_RULE_MACRO_ATTR_KEY); + + }/* while */ + + return(working_list); + } + + +} + +/* + * returns ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW. + * + * user is a string from the userdn keyword which may contain + * * components. This routine does the compare component by component, so + * that * behaves differently to "normal". + * Any ($dn) or $attr must have been removed from user before this is called. +*/ +static int +acllas_eval_one_user( struct acl_pblock *aclpb, char * clientDN, char *rule) { + + int exact_match = 0; + int ret_code = 0; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + char *s = NULL; + + + + /* URL format */ + if ((s = strchr (rule, '?'))!= NULL) { + /* URL format */ + if (acllas__client_match_URL ( aclpb, clientDN, + rule) == ACL_TRUE) { + exact_match = 1; + } + } else if ( strstr(rule, "=*") == NULL ) { + /* Just a straight compare */ + /* skip the ldap:/// part */ + rule += LDAP_URL_prefix_len; + exact_match = !slapi_utf8casecmp((ACLUCHP)clientDN, + (ACLUCHP)rule); + } else{ + /* Here, contains a =*, so need to match comp by comp */ + /* skip the ldap:/// part */ + rule += LDAP_URL_prefix_len; + ret_code = acl_match_prefix( rule, clientDN, &exact_match); + } + if ( exact_match) { + return( ACL_TRUE); + } else { + return(ACL_FALSE); + } +} + +/* + * returns ACL_TRUE, ACL_FALSE and ACL_DONT_KNOW. + * + * The user string has had all ($dn) and $attr replaced + * so the only dodgy thing left is a *. + * + * If * appears in such a user string, then it matches only that + * component, not .*, like it would otherwise. + * +*/ +static int +acllas_eval_one_group(char *groupbuf, lasInfo *lasinfo) { + + if (groupbuf) { + return( acllas__user_ismember_of_group ( + lasinfo->aclpb, + groupbuf, + lasinfo->clientDn, + ACLLAS_CACHE_ALL_GROUPS, + lasinfo->aclpb->aclpb_clientcert + )); + } else { + return(ACL_FALSE); /* not in the empty group */ + } +} + +/* + * returns ACL_TRUE for match, ACL_FALSE for not a match, ACL_DONT_KNOW otherwise. +*/ +static int +acllas_eval_one_role(char *role, lasInfo *lasinfo) { + + Slapi_DN *roleDN = NULL; + int rc = ACL_FALSE; + char ebuf [ BUFSIZ ]; + + /* + * See if lasinfo.clientDn has role rolebuf. + * Here we know it's not an anom user nor + * a an anyone user--the client dn must be matched against + * a real role. + */ + + roleDN = slapi_sdn_new_dn_byval(role); + if (role) { + rc = acllas__user_has_role( + lasinfo->aclpb, + roleDN, + lasinfo->aclpb->aclpb_authorization_sdn); + } else { /* The user does not have the empty role */ + rc = ACL_FALSE; + } + slapi_sdn_free(&roleDN ); + + /* Some useful logging */ + if (rc == ACL_TRUE ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "role evaluation: user '%s' does have role '%s'\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo->clientDn, ebuf), + role); + } else { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "role evaluation: user '%s' does NOT have role '%s'\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo->clientDn, ebuf), + role); + } + return(rc); +} + +/* + * returns ACL_TRUE if e matches the filter str, ACL_FALSE if not, + * ACL_DONT_KNOW otherwise. +*/ +static int acllas_eval_one_target_filter( char * str, Slapi_Entry *e) { + + int rc = ACL_FALSE; + Slapi_Filter *f = NULL; + + if ((f = slapi_str2filter(str)) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Warning: Bad targetfilter(%s) in aci: does not match\n", str); + return(ACL_DONT_KNOW); + } + + if (slapi_vattr_filter_test(NULL, e, f, 0 /*don't do acess chk*/)!= 0) { + rc = ACL_FALSE; /* Filter does not match */ + } else { + rc = ACL_TRUE; /* filter does match */ + } + slapi_filter_free(f, 1); + + return(rc); + +} + + + + + +/***************************************************************************/ +/* E N D */ +/***************************************************************************/ diff --git a/ldap/servers/plugins/acl/acllist.c b/ldap/servers/plugins/acl/acllist.c new file mode 100644 index 00000000..0147626d --- /dev/null +++ b/ldap/servers/plugins/acl/acllist.c @@ -0,0 +1,940 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/************************************************************************ + * + * ACLLIST + * + * All the ACLs are read when the server is started. The ACLs are + * parsed and kept in an AVL tree. All the ACL List management are + * in this file. + * + * The locking on the aci cache is implemented using the acllist_acicache*() + * routines--a read/write lock. + * + * The granularity of the view of the cache is the entry level--ie. + * when an entry is being modified (mod,add,delete,modrdn) and the mod + * involves the aci attribute, then other operations will see the acl cache + * before the whole change or after the whole change, but not during the change. + * cf. acl.c:acl_modified() + * + * The only tricky issue is that there is also locking + * implemented for the anonymous profile and sometimes we need to take both + * locks cf. aclanom_anom_genProfile(). The rule is + * always take the acicache lock first, followed by the anon lock--following + * this rule will ensure no dead lock scenarios can arise. + * + * Some routines are called in different places with different lock + * contexts--for these routines acl_lock_flag_t is used to + * pass the context. + * + */ +#include "acl.h" + +static PRRWLock *aci_rwlock = NULL; +#define ACILIST_LOCK_READ() PR_RWLock_Rlock (aci_rwlock ) +#define ACILIST_UNLOCK_READ() PR_RWLock_Unlock (aci_rwlock ) +#define ACILIST_LOCK_WRITE() PR_RWLock_Wlock (aci_rwlock ) +#define ACILIST_UNLOCK_WRITE() PR_RWLock_Unlock (aci_rwlock ) + + +/* Root of the TREE */ +static Avlnode *acllistRoot = NULL; + +#define CONTAINER_INCR 2000 + +/* The container array */ +static AciContainer **aciContainerArray; +static PRUint32 currContainerIndex =0; +static PRUint32 maxContainerIndex = 0; +static int curAciIndex = 1; + +/* PROTOTYPES */ +static int __acllist_add_aci ( aci_t *aci ); +static int __acllist_aciContainer_node_cmp ( caddr_t d1, caddr_t d2 ); +static int __acllist_aciContainer_node_dup ( caddr_t d1, caddr_t d2 ); +static void __acllist_free_aciContainer ( AciContainer **container); +static void free_targetattrfilters( Targetattrfilter ***input_attrFilterArray); + +void my_print( Avlnode *root ); + + +int +acllist_init () +{ + + if (( aci_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ACLLIST LOCK") ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "acllist_init:failed in getting the rwlock\n" ); + return 1; + } + + aciContainerArray = (AciContainer **) slapi_ch_calloc ( 1, + CONTAINER_INCR * sizeof ( AciContainer * ) ); + maxContainerIndex = CONTAINER_INCR; + currContainerIndex = 0; + + return 0; +} + +/* + * This is the callback for backend state changes. + * It needs to add/remove acis as backends come up and go down. + * + * The strategy is simple: + * When a backend moves to the SLAPI_BE_STATE_ON then we go get all the acis + * add them to the cache. + * When a backend moves out of the SLAPI_BE_STATE_ON then we remove them all. + * +*/ + +void acl_be_state_change_fnc ( void *handle, char *be_name, int old_state, + int new_state) { + Slapi_Backend *be=NULL; + const Slapi_DN *sdn; + + + if ( old_state == SLAPI_BE_STATE_ON && new_state != SLAPI_BE_STATE_ON) { + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Backend %s is no longer STARTED--deactivating it's acis\n", + be_name); + + if ( (be = slapi_be_select_by_instance_name( be_name )) == NULL) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + /* + * Just get the first suffix--if there are multiple XXX ? + */ + + if ( (sdn = slapi_be_getsuffix( be, 0)) == NULL ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + aclinit_search_and_update_aci ( 1, /* thisbeonly */ + sdn, /* base */ + be_name,/* be name */ + LDAP_SCOPE_SUBTREE, + ACL_REMOVE_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + + } else if ( old_state != SLAPI_BE_STATE_ON && new_state == SLAPI_BE_STATE_ON) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Backend %s is now STARTED--activating it's acis\n", be_name); + + if ( (be = slapi_be_select_by_instance_name( be_name )) == NULL) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + /* + * In fact there can onlt be one sufffix here. + */ + + if ( (sdn = slapi_be_getsuffix( be, 0)) == NULL ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + aclinit_search_and_update_aci ( 1, /* thisbeonly */ + sdn, + be_name, /* be name */ + LDAP_SCOPE_SUBTREE, + ACL_ADD_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + } + +} + +/* This routine must be called with the acicache write lock taken */ +int +acllist_insert_aci_needsLock( const Slapi_DN *e_sdn, const struct berval* aci_attr) +{ + + aci_t *aci; + char *acl_str; + int rv =0; + + if (aci_attr->bv_len <= 0) + return 0; + + aci = acllist_get_aci_new (); + slapi_sdn_set_ndn_byval ( aci->aci_sdn, slapi_sdn_get_ndn ( e_sdn ) ); + + acl_str = slapi_ch_strdup(aci_attr->bv_val); + /* Parse the ACL TEXT */ + if ( 0 != (rv = acl_parse ( acl_str, aci )) ) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "ACL PARSE ERR(rv=%d): %s\n", rv, acl_str ); + slapi_ch_free ( (void **) &acl_str ); + acllist_free_aci ( aci ); + + return 1; + } + + /* Now add it to the list */ + if ( 0 != (rv =__acllist_add_aci ( aci ))) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACL ADD ACI ERR(rv=%d): %s\n", rv, acl_str ); + slapi_ch_free ( (void **) &acl_str ); + acllist_free_aci ( aci ); + return 1; + } + + slapi_ch_free ( (void **) &acl_str ); + acl_regen_aclsignature (); + if ( aci->aci_elevel == ACI_ELEVEL_USERDN_ANYONE) + aclanom_invalidateProfile (); + return 0; +} + +/* This routine must be called with the acicache write lock taken */ +static int +__acllist_add_aci ( aci_t *aci ) +{ + + int rv = 0; /* OK */ + AciContainer *aciListHead; + AciContainer *head; + PRUint32 i; + + aciListHead = acllist_get_aciContainer_new ( ); + slapi_sdn_set_ndn_byval ( aciListHead->acic_sdn, slapi_sdn_get_ndn ( aci->aci_sdn ) ); + + /* insert the aci */ + switch (avl_insert ( &acllistRoot, aciListHead, __acllist_aciContainer_node_cmp, + __acllist_aciContainer_node_dup ) ) { + + case 1: /* duplicate ACL on the same entry */ + + /* Find the node that contains the acl. */ + if ( NULL == (head = (AciContainer *) avl_find( acllistRoot, aciListHead, + (IFP) __acllist_aciContainer_node_cmp ) ) ) { + slapi_log_error ( SLAPI_PLUGIN_ACL, plugin_name, + "Can't insert the acl in the tree\n"); + rv = 1; + } else { + aci_t *t_aci;; + + /* Attach the list */ + t_aci = head->acic_list;; + while ( t_aci && t_aci->aci_next ) + t_aci = t_aci->aci_next; + + /* Now add the new one to the end of the list */ + t_aci->aci_next = aci; + } + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Added the ACL:%s to existing container:[%d]%s\n", + aci->aclName, head->acic_index, slapi_sdn_get_ndn( head->acic_sdn )); + + /* now free the tmp container */ + aciListHead->acic_list = NULL; + __acllist_free_aciContainer ( &aciListHead ); + + break; + default: + /* The container is inserted. Now hook up the aci and setup the + * container index. Donot free the "aciListHead" here. + */ + aciListHead->acic_list = aci; + + /* + * First, see if we have an open slot or not - -if we have reuse it + */ + i = 0; + while ( (i < currContainerIndex) && aciContainerArray[i] ) + i++; + + if ( currContainerIndex >= (maxContainerIndex - 2)) { + maxContainerIndex += CONTAINER_INCR; + aciContainerArray = (AciContainer **) slapi_ch_realloc ( (char *) aciContainerArray, + maxContainerIndex * sizeof ( AciContainer * ) ); + } + aciListHead->acic_index = i; + /* If i < currContainerIndex, we are just re-using an old slot. */ + /* We don't need to increase currContainerIndex if we just re-use an old one. */ + if (i == currContainerIndex) + currContainerIndex++; + + aciContainerArray[ aciListHead->acic_index ] = aciListHead; + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Added %s to container:%d\n", + slapi_sdn_get_ndn( aciListHead->acic_sdn ), aciListHead->acic_index ); + break; + } + + return rv; +} + + + +static int +__acllist_aciContainer_node_cmp ( caddr_t d1, caddr_t d2 ) +{ + + int rc =0; + AciContainer *c1 = (AciContainer *) d1; + AciContainer *c2 = (AciContainer *) d2; + + + rc = slapi_sdn_compare ( c1->acic_sdn, c2->acic_sdn ); + return rc; +} + +static int +__acllist_aciContainer_node_dup ( caddr_t d1, caddr_t d2 ) +{ + + /* we allow duplicates -- they are not exactly duplicates + ** but multiple aci value on the same node + */ + return 1; + +} + + +/* + * Remove the ACL + * + * This routine must be called with the aclcache write lock taken. + * It takes in addition the one for the anom profile taken in + * aclanom_invalidateProfile(). + * They _must_ be taken in this order or there + * is a deadlock scenario with aclanom_gen_anomProfile() which + * also takes them is this order. +*/ + +int +acllist_remove_aci_needsLock( const Slapi_DN *sdn, const struct berval *attr ) +{ + + aci_t *head, *next; + int rv = 0; + AciContainer *aciListHead, *root; + AciContainer *dContainer; + int removed_anom_acl = 0; + + /* we used to delete the ACL by value but we don't do that anymore. + * rather we delete all the acls in that entry and then repopulate it if + * there are any more acls. + */ + + aciListHead = acllist_get_aciContainer_new ( ); + slapi_sdn_set_ndn_byval ( aciListHead->acic_sdn, slapi_sdn_get_ndn ( sdn ) ); + + /* now find it */ + if ( NULL == (root = (AciContainer *) avl_find( acllistRoot, aciListHead, + (IFP) __acllist_aciContainer_node_cmp ))) { + /* In that case we don't have any acl for this entry. cool !!! */ + + __acllist_free_aciContainer ( &aciListHead ); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "No acis to remove in this entry\n" ); + return 0; + } + + head = root->acic_list; + if ( head) + next = head->aci_next; + while ( head ) { + if ( head->aci_elevel == ACI_ELEVEL_USERDN_ANYONE) + removed_anom_acl = 1; + + /* Free the acl */ + acllist_free_aci ( head ); + + head = next; + next = NULL; + if ( head && head->aci_next ) + next = head->aci_next; + } + root->acic_list = NULL; + + /* remove the container from the slot */ + aciContainerArray[root->acic_index] = NULL; + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Removing container[%d]=%s\n", root->acic_index, + slapi_sdn_get_ndn ( root->acic_sdn) ); + dContainer = (AciContainer *) avl_delete ( &acllistRoot, aciListHead, + __acllist_aciContainer_node_cmp ); + __acllist_free_aciContainer ( &dContainer ); + + acl_regen_aclsignature (); + if ( removed_anom_acl ) + aclanom_invalidateProfile (); + + /* + * Now read back the entry and repopulate ACLs for that entry, but + * only if a specific aci was deleted, otherwise, we do a + * "When Harry met Sally" and nail 'em all. + */ + + if ( attr != NULL) { + + if (0 != (rv = aclinit_search_and_update_aci ( 0, /* thisbeonly */ + sdn, /* base */ + NULL, /* be name */ + LDAP_SCOPE_BASE, + ACL_ADD_ACIS, + DONT_TAKE_ACLCACHE_WRITELOCK))) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + " Can't add the rest of the acls for entry:%s after delete\n", + slapi_sdn_get_dn ( sdn ) ); + } + } + + /* Now free the tmp container we used */ + __acllist_free_aciContainer ( &aciListHead ); + + /* + * regenerate the anonymous profile if we have deleted + * anyone acls. + * We don't need the aclcache readlock because the context of + * this routine is we have the write lock already. + */ + if ( removed_anom_acl ) + aclanom_gen_anomProfile(DONT_TAKE_ACLCACHE_READLOCK); + + return rv; +} + +AciContainer * +acllist_get_aciContainer_new ( ) +{ + + AciContainer *head; + + head = (AciContainer * ) slapi_ch_calloc ( 1, sizeof ( AciContainer ) ); + head->acic_sdn = slapi_sdn_new ( ); + head->acic_index = -1; + + return head; +} +static void +__acllist_free_aciContainer ( AciContainer **container) +{ + + PR_ASSERT ( container != NULL ); + + if ( (*container)->acic_index >= 0 ) + aciContainerArray[ (*container)->acic_index] = NULL; + if ( (*container)->acic_sdn ) + slapi_sdn_free ( &(*container)->acic_sdn ); + slapi_ch_free ( (void **) container ); + +} + +void +acllist_done_aciContainer ( AciContainer *head ) +{ + + PR_ASSERT ( head != NULL ); + + slapi_sdn_done ( head->acic_sdn ); + head->acic_index = -1; + + /* The caller is responsible for taking care of list */ + head->acic_list = NULL; +} + + +aci_t * +acllist_get_aci_new () +{ + aci_t *aci_item; + + aci_item = (aci_t *) slapi_ch_calloc (1, sizeof (aci_t)); + aci_item->aci_sdn = slapi_sdn_new (); + aci_item->aci_index = curAciIndex++; + aci_item->aci_elevel = ACI_DEFAULT_ELEVEL; /* by default it's a complex */ + aci_item->targetAttr = (Targetattr **) slapi_ch_calloc ( + ACL_INIT_ATTR_ARRAY, + sizeof (Targetattr *)); + return aci_item; +} + +void +acllist_free_aci(aci_t *item) +{ + + Targetattr **attrArray; + + /* The caller is responsible for taking + ** care of list issue + */ + if (item == NULL) return; + + slapi_sdn_free ( &item->aci_sdn ); + slapi_filter_free (item->target, 1); + + /* slapi_filter_free(item->targetAttr, 1); */ + attrArray = item->targetAttr; + if (attrArray) { + int i = 0; + Targetattr *attr; + + while (attrArray[i] != NULL) { + attr = attrArray[i]; + if (attr->attr_type & ACL_ATTR_FILTER) { + slapi_filter_free(attr->u.attr_filter, 1); + } else { + slapi_ch_free ( (void **) &attr->u.attr_str ); + } + slapi_ch_free ( (void **) &attr ); + i++; + } + /* Now free the array */ + slapi_ch_free ( (void **) &attrArray ); + } + + /* Now free any targetattrfilters in this aci item */ + + if ( item->targetAttrAddFilters ) { + free_targetattrfilters(&item->targetAttrAddFilters); + } + + if ( item->targetAttrDelFilters ) { + free_targetattrfilters(&item->targetAttrDelFilters); + } + + if (item->targetFilterStr) slapi_ch_free ( (void **) &item->targetFilterStr ); + slapi_filter_free(item->targetFilter, 1); + + /* free the handle */ + if (item->aci_handle) ACL_ListDestroy(NULL, item->aci_handle); + + /* Free the name */ + if (item->aclName) slapi_ch_free((void **) &item->aclName); + + /* Free any macro info*/ + if (item->aci_macro) { + slapi_ch_free((void **) &item->aci_macro->match_this); + slapi_ch_free((void **) &item->aci_macro); + } + + /* free at last -- free at last */ + slapi_ch_free ( (void **) &item ); +} + +static void free_targetattrfilters( Targetattrfilter ***attrFilterArray) { + + if (*attrFilterArray) { + int i = 0; + Targetattrfilter *attrfilter; + + while ((*attrFilterArray)[i] != NULL) { + attrfilter = (*attrFilterArray)[i]; + + if ( attrfilter->attr_str != NULL) { + slapi_ch_free ( (void **) &attrfilter->attr_str ); + } + + if (attrfilter->filter != NULL) { + slapi_filter_free(attrfilter->filter, 1); + } + + if( attrfilter->filterStr != NULL) { + slapi_ch_free ( (void **) &attrfilter->filterStr ); + } + + slapi_ch_free ( (void **) &attrfilter ); + i++; + } + /* Now free the array */ + slapi_ch_free ( (void **) attrFilterArray ); + } + +} + +/* SEARCH */ +void +acllist_init_scan (Slapi_PBlock *pb, int scope, char *base) +{ + Acl_PBlock *aclpb; + int i; + AciContainer *root; + char *basedn = NULL; + int index; + + if ( acl_skip_access_check ( pb, NULL ) ) { + return; + } + + /*acllist_print_tree ( acllistRoot, &depth, "top", "top") ; */ + /* my_print ( acllistRoot );*/ + /* If we have an anonymous profile and I am an anom dude - let's skip it */ + if ( aclanom_is_client_anonymous ( pb )) { + return; + } + aclpb = acl_get_aclpb (pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 4 \n" ); + return; + } + + aclpb->aclpb_handles_index[0] = -1; + + /* If base is NULL - it means we are going to go thru all the ACLs + * This is needed when we do anonymous profile generation. + */ + if ( NULL == base ) { + return; + } + + aclpb->aclpb_state |= ACLPB_SEARCH_BASED_ON_LIST ; + + acllist_acicache_READ_LOCK(); + + basedn = slapi_ch_strdup (base); + index = 0; + aclpb->aclpb_search_base = slapi_ch_strdup ( base ); + + while (basedn ) { + char *tmp = NULL; + + slapi_sdn_set_ndn_byref ( aclpb->aclpb_aclContainer->acic_sdn, basedn ); + + root = (AciContainer *) avl_find( acllistRoot, + (caddr_t) aclpb->aclpb_aclContainer, + (IFP) __acllist_aciContainer_node_cmp); + if ( index >= ACLPB_MAX_SELECTED_ACLS -2 ) { + aclpb->aclpb_handles_index[0] = -1; + slapi_ch_free ( (void **) &basedn); + break; + } else if ( NULL != root ) { + aclpb->aclpb_base_handles_index[index++] = root->acic_index; + aclpb->aclpb_base_handles_index[index] = -1; + } + tmp = slapi_dn_parent ( basedn ); + slapi_ch_free ( (void **) &basedn); + basedn = tmp; + } + + acllist_done_aciContainer ( aclpb->aclpb_aclContainer); + + if ( aclpb->aclpb_base_handles_index[0] == -1 ) + aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_LIST ; + + acllist_acicache_READ_UNLOCK(); + + i = 0; + while ( i < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_base_handles_index[i] != -1 ) { + i++; + } +} + +/* + * Initialize aclpb_handles_index[] (sentinel -1) to + * contain a list of all aci items at and above edn in the DIT tree. + * This list will be subsequestly scanned to find applicable aci's for + * the given operation. +*/ + +void +acllist_aciscan_update_scan ( Acl_PBlock *aclpb, char *edn ) +{ + + int i, index = 0; + char *basedn = NULL; + AciContainer *root; + int is_not_search_base = 1; + + + /* First copy the containers indx from the base to the one which is + * going to be used. + * The base handles get done in acllist_init_scan(). + * This stuff is only used if it's a search operation. + */ + if ( aclpb && aclpb->aclpb_search_base ) { + while ( aclpb->aclpb_base_handles_index[index] != -1 && + index < ACLPB_MAX_SELECTED_ACLS -2 ) { + aclpb->aclpb_handles_index[index] = + aclpb->aclpb_base_handles_index[index]; + index++; + } + if ( strcasecmp ( edn, aclpb->aclpb_search_base) == 0) { + is_not_search_base = 0; + } + } + aclpb->aclpb_handles_index[index] = -1; + + /* + * Here, make a list of all the aci's that will apply + * to edn ie. all aci's at and above edn in the DIT tree. + * + * Do this by walking up edn, looking at corresponding + * points in the acllistRoot aci tree. + * + * If is_not_search_base is true, then we need to iterate on edn, otherwise + * we've already got all the base handles above. + * + */ + + if (is_not_search_base) { + + basedn = slapi_ch_strdup ( edn ); + + while (basedn ) { + char *tmp = NULL; + + slapi_sdn_set_ndn_byref ( aclpb->aclpb_aclContainer->acic_sdn, basedn ); + + root = (AciContainer *) avl_find( acllistRoot, + (caddr_t) aclpb->aclpb_aclContainer, + (IFP) __acllist_aciContainer_node_cmp); + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Searching AVL tree for update:%s: container:%d\n", basedn , + root ? root->acic_index: -1); + if ( index >= ACLPB_MAX_SELECTED_ACLS -2 ) { + aclpb->aclpb_handles_index[0] = -1; + slapi_ch_free ( (void **) &basedn); + break; + } else if ( NULL != root ) { + aclpb->aclpb_handles_index[index++] = root->acic_index; + aclpb->aclpb_handles_index[index] = -1; + } + tmp = slapi_dn_parent ( basedn ); + slapi_ch_free ( (void **) &basedn); + basedn = tmp; + if ( aclpb->aclpb_search_base && tmp && + ( 0 == strcasecmp ( tmp, aclpb->aclpb_search_base))) { + slapi_ch_free ( (void **) &basedn); + tmp = NULL; + } + } /* while */ + } + + acllist_done_aciContainer ( aclpb->aclpb_aclContainer ); + i = 0; + while ( i < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[i] != -1 ) { + i++; + } + +} + +aci_t * +acllist_get_first_aci (Acl_PBlock *aclpb, PRUint32 *cookie ) +{ + + int val; + + *cookie = val = 0; + if ( aclpb && aclpb->aclpb_handles_index[0] != -1 ) { + val = aclpb->aclpb_handles_index[*cookie]; + } + if ( NULL == aciContainerArray[val]) { + return ( acllist_get_next_aci ( aclpb, NULL, cookie ) ); + } + + return (aciContainerArray[val]->acic_list ); +} +/* + * acllist_get_next_aci + * Return the next aci in the list + * + * Inputs + * Acl_PBlock *aclpb -- acl Main block; if aclpb= NULL, + * -- then we scan thru the whole list. + * -- which is used by anom profile code. + * aci_t *curaci -- the current aci + * PRUint32 *cookie -- cookie -- to maintain a state (what's next) + * + */ + +aci_t * +acllist_get_next_aci ( Acl_PBlock *aclpb, aci_t *curaci, PRUint32 *cookie ) +{ + PRUint32 val; + int scan_entire_list; + + /* + Here, if we're passed a curaci and there's another aci in the same node, + return that one. + */ + + if ( curaci && curaci->aci_next ) + return ( curaci->aci_next ); + + /* + Determine if we need to scan the entire list of acis. + We do if the aclpb==NULL or if the first handle index is -1. + That means that we want to go through + the entire aciContainerArray up to the currContainerIndex to get + acis; the -1 in the first position is a special keyword which tells + us that the acis have changed, so we need to go through all of them. + */ + + scan_entire_list = (aclpb == NULL || aclpb->aclpb_handles_index[0] == -1); + +start: + (*cookie)++; + val = *cookie; + + /* if we are not scanning the entire aciContainerArray list, we only want to + look at the indexes specified in the handles index */ + if ( !scan_entire_list ) + val = aclpb->aclpb_handles_index[*cookie]; + + /* the hard max end */ + if ( val >= maxContainerIndex) + return NULL; + + /* reached the end of the array */ + if ((!scan_entire_list && (*cookie >= (ACLPB_MAX_SELECTED_ACLS-1))) || + (*cookie >= currContainerIndex)) { + return NULL; + } + + /* if we're only using the handles list for our aciContainerArray + indexes, the -1 value marks the end of that list */ + if ( !scan_entire_list && (aclpb->aclpb_handles_index[*cookie] == -1) ) { + return NULL; + } + + /* if we're scanning the entire list, and we hit a null value in the + middle of the list, just try the next one; this can happen if + an aci was deleted - it can leave "holes" in the array */ + if ( scan_entire_list && ( NULL == aciContainerArray[val])) { + goto start; + } + + if ( aciContainerArray[val] ) + return (aciContainerArray[val]->acic_list ); + else + return NULL; +} + +void +acllist_acicache_READ_UNLOCK( ) +{ + ACILIST_UNLOCK_READ (); + +} + +void +acllist_acicache_READ_LOCK() +{ + /* get a reader lock */ + ACILIST_LOCK_READ (); + +} + +void +acllist_acicache_WRITE_UNLOCK( ) +{ + ACILIST_UNLOCK_WRITE (); + +} + +void +acllist_acicache_WRITE_LOCK( ) +{ + ACILIST_LOCK_WRITE (); + +} + +/* This routine must be called with the acicache write lock taken */ +int +acllist_moddn_aci_needsLock ( Slapi_DN *oldsdn, char *newdn ) +{ + + + AciContainer *aciListHead; + AciContainer *head; + + /* first get the container */ + + aciListHead = acllist_get_aciContainer_new ( ); + slapi_sdn_free(&aciListHead->acic_sdn); + aciListHead->acic_sdn = oldsdn; + + + if ( NULL == (head = (AciContainer *) avl_find( acllistRoot, aciListHead, + (IFP) __acllist_aciContainer_node_cmp ) ) ) { + + slapi_log_error ( SLAPI_PLUGIN_ACL, plugin_name, + "Can't find the acl in the tree for moddn operation:olddn%s\n", + slapi_sdn_get_ndn ( oldsdn )); + aciListHead->acic_sdn = NULL; + __acllist_free_aciContainer ( &aciListHead ); + return 1; + } + + + /* Now set the new DN */ + slapi_sdn_done ( head->acic_sdn ); + slapi_sdn_set_ndn_byval ( head->acic_sdn, newdn ); + + aciListHead->acic_sdn = NULL; + __acllist_free_aciContainer ( &aciListHead ); + + return 0; +} + +void +acllist_print_tree ( Avlnode *root, int *depth, char *start, char *side) +{ + + AciContainer *aciHeadList; + + if ( NULL == root ) { + return; + } + aciHeadList = (AciContainer *) root->avl_data; + slapi_log_error ( SLAPI_LOG_ACL, "plugin_name", + "Container[ Depth=%d%s-%s]: %s\n", *depth, start, side, + slapi_sdn_get_ndn ( aciHeadList->acic_sdn ) ); + + (*depth)++; + + acllist_print_tree ( root->avl_left, depth, side, "L" ); + acllist_print_tree ( root->avl_right, depth, side, "R" ); + + (*depth)--; + +} + +static +void +ravl_print( Avlnode *root, int depth ) +{ + int i; + + AciContainer *aciHeadList; + if ( root == 0 ) + return; + + ravl_print( root->avl_right, depth+1 ); + + for ( i = 0; i < depth; i++ ) + printf( " " ); + aciHeadList = (AciContainer *) root->avl_data; + printf( "%s\n", slapi_sdn_get_ndn ( aciHeadList->acic_sdn ) ); + + ravl_print( root->avl_left, depth+1 ); +} + +void +my_print( Avlnode *root ) +{ + printf( "********\n" ); + + if ( root == 0 ) + printf( "\tNULL\n" ); + else + ( void ) ravl_print( root, 0 ); + + printf( "********\n" ); +} diff --git a/ldap/servers/plugins/acl/aclparse.c b/ldap/servers/plugins/acl/aclparse.c new file mode 100644 index 00000000..2247471a --- /dev/null +++ b/ldap/servers/plugins/acl/aclparse.c @@ -0,0 +1,1928 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int __aclp__parse_aci(char *str, aci_t *aci_item); +static int __aclp__sanity_check_acltxt(aci_t *aci_item, char *str); +static char * __aclp__normalize_acltxt (aci_t *aci_item, char *str); +static char * __aclp__getNextLASRule(aci_t *aci_item, char *str, + char **endOfCurrRule); +static char * __aclp__dn_normalize( char *dn , char *end); +static int __aclp__get_aci_right ( char *str); +static int __aclp__init_targetattr (aci_t *aci, char *attr_val); +static int __acl__init_targetattrfilters( aci_t *aci_item, char *str); +static int process_filter_list( Targetattrfilter ***attrfilterarray, + char * str); +static int __acl_init_targetattrfilter( Targetattrfilter *attrfilter, char *str ); +static void __aclp_chk_paramRules ( aci_t *aci_item, char *start, + char *end); +static void __acl_strip_trailing_space( char *str); +static void __acl_strip_leading_space( char **str); +static char * __acl_trim_filterstr( char * str ); +static int acl_verify_exactly_one_attribute( char *attr_name, Slapi_Filter *f); +static int type_compare( Slapi_Filter *f, void *arg); +static int acl_check_for_target_macro( aci_t *aci_item, char *value); +static int get_acl_rights_as_int( char * strValue); + +/*************************************************************************** +* +* acl_parse +* +* Parses the input string and copies the information into the +* correct place in the aci. +* +* +* Input: +* char *str - Input string which has the ACL +* This is a duped copy, so here we have +* the right to stich '\0' characters into str for +* processing purposes. If you want to keep +* a piece of str, you'll need to dup it +* as it gets freed outside the scope of acl_parse. +* aci_t *item - the aci item where the ACL info will be +* - stored. +* +* Returns: +* 0 -- Parsed okay +* < 0 -- error codes +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_parse(char * str, aci_t *aci_item) +{ + + int rv=0; + char *next; + char *save; + + while(*str) { + __acl_strip_leading_space( &str ); + if (*str == '\0') break; + + if (*str == '(') { + if ((next = slapi_find_matching_paren(str)) == NULL) { + return(ACL_SYNTAX_ERR); + } + } else { + /* then we have done all the processing */ + return 0; + } + LDAP_UTF8INC(str); /* skip the "(" */ + save = next; + LDAP_UTF8INC(next); + *save = '\0'; + + /* Now we have a "str)" */ + if ( 0 != (rv = __aclp__parse_aci(str, aci_item))) { + return(rv); + } + + /* Move to the next */ + str = next; + } + + /* check if have a ACLTXT or not */ + if (!(aci_item->aci_type & ACI_ACLTXT)) + return ACL_SYNTAX_ERR; + + if (aci_item->target) { + Slapi_Filter *f; + + /* Make sure that the target is a valid target. + ** Example: ACL is located in + ** "ou=engineering, o=ace industry, c=us + ** but if the target is "o=ace industry, c=us", + ** then it's an ERROR. + */ + f = aci_item->target; + if (aci_item->aci_type & ACI_TARGET_DN) { + char *avaType; + struct berval *avaValue; + const char *dn; + + dn = slapi_sdn_get_ndn ( aci_item->aci_sdn ); + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + if (!slapi_dn_issuffix( avaValue->bv_val, dn)) + return ACL_INVALID_TARGET; + } + } + + /* + ** We need to keep the taregetFilterStr for anyone ACL only. + ** same for targetValueFilterStr. + ** We need to keep it for macros too as it needs to be expnaded at eval time. + ** + */ + if ( (aci_item->aci_elevel != ACI_ELEVEL_USERDN_ANYONE) && + !(aci_item->aci_type & ACI_TARGET_MACRO_DN) ) { + slapi_ch_free ( (void **) & aci_item->targetFilterStr ); + } + + /* + * If we parsed the aci and there was a ($dn) on the user side + * but none in hte taget then that's an error as the user side + * value is derived from the target side value. + */ + + if (!(aci_item->aci_type & ACI_TARGET_MACRO_DN) && + (aci_item->aci_ruleType & ACI_PARAM_DNRULE)) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl_parse: A macro in a subject ($dn) must have a macro in the target.\n"); + return(ACL_INVALID_TARGET); + } + + return 0; +} + +/*************************************************************************** +* +* __aclp__parse_aci +* +* Parses Each individual subset of information/ +* +* Input: +* char *str - Input string which has the ACL like "str)" +* aci_t *item - the aci item where the ACL info will be +* - stored. +* +* Returns: +* 0 -- Parsed okay +* < 0 -- error codes +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +__aclp__parse_aci (char *str, aci_t *aci_item) +{ + + int len; + int rv; + int type; + char *tmpstr; + char *s = NULL; + char *value = NULL; + Slapi_Filter *f = NULL; + int targetattrlen = strlen(aci_targetattr); + int targetdnlen = strlen (aci_targetdn); + int tfilterlen = strlen(aci_targetfilter); + int targetattrfilterslen = strlen(aci_targetattrfilters); + + __acl_strip_leading_space( &str ); + + if (*str == '\0') { + return(ACL_SYNTAX_ERR); + } + + /* The first letter should tell us something */ + switch(*str) { + case 'v': + type = ACI_ACLTXT; + + if ( 0 != (rv= __aclp__sanity_check_acltxt(aci_item, str ) ) ) { + + return rv; + } + break; + + case 't': + if (strncmp(str, aci_targetattrfilters,targetattrfilterslen ) == 0) { + type = ACI_TARGET_ATTR; + + + /* + * The targetattrfilters bit looks like this: + * (targetattrfilters="add= attr1:F1 && attr2:F2 ... && attrn:Fn, + * del= attr1:F1 && attr2:F2... && attrn:Fn") + */ + if ( 0 != (rv= __acl__init_targetattrfilters( + aci_item, str))) { + return rv; + } + } else if (strncmp(str, aci_targetattr,targetattrlen ) == 0) { + type = ACI_TARGET_ATTR; + + if ( (s = strstr( str, "!=" )) != NULL ) { + type |= ACI_TARGET_ATTR_NOT; + strncpy(s, " ", 1); + } + /* Get individual components of the targetattr. + * (targetattr = "cn || u* || phone ||tel:add:(tel=1234) + * || sn:del:(gn=5678)") + * If it contains a value filter, the type will also be + * ACI_TARGET_VALUE_ATTR. + */ + if ( 0 != (rv= __aclp__init_targetattr( + aci_item, str))) { + return rv; + } + } else if (strncmp(str, aci_targetfilter,tfilterlen ) == 0) { + if ( aci_item->targetFilter) + return ACL_SYNTAX_ERR; + + type = ACI_TARGET_FILTER; + /* we need to remove the targetfilter stuff*/ + if ( (s = strstr( str, "!=" )) != NULL ) { + type |= ACI_TARGET_FILTER_NOT; + } + + /* + * If it's got a macro in the targetfilter then it must + * have a target and it must have a macro. + */ + + if ((s = strstr (str, ACL_RULE_MACRO_DN_KEY)) != NULL || + ((s = strstr(str, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL)) { + + /* Must have a targetmacro */ + if ( !(aci_item->aci_type & ACI_TARGET_MACRO_DN)) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl_parse: A macro in a targetfilter ($dn) must have a macro in the target.\n"); + return(ACL_SYNTAX_ERR); + } + + type|= ACI_TARGET_FILTER_MACRO_DN; + } + + tmpstr = strchr(str, '='); + tmpstr++; + __acl_strip_leading_space(&tmpstr); + + /* + * Trim off enclosing quotes and enclosing + * superfluous brackets. + * The result has been duped so it can be kept. + */ + + tmpstr = __acl_trim_filterstr( tmpstr ); + + f = slapi_str2filter(tmpstr); + + /* save the filter string */ + aci_item->targetFilterStr = tmpstr; + + } else if (strncmp(str, aci_targetdn, targetdnlen) == 0) { + char *tstr = NULL; + const size_t LDAP_URL_prefix_len = strlen (LDAP_URL_prefix); + char *tt; + type = ACI_TARGET_DN; + /* Keep a copy of the target attr */ + if (aci_item->target) { + return (ACL_SYNTAX_ERR); + } + if ( (s = strstr( str, "!=" )) != NULL ) { + type |= ACI_TARGET_NOT; + strncpy(s, " ", 1); + } + + /* Convert it to lower as slapi_dn_normalize() does not */ + for (tt = str; *tt; tt++) *tt = TOLOWER ( *tt ); + + if ( (s = strchr( str, '=' )) != NULL ) { + value = s + 1; + slapi_dn_normalize(value); + len = strlen ( value ); + if (*value == '"' && value[len-1] == '"'){ + value[len-1] = '\0'; + value++; + } + __acl_strip_leading_space(&value); + } else { + return ( ACL_SYNTAX_ERR ); + } + + if ( strncasecmp ( value, LDAP_URL_prefix , LDAP_URL_prefix_len) ) + return ( ACL_SYNTAX_ERR ); + + value += LDAP_URL_prefix_len; + len = strlen ( value ); + tstr = (char *) slapi_ch_malloc ( targetdnlen + len + 4 ); + sprintf ( tstr, "(target=%s)", value); + if ( (rv = acl_check_for_target_macro( aci_item, value)) == -1) { + slapi_ch_free ( (void **) &tstr ); + return(ACL_SYNTAX_ERR); + } else if ( rv > 0) { + /* is present, so the type is now ACL_TARGET_MACRO_DN */ + type = ACI_TARGET_MACRO_DN; + } else { + /* it's a normal target with no macros inside */ + f = slapi_str2filter ( tstr ); + } + slapi_ch_free ( (void **) &tstr ); + } else { + /* did start with a 't' but was not a recognsied keyword */ + return(ACL_SYNTAX_ERR); + } + + /* + * Here, it was a recognised keyword that started with 't'. + * Check that the filter associated with ACI_TARGET_DN and + * ACI_TARGET_FILTER are OK. + */ + if (f == NULL) { + /* The following types require a filter to have been created */ + if (type & ACI_TARGET_DN) + return ACL_TARGET_FILTER_ERR; + else if (type & ACI_TARGET_FILTER) + return ACL_TARGETFILTER_ERR; + } else { + int filterChoice; + + filterChoice = slapi_filter_get_choice ( f ); + if ( (type & ACI_TARGET_DN) && + ( filterChoice == LDAP_FILTER_PRESENT)) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__parse_aci: Unsupported filter type:%d\n", filterChoice); + return(ACL_SYNTAX_ERR); + } else if (( filterChoice == LDAP_FILTER_SUBSTRINGS) && + (type & ACI_TARGET_DN)) { + type &= ~ACI_TARGET_DN; + type |= ACI_TARGET_PATTERN; + } + } + + if ((type & ACI_TARGET_DN) || + (type & ACI_TARGET_PATTERN)) { + if (aci_item->target) { + /* There is something already. ERROR */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Multiple targets in the ACL syntax\n", + 0,0,0); + slapi_filter_free(f, 1); + return(ACL_SYNTAX_ERR); + } else { + aci_item->target = f; + } + } else if ( type & ACI_TARGET_FILTER) { + if (aci_item->targetFilter) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Multiple target Filters in the ACL Syntax\n", + 0,0,0); + slapi_filter_free(f, 1); + return(ACL_SYNTAX_ERR); + } else { + aci_item->targetFilter = f; + } + } + break; /* 't' */ + default: + /* Here the keyword did not start with 'v' ot 't' so error */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Unknown keyword at \"%s\"\n Expecting" + " \"target\", \"targetattr\", \"targetfilter\", \"targattrfilters\"" + " or \"version\"\n", str, 0, 0); + return(ACL_SYNTAX_ERR); + }/* switch() */ + + /* Store the type info */ + aci_item->aci_type |= type; + + return 0; +} + +/*************************************************************************** +* acl__sanity_check_acltxt +* +* Check the input ACL text. Reports any errors. Also forgivs if certain +* things are missing. +* +* Input: +* char *str - String containg the acl text +* int *err - error status +* +* Returns: +* 0 --- good status +* <0 --- error +* +* Error Handling: +* None. +* +* +**************************************************************************/ +static int +__aclp__sanity_check_acltxt (aci_t *aci_item, char *str) +{ + NSErr_t errp; + char *s; + ACLListHandle_t *handle = NULL; + char *newstr = NULL; + char *word; + char *next; + + memset (&errp, 0, sizeof(NSErr_t)); + newstr = str; + + while ((s = strstr(newstr, "authenticate")) != NULL) { + char *next; + next = s + 12; + s--; + while (s != str && ldap_utf8isspace(s)) LDAP_UTF8DEC(s); + if (s && *s == ';') { + /* We don't support authenticate stuff */ + return ACL_INVALID_AUTHORIZATION; + + } else { + newstr = next; + } + } + + newstr = slapi_ch_strdup (str); + word = ldap_utf8strtok_r(newstr, " ", &next); + if (strcasecmp (word, "version") == 0) { + word = ldap_utf8strtok_r(NULL, " ", &next); + if (atoi(word) != 3) { + slapi_ch_free ( (void **) &newstr ); + return ACL_INCORRECT_ACI_VERSION; + } + } + slapi_ch_free ( (void **) &newstr ); + + /* We need to normalize the DNs in the userdn and group dn + ** so that, it's only done once. + */ + if ((newstr = __aclp__normalize_acltxt (aci_item, str )) == NULL) { + return ACL_SYNTAX_ERR; + } + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Normalized String:%s\n", newstr, 0,0); + + /* check for acl syntax error */ + if ((handle = (ACLListHandle_t *) ACL_ParseString(&errp, + newstr)) == NULL) { + acl_print_acllib_err(&errp, str); + slapi_ch_free ( (void **) &newstr ); + return ACL_SYNTAX_ERR; + } else { + /* get the rights and the aci type */ + aci_item->aci_handle = handle; + nserrDispose(&errp); + slapi_ch_free ( (void **) &newstr ); + + return 0; + } +} +/****************************************************************************** +* +* acl__normalize_acltxt +* +* +* XXXrbyrne this routine should be re-written when someone eventually +* gets sick enough of it. Same for getNextLAS() below. +* +* Normalize the acltxt i.e normalize all the DNs specified in the +* Userdn and Groupdn rule so that we normalize once here and not +* over and over again at the runtime in the LASes. We have to normalize +* before we generate the handle otherwise it's of no use. +* Also convert deny to deny absolute +* +* The string that comes in is something like: +* version 3.0; acl "Dept domain administration"; allow (all) +* groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP"; ) +* +* Returns NULL on error. +* +******************************************************************************/ +static char * +__aclp__normalize_acltxt ( aci_t * aci_item, char * str ) +{ + + char *s, *p; + char *end; + char *aclstr, *s_aclstr; + char *ret_str = NULL; + int len; + char *ptr, *aclName; + char *nextACE; + char *tmp_str = NULL; + char *acestr = NULL; + char *s_acestr = NULL; + int aci_rights_val = 0; /* bug 389975 */ + + /* make a copy first */ + s_aclstr = aclstr = slapi_ch_strdup ( str ); + + /* The rules are like this version 3.0; acl "xyz"; rule1; rule2; */ + s = strchr (aclstr, ';'); + if ( NULL == s) { + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } + aclstr = ++s; + + /* From DS 4.0, we support both aci (or aci) "name" -- we have to change to acl + ** as libaccess will not like it + */ + s = aclstr; + while (s && ldap_utf8isspace(s)) LDAP_UTF8INC(s); + *(s+2 ) = 'l'; + + aclName = s+3; + + s = strchr (aclstr, ';'); + if ( NULL == s) { + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } + + aclstr = s; + LDAP_UTF8INC(aclstr); + *s = '\0'; + + /* Here aclName is the acl description string */ + aci_item->aclName = slapi_ch_strdup ( aclName ); + + aclutil_str_appened (&ret_str, s_aclstr); + aclutil_str_appened (&ret_str, ";"); + + /* start with the string */ + acestr = aclstr; + + /* + * Here acestr is something like: + * + * " allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP";)" + * + * + */ + +normalize_nextACERule: + + /* now we are in the rule part */ + tmp_str = acestr; + s = strchr (tmp_str, ';'); + if ( s == NULL) { + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } + nextACE = s; + LDAP_UTF8INC(nextACE); + *s = '\0'; + + /* acestr now will hold copy of the ACE. Also add + ** some more space in case we need to add "absolute" + ** for deny rule. We will never need more 2 times + ** the len. + */ + len = strlen (tmp_str); + s_acestr = acestr = slapi_ch_calloc ( 1, 2 * len); + + __acl_strip_leading_space(&tmp_str); + + /* + * Now it's something like: + * allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP"; + */ + if (strncasecmp(tmp_str, "allow", 5) == 0) { + memcpy(acestr, tmp_str, len); + tmp_str += 5; + /* gather the rights */ + aci_rights_val = __aclp__get_aci_right (tmp_str);/* bug 389975 */ + aci_item->aci_type |= ACI_HAS_ALLOW_RULE; + + } else if (strncasecmp(tmp_str, "deny", 4) == 0) { + char *d_rule ="deny absolute"; + /* Then we have to add "absolute" to the deny rule + ** What we are doing here is to tackle this situation. + ** + ** allow -- deny -- allow + ** deny -- allow + ** + ** by using deny absolute we force the precedence rule + ** i.e deny has a precedence over allow. Since there doesn't + ** seem to be an easy to detect the mix, forcing this + ** to all the deny rules will do the job. + */ + __acl_strip_leading_space(&tmp_str); + tmp_str += 4; + + /* We might have an absolute there already */ + if ((s = strstr (tmp_str, "absolute")) != NULL) { + tmp_str = s; + tmp_str += 8; + } + /* gather the rights */ + aci_rights_val = __aclp__get_aci_right (tmp_str);/* bug 389975 */ + aci_item->aci_type |= ACI_HAS_DENY_RULE; + + len = strlen ( d_rule ); + memcpy (acestr, d_rule, len ); + memcpy (acestr+len, tmp_str, strlen (tmp_str) ); + } else { + /* wrong syntax */ + aci_rights_val = -1 ; + } + if (aci_rights_val == -1 ) + { + /* wrong syntax */ + slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_acestr ); + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } else + aci_item->aci_access |= aci_rights_val; + + + /* Normalize all the DNs in the userdn rule */ + + /* + * + * Here acestr starts like this: + * " allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP" + */ + + s = __aclp__getNextLASRule(aci_item, acestr, &end); + while ( s ) { + if ( 0 == strncmp ( s, DS_LAS_USERDNATTR, 10) || + ( 0 == strncmp ( s, DS_LAS_USERATTR, 8))) { + /* + ** For userdnattr/userattr rule, the resources changes and hence + ** we cannot cache the result. See above for more comments. + */ + aci_item->aci_elevel = ACI_ELEVEL_USERDNATTR; + } else if ( 0== strncmp ( s, DS_LAS_USERDN, 6)) { + p = strstr ( s, "="); + p--; + if ( strncmp (p, "!=", 2) == 0) + aci_item->aci_type |= ACI_CONTAIN_NOT_USERDN; + + /* XXXrbyrne + * Here we need to scan for more ldap:/// within + * this userdn rule type: + * eg. userdn = "ldap:///cn=joe,o=sun.com || ldap:///self" + * This is handled correctly in DS_LASUserDnEval + * but the bug here is not setting ACI_USERDN_SELFRULE + * which would ensure that acl info is not cached from + * one resource entry to the next. (bug 558519) + */ + p = strstr ( p, "ldap"); + if (p == NULL) { + /* must start with ldap */ + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + p += 8; /* for ldap:/// */ + if( __aclp__dn_normalize (p, end) == NULL) { + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + + /* we have a rule like userdn = "ldap:///blah". s points to blah now. + ** let's find if we have a SELF rule like userdn = "ldap:///self". + ** Since the resource changes on entry basis, we can't cache the + ** evalation of handle for all time. The cache result is valid + ** within the evaluation of that resource. + */ + if (strncasecmp(p, "self", 4) == 0) { + aci_item->aci_ruleType |= ACI_USERDN_SELFRULE; + } else if ( strncasecmp(p, "anyone", 6) == 0 ) { + aci_item->aci_elevel = ACI_ELEVEL_USERDN_ANYONE; + + } else if ( strncasecmp(p, "all", 3) == 0 ) { + if ( aci_item->aci_elevel > ACI_ELEVEL_USERDN_ALL ) + aci_item->aci_elevel = ACI_ELEVEL_USERDN_ALL; + + } else { + if ( aci_item->aci_elevel > ACI_ELEVEL_USERDN ) + aci_item->aci_elevel = ACI_ELEVEL_USERDN; + } + + /* See if we have a parameterized rule */ + __aclp_chk_paramRules ( aci_item, p, end ); + } else if ( 0 == strncmp ( s, DS_LAS_GROUPDNATTR, 11)) { + /* + ** For groupdnattr rule, the resources changes and hence + ** we cannot cache the result. See above for more comments. + */ + /* Find out if we have a URL type of rule */ + if ((p= strstr (s, "ldap")) != NULL) { + if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDNATTR_URL ) + aci_item->aci_elevel = ACI_ELEVEL_GROUPDNATTR_URL; + } else if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDNATTR ) { + aci_item->aci_elevel = ACI_ELEVEL_GROUPDNATTR; + } + aci_item->aci_ruleType |= ACI_GROUPDNATTR_RULE; + } else if ( 0 == strncmp ( s, DS_LAS_GROUPDN, 7)) { + + p = strstr ( s, "="); + p--; + if ( strncmp (p, "!=", 2) == 0) + aci_item->aci_type |= ACI_CONTAIN_NOT_GROUPDN; + + p = strstr ( s, "ldap"); + if (p == NULL) { + /* must start with ldap */ + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + p += 8; + if (__aclp__dn_normalize (p, end) == NULL) { + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + /* check for param rules */ + __aclp_chk_paramRules ( aci_item, p, end ); + + if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDN ) + aci_item->aci_elevel = ACI_ELEVEL_GROUPDN; + aci_item->aci_ruleType |= ACI_GROUPDN_RULE; + + } else if ( 0 == strncmp ( s, DS_LAS_ROLEDN, 6)) { + + p = strstr ( s, "="); + p--; + if ( strncmp (p, "!=", 2) == 0) + aci_item->aci_type |= ACI_CONTAIN_NOT_ROLEDN; + + p = strstr ( s, "ldap"); + if (p == NULL) { + /* must start with ldap */ + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + p += 8; + if (__aclp__dn_normalize (p, end) == NULL) { + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + /* check for param rules */ + __aclp_chk_paramRules ( aci_item, p, end ); + + /* XXX need this for roledn ? + if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDN ) + aci_item->aci_elevel = ACI_ELEVEL_GROUPDN;*/ + aci_item->aci_ruleType |= ACI_ROLEDN_RULE; + } + s = ++end; + s = __aclp__getNextLASRule(aci_item, s, &end); + }/* while */ + + /* get the head of the string */ + acestr = s_acestr; + len = strlen( acestr); + ptr = acestr +len-1; + while (*ptr && *ptr != '\"' && *ptr != ')' ) *ptr-- = ' '; + ptr++; + *ptr = ';'; + + aclutil_str_appened (&ret_str, acestr); + if (s_acestr) { + slapi_ch_free ( (void **) &s_acestr ); + } + s_acestr = NULL; + + if (nextACE) { + s = strstr (nextACE, "allow"); + if (s == NULL) s = strstr (nextACE, "deny"); + if (s == NULL) { + if (nextACE && *nextACE != '\0') + aclutil_str_appened (&ret_str, nextACE); + slapi_ch_free ( (void **) &s_aclstr ); + return (ret_str); + } + acestr = nextACE; + goto normalize_nextACERule; + } + + slapi_ch_free ( (void **) &s_aclstr ); + return (ret_str); +} +/* + * + * acl__getNextLASRule + * Find the next rule. + * + * Returns: + * endOfCurrRule - end of current rule + * nextRule - start of next rule + */ +static char * +__aclp__getNextLASRule (aci_t *aci_item, char *original_str , char **endOfCurrRule) +{ + char *newstr, *word, *next, *start, *end; + char *ruleStart = NULL; + int len, ruleLen; + int in_dn_expr = 0; + + *endOfCurrRule = NULL; + end = start = NULL; + + newstr = slapi_ch_strdup (original_str); + + if ( (strncasecmp(newstr, "allow", 5) == 0) || + (strncasecmp(newstr, "deny", 4) == 0) ) { + word = ldap_utf8strtok_r(newstr, ")", &next); + } + else { + word = ldap_utf8strtok_r(newstr, " ", &next); + } + + /* + * The first word is of no interest -- skip it + * it's allow or deny followed by the rights (<rights>), + * so skip over the rights as well or it's 'and', 'or',.... + */ + + while ( (word = ldap_utf8strtok_r(NULL, " ", &next)) != NULL) { + int got_rule = 0; + int ruleType = 0; + /* + ** The next word must be one of these to be considered + ** a valid rule. + ** This is making me crazy. We might have a case like + ** "((userdn=". strtok is returning me that word. + */ + len = strlen ( word ); + word [len] = '\0'; + + if ( (ruleStart= strstr(word, DS_LAS_USERDNATTR)) != NULL) { + ruleType |= ACI_USERDNATTR_RULE; + ruleLen = strlen ( DS_LAS_USERDNATTR) ; + } else if ( (ruleStart = strstr(word, DS_LAS_USERDN)) != NULL) { + ruleType = ACI_USERDN_RULE; + ruleLen = strlen ( DS_LAS_USERDN); + in_dn_expr = 1; + } else if ( (ruleStart = strstr(word, DS_LAS_GROUPDNATTR)) != NULL) { + ruleType = ACI_GROUPDNATTR_RULE; + ruleLen = strlen ( DS_LAS_GROUPDNATTR) ; + } else if ((ruleStart= strstr(word, DS_LAS_GROUPDN)) != NULL) { + ruleType = ACI_GROUPDN_RULE; + ruleLen = strlen ( DS_LAS_GROUPDN) ; + in_dn_expr = 1; + } else if ((ruleStart = strstr(word, DS_LAS_USERATTR)) != NULL) { + ruleType = ACI_USERATTR_RULE; + ruleLen = strlen ( DS_LAS_USERATTR) ; + } else if ((ruleStart= strstr(word, DS_LAS_ROLEDN)) != NULL) { + ruleType = ACI_ROLEDN_RULE; + ruleLen = strlen ( DS_LAS_ROLEDN); + in_dn_expr = 1; + } else if ((ruleStart= strstr(word, DS_LAS_AUTHMETHOD)) != NULL) { + ruleType = ACI_AUTHMETHOD_RULE; + ruleLen = strlen ( DS_LAS_AUTHMETHOD); + } else if ((ruleStart = strstr(word, ACL_ATTR_IP)) != NULL) { + ruleType = ACI_IP_RULE; + ruleLen = strlen ( ACL_ATTR_IP) ; + } else if ((ruleStart = strstr(word, DS_LAS_TIMEOFDAY)) != NULL) { + ruleType = ACI_TIMEOFDAY_RULE; + ruleLen = strlen ( DS_LAS_TIMEOFDAY) ; + } else if ((ruleStart = strstr(word, DS_LAS_DAYOFWEEK)) != NULL) { + ruleType = ACI_DAYOFWEEK_RULE; + ruleLen = strlen ( DS_LAS_DAYOFWEEK) ; + } else if ((ruleStart = strstr(word, ACL_ATTR_DNS)) != NULL) { + ruleType = ACI_DNS_RULE; + ruleLen = strlen ( ACL_ATTR_DNS) ; + } + /* Here, we've found a space...if we were in in_dn_expr mode + * and we'vve found a closure for that ie.a '"' or a ')' + * eg. "'ldap:///all"' or 'ldap:///all")' then exit in_dn_expr mode. + */ + if ( in_dn_expr && (word[len-1] == '"' || + len>1 && word[len-2] == '"' || + len>2 && word[len-3] == '"')) { + in_dn_expr = 0; + } + + /* + * ruleStart may be NULL as word could be (all) for example. + * this word will just be skipped--we're really waiting for + * userdn or groupdn or... + */ + + if ( ruleStart && ruleType ) { + /* Look in the current word for "=" or else look into + ** the next word -- if none of them are true, then this + ** is not the start of the rule + */ + char *tmpStr = ruleStart + ruleLen; + if ( strchr ( tmpStr, '=') || + ((word = ldap_utf8strtok_r(NULL, " ", &next) ) && + word && ((strncmp ( word, "=", 1) == 0 ) || + (strncmp ( word, "!=",2) ==0) || + (strncmp ( word, ">", 1) == 0 ) || + (strncmp ( word, "<", 1) == 0 ) || + (strncmp ( word, "<", 1) == 0 ) || + (strncmp ( word, "<=",2) ==0 ) || + (strncmp ( word, ">=",2) ==0) || + (strncmp ( word, "=>",2) ==0) || + (strncmp ( word, "=<",2) ==0)) + ) ){ + aci_item->aci_ruleType |= ruleType; + got_rule = 1; + } + } + if ( NULL == start && got_rule ) { + /* + * We've just found a rule start--keep going though because + * we need to return the end of this rule too. + */ + start= ruleStart; + got_rule = 0; + } else { + /* + * Here, we have a candidate for the end of the rule we've found + * (the start of which is currently in start). + * But we need to be sure it really is the end and not a + * "fake end" due to a keyword bbeing embeded in a dn. + */ + if (word && !in_dn_expr && + ((strcasecmp(word, "and") == 0) || + (strcasecmp(word, "or") == 0) || + (strcasecmp(word, "not") == 0) || + (strcasecmp(word, ";") == 0))) { + /* If we have start, then it really is the end */ + word--; + if (start) { + end = word; + break; + } else { + /* We found a fake end, but we've no start so keep going */ + } + } + } + } /* while */ + + + if ( end ) { + /* Found an end to the rule and it's not the last rule */ + len = end - newstr; + end = original_str +len; + while ( (end != original_str) && *end != '\"') end--; + *endOfCurrRule = end; + len = start - newstr; + ruleStart = original_str + len; + } else { + /* Walked off the end of the string so it's the last rule */ + end = original_str + strlen(original_str)-1; + while ( (end != original_str) && *end != '\"') end--; + *endOfCurrRule = end; + } + if ( start ) { + /* Got a rule, fixup the pointer */ + len = start - newstr; + ruleStart = original_str + len; + } + slapi_ch_free ( (void **) &newstr ); + + /* + * Here, ruleStart points to the start of the next rule in original_str. + * end points to the end of this rule. + */ + + return ( ruleStart ); +} +/****************************************************************************** +* +* __aclp__dn_normalize +* +* Normalize the DN INPLACE. This routine is similar to slapi_dn_normalize() +* except various small stuff at the end. +* Normalize until the "end" and not to the end of string. +* +******************************************************************************/ +static char * +__aclp__dn_normalize( char *dn , char *end) +{ + char *d; + + if ((end - dn) < 0) { + return(NULL); + } + + d = slapi_dn_normalize_to_end ( dn, end ); + + /* Do I have the quotes already */ + if (*d != '\"' ) { + /* + ** We are taking care of this situation + ** " ") ". We need to remove the space + ** infront and tack it after the quote like this. + ** "" ) ". + */ + + *d = '\"'; + d++; + while (*d && *d != '\"') *d++ = ' '; + *d = ' '; + } + + return( dn ); +} +/*************************************************************************** +* acl__get_aci_right +* +* Go thru the one acl text str and figure our the rights declared. +* +*****************************************************************************/ +static int +__aclp__get_aci_right (char *str) +{ + + char *sav_str = slapi_ch_strdup(str); + char *t, *tt; + int type = 0; + char *delimiter = ","; + char *val = NULL; + int aclval = 0; + + t = sav_str; + __acl_strip_leading_space( &t ); + + if (*t == '(' ) { + if ((tt = slapi_find_matching_paren(t)) == NULL) { + slapi_ch_free ( (void **) &sav_str ); + return -1; + } else { + t++; /* skip the first character which is ( */ + *tt = '\0'; + } + } else { + slapi_ch_free ( (void **) &sav_str ); + return -1; + } + /* get the tokens separated by "," */ + val = ldap_utf8strtok_r(t,delimiter, &tt); + if (val == NULL ) + { + slapi_ch_free ( (void **) &sav_str ); + return -1; + } + while (val != NULL) + { + /* get the corresponding integer value */ + aclval = get_acl_rights_as_int(val); + if (aclval == -1 ) + { + type = -1; + break; + } + type |= aclval; + val = ldap_utf8strtok_r(NULL,delimiter, &tt); /* get the next token */ + } + + slapi_ch_free ( (void **) &sav_str ); + return type; + +} + +static int get_acl_rights_as_int( char * strValue) +{ + + if (strValue == NULL ) + return -1; + /* First strip out the leading and trailing spaces */ + __acl_strip_leading_space( &strValue ); + __acl_strip_trailing_space( strValue ); + + /* We have to do a strcasecmp (case insensitive cmp) becuase we should return + only if it is exact match. */ + + if (strcasecmp (strValue, "read") == 0 ) + return SLAPI_ACL_READ; + else if (strcasecmp (strValue, "write") == 0 ) + return SLAPI_ACL_WRITE; + else if (strcasecmp (strValue, "search") == 0 ) + return SLAPI_ACL_SEARCH; + else if (strcasecmp (strValue, "compare") == 0 ) + return SLAPI_ACL_COMPARE; + else if (strcasecmp (strValue, "add") == 0 ) + return SLAPI_ACL_ADD; + else if (strcasecmp (strValue, "delete") == 0 ) + return SLAPI_ACL_DELETE; + else if (strcasecmp (strValue, "proxy") == 0 ) + return SLAPI_ACL_PROXY; + else if (strcasecmp (strValue, "selfwrite") == 0 ) + return (SLAPI_ACL_SELF | SLAPI_ACL_WRITE); + else if (strcasecmp (strValue, "all") == 0 ) + return SLAPI_ACL_ALL; + else + return -1; /* error */ +} +/*************************************************************************** +* +* acl_access2str +* +* Convert the access bits into character strings. +* Example: "read, self read" +* +* Input: +* +* int access - The access in bits +* char **rights - rights in chars +* +* Returns: +* NULL - No rights to start with +* right - rights converted. +* +* Error Handling: +* None. +* +**************************************************************************/ +char * +acl_access2str(int access) +{ + + if ( access & SLAPI_ACL_COMPARE ) { + return access_str_compare; + } else if ( access & SLAPI_ACL_SEARCH ) { + return access_str_search; + } else if ( access & SLAPI_ACL_READ ) { + return access_str_read; + } else if ( access & SLAPI_ACL_DELETE) { + return access_str_delete; + } else if ( access & SLAPI_ACL_ADD) { + return access_str_add; + } else if ( (access & SLAPI_ACL_WRITE ) && (access & SLAPI_ACL_SELF)) { + return access_str_selfwrite; + } else if (access & SLAPI_ACL_WRITE ) { + return access_str_write; + } else if (access & SLAPI_ACL_PROXY ) { + return access_str_proxy; + } + + return NULL; +} +/*************************************************************************** +* +* __aclp__init_targetattr +* +* Parse the targetattr string and create a array of attrs. This will +* help us to do evaluation at run time little faster. +* entry. +* Here, also extract any target value filters. +* +* Input: +* aci_t *aci -- The aci item +* char *str -- the targetattr string +* +* Returns: +* ACL_OK - everything ok +* ACL_SYNTAX_ERROR - in case of error. +* +* +***************************************************************************/ +static int +__aclp__init_targetattr (aci_t *aci, char *attr_val) +{ + + int numattr=0; + Targetattr **attrArray; + char *s, *end_attr, *str; + int len; + Targetattr *attr = NULL; + + s = strchr (attr_val, '='); + s++; + __acl_strip_leading_space(&s); + len = strlen(s); + if (*s == '"' && s[len-1] == '"') { + s[len-1] = '\0'; + s++; + } + + str = s; + attrArray = aci->targetAttr; + + if (attrArray[0] != NULL) { + /* + ** That means we are visiting more than once. + ** Syntax error. We have a case like: (targetattr) (targetattr) + */ + return ACL_SYNTAX_ERR; + } + + while (str != 0 && *str != 0) { + + __acl_strip_leading_space(&str); + + if ((end_attr = strstr(str, "||")) != NULL) { + /* skip the two '|' chars */ + auto char *t = end_attr; + LDAP_UTF8INC(end_attr); + LDAP_UTF8INC(end_attr); + *t = 0; + } + __acl_strip_trailing_space(str); + + /* + * Here: + * end_attr points to the next attribute thing. + * + * str points to the current one to be processed and it looks like this: + * rbyrneXXX Watchout is it OK to use : as the speperator ? + * cn + * c*n* + * * + * The attribute goes in the attrTarget list. + * + */ + + + attr = (Targetattr *) slapi_ch_malloc (sizeof (Targetattr)); + memset (attr, 0, sizeof(Targetattr)); + + if (strchr(str, '*')) { + + /* It contains a * so it's something like * or cn* */ + if (strcmp(str, "*" ) != 0) { + char line[100]; + char *lineptr = &line[0]; + char *newline = NULL; + int lenstr = 0; + struct slapi_filter *f = NULL; + + if ((lenstr = strlen(str)) > 91) { /* 100 - 8 for "(attr =%s)" */ + newline = slapi_ch_malloc(lenstr + 9); + lineptr = newline; + } + + attr->attr_type = ACL_ATTR_FILTER; + sprintf (lineptr, "(attr =%s)", str); + f = slapi_str2filter (lineptr); + + if (f == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "__aclp__init_targetattr:Unable to generate filter (%s)\n", lineptr,0,0); + } else { + attr->u.attr_filter = f; + } + + if (newline) slapi_ch_free((void **) &newline); + } else { + attr->attr_type = ACL_ATTR_STAR; + attr->u.attr_str = slapi_ch_strdup (str); + } + + } else { + attr->u.attr_str = slapi_ch_strdup (str); + attr->attr_type = ACL_ATTR_STRING; + } + + + /* + * Add the attr to the targetAttr list + */ + + attrArray[numattr] = attr; + numattr++; + if (!(numattr % ACL_INIT_ATTR_ARRAY)) { + aci->targetAttr = (Targetattr **) slapi_ch_realloc ( + (void *) aci->targetAttr, + (numattr+ACL_INIT_ATTR_ARRAY) * + sizeof(Targetattr *)); + attrArray = aci->targetAttr; + } + + + /* Move on to the next attribute in the list */ + str = end_attr; + + } /* while */ + + /* NULL teminate the list */ + attrArray[numattr] = NULL; + return 0; +} + +void +acl_strcpy_special (char *d, char *s) +{ + for (; *s; LDAP_UTF8INC(s)) { + switch (*s) { + case '.': + case '\\': + case '[': + case ']': + case '*': + case '+': + case '^': + case '$': + *d = '\\'; + LDAP_UTF8INC(d); + /* FALL */ + default: + d += LDAP_UTF8COPY(d, s); + } + } + *d = '\0'; +} +/*************************************************************************** +* +* acl_verify_aci_syntax +* verify if the aci's being added for the entry has a valid syntax or not. +* +* Input: +* Slapi_Entry *e - The Slapi_Entry itself +* char **errbuf; -- error message +* +* Returns: +* -1 (ACL_ERR) - Syntax error +* 0 - No error +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_verify_aci_syntax (Slapi_Entry *e, char **errbuf) +{ + + if (e != NULL) { + Slapi_DN *e_sdn; + int rv; + Slapi_Attr *attr = NULL; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + e_sdn = slapi_entry_get_sdn ( e ); + + slapi_entry_attr_find (e, aci_attr_type, &attr); + if (! attr ) return 0; + + i= slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + rv=acl_verify_syntax ( e_sdn, attrVal); + if ( 0 != rv ) { + aclutil_print_err(rv, e_sdn, attrVal, errbuf); + return ACL_ERR; + } + i = slapi_attr_next_value ( attr, i, &sval ); + } + } + return(0); +} +/*************************************************************************** +* +* acl__verify_syntax +* Called from slapi_acl_check_mods() to verify if the new aci being +* added/replaced has the right syntax or not. +* +* Input: +* Slapi_DN *e_sdn - sdn of the entry +* berval *bval - The berval containg the aci value +* +* Returns: +* return values from acl__parse_aci() +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_verify_syntax(const Slapi_DN *e_sdn, const struct berval *bval) +{ + aci_t *aci_item; + int rv = 0; + char *str; + aci_item = acllist_get_aci_new (); + slapi_sdn_set_ndn_byval ( aci_item->aci_sdn, slapi_sdn_get_ndn ( e_sdn ) ); + + /* make a copy the the string */ + str = slapi_ch_strdup(bval->bv_val); + rv = acl_parse(str, aci_item); + + /* cleanup before you leave ... */ + acllist_free_aci (aci_item); + slapi_ch_free ( (void **) &str ); + return(rv); +} +static void +__aclp_chk_paramRules ( aci_t *aci_item, char *start, char *end) +{ + + size_t len; + char *str; + char *p, *s; + + + len = end - start; + + s = str = (char *) slapi_ch_calloc(1, len + 1); + memcpy ( str, start, len); + while ( (p= strchr ( s, '$')) != NULL) { + p++; /* skip the $ */ + if ( 0 == strncasecmp ( p, "dn", 2)) + aci_item->aci_ruleType |= ACI_PARAM_DNRULE; + else if ( 0 == strncasecmp ( p, "attr", 4)) + aci_item->aci_ruleType |= ACI_PARAM_ATTRRULE; + + s = p; + } + slapi_ch_free ( (void **) &str ); +} + +/* + * Check for an ocurrence of a macro aci in the target. + * value is the normalized target string. + * + * this is something like: + * (target="ldap:///cn=*,ou=people,($dn),o=sun.com") + * + * + * returns 1 if there is a $dn present. + * returns 0 if not. + * returns -1 is syntax error. + * If succes then: + * ACI_TARGET_MACRO_DN is the type. + * type can also include, ACI_TARGET_PATTERN, ACI_TARGET_NOT. + * Also aci_item->aci_macro->match_this is set to be + * cn=*,ou=people,($dn),o=sun.com, to be used later. + * + * . we allow at most one ($dn) in a target. + * . if a "*" accurs with it, it must be the first component and at most + * once. + * . it's ok for ($dn) to occur on it's own in a target, but if it appears in + * a user rule, then it must be in the target. + * + * + * +*/ + +static int +acl_check_for_target_macro( aci_t *aci_item, char *value) +{ + + char *str = NULL; + + str = strstr( value, ACL_TARGET_MACRO_DN_KEY); + + if (str != NULL) { + aci_item->aci_type &= ~ACI_TARGET_DN; + aci_item->aci_type |= ACI_TARGET_MACRO_DN; + aci_item->aci_macro = (aciMacro *)slapi_ch_malloc(sizeof(aciMacro)); + aci_item->aci_macro->match_this = slapi_ch_strdup(value); + aci_item->aci_macro->macro_ptr = strstr( aci_item->aci_macro->match_this, + ACL_TARGET_MACRO_DN_KEY); + return(1); + } + + return(0); +} + +/* Strip trailing spaces from str by writing '\0' into them */ + +static void +__acl_strip_trailing_space( char *str) { + + char *ptr = NULL; + int len = 0; + + if (*str) { + /* ignore trailing whitespace */ + len = strlen(str); + ptr = str+len-1; + while(ldap_utf8isspace(ptr)){ *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } +} + +/* + * Strip leading spaces by resetting str to point to the first + * non-space charater. +*/ + +static void +__acl_strip_leading_space( char **str) { + + char *tmp_ptr = NULL; + + tmp_ptr = *str; + while ( *tmp_ptr && ldap_utf8isspace( tmp_ptr ) ) LDAP_UTF8INC(tmp_ptr); + *str = tmp_ptr; +} + + +/* + * str is a string containing an LDAP filter. + * Trim off enclosing quotes and enclosing + * superfluous brackets. + * The result is duped so it can be kept. +*/ + +static char * +__acl_trim_filterstr( char * str ) { + + char *tmpstr; + int len; + char *end; + + tmpstr = str; + + /* If the last char is a "," take it out */ + + len = strlen (tmpstr); + if (len>0 && (tmpstr[len-1] == ',')) { + tmpstr [len-1] = '\0'; + } + + + /* Does it have quotes around it */ + len = strlen (tmpstr); + if (*tmpstr == '"' && tmpstr[len-1] == '"') { + tmpstr [len-1] = '\0'; + tmpstr++; + } + + str = tmpstr; + + /* If we have a filter like + ** (((&(...) (...)))), we need to get rid of the + ** multiple parens or slapi_str2filter will not + ** evaluate properly. Need to package like + ** (filter ). probably I should fix str2filter + ** code. + */ + + while (*tmpstr++ == '(' && *tmpstr == '(') { + if ((end = slapi_find_matching_paren( str )) != NULL) { + *end = '\0'; + str++; + } + } + + return( slapi_ch_strdup(str)); +} + +/* + * Here str points to a targetattrfilters thing which looks tlike this: + * + * targetattrfilters="add=attr1:F1 && attr2:F2 ... && attrn:Fn, + * del=attr1:F1 && attr2:F2... && attrn:Fn") + * + * + */ + +static int __acl__init_targetattrfilters( aci_t *aci, char *input_str) { + + int numattr=0; + char *s, *str; + int len; + char *addlistptr = NULL; + char *dellistptr = NULL; + + if (aci->targetAttrAddFilters != NULL || + aci->targetAttrDelFilters != NULL) { + + /* + ** That means we are visiting more than once. + ** Syntax error. + ** We have a case like: (targetattrfilters) (targetattrfilters) + */ + + return ACL_SYNTAX_ERR; + } + + /* First, skip the "targetattrfilters" */ + + s = strchr (input_str, '='); + s++; /* skip the = */ + __acl_strip_leading_space(&s); /* skip to next significant character */ + len = strlen(s); /* Knock off the " and trailing ) */ + if (*s == '"' && s[len-1] == '"') { + s[len-1] = '\0'; + s++; /* skip the first " */ + } else { /* No matching quotes */ + + return (ACL_SYNTAX_ERR); + } + + str = s; + + /* + * Here str looks like add=attr1:F1...attrn:Fn, + * del=attr1:F1...attrn:Fn + * + * extract the add and del filter lists and process each one + * in turn. + */ + + s = strchr (str, '='); + *s = '\0'; + s++; /* skip the = */ + __acl_strip_leading_space(&s); /* start of the first filter list */ + + + /* + * Now str is add or del + * s points to the first filter list. + */ + + if (strcmp(str, "add") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_ADD_FILTERS; + addlistptr = s; + + /* Now isolate the first filter list. */ + if ((str = strstr(s , "del=")) || ((str = strstr(s , "del ="))) ) { + str--; + *str = '\0'; + *str++; + } + + + } else if (strcmp(str, "del") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_DEL_FILTERS; + dellistptr = s; + + /* Now isolate the first filter list. */ + if ((str = strstr(s , "add=")) || ((str = strstr(s , "add ="))) ) { + str--; + *str = '\0'; + *str++; + } + } else { + return(ACL_SYNTAX_ERR); + } + + __acl_strip_trailing_space(s); + + /* + * Here, we have isolated the first filter list. + * There may be a second one. + * Now, str points to the start of the + * string that contains the second filter list. + * If there is none then str is NULL. + */ + + if (str != NULL ){ + + __acl_strip_leading_space(&str); + s = strchr (str, '='); + *s = '\0'; + s++; + __acl_strip_trailing_space(str); + __acl_strip_leading_space(&s); + + + /* + * s points to the start of the second filter list. + * str is add or del + */ + + if (aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + if (strcmp(str, "del") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_DEL_FILTERS; + dellistptr = s; + } else { + return(ACL_SYNTAX_ERR); + } + } else if ( aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) { + if (strcmp(str, "add") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_ADD_FILTERS; + addlistptr = s; + } else { + return(ACL_SYNTAX_ERR); + } + } + } + + /* + * addlistptr points to the add filter list. + * dellistptr points to the del filter list. + * In both cases the strings have been leading and trailing space + * stripped. + * Either may be NULL. + */ + + if (process_filter_list( &aci->targetAttrAddFilters, addlistptr) + == ACL_SYNTAX_ERR) { + return( ACL_SYNTAX_ERR); + } + + if (process_filter_list( &aci->targetAttrDelFilters, dellistptr) + == ACL_SYNTAX_ERR) { + return( ACL_SYNTAX_ERR); + } + + return(0); + +} + +/* + * We have a list of filters that looks like this: + * attr1:F1 &&....attrn:Fn + * + * We need to put each component into a targetattrfilter component of + * the array. + * +*/ + +static int process_filter_list( Targetattrfilter ***input_attrFilterArray, + char * input_str) { + + char *str, *end_attr, *tmp_attr; + Targetattrfilter *attrfilter = NULL; + int numattr=0; + Targetattrfilter **attrFilterArray = NULL; + + str = input_str; + + while (str != 0 && *str != 0) { + + if ((end_attr = strstr(str, "&&")) != NULL) { + /* skip the two '|' chars */ + auto char *t = end_attr; + LDAP_UTF8INC(end_attr); + LDAP_UTF8INC(end_attr); + *t = 0; + } + __acl_strip_trailing_space(str); + __acl_strip_leading_space(&str); + + /* + * Here: + * end_attr points to the next attribute thing. + * + * str points to the current one to be processed and it looks like + * this: + * + * attr1:F1 + * + */ + + attrfilter = (Targetattrfilter *) slapi_ch_malloc (sizeof (Targetattrfilter)); + memset (attrfilter, 0, sizeof(Targetattrfilter)); + + if ((tmp_attr = strstr( str,":")) != NULL) { + + if ( __acl_init_targetattrfilter( attrfilter, str ) != 0 ) { + slapi_ch_free((void**)&attrfilter); + return(ACL_SYNTAX_ERR); + } + } else { + slapi_ch_free((void**)&attrfilter); + return(ACL_SYNTAX_ERR); + } + + + /* + * Add the attrfilte to the targetAttrFilter list + */ + + + attrFilterArray = (Targetattrfilter **) slapi_ch_realloc ( + (void *) attrFilterArray, + ((numattr+1)*sizeof(Targetattrfilter *)) ); + attrFilterArray[numattr] = attrfilter; + numattr++; + + /* Move on to the next attribute in the list */ + str = end_attr; + + }/* while */ + + /* NULL terminate the list */ + + attrFilterArray = (Targetattrfilter **) slapi_ch_realloc ( + (void *) attrFilterArray, + ((numattr+1)*sizeof(Targetattrfilter *)) ); + attrFilterArray[numattr] = NULL; + + *input_attrFilterArray = attrFilterArray; + return 0; + +} + +/* + * Take str and put it into the attrfilter component. + * + * str looks as follows: attr1:F1 + * + * It has had leading and trailing space stripped. +*/ + +static int __acl_init_targetattrfilter( Targetattrfilter *attrfilter, + char *str ) { + + char *tmp_ptr, *s, *filter_ptr; + Slapi_Filter *f = NULL; + + s = str; + + /* First grab the attribute name */ + + if ( (tmp_ptr = strstr( str, ":")) == NULL ) { + /* No :, syntax error */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Bad targetattrfilter %s:%s\n", + str,"Expecting \":\"",0); + + return(ACL_SYNTAX_ERR); + } + *tmp_ptr = '\0'; + LDAP_UTF8INC(tmp_ptr); + + __acl_strip_trailing_space(s); + + /* s should be the attribute name-make sure it's non-empty. */ + + if ( *s == '\0' ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "No attribute name in targattrfilters\n", + 0,0); + return(ACL_SYNTAX_ERR); + } + + attrfilter->attr_str = slapi_ch_strdup (s); + + /* Now grab the filter */ + + filter_ptr = tmp_ptr; + __acl_strip_leading_space(&filter_ptr); + __acl_strip_trailing_space(filter_ptr); + + /* trim dups the string, so we need to free it later if it's not kept. */ + tmp_ptr = __acl_trim_filterstr(filter_ptr); + + if ((f = slapi_str2filter(tmp_ptr)) == NULL) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Bad targetattr filter for attribute %s:%s\n", + attrfilter->attr_str,tmp_ptr,0); + slapi_ch_free( (void **) &attrfilter->attr_str); + slapi_ch_free( (void **) &tmp_ptr); + return(ACL_SYNTAX_ERR); + } + + /* + * Here verify that the named attribute is the only one + * that appears in the filter. + */ + + if (acl_verify_exactly_one_attribute( attrfilter->attr_str, f) != + SLAPI_FILTER_SCAN_NOMORE) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Exactly one attribute type per filter allowed in targattrfilters (%s)\n", + attrfilter->attr_str, 0); + slapi_ch_free( (void **) &attrfilter->attr_str); + slapi_ch_free( (void **) &tmp_ptr); + slapi_filter_free( f, 1 ); + return(ACL_SYNTAX_ERR); + } + + /* free the tmp_ptr */ + slapi_ch_free( (void **) &tmp_ptr); + attrfilter->filterStr = slapi_ch_strdup (filter_ptr); + attrfilter->filter = f; + + return(LDAP_SUCCESS); +} + +/* + * Returns 0 if attr_name is the only attribute name to + * appear in original_filter AND it appears at least once. + * Otherwise returns STOP_FILTER_SCAN. +*/ + +static int acl_verify_exactly_one_attribute( char *attr_name, + Slapi_Filter *original_filter) { + int error_code; + + return( slapi_filter_apply( original_filter, type_compare, + (void *)attr_name, &error_code)); + +} + +static int type_compare( Slapi_Filter *f, void *arg) { + + /* Compare only the base names: eg cn and cn;lang-eb will be the same. */ + + char *t = (char *)arg; + char *filter_type; + int rc = SLAPI_FILTER_SCAN_STOP; + + if (slapi_filter_get_attribute_type( f, &filter_type) == 0) { + t = slapi_attr_syntax_normalize(t); + filter_type = slapi_attr_syntax_normalize(filter_type); + + if (slapi_attr_type_cmp(filter_type, t, 1) == 0) { + rc = SLAPI_FILTER_SCAN_CONTINUE; + } + + slapi_ch_free( (void **)&t ); + slapi_ch_free( (void **)&filter_type ); + } + + return rc; +} diff --git a/ldap/servers/plugins/acl/aclplugin.c b/ldap/servers/plugins/acl/aclplugin.c new file mode 100644 index 00000000..a39ef3cd --- /dev/null +++ b/ldap/servers/plugins/acl/aclplugin.c @@ -0,0 +1,355 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * There are 3 ACL PLUGIN points + * PREOP, POSTOP and ACL plugin + * + */ +#include "acl.h" +#include "dirver.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +static Slapi_PluginDesc pdesc = { "acl", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "acl access check plugin" }; +char *plugin_name = ACL_PLUGIN_NAME; + +/* Prototypes */ + +static int aclplugin_preop_search ( Slapi_PBlock *pb ); +static int aclplugin_preop_modify ( Slapi_PBlock *pb ); +static int aclplugin_preop_common ( Slapi_PBlock *pb ); + +/******************************************************************************* + * ACL PLUGIN Architecture + * + * There are 3 registered plugins: + * + * 1) PREOP ACL Plugin + * The preop plugin does all the initialization. It allocate the ACL + * PBlock and copies stuff from the connection if it needs to. + * + * 2) POSTOP ACL Plugin + * The Postop plugin cleans up the ACL PBlock. It copies Back to the + * connection struct. The Postop bind & Unbind cleans up the + * ACL CBlock ( struct hanging from conn struct ). + * + * 3) ACCESSCONTROL Plugin + * Main module which does the access check. There are 5 entry point + * from this plugin + * a) Initilize the ACL system i.e read all the ACLs and generate the + * the ACL List. + * b) Check for ACI syntax. + * c) Check for normal access. + * d) Check for access to a mod request. + * e) Update the in-memory ACL List. + * + *******************************************************************************/ + +/******************************************************************************* + * PREOP + *******************************************************************************/ + +/* Plugin identity is passed by the server in the plugin init function and must + be supplied by the plugin to all internal operations it initiates + */ +void* g_acl_preop_plugin_identity; + +int +acl_preopInit (Slapi_PBlock *pb) +{ + int rc = 0; + + /* save plugin identity to later pass to internal operations */ + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &g_acl_preop_plugin_identity); + + /* Declare plugin version */ + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + + /* Provide descriptive information */ + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void*)&pdesc); + + /* Register functions */ + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void*)aclplugin_preop_search); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void*)aclplugin_preop_search); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void*)aclplugin_preop_modify); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void*)aclplugin_preop_modify); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void*)aclplugin_preop_modify); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void*)aclplugin_preop_modify); + +#if 0 + /* + * XXXmcs: In order to support access control checking from + * extended operations, we need a SLAPI_PLUGIN_PRE_EXTENDED_FN hook. + * But today no such entry point exists. + */ + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_EXTENDED_FN, (void*)aclplugin_preop_modify); +#endif + + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= acl_preop_Init %d\n", rc, 0, 0 ); + return( rc ); +} + +/* For preop search we do two things: + * 1) based on the search base, we preselect the acls. + * 2) also get hold of a acl_pblock for use + */ +static int +aclplugin_preop_search ( Slapi_PBlock *pb ) +{ + int scope; + char *base = NULL; + int optype; + int isRoot; + int rc = 0; + + TNF_PROBE_0_DEBUG(aclplugin_preop_search_start ,"ACL",""); + + slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &optype ); + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + + if ( isRoot ) { + TNF_PROBE_1_DEBUG(aclplugin_preop_search_end ,"ACL","", + tnf_string,isroot,""); + return rc; + } + + slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &base ); + /* For anonymous client doing search nothing needs to be set up */ + if ( optype == SLAPI_OPERATION_SEARCH && aclanom_is_client_anonymous ( pb ) && + ! slapi_dn_issuffix( base, "cn=monitor") ) { + TNF_PROBE_1_DEBUG(aclplugin_preop_search_end ,"ACL","", + tnf_string,anon,""); + return rc; + } + + if ( 0 == ( rc = aclplugin_preop_common( pb ))) { + slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope ); + acllist_init_scan ( pb, scope, base ); + } + + TNF_PROBE_0_DEBUG(aclplugin_preop_search_end ,"ACL",""); + + return rc; +} + +/* + * For rest of the opertion type, we get a hold of the acl + * private Block. + */ +static int +aclplugin_preop_modify ( Slapi_PBlock *pb ) +{ + /* + * Note: since we don't keep the anom profile for modifies, we have to go + * through the regular process to check the access. + */ + return aclplugin_preop_common( pb ); +} + +/* + * Common function that is called by aclplugin_preop_search() and + * aclplugin_preop_modify(). + * + * Return values: + * 0 - all is well; proceed. + * 1 - fatal error; result has been sent to client. + */ +static int +aclplugin_preop_common( Slapi_PBlock *pb ) +{ + char *proxy_dn; /* id being assumed */ + char *dn; /* proxy master */ + char *errtext = NULL; + int lderr; + Acl_PBlock *aclpb; + + TNF_PROBE_0_DEBUG(aclplugin_preop_common_start ,"ACL",""); + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + /* + * The following mallocs memory for proxy_dn, but not the dn. + * The proxy_dn is the id being assumed, while dn + * is the "proxy master". + */ + proxy_dn = NULL; + if ( LDAP_SUCCESS != ( lderr = acl_get_proxyauth_dn( pb, &proxy_dn, + &errtext ))) { + /* + * Fatal error -- send a result to the client and arrange to skip + * any further processing. + */ + slapi_send_ldap_result( pb, lderr, NULL, errtext, 0, NULL ); + TNF_PROBE_1_DEBUG(aclplugin_preop_common_end ,"ACL","", + tnf_string,proxid_error,""); + + return 1; /* skip any further processing */ + } + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &dn ); + + + /* + * The dn is copied into the aclpb during initialization. + */ + if ( proxy_dn) { + TNF_PROBE_0_DEBUG(proxyacpb_init_start,"ACL",""); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "proxied authorization dn is (%s)\n", proxy_dn ); + acl_init_aclpb ( pb, aclpb, proxy_dn, 1 ); + aclpb = acl_new_proxy_aclpb (pb ); + acl_init_aclpb ( pb, aclpb, dn, 0 ); + slapi_ch_free ( (void **) &proxy_dn ); + + TNF_PROBE_0_DEBUG(proxyacpb_init_end,"ACL",""); + + } else { + TNF_PROBE_0_DEBUG(aclpb_init_start,"ACL",""); + acl_init_aclpb ( pb, aclpb, dn, 1 ); + TNF_PROBE_0_DEBUG(aclpb_init_end,"ACL",""); + + } + + TNF_PROBE_0_DEBUG(aclplugin_preop_common_end ,"ACL",""); + + return 0; +} + +/******************************************************************************* + * POSTOP + *******************************************************************************/ + +/******************************************************************************* + * ACCESSCONTROL PLUGIN + *******************************************************************************/ + +void* g_acl_plugin_identity; + +/* For now, the acl component is implemented as 2 different plugins */ +/* Return the right plugin identity */ +void * aclplugin_get_identity(int plug) { + if (plug == ACL_PLUGIN_IDENTITY) + return g_acl_plugin_identity; + if (plug == ACL_PREOP_PLUGIN_IDENTITY) + return g_acl_preop_plugin_identity; + return NULL; +} + +int +aclplugin_init (Slapi_PBlock *pb ) +{ + + int rc = 0; /* OK */ + rc = aclinit_main ( pb ); + + return rc; + +} +int +aclplugin_stop ( Slapi_PBlock *pb ) +{ + int rc = 0; /* OK */ + + /* nothing to be done now */ + return rc; +} + +int +acl_init( Slapi_PBlock *pb ) +{ + int rc =0; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> acl_init\n", 0, 0, 0 ); + + if ( 0 != acl_init_ext() ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to initialize the extensions\n"); + return 1; + } + + /* save plugin identity to later pass to internal operations */ + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &g_acl_plugin_identity); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) aclplugin_init ); + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) aclplugin_stop ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_SYNTAX_CHECK, + (void *) acl_verify_aci_syntax ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_ALLOW_ACCESS, + (void *) acl_access_allowed_main ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_MODS_ALLOWED, + (void *) acl_check_mods ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_MODS_UPDATE, + (void *) acl_modified ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= acl_init %d\n", rc, 0, 0 ); + return( rc ); +} + +/* + * + * acl_access_allowed_main + * Main interface to the plugin. Calls different access check functions + * based on the flag. + * + * + * Returns: + * LDAP_SUCCESS -- access is granted + * LDAP_INSUFFICIENT_ACCESS -- access denied + * <other ldap error> -- ex: opererations error + * + */ +int +acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char **attrs, + struct berval *val, int access , int flags, char **errbuf) +{ + int rc =0; + char *attr = NULL; + + TNF_PROBE_0_DEBUG(acl_access_allowed_main_start,"ACL",""); + + if (attrs && *attrs) attr = attrs[0]; + + if (ACLPLUGIN_ACCESS_READ_ON_ENTRY == flags) + rc = acl_read_access_allowed_on_entry ( pb, e, attrs, access); + else if ( ACLPLUGIN_ACCESS_READ_ON_ATTR == flags) + rc = acl_read_access_allowed_on_attr ( pb, e, attr, val, access); + else if ( ACLPLUGIN_ACCESS_READ_ON_VLV == flags) + rc = acl_access_allowed_disjoint_resource ( pb, e, attr, val, access); + else if ( ACLPLUGIN_ACCESS_MODRDN == flags) + rc = acl_access_allowed_modrdn ( pb, e, attr, val, access); + else if ( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS == flags) + rc = acl_get_effective_rights ( pb, e, attrs, val, access, errbuf ); + else + rc = acl_access_allowed ( pb, e, attr, val, access); + + /* generate the appropriate error message */ + if ( ( rc != LDAP_SUCCESS ) && errbuf && + ( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS != flags ) && + ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE ))) { + + char *edn = slapi_entry_get_dn ( e ); + + acl_gen_err_msg(access, edn, attr, errbuf); + } + + TNF_PROBE_0_DEBUG(acl_access_allowed_main_end,"ACL",""); + + return rc; +} +#ifdef _WIN32 + +int *module_ldap_debug = 0; +void plugin_init_debug_level ( int *level_ptr ) +{ + module_ldap_debug = level_ptr; +} +#endif + diff --git a/ldap/servers/plugins/acl/aclproxy.c b/ldap/servers/plugins/acl/aclproxy.c new file mode 100644 index 00000000..9065bddc --- /dev/null +++ b/ldap/servers/plugins/acl/aclproxy.c @@ -0,0 +1,195 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +#define BEGIN do { +#define END } while(0); + +/* ------------------------------------------------------------ + * LDAPProxyAuth + * + * ProxyAuthControl ::= SEQUENCE { + * authorizationDN LDAPDN + * } + */ +struct LDAPProxyAuth +{ + char *auth_dn; +}; +typedef struct LDAPProxyAuth LDAPProxyAuth; + +/* + * delete_LDAPProxyAuth + */ +static void +delete_LDAPProxyAuth(LDAPProxyAuth *spec) +{ + if (!spec) return; + + slapi_ch_free((void**)&spec->auth_dn); + slapi_ch_free((void**)&spec); +} + +/* + * parse_LDAPProxyAuth + * + * Parse a BER encoded value into the compoents of the LDAP ProxyAuth control. + * The 'version' parameter should be 1 or 2. + * + * Returns an LDAP error code (LDAP_SUCCESS if all goes well) and sets + * *errtextp if appropriate. + */ +static int +parse_LDAPProxyAuth(struct berval *spec_ber, int version, char **errtextp, + LDAPProxyAuth **out) +{ + int lderr = LDAP_OPERATIONS_ERROR; /* pessimistic */ + LDAPProxyAuth *spec = NULL; + BerElement *ber = NULL; + char *errstring = "unable to parse proxied authorization control"; + + + BEGIN + unsigned long tag; + + if ( version != 1 && version != 2 ) { + break; + } + + /* create_LDAPProxyAuth */ + spec = (LDAPProxyAuth*)slapi_ch_calloc(1,sizeof (LDAPProxyAuth)); + if (!spec) { + break; + } + + ber = ber_init(spec_ber); + if (!ber) { + break; + } + + if ( version == 1 ) { + tag = ber_scanf(ber, "{a}", &spec->auth_dn); + } else { + tag = ber_scanf(ber, "a", &spec->auth_dn); + } + if (tag == LBER_ERROR) { + lderr = LDAP_PROTOCOL_ERROR; + break; + } + + /* + * In version 2 of the control, the control value is actually an + * authorization ID (see section 9 of RFC 2829). We only support + * the "dnAuthzId" flavor, which looks like "dn:<DN>" where <DN> is + * an actual DN, e.g., "dn:uid=bjensen,dc=example,dc=com". So we + * need to strip off the dn: if present and reject the operation if + * not. + */ + if (2 == version) { + if ( NULL == spec->auth_dn || strlen( spec->auth_dn ) < 3 || + strncmp( "dn:", spec->auth_dn, 3 ) != 0 ) { + lderr = LDAP_INSUFFICIENT_ACCESS; /* per Proxied Auth. I-D */ + errstring = "proxied authorization id must be a DN (dn:...)"; + break; + } + strcpy( spec->auth_dn, spec->auth_dn + 3 ); + } + + slapi_dn_normalize(spec->auth_dn); + lderr = LDAP_SUCCESS; /* got it! */ + END + + /* Cleanup */ + if (ber) ber_free(ber, 0); + + if ( LDAP_SUCCESS != lderr) + { + if (spec) delete_LDAPProxyAuth(spec); + spec = 0; + if ( NULL != errtextp ) { + *errtextp = errstring; + } + } + + *out = spec; + + return lderr; +} + +/* + * proxyauth_dn - find the users DN in the proxyauth control if it is + * present. The return value has been malloced for you. + * + * Returns an LDAP error code. If anything than LDAP_SUCCESS is returned, + * the error should be returned to the client. LDAP_SUCCESS is always + * returned if the proxy auth control is not present or not critical. + */ +int +acl_get_proxyauth_dn( Slapi_PBlock *pb, char **proxydnp, char **errtextp ) +{ + char *dn = 0; + LDAPProxyAuth *spec = 0; + int rv, lderr = LDAP_SUCCESS; /* optimistic */ + + BEGIN + struct berval *spec_ber; + LDAPControl **controls; + int present; + int critical; + int version = 1; + + rv = slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls ); + if (rv) break; + + present = slapi_control_present( controls, LDAP_CONTROL_PROXYAUTH, + &spec_ber, &critical ); + if (!present) { + present = slapi_control_present( controls, LDAP_CONTROL_PROXIEDAUTH, + &spec_ber, &critical ); + if (!present) break; + version = 2; + /* + * Note the according to the Proxied Authorization I-D, the + * control is always supposed to be marked critical by the + * client. If it is not, we return a protocolError. + */ + if ( !critical ) { + lderr = LDAP_PROTOCOL_ERROR; + if ( NULL != errtextp ) { + *errtextp = "proxy control must be marked critical"; + } + break; + } + } + + rv = parse_LDAPProxyAuth(spec_ber, version, errtextp, &spec); + if (LDAP_SUCCESS != rv) { + if ( critical ) { + lderr = rv; + } + break; + } + + dn = slapi_ch_strdup(spec->auth_dn); + if (slapi_dn_isroot(dn) ) { + lderr = LDAP_UNWILLING_TO_PERFORM; + *errtextp = "Proxy dn should not be rootdn"; + break; + + } + END + + if (spec) delete_LDAPProxyAuth(spec); + + if ( NULL != proxydnp ) { + *proxydnp = dn; + } else { + slapi_ch_free( (void **)&dn ); + } + + return lderr; +} + diff --git a/ldap/servers/plugins/acl/aclutil.c b/ldap/servers/plugins/acl/aclutil.c new file mode 100644 index 00000000..168ca482 --- /dev/null +++ b/ldap/servers/plugins/acl/aclutil.c @@ -0,0 +1,1475 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/************************************************************************** +* Defines and usefuls stuff +****************************************************************************/ + +/************************************************************************* +* Prototypes +*************************************************************************/ +static void aclutil__typestr (int type , char str[]); +static void aclutil__Ruletypestr (int type , char str[]); +static char* __aclutil_extract_dn_component ( char **e_dns, int position, + char *attrName ); +static char* acl_get_final_component(char *macro_prefix) ; +static char* acl_match_component( char *start, char *component); +static int aclutil_compare_components( char * comp1, char *comp2); +static int acl_find_comp_start(char * s, int pos ); +static PRIntn acl_ht_free_entry_and_value(PLHashEntry *he, PRIntn i, + void *arg); +static PLHashNumber acl_ht_hash( const void *key); +static PRIntn acl_ht_display_entry(PLHashEntry *he, PRIntn i, void *arg); + +/***************************************************************************/ +/* UTILITY FUNCTIONS */ +/***************************************************************************/ +int +aclutil_str_appened(char **str1, const char *str2) +{ + int new_len; + + if ( str1 == NULL || str2 == NULL ) + return(0); + + if ( *str1 == NULL ) { + new_len = strlen(str2) + 1; + *str1 = (char *)slapi_ch_malloc(new_len); + *str1[0] = 0; + } else { + new_len = strlen(*str1) + strlen(str2) + 1; + *str1 = (char *)slapi_ch_realloc(*str1, new_len); + } + if ( *str1 == NULL ) + return(-1); + + strcat(*str1, str2); + return(0); +} + +/***************************************************************************/ +/* Print routines */ +/***************************************************************************/ + +/* print eroror message returned from the ACL Library */ +#define ACLUTIL_ACLLIB_MSGBUF_LEN 200 +void +acl_print_acllib_err (NSErr_t *errp , char * str) +{ + char msgbuf[ ACLUTIL_ACLLIB_MSGBUF_LEN ]; + + if ((NULL == errp ) || !slapi_is_loglevel_set ( SLAPI_LOG_ACL ) ) + return; + + aclErrorFmt(errp, msgbuf, ACLUTIL_ACLLIB_MSGBUF_LEN, 1); + msgbuf[ACLUTIL_ACLLIB_MSGBUF_LEN-1] = '\0'; + + if (msgbuf) + slapi_log_error(SLAPI_LOG_ACL, plugin_name,"ACL LIB ERR:(%s)(%s)\n", + msgbuf, str ? str: "NULL",0); +} +void +aclutil_print_aci (aci_t *aci_item, char *type) +{ + char str[BUFSIZ]; + const char *dn; + + if ( ! slapi_is_loglevel_set ( SLAPI_LOG_ACL ) ) + return; + + if (!aci_item) { + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl__print_aci: Null item\n",0,0,0); + return; + } + + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "***BEGIN ACL INFO[ Name:%s]***\n", aci_item->aclName); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACL Index:%d ACL_ELEVEL:%d\n", aci_item->aci_index, aci_item->aci_elevel ); + aclutil__access_str (aci_item->aci_access, str); + aclutil__typestr (aci_item->aci_type, &str[strlen(str)]); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACI type:(%s)\n", str); + + aclutil__Ruletypestr (aci_item->aci_ruleType, str); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACI RULE type:(%s)\n",str); + + dn = slapi_sdn_get_dn ( aci_item->aci_sdn ); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Slapi_Entry DN:%s\n", escape_string_with_punctuation (dn, str)); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "***END ACL INFO*****************************\n"); + +} +void +aclutil_print_err (int rv , const Slapi_DN *sdn, const struct berval* val, + char **errbuf) +{ + char ebuf [BUFSIZ]; + /* + * The maximum size of line is ebuf_size + the log message + * itself (less than 200 characters for all but potentially ACL_INVALID_TARGET) + */ + char line [BUFSIZ + 200]; + char str [1024]; + const char *dn; + char *lineptr = line; + char *newline = NULL; + + if ( rv >= 0) + return; + + if (val->bv_len > 0 && val->bv_val != NULL) { + sprintf (str, "%.1023s", val->bv_val); + } else { + str[0] = '\0'; + } + + dn = slapi_sdn_get_dn ( sdn ); + if (dn && (rv == ACL_INVALID_TARGET) && ((strlen(dn) + strlen(str)) > BUFSIZ)) { + /* + * if (str_length + dn_length + 200 char message) > (BUFSIZ + 200) line + * we have to make space for a bigger line... + */ + newline = slapi_ch_malloc(strlen(dn) + strlen(str) + 200); + lineptr = newline; + } + + switch (rv) { + case ACL_TARGET_FILTER_ERR: + sprintf (line, "ACL Internal Error(%d): " + "Error in generating the target filter for the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_TARGETATTR_FILTER_ERR: + sprintf (line, "ACL Internal Error(%d): " + "Error in generating the targetattr filter for the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_TARGETFILTER_ERR: + sprintf (line, "ACL Internal Error(%d): " + "Error in generating the targetfilter filter for the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_SYNTAX_ERR: + sprintf (line, "ACL Syntax Error(%d):%s\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_ONEACL_TEXT_ERR: + sprintf (line, "ACL Syntax Error in the Bind Rules(%d):%s\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_ERR_CONCAT_HANDLES: + sprintf (line, "ACL Internal Error(%d): " + "Error in Concatenating List handles\n", + rv); + break; + case ACL_INVALID_TARGET: + sprintf (lineptr, "ACL Invalid Target Error(%d): " + "Target is beyond the scope of the ACL(SCOPE:%s)", + rv, dn ? escape_string_with_punctuation (dn, ebuf) : "NULL"); + sprintf (lineptr + strlen(lineptr), " %s\n", escape_string_with_punctuation (str, ebuf)); + break; + case ACL_INVALID_AUTHMETHOD: + sprintf (line, "ACL Multiple auth method Error(%d):" + "Multiple Authentication Metod in the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_INVALID_AUTHORIZATION: + sprintf (line, "ACL Syntax Error(%d):" + "Invalid Authorization statement in the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_INCORRECT_ACI_VERSION: + sprintf (line, "ACL Syntax Error(%d):" + "Incorrect version Number in the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + default: + sprintf (line, "ACL Internal Error(%d):" + "ACL generic error (%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + } + + if (errbuf) { + /* If a buffer is provided, then copy the error */ + aclutil_str_appened(errbuf, lineptr ); + } + + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "%s", lineptr); + if (newline) slapi_ch_free((void **) &newline); +} + +/*************************************************************************** +* Convert access to str +***************************************************************************/ +char* +aclutil__access_str (int type , char str[]) +{ + char *p; + + str[0] = '\0'; + p = str; + + if (type & SLAPI_ACL_COMPARE) { + strcpy (p, "compare "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_SEARCH) { + strcpy (p, "search "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_READ) { + strcpy (p, "read "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_WRITE) { + strcpy (p, "write "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_DELETE) { + strcpy (p, "delete "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_ADD) { + strcpy (p, "add "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_SELF) { + strcpy (p, "self "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_PROXY) { + strcpy (p, "proxy "); + } + return str; +} + +/*************************************************************************** +* Convert type to str +***************************************************************************/ +static void +aclutil__typestr (int type , char str[]) +{ + char *p; + + /* Start copying in at whatever location is passed in */ + + p = str; + + if (type & ACI_TARGET_DN) { + strcpy (p, "target_DN "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_ATTR) { + strcpy (p, "target_attr "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_PATTERN) { + strcpy (p, "target_patt "); + p = strchr (p, '\0'); + } + if ((type & ACI_TARGET_ATTR_ADD_FILTERS) | (type & ACI_TARGET_ATTR_DEL_FILTERS)) { + strcpy (p, "targetattrfilters "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_FILTER) { + strcpy (p, "target_filter "); + p = strchr (p, '\0'); + } + if (type & ACI_ACLTXT) { + strcpy (p, "acltxt "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_NOT) { + strcpy (p, "target_not "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_ATTR_NOT) { + strcpy (p, "target_attr_not "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_FILTER_NOT) { + strcpy (p, "target_filter_not "); + p = strchr (p, '\0'); + } + + if (type & ACI_HAS_ALLOW_RULE) { + strcpy (p, "allow_rule "); + p = strchr (p, '\0'); + } + if (type & ACI_HAS_DENY_RULE) { + strcpy (p, "deny_rule "); + p = strchr (p, '\0'); + } +} +static void +aclutil__Ruletypestr (int type , char str[]) +{ + char *p; + + str[0] = '\0'; + p = str; + if ( type & ACI_USERDN_RULE) { + strcpy (p, "userdn "); + p = strchr (p, '\0'); + } + if ( type & ACI_USERDNATTR_RULE) { + strcpy (p, "userdnattr "); + p = strchr (p, '\0'); + } + if ( type & ACI_USERATTR_RULE) { + strcpy (p, "userattr "); + p = strchr (p, '\0'); + } + if ( type & ACI_GROUPDN_RULE) { + strcpy (p, "groupdn "); + p = strchr (p, '\0'); + } + if ( type & ACI_GROUPDNATTR_RULE) { + strcpy (p, "groupdnattr "); + p = strchr (p, '\0'); + } + if ( type & ACI_ROLEDN_RULE) { + strcpy (p, "roledn "); + p = strchr (p, '\0'); + } + if ( type & ACI_IP_RULE) { + strcpy (p, "ip "); + p = strchr (p, '\0'); + } + if ( type & ACI_DNS_RULE) { + strcpy (p, "dns "); + p = strchr (p, '\0'); + } + if ( type & ACI_TIMEOFDAY_RULE) { + strcpy (p, "timeofday "); + p = strchr (p, '\0'); + } + if ( type & ACI_DAYOFWEEK_RULE) { + strcpy (p, "dayofweek "); + p = strchr (p, '\0'); + } + if ( type & ACI_AUTHMETHOD_RULE) { + strcpy (p, "authmethod "); + p = strchr (p, '\0'); + } + if ( type & ACI_PARAM_DNRULE) { + strcpy (p, "paramdn "); + p = strchr (p, '\0'); + } + if ( type & ACI_PARAM_ATTRRULE) { + strcpy (p, "paramAttr "); + p = strchr (p, '\0'); + } +} +/* +** acl_gen_err_msg +** This function is called by backend to generate the error message +** if access is denied. +*/ +void +acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf) +{ + char *line = NULL; + + if (access & SLAPI_ACL_WRITE) { + line = PR_smprintf( + "Insufficient 'write' privilege to the '%s' attribute of entry '%s'.\n", + attr ? attr: "NULL", edn); + } else if ( access & SLAPI_ACL_ADD ) { + line = PR_smprintf( + "Insufficient 'add' privilege to add the entry '%s'.\n",edn); + + } else if ( access & SLAPI_ACL_DELETE ) { + line = PR_smprintf( + "Insufficient 'delete' privilege to delete the entry '%s'.\n",edn); + } + aclutil_str_appened(errbuf, line ); + + if (line) { + PR_smprintf_free(line); + line = NULL; + } +} +short +aclutil_gen_signature ( short c_signature ) +{ + short o_signature; + o_signature = c_signature ^ (slapi_rand() % 32768); + if (!o_signature) + o_signature = c_signature ^ (slapi_rand() % 32768); + + return o_signature; +} + +void +aclutil_print_resource( struct acl_pblock *aclpb, char *right , char *attr, char *clientdn ) +{ + + char str[BUFSIZ]; + const char *dn; + + + if ( aclpb == NULL) return; + + if ( ! slapi_is_loglevel_set ( SLAPI_LOG_ACL ) ) + return; + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ************ RESOURCE INFO STARTS *********\n",0,0,0); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " Client DN: %s\n", + clientdn ? escape_string_with_punctuation (clientdn, str) : "NULL", 0,0); + aclutil__access_str (aclpb->aclpb_access, str); + aclutil__typestr (aclpb->aclpb_res_type, &str[strlen(str)]); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " resource type:%d(%s)\n", + aclpb->aclpb_res_type, str, 0); + + dn = slapi_sdn_get_dn ( aclpb->aclpb_curr_entry_sdn ); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " Slapi_Entry DN: %s\n", + dn ? escape_string_with_punctuation ( dn , str) : "NULL",0,0); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ATTR: %s\n", attr ? attr : "NULL",0,0); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " rights:%s\n", right ? right: "NULL",0,0); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ************ RESOURCE INFO ENDS *********\n",0,0,0); +} +/* + * The input string contains a rule like + * "cn=helpdesk, ou=$attr.deptName, o=$dn.o, o=ISP" + * + * Where $attr -- means look into the attribute list for values + * $dn -- means look into the entry's dn + * + * We extract the values from the entry and returned a string + * with the values added. + * For "$attr" rule - if we find multiple values then it is + * the pattern is not expanded. + * For "$dn" rule, if we find multiple of them, we use the relative + * position. + * NOTE: The caller is responsible in freeing the memory. + */ +char * +aclutil_expand_paramString ( char *str, Slapi_Entry *e ) +{ + + char **e_dns; + char **a_dns; + char *attrName; + char *s, *p; + char *attrVal; + int i, len; + int ncomponents, type; + int rc = -1; + char *buf = NULL; + + + e_dns = ldap_explode_dn ( slapi_entry_get_ndn ( e ), 0 ); + a_dns = ldap_explode_dn ( str, 0 ); + + i = 0; + ncomponents = 0; + while ( a_dns[ncomponents] ) + ncomponents++; + + + for (i=0; i < ncomponents; i++ ) { + + /* Look for"$" char */ + if ( (s = strchr ( a_dns[i], '$') ) != NULL) { + p = s; + s++; + if ( strncasecmp (s, "dn", 2) == 0 ) + type = 1; + else if ( strncasecmp (s, "attr", 4) == 0 ) + type = 2; + else { + /* error */ + goto cleanup; + } + *p = '\0'; + aclutil_str_appened ( &buf,a_dns[i]); + + if ( type == 1 ) { + /* xyz = $dn.o */ + s +=3; + attrName = s; + + attrVal = __aclutil_extract_dn_component (e_dns, + ncomponents-i, attrName); + if ( NULL == attrVal ) /*error*/ + goto cleanup; + + } else { + Slapi_Attr *attr; + const struct berval *attrValue; + int kk; + Slapi_Value *sval, *t_sval; + + + /* The pattern is x=$attr.o" */ + s +=5; + attrName = s; + + slapi_entry_attr_find ( e, attrName, &attr ); + if ( NULL == attr ) + goto cleanup; + + kk= slapi_attr_first_value ( attr, &sval ); + if ( kk != -1 ) { + t_sval = sval; + kk= slapi_attr_next_value( attr, kk, &sval ); + if ( kk != -1 ) /* can't handle multiple --error */ + goto cleanup; + } + attrValue = slapi_value_get_berval ( t_sval ); + attrVal = attrValue->bv_val; + } + } else { + attrVal = a_dns[i]; + } + aclutil_str_appened ( &buf, attrVal); + aclutil_str_appened ( &buf, ","); + } + rc = 0; /* everything is okay*/ + /* remove the last comma */ + len = strlen ( buf); + buf[len-1] = '\0'; + +cleanup: + + ldap_value_free ( a_dns ); + ldap_value_free ( e_dns ); + if ( 0 != rc ) /* error */ { + slapi_ch_free ( (void **) &buf ); + buf = NULL; + } + + return buf; +} +static char * +__aclutil_extract_dn_component ( char **e_dns, int position, char *attrName ) +{ + + int i, matched, len; + char *s; + int matchedPosition; + + len = strlen ( attrName ); + + /* First check if there thare are multiple of these */ + i = matched = 0; + while ( e_dns[i] ) { + if (0 == strncasecmp (e_dns[i], attrName, len) ) { + matched++; + matchedPosition = i; + } + i++; + } + + if (!matched ) + return NULL; + + if ( matched > 1 ) { + matchedPosition = i - position; + } + + if ( NULL == e_dns[matchedPosition]) + return NULL; + + s = strstr ( e_dns[matchedPosition], "="); + if ( NULL == s) + return NULL; + else + return s+1; +} + +/* + * Does the first component of ndn match the first component of match_this ? +*/ + +int +acl_dn_component_match( const char *ndn, char *match_this, int component_number) { + + return(1); +} + +/* + * Here, ndn is a resource dn and match_this is a dn, containing a macro, ($dn). + * + * eg. ndn is cn=fred,ou=groups,ou=people,ou=icnc,o=ISP and + * match_this is "ou=Groups,($dn),o=ISP" or + * "cn=*,ou=Groups,($dn),o=ISP". + * + * They match if: + * match_this is a suffix of ndn + * + * It returns NULL, if they do not match. + * Otherwise it returns a copy of the substring of ndn that matches the ($dn). + * + * eg. in the above example, "ou=people,ou=icnc" +*/ + +char * +acl_match_macro_in_target( const char *ndn, char * match_this, + char *macro_ptr) { + + char *macro_prefix = NULL; + int macro_prefix_len = 0; + char *macro_suffix = NULL; + char *tmp_ptr = NULL; + char *matched_val = NULL; + char *ndn_suffix_start = NULL; + char *macro_prefix_final_component = NULL; + char *ret_val = NULL; + int ndn_len = 0; + int macro_suffix_len = 0; + int ndn_prefix_len = 0; + int ndn_prefix_end = 0; + int matched_val_len = 0; + + /* + * First, grab the macro_suffix--the bit after the ($dn) + * + */ + + if (strlen(macro_ptr) == strlen(ACL_TARGET_MACRO_DN_KEY)) { + macro_suffix = NULL; /* just ($dn) */ + } else { + if ( macro_ptr[strlen(ACL_TARGET_MACRO_DN_KEY)] == ',') { + macro_suffix = ¯o_ptr[strlen(ACL_TARGET_MACRO_DN_KEY) + 1]; + } else { + macro_suffix = ¯o_ptr[strlen(ACL_TARGET_MACRO_DN_KEY)]; + } + } + + /* + * First ensure that the suffix of match_this is + * a suffix of ndn. + */ + + ndn_len = strlen(ndn); + if ( macro_suffix != NULL) { + macro_suffix_len = strlen(macro_suffix); + if( macro_suffix_len >= ndn_len ) { + + /* + * eg ndn: o=icnc,o=sun.com + * match_this: ($dn),o=icnc,o=sun.com + */ + return(NULL); /* ($dn) must match something. */ + } else { + /* + * eg ndn: ou=People,o=icnc,o=sun.com + * match_this: ($dn),o=icnc,o=sun.com + * + * we can do a direct strncmp() because we know that + * there can be no "*" after the ($dn)...by definition. + */ + if (strncasecmp( macro_suffix, &ndn[ndn_len-macro_suffix_len], + macro_suffix_len) != 0) { + return(NULL); /* suffix must match */ + } + } + } + + /* Start of the suffix in ndn...and it matched. */ + ndn_suffix_start = (char*)&ndn[ndn_len-macro_suffix_len]; + + /* Here, macro_suffix is a suffix of ndn. + * + * + * Now, look at macro_prefix, if it is NULL, then ($dn) matches + * ndn[0..ndn_len-macro_suffix_len]. + * (eg, ndn: cn=fred,ou=People,o=sun.com + * match_this: ($dn),o=sun.com. + * + */ + + macro_prefix = slapi_ch_strdup(match_this); + + /* we know it's got a $(dn) */ + tmp_ptr = strstr(macro_prefix, ACL_TARGET_MACRO_DN_KEY); + *tmp_ptr = '\0'; + /* There may be a NULL prefix eg. match_this: ($dn),o=sun.com */ + macro_prefix_len = strlen(macro_prefix); + if (macro_prefix_len == 0) { + slapi_ch_free((void **) ¯o_prefix); + macro_prefix = NULL; + } + + if (macro_prefix == NULL ) { + /* + * ($dn) matches ndn[0..ndn_len-macro_suffix_len] + */ + int matched_val_len = 0; + + matched_val_len = ndn_len-macro_suffix_len; + + matched_val = (char *)slapi_ch_malloc(matched_val_len + 1); + strncpy(matched_val, ndn, ndn_len-macro_suffix_len); + /* + * Null terminate matched_val, removing trailing "," if there is + * one. + */ + if (matched_val_len > 1) { + if (matched_val[matched_val_len-1] == ',' ) { + matched_val[matched_val_len-1] = '\0'; + } else { + matched_val[matched_val_len] = '\0'; + } + } + ret_val = matched_val; + } else { + + + /* + * If it is not NULL, then if macro_prefix contains a * then + * it needs to be an exact prefix of ndn (modulo the * component + * which matches anything) becuase that's the semantics + * of target patterns containing *'s, except that we just + * make it match one component. + * If it is such a prefix then ($dn) matches that portion of ndn + * from the end of the prefix, &ndn[ndn_prefix_end] to + * ndn_suffix_start. + * If ndn_prefix_len > ndn_len-macro_suffix_len then return(NULL), + * otherwise $(dn) matches ndn[ndn_prefix_len..ndn_len-macro_suffix_len]. + * + * + * eg. ndn: cn=fred,ou=P,o=sun.com + * match_this: cn=*,($dn),o=sun.com + */ + + if ( strstr(macro_prefix, "=*") != NULL ) { + int exact_match = 0; + + ndn_prefix_len = acl_match_prefix( macro_prefix, ndn, &exact_match); + if ( ndn_prefix_len != -1 ) { + + /* + * ndn[0..ndn_prefix_len] is the prefix in ndn. + * ndn[ndn_prefix_len..ndn_len-macro_suffix_len] is the + * matched string. + */ + if (ndn_prefix_len >= ndn_len-macro_suffix_len) { + + /* + * eg ndn: cn=fred,ou=People,o=icnc,o=sun.com + * cn=*,ou=People,o=icnc,($dn),o=icnc,o=sun.com + */ + + ret_val = NULL; /* matched string is empty */ + } else { + + /* + * eg ndn: cn=fred,ou=People,o=icnc,o=sun.com + * cn=*,ou=People,($dn),o=sun.com + */ + + matched_val_len = ndn_len-macro_suffix_len-ndn_prefix_len; + matched_val = (char *)slapi_ch_malloc(matched_val_len + 1); + strncpy(matched_val, &ndn[ndn_prefix_len], matched_val_len); + if (matched_val_len > 1) { + if (matched_val[matched_val_len-1] == ',' ) { + matched_val[matched_val_len-1] = '\0'; + } else { + matched_val[matched_val_len] = '\0'; + } + } + matched_val[matched_val_len] = '\0'; + ret_val = matched_val; + } + } else { + /* Was not a prefix so not a match */ + ret_val = NULL; + } + } else { + + /* + * + * If macro_prefix is not NULL and it does not + * contain a =* then + * we need to ensure that macro_prefix is a substring + * ndn. + * If it is and the position of the character after it's end in + * ndn is + * ndn_prefix_end then ($dn) matches + * ndn[ndn_prefix_end..ndn_len-macro_suffix_len]. + * + * + * One important principal is that ($dn) matches a maximal + * chunk--this way it will serve to make the link + * between resources and users at each level of the structure. + * + * eg. ndn: ou=Groups,ou=Groups,ou=Groups,c=fr + * macro_prefix: ou=Groups,($dn),c=fr + * + * then ($dn) matches ou=Groups,ou=Groups. + * + * + * + * If it is not a substring, then there is no match. + * If it is a substring and + * ndn[ndn_prefix_end..ndn_len-macro_suffix_len] is empty then + * it's also not a match as we demand that ($dn) match a non-empty + * string. + * + * + * + * (eg. ndn: cn=fred,o=icnc,ou=People,o=sun.com + * match_this: o=icnc,($dn),o=sun.com.) + * + * + * (eg. ndn: cn=fred,o=menlo park,ou=People,o=icnc,o=sun.com + * match_this: o=menlo park,ou=People,($dn),o=sun.com + * + */ + + ndn_prefix_end = acl_strstr((char *)ndn, macro_prefix); + if ( ndn_prefix_end == -1) { + ret_val = NULL; + } else { + /* Is a substring */ + + ndn_prefix_end += macro_prefix_len; + + /* + * make sure the matching part is non-empty: + * + * ndn[ndn_prefix_end..mndn_len-macro_suffix_len]. + */ + + if ( ndn_prefix_end >= ndn_len-macro_suffix_len) { + ret_val = NULL; + } else { + /* + * ($dn) matches the non-empty string segment + * ndn[ndn_prefix_end..mndn_len-macro_suffix_len] + * the -1 is because macro_suffix_eln does not include + * the coma before the suffix. + */ + + matched_val_len = ndn_len-macro_suffix_len- + ndn_prefix_end - 1; + + matched_val = (char *)slapi_ch_malloc(matched_val_len + 1); + strncpy(matched_val, &ndn[ndn_prefix_end], + matched_val_len); + matched_val[matched_val_len] = '\0'; + + ret_val = matched_val; + } + } + }/* contains an =* */ + slapi_ch_free((void **) ¯o_prefix); + }/* macro_prefix != NULL */ + + return(ret_val); +} + +/* + * Checks to see if macro_prefix is an exact prefix of ndn. + * macro_prefix may contain a * component. + * + * The length of the matched prefix in ndn is returned. + * If it was not a match, a negative int is returned. + * Also, if the string matched exactly, + * exact_match is set to 1, other wise it was a proper prefix. + * +*/ + +int +acl_match_prefix( char *macro_prefix, const char *ndn, int *exact_match) { + + int macro_index = 0; + int ndn_index = 0; + int ret_code = -1; + char *curr_macro_component = NULL; + char *curr_ndn_component = NULL; + int matched = 0; + int macro_prefix_len = 0; + int ndn_len = 0; + int i = 0; + int j = 0; + int done = 0; + int t = 0; + char * tmp_str = NULL; + int k,l = 0; + + *exact_match = 0; /* default to not an exact match */ + + /* The NULL prefix matches everthing*/ + if (macro_prefix == NULL) { + if ( ndn == NULL ) { + *exact_match = 1; + } + return(0); + } else { + /* macro_prefix is not null, so if ndn is NULL, it's not a match. */ + if ( ndn == NULL) { + return(-1); + } + } + /* + * Here, neither macro_prefix nor ndn are NULL. + * + * eg. macro_prefix: cn=*,ou=people,o=sun.com + * ndn : cn=fred,ou=people,o=sun.com + */ + + + /* + * Here, there is a component with a * (eg. cn=* ) so + * we need to step through the macro_prefix, and where there is + * such a * match on that component, + * when we run out of * componenets, jsut do a straight match. + * + * Out of interest, the following extended regular expression + * will match just one ou rdn value from a string: + * "^uid=admin,ou=\([^,]*\\\,\)*[^,]*,o=sun.com$" + * + * + * eg. cn=fred,ou=People,o=sun.com + * + * + * s points to the = of the component. + */ + + macro_prefix_len = strlen(macro_prefix); + ndn_len = strlen(ndn); + i = 0; + j = 0; + done = 0; + while ( !done ) { + + /* Here ndn[0..i] has matched macro_prefix[0..j] && j<= i + * i<=ndn_len j<=macro_prefix_len */ + + if ( (t = acl_strstr(¯o_prefix[j], "=*")) < 0 ) { + /* + * No more *'s, do a straight match on + * macro_prefix[j..macro_prefix_len] and + * ndn[i..macro_prefix_len] + */ + + if( macro_prefix_len-j > ndn_len-i) { + /* Not a prefix, nor a match */ + *exact_match = 0; + ret_code = -1; + done = 1; + } else { + /* + * ndn_len-i >= macro_prefix_len - j + * if macro_prefix_len-j is 0, then + * it's a null prefix, so it matches. + * If in addition ndn_len-i is 0 then it's + * an exact match. + * Otherwise, do the cmp. + */ + + if ( macro_prefix_len-j == 0) { + done = 1; + ret_code = i; + if ( ndn_len-i == 0) { + *exact_match = 1; + } + }else { + + if (strncasecmp(¯o_prefix[j], &ndn[i], + macro_prefix_len-j) == 0) { + *exact_match = (macro_prefix_len-j == ndn_len-i); + ret_code = i + macro_prefix_len -j; + done = 1; + } else { + /* not a prefix not a match */ + *exact_match = 0; + ret_code = -1; + done = 1; + } + } + } + }else { + /* + * Is another * component, so: + * 1. match that component in macro_prefix (at pos k say) + * with the corresponding compoent (at pos l say ) in ndn + * + * 2. match the intervening string ndn[i..l] and + * macro_prefix[j..k]. + */ + + /* First, find the start of the component in macro_prefix. */ + + t++; /* move to the--this way we will look for "ou=" in ndn */ + k = acl_find_comp_start(macro_prefix, t); + + /* Now identify that component in ndn--if it's not there no match */ + tmp_str = slapi_ch_malloc(t-k+1); + strncpy(tmp_str, ¯o_prefix[k], t-k); + tmp_str[t-k] = '\0'; + l = acl_strstr((char*)&ndn[i], tmp_str); + if (l == -1) { + *exact_match = 0; + ret_code = -1; + done = 1; + } else { + /* + * Found the comp in ndn, so the comp matches. + * Now test the intervening string segments: + * ndn[i..l] and macro_prefix[j..k] + */ + + if ( k-j != l-i ) { + *exact_match = 0; + ret_code = -1; + done = 1; + } else{ + if (strncasecmp(¯o_prefix[j], &ndn[i], k-j) != 0) { + *exact_match = 0; + ret_code = -1; + done = 1; + } else { + /* Matched, so bump i and j and keep going.*/ + i += acl_find_comp_end((char*)&ndn[l]); + j += acl_find_comp_end((char*)¯o_prefix[k]); + } + } + } + slapi_ch_free((void **)&tmp_str); + } + }/* while */ + + return(ret_code); + +} + +/* + * returns the index in s of where the component at position + * s[pos] starts. + * This is the index of the character after the first unescaped comma + * moving backwards in s from pos. + * If this is not found then return 0, ie. the start of the string. + * If the index returned is > strlen(s) then could not find it. + * only such case is if you pass ",", in which case there is no component start. +*/ + +static int +acl_find_comp_start(char * s, int pos ) { + + int i =0; + int comp_start = 0; + + i = pos; + while( i > 0 && (s[i] != ',' || + s[i-1] == '\\')) { + i--; + } + /* + * i == 0 || (s[i] == ',' && s[i-1] != '\\') + */ + if (i==0) { + /* Got all the way with no unescaped comma */ + if (s[i] == ',') { + comp_start = i+1; + } else { + comp_start = i; + } + } else { /* Found an unescaped comma */ + comp_start = i + 1; + } + + return( comp_start); +} + +/* + * returns the index in s of the first character after the + * first unescaped comma. + * If ther is no such character, returns strlen(s); +*/ + +int +acl_find_comp_end( char * s) { + + int i = 0; + int s_len = 0; + + s_len = strlen(s); + + if ( s_len == 0 || s_len == 1) { + return(s_len); + } + + /* inv: i+1<s_len && (s[i] == '\\' || s[i+1] != ',')*/ + + i = 0; + while( i+1 < s_len && (s[i] == '\\' || + s[i+1] != ',')) { + i++; + } + if ( i + 1 == s_len) { + return(s_len); + } else { + return(i+2); + } +} + +/* + * return the index in s where substr occurs, if none + * returns -1. +*/ + +int +acl_strstr(char * s, char *substr) { + + char *t = NULL; + char *tmp_str = NULL; + + tmp_str = slapi_ch_strdup(s); + + if ( (t = strstr(tmp_str, substr)) == NULL ) { + slapi_ch_free((void **)&tmp_str); + return(-1); + } else { + int l = 0; + *t = '\0'; + l = strlen(tmp_str); + slapi_ch_free((void **)&tmp_str); + return(l); + } +} + +/* + * replace all occurences of substr in s with replace_str. + * + * returns a malloced version of the patched string. +*/ + +char * +acl_replace_str(char * s, char *substr, char* replace_with_str) { + + char *str = NULL; + char *working_s, *suffix, *prefix, *patched; + int replace_with_len, substr_len, prefix_len, suffix_len; + + if ( (str = strstr(s, substr)) == NULL) { + return(slapi_ch_strdup(s)); + } else { + + + replace_with_len = strlen(replace_with_str); + substr_len = strlen(substr); + + working_s = slapi_ch_strdup(s); + prefix = working_s; + str = strstr(prefix, substr); + + while (str != NULL) { + + /* + * working_s is a copy of the string to be patched + * str points to a substr to be patched + * prefix points to working_s + */ + + *str = '\0'; + + suffix = &str[substr_len]; + prefix_len = strlen(prefix); + suffix_len = strlen(suffix); + + patched = (char *)slapi_ch_malloc(prefix_len + + replace_with_len + + suffix_len +1 ); + strcpy(patched, prefix); + strcat(patched, replace_with_str); + strcat(patched, suffix); + + slapi_ch_free((void **)&working_s); + + working_s = patched; + prefix = working_s; + str = strstr(prefix, substr); + + } + + return(working_s); + } + +} + + +/* + * Start at index and return a malloced string that is the + * next component in dn (eg. "ou=People"), + * or NULL if couldn't find the next one. +*/ + +char * +get_next_component(char *dn, int *index) { + + int dn_len = strlen(dn); + int start_next = -1; + int i = 0; + char *ret_comp; + + if (*index>= dn_len) { + return(NULL); + } + + start_next = acl_find_comp_end( &dn[*index]); + + if ( start_next >= dn_len ) { + *index = start_next; + return(NULL); /* no next comp */ + } + + /* + *Here, start_next should be the start of the next + * component--so far have not run off the end. + */ + + i = acl_find_comp_end( &dn[start_next]); + + /* + * Here, the matched string is all from start_next to i. + */ + + ret_comp = (char *)slapi_ch_malloc(i - start_next +1); + memcpy( ret_comp, &dn[start_next], i-start_next); + ret_comp[i-start_next] = '\0'; + + return(ret_comp); +} + +char * +get_this_component(char *dn, int *index) { + + int dn_len = strlen(dn); + int i = 0; + char *ret_comp; + + if (*index>= dn_len) { + return(NULL); + } + + if (dn_len == *index + 1) { + /* Just return a copy of the string. */ + return(slapi_ch_strdup(dn)); + }else { + /* *index + 1 < dn_len */ + i = *index+1; + while( (dn[i] != '\0') && dn[i] != ',' && dn[i-1] != '\\') { + i += 1; + } + + /* + * Here, the matched string is all from *index to i. + */ + + ret_comp = (char *)slapi_ch_malloc(i - *index +1); + memcpy( ret_comp, &dn[*index], i - *index); + ret_comp[i-*index] = '\0'; + + if (i < dn_len) { + /* Found a comma before the end */ + *index = i + 1; /* skip it */ + } + + return(ret_comp); + } + +} + +/* + * return 1 if comp1==comp2, + * return 0 otherwise. + * + * the components might have *'s. + * + * eg: comp1: cn=* + * comp2: cn=fred + * + * +*/ + +static int +aclutil_compare_components( char * comp1, char *comp2) { + + char *tmp_str = NULL; + + tmp_str = strstr( comp1, "=*"); + if ( tmp_str == NULL) { + + /* Just a straight cmp */ + + if (slapi_utf8casecmp((ACLUCHP)comp1, (ACLUCHP)comp2) == 0) { + return(1); + } else { + return(0); + } + } else { + + char *tmp_comp1= NULL; + char *tmp_comp2 = NULL; + int ret_code = 0; + + /* Here, just compare the bit before the = */ + + tmp_comp1 = slapi_ch_strdup(comp1); + tmp_comp2 = slapi_ch_strdup(comp2); + + /* + * Probably need to verify it's not escaped--see code for looking for + * unescaped commas. + */ + + tmp_str = strstr(tmp_comp1, "="); + *tmp_str = '\0'; + + tmp_str = strstr(tmp_comp2, "="); + if ( tmp_str == NULL) { + ret_code = 0; + } else{ + + *tmp_str = '\0'; + + if (slapi_utf8casecmp((ACLUCHP)comp1, (ACLUCHP)comp2) == 0) { + ret_code = 1; + } else { + ret_code = 0; + } + + slapi_ch_free((void **)&tmp_comp1); + slapi_ch_free((void **)&tmp_comp2); + + return(ret_code); + + } + + } +} + +/* + * return a pointer to the final component of macro_prefix. +*/ + +static char * +acl_get_final_component(char *macro_prefix) { + + return(NULL); +} + +/* + * + * +*/ + +static char * +acl_match_component( char *start, char *component) { + + + return(NULL); +} + +/* acl hash table funcs */ + +/* + * Add the key adn value to the ht. + * If it already exists then remove the old one and free + * the value. +*/ +void acl_ht_add_and_freeOld(acl_ht_t * acl_ht, + PLHashNumber key, + char *value){ + char *old_value = NULL; + + if ( (old_value = (char *)acl_ht_lookup( acl_ht, key)) != NULL ) { + acl_ht_remove( acl_ht, key); + slapi_ch_free((void **)&old_value); + } + + PL_HashTableAdd( acl_ht, (const void *)key, value); +} + +/* + * Return a new acl_ht_t * +*/ +acl_ht_t *acl_ht_new(void) { + + return(PL_NewHashTable(30, acl_ht_hash, /* key hasher */ + PL_CompareValues, /* keyCompare */ + PL_CompareStrings, 0, 0)); /* value compare */ +} + +static PLHashNumber acl_ht_hash( const void *key) { + + return( (PLHashNumber)key ); +} + +/* Free all the values in the ht */ +void acl_ht_free_all_entries_and_values( acl_ht_t *acl_ht) { + + PL_HashTableEnumerateEntries( acl_ht, acl_ht_free_entry_and_value, + NULL); +} + +static PRIntn +acl_ht_free_entry_and_value(PLHashEntry *he, PRIntn i, void *arg) +{ + + slapi_ch_free((void **)&he->value); /* free value */ + + /* Free this entry anfd go on to next one */ + return ( HT_ENUMERATE_NEXT | HT_ENUMERATE_REMOVE); +} + +/* Free all the values in the ht */ +void acl_ht_display_ht( acl_ht_t *acl_ht) { + +#ifdef DEBUG + PL_HashTableEnumerateEntries( acl_ht, acl_ht_display_entry, NULL); +#endif +} + +static PRIntn +acl_ht_display_entry(PLHashEntry *he, PRIntn i, void *arg) +{ + PLHashNumber aci_index = (PLHashNumber)he->key; + char *matched_val = (char *)he->value; + + LDAPDebug(LDAP_DEBUG_ACL,"macro ht entry: key='%d' matched_val='%s'" + "keyhash='%d'\n", + aci_index, (matched_val ? matched_val: "NULL"), + (PLHashNumber)he->keyHash); + + return HT_ENUMERATE_NEXT; + +} + +/* remove this entry from the ht--doesn't free the value.*/ +void acl_ht_remove( acl_ht_t *acl_ht, PLHashNumber key) { + + PL_HashTableRemove( acl_ht, (const void *)key); +} + +/* Retrieve a pointer to the value of the entry with key */ +void *acl_ht_lookup( acl_ht_t *acl_ht, + PLHashNumber key) { + + return( PL_HashTableLookup( acl_ht, (const void *)key) ); +} + + +/***************************************************************************/ +/* E N D */ +/***************************************************************************/ + diff --git a/ldap/servers/plugins/acl/libacl.def b/ldap/servers/plugins/acl/libacl.def new file mode 100644 index 00000000..947638cd --- /dev/null +++ b/ldap/servers/plugins/acl/libacl.def @@ -0,0 +1,16 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7.0 ACL Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + acl_preopInit @1 +; unused @2 + acl_init @3 + plugin_init_debug_level @4 diff --git a/ldap/servers/plugins/chainingdb/Makefile b/ldap/servers/plugins/chainingdb/Makefile new file mode 100644 index 00000000..4f47e480 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/Makefile @@ -0,0 +1,93 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server "Chaining Backend" plugin +# + +LDAP_SRC = ../../.. + +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libcb +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libcb.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd + +CB_OBJS= cb_temp.o cb_init.o cb_config.o cb_instance.o cb_start.o cb_search.o cb_utils.o cb_add.o cb_delete.o cb_schema.o \ +cb_acl.o cb_modify.o cb_compare.o cb_modrdn.o cb_abandon.o cb_conn_stateless.o cb_bind.o cb_unbind.o cb_monitor.o \ +cb_controls.o cb_size.o cb_test.o cb_close.o cb_cleanup.o cb_debug.o + +OBJS = $(addprefix $(OBJDEST)/, $(CB_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBCB_DLL_OBJ = $(addprefix $(OBJDEST)/, cbdllmain.o) +endif + +LIBCB= $(addprefix $(LIBDIR)/, $(CB_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP) $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(SECURITY_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(LDAP_LIBUTIL) $(LDAP_COMMON_LIBS) $(SECURITYLINK) $(NSPRLINK) + +endif + + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libcb.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP) $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(SECURITY_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(LIBUTIL) $(LDAP_COMMON_LIBS) $(SECURITYLINK) $(NSPRLINK) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBCB) + +$(LIBCB): $(OBJS) $(LIBCB_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBCB_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBCB_DLL_OBJ) +endif + $(RM) $(LIBCB) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(OBJS): cb.h diff --git a/ldap/servers/plugins/chainingdb/cb.h b/ldap/servers/plugins/chainingdb/cb.h new file mode 100644 index 00000000..8aed412c --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb.h @@ -0,0 +1,461 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#ifndef CBHFILE +#define CBHFILE + +/*** #define CB_YIELD ***/ + +#include <stdio.h> +#include <string.h> +#include <prlock.h> +#include <prcvar.h> +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "portable.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" + +/* Constants */ + +#define CB_DIRECTORY_MANAGER_DN "cn=directory manager" +#define CB_CHAINING_BACKEND_TYPE "chaining database" +#define CB_PLUGIN_NAME "chaining database" +#define CB_PLUGIN_SUBSYSTEM "chaining database" +#define CB_PLUGIN_DESCRIPTION "LDAP chaining backend database plugin" + +#define CB_LDAP_SECURE_PORT 636 +#define CB_BUFSIZE 2048 + + +/* Macros */ + +#define CB_LDAP_CONN_ERROR( err ) ( (err) == LDAP_SERVER_DOWN || \ + (err) == LDAP_CONNECT_ERROR ) +#define CB_ASSERT( expr ) PR_ASSERT( expr ) + +/* Innosoft chaining extension for loop detection */ + +#define CB_LDAP_CONTROL_CHAIN_SERVER "1.3.6.1.4.1.1466.29539.12" + +/* Chaining backend configuration attributes */ + +/* Monitor entry */ +#define CB_MONITOR_EXTENSIBLEOCL "extensibleObject" +#define CB_MONITOR_INSTNAME "cn" +#define CB_MONITOR_ADDCOUNT "nsAddCount" +#define CB_MONITOR_DELETECOUNT "nsDeleteCount" +#define CB_MONITOR_MODIFYCOUNT "nsModifyCount" +#define CB_MONITOR_MODRDNCOUNT "nsRenameCount" +#define CB_MONITOR_SEARCHBASECOUNT "nsSearchBaseCount" +#define CB_MONITOR_SEARCHONELEVELCOUNT "nsSearchOneLevelCount" +#define CB_MONITOR_SEARCHSUBTREECOUNT "nsSearchSubtreeCount" +#define CB_MONITOR_ABANDONCOUNT "nsAbandonCount" +#define CB_MONITOR_BINDCOUNT "nsBindCount" +#define CB_MONITOR_UNBINDCOUNT "nsUnbindCount" +#define CB_MONITOR_COMPARECOUNT "nsCompareCount" +#define CB_MONITOR_OUTGOINGCONN "nsOpenOpConnectionCount" +#define CB_MONITOR_OUTGOINGBINDCOUNT "nsOpenBindConnectionCount" + +/* Global configuration */ +#define CB_CONFIG_GLOBAL_FORWARD_CTRLS "nsTransmittedControls" +#define CB_CONFIG_GLOBAL_CHAINING_COMPONENTS "nsActiveChainingComponents" +#define CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS "nsPossibleChainingComponents" +/* not documented */ +#define CB_CONFIG_GLOBAL_DEBUG "nsDebug" + + +/* Instance-specific configuration */ +#define CB_CONFIG_CHAINING_COMPONENTS CB_CONFIG_GLOBAL_CHAINING_COMPONENTS +#define CB_CONFIG_EXTENSIBLEOCL "extensibleObject" +/* XXXSD to be changed */ +#define CB_CONFIG_INSTANCE_FILTER "(objectclass=nsBackendInstance)" +#define CB_CONFIG_INSTNAME "cn" +#define CB_CONFIG_SUFFIX "nsslapd-suffix" +#define CB_CONFIG_SIZELIMIT "nsslapd-sizelimit" +#define CB_CONFIG_TIMELIMIT "nsslapd-timelimit" +#define CB_CONFIG_HOSTURL "nsFarmServerURL" + +#define CB_CONFIG_BINDUSER "nsMultiplexorBindDn" +#define CB_CONFIG_USERPASSWORD "nsMultiplexorCredentials" +#define CB_CONFIG_MAXBINDCONNECTIONS "nsBindConnectionsLimit" +#define CB_CONFIG_MAXCONNECTIONS "nsOperationConnectionsLimit" +#define CB_CONFIG_MAXCONCURRENCY "nsConcurrentOperationsLimit" +#define CB_CONFIG_MAXBINDCONCURRENCY "nsConcurrentBindLimit" + +#define CB_CONFIG_IMPERSONATION "nsProxiedAuthorization" + +#define CB_CONFIG_BINDTIMEOUT "nsBindTimeout" +#define CB_CONFIG_TIMEOUT "nsOperationTimeout" +#define CB_CONFIG_MAX_IDLE_TIME "nsMaxResponseDelay" +#define CB_CONFIG_MAX_TEST_TIME "nsMaxTestResponseDelay" + +#define CB_CONFIG_REFERRAL "nsReferralOnScopedSearch" + +#define CB_CONFIG_CONNLIFETIME "nsConnectionLife" +#define CB_CONFIG_ABANDONTIMEOUT "nsAbandonedSearchCheckInterval " +#define CB_CONFIG_BINDRETRY "nsBindRetryLimit" +#define CB_CONFIG_LOCALACL "nsCheckLocalACI" +#define CB_CONFIG_HOPLIMIT "nsHopLimit" + +/* not documented */ +#define CB_CONFIG_ILLEGAL_ATTRS "nsServerDefinedAttributes" + +/* Default configuration values (as string) */ + +/* + * CB_DEF_MAXCONNECTIONS and CB_DEF_MAXCONCURRENCY used to be 10. + * Reduced CB_DEF_MAXCONCURRENCY to 2 to workaround bug 623793 - + * err=1 in accesslogs and ber parsing errors in errors logs. + */ +#define CB_DEF_MAXCONNECTIONS "20" /* CB_CONFIG_MAXCONNECTIONS */ +#define CB_DEF_MAXCONCURRENCY "2" /* CB_CONFIG_MAXCONCURRENCY */ +#define CB_DEF_BIND_MAXCONNECTIONS "3" /* CB_CONFIG_MAXBINDCONNECTIONS */ +#define CB_DEF_BIND_MAXCONCURRENCY "10" /* CB_CONFIG_MAXBINDCONCURRENCY */ +#define CB_DEF_BINDTIMEOUT "15" /* CB_CONFIG_BINDTIMEOUT */ +#define CB_DEF_CONNLIFETIME "0" /* CB_CONFIG_CONNLIFETIME */ +#define CB_DEF_IMPERSONATION "on" /* CB_CONFIG_IMPERSONATION */ +#define CB_DEF_SEARCHREFERRAL "off" /* CB_CONFIG_REFERRAL */ +#define CB_DEF_ABANDON_TIMEOUT "1" /* CB_CONFIG_ABANDONTIMEOUT */ +#define CB_DEF_BINDRETRY "3" /* CB_CONFIG_BINDRETRY */ +#define CB_DEF_LOCALACL "off" /* CB_CONFIG_LOCALACL */ +#define CB_DEF_TIMELIMIT "3600" +#define CB_DEF_SIZELIMIT "2000" +#define CB_DEF_HOPLIMIT "10" /* CB_CONFIG_HOPLIMIT */ +#define CB_DEF_MAX_IDLE_TIME "60" /* CB_CONFIG_MAX_IDLE_TIME */ +#define CB_DEF_MAX_TEST_TIME "15" /* CB_CONFIG_MAX_TEST_TIME */ + +typedef void *cb_config_get_fn_t(void *arg); +typedef int cb_config_set_fn_t(void *arg, void *value, char *errorbuf, int phase, int apply); +typedef struct _cb_instance_config_info { + char *config_name; + int config_type; + char *config_default_value; + cb_config_get_fn_t *config_get_fn; + cb_config_set_fn_t *config_set_fn; + int config_flags; +} cb_instance_config_info; + +#define CB_CONFIG_TYPE_ONOFF 1 /* val = (int) value */ +#define CB_CONFIG_TYPE_STRING 2 /* val = (char *) value - The get functions + * for this type must return alloced memory + * that should be freed by the caller. */ +#define CB_CONFIG_TYPE_INT 3 /* val = (int) value */ +#define CB_CONFIG_TYPE_LONG 4 /* val = (long) value */ +#define CB_CONFIG_TYPE_INT_OCTAL 5 /* Same as CONFIG_TYPE_INT, but shown in octal*/ +#define CB_PREVIOUSLY_SET 1 +#define CB_ALWAYS_SHOW 2 +#define CB_CONFIG_PHASE_INITIALIZATION 1 +#define CB_CONFIG_PHASE_STARTUP 2 +#define CB_CONFIG_PHASE_RUNNING 3 +#define CB_CONFIG_PHASE_INTERNAL 4 + +/*jarnou: default amount of time in seconds during wich the chaining backend will be unavailable */ +#define CB_UNAVAILABLE_PERIOD 30 /* CB_CONFIG_UNAVAILABLE_PERIOD */ +#define CB_INFINITE_TIME 360000 /* must be enough ... */ +/*jarnou: default number of connections failed from which the farm is declared unavailable */ +#define CB_NUM_CONN_BEFORE_UNAVAILABILITY 1 +#define FARMSERVER_UNAVAILABLE 1 +#define FARMSERVER_AVAILABLE 0 + +/* Internal data structures */ + +/* cb_backend represents the chaining backend type. */ +/* Only one instance is created when the plugin is */ +/* loaded. Contain global conf */ +typedef struct _cb_backend { + + /* + ** keep track of plugin identity. + ** Used for internal operations + */ + + void *identity; + char * pluginDN; + char * configDN; + + /* + ** There are times when we need a pointer to the chaining database + ** plugin, so we will store a pointer to it here. Examples of + ** when we need it are when we create a new instance and when + ** we need the name of the plugin to do internal ops. + */ + + struct slapdplugin *plugin; + + /* + ** Global config. shared by all chaining db instances + */ + + struct { + char ** forward_ctrls; /* List of forwardable controls */ + char ** chaining_components; /* List of plugins that chains */ + char ** chainable_components; /* List of plugins allowed to chain*/ + /* internal operations. */ + PRRWLock *rwl_config_lock; /* Protect the global config */ + } config; + + int started; /* TRUE when started */ + +} cb_backend; + + +/* Connection management */ + +/* states */ +#define CB_CONNSTATUS_OK 1 /* Open */ +#define CB_CONNSTATUS_DOWN 2 /* Down */ +#define CB_CONNSTATUS_STALE 3 + +#define ENABLE_MULTITHREAD_PER_CONN 1 /* to allow multiple threads to perform LDAP operations on a connection */ +#define DISABLE_MULTITHREAD_PER_CONN 0 /* to allow only one thread to perform LDAP operations on a connection */ + +/************** WARNING: Be careful if you want to change this constant. It is used in hexadecimal in cb_conn_stateless.c in the function PR_ThreadSelf() ************/ +#define MAX_CONN_ARRAY 2048 /* we suppose the number of threads in the server not to exceed this limit*/ +/**********************************************************************************************************/ +typedef struct _cb_outgoing_conn{ + LDAP *ld; + unsigned long refcount; + struct _cb_outgoing_conn *next; + time_t opentime; + int status; + int ThreadId ; /* usefull to identify the thread when SSL is enabled */ +} cb_outgoing_conn; + +typedef struct { + char *hostname; /* Farm server name */ + char *url; + unsigned int port; + int secure; + char *binddn; /* normalized */ + char *binddn2; /* not normalized, value returned to the client */ + char *password; + int bindit; /* If true, open AND bind */ + char ** waste_basket; /* stale char * */ + + struct { + unsigned int maxconnections; + unsigned int maxconcurrency; + unsigned int connlifetime; + struct timeval op_timeout; + struct timeval bind_timeout; + + Slapi_Mutex *conn_list_mutex; + Slapi_CondVar *conn_list_cv; + cb_outgoing_conn *conn_list; + unsigned int conn_list_count; + + } conn; + + cb_outgoing_conn *connarray[MAX_CONN_ARRAY]; /* array of secure connections */ + + /* To protect the config set by LDAP */ + PRRWLock * rwl_config_lock; +} cb_conn_pool; + + +/* _cb_backend_instance represents a instance of the chaining */ +/* backend. */ + +typedef struct _cb_backend_instance { + + char *inst_name; /* Unique name */ + Slapi_Backend *inst_be; /* Slapi_Bakedn associated with it */ + cb_backend *backend_type; /* pointer to the backend type */ + + /* configuration */ + + PRRWLock *rwl_config_lock; /* protect the config */ + char *configDn; /* config entry dn */ + char *monitorDn; /* monitor entry dn */ + int local_acl; /* True if local acl evaluation */ + /* sometimes a chaining backend may be associated with a local backend + 1) The chaining backend is the backend of a sub suffix, and the + parent suffix has a local backend + 2) Entry distribution is being used to distribute write operations to + a chaining backend and other operations to a local backend + (e.g. a replication hub or consumer) + If the associated local backend is being initialized (import), it will be + disabled, and it will be impossible to evaluate local acls. In this case, + we still want to be able to chain operations to a farm server or another + database chain. But the current code will not allow cascading without + local acl evaluation (cb_controls.c). The following variable allows us to relax that + restriction while the associated backend is disabled + */ + int associated_be_is_disabled; /* true if associated backend is disabled */ + int isconfigured; /* True when valid config entry */ + int impersonate; /* TRUE to impersonate users */ + int searchreferral; /* TRUE to return referral for scoped searches */ + int bind_retry; + struct timeval abandon_timeout; /* check for abandoned op periodically */ + struct timeval op_timeout; + char **url_array; /* list of urls to farm servers */ + char **chaining_components; /* List of plugins using chaining */ + char **illegal_attributes; /* Attributes not forwarded */ + char **every_attribute; /* attr list to get every attr, including op attrs */ + int sizelimit; + int timelimit; + int hoplimit; + int max_idle_time; /* how long we wait before pinging the farm server */ + int max_test_time; /* how long we wait during ping */ + + cb_conn_pool *pool; /* Operation cnx pool */ + cb_conn_pool *bind_pool; /* Bind cnx pool */ + + Slapi_Eq_Context eq_ctx; /* Use to identify the function put in the queue */ + + /* Monitoring */ + + struct { + Slapi_Mutex *mutex; + unsigned long addcount; + unsigned long deletecount; + unsigned long modifycount; + unsigned long modrdncount; + unsigned long searchbasecount; + unsigned long searchonelevelcount; + unsigned long searchsubtreecount; + unsigned long abandoncount; + unsigned long bindcount; + unsigned long unbindcount; + unsigned long comparecount; + } monitor; + + /* Monitoring the chaining BE availability */ + /* Principle: as soon as we detect an abnormal pb with an ldap operation, and we close the connection + or if we can't open a connection, we increment a counter (cpt). This counter represents the number of + continuously pbs we can notice. Before forwarding an LDAP operation, wether the farmserver is available or not, + through the value of the counter. If the farmserver is not available, we just return an error msg to the client */ + + struct { + int unavailable_period ; /* how long we wait as soon as the farm is declared unavailable */ + int max_num_conn_failed ; /* max number of consecutive failed/aborted connections before we declared the farm as unreachable */ + time_t unavailableTimeLimit ; /* time from which the chaining BE becomes available */ + int farmserver_state ; /* FARMSERVER_AVAILABLE if the chaining is available, FARMSERVER_UNAVAILABLE else */ + int cpt ; /* count the number of consecutive failed/aborted connexions */ + Slapi_Mutex *cpt_lock ; /* lock to protect the counter cpt */ + Slapi_Mutex *lock_timeLimit ; /* lock to protect the unavailableTimeLimit variable*/ + } monitor_availability; + + +} cb_backend_instance; + +/* Data structure for the search operation to carry candidates */ + +#define CB_SEARCHCONTEXT_ENTRY 2 + +typedef struct _cb_searchContext { + int type; + void *data; + int msgid; + LDAP *ld; + cb_outgoing_conn *cnx; + Slapi_Entry *tobefreed; + LDAPMessage *pending_result; + int pending_result_type; +} cb_searchContext; + +#define CB_REOPEN_CONN -1968 /* Different from any LDAP_XXX errors */ + +/* Forward declarations */ + +/* for ctrl_flags on cb_update_controls */ +#define CB_UPDATE_CONTROLS_ADDAUTH 1 +#define CB_UPDATE_CONTROLS_ISABANDON 2 + + +int cb_get_connection(cb_conn_pool * pool, LDAP ** ld, cb_outgoing_conn ** cnx, struct timeval * tmax, char **errmsg); +int cb_config(cb_backend_instance * cb, int argc, char ** argv ); +int cb_update_controls( Slapi_PBlock *pb, LDAP * ld, LDAPControl *** controls, int ctrl_flags); +int cb_is_control_forwardable(cb_backend * cb, char *controloid); +int cb_access_allowed (Slapi_PBlock *pb,Slapi_Entry *e,char *type,struct berval * bval, int op, char ** buf); +int cb_forward_operation(Slapi_PBlock * op); +int cb_parse_instance_config_entry(cb_backend * cb, Slapi_Entry * e); +int cb_abandon_connection(cb_backend_instance * cb, Slapi_PBlock * pb, LDAP ** ld); +int cb_atoi(char *str); +int cb_check_forward_abandon(cb_backend_instance * cb,Slapi_PBlock * pb, LDAP * ld, int msgid ); +int cb_search_monitor_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *e2, int *ret, char *t,void *a); +int cb_config_load_dse_info(Slapi_PBlock * pb); +int cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *string2, char *string3); +int cb_add_suffix(cb_backend_instance *inst, struct berval **bvals, int apply_mod, char *returntext); +int cb_create_default_backend_instance_config(cb_backend * cb); +int cb_build_backend_instance_config(cb_backend_instance *inst, Slapi_Entry * conf,int apply); +int cb_instance_delete_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg); +int cb_instance_search_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg); +int cb_instance_add_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg); +int cb_instance_modify_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg); +int cb_dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg); +int cb_config_search_callback(Slapi_PBlock *pb, Slapi_Entry* e1, Slapi_Entry* e2, int *returncode, + char *returntext, void *arg); +int cb_config_add_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg); +int cb_config_delete_instance_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg); +int cb_config_modify_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg); +int cb_config_add_instance_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg); +int cb_delete_monitor_callback(Slapi_PBlock * pb, Slapi_Entry * e, Slapi_Entry * entryAfter, int * returnCode, char * returnText, void * arg); +int cb_config_add_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode, + char *returntext, void *arg); +int cb_instance_add_config_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg); +int cb_config_modify_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, + char *returntext, void *arg); + +void cb_eliminate_illegal_attributes(cb_backend_instance * inst, Slapi_Entry * e); +void cb_release_op_connection(cb_conn_pool * pool, LDAP *ldd, int dispose); +void cb_register_supported_control( cb_backend * cb, char *controloid, unsigned long controlops ); +void cb_unregister_all_supported_control( cb_backend * cb ); +void cb_register_supported_control( cb_backend * cb, char *controloid, unsigned long controlops ); +void cb_unregister_supported_control( cb_backend * cb, char *controloid, unsigned long controlops ); +void cb_set_acl_policy(Slapi_PBlock *pb); +void cb_close_conn_pool(cb_conn_pool * pool); +void cb_update_monitor_info(Slapi_PBlock * pb, cb_backend_instance * inst,int op); +void cb_send_ldap_result(Slapi_PBlock *pb, int err, char *m,char *t, int ne, struct berval **urls ); +void cb_stale_all_connections( cb_backend_instance * be); +int +cb_config_add_instance_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg); + + +int chaining_back_add ( Slapi_PBlock *pb ); +int chaining_back_delete ( Slapi_PBlock *pb ); +int chaining_back_compare ( Slapi_PBlock *pb ); +int chaining_back_modify ( Slapi_PBlock *pb ); +int chaining_back_modrdn ( Slapi_PBlock *pb ); +int chaining_back_abandon ( Slapi_PBlock *pb ); +int chaining_back_entry_release ( Slapi_PBlock *pb ); +int chainingdb_next_search_entry( Slapi_PBlock *pb ); +int chainingdb_build_candidate_list ( Slapi_PBlock *pb ); +int chainingdb_start (Slapi_PBlock *pb ); +int chainingdb_bind (Slapi_PBlock *pb ); +int cb_db_size (Slapi_PBlock *pb ); +int cb_back_close (Slapi_PBlock *pb ); +int cb_back_cleanup (Slapi_PBlock *pb ); + +long cb_atol(char *str); + +Slapi_Entry * cb_LDAPMessage2Entry(LDAP * ctx, LDAPMessage * msg, int attrsonly); +char * cb_urlparse_err2string( int err ); +char * cb_get_rootdn(); +struct berval ** referrals2berval(char ** referrals); +cb_backend_instance * cb_get_instance(Slapi_Backend * be); +cb_backend * cb_get_backend_type(); +int cb_debug_on(); +void cb_set_debug(int on); +int cb_ping_farm(cb_backend_instance *cb,cb_outgoing_conn * cnx,time_t end); +void cb_update_failed_conn_cpt ( cb_backend_instance *cb ) ; +void cb_reset_conn_cpt( cb_backend_instance *cb ) ; +int cb_check_availability( cb_backend_instance *cb, Slapi_PBlock *pb ) ; + +time_t current_time(); +char* get_localhost_DNS(); + +/* this function is called when state of a backend changes */ +void cb_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state); + +#endif diff --git a/ldap/servers/plugins/chainingdb/cb_abandon.c b/ldap/servers/plugins/chainingdb/cb_abandon.c new file mode 100644 index 00000000..ca0cfc09 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_abandon.c @@ -0,0 +1,50 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Perform an abandon operation + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chaining_back_abandon ( Slapi_PBlock *pb ) +{ + /* + * Abandon forwarded to the farm server for scoped + * searches only. Done in cb_search.c + */ + return 0; +} + +int cb_check_forward_abandon(cb_backend_instance * cb,Slapi_PBlock * pb, LDAP * ld, int msgid ) { + + int rc; + LDAPControl ** ctrls=NULL; + + if (slapi_op_abandoned( pb )) { + + if ((rc=cb_forward_operation(pb)) != LDAP_SUCCESS ) { + return 0; + } + + if ((rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ISABANDON )) != LDAP_SUCCESS ) { + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return 0; + } + rc = ldap_abandon_ext(ld, msgid, ctrls, NULL ); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return 1; + } + return 0; +} diff --git a/ldap/servers/plugins/chainingdb/cb_acl.c b/ldap/servers/plugins/chainingdb/cb_acl.c new file mode 100644 index 00000000..ce0a6793 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_acl.c @@ -0,0 +1,60 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* +** generic function to send back results +** Turn off acl eval on front-end when needed +*/ + +void cb_set_acl_policy(Slapi_PBlock *pb) { + + Slapi_Backend *be; + cb_backend_instance *cb; + int noacl; + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + /* disable acl checking if the local_acl flag is not set + or if the associated backend is disabled */ + noacl=!(cb->local_acl) || cb->associated_be_is_disabled; + + if (noacl) { + slapi_pblock_set(pb, SLAPI_PLUGIN_DB_NO_ACL, &noacl); + } else { + /* Be very conservative about acl evaluation */ + slapi_pblock_set(pb, SLAPI_PLUGIN_DB_NO_ACL, &noacl); + } +} + +int cb_access_allowed( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access, /* access rights */ + char **errbuf + ) + +{ + +switch (access) { + + case SLAPI_ACL_ADD: + case SLAPI_ACL_DELETE: + case SLAPI_ACL_COMPARE: + case SLAPI_ACL_WRITE: + case SLAPI_ACL_PROXY: + + /* Keep in mind some entries are NOT */ + /* available for acl evaluation */ + + return slapi_access_allowed(pb,e,attr,val,access); + default: + return LDAP_INSUFFICIENT_ACCESS; +} +} diff --git a/ldap/servers/plugins/chainingdb/cb_add.c b/ldap/servers/plugins/chainingdb/cb_add.c new file mode 100644 index 00000000..e0b49f7c --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_add.c @@ -0,0 +1,227 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Perform an add operation + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chaining_back_add ( Slapi_PBlock *pb ) +{ + + Slapi_Backend *be; + Slapi_Entry *e; + cb_backend_instance *cb; + LDAPControl **serverctrls=NULL; + LDAPControl **ctrls=NULL; + int rc,parse_rc,msgid,i; + LDAP *ld=NULL; + char **referrals=NULL; + LDAPMod ** mods; + LDAPMessage * res; + char *dn,* matched_msg, *error_msg; + char *cnxerrbuf=NULL; + time_t endtime; + cb_outgoing_conn *cnx; + + if ( (rc=cb_forward_operation(pb)) != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL, "Remote data access disabled", 0, NULL ); + return -1; + } + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + /* Update monitor info */ + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_ADD); + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + + slapi_pblock_get( pb, SLAPI_ADD_TARGET, &dn ); + slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e ); + + /* Check local access controls */ + if (cb->local_acl && !cb->associated_be_is_disabled) { + char * errbuf=NULL; + rc = cb_access_allowed (pb, e, NULL, NULL, SLAPI_ACL_ADD, &errbuf); + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL ); + slapi_ch_free((void **)&errbuf); + return -1; + } + } + + /* Build LDAPMod from the SLapi_Entry */ + cb_eliminate_illegal_attributes(cb,e); + + if ((rc = slapi_entry2mods ((const Slapi_Entry *)e, NULL, &mods)) != LDAP_SUCCESS) { + cb_send_ldap_result( pb, rc,NULL,NULL, 0, NULL); + return -1; + } + + /* Grab a connection handle */ + if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR,NULL,cnxerrbuf, 0, NULL); + ldap_mods_free(mods,1); + slapi_ch_free((void **)&cnxerrbuf); + /* ping the farm. If the farm is unreachable, we increment the counter */ + cb_ping_farm(cb,NULL,0); + + return -1; + } + + /* Control management */ + if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH)) != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + ldap_mods_free(mods,1); + return -1; + } + + if ( slapi_op_abandoned( pb )) { + cb_release_op_connection(cb->pool,ld,0); + ldap_mods_free(mods,1); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return -1; + } + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + /* Send LDAP operation to the remote host */ + rc = ldap_add_ext( ld, dn, mods, ctrls, NULL, &msgid ); + + if ( NULL != ctrls) + ldap_controls_free(ctrls); + + if ( rc != LDAP_SUCCESS ) { + + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + ldap_mods_free(mods,1); + return -1; + } + + /* + * Poll the server for the results of the add operation. + * Check for abandoned operation regularly. + */ + + while ( 1 ) { + + if (cb_check_forward_abandon(cb,pb,ld,msgid)) { + /* connection handle released in cb_check_forward_abandon() */ + ldap_mods_free(mods,1); + return -1; + } + + rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res ); + switch ( rc ) { + case -1: + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + ldap_mods_free(mods,1); + if (res) + ldap_msgfree(res); + return -1; + case 0: + + if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) { + + /* does not respond. give up and return a*/ + /* error to the client. */ + + /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL);*/ + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + ldap_mods_free(mods,1); + if (res) + ldap_msgfree(res); + return -1; + } +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + default: + serverctrls=NULL; + matched_msg=error_msg=NULL; + referrals=NULL; + + parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, + &error_msg, &referrals, &serverctrls, 1 ); + + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(parse_rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc)); + ldap_mods_free(mods,1); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free referrals */ + if (referrals) + charray_free(referrals); + return -1; + } + + if ( rc != LDAP_SUCCESS ) { + struct berval ** refs = referrals2berval(referrals); + cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + ldap_mods_free(mods,1); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (refs) + ber_bvecfree(refs); + if (referrals) + charray_free(referrals); + if (serverctrls) + ldap_controls_free(serverctrls); + return -1; + } + + ldap_mods_free(mods,1 ); + cb_release_op_connection(cb->pool,ld,0); + + /* Add control response sent by the farm server */ + + for (i=0; serverctrls && serverctrls[i];i++) + slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free matched_msg, error_msg, and referrals if necessary */ + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (referrals) + charray_free(referrals); + cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL ); + + slapi_entry_free(e); + slapi_pblock_set( pb, SLAPI_ADD_ENTRY, NULL ); + + return 0; + } + } + + /* Never reached */ +} diff --git a/ldap/servers/plugins/chainingdb/cb_bind.c b/ldap/servers/plugins/chainingdb/cb_bind.c new file mode 100644 index 00000000..495b672f --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_bind.c @@ -0,0 +1,290 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +static void +cb_free_bervals( struct berval **bvs ); + + +static int +cb_sasl_bind_once_s( cb_conn_pool *pool, char *dn, int method, char * mechanism, + struct berval *creds, LDAPControl **reqctrls, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp , int * status); + +/* + * Attempt to chain a bind request off to "srvr." We return an LDAP error + * code that indicates whether we successfully got a response from the + * other server or not. If we succeed, we return LDAP_SUCCESS and *lderrnop + * is set to the result code from the remote server. + * + * Note that in the face of "ldap server down" or "ldap connect failed" errors + * we make up to "tries" attempts to bind to the remote server. Since we + * are only interested in recovering silently when the remote server is up + * but decided to close our connection, we retry without pausing between + * attempts. + */ + +static int +cb_sasl_bind_s(Slapi_PBlock * pb, cb_conn_pool *pool, int tries, + char *dn, int method,char * mechanism, struct berval *creds, LDAPControl **reqctrls, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp ,int *status) { + + int rc; + + do { + /* check to see if operation has been abandoned...*/ + + if (LDAP_AUTH_SIMPLE!=method) + return LDAP_AUTH_METHOD_NOT_SUPPORTED; + + if ( slapi_op_abandoned( pb )) { + rc = LDAP_USER_CANCELLED; + } else { + rc = cb_sasl_bind_once_s( pool, dn, method,mechanism, creds, reqctrls, + matcheddnp, errmsgp, refurlsp, resctrlsp ,status); + } + } while ( CB_LDAP_CONN_ERROR( rc ) && --tries > 0 ); + + return( rc ); +} + +static int +cb_sasl_bind_once_s( cb_conn_pool *pool, char *dn, int method, char * mechanism, + struct berval *creds, LDAPControl **reqctrls, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp , int * status) { + + int rc, msgid; + char **referrals; + struct timeval timeout_copy, *timeout; + LDAPMessage *result=NULL; + LDAP *ld=NULL; + char *cnxerrbuf=NULL; + cb_outgoing_conn *cnx; + int version=LDAP_VERSION3; + + /* Grab an LDAP connection to use for this bind. */ + + PR_RWLock_Rlock(pool->rwl_config_lock); + timeout_copy.tv_sec = pool->conn.bind_timeout.tv_sec; + timeout_copy.tv_usec = pool->conn.bind_timeout.tv_usec; + PR_RWLock_Unlock(pool->rwl_config_lock); + + if (( rc = cb_get_connection( pool, &ld ,&cnx, NULL, &cnxerrbuf)) != LDAP_SUCCESS ) { + *errmsgp=cnxerrbuf; + goto release_and_return; + } + + /* Send the bind operation (need to retry on LDAP_SERVER_DOWN) */ + + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ); + + if (( rc = ldap_sasl_bind( ld, dn, LDAP_SASL_SIMPLE, creds, reqctrls, + NULL, &msgid )) != LDAP_SUCCESS ) { + goto release_and_return; + } + + /* XXXSD what is the exact semantics of bind_to ? it is used to get a + connection handle and later to bind ==> bind op may last 2*bind_to + from the user point of view + confusion comes from teh fact that bind to is used 2for 3 differnt thinks, + */ + + /* + * determine timeout value (how long we will wait for a response) + * if timeout is zero'd, we wait indefinitely. + */ + if ( timeout_copy.tv_sec == 0 && timeout_copy.tv_usec == 0 ) { + timeout = NULL; + } else { + timeout = &timeout_copy; + } + + /* + * Wait for a result. + */ + rc = ldap_result( ld, msgid, 1, timeout, &result ); + + /* + * Interpret the result. + */ + + if ( rc == 0 ) { /* timeout */ + /* + * Timed out waiting for a reply from the server. + */ + rc = LDAP_TIMEOUT; + } else if ( rc < 0 ) { + + /* Some other error occurred (no result received). */ + char * matcheddnp2, * errmsgp2; + matcheddnp2=errmsgp2=NULL; + + rc = ldap_get_lderrno( ld, &matcheddnp2, &errmsgp2 ); + + /* Need to allocate errmsgs */ + if (matcheddnp2) + *matcheddnp=slapi_ch_strdup(matcheddnp2); + if (errmsgp2) + *errmsgp=slapi_ch_strdup(errmsgp2); + + if ( LDAP_SUCCESS != rc ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_sasl_bind_once_s failed (%s)\n",ldap_err2string(rc)); + } + } else { + + /* Got a result from remote server -- parse it.*/ + + char * matcheddnp2, * errmsgp2; + matcheddnp2=errmsgp2=NULL; + *resctrlsp=NULL; + rc = ldap_parse_result( ld, result, status, matcheddnp, errmsgp, + &referrals, resctrlsp, 1 ); + if ( referrals != NULL ) { + *refurlsp = referrals2berval( referrals ); + ldap_value_free( referrals ); + } + /* realloc matcheddn & errmsg because the mem alloc model */ + /* may differ from malloc */ + if (matcheddnp2) { + *matcheddnp=slapi_ch_strdup(matcheddnp2); + ldap_memfree(matcheddnp2); + } + if (errmsgp2) { + *errmsgp=slapi_ch_strdup(errmsgp2); + ldap_memfree(errmsgp2); + } + + } + +release_and_return: + if ( ld != NULL ) { + cb_release_op_connection( pool, ld, CB_LDAP_CONN_ERROR( rc )); + } + + return( rc ); +} + +int +chainingdb_bind( Slapi_PBlock *pb ) { + + int status=LDAP_SUCCESS; + int allocated_errmsg; + int rc=LDAP_SUCCESS; + cb_backend_instance *cb; + Slapi_Backend *be; + char *dn; + int method; + struct berval *creds, **urls; + char *matcheddn,*errmsg; + LDAPControl **reqctrls, **resctrls, **ctrls; + char * mechanism; + int freectrls=1; + int bind_retry; + + if ( LDAP_SUCCESS != (rc = cb_forward_operation(pb) )) { + cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL ); + return SLAPI_BIND_FAIL; + } + + ctrls=NULL; + /* don't add proxy auth control. use this call to check for supported */ + /* controls only. */ + if ( LDAP_SUCCESS != ( rc = cb_update_controls( pb, NULL, &ctrls, 0 )) ) { + cb_send_ldap_result( pb, rc, NULL, NULL, 0, NULL ); + if (ctrls) + ldap_controls_free(ctrls); + return SLAPI_BIND_FAIL; + } + if (ctrls) + ldap_controls_free(ctrls); + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + slapi_pblock_get( pb, SLAPI_BIND_TARGET, &dn ); + slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ); + slapi_pblock_get( pb, SLAPI_BIND_SASLMECHANISM, &mechanism); + slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &creds ); + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &reqctrls ); + cb = cb_get_instance(be); + + if ( NULL == dn ) + dn=""; + + /* always allow noauth simple binds */ + if (( method == LDAP_AUTH_SIMPLE) && creds->bv_len == 0 ) { + return( SLAPI_BIND_ANONYMOUS ); + } + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_BIND); + + matcheddn=errmsg=NULL; + allocated_errmsg = 0; + resctrls=NULL; + urls=NULL; + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + PR_RWLock_Rlock(cb->rwl_config_lock); + bind_retry=cb->bind_retry; + PR_RWLock_Unlock(cb->rwl_config_lock); + + if ( LDAP_SUCCESS == (rc = cb_sasl_bind_s(pb, cb->bind_pool, bind_retry, dn,method,mechanism, + creds,reqctrls,&matcheddn,&errmsg,&urls,&resctrls, &status))) { + rc = status; + allocated_errmsg = 1; + } else + if ( LDAP_USER_CANCELLED != rc ) { + errmsg = ldap_err2string( rc ); + if (rc == LDAP_TIMEOUT) { + cb_ping_farm(cb,NULL,NULL); + } + rc = LDAP_OPERATIONS_ERROR; + } + + if ( rc != LDAP_USER_CANCELLED ) { /* not abandoned */ + if ( resctrls != NULL ) { + slapi_pblock_set( pb, SLAPI_RESCONTROLS, resctrls ); + freectrls=0; + } + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, matcheddn, errmsg, 0, urls ); + } + } + + if ( urls != NULL ) { + cb_free_bervals( urls ); + } + if ( freectrls && ( resctrls != NULL )) { + ldap_controls_free( resctrls ); + } + slapi_ch_free((void **)& matcheddn ); + if ( allocated_errmsg && errmsg != NULL ) { + slapi_ch_free((void **)& errmsg ); + } + + return ((rc == LDAP_SUCCESS ) ? SLAPI_BIND_SUCCESS : SLAPI_BIND_FAIL ); +} + +static void +cb_free_bervals( struct berval **bvs ) +{ + int i; + + if ( bvs != NULL ) { + for ( i = 0; bvs[ i ] != NULL; ++i ) { + slapi_ch_free( (void **)&bvs[ i ] ); + } + } + slapi_ch_free( (void **)&bvs ); +} + diff --git a/ldap/servers/plugins/chainingdb/cb_cleanup.c b/ldap/servers/plugins/chainingdb/cb_cleanup.c new file mode 100644 index 00000000..b034b0a1 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_cleanup.c @@ -0,0 +1,22 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* +** cLeanup a chaining backend instance +*/ + +int cb_back_cleanup( Slapi_PBlock *pb ) +{ + + /* + ** Connections have been closed in cb_back_close() + ** For now, don't do more + */ + + return 0; +} + diff --git a/ldap/servers/plugins/chainingdb/cb_close.c b/ldap/servers/plugins/chainingdb/cb_close.c new file mode 100644 index 00000000..3ef52e32 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_close.c @@ -0,0 +1,67 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* +** Close a chaining backend instance +** Should be followed by a cleanup +*/ + +int cb_back_close( Slapi_PBlock *pb ) +{ + Slapi_Backend * be; + cb_backend_instance * inst; + int rc; + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + if (be == NULL) { + + cb_backend * cb = cb_get_backend_type(); + CB_ASSERT(cb!=NULL); + + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, cb->configDN, LDAP_SCOPE_BASE, + "(objectclass=*)",cb_config_modify_callback); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, cb->configDN, LDAP_SCOPE_BASE, + "(objectclass=*)",cb_config_modify_check_callback); + + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->configDN, LDAP_SCOPE_BASE, + "(objectclass=*)",cb_config_add_callback); + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, cb->configDN, LDAP_SCOPE_BASE, + "(objectclass=*)",cb_config_add_check_callback); + + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, cb->configDN, LDAP_SCOPE_BASE, + "(objectclass=*)",cb_config_search_callback); + + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->pluginDN, + LDAP_SCOPE_SUBTREE, CB_CONFIG_INSTANCE_FILTER, cb_config_add_instance_callback); + + return 0; + } + + /* XXXSD: temp fix . Sometimes, this functions */ + /* gets called with a ldbm backend instance... */ + + { + const char * betype = slapi_be_gettype(be); + if (!betype || strcasecmp(betype,CB_CHAINING_BACKEND_TYPE)) { + + slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "Wrong database type.\n"); + return 0; + } + } + + inst = cb_get_instance(be); + CB_ASSERT( inst!=NULL ); + + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"Stopping chaining database instance %s\n", + inst->configDn); + /* emulate a backend instance deletion */ + /* to clean up everything */ + cb_instance_delete_config_callback(NULL, NULL,NULL, &rc, NULL, inst); + + return 0; +} diff --git a/ldap/servers/plugins/chainingdb/cb_compare.c b/ldap/servers/plugins/chainingdb/cb_compare.c new file mode 100644 index 00000000..ebce1645 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_compare.c @@ -0,0 +1,217 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Perform a compare operation + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chaining_back_compare ( Slapi_PBlock *pb ) +{ + + Slapi_Backend * be; + cb_backend_instance *cb=NULL; + struct berval *bval=NULL; + LDAPControl **ctrls, **serverctrls; + int rc,parse_rc,msgid,i,checkacl; + LDAP *ld=NULL; + char **referrals=NULL; + LDAPMessage * res; + char *type,*dn,* matched_msg, *error_msg; + char *cnxerrbuf=NULL; + time_t endtime; + cb_outgoing_conn *cnx; + + if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) { + cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL ); + return -1; + } + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_COMPARE); + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + slapi_pblock_get( pb, SLAPI_COMPARE_TARGET, &dn ); + slapi_pblock_get( pb, SLAPI_COMPARE_TYPE, &type ); + slapi_pblock_get( pb, SLAPI_COMPARE_VALUE, &bval ); + + /* + * Check local acls + * No need to lock the config to access cb->local_acl + */ + + checkacl=cb->local_acl && !cb->associated_be_is_disabled; + + if (checkacl) { + char * errbuf=NULL; + Slapi_Entry *te = slapi_entry_alloc(); + slapi_entry_set_dn(te,slapi_ch_strdup(dn)); + rc = cb_access_allowed (pb, te, type, bval, SLAPI_ACL_COMPARE,&errbuf); + slapi_entry_free(te); + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL ); + slapi_ch_free((void **) &errbuf); + return 1; + } + } + + /* + * Grab a connection handle + */ + + if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL); + slapi_ch_free((void **)&cnxerrbuf); + /* ping the farm. If the farm is unreachable, we increment the counter */ + cb_ping_farm(cb,NULL,0); + return 1; + } + + /* + * Control management + */ + + if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return 1; + } + + if ( slapi_op_abandoned( pb )) { + cb_release_op_connection(cb->pool,ld,0); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return -1; + } + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + /* + * Send LDAP operation to the remote host + */ + + rc = ldap_compare_ext( ld, dn, type, bval, ctrls, NULL, &msgid ); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return 1; + } + + while ( 1 ) { + + if (cb_check_forward_abandon(cb,pb,ld,msgid)) { + return -1; + } + + /* No need to lock the config to access cb->abandon_timeout */ + rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res ); + switch ( rc ) { + case -1: + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return 1; + case 0: + if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) { + + /* does not respond. give up and return a*/ + /* error to the client. */ + + /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL);*/ + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return 1; + } +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + default: + matched_msg=error_msg=NULL; + parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, + &error_msg, &referrals, &serverctrls, 1 ); + if ( parse_rc != LDAP_SUCCESS ) { + + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(parse_rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free referrals */ + if (referrals) + charray_free(referrals); + return 1; + } + + switch ( rc ) { + + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + break; + default: { + struct berval ** refs = referrals2berval(referrals); + + cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (refs) + ber_bvecfree(refs); + if (referrals) + charray_free(referrals); + if (serverctrls) + ldap_controls_free(serverctrls); + return 1; + } + } + + /* Add control response sent by the farm server */ + + for (i=0; serverctrls && serverctrls[i];i++) + slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free matched_msg, error_msg, and referrals if necessary */ + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (referrals) + charray_free(referrals); + + cb_send_ldap_result( pb, rc , NULL, NULL, 0, NULL ); + cb_release_op_connection(cb->pool,ld,0); + return 0; + } + } + + /* Never reached */ + /* return 0; */ +} diff --git a/ldap/servers/plugins/chainingdb/cb_config.c b/ldap/servers/plugins/chainingdb/cb_config.c new file mode 100644 index 00000000..13dd7b22 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_config.c @@ -0,0 +1,600 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" +#include <errno.h> + +/* Forward declarations */ + +static int cb_parse_config_entry(cb_backend * cb, Slapi_Entry *e); + +/* body starts here */ + +/* Used to add an array of entries, like the one above and +** cb_instance_skeleton_entries to the dse. +** Returns 0 on success. +*/ + + + +int cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *string2, char *string3) +{ + int x; + Slapi_Entry *e; + Slapi_PBlock *util_pb = NULL; + int res, rc = 0; + char entry_string[CB_BUFSIZE]; + + for(x = 0; strlen(entries[x]) > 0; x++) { + util_pb = slapi_pblock_new(); + sprintf(entry_string, entries[x], string1, string2, string3); + e = slapi_str2entry(entry_string, 0); + slapi_add_entry_internal_set_pb(util_pb, e, NULL, cb->identity, 0); + slapi_add_internal_pb(util_pb); + slapi_pblock_get(util_pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if ( LDAP_SUCCESS != res && LDAP_ALREADY_EXISTS != res ) { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Unable to add config entry (%s) to the DSE: %s\n", + escape_string(slapi_entry_get_dn(e), ebuf), + ldap_err2string(res)); + rc = res; + slapi_pblock_destroy(util_pb); + break; + } + slapi_pblock_destroy(util_pb); + } + return rc; +} + +/* +** Try to read the entry cn=config,cn=chaining database,cn=plugins,cn=config +** If the entry is there, then process the configuration information it stores. +** If it is missing, create it with default configuration. +** The default configuration is taken from the default entry if it exists +*/ + +int cb_config_load_dse_info(Slapi_PBlock * pb) { + + Slapi_PBlock *search_pb,*default_pb; + Slapi_Entry **entries = NULL; + Slapi_Entry *configEntry=NULL; + int res,default_res,i; + char defaultDn[CB_BUFSIZE]; + cb_backend *cb; + + slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cb ); + + /* Get global configuration entry */ + search_pb = slapi_pblock_new(); + slapi_search_internal_set_pb(search_pb, cb->configDN, LDAP_SCOPE_BASE, + "objectclass=*", NULL, 0, NULL, NULL, cb->identity, 0); + slapi_search_internal_pb (search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + + if ( LDAP_SUCCESS == res ) { + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || entries[0] == NULL) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Error accessing entry <%s>\n",cb->configDN); + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + return 1; + } + configEntry=entries[0]; + } else + if ( LDAP_NO_SUCH_OBJECT == res ) { + /* Don't do anything. The default conf is used */ + configEntry=NULL; + } else { + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Error accessing entry <%s> (%s)\n",cb->configDN,ldap_err2string(res)); + return 1; + } + + /* Parse the configuration entry */ + /* Default config if configEntry is NULL*/ + + cb_parse_config_entry(cb, configEntry); + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + + /* + ** Parse the chaining backend instances + ** Immediate subordinates of cn=<plugin name>,cn=plugins,cn=config + */ + + search_pb = slapi_pblock_new(); + + slapi_search_internal_set_pb(search_pb, cb->pluginDN, LDAP_SCOPE_ONELEVEL, + CB_CONFIG_INSTANCE_FILTER,NULL,0,NULL,NULL,cb->identity, 0); + slapi_search_internal_pb (search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (res != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Error accessing the config DSE (%s)\n",ldap_err2string(res)); + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + return 1; + } + + /* Get the default instance value entry if it exists */ + /* else create it */ + + sprintf(defaultDn,"cn=default instance config,%s",cb->pluginDN); + + default_pb = slapi_pblock_new(); + slapi_search_internal_set_pb(default_pb, defaultDn, LDAP_SCOPE_BASE, + "objectclass=*", NULL, 0, NULL, NULL, cb->identity, 0); + slapi_search_internal_pb (default_pb); + slapi_pblock_get(default_pb, SLAPI_PLUGIN_INTOP_RESULT, &default_res); + if (LDAP_SUCCESS != default_res) { + cb_create_default_backend_instance_config(cb); + } + + slapi_free_search_results_internal(default_pb); + slapi_pblock_destroy(default_pb); + + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + for (i=0; entries && entries[i]; i++) { + int retcode; + char * aDn=slapi_entry_get_dn(entries[i]); + slapi_dn_normalize(aDn); + + cb_instance_add_config_callback(pb,entries[i],NULL,&retcode,NULL,cb); + } + + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + + + /* Add callbacks */ + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, cb->configDN, + LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_modify_check_callback, (void *) cb); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, cb->configDN, + LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_modify_callback, (void *) cb); + + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, cb->configDN, + LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_add_check_callback, (void *) cb); + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->configDN, + LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_add_callback, (void *) cb); + + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, cb->configDN, + LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_search_callback, (void *) cb); + + /* instance creation */ + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, cb->pluginDN, + LDAP_SCOPE_SUBTREE, CB_CONFIG_INSTANCE_FILTER, cb_config_add_instance_check_callback, (void *) cb); + + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->pluginDN, + LDAP_SCOPE_SUBTREE, CB_CONFIG_INSTANCE_FILTER, cb_config_add_instance_callback, (void *) cb); + + return 0; +} + +/* Check validity of the modification */ + +int cb_config_add_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode, + char *returntext, void *arg) +{ + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + struct berval * bval; + int i; + cb_backend *cb = (cb_backend *) arg; + + CB_ASSERT (cb!=NULL); + + for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) { + char * attr_name=NULL; + slapi_attr_get_type(attr, &attr_name); + + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) { + /* First, parse the values to make sure they are valid */ + i = slapi_attr_first_value(attr, &sval); + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + if (!cb_is_control_forwardable(cb,bval->bv_val)) { + slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM, + "control %s can't be forwarded.\n",bval->bv_val); + *returncode=LDAP_CONSTRAINT_VIOLATION; + return SLAPI_DSE_CALLBACK_ERROR; + } + i = slapi_attr_next_value(attr, i, &sval); + } + } + } + *returncode=LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +/* +** Global config is beeing added +** Take the new values into account +*/ + +int +cb_config_add_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode, + char *returntext, void *arg) +{ + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + struct berval * bval; + int i; + cb_backend *cb = (cb_backend *) arg; + + CB_ASSERT (cb!=NULL); + + for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) { + char * attr_name=NULL; + slapi_attr_get_type(attr, &attr_name); + + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) { + /* First, parse the values to make sure they are valid */ + i = slapi_attr_first_value(attr, &sval); + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + if (!cb_is_control_forwardable(cb,bval->bv_val)) { + slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM, + "control %s can't be forwarded.\n",bval->bv_val); + *returncode=LDAP_CONSTRAINT_VIOLATION; + return SLAPI_DSE_CALLBACK_ERROR; + } + i = slapi_attr_next_value(attr, i, &sval); + } + /* second pass. apply changes */ + cb_unregister_all_supported_control(cb); + i = slapi_attr_first_value(attr, &sval); + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + cb_register_supported_control(cb,bval->bv_val,0); + i = slapi_attr_next_value(attr, i, &sval); + } + } + } + *returncode=LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +int +cb_config_search_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode, + char *returntext, void *arg) { + + cb_backend *cb = (cb_backend *) arg; + struct berval val; + struct berval *vals[2]; + int i = 0; + + CB_ASSERT (cb!=NULL); + + vals[0] = &val; + vals[1] = NULL; + + /* naming attribute */ + val.bv_val = "config"; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "cn", (struct berval **)vals ); + + /* objectclass attribute */ + val.bv_val = "top"; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "objectclass", (struct berval **)vals ); + val.bv_val = CB_CONFIG_EXTENSIBLEOCL; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_merge( e, "objectclass", (struct berval **)vals ); + + /* other attributes */ + + PR_RWLock_Rlock(cb->config.rwl_config_lock); + + for (i=0; cb->config.forward_ctrls && cb->config.forward_ctrls[i] ; i++) { + val.bv_val=cb->config.forward_ctrls[i]; + val.bv_len = strlen( val.bv_val ); + if (i==0) + slapi_entry_attr_replace( e, CB_CONFIG_GLOBAL_FORWARD_CTRLS, (struct berval **)vals ); + else + slapi_entry_attr_merge( e, CB_CONFIG_GLOBAL_FORWARD_CTRLS, (struct berval **)vals ); + } + + for (i=0;cb->config.chaining_components && cb->config.chaining_components[i];i++) { + val.bv_val=cb->config.chaining_components[i]; + val.bv_len = strlen( val.bv_val ); + if (i==0) + slapi_entry_attr_replace( e, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS, + (struct berval **)vals ); + else + slapi_entry_attr_merge( e, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS, + (struct berval **)vals ); + } + + for (i=0; cb->config.chainable_components && cb->config.chainable_components[i]; i++) { + val.bv_val=cb->config.chainable_components[i]; + val.bv_len = strlen( val.bv_val ); + if (i==0) + slapi_entry_attr_replace( e, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS, + (struct berval **)vals ); + else + slapi_entry_attr_merge( e, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS, + (struct berval **)vals ); + } + + + PR_RWLock_Unlock(cb->config.rwl_config_lock); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +/* Check validity of the modification */ + +int +cb_config_modify_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, + char *returntext, void *arg) +{ + LDAPMod **mods; + char *attr_name; + int i,j; + cb_backend *cb = (cb_backend *) arg; + + CB_ASSERT (cb!=NULL); + + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + + for (i = 0; mods[i] ; i++) { + attr_name = mods[i]->mod_type; + + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) { + char * config_attr_value; + for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) { + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + if (!cb_is_control_forwardable(cb,config_attr_value)) { + slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM, + "control %s can't be forwarded.\n",config_attr_value); + *returncode=LDAP_CONSTRAINT_VIOLATION; + return SLAPI_DSE_CALLBACK_ERROR; + } + } + } + } + *returncode=LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +int +cb_config_modify_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, + char *returntext, void *arg) +{ + LDAPMod **mods; + char *attr_name; + int i,j; + cb_backend *cb = (cb_backend *) arg; + + CB_ASSERT (cb!=NULL); + + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + + for (i = 0; mods[i] ; i++) { + attr_name = mods[i]->mod_type; + + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) { + char * config_attr_value; + int done=0; + for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) { + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + if (!cb_is_control_forwardable(cb,config_attr_value)) { + slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM, + "control %s can't be forwarded.\n",config_attr_value); + *returncode=LDAP_CONSTRAINT_VIOLATION; + return SLAPI_DSE_CALLBACK_ERROR; + } + + if ( mods[i]->mod_op & LDAP_MOD_REPLACE) { + if (!done) { + cb_unregister_all_supported_control(cb); + done=1; + } + cb_register_supported_control(cb,config_attr_value,0); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) { + cb_register_supported_control(cb,config_attr_value,0); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + cb_unregister_supported_control(cb,config_attr_value,0); + } + } + if (NULL == mods[i]->mod_bvalues) + cb_unregister_all_supported_control(cb); + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_DEBUG )) { + /* assume single-valued */ + if (mods[i]->mod_op & LDAP_MOD_DELETE) + cb_set_debug(0); + else if ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) + cb_set_debug(1); + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS )) { + char * config_attr_value; + int done=0; + + PR_RWLock_Wlock(cb->config.rwl_config_lock); + + for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) { + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + if ( mods[i]->mod_op & LDAP_MOD_REPLACE) { + if (!done) { + charray_free(cb->config.chaining_components); + cb->config.chaining_components=NULL; + done=1; + } + /* XXXSD assume dn. Normalize it */ + charray_add(&cb->config.chaining_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value))); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) { + charray_add(&cb->config.chaining_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value))); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + charray_remove(cb->config.chaining_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value))); + } + } + if (NULL == mods[i]->mod_bvalues) { + charray_free(cb->config.chaining_components); + cb->config.chaining_components=NULL; + } + + PR_RWLock_Unlock(cb->config.rwl_config_lock); + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS )) { + char * config_attr_value; + int done=0; + + PR_RWLock_Wlock(cb->config.rwl_config_lock); + + for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) { + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + if ( mods[i]->mod_op & LDAP_MOD_REPLACE) { + if (!done) { + charray_free(cb->config.chainable_components); + cb->config.chainable_components=NULL; + done=1; + } + charray_add(&cb->config.chainable_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value) +)); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) { + charray_add(&cb->config.chainable_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value) +)); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + charray_remove(cb->config.chainable_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value) +)); + } + } + if (NULL == mods[i]->mod_bvalues) { + charray_free(cb->config.chainable_components); + cb->config.chainable_components=NULL; + } + + PR_RWLock_Unlock(cb->config.rwl_config_lock); + } + + + } + *returncode=LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +/* +** Creation of a new backend instance +*/ + +int +cb_config_add_instance_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, + char *returntext, void *arg) +{ + cb_backend *cb=(cb_backend *)arg; + CB_ASSERT(cb!=NULL); + cb_instance_add_config_callback(pb,entryBefore,NULL,returncode,returntext,cb); + return SLAPI_DSE_CALLBACK_OK; +} + +int +cb_config_add_instance_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, + char *returntext, void *arg) +{ + cb_backend *cb=(cb_backend *)arg; + CB_ASSERT(cb!=NULL); + return cb_instance_add_config_check_callback(pb,entryBefore,NULL,returncode,returntext,cb); +} + +/* +** Parse the global chaining backend configuration +*/ + +static int cb_parse_config_entry(cb_backend * cb, Slapi_Entry *e) +{ + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + struct berval *bval; + int i; + + if (e == NULL) + return LDAP_SUCCESS; + + cb_set_debug(0); + + for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) { + char * attr_name=NULL; + slapi_attr_get_type(attr, &attr_name); + + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) { + i = slapi_attr_first_value(attr, &sval); + + PR_RWLock_Wlock(cb->config.rwl_config_lock); + if (cb->config.forward_ctrls) { + charray_free(cb->config.forward_ctrls); + cb->config.forward_ctrls=NULL; + } + PR_RWLock_Unlock(cb->config.rwl_config_lock); + + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + /* For now, don't support operation type */ + cb_register_supported_control(cb,bval->bv_val, + SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE | + SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE | + SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN); + i = slapi_attr_next_value(attr, i, &sval); + } + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS )) { + i = slapi_attr_first_value(attr, &sval); + PR_RWLock_Wlock(cb->config.rwl_config_lock); + if (cb->config.chaining_components) { + charray_free(cb->config.chaining_components); + cb->config.chaining_components=NULL; + } + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + /* XXXSD assume dn. Normalize it */ + charray_add( &cb->config.chaining_components, + slapi_dn_normalize(slapi_ch_strdup(bval->bv_val))); + i = slapi_attr_next_value(attr, i, &sval); + } + PR_RWLock_Unlock(cb->config.rwl_config_lock); + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS )) { + i = slapi_attr_first_value(attr, &sval); + PR_RWLock_Wlock(cb->config.rwl_config_lock); + if (cb->config.chainable_components) { + charray_free(cb->config.chainable_components); + cb->config.chainable_components=NULL; + } + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + charray_add( &cb->config.chainable_components, + slapi_dn_normalize(slapi_ch_strdup(bval->bv_val))); + i = slapi_attr_next_value(attr, i, &sval); + } + PR_RWLock_Unlock(cb->config.rwl_config_lock); + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_DEBUG )) { + i = slapi_attr_first_value(attr, &sval); + if (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + /* any value */ + cb_set_debug(1); + } + } + } + return LDAP_SUCCESS; +} diff --git a/ldap/servers/plugins/chainingdb/cb_conn_stateless.c b/ldap/servers/plugins/chainingdb/cb_conn_stateless.c new file mode 100644 index 00000000..d97f947e --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_conn_stateless.c @@ -0,0 +1,1132 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Most of the complicated connection-related code lives in this file. Some + * general notes about how we manage our connections to "remote" LDAP servers: + * + * 1) Each farm server we have a relationship with is managed independently. + * + * 2) We may simultaneously issue multiple requests on a single LDAP + * connection. Each server has a "maxconcurrency" configuration + * parameter associated with it that caps the number of outstanding operations + * per connection. For each connection we maintain a "usecount" + * which is used to track the number of threads using the connection. + * + * 3) IMPORTANT NOTE: This connexion management is stateless i.e there is no garanty that + * operation from the same incoming client connections are sent to the same + * outgoing connection to the farm server. Today, this is not a problem because + * all controls we support are stateless. The implementation of the abandon + * operation takes this limitation into account. + * + * 4) We may open more than one connection to a server. Each farm server + * has a "maxconnections" configuration parameter associated with it + * that caps the number of connections. + * + * 5) If no connection is available to service a request , threads + * go to sleep on a condition variable and one is woken up each time + * a connection's "usecount" is decremented. + * + * 6) If we see an LDAP_CONNECT_ERROR or LDAP_SERVER_DOWN error on a + * session handle, we mark its status as CB_LDAP_STATUS_DOWN and + * close it as soon as all threads using it release it. Connections + * marked as "down" are not counted against the "maxconnections" limit. + * + * 7) We close and reopen connections that have been open for more than + * the server's configured connection lifetime. This is done to ensure + * that we reconnect to a primary server after failover occurs. If no + * lifetime is configured or it is set to 0, we never close and reopen + * connections. + */ + +static void cb_close_and_dispose_connection ( cb_outgoing_conn * conn ); +static void cb_check_for_stale_connections(cb_conn_pool * pool); + +PRUint32 PR_GetThreadID(PRThread *thread); + +/* returns the threadId of the current thread modulo MAX_CONN_ARRAY +=> gives the position of the thread in the array of secure connections */ +static int PR_ThreadSelf() { + PRThread *thr = PR_GetCurrentThread(); + PRUint32 myself = PR_GetThreadID(thr); + myself &= 0x000007FF ; + return myself; +} + +static int PR_MyThreadId() { + PRThread *thr = PR_GetCurrentThread(); + PRUint32 myself = PR_GetThreadID(thr); + return myself; +} + +/* +** Close outgoing connections +*/ + +void cb_close_conn_pool(cb_conn_pool * pool) { + + cb_outgoing_conn *conn, *nextconn; + int secure = pool->secure; + int i = 0; + + slapi_lock_mutex( pool->conn.conn_list_mutex ); + + if (secure) { + for (i=0; i< MAX_CONN_ARRAY; i++) { + for (conn = pool->connarray[i]; conn != NULL; conn = nextconn) { + if ( conn->status != CB_CONNSTATUS_OK ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_close_conn_pool: unexpected connection state (%d)\n",conn->status); + } + nextconn=conn->next; + cb_close_and_dispose_connection(conn); + } + } + } + else { + for ( conn = pool->conn.conn_list; conn != NULL; conn = nextconn ) { + if ( conn->status != CB_CONNSTATUS_OK ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_close_conn_pool: unexpected connection state (%d)\n",conn->status); + } + nextconn=conn->next; + cb_close_and_dispose_connection(conn); + } + } + + pool->conn.conn_list=NULL; + pool->conn.conn_list_count=0; + + slapi_unlock_mutex( pool->conn.conn_list_mutex ); +} + +/* + * Get an LDAP session handle for communicating with the farm servers. + * + * Returns an LDAP eror code, typically: + * LDAP_SUCCESS + * LDAP_TIMELIMIT_EXCEEDED + * LDAP_CONNECT_ERROR + * NOTE : if maxtime NULL, use operation timeout + */ + +int cb_get_connection(cb_conn_pool * pool, LDAP ** lld, cb_outgoing_conn ** cc,struct timeval * maxtime, char **errmsg) { + + int rc=LDAP_SUCCESS; /* optimistic */ + cb_outgoing_conn *conn=NULL; + cb_outgoing_conn *connprev=NULL; + LDAP *ld=NULL; + time_t endbefore=0; + int checktime=0; + struct timeval bind_to, op_to; + unsigned int maxconcurrency,maxconnections; + char *password,*binddn,*hostname; + unsigned int port; + int secure; + static char *error1="Can't contact remote server : %s"; + static char *error2="Can't bind to remote server : %s"; + int isMultiThread = ENABLE_MULTITHREAD_PER_CONN ; /* by default, we enable multiple operations per connection */ + + /* + ** return an error if we can't get a connection + ** before the operation timeout has expired + ** bind_timeout: timeout for the bind operation (if bind needed) + ** ( checked in ldap_result ) + ** op_timeout: timeout for the op that needs a connection + ** ( checked in the loop ) + */ + *cc=NULL; + + PR_RWLock_Rlock(pool->rwl_config_lock); + maxconcurrency=pool->conn.maxconcurrency; + maxconnections=pool->conn.maxconnections; + bind_to.tv_sec = pool->conn.bind_timeout.tv_sec; + bind_to.tv_usec = pool->conn.bind_timeout.tv_usec; + op_to.tv_sec = pool->conn.op_timeout.tv_sec; + op_to.tv_usec = pool->conn.op_timeout.tv_usec; + + /* SD 02/10/2000 temp fix */ + /* allow dynamic update of the binddn & password */ + /* host, port and security mode */ + /* previous values are NOT freed when changed */ + /* won't likely to be changed often */ + /* pointers put in the waste basket fields and */ + /* freed when the backend is stopped. */ + + password=pool->password; + binddn=pool->binddn; + hostname=pool->hostname; + port=pool->port; + secure=pool->secure; + + PR_RWLock_Unlock(pool->rwl_config_lock); + + if (secure) { + isMultiThread = DISABLE_MULTITHREAD_PER_CONN ; + } + + /* For stupid admins */ + if (maxconnections <=0) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "<== cb_get_connection error (no connection available)\n"); + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error1,"no connection available"); + } + return LDAP_CONNECT_ERROR; + } + + if (maxtime) { + if (maxtime->tv_sec != 0) { + checktime=1; + endbefore = current_time() + maxtime->tv_sec; + + /* make sure bind to <= operation timeout */ + if ((bind_to.tv_sec==0) || (bind_to.tv_sec > maxtime->tv_sec)) + bind_to.tv_sec=maxtime->tv_sec; + } + } else { + if (op_to.tv_sec != 0) { + checktime=1; + endbefore = current_time() + op_to.tv_sec; + + /* make sure bind to <= operation timeout */ + if ((bind_to.tv_sec==0) || (bind_to.tv_sec > op_to.tv_sec)) + bind_to.tv_sec=op_to.tv_sec; + } + } + + /* + * Close (or mark to be closed) any connections for this farm server that have + * exceeded the maximum connection lifetime. + */ + + cb_check_for_stale_connections(pool); + + /* + * Look for an available, already open connection + */ + + slapi_lock_mutex( pool->conn.conn_list_mutex ); + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "==> cb_get_connection server %s conns: %d maxconns: %d\n", + hostname, pool->conn.conn_list_count, maxconnections ); + } + + for (;;) { + + /* time limit mgmt */ + if (checktime) { + if (current_time() > endbefore ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_get_connection server %s expired.\n", hostname ); + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error1,"timelimit exceeded"); + } + rc=LDAP_TIMELIMIT_EXCEEDED; + conn=NULL; + ld=NULL; + goto unlock_and_return; + } + } + + /* + * First, look for an available, already open/bound connection + */ + + if (secure) { + for (conn = pool->connarray[PR_ThreadSelf()]; conn != NULL; conn = conn->next) { + if ((conn->ThreadId == PR_MyThreadId()) && (conn->status == CB_CONNSTATUS_OK && + conn->refcount < maxconcurrency)){ + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "<= cb_get_connection server found conn 0x%x to use)\n", conn ); + } + goto unlock_and_return; /* found one */ + } + } + } + else { + connprev = NULL; + for ( conn = pool->conn.conn_list; conn != NULL; conn = conn->next ) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "list: conn 0x%x status %d refcount %d\n", conn, + conn->status, conn->refcount ); + } + + if ( conn->status == CB_CONNSTATUS_OK + && conn->refcount < maxconcurrency ) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "<= cb_get_connection server found conn 0x%x to use)\n", conn ); + } + goto unlock_and_return; /* found one */ + } + connprev = conn; + } + } + + if ( secure || pool->conn.conn_list_count <maxconnections) { + + int version=LDAP_VERSION3; + + /* check wether the security libraries are correctly initialized */ + if (secure && slapd_security_library_is_initialized() != 1) { + slapi_log_error( + SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "SSL Not Initialized, Chaining Backend over SSL FAILED\n"); + rc = LDAP_CONNECT_ERROR; + goto unlock_and_return; + } + + /* + * we have not exceeded the maximum number of connections allowed, + * so we initialize a new one and add it to the end of our list. + */ + + /* No need to lock. url can't be changed dynamically */ + if ((ld=slapi_ldap_init(hostname,port,secure,isMultiThread))== NULL) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Can't contact server <%s> port <%d>.\n", hostname, port); + } + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error1,"unknown reason"); + } + rc = LDAP_CONNECT_ERROR; + goto unlock_and_return; + } + + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ); + /* Don't chase referrals */ + ldap_set_option( ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF ); + + /* no controls and simple bind only */ + /* For now, bind even if no user to detect error */ + /* earlier */ + if (pool->bindit) { + int msgid; + LDAPMessage *res=NULL; + int parse_rc; + PRErrorCode prerr = 0; + LDAPControl **serverctrls=NULL; + char **referrals=NULL; + + char *plain = NULL; + int ret = -1; + + rc=LDAP_SUCCESS; + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Bind to to server <%s> port <%d> as <%s>\n", + hostname, port, binddn); + } + + ret = pw_rever_decode(password, &plain, CB_CONFIG_USERPASSWORD); + + /* Pb occured in decryption: stop now, binding will fail */ + if ( ret == -1 ) + { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Internal credentials decoding error\n.", + 0, 0, 0); + } + rc = LDAP_LOCAL_ERROR; + goto unlock_and_return; + } + + /* Password-based client authentication */ + + if (( msgid = ldap_simple_bind( ld, binddn, plain)) <0) { + rc=ldap_get_lderrno( ld, NULL, NULL ); + prerr=PR_GetError(); + } + if ( ret == 0 ) free(plain); /* free plain only if it has been duplicated */ + + if ( rc != LDAP_SUCCESS ) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Can't bind to server <%s> port <%d>. " + "(LDAP error %d - %s; " + SLAPI_COMPONENT_NAME_NSPR " error %d - %s)\n", + hostname, port, rc, + ldap_err2string(rc), + prerr, slapd_pr_strerror(prerr)); + } + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error2, ldap_err2string(rc)); + } + rc = LDAP_CONNECT_ERROR; + goto unlock_and_return; + } + + rc = ldap_result( ld, msgid, 0, &bind_to, &res ); + switch (rc) { + case -1: + rc = ldap_get_lderrno( ld, NULL, NULL ); + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Can't bind to server <%s> port <%d>. " + "(LDAP error %d - %s; " + SLAPI_COMPONENT_NAME_NSPR " error %d - %s)\n", + hostname, port, rc, + ldap_err2string(rc), + prerr, slapd_pr_strerror(prerr)); + } + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error2,ldap_err2string(rc)); + } + rc = LDAP_CONNECT_ERROR; + goto unlock_and_return; + case 0: + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Can't bind to server <%s> port <%d>. (%s)\n", + hostname, port, "time-out expired"); + } + rc = LDAP_CONNECT_ERROR; + goto unlock_and_return; + default: + + parse_rc = ldap_parse_result( ld, res, &rc, NULL, + NULL, &referrals, &serverctrls, 1 ); + + if ( parse_rc != LDAP_SUCCESS ) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Can't bind to server <%s> port <%d>. (%s)\n", + hostname, port, ldap_err2string(parse_rc)); + } + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error2,ldap_err2string(parse_rc)); + } + rc = parse_rc; + goto unlock_and_return; + } + + if ( rc != LDAP_SUCCESS ) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Can't bind to server <%s> port <%d>. (%s)\n", + hostname, port, ldap_err2string(rc)); + } + if ( errmsg ) { + *errmsg = slapi_ch_malloc(CB_BUFSIZE); + sprintf(*errmsg,error2, ldap_err2string(rc)); + } + goto unlock_and_return; + } + + if ( serverctrls ) + { + int i; + for( i = 0; serverctrls[ i ] != NULL; ++i ) + { + if ( !(strcmp( serverctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRED)) ) + { + /* Bind is successful but password has expired */ + slapi_log_error(SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "Succesfully bound as %s to remote server %s:%d, " + "but password has expired.\n", + binddn, hostname, port); + } + else if ( !(strcmp( serverctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRING)) ) + { + /* The password is expiring in n seconds */ + if ( (serverctrls[ i ]->ldctl_value.bv_val != NULL) && + (serverctrls[ i ]->ldctl_value.bv_len > 0) ) + { + int password_expiring = atoi( serverctrls[ i ]->ldctl_value.bv_val ); + slapi_log_error(SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "Succesfully bound as %s to remote server %s:%d, " + "but password is expiring in %d seconds.\n", + binddn, hostname, port, password_expiring); + } + } + } + ldap_controls_free(serverctrls); + } + + if (referrals) + charray_free(referrals); + } + } + + conn = (cb_outgoing_conn *) slapi_ch_malloc(sizeof(cb_outgoing_conn)); + conn->ld=ld; + conn->status=CB_CONNSTATUS_OK; + conn->refcount=0; /* incremented below */ + conn->opentime=current_time(); + conn->ThreadId=PR_MyThreadId(); /* store the thread id */ + conn->next=NULL; + if (secure) { + if (pool->connarray[PR_ThreadSelf()] == NULL) { + pool->connarray[PR_ThreadSelf()] = conn; + } + else { + conn->next = pool->connarray[PR_ThreadSelf()]; + pool->connarray[PR_ThreadSelf()] = conn ; + } + } + else { + if ( NULL == connprev ) { + pool->conn.conn_list = conn; + } else { + connprev->next=conn; + } + } + + ++pool->conn.conn_list_count; + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "<= cb_get_connection added new conn 0x%x, " + "conn count now %d\n", conn->ld, pool->conn.conn_list_count ); + } + goto unlock_and_return; /* got a new one */ + } + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "... cb_get_connection waiting for conn to free up\n" ); + } + + if (!secure) slapi_wait_condvar( pool->conn.conn_list_cv, NULL ); + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "... cb_get_connection awake again\n" ); + } + } + +unlock_and_return: + if ( conn != NULL ) { + ++conn->refcount; + *lld=conn->ld; + *cc=conn; + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "<== cb_get_connection ld=0x%x (concurrency now %d)\n",*lld, conn->refcount ); + } + + } else { + if ( NULL != ld ) { + slapi_ldap_unbind( ld ); + } + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "<== cb_get_connection error %d\n", rc ); + } + } + + slapi_unlock_mutex(pool->conn.conn_list_mutex); + return( rc ); +} + +/* + * We are done with the connection handle because the + * LDAP operation has completed. + */ + +void cb_release_op_connection(cb_conn_pool* pool, LDAP *lld, int dispose) { + + cb_outgoing_conn *conn; + cb_outgoing_conn *connprev = NULL; + int secure = pool->secure; + int myself = 0; + + slapi_lock_mutex(pool->conn.conn_list_mutex); + /* + * find the connection structure this ld is part of + */ + + if (secure) { + myself = PR_ThreadSelf(); + for (conn = pool->connarray[myself]; conn != NULL; conn = conn->next ) { + if ( lld == conn->ld ) + break; + connprev = conn; + } + } + else { + for ( conn = pool->conn.conn_list; conn != NULL; conn = conn->next ){ + if ( lld == conn->ld ) + break; + connprev = conn; + } + } + + if ( conn == NULL ) { /* ld not found -- unexpected */ + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "==> cb_release_op_connection ld=0x%x not found\n", lld ); + } else { + + --conn->refcount; + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "release conn 0x%x status %d refcount after release %d\n", conn, + conn->status, conn->refcount ); + } + + if ( dispose ) { + conn->status = CB_CONNSTATUS_DOWN; + } + + if ( conn->status != CB_CONNSTATUS_OK && conn->refcount == 0 ) { + + /* + * remove from server's connection list + */ + + if (!secure) { + if ( connprev == NULL ) { + pool->conn.conn_list = conn->next; + } else { + connprev->next = conn->next; + } + } + else { + if ( connprev == NULL ) { + pool->connarray[myself] = conn->next; + } else { + connprev->next = conn->next; + } + } + + --pool->conn.conn_list_count; + + /* + * close connection and free memory + */ + cb_close_and_dispose_connection( conn ); + } + } + + /* + * wake up a thread that is waiting for a connection + */ + + if (!secure) slapi_notify_condvar( pool->conn.conn_list_cv, 0 ); + + slapi_unlock_mutex( pool->conn.conn_list_mutex ); +} + + +static void +cb_close_and_dispose_connection( cb_outgoing_conn *conn ) +{ + slapi_ldap_unbind( conn->ld ); + conn->ld = NULL; + slapi_ch_free( (void **)&conn ); +} + +static void cb_check_for_stale_connections(cb_conn_pool * pool) { + + cb_outgoing_conn * connprev, *conn, *conn_next; + time_t curtime; + int connlifetime; + int myself; + + PR_RWLock_Rlock(pool->rwl_config_lock); + connlifetime=pool->conn.connlifetime; + PR_RWLock_Unlock(pool->rwl_config_lock); + + connprev = NULL; + conn_next = NULL; + + slapi_lock_mutex(pool->conn.conn_list_mutex); + + if (connlifetime > 0) + curtime=current_time(); + + if (pool->secure) { + myself = PR_ThreadSelf(); + for (conn = pool->connarray[myself]; conn != NULL; conn = conn_next){ + if ((conn->status == CB_CONNSTATUS_STALE) || + (( connlifetime > 0) && (curtime - conn->opentime > connlifetime))) { + if ( conn->refcount == 0 ) { + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_check_for_stale_connections: conn 0x%x idle and stale\n",conn); + } + --pool->conn.conn_list_count; + if (connprev == NULL) { + pool->connarray[myself] = conn->next ; + } + else { + connprev->next = conn->next ; + } + conn_next = conn->next ; + cb_close_and_dispose_connection( conn ); + continue; + } + /* Connection is stale but in use */ + /* Mark to be disposed later but let it in the backend list */ + /* so that it is counted as a valid connection */ + else { + conn->status = CB_CONNSTATUS_STALE; + } + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_check_for_stale_connections: conn 0x%x stale\n",conn); + } + } + connprev = conn ; + conn_next = conn->next; + } + slapi_unlock_mutex(pool->conn.conn_list_mutex); + return; + } + + for ( conn = pool->conn.conn_list; conn != NULL; conn=conn_next ) { + if ((conn->status == CB_CONNSTATUS_STALE) || + (( connlifetime > 0) && (curtime - conn->opentime > connlifetime))) { + if ( conn->refcount == 0 ) { + + /* Connection idle & stale. Remove and free. */ + + if ( NULL == connprev ) + pool->conn.conn_list = conn->next; + else + connprev->next=conn->next; + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_check_for_stale_connections: conn 0x%x idle and stale\n",conn); + } + --pool->conn.conn_list_count; + conn_next=conn->next; + cb_close_and_dispose_connection( conn ); + continue; + } + + /* Connection is stale but in use */ + /* Mark to be disposed later but let it in the backend list */ + /* so that it is counted as a valid connection */ + else { + conn->status = CB_CONNSTATUS_STALE; + } + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_check_for_stale_connections: conn 0x%x stale\n",conn); + } + } + connprev = conn; + conn_next=conn->next; + } + + /* Generate an event to wake up threads waiting */ + /* for a conn to be released. Useful to detect */ + /* exceeded time limit. May be expensive */ + + slapi_notify_condvar( pool->conn.conn_list_cv, 0 ); + + slapi_unlock_mutex(pool->conn.conn_list_mutex); +} + +/* + * close all open connections in preparation for server shutdown, etc. + * WARNING: Don't wait for current operations to complete + */ + +void +cb_close_all_connections( Slapi_Backend * be ) +{ + + cb_outgoing_conn *conn, *next_conn; + cb_backend_instance * cb= cb_get_instance(be); + int i; + + slapi_lock_mutex(cb->pool->conn.conn_list_mutex); + if (cb->pool->secure) { + for (i=0; i< MAX_CONN_ARRAY; i++) { + for (conn = cb->pool->connarray[i]; conn != NULL; conn = next_conn ){ + next_conn = conn->next; + cb_close_and_dispose_connection(conn); + } + } + } else { + for ( conn = cb->pool->conn.conn_list; conn != NULL; conn = next_conn ) { + next_conn=conn->next; + cb_close_and_dispose_connection(conn); + } + } + slapi_unlock_mutex(cb->pool->conn.conn_list_mutex); + + slapi_lock_mutex(cb->bind_pool->conn.conn_list_mutex); + if (cb->bind_pool->secure) { + for (i=0; i< MAX_CONN_ARRAY; i++) { + for (conn = cb->bind_pool->connarray[i]; conn != NULL; conn = next_conn ){ + next_conn=conn->next; + cb_close_and_dispose_connection(conn); + } + } + } else { + for ( conn = cb->bind_pool->conn.conn_list; conn != NULL; conn = next_conn ) { + next_conn=conn->next; + cb_close_and_dispose_connection(conn); + } + } + slapi_unlock_mutex(cb->bind_pool->conn.conn_list_mutex); +} + +/* Mark used connections as stale and close unsued connections */ +/* Called when the target farm url has changed */ + +void cb_stale_all_connections( cb_backend_instance * cb) +{ + cb_outgoing_conn *conn, *next_conn, *prev_conn; + int notify=0; + int i, j; + cb_conn_pool *pools[3]; + + pools[0]=cb->pool; + pools[1]=cb->bind_pool; + pools[2]=NULL; + + for (i=0; pools[i]; i++) { + slapi_lock_mutex(pools[i]->conn.conn_list_mutex); + for (j=0; j< MAX_CONN_ARRAY; j++) { + prev_conn=NULL; + for (conn = pools[i]->connarray[j]; conn != NULL; conn=next_conn) { + next_conn=conn->next; + if (conn->refcount > 0) { + /* + ** Connection is stale but in use + ** Mark to be disposed later but let it in the backend list + ** so that it is counted as a valid connection + */ + conn->status = CB_CONNSTATUS_STALE; + prev_conn=conn; + } else { + if (prev_conn == NULL) { + pools[i]->connarray[j]=next_conn; + } else { + prev_conn->next=next_conn; + } + cb_close_and_dispose_connection(conn); + pools[i]->conn.conn_list_count--; + } + } + } + prev_conn = NULL ; + for ( conn = pools[i]->conn.conn_list; conn != NULL; conn = next_conn ) { + next_conn=conn->next; + if (conn->refcount > 0) { + /* + ** Connection is stale but in use + ** Mark to be disposed later but let it in the backend list + ** so that it is counted as a valid connection + */ + conn->status = CB_CONNSTATUS_STALE; + prev_conn=conn; + } + else { + if (conn==pools[i]->conn.conn_list) { + pools[i]->conn.conn_list=next_conn; + } else { + prev_conn->next=next_conn; + } + cb_close_and_dispose_connection(conn); + pools[i]->conn.conn_list_count--; + notify=1; + } + } + if (notify && (! pools[i]->secure)) { + slapi_notify_condvar( pools[i]->conn.conn_list_cv, 0 ); + } + slapi_unlock_mutex(pools[i]->conn.conn_list_mutex); + } +} + + + +/**************************************************************************/ +/* Need to use our own connect function until we've switched to C-SDK 4.1 */ +/* to have a timeout in the connect system call. */ +/**************************************************************************/ + +static int global_connect_to; + +#if 0 + +/* Taken from C-SDK 4.1 */ +#include <fcntl.h> +#include <errno.h> +#define LDAP_X_IO_TIMEOUT_NO_TIMEOUT (-1) + +static int +nsldapi_os_connect_with_to(LBER_SOCKET sockfd, struct sockaddr *saptr, + int salen) +{ +#ifndef _WIN32 + int flags; +#endif /* _WIN32 */ + int n, error; + int len; + fd_set rset, wset; + struct timeval tval; +#ifdef _WIN32 + int nonblock = 1; + int block = 0; + fd_set eset; +#endif /* _WIN32 */ + + int msec=global_connect_to; /* global */ + +#ifdef _WIN32 + ioctlsocket(sockfd, FIONBIO, &nonblock); +#else + flags = fcntl(sockfd, F_GETFL, 0); + fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); +#endif /* _WIN32 */ + + error = 0; + if ((n = connect(sockfd, saptr, salen)) < 0) +#ifdef _WIN32 + if ((n != SOCKET_ERROR) && (WSAGetLastError() != WSAEWOULDBLOCK)) { +#else + if (errno != EINPROGRESS) { +#endif /* _WIN32 */ + return (-1); + } + + /* success */ + if (n == 0) + goto done; + + FD_ZERO(&rset); + FD_SET(sockfd, &rset); + wset = rset; + +#ifdef _WIN32 + eset = rset; +#endif /* _WIN32 */ + + if (msec < 0 && msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) { + msec = LDAP_X_IO_TIMEOUT_NO_TIMEOUT; + } else { + if (msec != 0) + tval.tv_sec = msec / 1000; + else + tval.tv_sec = 0; + tval.tv_usec = 0; + } + + /* if timeval structure == NULL, select will block indefinitely */ + /* != NULL, and value == 0, select will */ + /* not block */ + /* Windows is a bit quirky on how it behaves w.r.t nonblocking */ + /* connects. If the connect fails, the exception fd, eset, is */ + /* set to show the failure. The first argument in select is */ + /* ignored */ + +#ifdef _WIN32 + if ((n = select(sockfd +1, &rset, &wset, &eset, + (msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) ? &tval : NULL)) == 0) { + errno = WSAETIMEDOUT; + return (-1); + } + /* if wset is set, the connect worked */ + if (FD_ISSET(sockfd, &wset) || FD_ISSET(sockfd, &rset)) { + len = sizeof(error); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) + < 0) + return (-1); + goto done; + } + + /* if eset is set, the connect failed */ + if (FD_ISSET(sockfd, &eset)) { + return (-1); + } + + /* failure on select call */ + if (n == SOCKET_ERROR) { + return (-1); + } +#else + if ((n = select(sockfd +1, &rset, &wset, NULL, + (msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) ? &tval : NULL)) == 0) { + errno = ETIMEDOUT; + return (-1); + } + if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { + len = sizeof(error); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) + < 0) + return (-1); + } +#endif /* _WIN32 */ +done: +#ifdef _WIN32 + ioctlsocket(sockfd, FIONBIO, &block); +#else + fcntl(sockfd, F_SETFL, flags); +#endif /* _WIN32 */ + + if (error) { + errno = error; + return (-1); + } + + return (0); +} + +#endif + +/* Try to figure out if a farm server is still alive */ + +int cb_ping_farm(cb_backend_instance *cb, cb_outgoing_conn * cnx,time_t end_time) { + + char *attrs[] ={"1.1",NULL}; + int rc; + struct timeval timeout; + LDAP *ld; + LDAPMessage *result; +#if 0 + struct ldap_io_fns iof; +#endif + time_t now; + if (cb->max_idle_time <=0) /* Heart-beat disabled */ + return LDAP_SUCCESS; + + if (cnx && (cnx->status != CB_CONNSTATUS_OK )) /* Known problem */ + return LDAP_SERVER_DOWN; + + now = current_time(); + if (end_time && ((now <= end_time) || (end_time <0))) return LDAP_SUCCESS; + + ld=slapi_ldap_init(cb->pool->hostname,cb->pool->port,cb->pool->secure,0); + if (NULL == ld) { + cb_update_failed_conn_cpt( cb ); + return LDAP_SERVER_DOWN; + } + +#if 0 + memset(&iof,0,sizeof(struct ldap_io_fns)); + if (LDAP_SUCCESS !=ldap_get_option(ld,LDAP_OPT_IO_FN_PTRS,&iof)) { + slapi_ldap_unbind( ld ); + cb_update_failed_conn_cpt( cb ); + return LDAP_SERVER_DOWN; + } + + iof.liof_connect = nsldapi_os_connect_with_to; + if (LDAP_SUCCESS !=ldap_set_option(ld,LDAP_OPT_IO_FN_PTRS,&iof)) { + slapi_ldap_unbind( ld ); + cb_update_failed_conn_cpt( cb ); + return LDAP_SERVER_DOWN; + } + +#endif + + timeout.tv_sec=cb->max_test_time; + timeout.tv_usec=0; + + global_connect_to=cb->max_test_time * 1000; /* Reuse the same for the connect */ + rc=ldap_search_ext_s(ld ,NULL,LDAP_SCOPE_BASE,"objectclass=*",attrs,1,NULL, + NULL, &timeout, 1,&result); + if ( LDAP_SUCCESS != rc ) { + slapi_ldap_unbind( ld ); + cb_update_failed_conn_cpt( cb ); + return LDAP_SERVER_DOWN; + } + + ldap_msgfree(result); + slapi_ldap_unbind( ld ); + cb_reset_conn_cpt( cb ); + return LDAP_SUCCESS; +} + + + +void cb_update_failed_conn_cpt ( cb_backend_instance *cb ) { + /* if the chaining BE is already unavailable, we do nothing*/ + time_t now; + if (cb->monitor_availability.farmserver_state == FARMSERVER_AVAILABLE) { + slapi_lock_mutex(cb->monitor_availability.cpt_lock); + cb->monitor_availability.cpt ++; + slapi_unlock_mutex(cb->monitor_availability.cpt_lock); + if (cb->monitor_availability.cpt >= CB_NUM_CONN_BEFORE_UNAVAILABILITY ) { + /* we reach the limit of authorized failed connections => we setup the chaining BE state to unavailable */ + now = current_time(); + slapi_lock_mutex(cb->monitor_availability.lock_timeLimit); + cb->monitor_availability.unavailableTimeLimit = now + CB_UNAVAILABLE_PERIOD ; + slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit); + cb->monitor_availability.farmserver_state = FARMSERVER_UNAVAILABLE ; + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_update_failed_conn_cpt: Farm server unavailable"); + } + + } +} + +void cb_reset_conn_cpt( cb_backend_instance *cb ) { + if (cb->monitor_availability.cpt > 0) { + slapi_lock_mutex(cb->monitor_availability.cpt_lock); + cb->monitor_availability.cpt = 0 ; + if (cb->monitor_availability.farmserver_state == FARMSERVER_UNAVAILABLE) { + cb->monitor_availability.farmserver_state = FARMSERVER_AVAILABLE ; + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_reset_conn_cpt: Farm server is back"); + } + slapi_unlock_mutex(cb->monitor_availability.cpt_lock); + } +} + +int cb_check_availability( cb_backend_instance *cb, Slapi_PBlock *pb ) { + /* check wether the farmserver is available or not */ + int rc ; + time_t now ; + if ( cb->monitor_availability.farmserver_state == FARMSERVER_UNAVAILABLE ){ + slapi_lock_mutex(cb->monitor_availability.lock_timeLimit); + now = current_time(); + if (now >= cb->monitor_availability.unavailableTimeLimit) { + cb->monitor_availability.unavailableTimeLimit = now + CB_INFINITE_TIME ; /* to be sure only one thread can do the test */ + slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit); + } + else { + slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit); + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL) ; + return FARMSERVER_UNAVAILABLE ; + } + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_check_availability: ping the farm server and check if it's still unavailable"); + if ((rc = cb_ping_farm(cb, NULL, 0)) != LDAP_SUCCESS) { /* farm still unavailable... Just change the timelimit */ + slapi_lock_mutex(cb->monitor_availability.lock_timeLimit); + now = current_time(); + cb->monitor_availability.unavailableTimeLimit = now + CB_UNAVAILABLE_PERIOD ; + slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit); + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL) ; + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_check_availability: Farm server still unavailable"); + return FARMSERVER_UNAVAILABLE ; + } + else { + /* farm is back !*/ + slapi_lock_mutex(cb->monitor_availability.lock_timeLimit); + now = current_time(); + cb->monitor_availability.unavailableTimeLimit = now ; /* the unavailable period is finished */ + slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit); + /* The farmer server state backs to FARMSERVER_AVAILABLE, but this already done in cb_ping_farm, and also the reset of cpt*/ + return FARMSERVER_AVAILABLE ; + } + } + return FARMSERVER_AVAILABLE ; +} diff --git a/ldap/servers/plugins/chainingdb/cb_controls.c b/ldap/servers/plugins/chainingdb/cb_controls.c new file mode 100644 index 00000000..ab612099 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_controls.c @@ -0,0 +1,289 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* +** Controls that can't be forwarded due to the current implementation +*/ + +static char * unsupported_ctrls[] = {LDAP_CONTROL_PERSISTENTSEARCH,NULL}; + +int cb_is_control_forwardable(cb_backend * cb, char *controloid) { + return (!(charray_inlist(unsupported_ctrls,controloid))); +} + +void +cb_register_supported_control( cb_backend * cb, char *controloid, unsigned long controlops ) +{ + /* For now, ignore controlops */ + if ( controloid != NULL ) { + PR_RWLock_Wlock(cb->config.rwl_config_lock); + charray_add( &cb->config.forward_ctrls,slapi_ch_strdup( controloid )); + PR_RWLock_Unlock(cb->config.rwl_config_lock); + } +} + + +void +cb_unregister_all_supported_control( cb_backend * cb ) { + + PR_RWLock_Wlock(cb->config.rwl_config_lock); + charray_free(cb->config.forward_ctrls); + cb->config.forward_ctrls=NULL; + PR_RWLock_Unlock(cb->config.rwl_config_lock); +} + +void +cb_unregister_supported_control( cb_backend * cb, char *controloid, unsigned long controlops ) +{ + + /* For now, ignore controlops */ + if ( controloid != NULL ) { + int i; + PR_RWLock_Wlock(cb->config.rwl_config_lock); + for ( i = 0; cb->config.forward_ctrls != NULL && cb->config.forward_ctrls[i] != NULL; ++i ) { + if ( strcmp( cb->config.forward_ctrls[i], controloid ) == 0 ) { + break; + } + } + if ( cb->config.forward_ctrls == NULL || cb->config.forward_ctrls[i] == NULL) { + PR_RWLock_Unlock(cb->config.rwl_config_lock); + return; + } + if ( controlops == 0 ) { + charray_remove(cb->config.forward_ctrls,controloid); + } + PR_RWLock_Unlock(cb->config.rwl_config_lock); + } +} + +int cb_create_loop_control ( + const int hops, + LDAPControl **ctrlp) + +{ + BerElement *ber; + int rc; + + if ((ber = ber_alloc()) == NULL) + return -1; + + if ( ber_printf( ber, "i", hops ) < 0) { + ber_free(ber,1); + return -1; + } + + rc = slapi_build_control( CB_LDAP_CONTROL_CHAIN_SERVER, ber, 0, ctrlp); + + ber_free(ber,1); + + return rc; +} + +/* +** Return the controls to be passed to the remote +** farm server and the LDAP error to return. +** +** Add the Proxied Authorization control when impersonation +** is enabled. Other controls present in the request are added +** to the control list +** +** #622885 .abandon should not inherit the to-be-abandoned-operation's controls +** .controls attached to abandon should not be critical +*/ + +int cb_update_controls( Slapi_PBlock * pb, + LDAP * ld, + LDAPControl *** controls, + int ctrl_flags + ) +{ + + int cCount=0; + int dCount=0; + int i; + char * proxyDN=NULL; + LDAPControl ** reqControls = NULL; + LDAPControl ** ctrls = NULL; + cb_backend_instance * cb; + cb_backend * cbb; + Slapi_Backend * be; + int rc=LDAP_SUCCESS; + int hops=0; + int useloop=0; + int addauth = (ctrl_flags & CB_UPDATE_CONTROLS_ADDAUTH); + int isabandon = (ctrl_flags & CB_UPDATE_CONTROLS_ISABANDON); + int op_type = 0; + + *controls = NULL; + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &op_type); + if (!isabandon || op_type == SLAPI_OPERATION_ABANDON) { + /* if not abandon or abandon sent by client */ + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &reqControls ); + } + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cbb ); + cb = cb_get_instance(be); + + /*****************************************/ + /* First, check for unsupported controls */ + /* Return an error if critical control */ + /* else remove it from the control list */ + /*****************************************/ + + for ( cCount=0; reqControls && reqControls[cCount]; cCount++ ); + ctrls = (LDAPControl **)slapi_ch_calloc(1,sizeof(LDAPControl *) * (cCount +3)); + + PR_RWLock_Rlock(cbb->config.rwl_config_lock); + + for ( cCount=0; reqControls && reqControls[cCount]; cCount++ ) { + + /* XXXSD CASCADING */ + /* For now, allow PROXY_AUTH control forwarding only when */ + /* local acl evaluation to prevent unauthorized access */ + + if (!strcmp(reqControls[cCount]->ldctl_oid,LDAP_CONTROL_PROXYAUTH)) { + + /* we have to force remote acl checking if the associated backend to this + chaining backend is disabled - disabled == no acl check possible */ + if (!cb->local_acl && !cb->associated_be_is_disabled) { + slapi_log_error( SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM, + "local aci check required to handle proxied auth control. Deny access.\n"); + rc= LDAP_INSUFFICIENT_ACCESS; + break; + } + + /* XXXSD Not safe to use proxied authorization with Directory Manager */ + /* checked earlier when impersonation is on */ + + if (!cb->impersonate) { + char * requestor,*rootdn; + char * requestorCopy=NULL; + + rootdn=cb_get_rootdn(); + slapi_pblock_get( pb, SLAPI_REQUESTOR_DN, &requestor ); + requestorCopy=slapi_ch_strdup(requestor); + slapi_dn_normalize_case(requestorCopy); + + if (!strcmp( requestorCopy, rootdn )) { /* UTF8- aware */ + slapi_log_error( SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM, + "Use of user <%s> incompatible with proxied auth. control\n",rootdn); + rc=LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + slapi_ch_free((void **)&requestorCopy); + break; + } + slapi_ch_free((void **)&rootdn); + slapi_ch_free((void **)&requestorCopy); + } + + addauth=0; + ctrls[dCount]=slapi_dup_control(reqControls[cCount]); + dCount++; + + } else + if (!strcmp(reqControls[cCount]->ldctl_oid,CB_LDAP_CONTROL_CHAIN_SERVER)) { + + /* Max hop count reached ? */ + /* Checked realier by a call to cb_forward_operation() */ + + BerElement *ber = NULL; + + ber = ber_init(&(reqControls[cCount]->ldctl_value)); + ber_scanf(ber,"i",&hops); + ber_free(ber,1); + useloop=1; + + /* Add to the control list later */ + + } else { + + int i; + for ( i = 0; cbb->config.forward_ctrls != NULL + && cbb->config.forward_ctrls[i] != NULL; ++i ) { + if ( strcmp( cbb->config.forward_ctrls[i], reqControls[cCount]->ldctl_oid ) == 0 ) { + break; + } + } + /* For now, ignore optype */ + if ( cbb->config.forward_ctrls == NULL || cbb->config.forward_ctrls[i] == NULL) { + if (reqControls[cCount]->ldctl_iscritical) { + rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + break; + } + /* Skip it */ + } else { + ctrls[dCount]=slapi_dup_control(reqControls[cCount]); + dCount++; + } + } + } + + PR_RWLock_Unlock(cbb->config.rwl_config_lock); + + if (LDAP_SUCCESS != rc) { + ldap_controls_free(ctrls); + return rc; + } + + /***************************************/ + /* add impersonation control if needed */ + /***************************************/ + + if ( !(cb->impersonate) ) { + + /* don't add proxy control */ + addauth=0; + } + + if (addauth) { + slapi_pblock_get( pb, SLAPI_REQUESTOR_DN, &proxyDN ); + + if ( ldap_create_proxyauth_control(ld, proxyDN, isabandon?0:1, &ctrls[dCount] )) { + ldap_controls_free(ctrls); + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "LDAP_CONTROL_PROXYAUTH control encoding failed.\n"); + return LDAP_OPERATIONS_ERROR; + } + dCount++; + } + + /***********************************************************/ + /* add loop control if needed */ + /* Don't add it if not in the list of forwardable controls */ + /***********************************************************/ + + if (!useloop) { + for ( i = 0; cbb->config.forward_ctrls != NULL + && cbb->config.forward_ctrls[i] != NULL; ++i ) { + if ( strcmp( cbb->config.forward_ctrls[i], + CB_LDAP_CONTROL_CHAIN_SERVER) == 0 ) { + break; + } + } + } + if ( useloop || (cbb->config.forward_ctrls !=NULL && cbb->config.forward_ctrls[i] !=NULL)){ + + if (hops > 0) { + hops--; + } else { + hops = cb->hoplimit; + } + + /* loop control's critical flag is 0; + * no special treatment is needed for abandon */ + cb_create_loop_control(hops,&ctrls[dCount]); + dCount++; + } + + if (dCount==0) { + ldap_controls_free(ctrls); + } else { + *controls = ctrls; + } + + return LDAP_SUCCESS; + +} diff --git a/ldap/servers/plugins/chainingdb/cb_debug.c b/ldap/servers/plugins/chainingdb/cb_debug.c new file mode 100644 index 00000000..8a33e440 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_debug.c @@ -0,0 +1,23 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * cb_debug.c - debugging-related code for Chaining backend + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "cb.h" + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + diff --git a/ldap/servers/plugins/chainingdb/cb_delete.c b/ldap/servers/plugins/chainingdb/cb_delete.c new file mode 100644 index 00000000..f7eb77f6 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_delete.c @@ -0,0 +1,203 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Perform an delete operation + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chaining_back_delete ( Slapi_PBlock *pb ) +{ + + Slapi_Backend * be; + cb_backend_instance *cb; + LDAPControl **ctrls, **serverctrls; + int rc,parse_rc,msgid,i; + LDAP *ld=NULL; + char **referrals=NULL; + LDAPMessage * res; + char *dn,* matched_msg, *error_msg; + char *cnxerrbuf=NULL; + time_t endtime; + cb_outgoing_conn *cnx; + + if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) { + cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL ); + return -1; + } + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_DELETE); + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + slapi_pblock_get( pb, SLAPI_DELETE_TARGET, &dn ); + + /* + * Check local acls + */ + + if (cb->local_acl && !cb->associated_be_is_disabled) { + char * errbuf=NULL; + Slapi_Entry *te = slapi_entry_alloc(); + slapi_entry_set_dn(te,slapi_ch_strdup(dn)); + rc = cb_access_allowed (pb, te, NULL, NULL, SLAPI_ACL_DELETE,&errbuf); + slapi_entry_free(te); + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL ); + slapi_ch_free((void **)&errbuf); + return -1; + } + } + + /* + * Grab a connection handle + */ + + if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL); + slapi_ch_free((void **)&cnxerrbuf); + /* ping the farm. If the farm is unreachable, we increment the counter */ + cb_ping_farm(cb,NULL,0); + return -1; + } + + /* + * Control management + */ + + if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return -1; + } + + if ( slapi_op_abandoned( pb )) { + cb_release_op_connection(cb->pool,ld,0); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return -1; + } + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + /* + * Send LDAP operation to the remote host + */ + + rc = ldap_delete_ext( ld, dn, ctrls, NULL, &msgid ); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + if ( rc != LDAP_SUCCESS ) { + + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return -1; + } + + while ( 1 ) { + + if (cb_check_forward_abandon(cb,pb,ld,msgid)) { + return -1; + } + + rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res ); + switch ( rc ) { + case -1: + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return -1; + case 0: + if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) { + + /* does not respond. give up and return a*/ + /* error to the client. */ + + /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL);*/ + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return -1; + } +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + default: + matched_msg=error_msg=NULL; + parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, + &error_msg, &referrals, &serverctrls, 1 ); + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(parse_rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free referrals */ + if (referrals) + charray_free(referrals); + return -1; + } + + if ( rc != LDAP_SUCCESS ) { + struct berval ** refs = referrals2berval(referrals); + + cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (refs) + ber_bvecfree(refs); + if (referrals) + charray_free(referrals); + if (serverctrls) + ldap_controls_free(serverctrls); + return -1; + } + + cb_release_op_connection(cb->pool,ld,0); + + /* Add control response sent by the farm server */ + + for (i=0; serverctrls && serverctrls[i];i++) + slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free matched_msg, error_msg, and referrals if necessary */ + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (referrals) + charray_free(referrals); + cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL ); + return 0; + } + } + + /* Never reached */ + /* return 0; */ +} diff --git a/ldap/servers/plugins/chainingdb/cb_init.c b/ldap/servers/plugins/chainingdb/cb_init.c new file mode 100644 index 00000000..70af3d42 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_init.c @@ -0,0 +1,120 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +Slapi_PluginDesc chainingdbdesc = { CB_PLUGIN_NAME, + PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, + CB_PLUGIN_DESCRIPTION }; + + +static cb_backend * cb_backend_type=NULL; + +cb_backend * cb_get_backend_type() { + return cb_backend_type; +} + +static void cb_set_backend_type(cb_backend * cb) { + cb_backend_type=cb; +} + +/* Initialization function */ +#ifdef _WIN32 +__declspec(dllexport) +#endif + +int +chaining_back_init( Slapi_PBlock *pb ) +{ + + int rc=0; + cb_backend *cb; + struct slapdplugin *p; + + cb = (cb_backend *) slapi_ch_calloc( 1, sizeof(cb_backend)); + + /* Record the identity of the chaining plugin. used during internal ops.*/ + slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &(cb->identity)); + + /* keep a pointer back to the plugin */ + slapi_pblock_get(pb, SLAPI_PLUGIN, &p); + cb->plugin = p; + + /* Initialize misc. fields */ + cb->config.rwl_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "chaining_db"); + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_PRIVATE, (void *) cb ); + cb->pluginDN=slapi_ch_calloc( 1,strlen(PLUGIN_BASE_DN)+strlen(CB_PLUGIN_NAME)+5); + sprintf(cb->pluginDN,"cn=%s,%s",CB_PLUGIN_NAME,PLUGIN_BASE_DN); + + cb->configDN=slapi_ch_calloc( 1,strlen(cb->pluginDN)+11); + sprintf(cb->configDN,"cn=config,%s",cb->pluginDN); + + /* Set backend callback functions */ + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&chainingdbdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_SEARCH_FN, + (void *) chainingdb_build_candidate_list ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN, + (void *) chainingdb_next_search_entry ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, + (void *) chainingdb_start ) ; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_BIND_FN, + (void *) chainingdb_bind ) ; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_ADD_FN, + (void *) chaining_back_add ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_DELETE_FN, + (void *) chaining_back_delete ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_COMPARE_FN, + (void *) chaining_back_compare ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_MODIFY_FN, + (void *) chaining_back_modify ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_MODRDN_FN, + (void *) chaining_back_modrdn ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_ABANDON_FN, + (void *) chaining_back_abandon ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_SIZE_FN, + (void *) cb_db_size ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) cb_back_close ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_CLEANUP_FN, + (void *) cb_back_cleanup ); + +/**** + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_ENTRY_RELEASE_FN, + (void *) chaining_back_entry_release ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_INIT_INSTANCE_FN, + (void *) chaining_back_init); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_TEST_FN, + (void *) cb_back_test ); +****/ + + /* + ** The following callbacks are not implemented + ** by the chaining backend + ** - SLAPI_PLUGIN_DB_FLUSH_FN + ** - SLAPI_PLUGIN_DB_SEQ_FN + ** - SLAPI_PLUGIN_DB_RMDB_FN + ** - SLAPI_PLUGIN_DB_DB2INDEX_FN + ** - SLAPI_PLUGIN_DB_LDIF2DB_FN + ** - SLAPI_PLUGIN_DB_DB2LDIF_FN + ** - SLAPI_PLUGIN_DB_ARCHIVE2DB_FN + ** - SLAPI_PLUGIN_DB_DB2ARCHIVE_FN + ** - SLAPI_PLUGIN_DB_BEGIN_FN + ** - SLAPI_PLUGIN_DB_COMMIT_FN + ** - SLAPI_PLUGIN_DB_ABORT_FN + ** - SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_EXT_FN + */ + + if ( rc != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, "chaining_back_init failed\n"); + return( -1 ); + } + + cb_set_backend_type(cb); + + return (0); +} + diff --git a/ldap/servers/plugins/chainingdb/cb_instance.c b/ldap/servers/plugins/chainingdb/cb_instance.c new file mode 100644 index 00000000..60af726f --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_instance.c @@ -0,0 +1,1871 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* +** 1 set/get function for each parameter of a backend instance +** NOTE: Some parameters won't be taken into account until the server has restarted +** In such cases, the internal conf is not updated but the new value is stored in the +** dse.ldif file. +**/ + +/* Get functions */ + +static void *cb_instance_hosturl_get(void *arg); +static void *cb_instance_binduser_get(void *arg); +static void *cb_instance_userpassword_get(void *arg); +static void *cb_instance_maxbconn_get(void *arg); +static void *cb_instance_maxconn_get(void *arg); +static void *cb_instance_abandonto_get(void *arg); +static void *cb_instance_maxbconc_get(void *arg); +static void *cb_instance_maxconc_get(void *arg); +static void *cb_instance_imperson_get(void *arg); +static void *cb_instance_connlife_get(void *arg); +static void *cb_instance_bindto_get(void *arg); +static void *cb_instance_opto_get(void *arg); +static void *cb_instance_ref_get(void *arg); +static void *cb_instance_acl_get(void *arg); +static void *cb_instance_bindretry_get(void *arg); +static void *cb_instance_sizelimit_get(void *arg); +static void *cb_instance_timelimit_get(void *arg); +static void *cb_instance_hoplimit_get(void *arg); +static void *cb_instance_max_idle_get(void *arg); +static void *cb_instance_max_test_get(void *arg); + + +/* Set functions */ + +static int cb_instance_hosturl_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_binduser_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_userpassword_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_maxbconn_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_maxconn_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_abandonto_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_maxbconc_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_maxconc_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_imperson_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_connlife_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_bindto_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_opto_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_ref_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_acl_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_bindretry_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_sizelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_timelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_hoplimit_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_max_idle_set(void *arg, void *value, char *errorbuf, int phase, int apply); +static int cb_instance_max_test_set(void *arg, void *value, char *errorbuf, int phase, int apply); + +/* Default hardwired values */ + +cb_instance_config_info cb_the_instance_config[] = { +{CB_CONFIG_HOSTURL,CB_CONFIG_TYPE_STRING,"",&cb_instance_hosturl_get,&cb_instance_hosturl_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_BINDUSER, CB_CONFIG_TYPE_STRING, "", &cb_instance_binduser_get, &cb_instance_binduser_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_USERPASSWORD,CB_CONFIG_TYPE_STRING,"",&cb_instance_userpassword_get,&cb_instance_userpassword_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_MAXBINDCONNECTIONS,CB_CONFIG_TYPE_INT,CB_DEF_BIND_MAXCONNECTIONS,&cb_instance_maxbconn_get, &cb_instance_maxbconn_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_MAXCONNECTIONS,CB_CONFIG_TYPE_INT,CB_DEF_MAXCONNECTIONS,&cb_instance_maxconn_get, &cb_instance_maxconn_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_ABANDONTIMEOUT,CB_CONFIG_TYPE_INT,CB_DEF_ABANDON_TIMEOUT,&cb_instance_abandonto_get, &cb_instance_abandonto_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_MAXBINDCONCURRENCY,CB_CONFIG_TYPE_INT,CB_DEF_BIND_MAXCONCURRENCY,&cb_instance_maxbconc_get, &cb_instance_maxbconc_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_MAXCONCURRENCY,CB_CONFIG_TYPE_INT,CB_DEF_MAXCONCURRENCY,&cb_instance_maxconc_get, &cb_instance_maxconc_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_IMPERSONATION,CB_CONFIG_TYPE_ONOFF,CB_DEF_IMPERSONATION,&cb_instance_imperson_get, &cb_instance_imperson_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_CONNLIFETIME,CB_CONFIG_TYPE_INT,CB_DEF_CONNLIFETIME,&cb_instance_connlife_get, &cb_instance_connlife_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_BINDTIMEOUT,CB_CONFIG_TYPE_INT,CB_DEF_BINDTIMEOUT,&cb_instance_bindto_get, &cb_instance_bindto_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_TIMEOUT,CB_CONFIG_TYPE_INT,"0",&cb_instance_opto_get, &cb_instance_opto_set,0}, +{CB_CONFIG_REFERRAL,CB_CONFIG_TYPE_ONOFF,CB_DEF_SEARCHREFERRAL,&cb_instance_ref_get, &cb_instance_ref_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_LOCALACL,CB_CONFIG_TYPE_ONOFF,CB_DEF_LOCALACL,&cb_instance_acl_get, &cb_instance_acl_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_BINDRETRY,CB_CONFIG_TYPE_INT,CB_DEF_BINDRETRY,&cb_instance_bindretry_get, &cb_instance_bindretry_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_SIZELIMIT,CB_CONFIG_TYPE_INT,CB_DEF_SIZELIMIT,&cb_instance_sizelimit_get, &cb_instance_sizelimit_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_TIMELIMIT,CB_CONFIG_TYPE_INT,CB_DEF_TIMELIMIT,&cb_instance_timelimit_get, &cb_instance_timelimit_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_HOPLIMIT,CB_CONFIG_TYPE_INT,CB_DEF_HOPLIMIT,&cb_instance_hoplimit_get, &cb_instance_hoplimit_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_MAX_IDLE_TIME,CB_CONFIG_TYPE_INT,CB_DEF_MAX_IDLE_TIME,&cb_instance_max_idle_get, &cb_instance_max_idle_set,CB_ALWAYS_SHOW}, +{CB_CONFIG_MAX_TEST_TIME,CB_CONFIG_TYPE_INT,CB_DEF_MAX_TEST_TIME,&cb_instance_max_test_get, &cb_instance_max_test_set,CB_ALWAYS_SHOW}, +{NULL, 0, NULL, NULL, NULL, 0} +}; + +/* Others forward declarations */ + +int cb_instance_delete_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg); +int cb_instance_search_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg); +int cb_instance_add_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg); +static int +cb_instance_config_set(void *arg, char *attr_name, cb_instance_config_info *config_array, +struct berval *bval, char *err_buf, int phase, int apply_mod); + + +int +cb_dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + *returncode=LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +static char *cb_skeleton_entries[] = +{ + + "dn:cn=monitor, cn=%s, cn=%s, cn=plugins, cn=config\n" + "objectclass:top\n" + "objectclass:extensibleObject\n" + "cn:monitor\n", + + "" +}; + +/* +** Initialize a backend instance with a default configuration +*/ + +static void cb_instance_config_set_default(cb_backend_instance *inst) +{ + cb_instance_config_info *config; + char err_buf[CB_BUFSIZE]; + + for (config = cb_the_instance_config; config->config_name != NULL; config++) { + cb_instance_config_set((void *)inst, + config->config_name, cb_the_instance_config, NULL /* use default */, err_buf, + CB_CONFIG_PHASE_INITIALIZATION, 1 /* apply */); + } + + /* Set backend instance flags */ + if (inst->inst_be) + slapi_be_set_flag(inst->inst_be,SLAPI_BE_FLAG_REMOTE_DATA); +} + +/* +** Allocate a new chaining backend instance. Internal structure +*/ + +static cb_backend_instance * cb_instance_alloc(cb_backend * cb, char * name, char * basedn) { + + int i; + + cb_backend_instance * inst = + (cb_backend_instance *)slapi_ch_calloc(1, sizeof(cb_backend_instance)); + + /* associated_be_is_disabled defaults to 0 - this may be a problem if the associated + be is disabled at instance creation time + */ + inst->inst_name = slapi_ch_strdup(name); + inst->monitor.mutex = slapi_new_mutex(); + inst->monitor_availability.cpt_lock = slapi_new_mutex(); + inst->monitor_availability.lock_timeLimit = slapi_new_mutex(); + inst->pool= (cb_conn_pool *) slapi_ch_calloc(1,sizeof(cb_conn_pool)); + inst->pool->conn.conn_list_mutex = slapi_new_mutex(); + inst->pool->conn.conn_list_cv = slapi_new_condvar(inst->pool->conn.conn_list_mutex); + inst->pool->bindit=1; + + inst->bind_pool= (cb_conn_pool *) slapi_ch_calloc(1,sizeof(cb_conn_pool)); + inst->bind_pool->conn.conn_list_mutex = slapi_new_mutex(); + inst->bind_pool->conn.conn_list_cv = slapi_new_condvar(inst->bind_pool->conn.conn_list_mutex); + + inst->backend_type=cb; + /* initialize monitor_availability */ + inst->monitor_availability.farmserver_state = FARMSERVER_AVAILABLE ; /* we expect the farm to be available */ + inst->monitor_availability.cpt = 0 ; /* set up the failed conn counter to 0 */ + + /* create RW lock to protect the config */ + inst->rwl_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, slapi_ch_strdup(name)); + + /* quick hack */ + /* put a ref to the config lock in the pool */ + /* so that the connection mgmt module can */ + /* access the config safely */ + + inst->pool->rwl_config_lock = inst->rwl_config_lock; + inst->bind_pool->rwl_config_lock = inst->rwl_config_lock; + + for (i=0; i < MAX_CONN_ARRAY; i++) { + inst->pool->connarray[i] = NULL; + inst->bind_pool->connarray[i] = NULL; + } + + /* Config is now merged with the backend entry */ + inst->configDn=slapi_ch_strdup(basedn); + + inst->monitorDn=(char *) slapi_ch_calloc(1,strlen(basedn)+15); + sprintf(inst->monitorDn,"cn=monitor,%s",basedn); + + inst->eq_ctx = NULL; + + return inst; +} + +void cb_instance_free(cb_backend_instance * inst) { + + if (inst) { + PR_RWLock_Wlock(inst->rwl_config_lock); + + if ( inst->eq_ctx != NULL ) + { + slapi_eq_cancel(inst->eq_ctx); + inst->eq_ctx = NULL; + } + + if (inst->bind_pool) + cb_close_conn_pool(inst->bind_pool); + if (inst->pool) + cb_close_conn_pool(inst->pool); + + slapi_destroy_condvar(inst->bind_pool->conn.conn_list_cv); + slapi_destroy_condvar(inst->pool->conn.conn_list_cv); + slapi_destroy_mutex(inst->monitor.mutex); + slapi_destroy_mutex(inst->bind_pool->conn.conn_list_mutex); + slapi_destroy_mutex(inst->pool->conn.conn_list_mutex); + slapi_destroy_mutex(inst->monitor_availability.cpt_lock); + slapi_destroy_mutex(inst->monitor_availability.lock_timeLimit); + slapi_ch_free((void **) &inst->configDn); + slapi_ch_free((void **) &inst->monitorDn); + slapi_ch_free((void **) &inst->inst_name); + charray_free(inst->every_attribute); + + slapi_ch_free((void **) &inst->bind_pool); + slapi_ch_free((void **) &inst->pool); + + PR_RWLock_Unlock(inst->rwl_config_lock); + + PR_DestroyRWLock(inst->rwl_config_lock); + + slapi_ch_free((void **) &inst); + } +/* XXXSD */ +} + +/* +** Check the change the configuration of an existing chaining +** backend instance. +*/ + +int cb_instance_modify_config_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) { + + cb_backend_instance * inst = (cb_backend_instance *) arg; + LDAPMod **mods; + int rc = LDAP_SUCCESS; + int i; + char * attr_name; + returntext[0] = '\0'; + + CB_ASSERT(inst!=NULL); + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + + /* First pass to validate input */ + for (i = 0; mods[i] && LDAP_SUCCESS == rc; i++) { + attr_name = mods[i]->mod_type; + + /* specific processing for multi-valued attributes */ + if ( !strcasecmp ( attr_name, CB_CONFIG_SUFFIX )) { + sprintf(returntext, "suffix modification not allowed\n"); + rc = LDAP_UNWILLING_TO_PERFORM; + continue; + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_ILLEGAL_ATTRS )) { + continue; + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_CHAINING_COMPONENTS )) { + continue; + } else + + /* CB_CONFIG_BINDUSER & CB_CONFIG_USERPASSWORD may be added */ + /* or deleted */ + + if ( !strcasecmp ( attr_name, CB_CONFIG_USERPASSWORD )) { + continue; + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_BINDUSER )) { + + /* Make sure value is not forbidden */ + if ((mods[i]->mod_op & LDAP_MOD_REPLACE) || + ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)) { + + rc = cb_instance_config_set((void *) inst, attr_name, + cb_the_instance_config, mods[i]->mod_bvalues[0], returntext, + CB_CONFIG_PHASE_RUNNING, 0); + continue; + } + } + + if ((mods[i]->mod_op & LDAP_MOD_DELETE) || + ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)) { + rc= LDAP_UNWILLING_TO_PERFORM; + sprintf(returntext, "%s attributes is not allowed", + (mods[i]->mod_op & LDAP_MOD_DELETE) ? "Deleting" : "Adding"); + } else if (mods[i]->mod_op & LDAP_MOD_REPLACE) { + /* This assumes there is only one bval for this mod. */ + rc = cb_instance_config_set((void *) inst, attr_name, + cb_the_instance_config, mods[i]->mod_bvalues[0], returntext, + CB_CONFIG_PHASE_RUNNING, 0); + } + } + *returncode= rc; + return ((LDAP_SUCCESS == rc) ? 1:-1); +} + + +/* +** Change the configuration of an existing chaining +** backend instance. +*/ + +int cb_instance_modify_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) { + + cb_backend_instance * inst = (cb_backend_instance *) arg; + LDAPMod **mods; + int rc = LDAP_SUCCESS; + int i; + int reopen_conn=0; + char * attr_name; + returntext[0] = '\0'; + + CB_ASSERT(inst!=NULL); + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + + /* input checked in the preop modify callback */ + + for (i = 0; mods[i] && LDAP_SUCCESS == rc; i++) { + attr_name = mods[i]->mod_type; + + /* specific processing for multi-valued attributes */ + if ( !strcasecmp ( attr_name, CB_CONFIG_ILLEGAL_ATTRS )) { + char * config_attr_value; + int done=0; + int j; + + PR_RWLock_Wlock(inst->rwl_config_lock); + for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) { + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + if ( mods[i]->mod_op & LDAP_MOD_REPLACE) { + if (!done) { + charray_free(inst->illegal_attributes); + inst->illegal_attributes=NULL; + done=1; + } + charray_add(&inst->illegal_attributes, + slapi_ch_strdup(config_attr_value)); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) { + charray_add(&inst->illegal_attributes, + slapi_ch_strdup(config_attr_value)); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + charray_remove(inst->illegal_attributes, + slapi_ch_strdup(config_attr_value)); + } + } + if (NULL == mods[i]->mod_bvalues) { + charray_free(inst->illegal_attributes); + inst->illegal_attributes=NULL; + } + PR_RWLock_Unlock(inst->rwl_config_lock); + continue; + } + if ( !strcasecmp ( attr_name, CB_CONFIG_CHAINING_COMPONENTS )) { + char * config_attr_value; + int done=0; + int j; + + PR_RWLock_Wlock(inst->rwl_config_lock); + for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) { + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + if ( mods[i]->mod_op & LDAP_MOD_REPLACE) { + if (!done) { + charray_free(inst->chaining_components); + inst->chaining_components=NULL; + done=1; + } + /* XXXSD assume dns */ + charray_add(&inst->chaining_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value))); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) { + charray_add(&inst->chaining_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value))); + } else + if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + charray_remove(inst->chaining_components, + slapi_dn_normalize(slapi_ch_strdup(config_attr_value))); + } + } + if (NULL == mods[i]->mod_bvalues) { + charray_free(inst->chaining_components); + inst->chaining_components=NULL; + } + PR_RWLock_Unlock(inst->rwl_config_lock); + continue; + } + + + + if ((mods[i]->mod_op & LDAP_MOD_DELETE) || + ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)) { + + /* Special processing for binddn & password */ + /* because they are optional */ + + if (( !strcasecmp ( attr_name, CB_CONFIG_BINDUSER )) || + ( !strcasecmp ( attr_name, CB_CONFIG_USERPASSWORD ))) { + + if (mods[i]->mod_op & LDAP_MOD_DELETE) { + rc = cb_instance_config_set((void *) inst, attr_name, + cb_the_instance_config, NULL, returntext, + CB_CONFIG_PHASE_RUNNING, 1); } + else { + rc = cb_instance_config_set((void *) inst, attr_name, + cb_the_instance_config, mods[i]->mod_bvalues[0], returntext, + CB_CONFIG_PHASE_RUNNING, 1); + } + if (rc==CB_REOPEN_CONN) { + reopen_conn=1; + rc=LDAP_SUCCESS; + } + continue; + } + + rc= LDAP_UNWILLING_TO_PERFORM; + sprintf(returntext, "%s attributes is not allowed", + (mods[i]->mod_op & LDAP_MOD_DELETE) ? "Deleting" : "Adding"); + } else if (mods[i]->mod_op & LDAP_MOD_REPLACE) { + /* This assumes there is only one bval for this mod. */ + rc = cb_instance_config_set((void *) inst, attr_name, + cb_the_instance_config, mods[i]->mod_bvalues[0], returntext, + CB_CONFIG_PHASE_RUNNING, 1); + + /* Update requires to reopen connections */ + /* Expensive operation so do it only once */ + if (rc==CB_REOPEN_CONN) { + reopen_conn=1; + rc=LDAP_SUCCESS; + } + } + } + + *returncode= rc; + + if (reopen_conn) { + cb_stale_all_connections(inst); + } + + return ((LDAP_SUCCESS == rc) ? SLAPI_DSE_CALLBACK_OK:SLAPI_DSE_CALLBACK_ERROR); +} + +/* +** Parse and instantiate backend instances +*/ + +int +cb_parse_instance_config_entry(cb_backend * cb, Slapi_Entry * e) { + + int rc =LDAP_SUCCESS; + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + const struct berval *attrValue; + cb_backend_instance *inst=NULL; + char *instname; + Slapi_PBlock *search_pb=NULL; + char retmsg[CB_BUFSIZE]; + + CB_ASSERT(e!=NULL); + + /* + ** Retrieve the instance name and make sure it is not + ** already declared. + */ + + if ( 0 == slapi_entry_attr_find( e, CB_CONFIG_INSTNAME, &attr )) { + slapi_attr_first_value(attr, &sval); + attrValue = slapi_value_get_berval(sval); + instname=attrValue->bv_val; + } else { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Malformed backend instance (<%s> missing)>\n", CB_CONFIG_INSTNAME); + return LDAP_LOCAL_ERROR; + } + + /* Allocate a new backend internal data structure */ + inst = cb_instance_alloc(cb,instname,slapi_entry_get_dn(e)); + + /* Emulate a add config entry to configure */ + /* this backend instance. */ + + cb_instance_add_config_callback(NULL,e,NULL,&rc,retmsg,inst); + if ( rc != LDAP_SUCCESS ) { + cb_instance_free(inst); + } + return rc; +} + +/* +** Update the instance configuration +*/ + +static int +cb_instance_config_initialize(cb_backend_instance * inst, Slapi_Entry * e , int phase, int apply) { + + int rc =LDAP_SUCCESS; + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + struct berval * bval; + int using_def_connlifetime,i; + char err_buf[CB_BUFSIZE]; + int urlfound=0; + char *rootdn; + + using_def_connlifetime=1; + + for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) { + char * attr_name=NULL; + slapi_attr_get_type(attr, &attr_name); + + if ( !strcasecmp ( attr_name, CB_CONFIG_SUFFIX )) { + if (apply && ( inst->inst_be != NULL )) { + Slapi_DN *suffix; + suffix = slapi_sdn_new(); + i = slapi_attr_first_value(attr, &sval); + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + slapi_sdn_init_dn_byref(suffix, bval->bv_val); + + if (!slapi_be_issuffix(inst->inst_be, suffix)) { + slapi_be_addsuffix(inst->inst_be, suffix); + } + slapi_sdn_done(suffix); + slapi_sdn_free(&suffix); + i = slapi_attr_next_value(attr, i, &sval); + } + } + continue; + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_CHAINING_COMPONENTS )) { + + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + i = slapi_attr_first_value(attr, &sval); + charray_free(inst->chaining_components); + inst->chaining_components=NULL; + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + charray_add(&inst->chaining_components, + slapi_dn_normalize(slapi_ch_strdup(bval->bv_val))); + i = slapi_attr_next_value(attr, i, &sval); + } + PR_RWLock_Unlock(inst->rwl_config_lock); + } + continue; + } else + if ( !strcasecmp ( attr_name, CB_CONFIG_ILLEGAL_ATTRS )) { + + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + i = slapi_attr_first_value(attr, &sval); + charray_free(inst->illegal_attributes); + inst->illegal_attributes=NULL; + while (i != -1 ) { + bval = (struct berval *) slapi_value_get_berval(sval); + charray_add(&inst->illegal_attributes, + slapi_ch_strdup(bval->bv_val)); + i = slapi_attr_next_value(attr, i, &sval); + } + PR_RWLock_Unlock(inst->rwl_config_lock); + } + continue; + } + + + if ( !strcasecmp ( attr_name, CB_CONFIG_HOSTURL )) { + urlfound=1; + } + + + /* We are assuming that each of these attributes are to have + * only one value. If they have more than one value, like + * the nsslapd-suffix attribute, then they need to be + * handled differently. */ + + slapi_attr_first_value(attr, &sval); + bval = (struct berval *) slapi_value_get_berval(sval); + + if (cb_instance_config_set((void *) inst, attr_name, + cb_the_instance_config, bval, err_buf, phase, apply ) != LDAP_SUCCESS) { + slapi_log_error( SLAPI_LOG_FATAL, + CB_PLUGIN_SUBSYSTEM,"Error with config attribute %s : %s\n", + attr_name, err_buf); + rc=LDAP_LOCAL_ERROR; + break; + } + if ( !strcasecmp ( attr_name, CB_CONFIG_CONNLIFETIME )) { + using_def_connlifetime=0; + } + } + + + /* + ** Check for mandatory attributes + ** Post-Processing + */ + + if (LDAP_SUCCESS == rc) { + if (!urlfound) { + slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "Malformed backend instance entry. Mandatory attr <%s> missing\n", + CB_CONFIG_HOSTURL); + rc= LDAP_LOCAL_ERROR; + } + + if (apply ) { + if ( using_def_connlifetime && + strchr( inst->pool->hostname, ' ' ) != NULL ) { + + cb_instance_config_set((void *)inst, CB_CONFIG_CONNLIFETIME, + cb_the_instance_config, NULL /* use default */, err_buf, + CB_CONFIG_PHASE_INITIALIZATION, 1 ); + } + } + } + + /* + ** Additional checks + ** It is forbidden to use directory manager as proxy user + ** due to a bug in the acl check + */ + + rootdn=cb_get_rootdn(); + + if (inst->impersonate && inst->pool && inst->pool->binddn && + !strcmp(inst->pool->binddn,rootdn)) { /* UTF8 aware */ + slapi_log_error( SLAPI_LOG_FATAL, + CB_PLUGIN_SUBSYSTEM,"Error with config attribute %s (%s: forbidden value)\n", + CB_CONFIG_BINDUSER, rootdn); + rc=LDAP_LOCAL_ERROR; + } + slapi_ch_free((void **)&rootdn); + + return rc; +} + +/******************************************************** +** Get and set functions for chaining backend instances * +********************************************************* +*/ + +static void *cb_instance_hosturl_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + char * data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = slapi_ch_strdup(inst->pool->url); + PR_RWLock_Unlock(inst->rwl_config_lock); + return data; +} + +static int cb_instance_hosturl_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance *inst=(cb_backend_instance *) arg; + char *url = (char *) value; + LDAPURLDesc *ludp=NULL; + int rc=LDAP_SUCCESS; + + if (( rc = ldap_url_parse( url, &ludp )) != 0 ) { + strcpy(errorbuf,cb_urlparse_err2string( rc )); + if (CB_CONFIG_PHASE_INITIALIZATION == phase) + inst->pool->url=slapi_ch_strdup(""); + return(LDAP_INVALID_SYNTAX); + } + + if (apply) { + + char * ptr; + + PR_RWLock_Wlock(inst->rwl_config_lock); + + if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) && + ( phase != CB_CONFIG_PHASE_STARTUP )) { + + /* Dynamic modification */ + /* Don't free char * pointer now */ + /* STore them in a waste basket */ + /* Will be relesase when the backend stops */ + + if (inst->pool->hostname) + charray_add(&inst->pool->waste_basket,inst->pool->hostname); + if (inst->pool->url) + charray_add(&inst->pool->waste_basket,inst->pool->url); + + if (inst->bind_pool->hostname) + charray_add(&inst->bind_pool->waste_basket,inst->bind_pool->hostname); + if (inst->bind_pool->url) + charray_add(&inst->bind_pool->waste_basket,inst->bind_pool->url); + + /* Require connection cleanup */ + rc=CB_REOPEN_CONN; + } + + /* Normal case. Extract useful data from */ + /* the url and update the configuration */ + + if ((ludp->lud_host==NULL) || (strlen(ludp->lud_host)==0)) { + inst->pool->hostname=(char *)slapi_ch_strdup((char *)get_localhost_DNS()); + } else { + inst->pool->hostname = slapi_ch_strdup( ludp->lud_host ); + } + inst->pool->url = slapi_ch_strdup( url); + inst->pool->secure = (( ludp->lud_options & LDAP_URL_OPT_SECURE ) != 0 ); + + if ((ludp->lud_port==0) && inst->pool->secure) + inst->pool->port=CB_LDAP_SECURE_PORT; + else + inst->pool->port = ludp->lud_port; + + /* Build a charray of <host>:<port> */ + /* hostname is of the form <host>[:port] <host>[:port] */ + + { char * aBufCopy, * aHostName; + char * iter = NULL; + aBufCopy= aBufCopy=slapi_ch_strdup(inst->pool->hostname); + + aHostName=ldap_utf8strtok_r(aBufCopy," ", &iter); + charray_free(inst->url_array); + inst->url_array=NULL; + while (aHostName) { + + char * aHostPort = slapi_ch_calloc(1,strlen(aHostName)+30); + if ( NULL == ( ptr=strstr(aHostName,":"))) + sprintf(aHostPort,"%s://%s:%d/", + inst->pool->secure ? "ldaps" : "ldap", + aHostName,inst->pool->port); + else + sprintf(aHostPort,"%s://%s/", + inst->pool->secure ? "ldaps" : "ldap", + aHostName); + + charray_add(&inst->url_array,aHostPort); + aHostName=ldap_utf8strtok_r(NULL," ", &iter); + } + + slapi_ch_free((void **) &aBufCopy); + } + + inst->bind_pool->port=inst->pool->port; + inst->bind_pool->secure=inst->pool->secure; + inst->bind_pool->hostname=slapi_ch_strdup(inst->pool->hostname); + + PR_RWLock_Unlock(inst->rwl_config_lock); + } + + if ( ludp != NULL ) { + ldap_free_urldesc( ludp ); + } + return rc; +} + +static void *cb_instance_binduser_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + char * data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = slapi_ch_strdup(inst->pool->binddn2); /* not normalized */ + PR_RWLock_Unlock(inst->rwl_config_lock); + return data; +} + +static int cb_instance_binduser_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int rc=LDAP_SUCCESS; + + if (apply) { + + PR_RWLock_Wlock(inst->rwl_config_lock); + if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) && + ( phase != CB_CONFIG_PHASE_STARTUP )) { + + /* Dynamic modif */ + /* Free user later */ + + charray_add(&inst->pool->waste_basket,inst->pool->binddn); + charray_add(&inst->pool->waste_basket,inst->pool->binddn2); + rc=CB_REOPEN_CONN; + } + + inst->pool->binddn=slapi_ch_strdup((char *) value); + inst->pool->binddn2=slapi_ch_strdup((char *) value); + slapi_dn_normalize_case(inst->pool->binddn); + PR_RWLock_Unlock(inst->rwl_config_lock); + } else { + + /* Security check */ + /* directory manager of the farm server should not be used as */ + /* proxing user. This is hard to check, so assume same directory */ + /* manager across servers. */ + + char * rootdn = cb_get_rootdn(); + char * theValueCopy = NULL; + + if (value) { + theValueCopy=slapi_ch_strdup((char *) value); + slapi_dn_normalize_case(theValueCopy); + } + + PR_RWLock_Rlock(inst->rwl_config_lock); + if (inst->impersonate && theValueCopy && + !strcmp(theValueCopy,rootdn)) { /* UTF8-aware. See cb_get_dn() */ + rc=LDAP_UNWILLING_TO_PERFORM; + if (errorbuf) { + sprintf(errorbuf,"value %s not allowed",rootdn); + } + } + PR_RWLock_Unlock(inst->rwl_config_lock); + + slapi_ch_free((void **)&theValueCopy); + slapi_ch_free((void **)&rootdn); + } + + return rc; +} + + +static void *cb_instance_userpassword_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + char * data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = slapi_ch_strdup(inst->pool->password); + PR_RWLock_Unlock(inst->rwl_config_lock); + return data; +} + +static int cb_instance_userpassword_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int rc=LDAP_SUCCESS; + + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) && + ( phase != CB_CONFIG_PHASE_STARTUP )) { + + /* Dynamic modif */ + charray_add(&inst->pool->waste_basket,inst->pool->password); + rc=CB_REOPEN_CONN; + } + + inst->pool->password=slapi_ch_strdup((char *) value); + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return rc; +} + +static void *cb_instance_sizelimit_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->sizelimit; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_sizelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->sizelimit=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + if (inst->inst_be) + be_set_sizelimit(inst->inst_be, (int) value); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_timelimit_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->timelimit; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_timelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->timelimit=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + if (inst->inst_be) + be_set_timelimit(inst->inst_be, (int) value); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_max_test_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->max_test_time; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_max_test_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->max_test_time=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_max_idle_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->max_idle_time; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_max_idle_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->max_idle_time=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + + +static void *cb_instance_hoplimit_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->hoplimit; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_hoplimit_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->hoplimit=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_maxbconn_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->bind_pool->conn.maxconnections; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_maxbconn_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->bind_pool->conn.maxconnections=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_maxconn_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->pool->conn.maxconnections; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_maxconn_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->pool->conn.maxconnections=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_abandonto_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->abandon_timeout.tv_sec; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_abandonto_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + + if (apply) { + if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) && + ( phase != CB_CONFIG_PHASE_STARTUP )) { + + /* Dynamic modif not supported */ + /* Stored in ldif only */ + return LDAP_SUCCESS; + } + + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->abandon_timeout.tv_sec=(int) value; + inst->abandon_timeout.tv_usec=0; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_maxbconc_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->bind_pool->conn.maxconcurrency; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_maxbconc_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->bind_pool->conn.maxconcurrency=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_maxconc_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->pool->conn.maxconcurrency; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_maxconc_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->pool->conn.maxconcurrency=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_imperson_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data = inst->impersonate; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_imperson_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int rc=LDAP_SUCCESS; + + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->impersonate=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } else { + /* Security check: Make sure the proxing user is */ + /* not the directory manager. */ + + char * rootdn=cb_get_rootdn(); + + PR_RWLock_Rlock(inst->rwl_config_lock); + if (((int) value) && inst->pool && inst->pool->binddn && + !strcmp(inst->pool->binddn,rootdn)) { /* UTF-8 aware */ + rc=LDAP_UNWILLING_TO_PERFORM; + if (errorbuf) + sprintf(errorbuf,"Proxy mode incompatible with %s value (%s not allowed)", + CB_CONFIG_BINDUSER,rootdn); + } + PR_RWLock_Unlock(inst->rwl_config_lock); + slapi_ch_free((void **)&rootdn); + } + + return rc; +} + +static void *cb_instance_connlife_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data=inst->pool->conn.connlifetime; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_connlife_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->pool->conn.connlifetime=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_bindto_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data=inst->bind_pool->conn.op_timeout.tv_sec; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_bindto_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->bind_pool->conn.op_timeout.tv_sec=(int) value; + inst->bind_pool->conn.op_timeout.tv_usec=0; + inst->bind_pool->conn.bind_timeout.tv_sec=(int) value; + inst->bind_pool->conn.bind_timeout.tv_usec=0; + /* Used to bind to the farm server */ + inst->pool->conn.bind_timeout.tv_sec=(int) value; + inst->pool->conn.bind_timeout.tv_usec=0; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_opto_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data=inst->pool->conn.op_timeout.tv_sec; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_opto_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->pool->conn.op_timeout.tv_sec=(int) value; + inst->pool->conn.op_timeout.tv_usec=0; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_ref_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data=inst->searchreferral; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_ref_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->searchreferral=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_acl_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data=inst->local_acl; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_acl_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + + if (apply) { + if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) && + ( phase != CB_CONFIG_PHASE_STARTUP )) { + + /* Dynamic modif not supported */ + /* Stored in ldif only */ + return LDAP_SUCCESS; + } + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->local_acl=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + +static void *cb_instance_bindretry_get(void *arg) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + int data; + + PR_RWLock_Rlock(inst->rwl_config_lock); + data=inst->bind_retry; + PR_RWLock_Unlock(inst->rwl_config_lock); + return (void *) data; +} + +static int cb_instance_bindretry_set(void *arg, void *value, char *errorbuf, int phase, int apply) +{ + cb_backend_instance * inst=(cb_backend_instance *) arg; + if (apply) { + PR_RWLock_Wlock(inst->rwl_config_lock); + inst->bind_retry=(int) value; + PR_RWLock_Unlock(inst->rwl_config_lock); + } + return LDAP_SUCCESS; +} + + + + +/* Finds an entry in a config_info array with the given name. Returns + * the entry on success and NULL when not found. + */ +static cb_instance_config_info *cb_get_config_info(cb_instance_config_info *config_array, char *attr_name) +{ + int x; + + for(x = 0; config_array[x].config_name != NULL; x++) { + if (!strcasecmp(config_array[x].config_name, attr_name)) { + return &(config_array[x]); + } + } + return NULL; +} + +/* +** Update an attribute value +** For now, unknown attributes are ignored +** Return a LDAP error code OR CB_REOPEN_CONN when the +** update requires to close open connections. +*/ + +static int +cb_instance_config_set(void *arg, char *attr_name, cb_instance_config_info *config_array, +struct berval *bval, char *err_buf, int phase, int apply_mod) +{ + cb_instance_config_info *config; + int use_default; + int int_val; + long long_val; + int retval=LDAP_LOCAL_ERROR; + + config = cb_get_config_info(config_array, attr_name); + if (NULL == config) { + /* Ignore unknown attributes */ + return LDAP_SUCCESS; + } + + /* If the config phase is initialization or if bval is NULL, we will use + * the default value for the attribute. */ + if (CB_CONFIG_PHASE_INITIALIZATION == phase || NULL == bval) { + use_default = 1; + } else { + use_default = 0; + /* Since we are setting the value for the config attribute, we + * need to turn on the CB_PREVIOUSLY_SET flag to make + * sure this attribute is shown. */ + config->config_flags |= CB_PREVIOUSLY_SET; + } + + switch(config->config_type) { + case CB_CONFIG_TYPE_INT: + if (use_default) { + int_val = cb_atoi(config->config_default_value); + } else { + int_val = cb_atoi((char *)bval->bv_val); + } + retval = config->config_set_fn(arg, (void *) int_val, err_buf, phase, apply_mod); + break; + case CB_CONFIG_TYPE_INT_OCTAL: + if (use_default) { + int_val = (int) strtol(config->config_default_value, NULL, 8); + } else { + int_val = (int) strtol((char *)bval->bv_val, NULL, 8); + } + retval = config->config_set_fn(arg, (void *) int_val, err_buf, phase, apply_mod); + break; + case CB_CONFIG_TYPE_LONG: + if (use_default) { + long_val = cb_atol(config->config_default_value); + } else { + long_val = cb_atol((char *)bval->bv_val); + } + retval = config->config_set_fn(arg, (void *) long_val, err_buf, phase, apply_mod); + break; + case CB_CONFIG_TYPE_STRING: + if (use_default) { + retval = config->config_set_fn(arg, config->config_default_value, err_buf, phase, apply_mod); + } else { + retval = config->config_set_fn(arg, bval->bv_val, err_buf, phase, apply_mod); + } + break; + case CB_CONFIG_TYPE_ONOFF: + if (use_default) { + int_val = !strcasecmp(config->config_default_value, "on"); + } else { + int_val = !strcasecmp((char *) bval->bv_val, "on"); + } + retval = config->config_set_fn(arg, (void *) int_val, err_buf, phase, apply_mod); + break; + } + return retval; +} + +/* Utility function used in creating config entries. Using the + * config_info, this function gets info and formats in the correct + * way. + */ +void cb_instance_config_get(void *arg, cb_instance_config_info *config, char *buf) +{ + char *tmp_string; + + if (config == NULL) { + buf[0] = '\0'; + } + + switch(config->config_type) { + case CB_CONFIG_TYPE_INT: + sprintf(buf, "%d", (int) config->config_get_fn(arg)); + break; + case CB_CONFIG_TYPE_INT_OCTAL: + sprintf(buf, "%o", (int) config->config_get_fn(arg)); + break; + case CB_CONFIG_TYPE_LONG: + sprintf(buf, "%d", (long) config->config_get_fn(arg)); + break; + case CB_CONFIG_TYPE_STRING: + /* Remember the get function for strings returns memory + * that must be freed. */ + tmp_string = (char *) config->config_get_fn(arg); + sprintf(buf, "%s", (char *) tmp_string); + slapi_ch_free((void **)&tmp_string); + break; + case CB_CONFIG_TYPE_ONOFF: + if ((int) config->config_get_fn(arg)) { + sprintf(buf,"%s","on"); + } else { + sprintf(buf,"%s","off"); + } + break; + default: + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Invalid attribute syntax.\n"); + + } +} + +/* +** Search for instance config entry +** Always return 'active' values because some configuration changes +** won't be taken into account until the server restarts +*/ + +int cb_instance_search_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) { + + char buf[CB_BUFSIZE]; + struct berval val; + struct berval *vals[2]; + int i = 0; + cb_backend_instance *inst = (cb_backend_instance *)arg; + cb_instance_config_info * config; + + vals[0] = &val; + vals[1] = NULL; + + /* suffixes */ + + PR_RWLock_Rlock(inst->rwl_config_lock); + + { + const Slapi_DN *aSuffix; + i=0; + if (inst->inst_be) { + while ((aSuffix=slapi_be_getsuffix(inst->inst_be,i))) { + val.bv_val = (char *)slapi_sdn_get_dn(aSuffix); + val.bv_len = strlen( val.bv_val ); + if (val.bv_len) { + if (i==0) + slapi_entry_attr_replace(e,CB_CONFIG_SUFFIX,(struct berval **)vals ); + else + slapi_entry_attr_merge(e,CB_CONFIG_SUFFIX,(struct berval **)vals ); + } + i++; + } + } + } + + for (i=0; inst->chaining_components && inst->chaining_components[i]; i++) { + val.bv_val = inst->chaining_components[i]; + val.bv_len = strlen( val.bv_val ); + if (val.bv_len) { + if (i==0) + slapi_entry_attr_replace(e,CB_CONFIG_CHAINING_COMPONENTS,(struct berval **)vals ); + else + slapi_entry_attr_merge(e,CB_CONFIG_CHAINING_COMPONENTS,(struct berval **)vals ); + } + } + + for (i=0; inst->illegal_attributes && inst->illegal_attributes[i]; i++) { + val.bv_val = inst->illegal_attributes[i]; + val.bv_len = strlen( val.bv_val ); + if (val.bv_len) { + if (i==0) + slapi_entry_attr_replace(e,CB_CONFIG_ILLEGAL_ATTRS,(struct berval **)vals ); + else + slapi_entry_attr_merge(e,CB_CONFIG_ILLEGAL_ATTRS,(struct berval **)vals ); + } + } + + PR_RWLock_Unlock(inst->rwl_config_lock); + + /* standard attributes */ + for(config = cb_the_instance_config; config->config_name != NULL; config++) { + if (!(config->config_flags & (CB_ALWAYS_SHOW | CB_PREVIOUSLY_SET))) { + /* This config option shouldn't be shown */ + continue; + } + + cb_instance_config_get((void *) inst, config, buf); + + val.bv_val = buf; + val.bv_len = strlen(buf); + if (val.bv_len) + slapi_entry_attr_replace(e, config->config_name, vals); + } + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +/* +** Ooops!!! The backend instance is beeing deleted +*/ + +int cb_instance_delete_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg) { + + cb_backend_instance * inst = (cb_backend_instance *) arg; + int rc; + Slapi_Entry * anEntry=NULL; + Slapi_DN * aDn; + + CB_ASSERT( inst!=NULL ); + + /* notify the front-end */ + slapi_mtn_be_stopping(inst->inst_be); + + /* Now it is safe to stop */ + /* No pending op */ + + + /* unregister callbacks */ + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->configDn, + LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_search_config_callback); + + slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_POSTOP, inst->configDn, + LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_delete_config_callback); + + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->configDn, + LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_modify_config_check_callback); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, inst->configDn, + LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_modify_config_callback); + + /* At this point, the monitor entry should have been removed */ + /* If not, manually call delete callback */ + + aDn = slapi_sdn_new_dn_byref(inst->monitorDn); + if ( LDAP_SUCCESS==(slapi_search_internal_get_entry(aDn,NULL, &anEntry,inst->backend_type->identity))) { + cb_delete_monitor_callback( NULL, anEntry, NULL, &rc , NULL, inst ); + if (anEntry) + slapi_entry_free(anEntry); + } + slapi_sdn_done(aDn); + slapi_sdn_free(&aDn); + + /* free resources */ + cb_close_conn_pool(inst->bind_pool); + cb_close_conn_pool(inst->pool); + slapi_be_free(&(inst->inst_be)); + cb_instance_free(inst); + + return SLAPI_DSE_CALLBACK_OK; +} + +static void cb_instance_add_monitor_later(time_t when, void *arg) { + + cb_backend_instance * inst = (cb_backend_instance *) arg; + + if ( inst != NULL ) + { + PR_RWLock_Rlock(inst->rwl_config_lock); + + /* create the monitor entry if it is not there yet */ + if (LDAP_SUCCESS == cb_config_add_dse_entries(inst->backend_type, cb_skeleton_entries, + inst->inst_name,CB_PLUGIN_NAME, NULL)) + { + + /* add monitor callbacks */ + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE, + "(objectclass=*)", cb_search_monitor_callback, (void *) inst); + + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE, + "(objectclass=*)", cb_dont_allow_that, (void *) NULL); + + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP , inst->monitorDn, LDAP_SCOPE_BASE, + "(objectclass=*)", cb_delete_monitor_callback, (void *) inst); + } + PR_RWLock_Unlock(inst->rwl_config_lock); + } +} + + +int cb_instance_add_config_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg) { + + int rc=LDAP_SUCCESS; + cb_backend_instance *inst; + cb_backend *cb=(cb_backend *) arg; + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + const struct berval *attrValue; + char *instname=NULL; + + if (returntext) + returntext[0]='\0'; + + /* Basic entry check */ + if ( 0 == slapi_entry_attr_find( e, CB_CONFIG_INSTNAME, &attr )) { + slapi_attr_first_value(attr, &sval); + attrValue = slapi_value_get_berval(sval); + instname=attrValue->bv_val; + } + if ( instname == NULL ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Malformed backend instance (<%s> missing)>\n", CB_CONFIG_INSTNAME); + *returncode = LDAP_LOCAL_ERROR; + return SLAPI_DSE_CALLBACK_ERROR; + } + + /* Allocate a new backend internal data structure */ + inst = cb_instance_alloc(cb,instname,slapi_entry_get_dn(e)); + + /* build the backend instance from the default hardcoded conf, */ + /* the default instance config and the specific entry specified */ + if ((rc=cb_build_backend_instance_config(inst,e,0)) + != LDAP_SUCCESS) { + slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "Can't instantiate chaining backend instance %s.\n",inst->inst_name); + *returncode=rc; + cb_instance_free(inst); + return SLAPI_DSE_CALLBACK_ERROR; + } + + /* Free the dummy instance */ + *returncode=rc; + cb_instance_free(inst); + + return SLAPI_DSE_CALLBACK_OK; +} + + +/* Create the default instance config from hard-coded values */ +/* +** Initialize the backend instance with the config entry +** passed in arguments. +** <arg> : (cb_backend *) +*/ + +int cb_instance_add_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, + int *returncode, char *returntext, void *arg) { + + int rc=LDAP_SUCCESS; + cb_backend_instance *inst; + cb_backend *cb=(cb_backend *) arg; + Slapi_Attr *attr = NULL; + Slapi_Value *sval; + const struct berval *attrValue; + char *instname=NULL; + + if (returntext) + returntext[0]='\0'; + + /* Basic entry check */ + if ( 0 == slapi_entry_attr_find( e, CB_CONFIG_INSTNAME, &attr )) { + slapi_attr_first_value(attr, &sval); + attrValue = slapi_value_get_berval(sval); + instname=attrValue->bv_val; + } + if ( instname == NULL ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Malformed backend instance (<%s> missing)>\n", CB_CONFIG_INSTNAME); + *returncode = LDAP_LOCAL_ERROR; + return SLAPI_DSE_CALLBACK_ERROR; + } + + /* Allocate a new backend internal data structure */ + inst = cb_instance_alloc(cb,instname,slapi_entry_get_dn(e)); + + /* build the backend instance from the default hardcoded conf, */ + /* the default instance config and the specific entry specified */ + if ((rc=cb_build_backend_instance_config(inst,e,0)) + != LDAP_SUCCESS) { + slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "Can't instantiate chaining backend instance %s.\n",inst->inst_name); + *returncode=rc; + cb_instance_free(inst); + return SLAPI_DSE_CALLBACK_ERROR; + } + + /* Instantiate a Slapi_Backend if necessary */ + if (!inst->isconfigured) { + + Slapi_PBlock *aPb=NULL; + + inst->inst_be = slapi_be_new(CB_CHAINING_BACKEND_TYPE,slapi_ch_strdup(inst->inst_name),0,0); + aPb=slapi_pblock_new(); + slapi_pblock_set(aPb, SLAPI_PLUGIN, inst->backend_type->plugin); + slapi_be_setentrypoint(inst->inst_be,0,(void *)NULL,aPb); + slapi_be_set_instance_info(inst->inst_be,inst); + slapi_pblock_set(aPb, SLAPI_PLUGIN, NULL); + slapi_pblock_destroy(aPb); + } + + cb_build_backend_instance_config(inst,e,1); + + /* kexcoff: the order of the following calls is very important to prevent the deletion of the + instance to happen before the creation of the monitor part of the config. + However, not sure it solves all the situations, but at least it is worth to maintain + this order. */ + + if (!inst->isconfigured) + { + /* Add monitor entry and callback on it + * called from an add... + * we can't call recursively into the DSE to do more adds, they'll + * silently fail. instead, schedule the adds to happen in 1 second. + */ + inst->eq_ctx = slapi_eq_once(cb_instance_add_monitor_later, (void *)inst, time(NULL)+1); + } + + /* Get the list of operational attrs defined in the schema */ + /* see cb_search file for a reason for that */ + + inst->every_attribute=slapi_schema_list_attribute_names(SLAPI_ATTR_FLAG_OPATTR); + charray_add(&inst->every_attribute,slapi_ch_strdup(LDAP_ALL_USER_ATTRS)); + + if (!inst->isconfigured) + { + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->configDn, + LDAP_SCOPE_BASE,"(objectclass=*)",cb_instance_modify_config_check_callback, (void *) inst); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, inst->configDn, + LDAP_SCOPE_BASE,"(objectclass=*)",cb_instance_modify_config_callback, (void *) inst); + + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->configDn, + LDAP_SCOPE_BASE,"(objectclass=*)", cb_instance_search_config_callback, (void *) inst); + + /* allow deletion otherwise impossible to remote a backend instance */ + /* dynamically... */ + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_POSTOP, inst->configDn, + LDAP_SCOPE_BASE,"(objectclass=*)", cb_instance_delete_config_callback, (void *) inst); + } + + /* Notify the front-end */ + /* After the call below, we can receive ops */ + slapi_mtn_be_started(inst->inst_be); + + inst->isconfigured=1; + return SLAPI_DSE_CALLBACK_OK; +} + + +/* Create the default instance config from hard-coded values */ + +int cb_create_default_backend_instance_config(cb_backend * cb) { + + + int rc; + cb_backend_instance *dummy; + Slapi_Entry *e=slapi_entry_alloc(); + char defaultDn[CB_BUFSIZE]; + char *olddn; + struct berval val; + struct berval *vals[2]; + Slapi_PBlock *pb; + + dummy = cb_instance_alloc(cb, "dummy", "o=dummy"); + cb_instance_config_set_default(dummy); + cb_instance_search_config_callback(NULL,e,NULL, &rc, NULL,(void *) dummy); + + + /* set right dn and objectclass */ + + sprintf(defaultDn,"cn=default instance config,%s",cb->pluginDN); + olddn = slapi_entry_get_dn(e); + slapi_ch_free((void **) &olddn); + + slapi_entry_set_dn(e,slapi_ch_strdup(defaultDn)); + + vals[0] = &val; + vals[1] = NULL; + + val.bv_val = "top"; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "objectclass", (struct berval **)vals ); + val.bv_val = CB_CONFIG_EXTENSIBLEOCL; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_merge( e, "objectclass", (struct berval **)vals ); + val.bv_val = "default instance config"; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "cn", (struct berval **)vals ); + + /* create entry */ + pb = slapi_pblock_new(); + slapi_add_entry_internal_set_pb(pb, e, NULL, cb->identity, 0); + slapi_add_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if ( LDAP_SUCCESS != rc ) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Add %s failed (%s)\n",defaultDn,ldap_err2string(rc)); + } + + slapi_pblock_destroy(pb); + /* cleanup */ + cb_instance_free(dummy); + /* BEWARE: entry is consummed */ + return rc; +} + +/* Extract backend instance configuration from the LDAP entry */ + +int cb_build_backend_instance_config(cb_backend_instance *inst, Slapi_Entry * conf, int apply) { + + cb_backend *cb = inst->backend_type; + Slapi_PBlock *default_pb; + Slapi_Entry **default_entries = NULL; + Slapi_Entry *default_conf=NULL; + int default_res, rc; + char defaultDn[CB_BUFSIZE]; + cb_backend_instance * current_inst; + + rc=LDAP_SUCCESS; + + if (apply) + current_inst=inst; + else + current_inst=cb_instance_alloc(cb,inst->inst_name,"cn=dummy"); + + /* set default configuration */ + cb_instance_config_set_default(current_inst); + + /* 2: Overwrite values present in the default instance config */ + + sprintf(defaultDn,"cn=default instance config,%s",cb->pluginDN); + + default_pb = slapi_pblock_new(); + slapi_search_internal_set_pb(default_pb, defaultDn, LDAP_SCOPE_BASE, + "objectclass=*", NULL, 0, NULL, NULL, cb->identity, 0); + slapi_search_internal_pb (default_pb); + slapi_pblock_get(default_pb, SLAPI_PLUGIN_INTOP_RESULT, &default_res); + if ( LDAP_SUCCESS == default_res ) { + slapi_pblock_get(default_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &default_entries); + if (default_entries && default_entries[0] ) { + + struct berval val; + struct berval *vals[2]; + vals[0] = &val; + vals[1] = NULL; + default_conf=default_entries[0]; + + /* hack: add a dummy url (mandatory) to avoid error */ + /* will be overwritten by the one in conf entry */ + val.bv_val = "ldap://localhost/"; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( default_conf, CB_CONFIG_HOSTURL, (struct berval **)vals ); + + rc=cb_instance_config_initialize(current_inst,default_conf,CB_CONFIG_PHASE_STARTUP,1); + } + } + slapi_free_search_results_internal(default_pb); + slapi_pblock_destroy(default_pb); + + if (rc == LDAP_SUCCESS) + rc=cb_instance_config_initialize(current_inst,conf,CB_CONFIG_PHASE_STARTUP,1); + + if (!apply) + cb_instance_free(current_inst); + + return rc; +} diff --git a/ldap/servers/plugins/chainingdb/cb_modify.c b/ldap/servers/plugins/chainingdb/cb_modify.c new file mode 100644 index 00000000..51aed6a3 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_modify.c @@ -0,0 +1,244 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +static void cb_remove_illegal_mods(cb_backend_instance * inst, LDAPMod **mods); + +/* + * Perform a modify operation + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chaining_back_modify ( Slapi_PBlock *pb ) +{ + + Slapi_Backend *be; + cb_backend_instance *cb; + LDAPControl **ctrls, **serverctrls; + int rc,parse_rc,msgid,i; + LDAP *ld=NULL; + char **referrals=NULL; + LDAPMod ** mods; + LDAPMessage * res; + char *dn,* matched_msg, *error_msg; + char *cnxerrbuf=NULL; + time_t endtime; + cb_outgoing_conn *cnx; + + if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) { + cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL ); + return -1; + } + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_MODIFY); + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + slapi_pblock_get( pb, SLAPI_MODIFY_TARGET, &dn ); + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"modify: target:<%s>\n",dn); + } + + + ctrls=serverctrls=NULL; + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &ctrls ); + + /* Check acls */ + + if ( cb->local_acl && !cb->associated_be_is_disabled ) { + char * errbuf=NULL; + Slapi_Entry *te = slapi_entry_alloc(); + slapi_entry_set_dn(te,slapi_ch_strdup(dn)); + rc = slapi_acl_check_mods( pb, te, mods, &errbuf); + slapi_entry_free(te); + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL ); + slapi_ch_free((void **)&errbuf); + return -1; + } + } + + + /* Grab a connection handle */ + if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL); + slapi_ch_free((void **)&cnxerrbuf); + /* ping the farm. If the farm is unreachable, we increment the counter */ + cb_ping_farm(cb,NULL,0); + return -1; + } + + /* Control management */ + if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + /* Don't free mods here: are freed at the do_modify level */ + return -1; + } + + if ( slapi_op_abandoned( pb )) { + cb_release_op_connection(cb->pool,ld,0); + /* Don't free mods here: are freed at the do_modify level */ + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return -1; + } + + /* Remove illegal attributes from the mods */ + cb_remove_illegal_mods(cb,mods); + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + /* Send LDAP operation to the remote host */ + rc = ldap_modify_ext( ld, dn, mods, ctrls, NULL, &msgid ); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return -1; + } + + while ( 1 ) { + + if (cb_check_forward_abandon(cb,pb,ld,msgid)) { + /* connection handle released */ + return -1; + } + + rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res ); + switch ( rc ) { + case -1: + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return -1; + case 0: + if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) { + + /* does not respond. give up and return a*/ + /* error to the client. */ + + /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL);*/ + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return -1; + } +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + default: + + matched_msg=error_msg=NULL; + serverctrls=NULL; + parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, + &error_msg, &referrals, &serverctrls, 1 ); + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(parse_rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free referrals */ + if (referrals) + charray_free(referrals); + return -1; + } + + if ( rc != LDAP_SUCCESS ) { + struct berval ** refs = referrals2berval(referrals); + cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (refs) + ber_bvecfree(refs); + if (referrals) + charray_free(referrals); + if (serverctrls) + ldap_controls_free(serverctrls); + return -1; + } + + cb_release_op_connection(cb->pool,ld,0); + + /* Add control response sent by the farm server */ + + for (i=0; serverctrls && serverctrls[i];i++) + slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]); + /* SLAPI_ADD_RESCONTROL dups controls */ + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free matched_msg, error_msg, and referrals if necessary */ + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (referrals) + charray_free(referrals); + cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL ); + return 0; + } + } + + /* Never reached */ + /* return 0; */ +} + +/* Function removes mods which are not allowed over-the-wire */ +static void +cb_remove_illegal_mods(cb_backend_instance *inst, LDAPMod **mods) +{ + int i, j; + LDAPMod *tmp; + + if ( inst->illegal_attributes != NULL ) { /* Unlikely to happen */ + + PR_RWLock_Wlock(inst->rwl_config_lock); + + for (j=0; inst->illegal_attributes[j]; j++) { + for ( i = 0; mods[i] != NULL; i++ ) { + if (slapi_attr_types_equivalent(inst->illegal_attributes[j],mods[i]->mod_type)) { + tmp = mods[i]; + for ( j = i; mods[j] != NULL; j++ ) { + mods[j] = mods[j + 1]; + } + slapi_ch_free( (void**)&(tmp->mod_type) ); + if ( tmp->mod_bvalues != NULL ) { + ber_bvecfree( tmp->mod_bvalues ); + } + slapi_ch_free( (void**)&tmp ); + i--; + } + } + } + + PR_RWLock_Unlock(inst->rwl_config_lock); + } +} diff --git a/ldap/servers/plugins/chainingdb/cb_modrdn.c b/ldap/servers/plugins/chainingdb/cb_modrdn.c new file mode 100644 index 00000000..e6b5dadb --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_modrdn.c @@ -0,0 +1,239 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Perform a modrdn operation + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chaining_back_modrdn ( Slapi_PBlock *pb ) +{ + + Slapi_Backend * be; + cb_backend_instance *cb; + LDAPControl **ctrls, **serverctrls; + int rc,parse_rc,msgid,i; + LDAP *ld=NULL; + char **referrals=NULL; + LDAPMessage * res; + char * matched_msg, *error_msg,* pdn, *newdn, *dn; + int deleteoldrdn=0; + char * newsuperior, *newrdn; + char * cnxerrbuf=NULL; + time_t endtime; + cb_outgoing_conn * cnx; + + if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) { + cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL ); + return -1; + } + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_MODRDN); + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + newsuperior=newdn=newrdn=dn=NULL; + slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &dn ); + slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ); + slapi_pblock_get( pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperior ); + slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn ); + + /* + * Construct the new dn + */ + + dn = slapi_dn_normalize_case(dn); + if ( (pdn = slapi_dn_parent( dn )) != NULL ) { + /* parent + rdn + separator(s) + null */ + newdn = (char *) slapi_ch_malloc( strlen( pdn ) + strlen( newrdn ) + 3 ); + strcpy( newdn, newrdn ); + strcat( newdn, "," ); + strcat( newdn, pdn ); + + slapi_ch_free((void **)&pdn ); + + } else { + newdn = slapi_ch_strdup( newrdn ); + } + + /* + * Make sure the current backend is managing + * the new dn. We won't support moving entries + * across backend this way. + * Done in the front-end. + */ + + slapi_ch_free((void **)&newdn); + + if (cb->local_acl && !cb->associated_be_is_disabled) { + /* + * Check local acls + * Keep in mind We don't have the entry for acl evaluation + */ + + char * errbuf=NULL; + Slapi_Entry *te = slapi_entry_alloc(); + slapi_entry_set_dn(te,slapi_ch_strdup(dn)); + rc = cb_access_allowed (pb, te, NULL, NULL, SLAPI_ACL_WRITE,&errbuf); + slapi_entry_free(te); + + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL ); + slapi_ch_free((void **)&errbuf); + return -1; + } + } + + /* + * Grab a connection handle + */ + + if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL); + slapi_ch_free((void **)&cnxerrbuf); + /* ping the farm. If the farm is unreachable, we increment the counter */ + cb_ping_farm(cb,NULL,0); + return -1; + } + + /* + * Control management + */ + + if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return -1; + } + + + if ( slapi_op_abandoned( pb )) { + cb_release_op_connection(cb->pool,ld,0); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return -1; + } + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + /* + * Send LDAP operation to the remote host + */ + + rc = ldap_rename ( ld,dn,newrdn,newsuperior,deleteoldrdn,ctrls,NULL,&msgid); + + if ( NULL != ctrls) + ldap_controls_free(ctrls); + if ( rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + return -1; + } + + while ( 1 ) { + + if (cb_check_forward_abandon(cb,pb,ld,msgid)) { + return -1; + } + + rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res ); + switch ( rc ) { + case -1: + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return -1; + case 0: + if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) { + + /* does not respond. give up and return a*/ + /* error to the client. */ + + /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL);*/ + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + return -1; + } +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + default: + matched_msg=error_msg=NULL; + parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, + &error_msg, &referrals, &serverctrls, 1 ); + + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(parse_rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free referrals */ + if (referrals) + charray_free(referrals); + return -1; + } + + if ( rc != LDAP_SUCCESS ) { + struct berval ** refs = referrals2berval(referrals); + + cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (refs) + ber_bvecfree(refs); + if (referrals) + charray_free(referrals); + if (serverctrls) + ldap_controls_free(serverctrls); + return -1; + } + + cb_release_op_connection(cb->pool,ld,0); + + /* Add control response sent by the farm server */ + + for (i=0; serverctrls && serverctrls[i];i++) + slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]); + if (serverctrls) + ldap_controls_free(serverctrls); + /* jarnou: free matched_msg, error_msg, and referrals if necessary */ + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (referrals) + charray_free(referrals); + cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL ); + return 0; + } + } + + /* Never reached */ + /* return 0; */ +} diff --git a/ldap/servers/plugins/chainingdb/cb_monitor.c b/ldap/servers/plugins/chainingdb/cb_monitor.c new file mode 100644 index 00000000..11ef3bda --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_monitor.c @@ -0,0 +1,228 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +extern cb_instance_config_info cb_the_instance_config[]; +/* +** Chaining backend instance monitor function +** This function wraps up backend specific monitoring information +** and return it to the client as an LDAP entry. +** This function is usually called upon receipt of the monitor +** dn for this backend +** +** Monitor information: +** +** Database misc +** database +** +** Number of hits for each operation +** addopcount +** modifyopcount +** deleteopcount +** modrdnopcount +** compareopcount +** searchsubtreeopcount +** searchonelevelopcount +** searchbaseopcount +** bindopcount +** unbindopcount +** abandonopcount +** +** Outgoing connections +** outgoingopconnections +** outgoingbindconnections +** +*/ + +int +cb_search_monitor_callback(Slapi_PBlock * pb, Slapi_Entry * e, Slapi_Entry * entryAfter, int * returnCode, char * returnText, void * arg) +{ + + char buf[CB_BUFSIZE]; + struct berval val; + struct berval *vals[2]; + int deletecount,addcount,modifycount,modrdncount,searchbasecount,searchonelevelcount; + int searchsubtreecount,abandoncount,bindcount,unbindcount,comparecount; + int outgoingconn, outgoingbindconn; + cb_backend_instance *inst = (cb_backend_instance *)arg; + + /* First make sure the backend instance is configured */ + /* If not, don't return anything */ + + PR_RWLock_Rlock(inst->rwl_config_lock); + if (!inst->isconfigured) { + *returnCode= LDAP_NO_SUCH_OBJECT; + PR_RWLock_Unlock(inst->rwl_config_lock); + return SLAPI_DSE_CALLBACK_ERROR; + } + PR_RWLock_Unlock(inst->rwl_config_lock); + + vals[0] = &val; + vals[1] = NULL; + + slapi_lock_mutex(inst->monitor.mutex); + + addcount =inst->monitor.addcount; + deletecount =inst->monitor.deletecount; + modifycount =inst->monitor.modifycount; + modrdncount =inst->monitor.modrdncount; + searchbasecount =inst->monitor.searchbasecount; + searchonelevelcount =inst->monitor.searchonelevelcount; + searchsubtreecount =inst->monitor.searchsubtreecount; + abandoncount =inst->monitor.abandoncount; + bindcount =inst->monitor.bindcount; + unbindcount =inst->monitor.unbindcount; + comparecount =inst->monitor.comparecount; + + slapi_unlock_mutex(inst->monitor.mutex); + + /* + ** Get connection information + */ + + slapi_lock_mutex(inst->pool->conn.conn_list_mutex); + outgoingconn= inst->pool->conn.conn_list_count; + slapi_unlock_mutex(inst->pool->conn.conn_list_mutex); + + slapi_lock_mutex(inst->bind_pool->conn.conn_list_mutex); + outgoingbindconn= inst->bind_pool->conn.conn_list_count; + slapi_unlock_mutex(inst->bind_pool->conn.conn_list_mutex); + + sprintf( buf, "%lu", addcount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_ADDCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", deletecount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_DELETECOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", modifycount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_MODIFYCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", modrdncount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_MODRDNCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", searchbasecount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_SEARCHBASECOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", searchonelevelcount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_SEARCHONELEVELCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", searchsubtreecount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_SEARCHSUBTREECOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", abandoncount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_ABANDONCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", bindcount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_BINDCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", unbindcount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_UNBINDCOUNT, ( struct berval **)vals ); + + sprintf( buf, "%lu", comparecount ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_COMPARECOUNT, ( struct berval **)vals ); + + sprintf( buf, "%d", outgoingconn ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_OUTGOINGCONN, ( struct berval **)vals ); + + sprintf( buf, "%d", outgoingbindconn ); + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, CB_MONITOR_OUTGOINGBINDCOUNT, ( struct berval **)vals ); + + *returnCode= LDAP_SUCCESS; + return(SLAPI_DSE_CALLBACK_OK); +} + +void +cb_update_monitor_info(Slapi_PBlock * pb, cb_backend_instance * inst,int op) +{ + + int scope; + + slapi_lock_mutex(inst->monitor.mutex); + switch (op) { + case SLAPI_OPERATION_ADD: + inst->monitor.addcount++; + break; + case SLAPI_OPERATION_MODIFY: + inst->monitor.modifycount++; + break; + case SLAPI_OPERATION_DELETE: + inst->monitor.deletecount++; + break; + case SLAPI_OPERATION_MODRDN: +/** case SLAPI_OPERATION_MODDN: **/ + inst->monitor.modrdncount++; + break; + case SLAPI_OPERATION_COMPARE: + inst->monitor.comparecount++; + break; + case SLAPI_OPERATION_ABANDON: + inst->monitor.abandoncount++; + break; + case SLAPI_OPERATION_BIND: + inst->monitor.bindcount++; + break; + case SLAPI_OPERATION_UNBIND: + inst->monitor.unbindcount++; + break; + case SLAPI_OPERATION_SEARCH: + slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope ); + if ( LDAP_SCOPE_BASE == scope ) + inst->monitor.searchbasecount++; + else + if ( LDAP_SCOPE_ONELEVEL == scope ) + inst->monitor.searchonelevelcount++; + else + inst->monitor.searchsubtreecount++; + break; + default: + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"cb_update_monitor_info: invalid op type <%d>\n",op); + } + slapi_unlock_mutex(inst->monitor.mutex); +} + + +int +cb_delete_monitor_callback(Slapi_PBlock * pb, Slapi_Entry * e, Slapi_Entry * entryAfter, int * returnCode, char * returnText, void * arg) +{ + + cb_backend_instance *inst = (cb_backend_instance *)arg; + + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE, + "(objectclass=*)", cb_search_monitor_callback); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE, + "(objectclass=*)", cb_dont_allow_that); + slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE, + "(objectclass=*)", cb_delete_monitor_callback); + + *returnCode= LDAP_SUCCESS; + return(SLAPI_DSE_CALLBACK_OK); +} diff --git a/ldap/servers/plugins/chainingdb/cb_schema.c b/ldap/servers/plugins/chainingdb/cb_schema.c new file mode 100644 index 00000000..2337b977 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_schema.c @@ -0,0 +1,45 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +void cb_eliminate_illegal_attributes(cb_backend_instance * inst, Slapi_Entry * e) { + + /* get rid of illegal attributes before sending op to the */ + /* farm server. (Add) */ + + int rc,j; + Slapi_Attr *attr=NULL; + char *tobefreed=NULL; + + if (inst->illegal_attributes != NULL ) { /* Unlikely to happen */ + + PR_RWLock_Wlock(inst->rwl_config_lock); + + for (j=0; inst->illegal_attributes[j]; j++) { + char * aType=NULL; + rc=slapi_entry_first_attr(e,&attr); + while (rc==0) { + if (tobefreed) { + slapi_entry_attr_delete( e, tobefreed); + tobefreed=NULL; + } + slapi_attr_get_type(attr,&aType); + if (aType && slapi_attr_types_equivalent(inst->illegal_attributes[j],aType)) { + tobefreed=aType; + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "attribute <%s> not forwarded.\n",aType); + } + rc = slapi_entry_next_attr(e, attr, &attr); + } + if (tobefreed) { + slapi_entry_attr_delete( e, tobefreed); + tobefreed=NULL; + } + } + + PR_RWLock_Unlock(inst->rwl_config_lock); + } +} diff --git a/ldap/servers/plugins/chainingdb/cb_search.c b/ldap/servers/plugins/chainingdb/cb_search.c new file mode 100644 index 00000000..d9cf6ef7 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_search.c @@ -0,0 +1,698 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* + * Build a candidate list for this backentry and scope. + * Could be a BASE, ONELEVEL, or SUBTREE search. + * + * Returns: + * 0 - success + * <0 - fail + * + */ + +int +chainingdb_build_candidate_list ( Slapi_PBlock *pb ) +{ + + Slapi_Backend * be; + Slapi_Operation * op; + char *target, *filter; + int scope,attrsonly,sizelimit,timelimit,rc,searchreferral; + char **attrs=NULL; + LDAPControl **controls=NULL; + LDAPControl **ctrls=NULL; + LDAP *ld=NULL; + cb_backend_instance *cb = NULL; + cb_searchContext *ctx=NULL; + struct timeval timeout; + time_t optime; + int doit,parse_rc; + LDAPMessage *res=NULL; + char *matched_msg,*error_msg; + LDAPControl **serverctrls=NULL; + char **referrals=NULL; + char *cnxerrbuf=NULL; + time_t endbefore=0; + time_t endtime; + cb_outgoing_conn *cnx; + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + slapi_pblock_get( pb, SLAPI_SEARCH_STRFILTER, &filter ); + slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope ); + slapi_pblock_get( pb, SLAPI_OPINITIATED_TIME, &optime ); + slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &target ); + + if ( LDAP_SUCCESS != (parse_rc=cb_forward_operation(pb) )) { + + /* Don't return errors */ + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "local search: base:<%s> scope:<%s> filter:<%s>\n",target, + scope==LDAP_SCOPE_SUBTREE?"SUBTREE":scope==LDAP_SCOPE_ONELEVEL ? "ONE-LEVEL" : "BASE" , filter); + } + + ctx = (cb_searchContext *)slapi_ch_calloc(1,sizeof(cb_searchContext)); + ctx->type = CB_SEARCHCONTEXT_ENTRY; + ctx->data=NULL; + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx); + return 0; + } + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_SEARCH); + + /* Check wether the chaining BE is available or not */ + if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){ + return -1; + } + + if (cb_debug_on()) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "chained search: base:<%s> scope:<%s> filter:<%s>\n",target, + scope==LDAP_SCOPE_SUBTREE?"SUBTREE":scope==LDAP_SCOPE_ONELEVEL ? "ONE-LEVEL" : "BASE" , filter); + } + + slapi_pblock_get( pb, SLAPI_SEARCH_ATTRS, &attrs ); + slapi_pblock_get( pb, SLAPI_SEARCH_ATTRSONLY, &attrsonly ); + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls ); + slapi_pblock_get( pb, SLAPI_SEARCH_TIMELIMIT, &timelimit ); + slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &sizelimit ); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + + + if ((scope != LDAP_SCOPE_BASE) && (scope != LDAP_SCOPE_ONELEVEL) && (scope != LDAP_SCOPE_SUBTREE)) { + cb_send_ldap_result( pb, LDAP_PROTOCOL_ERROR, NULL, "Bad scope", 0, NULL ); + return 1; + } + + searchreferral=cb->searchreferral; + + if (( scope != LDAP_SCOPE_BASE ) && ( searchreferral )) { + + int i; + struct berval bv,*bvals[2]; + Slapi_Entry ** aciArray=(Slapi_Entry **) slapi_ch_malloc(2*sizeof(Slapi_Entry *)); + Slapi_Entry *anEntry = slapi_entry_alloc(); + + slapi_entry_set_dn(anEntry,slapi_ch_strdup(target)); + + bvals[1]=NULL; + bvals[0]=&bv; + bv.bv_val="referral"; + bv.bv_len=strlen(bv.bv_val); + slapi_entry_add_values( anEntry, "objectclass", bvals); + + PR_RWLock_Rlock(cb->rwl_config_lock); + for (i=0; cb->url_array && cb->url_array[i]; i++) { + char * anUrl= slapi_ch_calloc(1,strlen(cb->url_array[i])+strlen(target)+1); + sprintf(anUrl,"%s%s",cb->url_array[i],target); + bv.bv_val=anUrl; + bv.bv_len=strlen(bv.bv_val); + slapi_entry_attr_merge( anEntry, "ref", bvals); + slapi_ch_free((void **)&anUrl); + } + PR_RWLock_Unlock(cb->rwl_config_lock); + + aciArray[0]=anEntry; + aciArray[1]=NULL; + + ctx = (cb_searchContext *)slapi_ch_calloc(1,sizeof(cb_searchContext)); + ctx->type = CB_SEARCHCONTEXT_ENTRY; + ctx->data=aciArray; + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx); + return 0; + } + + /* + ** Time limit management. + ** Make sure the operation has not expired + */ + + if ( timelimit == -1 ) { + timeout.tv_sec = timeout.tv_usec = 0; + } else { + time_t now=current_time(); + endbefore=optime + timelimit; + if (now >= endbefore) { + cb_send_ldap_result( pb, LDAP_TIMELIMIT_EXCEEDED, NULL,NULL, 0, NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY, NULL ); + return 1; + } + timeout.tv_sec=timelimit-(now-optime); + timeout.tv_usec=0; + } + + /* Operational attribute support for internal searches: */ + /* The front-end relies on the fact that operational attributes */ + /* are returned along with standard attrs when the attr list is */ + /* NULL. To make it work, we need to explicitly request for all*/ + /* possible operational attrs. Too bad. */ + + if ( (attrs == NULL) && operation_is_flag_set(op, OP_FLAG_INTERNAL) ) { + attrs = cb->every_attribute; + + } + else + { + int i; + if ( attrs != NULL ) + { + for ( i = 0; attrs[i] != NULL; i++ ) { + if ( strcasecmp( "nsrole", attrs[i] ) == 0 ) + { + attrs = cb->every_attribute; + break; + } + } + } + } + + /* Grab a connection handle */ + + if ( LDAP_SUCCESS != (rc = cb_get_connection(cb->pool,&ld,&cnx,&timeout,&cnxerrbuf))) { + if (rc == LDAP_TIMELIMIT_EXCEEDED) + cb_send_ldap_result( pb, rc, NULL,cnxerrbuf, 0, NULL); + else + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,cnxerrbuf, 0, NULL); + + slapi_ch_free((void **)&cnxerrbuf); + /* ping the farm. If the farm is unreachable, we increment the counter */ + cb_ping_farm(cb,NULL,0); + return 1; + } + + /* + * Control management + */ + + if ( LDAP_SUCCESS != (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH ))) { + cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL); + cb_release_op_connection(cb->pool,ld,0); + return 1; + } + + if ( slapi_op_abandoned( pb )) { + cb_release_op_connection(cb->pool,ld,0); + if ( NULL != ctrls) + ldap_controls_free(ctrls); + return 1; + } + + ctx = (cb_searchContext *) slapi_ch_calloc(1,sizeof(cb_searchContext)); + + /* + ** We need to store the connection handle in the search context + ** to make sure we reuse it in the next_entry iteration + ** Indeed, if another thread on this connection detects a problem + ** on this connection, it may reallocate a new connection and + ** a call to get_connection may return a new cnx. Too bad. + */ + + ctx->ld=ld; + ctx->cnx=cnx; + + /* for some reasons, it is an error to pass in a zero'd timeval */ + /* to ldap_search_ext() */ + if ((timeout.tv_sec==0) && (timeout.tv_usec==0)) + timeout.tv_sec=timeout.tv_usec=-1; + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + rc=ldap_search_ext(ld ,target,scope,filter,attrs,attrsonly, + ctrls, NULL, &timeout,sizelimit, &(ctx->msgid) ); + + if ( NULL != ctrls) + ldap_controls_free(ctrls); + + if ( LDAP_SUCCESS != rc ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **) &ctx); + return 1; + } + + /* + ** Need to get the very first result to handle + ** errors properly, especially no search base. + */ + + doit=1; + while (doit) { + + if (cb_check_forward_abandon(cb,pb,ctx->ld,ctx->msgid)) { + slapi_ch_free((void **) &ctx); + return 1; + } + + rc=ldap_result(ld,ctx->msgid,LDAP_MSG_ONE,&cb->abandon_timeout,&res); + switch ( rc ) { + case -1: + /* An error occurred. return now */ + rc = ldap_get_lderrno(ld,NULL,NULL); + /* tuck away some errors in a OPERATION_ERROR */ + if (CB_LDAP_CONN_ERROR(rc)) { + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string( rc ), 0, NULL); + } else { + cb_send_ldap_result(pb,rc, NULL, NULL,0,NULL); + } + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + slapi_ch_free((void **)&ctx); + return 1; + case 0: + + /* Local timeout management */ + if (timelimit != -1) { + if (current_time() > endbefore) { + + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Local timeout expiration\n"); + + cb_send_ldap_result(pb,LDAP_TIMELIMIT_EXCEEDED, + NULL,NULL, 0, NULL); + /* Force connection close */ + cb_release_op_connection(cb->pool,ld,1); + if (res) + ldap_msgfree(res); + slapi_ch_free((void **)&ctx); + return 1; + } + } + /* heart-beat management */ + if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) { + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc)); + if (res) + ldap_msgfree(res); + slapi_ch_free((void **)&ctx); + return 1; + } + +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + /* Some results received */ + /* don't parse result here */ + ctx->pending_result=res; + ctx->pending_result_type=rc; + doit=0; + break; + case LDAP_RES_SEARCH_RESULT: + matched_msg=NULL; + error_msg=NULL; + referrals=NULL; + serverctrls=NULL; + parse_rc=ldap_parse_result(ld,res,&rc,&matched_msg, + &error_msg,&referrals, &serverctrls, 0 ); + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result(pb,parse_rc, + matched_msg,error_msg,0,NULL); + rc=-1; + } else + if ( rc != LDAP_SUCCESS ) { + ldap_get_lderrno( ctx->ld, &matched_msg, &error_msg ); + cb_send_ldap_result( pb, rc, matched_msg, + error_msg,0,NULL); + /* BEWARE: matched_msg and error_msg points */ + /* to ld fields. */ + matched_msg=NULL; + error_msg=NULL; + rc=-1; + } + + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (serverctrls) + ldap_controls_free(serverctrls); + if (referrals) + charray_free(referrals); + + if (rc!=LDAP_SUCCESS) { + cb_release_op_connection(cb->pool,ld, + CB_LDAP_CONN_ERROR(rc)); + ldap_msgfree(res); + slapi_ch_free((void **)&ctx); + return -1; + } + + /* Store the msg in the ctx */ + /* Parsed in iterate. */ + + ctx->pending_result=res; + ctx->pending_result_type=LDAP_RES_SEARCH_RESULT; + doit=0; + } + } + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx); + return 0; +} + +/* + * Return the next entry in the result set. The entry is returned + * in the pblock. + * Returns 0 normally. If -1 is returned, it means that some + * exceptional condition, e.g. timelimit exceeded has occurred, + * and this routine has sent a result to the client. If zero + * is returned and no entry is available in the PBlock, then + * we've iterated through all the entries. + */ + +int +chainingdb_next_search_entry ( Slapi_PBlock *pb ) +{ + + char *target; + int sizelimit,timelimit, rc, parse_rc, optime,i,retcode, attrsonly; + LDAPMessage *res=NULL; + char *matched_msg,*error_msg; + cb_searchContext *ctx=NULL; + Slapi_Entry *entry; + LDAPControl **serverctrls=NULL; + char **referrals=NULL; + cb_backend_instance * cb=NULL; + Slapi_Backend * be; + time_t endtime; + + matched_msg=error_msg=NULL; + + slapi_pblock_get( pb, SLAPI_SEARCH_RESULT_SET, &ctx ); + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + slapi_pblock_get( pb, SLAPI_SEARCH_TIMELIMIT, &timelimit ); + slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &sizelimit ); + slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &target ); + slapi_pblock_get( pb, SLAPI_OPINITIATED_TIME, &optime ); + slapi_pblock_get( pb, SLAPI_SEARCH_ATTRSONLY, &attrsonly ); + + cb = cb_get_instance(be); + + if ( NULL == ctx ) { + /* End of local search */ + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Unexpected NULL ctx in chainingdb_next_search_entry\n"); + return 0; + } + + if ( NULL != ctx->tobefreed ) { + slapi_entry_free(ctx->tobefreed); + ctx->tobefreed=NULL; + } + + if ( ctx->type == CB_SEARCHCONTEXT_ENTRY ) { + + int n; + Slapi_Entry ** ptr; + if ( (timelimit != -1) && (timelimit != 0)) { + time_t now=current_time(); + + if (now > (optime + timelimit)) { + cb_send_ldap_result( pb, LDAP_TIMELIMIT_EXCEEDED, NULL,NULL, 0, NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL ); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + + for ( n = 0, ptr=(Slapi_Entry **)ctx->data; ptr != NULL && ptr[n] != NULL; n++ ) { + slapi_entry_free(ptr[n]); + } + if (ctx->data) + slapi_ch_free((void **)&ctx->data); + slapi_ch_free((void **)&ctx); + return -1; + } + } + + /* + ** Return the Slapi_Entry of the result set one + ** by one + */ + + for ( n = 0, ptr=(Slapi_Entry **)ctx->data; ptr != NULL && ptr[n] != NULL; n++ ); + if ( n != 0) { + Slapi_Entry * anEntry=ptr[n-1]; + ptr[n-1]=NULL; + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,anEntry); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx); + cb_set_acl_policy(pb); + ctx->tobefreed=anEntry; + } else { + slapi_ch_free((void **) &ctx); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL ); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + } + return 0; + } + + /* + * Grab a connection handle. Should be the same as the one + * used in the build_candidate list. To be certain of that, grab it from + * the context. + */ + + /* Poll the server for the results of the search operation. + * Passing LDAP_MSG_ONE indicates that you want to receive + * the entries one at a time, as they come in. If the next + * entry that you retrieve is NULL, there are no more entries. + */ + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + while (1) { + + if (cb_check_forward_abandon(cb,pb,ctx->ld,ctx->msgid)) { + /* cnx handle released */ + if (ctx->pending_result) + ldap_msgfree(ctx->pending_result); + slapi_ch_free((void **) &ctx); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL ); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + return -1; + } + + /* Check for time limit done by the remote farm server */ + /* Check for size limit done by the remote farm server */ + + /* Use pending msg if one is available */ + if (ctx->pending_result) { + res=ctx->pending_result; + rc=ctx->pending_result_type; + ctx->pending_result=NULL; + } else { + + + rc=ldap_result(ctx->ld,ctx->msgid, + LDAP_MSG_ONE, &cb->abandon_timeout, &res ); + } + + /* The server can return three types of results back to the client, + * and the return value of ldap_result() indicates the result type: + * LDAP_RES_SEARCH_ENTRY identifies an entry found by the search, + * LDAP_RES_SEARCH_REFERENCE identifies a search reference returned + * by the server, and LDAP_RES_SEARCH_RESULT is the last result + * sent from the server to the client after the operation completes. + * We need to check for each of these types of results. + */ + + switch ( rc ) { + case -1: + + /* An error occurred. */ + rc = ldap_get_lderrno( ctx->ld, NULL, NULL ); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, ldap_err2string( rc ), 0, NULL); + + if (res) + ldap_msgfree(res); + cb_release_op_connection(cb->pool,ctx->ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **)&ctx); + return -1; + case 0: + /* heart-beat management */ + if ((rc=cb_ping_farm(cb,ctx->cnx,endtime)) != LDAP_SUCCESS) { + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + + cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string(rc), 0, NULL); + + if (res) + ldap_msgfree(res); + cb_release_op_connection(cb->pool,ctx->ld,CB_LDAP_CONN_ERROR(rc)); + slapi_ch_free((void **)&ctx); + return -1; + } +#ifdef CB_YIELD + DS_Sleep(PR_INTERVAL_NO_WAIT); +#endif + break; + + case LDAP_RES_SEARCH_ENTRY: + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + /* The server sent one of the entries found by the search */ + if ((entry = cb_LDAPMessage2Entry(ctx->ld,res,attrsonly)) == NULL) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"Invalid entry received.\n"); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, NULL , 0, NULL); + + ldap_msgfree(res); + cb_release_op_connection(cb->pool,ctx->ld,0); + slapi_ch_free((void **)&ctx); + return -1; + } + + ctx->tobefreed=entry; + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,entry); + cb_set_acl_policy(pb); + ldap_msgfree(res); + return 0; + + case LDAP_RES_SEARCH_REFERENCE: + + /* The server sent a search reference encountered during the + * search operation. + */ + + /* heart-beat management */ + if (cb->max_idle_time>0) + endtime=current_time() + cb->max_idle_time; + + parse_rc = ldap_parse_reference( ctx->ld, res, &referrals, NULL, 1 ); + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, + ldap_err2string( parse_rc ), 0, NULL); + cb_release_op_connection(cb->pool,ctx->ld,CB_LDAP_CONN_ERROR(parse_rc)); + slapi_ch_free((void **)&ctx); + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + return -1; + } + + /* + ** build a dummy entry on the fly with a ref attribute + */ + + { + + struct berval bv; + int i; + struct berval *bvals[2]; + Slapi_Entry *anEntry = slapi_entry_alloc(); + slapi_entry_set_dn(anEntry,slapi_ch_strdup(target)); + + bvals[1]=NULL; + bvals[0]=&bv; + + bv.bv_val="referral"; + bv.bv_len=strlen(bv.bv_val); + slapi_entry_add_values( anEntry, "objectclass", bvals); + + for (i=0;referrals[i] != NULL; i++) { + bv.bv_val=referrals[i]; + bv.bv_len=strlen(bv.bv_val); + slapi_entry_add_values( anEntry, "ref", bvals); + } + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,anEntry); + cb_set_acl_policy(pb); + } + + if (referrals != NULL) { + ldap_value_free( referrals ); + } + + return 0; + + case LDAP_RES_SEARCH_RESULT: + + /* Parse the final result received from the server. Note the last + * argument is a non-zero value, which indicates that the + * LDAPMessage structure will be freed when done. + */ + + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL); + slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL); + + parse_rc = ldap_parse_result( ctx->ld, res, + &rc,&matched_msg,&error_msg, &referrals, &serverctrls, 1 ); + if ( parse_rc != LDAP_SUCCESS ) { + cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, matched_msg, + ldap_err2string( parse_rc ), 0, NULL); + + retcode=-1; + } else + if ( rc != LDAP_SUCCESS ) { + ldap_get_lderrno( ctx->ld, &matched_msg, &error_msg ); + cb_send_ldap_result( pb, rc, matched_msg, NULL, 0, NULL); + + /* BEWARE: Don't free matched_msg && error_msg */ + /* Points to the ld fields */ + matched_msg=NULL; + error_msg=NULL; + retcode=-1; + } else { + /* Add control response sent by the farm server */ + for (i=0; serverctrls && serverctrls[i];i++) + slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]); + retcode=0; + } + + if (serverctrls) + ldap_controls_free(serverctrls); + slapi_ch_free((void **)&matched_msg); + slapi_ch_free((void **)&error_msg); + if (referrals) + charray_free(referrals); + + cb_release_op_connection(cb->pool,ctx->ld,0); + slapi_ch_free((void **)&ctx); + return retcode; + + default: + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "chainingdb_next_search_entry:default case.\n"); + + } + } + + /* Not reached */ + /* return 0; */ +} + +int +chaining_back_entry_release ( Slapi_PBlock *pb ) { + slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, "chaining_back_entry_release\n"); + return 0; +} + diff --git a/ldap/servers/plugins/chainingdb/cb_size.c b/ldap/servers/plugins/chainingdb/cb_size.c new file mode 100644 index 00000000..a6f1fb20 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_size.c @@ -0,0 +1,22 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +int +cb_db_size( Slapi_PBlock *pb ) +{ + + /* + ** Return the size in byte of the local database storage + ** Size is 0 for a chaining backend + */ + + unsigned int size=0; + + slapi_pblock_set( pb, SLAPI_DBSIZE, &size ); + return 0; +} + diff --git a/ldap/servers/plugins/chainingdb/cb_start.c b/ldap/servers/plugins/chainingdb/cb_start.c new file mode 100644 index 00000000..c4a4538b --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_start.c @@ -0,0 +1,43 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +int +chainingdb_start ( Slapi_PBlock *pb ) { + + cb_backend * cb; + + slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cb ); + + if (cb->started) { + /* We may be called multiple times due to */ + /* plugin dependency resolution */ + return 0; + } + + /* + ** Reads in any configuration information held in the dse for the + ** chaining plugin. Create dse entries used to configure the + ** chaining plugin if they don't exist. Registers plugins to maintain + ** those dse entries. + */ + + cb_config_load_dse_info(pb); + + /* Register new LDAPv3 controls supported by the chaining backend */ + + slapi_register_supported_control( CB_LDAP_CONTROL_CHAIN_SERVER, + SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE + | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE + | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN ); + + /* register to be notified when backend state changes */ + slapi_register_backend_state_change((void *)cb_be_state_change, + cb_be_state_change); + + cb->started=1; + return 0; +} diff --git a/ldap/servers/plugins/chainingdb/cb_temp.c b/ldap/servers/plugins/chainingdb/cb_temp.c new file mode 100644 index 00000000..fc5407ff --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_temp.c @@ -0,0 +1,15 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* +** Temp wrappers until the appropriate functions +** are implemented in the slapi interface +*/ + +cb_backend_instance * cb_get_instance(Slapi_Backend * be) { + return (cb_backend_instance *)slapi_be_get_instance_info(be); +} diff --git a/ldap/servers/plugins/chainingdb/cb_test.c b/ldap/servers/plugins/chainingdb/cb_test.c new file mode 100644 index 00000000..cb075664 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_test.c @@ -0,0 +1,76 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +int cb_back_test( Slapi_PBlock *pb ) +{ + + Slapi_Backend * be; + cb_backend * cb; + cb_backend_instance * inst; + Slapi_PBlock * apb; + int res; + int rc=0; + const Slapi_DN *aSuffix=NULL; + const char * aSuffixString; + char * theTarget; + + + slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cb ); + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + inst = cb_get_instance(be); + apb = slapi_pblock_new(); + + /* + ** Try to open a connection to the farm server + ** Try to get a dummy entry BELOW the suffix managed + ** by the chaining backend, in case the local root is shared + ** across different backend + */ + + printf("Begin test instance %s.\n",inst->inst_name); + + aSuffix = slapi_be_getsuffix(be,0); + aSuffixString=slapi_sdn_get_dn(aSuffix); + /* Remove leading white spaces */ + for (aSuffixString; *aSuffixString==' ';aSuffixString++) {} + theTarget=slapi_ch_calloc(1,strlen(aSuffixString)+20); + sprintf(theTarget,"cn=test,%s",aSuffixString); + + /* XXXSD make sure chaining allowed for this plugin... */ + slapi_search_internal_set_pb (apb, theTarget, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL, NULL, + cb->identity,0 ); + slapi_search_internal_pb (apb); + + slapi_ch_free((void **)&theTarget); + + if ( NULL == apb ) { + printf("Can't contact farm server. (Internal error).\n"); + rc=-1; + goto the_end; + } + + slapi_pblock_get(apb, SLAPI_PLUGIN_INTOP_RESULT, &res); + /* OPERATIONS ERRORS also returned when bind failed */ + if (CB_LDAP_CONN_ERROR(res) || (res==LDAP_OPERATIONS_ERROR )) + { + printf("Can't contact the remote farm server %s. (%s).\n",inst->pool->hostname,ldap_err2string(res)); + rc=-1; + goto the_end; + } else { + printf("Connection established with the remote farm server %s.\n",inst->pool->hostname); + } + +the_end: + if (apb) + { + slapi_free_search_results_internal(apb); + slapi_pblock_destroy (apb); + } + + return rc; +} + diff --git a/ldap/servers/plugins/chainingdb/cb_unbind.c b/ldap/servers/plugins/chainingdb/cb_unbind.c new file mode 100644 index 00000000..1400ae1f --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_unbind.c @@ -0,0 +1,23 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +int +chainingdb_unbind( Slapi_PBlock *pb ) { + + /* Nothing to do because connection mgmt is stateless*/ + + Slapi_Backend * be; + cb_backend_instance * cb; + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + cb_update_monitor_info(pb,cb,SLAPI_OPERATION_UNBIND); + + cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL ); + return SLAPI_BIND_SUCCESS; +} diff --git a/ldap/servers/plugins/chainingdb/cb_utils.c b/ldap/servers/plugins/chainingdb/cb_utils.c new file mode 100644 index 00000000..b73effd0 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cb_utils.c @@ -0,0 +1,375 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +/* return the rootdn configured in the server */ + +char * cb_get_rootdn() { + + char * ret=slapi_get_rootdn(); + if (ret == NULL) + ret = slapi_ch_strdup(CB_DIRECTORY_MANAGER_DN); + if (ret) + slapi_dn_normalize_case(ret); /* UTF8-aware */ + return ret; +} + +void +cb_send_ldap_result(Slapi_PBlock *pb, int err, char *matched,char *text, int nentries, struct berval **urls ) +{ + cb_set_acl_policy(pb); + slapi_send_ldap_result( pb, err, matched, text, nentries ,urls); +} + +Slapi_Entry * cb_LDAPMessage2Entry(LDAP * ld, LDAPMessage * msg, int attrsonly) { + + Slapi_Entry * e = slapi_entry_alloc(); + char * a=NULL; + BerElement * ber=NULL; + + if ( e == NULL ) return NULL; + if (msg == NULL) { + slapi_entry_free(e); + return NULL; + } + + /* + * dn not allocated by slapi + * attribute type and values ARE allocated + */ + + slapi_entry_set_dn( e, ldap_get_dn( ld, msg ) ); + + for ( a = ldap_first_attribute( ld, msg, &ber ); a!=NULL; + a=ldap_next_attribute( ld, msg, ber ) ) { + if(attrsonly) { + slapi_entry_add_value(e, a, (Slapi_Value *)NULL); + ldap_memfree(a); + } else { + struct berval ** aVal = ldap_get_values_len( ld, msg, a); + slapi_entry_add_values( e, a, aVal); + + ldap_memfree(a); + ldap_value_free_len(aVal); + } + } + if ( NULL != ber ) + ldap_ber_free( ber, 0 ); + + return e; +} + +struct berval ** referrals2berval(char ** referrals) { + + int i; + struct berval ** val=NULL; + + if (referrals == NULL) + return NULL; + + for (i=0;referrals[i];i++) {} + + val = (struct berval **) slapi_ch_calloc(1,(i+1)*sizeof(struct berval *)); + + for (i=0;referrals[i];i++) { + + val[i]=(struct berval *) slapi_ch_malloc(sizeof(struct berval)); + val[i]->bv_len= strlen(referrals[i]); + val[i]->bv_val = slapi_ch_strdup(referrals[i]); + } + + return val; +} + +char * +cb_urlparse_err2string( int err ) +{ + char *s="internal error"; + + switch( err ) { + case 0: + s = "no error"; + break; + case LDAP_URL_ERR_NOTLDAP: + s = "missing ldap:// or ldaps://"; + break; + case LDAP_URL_ERR_NODN: + s = "missing suffix"; + break; + case LDAP_URL_ERR_BADSCOPE: + s = "invalid search scope"; + break; + case LDAP_URL_ERR_MEM: + s = "unable to allocate memory"; + break; + case LDAP_URL_ERR_PARAM: + s = "bad parameter to an LDAP URL function"; + break; + } + + return( s ); +} + +/* +** Return LDAP_SUCCESS if an internal operation needs to be forwarded to +** the farm server. We check chaining policy for internal operations +** We also check max hop count for loop detection for both internal +** and external operations +*/ + +int cb_forward_operation(Slapi_PBlock * pb ) { + + Slapi_Operation *op=NULL; + Slapi_Backend *be; + struct slapi_componentid *cid = NULL; + char *pname; + cb_backend_instance *cb; + int retcode; + LDAPControl **ctrls=NULL; + + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + + /* Loop detection */ + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &ctrls ); + + if ( NULL != ctrls ) { + struct berval *ctl_value=NULL; + int iscritical=0; + + if (slapi_control_present(ctrls,CB_LDAP_CONTROL_CHAIN_SERVER,&ctl_value,&iscritical)) { + + /* Decode control data */ + /* hop INTEGER (0 .. maxInt) */ + + int hops = 0; + int rc; + BerElement *ber = NULL; + + if ((ber = ber_init(ctl_value)) == NULL) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "cb_forward_operation: ber_init: Memory allocation failed"); + return LDAP_NO_MEMORY; + } + rc = ber_scanf(ber,"i",&hops); + if (LBER_ERROR == rc) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Loop detection control badly encoded."); + ber_free(ber,1); + return LDAP_LOOP_DETECT; + } + + if (hops <=0) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "Max hop count exceeded. Loop detected.\n"); + ber_free(ber,1); + return LDAP_LOOP_DETECT; + } + ber_free(ber,1); + } + } + + if ( !operation_is_flag_set(op, OP_FLAG_INTERNAL)) + return LDAP_SUCCESS; + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &cid); + if ( cid == NULL ) { + /* programming error in the front-end */ + slapi_log_error(SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, + "NULL component identity in an internal operation."); + return LDAP_UNWILLING_TO_PERFORM; + } + pname=cid->sci_component_name; + + if (cb_debug_on()) { + slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, + "internal op received from %s component \n",pname ? pname : "NULL"); + } + + /* First, make sure chaining is not denied */ + if (operation_is_flag_set(op, SLAPI_OP_FLAG_NEVER_CHAIN)) + return LDAP_UNWILLING_TO_PERFORM; + + /* unidentified caller. should not happen */ + if (pname == NULL) + return LDAP_UNWILLING_TO_PERFORM; + + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + cb = cb_get_instance(be); + + /* Local policy */ + PR_RWLock_Rlock(cb->rwl_config_lock); + if ( cb->chaining_components != NULL ) { + retcode=charray_inlist(cb->chaining_components,pname); + PR_RWLock_Unlock(cb->rwl_config_lock); + if ( retcode ) + retcode=LDAP_SUCCESS; + else + retcode=LDAP_UNWILLING_TO_PERFORM; + return retcode; + } + PR_RWLock_Unlock(cb->rwl_config_lock); + + /* Global policy */ + PR_RWLock_Rlock(cb->backend_type->config.rwl_config_lock); + retcode=charray_inlist(cb->backend_type->config.chaining_components,pname); + PR_RWLock_Unlock(cb->backend_type->config.rwl_config_lock); + + if ( retcode ) + retcode=LDAP_SUCCESS; + else + retcode=LDAP_UNWILLING_TO_PERFORM; + return retcode; +} + +/* better atol -- it understands a trailing multiplier k/m/g + * for example, "32k" will be returned as 32768 + */ +long cb_atol(char *str) +{ + long multiplier = 1; + char *x = str; + + /* find possible trailing k/m/g */ + while ((*x >= '0') && (*x <= '9')) x++; + switch (*x) { + case 'g': + case 'G': + multiplier *= 1024; + case 'm': + case 'M': + multiplier *= 1024; + case 'k': + case 'K': + multiplier *= 1024; + } + return (atol(str) * multiplier); +} + +int cb_atoi(char *str) +{ + return (int)cb_atol(str); +} + + +/* This function is used by the instance modify callback to add a new + * suffix. It return LDAP_SUCCESS on success. + */ +int cb_add_suffix(cb_backend_instance *inst, struct berval **bvals, int apply_mod, char *returntext) +{ + Slapi_DN *suffix; + int x; + + returntext[0] = '\0'; + for (x = 0; bvals[x]; x++) { + suffix=slapi_sdn_new_dn_byval(bvals[x]->bv_val); + if (!slapi_be_issuffix(inst->inst_be, suffix) && apply_mod) { + slapi_be_addsuffix(inst->inst_be, suffix); + } + slapi_sdn_free(&suffix); + } + + return LDAP_SUCCESS; +} + +static int debug_on=0; + +int cb_debug_on() +{ + return debug_on; +} + +void cb_set_debug(int on) { + debug_on=on; +} + +/* this function is called when state of a backend changes */ +/* The purpose of this function is to handle the associated_be_is_disabled + flag in the cb instance structure. The associated database is used to + perform local acl evaluations. The associated database can be + 1) The chaining backend is the backend of a sub suffix, and the + parent suffix has a local backend + 2) Entry distribution is being used to distribute write operations to + a chaining backend and other operations to a local backend + (e.g. a replication hub or consumer) + If the associated local backend is being initialized (import), it will be + disabled, and it will be impossible to evaluate local acls. In this case, + we still want to be able to chain operations to a farm server or another + database chain. But the current code will not allow cascading without + local acl evaluation (cb_controls.c). associated_be_is_disabled allows + us to relax that restriction while the associated backend is disabled +*/ +/* + The first thing we need to do is to determine what our associated backends + are. An associated backend is defined as a backend used by the same + suffix which uses this cb instance or a backend used by any + parent suffix of the suffix which uses this cb instance + + We first see if the be_name is for a local database. If not, then just return. + So for the given be_name, we find the suffix which uses it, then the mapping tree + entry for that suffix. Then + get cb instances used by the suffix and set associated_be_is_disabled + get cb instances used by sub suffixes of this suffix and + set associated_be_is_disabled +*/ +void +cb_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state) +{ + const Slapi_DN *tmpsdn; + Slapi_DN *the_be_suffix; + char *cookie = NULL; + Slapi_Backend *chainbe; + Slapi_Backend *the_be = slapi_be_select_by_instance_name(be_name); + + /* no backend? */ + if (!the_be) { + return; + } + + /* ignore chaining backends - associated backends must be local */ + if (slapi_be_is_flag_set(the_be, SLAPI_BE_FLAG_REMOTE_DATA)) { + return; + } + + /* get the suffix for the local backend */ + tmpsdn = slapi_be_getsuffix(the_be, 0); + if (!tmpsdn) { + return; + } else { + the_be_suffix = slapi_sdn_dup(tmpsdn); + } + + /* now, iterate through the chaining backends */ + for (chainbe = slapi_get_first_backend(&cookie); + chainbe; chainbe = slapi_get_next_backend(cookie)) { + /* only look at chaining backends */ + if (slapi_be_is_flag_set(chainbe, SLAPI_BE_FLAG_REMOTE_DATA)) { + /* get the suffix */ + const Slapi_DN *tmpcbsuf = slapi_be_getsuffix(chainbe, 0); + if (tmpcbsuf) { + /* make a copy - to be safe */ + Slapi_DN *cbsuffix = slapi_sdn_dup(tmpcbsuf); + /* if the suffixes are equal, or the_be_suffix is a suffix + of cbsuffix, apply the flag */ + if (!slapi_sdn_compare(cbsuffix, the_be_suffix) || + slapi_sdn_issuffix(cbsuffix, the_be_suffix)) { + cb_backend_instance *cbinst = cb_get_instance(chainbe); + if (cbinst) { + /* the backend is disabled if the state is not ON */ + cbinst->associated_be_is_disabled = (new_be_state != SLAPI_BE_STATE_ON); + slapi_log_error(SLAPI_LOG_PLUGIN, "chainbe", "cb_be_state_change: set the " + "state of chainbe for %s to %d\n", + slapi_sdn_get_dn(cbsuffix), (new_be_state != SLAPI_BE_STATE_ON)); + } + } + slapi_sdn_free(&cbsuffix); + } + } + } + + /* clean up */ + slapi_sdn_free(&the_be_suffix); + slapi_ch_free_string(&cookie); +} diff --git a/ldap/servers/plugins/chainingdb/cbdllmain.c b/ldap/servers/plugins/chainingdb/cbdllmain.c new file mode 100644 index 00000000..cacf9cb5 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/cbdllmain.c @@ -0,0 +1,128 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "cb.h" + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif + +#ifdef LDAP_DEBUG +#ifndef _WIN32 +#include <stdarg.h> +#include <stdio.h> + +void LDAPDebug( int level, char* fmt, ... ) +{ + static char debugBuf[1024]; + + if (module_ldap_debug && (*module_ldap_debug & level)) + { + va_list ap; + va_start (ap, fmt); + _snprintf (debugBuf, sizeof(debugBuf), fmt, ap); + va_end (ap); + + OutputDebugString (debugBuf); + } +} +#endif +#endif + +#ifndef _WIN32 + +/* The 16-bit version of the RTL does not implement perror() */ + +#include <stdio.h> + +void perror( const char *msg ) +{ + char buf[128]; + wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ; + OutputDebugString( buf ); +} + +#endif diff --git a/ldap/servers/plugins/chainingdb/libcb.def b/ldap/servers/plugins/chainingdb/libcb.def new file mode 100644 index 00000000..71fe8482 --- /dev/null +++ b/ldap/servers/plugins/chainingdb/libcb.def @@ -0,0 +1,15 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7 Chaining Database Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + chaining_back_init @1 + plugin_init_debug_level @2 + cb_be_state_change @3 diff --git a/ldap/servers/plugins/collation/Makefile b/ldap/servers/plugins/collation/Makefile new file mode 100644 index 00000000..14619af7 --- /dev/null +++ b/ldap/servers/plugins/collation/Makefile @@ -0,0 +1,99 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC= ../../.. +MCOM_ROOT= ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST= $(OBJDIR)/lib/liblcoll +LIBDIR= $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +INCLUDES+= -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +COLLATION_OBJS= collate.o config.o orfilter.o + +ifeq ($(ARCH), WINNT) +COLLATION_OBJS+= debug.o +COLLATION_DLL_OBJ=$(addprefix $(OBJDEST)/, dllmain.o) +DEF_FILE:=./collation.def +EXTRA_LIBS+= $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL) $(LIBSLAPD) +EXTRA_LIBS_DEP+= $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP+=$(LDAPSDK_DEP) +endif + +# INCLUDES+= -I. -I$(ACLINC) -I$(MCOM_ROOT)/ldapserver/lib + +# ICU stuff +INCLUDES+= $(ICU_INCLUDE) +EXTRA_LIBS+=$(ICULINK) + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS+= $(LIBSLAPDLINK) $(NSPRLINK) $(LDAPLINK) +EXTRA_LIBS_DEP+= $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP+=$(LDAPSDK_DEP) +LD=ld +endif + +OBJS= $(addprefix $(OBJDEST)/, $(COLLATION_OBJS)) +COLLATION= $(addprefix $(LIBDIR)/, $(COLLATION_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(COLLATION) +ifeq (0, 1) +# Where the heck did the compiler options come from? + @echo ARCH=$(ARCH) + @echo DEBUG=$(DEBUG) + @echo BUILD_OPT=$(BUILD_OPT) + @echo CFLAGS=$(CFLAGS) + @echo " MCC_DEBUG="$(MCC_DEBUG) + @echo " PLATFORMCFLAGS="$(PLATFORMCFLAGS) + @echo " ACFLAGS="$(ACFLAGS) + @echo " EXTRACFLAGS="$(EXTRACFLAGS) + @echo " UNPROTOCFLAGS="$(UNPROTOCFLAGS) + @echo " SLCFLAGS="$(SLCFLAGS) + @echo "ALDFLAGS="$(ALDFLAGS) + @echo "DLL_LDFLAGS="$(DLL_LDFLAGS) + @echo "DLL_EXPORT_FLAGS="$(DLL_EXPORT_FLAGS) +endif + +ifeq ($(ARCH), WINNT) +$(COLLATION): $(OBJS) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS_DEP) $(DEF_FILE) + $(LINK_DLL) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +ifeq ($(ARCH), AIX) +$(COLLATION): $(OBJS) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS_DEP) + $(LINK_DLL) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS) +else +$(COLLATION): $(OBJS) $(EXTRA_LIBS_DEP) + $(LINK_DLL) $(EXTRA_LIBS) +endif +endif + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(COLLATION_DLL_OBJ) +endif + $(RM) $(COLLATION) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/collation/collate.c b/ldap/servers/plugins/collation/collate.c new file mode 100644 index 00000000..603caf53 --- /dev/null +++ b/ldap/servers/plugins/collation/collate.c @@ -0,0 +1,454 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* collate.c - implementation of indexing, using a Collation */ + +#include "collate.h" +#include <string.h> /* memcpy */ + +#include <unicode/ucol.h> /* Collation */ +#include <unicode/ucnv.h> /* Conversion */ +#include <unicode/ustring.h> /* UTF8 conversion */ + +#include <ldap.h> /* LDAP_UTF8LEN */ +#include <slap.h> /* for strcasecmp on non-UNIX platforms and correct debug macro */ + +void +collation_init( char *configpath ) + /* Called once per process, to initialize globals. */ +{ + /* ICU needs no initialization? */ +} + +typedef struct coll_profile_t { /* Collator characteristics */ + const char* language; + const char* country; + const char* variant; + UColAttributeValue strength; /* one of UCOL_PRIMARY = 0, UCOL_SECONDARY = 1, UCOL_TERTIARY = 2, UCOL_QUATERNARY = 3, UCOL_IDENTICAL = 4 */ + UColAttributeValue decomposition; /* one of UCOL_OFF = 0, UCOL_DEFAULT = 1, UCOL_ON = 2 */ +} coll_profile_t; + +typedef struct coll_id_t { /* associates an OID with a coll_profile_t */ + char* oid; + coll_profile_t* profile; +} coll_id_t; + +/* A list of all OIDs that identify collator profiles: */ +static const coll_id_t** collation_id = NULL; +static size_t collation_ids = 0; + +int +collation_config (size_t cargc, char** cargv, + const char* fname, size_t lineno) + /* Process one line from a configuration file. + Return 0 if it's OK, -1 if it's not recognized. + Any other return value is a process exit code. + */ +{ + if (cargc <= 0) { /* Bizarre. Oh, well... */ + } else if (!strcasecmp (cargv[0], "NLS")) { + /* ignore - not needed anymore with ICU - was used to get path for NLS_Initialize */ + } else if (!strcasecmp (cargv[0], "collation")) { + if ( cargc < 7 ) { + LDAPDebug (LDAP_DEBUG_ANY, + "%s: line %lu ignored: only %lu arguments (expected " + "collation language country variant strength decomposition oid ...)\n", + fname, (unsigned long)lineno, (unsigned long)cargc ); + } else { + auto size_t arg; + auto coll_profile_t* profile = (coll_profile_t*) slapi_ch_calloc (1, sizeof (coll_profile_t)); + if (*cargv[1]) profile->language = slapi_ch_strdup (cargv[1]); + if (*cargv[2]) profile->country = slapi_ch_strdup (cargv[2]); + if (*cargv[3]) profile->variant = slapi_ch_strdup (cargv[3]); + switch (atoi(cargv[4])) { + case 1: profile->strength = UCOL_PRIMARY; break; + case 2: profile->strength = UCOL_SECONDARY; /* no break here? fall through? wtf? */ + case 3: profile->strength = UCOL_TERTIARY; break; + case 4: profile->strength = UCOL_IDENTICAL; break; + default: profile->strength = UCOL_SECONDARY; + LDAPDebug (LDAP_DEBUG_ANY, + "%s: line %lu: strength \"%s\" not supported (will use 2)\n", + fname, (unsigned long)lineno, cargv[4]); + break; + } + switch (atoi(cargv[5])) { + case 1: profile->decomposition = UCOL_OFF; break; + case 2: profile->decomposition = UCOL_DEFAULT; /* no break here? fall through? wtf? */ + case 3: profile->decomposition = UCOL_ON; break; + default: profile->decomposition = UCOL_DEFAULT; + LDAPDebug (LDAP_DEBUG_ANY, + "%s: line %lu: decomposition \"%s\" not supported (will use 2)\n", + fname, (unsigned long)lineno, cargv[5]); + break; + } + + { + char descStr[256]; + char nameOrder[256]; + char nameSubstring[256]; + char oidString[256]; + char *tmpStr=NULL; + Slapi_MatchingRuleEntry *mrentry=slapi_matchingrule_new(); + + if(UCOL_PRIMARY == profile->strength) { + strcpy(nameOrder,"caseIgnoreOrderingMatch"); + strcpy(nameSubstring,"caseIgnoreSubstringMatch"); + } + else { + strcpy(nameOrder,"caseExactOrderingMatch"); + strcpy(nameSubstring,"caseExactSubstringMatch"); + } + + if(cargc > 7) { + strcat(nameOrder,"-"); + strcat(nameOrder,cargv[7]); + strcat(nameSubstring,"-"); + strcat(nameSubstring,cargv[7]); + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_NAME, + (void *)slapi_ch_strdup(nameOrder)); + } + else { + if(0 != cargv[1][0]) { + strcat(nameOrder,"-"); + strcat(nameSubstring,"-"); + } + strcat(nameOrder,cargv[1]); + strcat(nameSubstring,cargv[1]); + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_NAME, + (void *)slapi_ch_strdup(nameOrder)); + } + strcpy(oidString,cargv[6]); + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_OID, + (void *)slapi_ch_strdup(oidString)); + if(0 != cargv[2][0]) { + sprintf(descStr,"%s-%s",cargv[1],cargv[2]); + } + else { + strcpy(descStr,cargv[1]); + } + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_DESC, + (void *)slapi_ch_strdup(descStr)); + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_SYNTAX, + (void *)slapi_ch_strdup(DIRSTRING_SYNTAX_OID)); + slapi_matchingrule_register(mrentry); + slapi_matchingrule_get(mrentry,SLAPI_MATCHINGRULE_NAME, + (void *)&tmpStr); + slapi_ch_free((void **)&tmpStr); + slapi_matchingrule_get(mrentry,SLAPI_MATCHINGRULE_OID, + (void *)&tmpStr); + slapi_ch_free((void **)&tmpStr); + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_NAME, + (void *)slapi_ch_strdup(nameSubstring)); + strcat(oidString,".6"); + slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_OID, + (void *)slapi_ch_strdup(oidString)); + slapi_matchingrule_register(mrentry); + slapi_matchingrule_free(&mrentry,1); + } + + + for (arg = 6; arg < cargc; ++arg) { + auto coll_id_t* id = (coll_id_t*) slapi_ch_malloc (sizeof (coll_id_t)); + id->oid = slapi_ch_strdup (cargv[arg]); + id->profile = profile; + if (collation_ids <= 0) { + collation_id = (const coll_id_t**) slapi_ch_malloc (2 * sizeof (coll_id_t*)); + } else { + collation_id = (const coll_id_t**) slapi_ch_realloc + ((void*)collation_id, (collation_ids + 2) * sizeof (coll_id_t*)); + } + collation_id [collation_ids++] = id; + collation_id [collation_ids] = NULL; + } + } + } else { + return -1; /* unrecognized */ + } + return 0; /* success */ +} + +typedef struct collation_indexer_t + /* A kind of indexer, implemented using an ICU Collator */ +{ + UCollator* collator; + UConverter* converter; + struct berval** ix_keys; + int is_default_collator; +} collation_indexer_t; + +/* + Caller must ensure that U == NULL and Ulen == 0 the first time called +*/ +static UErrorCode +SetUnicodeStringFromUTF_8 (UChar** U, int32_t* Ulen, int *isAlloced, const struct berval* bv) + /* Copy the UTF-8 string bv into the UnicodeString U, + but remove leading and trailing whitespace, and + convert consecutive whitespaces into a single space. + Ulen is set to the number of UChars in the array (not necessarily the number of bytes!) + */ +{ + size_t n; + int32_t len = 0; /* length of non-space string */ + int32_t needLen = 0; /* number of bytes needed for string */ + UErrorCode err = U_ZERO_ERROR; + const char* s = bv->bv_val; + const char* begin = NULL; /* will point to beginning of non-space in val */ + const char* end = NULL; /* will point to the first space after the last non-space char in val */ + int32_t nUchars = 0; + + if (!bv->bv_len) { /* no value? */ + return U_INVALID_FORMAT_ERROR; /* don't know what else to use here */ + } + + /* first, set s to the first non-space char in bv->bv_val */ + for (n = 0; (n < bv->bv_len) && ldap_utf8isspace((char *)s); ) { /* cast away const */ + const char *next = LDAP_UTF8NEXT((char *)s); /* cast away const */ + n += (next - s); /* count bytes, not chars */ + s = next; + } + begin = s; /* begin points to first non-space char in val */ + + if (n >= bv->bv_len) { /* value is all spaces? */ + return U_INVALID_FORMAT_ERROR; /* don't know what else to use here */ + } + + s = bv->bv_val + (bv->bv_len-1); /* move s to last char of bv_val */ + end = s; /* end points at last char of bv_val - may change below */ + /* find the last non-null and non-space char of val */ + for (n = bv->bv_len; (n > 0) && (!*s || ldap_utf8isspace((char *)s));) { + const char *prev = LDAP_UTF8PREV((char *)s); + end = prev; + n -= (s - prev); /* count bytes, not chars */ + s = prev; + } + + /* end now points at last non-null/non-space of val */ + if (n < 0) { /* bogus */ + return U_INVALID_FORMAT_ERROR; /* don't know what else to use here */ + } + + len = LDAP_UTF8NEXT((char *)end) - begin; + + u_strFromUTF8(*U, *Ulen, &nUchars, begin, len, &err); + if (nUchars > *Ulen) { /* need more space */ + if (*isAlloced) { /* realloc space */ + *U = (UChar *)slapi_ch_realloc((char *)*U, sizeof(UChar) * nUchars); + } else { /* must use malloc */ + *U = (UChar *)slapi_ch_malloc(sizeof(UChar) * nUchars); + *isAlloced = 1; /* no longer using fixed buffer */ + } + *Ulen = nUchars; + err = U_ZERO_ERROR; /* reset */ + u_strFromUTF8(*U, *Ulen, NULL, begin, len, &err); + } else { + *Ulen = nUchars; + } + + return err; +} + +static struct berval** +collation_index (indexer_t* ix, struct berval** bvec, struct berval** prefixes) +{ + collation_indexer_t* etc = (collation_indexer_t*) ix->ix_etc; + struct berval** keys = NULL; + if (bvec) { + char keyBuffer[128]; /* try to use static space buffer to avoid malloc */ + int32_t keyLen = sizeof(keyBuffer); + char* key = keyBuffer; /* but key can grow if necessary */ + size_t keyn = 0; + struct berval** bv; + UChar charBuffer[128]; /* try to use static space buffer */ + int32_t nChars = sizeof(charBuffer)/sizeof(UChar); /* but grow if necessary */ + UChar *chars = charBuffer; /* try to reuse this */ + int isAlloced = 0; /* using fixed buffer */ + + for (bv = bvec; *bv; ++bv) { + /* if chars is allocated, nChars will be the capacity and the number of chars in chars */ + /* otherwise, nChars will be the number of chars, which may be less than the capacity */ + if (!isAlloced) { + nChars = sizeof(charBuffer)/sizeof(UChar); /* reset */ + } + if (U_ZERO_ERROR == SetUnicodeStringFromUTF_8 (&chars, &nChars, &isAlloced, *bv)) { + /* nChars is now the number of UChar in chars, which may be less than the + capacity of charBuffer if not allocated */ + struct berval* prefix = prefixes ? prefixes[bv-bvec] : NULL; + const size_t prefixLen = prefix ? prefix->bv_len : 0; + struct berval* bk = NULL; + int32_t realLen; /* real length of key, not keyLen which is buffer size */ + + /* try to get the sort key using key and keyLen; only grow key + if we need to */ + /* can use -1 for char len since the conversion from UTF8 + null terminates the string */ + realLen = ucol_getSortKey(etc->collator, chars, nChars, (uint8_t *)key, keyLen); + if (realLen > keyLen) { /* need more space */ + if (key == keyBuffer) { + key = (char*)slapi_ch_malloc(sizeof(char) * realLen); + } else { + key = (char*)slapi_ch_realloc(key, sizeof(char) * realLen); + } + keyLen = ucol_getSortKey(etc->collator, chars, nChars, (uint8_t *)key, realLen); + } + if (realLen > 0) { + bk = (struct berval*) slapi_ch_malloc (sizeof(struct berval)); + + bk->bv_len = prefixLen + realLen; + bk->bv_val = slapi_ch_malloc (bk->bv_len + 1); + if (prefixLen) { + memcpy(bk->bv_val, prefix->bv_val, prefixLen); + } + memcpy(bk->bv_val + prefixLen, key, realLen); + bk->bv_val[bk->bv_len] = '\0'; + LDAPDebug (LDAP_DEBUG_FILTER, "collation_index(%.*s) %lu bytes\n", + bk->bv_len, bk->bv_val, (unsigned long)bk->bv_len); + keys = (struct berval**) + slapi_ch_realloc ((void*)keys, sizeof(struct berval*) * (keyn + 2)); + keys[keyn++] = bk; + keys[keyn] = NULL; + } + } + } + if (chars != charBuffer) { /* realloc'ed, need to free */ + slapi_ch_free((void **)&chars); + } + if (key != keyBuffer) { /* realloc'ed, need to free */ + slapi_ch_free_string(&key); + } + } + if (etc->ix_keys != NULL) ber_bvecfree (etc->ix_keys); + etc->ix_keys = keys; + return keys; +} + +static void +collation_indexer_destroy (indexer_t* ix) + /* The destructor function for a collation-based indexer. */ +{ + collation_indexer_t* etc = (collation_indexer_t*) ix->ix_etc; + if (etc->converter) { + ucnv_close(etc->converter); + etc->converter = NULL; + } + if (!etc->is_default_collator) { + /* Don't delete the default collation - it seems to cause problems */ + ucol_close(etc->collator); + etc->collator = NULL; + } + if (etc->ix_keys != NULL) { + ber_bvecfree (etc->ix_keys); + etc->ix_keys = NULL; + } + slapi_ch_free((void**)&ix->ix_etc); + ix->ix_etc = NULL; /* just for hygiene */ +} + +static UErrorCode +s_newNamedLocaleFromComponents(char **locale, const char *lang, const char *country, const char *variant) +{ + UErrorCode err = U_ZERO_ERROR; + int hasLang = (lang && *lang); + int hasC = (country && *country); + int hasVar = (variant && *variant); + + *locale = NULL; + if (hasLang) { + *locale = PR_smprintf("%s%s%s%s%s", lang, (hasC ? "_" : ""), (hasC ? country : ""), + (hasVar ? "_" : ""), (hasVar ? variant : "")); + } else { + err = U_INVALID_FORMAT_ERROR; /* don't know what else to use here */ + } + + return err; +} + +indexer_t* +collation_indexer_create (const char* oid) + /* Return a new indexer, based on the collation identified by oid. + Return NULL if this can't be done. + */ +{ + indexer_t* ix = NULL; + const coll_id_t** id = collation_id; + char* locale = NULL; /* NULL == default locale */ + if (id) for (; *id; ++id) { + if (!strcasecmp (oid, (*id)->oid)) { + const coll_profile_t* profile = (*id)->profile; + const int is_default = (profile->language == NULL && + profile->country == NULL && + profile->variant == NULL); + UErrorCode err = U_ZERO_ERROR; + if ( ! is_default) { + if (locale) { + PR_smprintf_free(locale); + locale = NULL; + } + err = s_newNamedLocaleFromComponents(&locale, + profile->language, + profile->country, + profile->variant); + } + if (err == U_ZERO_ERROR) { + UCollator* coll = ucol_open(locale, &err); + /* + * If we found exactly the right collator for this locale, + * or if we found a fallback one, or if we are happy with + * the default, use it. + */ + if (err == U_ZERO_ERROR || err == U_USING_FALLBACK_WARNING || + (err == U_USING_DEFAULT_WARNING && is_default)) { + collation_indexer_t* etc = (collation_indexer_t*) + slapi_ch_calloc (1, sizeof (collation_indexer_t)); + ix = (indexer_t*) slapi_ch_calloc (1, sizeof (indexer_t)); + ucol_setAttribute (coll, UCOL_STRENGTH, profile->strength, &err); + if (err != U_ZERO_ERROR) { + LDAPDebug (LDAP_DEBUG_ANY, "collation_indexer_create: could not " + "set the collator strength for oid %s to %d: err %d\n", + oid, profile->strength, err); + } + ucol_setAttribute (coll, UCOL_DECOMPOSITION_MODE, profile->decomposition, &err); + if (err != U_ZERO_ERROR) { + LDAPDebug (LDAP_DEBUG_ANY, "collation_indexer_create: could not " + "set the collator decomposition mode for oid %s to %d: err %d\n", + oid, profile->decomposition, err); + } + etc->collator = coll; + etc->is_default_collator = is_default; + for (id = collation_id; *id; ++id) { + if ((*id)->profile == profile) { + break; /* found the 'official' id */ + } + } + ix->ix_etc = etc; + ix->ix_oid = (*id)->oid; + ix->ix_index = collation_index; + ix->ix_destroy = collation_indexer_destroy; + break; /* return */ + /* free (etc); */ + /* free (ix); */ + } else if (err == U_USING_DEFAULT_WARNING) { + LDAPDebug (LDAP_DEBUG_FILTER, "collation_indexer_create: could not " + "create an indexer for OID %s for locale %s and could not " + "use default locale\n", + oid, (locale ? locale : "(default)"), NULL); + } else { /* error */ + LDAPDebug (LDAP_DEBUG_FILTER, "collation_indexer_create: could not " + "create an indexer for OID %s for locale %s: err = %d\n", + oid, (locale ? locale : "(default)"), err); + } + if (coll) { + ucol_close (coll); + coll = NULL; + } + } + break; /* failed to create the specified collator */ + } + } + if (locale) { + PR_smprintf_free(locale); + locale = NULL; + } + return ix; +} diff --git a/ldap/servers/plugins/collation/collate.h b/ldap/servers/plugins/collation/collate.h new file mode 100644 index 00000000..29e51022 --- /dev/null +++ b/ldap/servers/plugins/collation/collate.h @@ -0,0 +1,36 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _COLLATE_H_ +#define _COLLATE_H_ + +#include <stddef.h> /* size_t */ +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +struct indexer_t; + +typedef void (*ix_destroy_t) (struct indexer_t*); +typedef struct berval** (*ix_index_t) (struct indexer_t*, struct berval** values, + struct berval** prefixes /* inserted into each key */); + +typedef struct indexer_t +{ + char* ix_oid; + ix_index_t ix_index; /* map values to index keys */ + ix_destroy_t ix_destroy; + void* ix_etc; /* whatever state the implementation needs */ +} indexer_t; + +extern void +collation_init( char *configpath ); + +extern int +collation_config (size_t argc, char** argv, const char* fname, size_t lineno); + +extern indexer_t* +collation_indexer_create (const char* oid); + +#endif diff --git a/ldap/servers/plugins/collation/collation.def b/ldap/servers/plugins/collation/collation.def new file mode 100644 index 00000000..bd5d531b --- /dev/null +++ b/ldap/servers/plugins/collation/collation.def @@ -0,0 +1,10 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7 Collation Plugin' +EXPORTS + orderingRule_init @2 + plugin_init_debug_level @3 diff --git a/ldap/servers/plugins/collation/config.c b/ldap/servers/plugins/collation/config.c new file mode 100644 index 00000000..ef3e66cf --- /dev/null +++ b/ldap/servers/plugins/collation/config.c @@ -0,0 +1,178 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "collate.h" +#include "config.h" +#include <stdio.h> +#include <string.h> +#include "slap.h" + +#define MAXARGS 16 + +static char * +strtok_quote( char *line, char *sep ) +{ + int inquote; + char *tmp, *d; + static char *next; + + if ( line != NULL ) { + next = line; + } + while ( *next && strchr( sep, *next ) ) { + next++; + } + + if ( *next == '\0' ) { + next = NULL; + return( NULL ); + } + + d = tmp = next; + for ( inquote = 0; *next; next++ ) { + switch ( *next ) { + case '"': + if ( inquote ) { + inquote = 0; + } else { + inquote = 1; + } + break; + +#ifndef _WIN32 + case '\\': + *d++ = *++next; + break; +#endif + + default: + if ( ! inquote ) { + if ( strchr( sep, *next ) != NULL ) { + *d++ = '\0'; + next++; + return( tmp ); + } + } + *d++ = *next; + break; + } + } + *d = '\0'; + + return( tmp ); +} + +static void +fp_parse_line( + char *line, + int *argcp, + char **argv +) +{ + char * token; + + *argcp = 0; + for ( token = strtok_quote( line, " \t" ); token != NULL; + token = strtok_quote( NULL, " \t" ) ) { + if ( *argcp == MAXARGS ) { + LDAPDebug( LDAP_DEBUG_ANY, "Too many tokens (max %d)\n", + MAXARGS, 0, 0 ); + exit( 1 ); + } + argv[(*argcp)++] = token; + } + argv[*argcp] = NULL; +} + +static char buf[BUFSIZ]; +static char *line; +static int lmax, lcur; + +static void +fp_getline_init( int *lineno ) +{ + *lineno = -1; + buf[0] = '\0'; +} + +#define CATLINE( buf ) { \ + int len; \ + len = strlen( buf ); \ + while ( lcur + len + 1 > lmax ) { \ + lmax += BUFSIZ; \ + line = (char *) slapi_ch_realloc( line, lmax ); \ + } \ + strcpy( line + lcur, buf ); \ + lcur += len; \ +} + +static char * +fp_getline( FILE *fp, int *lineno ) +{ + char *p; + + lcur = 0; + CATLINE( buf ); + (*lineno)++; + + /* hack attack - keeps us from having to keep a stack of bufs... */ + if ( strncasecmp( line, "include", 7 ) == 0 ) { + buf[0] = '\0'; + return( line ); + } + + while ( fgets( buf, sizeof(buf), fp ) != NULL ) { + if ( (p = strchr( buf, '\n' )) != NULL ) { + *p = '\0'; + } + if ( ! isspace( buf[0] ) ) { + return( line ); + } + + CATLINE( buf ); + (*lineno)++; + } + buf[0] = '\0'; + + return( line[0] ? line : NULL ); +} + +void +collation_read_config( char *fname ) +{ + FILE* fp; + char* line; + int cargc; + char* cargv[MAXARGS]; + int lineno; + + fp = fopen( fname, "r" ); + if ( fp == NULL ) { + LDAPDebug( LDAP_DEBUG_ANY, + "could not open config file \"%s\" - absolute path?\n", + fname, 0, 0 ); + return; /* Do not exit */ + } + + LDAPDebug( LDAP_DEBUG_CONFIG, "reading config file %s\n", fname, 0, 0 ); + + fp_getline_init( &lineno ); + while ( (line = fp_getline( fp, &lineno )) != NULL ) { + /* skip comments and blank lines */ + if ( line[0] == '#' || line[0] == '\0' ) { + continue; + } + LDAPDebug( LDAP_DEBUG_CONFIG, "line %d: %s\n", lineno, line, 0 ); + fp_parse_line( line, &cargc, cargv ); + if ( cargc < 1 ) { + LDAPDebug( LDAP_DEBUG_ANY, + "%s: line %d: bad config line (ignored)\n", + fname, lineno, 0 ); + continue; + } + collation_config (cargc, cargv, fname, lineno); + } + fclose(fp); +} diff --git a/ldap/servers/plugins/collation/config.h b/ldap/servers/plugins/collation/config.h new file mode 100644 index 00000000..1a4aef66 --- /dev/null +++ b/ldap/servers/plugins/collation/config.h @@ -0,0 +1,12 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#ifndef _COLL_CONFIG_H_ +#define _COLL_CONFIG_H_ + +extern void +collation_read_config( char *fname ); + +#endif diff --git a/ldap/servers/plugins/collation/debug.c b/ldap/servers/plugins/collation/debug.c new file mode 100644 index 00000000..99266a33 --- /dev/null +++ b/ldap/servers/plugins/collation/debug.c @@ -0,0 +1,14 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void +plugin_init_debug_level (int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif diff --git a/ldap/servers/plugins/collation/dllmain.c b/ldap/servers/plugins/collation/dllmain.c new file mode 100644 index 00000000..fa158500 --- /dev/null +++ b/ldap/servers/plugins/collation/dllmain.c @@ -0,0 +1,129 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for collation DLL + */ +#include "ldap.h" + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif + +#ifdef LDAP_DEBUG +#ifndef _WIN32 +#include <stdarg.h> +#include <stdio.h> + +void LDAPDebug( int level, char* fmt, ... ) +{ + static char debugBuf[1024]; + + if (module_ldap_debug && (*module_ldap_debug & level)) + { + va_list ap; + va_start (ap, fmt); + _snprintf (debugBuf, sizeof(debugBuf), fmt, ap); + va_end (ap); + + OutputDebugString (debugBuf); + } +} +#endif +#endif + +#ifndef _WIN32 +/* The 16-bit version of the RTL does not implement perror() */ +#include <stdio.h> + +void perror( const char *msg ) +{ + char buf[128]; + wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ; + OutputDebugString( buf ); +} + +#endif diff --git a/ldap/servers/plugins/collation/orfilter.c b/ldap/servers/plugins/collation/orfilter.c new file mode 100644 index 00000000..65222baa --- /dev/null +++ b/ldap/servers/plugins/collation/orfilter.c @@ -0,0 +1,984 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* orfilter.c - implementation of ordering rule filter */ + +#include <ldap.h> /* LDAP_UTF8INC */ +#include <slap.h> /* for debug macros */ +#include <slapi-plugin.h> /* slapi_berval_cmp, SLAPI_BERVAL_EQ */ +#include "collate.h" /* indexer_t, collation_xxx */ +#include "config.h" /* collation_read_config */ +#include "orfilter.h" + +#ifdef HPUX11 +#include <dl.h> +#endif /* HPUX11 */ + +static indexer_t* +indexer_create (const char* oid) +{ + return collation_indexer_create (oid); +} + +static void +indexer_free (indexer_t* ix) +{ + if (ix->ix_destroy != NULL) { + ix->ix_destroy (ix); + } + slapi_ch_free((void**)&ix); +} + +typedef struct or_filter_t { + /* implements a filter, using an indexer */ + char* or_type; + int or_op; /* LDAPI_OP_xxx */ + char* or_oid; + struct berval** or_values; + struct berval** or_match_keys; + struct berval** or_index_keys; + indexer_t* or_indexer; /* used to construct or_match_keys and or_index_keys */ +} or_filter_t; + +static or_filter_t* +or_filter_get (Slapi_PBlock* pb) +{ + auto void* obj = NULL; + if ( ! slapi_pblock_get (pb, SLAPI_PLUGIN_OBJECT, &obj)) { + return (or_filter_t*)obj; + } + return NULL; +} + +static int +or_filter_destroy (Slapi_PBlock* pb) +{ + auto or_filter_t* or = or_filter_get (pb); + LDAPDebug (LDAP_DEBUG_FILTER, "or_filter_destroy(%p)\n", (void*)or, 0, 0); + if (or != NULL) { + slapi_ch_free((void**)&or->or_type); + slapi_ch_free((void**)&or->or_oid); + if (or->or_values != NULL) { + ber_bvecfree (or->or_values); + or->or_values = NULL; + } + if (or->or_match_keys != NULL) { + ber_bvecfree (or->or_match_keys); + or->or_match_keys = NULL; + } + if (or->or_index_keys != NULL) { + ber_bvecfree (or->or_index_keys); + or->or_index_keys = NULL; + } + if (or->or_indexer != NULL) { + indexer_free (or->or_indexer); + or->or_indexer = NULL; + } + slapi_ch_free((void**)&or); + } + return 0; +} + +#define MAX_CHAR_COMBINING 3 +/* The maximum number of Unicode characters that may combine + to form a single collation element. +*/ + +static int +ss_match (struct berval* value, + const struct berval* key0, + indexer_t* ix) +/* returns: 0 a prefix of value matched key + * 1 a subsequent substring might match; try again + * -1 nothing in value will match; give up + */ +{ + auto struct berval* vals[2]; + auto struct berval val; + auto struct berval key; + auto size_t attempts = MAX_CHAR_COMBINING; + + vals[0] = &val; + vals[1] = NULL; + val.bv_val = value->bv_val; + val.bv_len = 0; + key.bv_val = key0->bv_val; + key.bv_len = key0->bv_len - 1; + while (1) { + auto struct berval** vkeys = ix->ix_index (ix, vals, NULL); + if (vkeys && vkeys[0]) { + auto const struct berval* vkey = vkeys[0]; + if (vkey->bv_len > key.bv_len) { + if (--attempts <= 0) { + break; /* No match at this starting point */ + } /* else Try looking at another character; + it may combine, and produce a shorter key. + */ + } else if (SLAPI_BERVAL_EQ (vkey, &key)) { + value->bv_len -= val.bv_len; + value->bv_val += val.bv_len; + return 0; + } + } + if (val.bv_len >= value->bv_len) { + break; + } + val.bv_len += LDAP_UTF8LEN (val.bv_val + val.bv_len); + } + if (value->bv_len > 0) { + auto size_t one = LDAP_UTF8LEN (value->bv_val); + value->bv_len -= one; + value->bv_val += one; + return 1; + } + return -1; +} + +static int +ss_filter_match (or_filter_t* or, struct berval** vals) +/* returns: 0 filter matched + * -1 filter did not match + * >0 an LDAP error code + */ +{ + auto int rc = -1; /* no match */ + auto indexer_t* ix = or->or_indexer; + if (vals != NULL) for (; *vals; ++vals) { + auto struct berval v; + auto struct berval** k = or->or_match_keys; + if (k == NULL || *k == NULL) { + rc = 0; /* present */ + break; + } + v.bv_len = (*vals)->bv_len; + v.bv_val = (*vals)->bv_val; + if ((*k)->bv_len > 0 && ss_match (&v, *k, ix) != 0) { + break; /* initial failed */ + } + rc = 0; /* so far, so good */ + while (*++k) { + if ((*k)->bv_len <= 0) { + rc = 0; + } else if (k[1]) { /* middle */ + do { + rc = ss_match (&v, *k, ix); + } while (rc > 0); + if (rc < 0) { + break; + } + } else { /* final */ + auto size_t attempts = MAX_CHAR_COMBINING; + auto char* limit = v.bv_val; + auto struct berval** vkeys; + auto struct berval* vals[2]; + auto struct berval key; + rc = -1; + vals[0] = &v; + vals[1] = NULL; + key.bv_val = (*k)->bv_val; + key.bv_len = (*k)->bv_len - 1; + v.bv_val = (*vals)->bv_val + (*vals)->bv_len; + while(1) { + v.bv_len = (*vals)->bv_len - (v.bv_val - (*vals)->bv_val); + vkeys = ix->ix_index (ix, vals, NULL); + if (vkeys && vkeys[0]) { + auto const struct berval* vkey = vkeys[0]; + if (vkey->bv_len > key.bv_len) { + if (--attempts <= 0) { + break; + } /* else Try looking at another character; + it may combine, and produce a shorter key. + */ + } else if (SLAPI_BERVAL_EQ (vkey, &key)) { + rc = 0; + break; + } + } + if (v.bv_val <= limit) break; + LDAP_UTF8DEC (v.bv_val); + } + break; + } + } + if (rc != -1) break; + } + return rc; +} + +static int +op_filter_match (or_filter_t* or, struct berval** vals) +{ + auto indexer_t* ix = or->or_indexer; + auto struct berval** v = ix->ix_index (ix, vals, NULL); + if (v != NULL) for (; *v; ++v) { + auto struct berval** k = or->or_match_keys; + if (k != NULL) for (; *k; ++k) { + switch (or->or_op) { + case SLAPI_OP_LESS: + if (slapi_berval_cmp (*v, *k) < 0) return 0; break; + case SLAPI_OP_LESS_OR_EQUAL: + if (slapi_berval_cmp (*v, *k) <= 0) return 0; break; + case SLAPI_OP_EQUAL: + if (SLAPI_BERVAL_EQ (*v, *k)) return 0; break; + case SLAPI_OP_GREATER_OR_EQUAL: + if (slapi_berval_cmp (*v, *k) >= 0) return 0; break; + case SLAPI_OP_GREATER: + if (slapi_berval_cmp (*v, *k) > 0) return 0; break; + default: + break; + } + } + } + return -1; +} + +static int +or_filter_match (void* obj, Slapi_Entry* entry, Slapi_Attr* attr) +/* returns: 0 filter matched + * -1 filter did not match + * >0 an LDAP error code + */ +{ + auto int rc = -1; /* no match */ + auto or_filter_t* or = (or_filter_t*)obj; + for (; attr != NULL; slapi_entry_next_attr (entry, attr, &attr)) { + auto char* type = NULL; + auto struct berval** vals = NULL; + +/* + * XXXmcs 1-March-2001: This code would perform better if it did not make + * a copy of the values here, but that would require re-writing the code + * in this file to use Slapi_ValueSet's instead of struct berval **'s + * (and that is not a small project). + */ + if (!slapi_attr_get_type (attr, &type) && type != NULL && + !slapi_attr_type_cmp (or->or_type, type, 2/*match subtypes*/) && + !slapi_attr_get_bervals_copy(attr, &vals) && vals != NULL) { + + if (or->or_op == SLAPI_OP_SUBSTRING) { + rc = ss_filter_match (or, vals); + } else { + rc = op_filter_match (or, vals); + } + + ber_bvecfree( vals ); + vals = NULL; + if (rc >= 0) break; + } + } + return rc; +} + +#define WILDCARD '*' +/* If you want a filter value to contain a non-wildcard '*' or '\' + you write "\2a" or "\5c" (the ASCII codes, in hexadecimal). + For example, "4\2a4*flim\5cflam" + matches a value that begins with "4*4" and ends with "flim\flam" + (except that all the "\" should be doubled in C string literals). + This conforms to <draft-ietf-asid-ldapv3-attributes-08> section 8.3. +*/ + +static void +ss_unescape (struct berval* val) +{ + char* s = val->bv_val; + char* t = s; + char* limit = s + val->bv_len; + while (s < limit) { + if (!memcmp (s, "\\2a", 3) || + !memcmp (s, "\\2A", 3)) { + *t++ = WILDCARD; + s += 3; + } else if (!memcmp (s, "\\5c", 3) || + !memcmp (s, "\\5C", 3)) { + *t++ = '\\'; + s += 3; + } else { + if (t == s) LDAP_UTF8INC (t); + else t += LDAP_UTF8COPY (t, s); + LDAP_UTF8INC (s); + } + } + val->bv_len = t - val->bv_val; +} + +static struct berval* +slapi_ch_bvdup0 (struct berval* val) + /* Return a copy of val, with a 0 byte following the end. */ +{ + auto struct berval* result = (struct berval*) + slapi_ch_malloc (sizeof (struct berval)); + result->bv_len = val->bv_len; + result->bv_val = slapi_ch_malloc (result->bv_len + 1); + if (result->bv_len > 0) { + memcpy (result->bv_val, val->bv_val, result->bv_len); + } + result->bv_val[result->bv_len] = '\0'; + return result; +} + +static struct berval* +ss_filter_value (const char* s, const size_t len, struct berval* val) +{ + val->bv_len = len; + if (len > 0) memcpy (val->bv_val, s, len); + ss_unescape (val); + return slapi_ch_bvdup0 (val); +} + +static struct berval** +ss_filter_values (struct berval* pattern, int* query_op) + /* Split the pattern into its substrings and return them. */ +{ + auto struct berval** result; + auto struct berval val; + auto size_t n; + auto char* s; + auto char* p; + auto char* plimit = pattern->bv_val + pattern->bv_len; + + /* Compute the length of the result array, and + the maximum bv_len of any of its elements. */ + val.bv_len = 0; + n = 2; /* one key, plus NULL terminator */ + s = pattern->bv_val; + for (p = s; p < plimit; LDAP_UTF8INC(p)) { + switch (*p) { + case WILDCARD: + ++n; + { + auto const size_t len = (p - s); + if (val.bv_len < len) + val.bv_len = len; + } + while (++p != plimit && *p == WILDCARD); + s = p; + break; + default: break; + } + } + if (n == 2) { /* no wildcards in pattern */ + auto struct berval** pvec = (struct berval**) slapi_ch_malloc (sizeof (struct berval*) * 2); + auto struct berval* pv = (struct berval*) slapi_ch_malloc (sizeof (struct berval)); + pvec[0] = pv; + pvec[1] = NULL; + pv->bv_len = pattern->bv_len; + pv->bv_val = slapi_ch_malloc (pv->bv_len); + memcpy (pv->bv_val, pattern->bv_val, pv->bv_len); + ss_unescape (pv); + *query_op = SLAPI_OP_EQUAL; + return pvec; + } else if (n == 3 && pattern->bv_len <= 1) { /* entire pattern is one wildcard */ + return NULL; /* presence */ + } + { + auto const size_t len = (p - s); + if (val.bv_len < len) + val.bv_len = len; + } + result = (struct berval**) slapi_ch_malloc (n * sizeof (struct berval*)); + val.bv_val = slapi_ch_malloc (val.bv_len); + n = 0; + s = pattern->bv_val; + for (p = s; p < plimit; LDAP_UTF8INC(p)) { + switch (*p) { + case WILDCARD: + result[n++] = ss_filter_value (s, p-s, &val); + while (++p != plimit && *p == WILDCARD); + s = p; + break; + default: break; + } + } + if (p != s || s == plimit) { + result[n++] = ss_filter_value (s, p-s, &val); + } + result[n] = NULL; + slapi_ch_free((void**)&val.bv_val); + return result; +} + +static struct berval* +ss_filter_key (indexer_t* ix, struct berval* val) +{ + struct berval* key = (struct berval*) slapi_ch_calloc (1, sizeof(struct berval)); + if (val->bv_len > 0) { + struct berval** keys = NULL; + auto struct berval* vals[2]; + vals[0] = val; + vals[1] = NULL; + keys = ix->ix_index (ix, vals, NULL); + if (keys && keys[0]) { + key->bv_len = keys[0]->bv_len + 1; + key->bv_val = slapi_ch_malloc (key->bv_len); + memcpy (key->bv_val, keys[0]->bv_val, keys[0]->bv_len); + key->bv_val[key->bv_len-1] = '\0'; + } + } + return key; +} + +static struct berval** +ss_filter_keys (indexer_t* ix, struct berval** values) + /* Index the substrings and return the key values, + with an extra byte appended to each key, so that + an empty key definitely implies an absent value. + */ +{ + auto struct berval** keys = NULL; + if (values != NULL) { + auto size_t n; /* how many substring values */ + auto struct berval** val; + for (n=0,val=values; *val != NULL; ++n,++val); + keys = (struct berval**) slapi_ch_malloc ((n+1) * sizeof (struct berval*)); + for (n=0,val=values; *val != NULL; ++n,++val) { + keys[n] = ss_filter_key (ix, *val); + } + keys[n] = NULL; + } + return keys; +} + +static int or_filter_index (Slapi_PBlock* pb); + +static int +or_filter_create (Slapi_PBlock* pb) +{ + auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */ + auto char* mrOID = NULL; + auto char* mrTYPE = NULL; + auto struct berval* mrVALUE = NULL; + auto or_filter_t* or = NULL; + + if (!slapi_pblock_get (pb, SLAPI_PLUGIN_MR_OID, &mrOID) && mrOID != NULL && + !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE) && mrTYPE != NULL && + !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUE, &mrVALUE) && mrVALUE != NULL) { + auto size_t len = mrVALUE->bv_len; + auto indexer_t* ix = NULL; + auto int op = SLAPI_OP_EQUAL; + auto struct berval bv; + auto int reusable = MRF_ANY_TYPE; + + LDAPDebug (LDAP_DEBUG_FILTER, "=> or_filter_create(oid %s; type %s)\n", + mrOID, mrTYPE, 0); + if (len > 1 && (ix = indexer_create (mrOID)) != NULL) { + auto char* val = mrVALUE->bv_val; + switch (val[0]) { + case '=': break; + case '<': op = (val[1] == '=') ? + SLAPI_OP_LESS_OR_EQUAL : SLAPI_OP_LESS; break; + case '>': op = (val[1] == '=') ? + SLAPI_OP_GREATER_OR_EQUAL : SLAPI_OP_GREATER; break; + case WILDCARD: op = SLAPI_OP_SUBSTRING; break; + default: + break; + } + for (; len > 0 && *val != ' '; ++val, --len); + if (len > 0) ++val, --len; /* skip the space */ + bv.bv_len = len; + bv.bv_val = (len > 0) ? val : NULL; + } else { /* mrOID does not identify an ordering rule. */ + /* Is it an ordering rule OID with a relational operator suffix? */ + auto size_t oidlen = strlen (mrOID); + if (oidlen > 2 && mrOID[oidlen-2] == '.') { + op = atoi (mrOID + oidlen - 1); + switch (op) { + case SLAPI_OP_LESS: + case SLAPI_OP_LESS_OR_EQUAL: + case SLAPI_OP_EQUAL: + case SLAPI_OP_GREATER_OR_EQUAL: + case SLAPI_OP_GREATER: + case SLAPI_OP_SUBSTRING: + { + auto char* or_oid = slapi_ch_strdup (mrOID); + or_oid [oidlen-2] = '\0'; + ix = indexer_create (or_oid); + if (ix != NULL) { + memcpy (&bv, mrVALUE, sizeof(struct berval)); + reusable |= MRF_ANY_VALUE; + } + slapi_ch_free((void**)&or_oid); + } + break; + default: /* not a relational operator */ + break; + } + } + } + if (ix != NULL) { + or = (or_filter_t*) slapi_ch_calloc (1, sizeof (or_filter_t)); + or->or_type = slapi_ch_strdup (mrTYPE); + or->or_indexer = ix; + or->or_op = op; + if (op == SLAPI_OP_SUBSTRING) { + or->or_values = ss_filter_values (&bv, &(or->or_op)); + } else { + or->or_values = (struct berval**) + slapi_ch_malloc (2 * sizeof (struct berval*)); + or->or_values[0] = slapi_ch_bvdup0 (&bv); + or->or_values[1] = NULL; + } + { + auto struct berval** val = or->or_values; + if (val) for (; *val; ++val) { + LDAPDebug (LDAP_DEBUG_FILTER, "value \"%s\"\n", (*val)->bv_val, 0, 0); + } + } + if (or->or_op == SLAPI_OP_SUBSTRING) { + or->or_match_keys = ss_filter_keys (ix, or->or_values); + } else { + or->or_match_keys = slapi_ch_bvecdup ( + ix->ix_index (ix, or->or_values, NULL)); + } + slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, or); + slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, (void*)or_filter_destroy); + slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_MATCH_FN, (void*)or_filter_match); + slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_INDEX_FN, (void*)or_filter_index); +/* slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_REUSABLE, &reusable); */ +/* slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_RESET_FN, ?); to be implemented */ + rc = LDAP_SUCCESS; + } + } else { + LDAPDebug (LDAP_DEBUG_FILTER, "=> or_filter_create missing parameter(s)\n", 0, 0, 0); + } + LDAPDebug (LDAP_DEBUG_FILTER, "<= or_filter_create(%p) %i\n", (void*)or, rc, 0); + return rc; +} + +static indexer_t* +op_indexer_get (Slapi_PBlock* pb) +{ + auto void* obj = NULL; + if ( ! slapi_pblock_get (pb, SLAPI_PLUGIN_OBJECT, &obj)) { + return (indexer_t*)obj; + } + return NULL; +} + +static int +op_indexer_destroy (Slapi_PBlock* pb) +{ + auto indexer_t* ix = op_indexer_get (pb); + LDAPDebug (LDAP_DEBUG_FILTER, "op_indexer_destroy(%p)\n", (void*)ix, 0, 0); + if (ix != NULL) { + indexer_free (ix); + } + return 0; +} + +static int +op_index_entry (Slapi_PBlock* pb) + /* Compute collation keys (when writing an entry). */ +{ + auto indexer_t* ix = op_indexer_get (pb); + auto int rc; + struct berval** values; + if (ix != NULL && ix->ix_index != NULL && + !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUES, &values) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, ix->ix_index (ix, values, NULL))) { + rc = 0; + } else { + rc = LDAP_OPERATIONS_ERROR; + } + LDAPDebug (LDAP_DEBUG_FILTER, "op_index_entry(%p) %i\n", (void*)ix, rc, 0); + return rc; +} + +static int +op_index_search (Slapi_PBlock* pb) + /* Compute collation keys (when searching for entries). */ +{ + auto or_filter_t* or = or_filter_get (pb); + auto int rc = LDAP_OPERATIONS_ERROR; + if (or != NULL) { + auto indexer_t* ix = or->or_indexer; + struct berval** values; + if (or->or_index_keys == NULL && ix != NULL && ix->ix_index != NULL && + !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUES, &values)) { + or->or_index_keys = slapi_ch_bvecdup ( + ix->ix_index (ix, values, NULL)); + } + if (or->or_index_keys) { + rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, or->or_index_keys); + } + } + LDAPDebug (LDAP_DEBUG_FILTER, "op_index_search(%p) %i\n", (void*)or, rc, 0); + return rc; +} + +typedef struct ss_indexer_t { + char* ss_oid; /* ss_indexer->ix_oid && ".6" */ + indexer_t* ss_indexer; +} ss_indexer_t; + +static void +ss_indexer_free (ss_indexer_t* ss) +{ + slapi_ch_free((void**)&ss->ss_oid); + if (ss->ss_indexer != NULL) { + indexer_free (ss->ss_indexer); + ss->ss_indexer = NULL; + } + slapi_ch_free((void**)&ss); +} + +static ss_indexer_t* +ss_indexer_get (Slapi_PBlock* pb) +{ + auto void* obj = NULL; + if ( ! slapi_pblock_get (pb, SLAPI_PLUGIN_OBJECT, &obj)) { + return (ss_indexer_t*)obj; + } + return NULL; +} + +static void +ss_indexer_destroy (Slapi_PBlock* pb) +{ + auto ss_indexer_t* ss = ss_indexer_get (pb); + LDAPDebug (LDAP_DEBUG_FILTER, "ss_indexer_destroy(%p)\n", (void*)ss, 0, 0); + if (ss) { + ss_indexer_free (ss); + } +} + +#define SS_INDEX_LENGTH 3 /* characters */ + +static char ss_prefixI = '['; +static char ss_prefixM = '|'; +static char ss_prefixF = '}'; + +static struct berval ss_index_initial = {1, &ss_prefixI}; +static struct berval ss_index_middle = {1, &ss_prefixM}; +static struct berval ss_index_final = {1, &ss_prefixF}; + +static int +long_enough (struct berval* bval, size_t enough) +{ + if (bval) { + auto size_t len = 0; + auto char* next = bval->bv_val; + auto char* last = next + bval->bv_len; + while (next < last) { + LDAP_UTF8INC (next); + if (++len >= enough) { + if (next > last) next = last; + bval->bv_len = next - bval->bv_val; + return 1; + } + } + } + return !enough; +} + +static int +ss_index_entry (Slapi_PBlock* pb) + /* Compute substring index keys (when writing an entry). */ +{ + auto int rc = LDAP_OPERATIONS_ERROR; + auto size_t substringsLen = 0; + struct berval** values; + auto ss_indexer_t* ss = ss_indexer_get (pb); + auto indexer_t* ix = ss ? ss->ss_indexer : NULL; + if (ix != NULL && ix->ix_index != NULL && + !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUES, &values)) { + auto struct berval* substrings = NULL; + auto struct berval** prefixes = NULL; + auto struct berval** value; + for (value = values; *value != NULL; ++value) { + auto struct berval substring; + substring.bv_val = (*value)->bv_val; + substring.bv_len = (*value)->bv_len; + if (long_enough (&substring, SS_INDEX_LENGTH-1)) { + auto struct berval* prefix = &ss_index_initial; + auto size_t offset; + for (offset = 0; 1; ++offset) { + ++substringsLen; + substrings = (struct berval*) + slapi_ch_realloc ((void*)substrings, substringsLen * sizeof(struct berval)); + memcpy (&(substrings[substringsLen-1]), &substring, sizeof (struct berval)); + prefixes = (struct berval**) + slapi_ch_realloc ((void*)prefixes, substringsLen * sizeof(struct berval*)); + prefixes[substringsLen-1] = prefix; + + if (offset != 0) LDAP_UTF8INC (substring.bv_val); + substring.bv_len = (*value)->bv_len - (substring.bv_val - (*value)->bv_val); + if (long_enough (&substring, SS_INDEX_LENGTH)) { + prefix = &ss_index_middle; + } else if (long_enough (&substring, SS_INDEX_LENGTH-1)) { + prefix = &ss_index_final; + } else { + break; + } + } + } + } + if (substrings != NULL) { + auto struct berval** vector = (struct berval**) + slapi_ch_malloc ((substringsLen+1) * sizeof(struct berval*)); + auto size_t i; + for (i = 0; i < substringsLen; ++i) vector[i] = &(substrings[i]); + vector[substringsLen] = NULL; + rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, ix->ix_index (ix, vector, prefixes)); + slapi_ch_free((void**)&vector); + slapi_ch_free((void**)&substrings); + slapi_ch_free((void**)&prefixes); + } + } + LDAPDebug (LDAP_DEBUG_FILTER, "ss_index_entry(%p) %i %lu substrings\n", + (void*)ss, rc, (unsigned long)substringsLen); + return rc; +} + +static int +ss_index_search (Slapi_PBlock* pb) + /* Compute substring search keys (when searching for entries). */ +{ + auto int rc = LDAP_OPERATIONS_ERROR; + auto or_filter_t* or = or_filter_get (pb); + if (or) { + if (or->or_index_keys == NULL /* not yet computed */ && + or->or_values && or->or_indexer && or->or_indexer->ix_index) { + auto size_t substringsLen = 0; + auto struct berval* substrings = NULL; + auto struct berval** prefixes = NULL; + auto struct berval** value; + for (value = or->or_values; *value != NULL; ++value) { + auto size_t offset; + auto struct berval substring; + substring.bv_val = (*value)->bv_val; + for (offset = 0; 1; ++offset, LDAP_UTF8INC (substring.bv_val)) { + auto struct berval* prefix = NULL; + substring.bv_len = (*value)->bv_len - (substring.bv_val - (*value)->bv_val); + if (offset == 0 && value == or->or_values) { + if (long_enough (&substring, SS_INDEX_LENGTH - 1)) { + prefix = &ss_index_initial; + } + } else if (value[1] != NULL) { + if (long_enough (&substring, SS_INDEX_LENGTH)) { + prefix = &ss_index_middle; + } + } else if (long_enough (&substring, SS_INDEX_LENGTH)) { + prefix = &ss_index_middle; + } else if (long_enough (&substring, SS_INDEX_LENGTH-1)) { + prefix = &ss_index_final; + } + if (prefix == NULL) break; + ++substringsLen; + substrings = (struct berval*) + slapi_ch_realloc ((void*)substrings, substringsLen * sizeof(struct berval)); + memcpy (&(substrings[substringsLen-1]), &substring, sizeof (struct berval)); + prefixes = (struct berval**) + slapi_ch_realloc ((void*)prefixes, substringsLen * sizeof(struct berval*)); + prefixes[substringsLen-1] = prefix; + } + } + if (substrings != NULL) { + auto indexer_t* ix = or->or_indexer; + auto struct berval** vector = (struct berval**) + slapi_ch_malloc ((substringsLen+1) * sizeof(struct berval*)); + auto size_t i; + for (i = 0; i < substringsLen; ++i) vector[i] = &(substrings[i]); + vector[substringsLen] = NULL; + or->or_index_keys = slapi_ch_bvecdup ( + ix->ix_index (ix, vector, prefixes)); + slapi_ch_free((void**)&vector); + slapi_ch_free((void**)&substrings); + slapi_ch_free((void**)&prefixes); + } + } + if (or->or_index_keys) { + rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, or->or_index_keys); + } + } + LDAPDebug (LDAP_DEBUG_FILTER, "ss_index_search(%p) %i\n", (void*)or, rc, 0); + return rc; +} + +static int +ss_indexable (struct berval** values) + /* at least one of the values is long enough to index */ +{ + auto struct berval** val = values; + if (val) for (; *val; ++val) { + auto struct berval value; + value.bv_val = (*val)->bv_val; + value.bv_len = (*val)->bv_len; + if (val == values) { /* initial */ + if (long_enough (&value, SS_INDEX_LENGTH-1)) return 1; + } else if (val[1]) { /* middle */ + if (long_enough (&value, SS_INDEX_LENGTH)) return 1; + } else { /* final */ + if (long_enough (&value, SS_INDEX_LENGTH-1)) return 1; + } + } + return 0; +} + +static struct berval ss_one_berval = {0,0}; +static struct berval* ss_one_value[2] = {&ss_one_berval, NULL}; + +static int +or_filter_index (Slapi_PBlock* pb) + /* Return an indexer and values that accelerate the given filter. */ +{ + auto or_filter_t* or = or_filter_get (pb); + auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + auto IFP mrINDEX_FN = NULL; + auto struct berval** mrVALUES = NULL; + auto char* mrOID = NULL; + auto int mrQUERY_OPERATOR; + if (or && or->or_indexer && or->or_indexer->ix_index) { + switch (or->or_op) { + case SLAPI_OP_LESS: + case SLAPI_OP_LESS_OR_EQUAL: + case SLAPI_OP_EQUAL: + case SLAPI_OP_GREATER_OR_EQUAL: + case SLAPI_OP_GREATER: + mrINDEX_FN = op_index_search; + mrVALUES = or->or_values; + mrOID = or->or_indexer->ix_oid; + mrQUERY_OPERATOR = or->or_op; + break; + case SLAPI_OP_SUBSTRING: + if (ss_indexable (or->or_values)) { + if (or->or_oid == NULL) { + auto const size_t len = strlen (or->or_indexer->ix_oid); + or->or_oid = slapi_ch_malloc (len + 3); + memcpy (or->or_oid, or->or_indexer->ix_oid, len); + sprintf (or->or_oid + len, ".%1i", SLAPI_OP_SUBSTRING); + } + mrINDEX_FN = ss_index_search; + mrVALUES = ss_one_value; + mrOID = or->or_oid; + mrQUERY_OPERATOR = SLAPI_OP_EQUAL; + } + break; + default: /* unsupported operator */ + break; + } + } + if (mrINDEX_FN != NULL && + !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, or)) && + !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_TYPE, or->or_type)) && + !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEX_FN, (void*)mrINDEX_FN)) && + !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_VALUES, mrVALUES)) && + !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_OID, mrOID))) { + rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_QUERY_OPERATOR, &mrQUERY_OPERATOR); + } + LDAPDebug (LDAP_DEBUG_FILTER, "or_filter_index(%p) %i\n", + (void*)(or ? or->or_indexer : NULL), rc, 0); + return rc; +} + +static int +or_indexer_create (Slapi_PBlock* pb) +{ + auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */ + auto char* mrOID = NULL; + auto void* mrOBJECT = NULL; + if (slapi_pblock_get (pb, SLAPI_PLUGIN_MR_OID, &mrOID) || mrOID == NULL) { + LDAPDebug (LDAP_DEBUG_FILTER, "=> or_indexer_create: no OID parameter\n", 0, 0, 0); + } else { + auto indexer_t* ix = indexer_create (mrOID); + auto char* mrTYPE = NULL; + slapi_pblock_get (pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE); + LDAPDebug (LDAP_DEBUG_FILTER, "=> or_indexer_create(oid %s; type %s)\n", + mrOID, mrTYPE ? mrTYPE : "<NULL>", 0); + if (ix != NULL) { + if (ix->ix_index != NULL && + !slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, ix) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_OID, ix->ix_oid) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEX_FN, (void*)op_index_entry) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, (void*)op_indexer_destroy)) { + mrOBJECT = ix; + rc = 0; /* success */ + } else { + indexer_free (ix); + } + } else { /* mrOID does not identify an ordering rule. */ + /* Is it an ordering rule OID with the substring suffix? */ + auto size_t oidlen = strlen (mrOID); + if (oidlen > 2 && mrOID[oidlen-2] == '.' && + atoi (mrOID + oidlen - 1) == SLAPI_OP_SUBSTRING) { + auto char* or_oid = slapi_ch_strdup (mrOID); + or_oid [oidlen-2] = '\0'; + ix = indexer_create (or_oid); + if (ix != NULL) { + auto ss_indexer_t* ss = (ss_indexer_t*) slapi_ch_malloc (sizeof (ss_indexer_t)); + ss->ss_indexer = ix; + oidlen = strlen (ix->ix_oid); + ss->ss_oid = slapi_ch_malloc (oidlen + 3); + memcpy (ss->ss_oid, ix->ix_oid, oidlen); + sprintf (ss->ss_oid + oidlen, ".%1i", SLAPI_OP_SUBSTRING); + if (ix->ix_index != NULL && + !slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, ss) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_OID, ss->ss_oid) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEX_FN, (void*)ss_index_entry) && + !slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, (void*)ss_indexer_destroy)) { + mrOBJECT = ss; + rc = 0; /* success */ + } else { + ss_indexer_free (ss); + } + } + slapi_ch_free((void**)&or_oid); + } + } + } + LDAPDebug (LDAP_DEBUG_FILTER, "<= or_indexer_create(%p) %i\n", mrOBJECT, rc, 0); + return rc; +} + +static Slapi_PluginDesc pdesc = { "orderingrule", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "internationalized ordering rule plugin" }; + +#define SLAPI_ORPLUGIN_NAME pdesc.spd_description + +int /* LDAP error code */ +orderingRule_init (Slapi_PBlock* pb) +{ + int rc; + int argc; + char** argv; + char* cfgpath; + +/* if (!(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_PRIVATE, ...)) && + !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_CLOSE_FN, ...))) +*/ + +#ifdef USE_HPUX_CC + /* not needed with ICU + shl_load ( "../lib/libnsbrk30.sl", BIND_IMMEDIATE, 0L ); + shl_load ( "../lib/libnscnv30.sl", BIND_IMMEDIATE, 0L ); + shl_load ( "../lib/libnscol30.sl", BIND_IMMEDIATE, 0L ); + shl_load ( "../lib/libnsfmt30.sl", BIND_IMMEDIATE, 0L ); + shl_load ( "../lib/libnsres30.sl", BIND_IMMEDIATE, 0L ); + shl_load ( "../lib/libnsuni30.sl", BIND_IMMEDIATE, 0L ); + */ +#endif + + if ( slapi_pblock_get( pb, SLAPI_CONFIG_DIRECTORY, &cfgpath ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, SLAPI_ORPLUGIN_NAME, + "Unable to retrieve slapd configuration pathname; using default\n" ); + cfgpath = NULL; + } + + collation_init( cfgpath ); + if (!slapi_pblock_get (pb, SLAPI_PLUGIN_ARGC, &argc) && + !slapi_pblock_get (pb, SLAPI_PLUGIN_ARGV, &argv) && + argc > 0) { + collation_read_config (argv[0]); + } + { + slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEXER_CREATE_FN, (void*)or_indexer_create); + rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_CREATE_FN, (void*)or_filter_create); + } + if ( rc == 0 ) { + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc ); + } + LDAPDebug (LDAP_DEBUG_FILTER, "orderingRule_init %i\n", rc, 0, 0); + return rc; +} diff --git a/ldap/servers/plugins/collation/orfilter.h b/ldap/servers/plugins/collation/orfilter.h new file mode 100644 index 00000000..85a32592 --- /dev/null +++ b/ldap/servers/plugins/collation/orfilter.h @@ -0,0 +1,10 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _ORFILTER_H_ +#define _ORFILTER_H_ + +#endif diff --git a/ldap/servers/plugins/cos/Makefile b/ldap/servers/plugins/cos/Makefile new file mode 100644 index 00000000..de540f5a --- /dev/null +++ b/ldap/servers/plugins/cos/Makefile @@ -0,0 +1,79 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libcos +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./cos.def +endif + +COS_OBJS = cos.o cos_cache.o +OBJS = $(addprefix $(OBJDEST)/, $(COS_OBJS)) + +COS_DLL = cos-plugin + +INCLUDES += -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +COS_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +COS= $(addprefix $(LIBDIR)/, $(COS_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(COS) + +ifeq ($(ARCH), WINNT) +$(COS): $(OBJS) $(COS_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(COS_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(COS): $(OBJS) $(COS_DLL_OBJ) + $(LINK_DLL) $(COS_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(COS_DLL_OBJ) +endif + $(RM) $(COS) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) diff --git a/ldap/servers/plugins/cos/cos.c b/ldap/servers/plugins/cos/cos.c new file mode 100644 index 00000000..5a077646 --- /dev/null +++ b/ldap/servers/plugins/cos/cos.c @@ -0,0 +1,266 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include <stdio.h> +#include <string.h> +#include "portable.h" +#include "nspr.h" +#include "slapi-plugin.h" +#include "slapi-private.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" +#include "cos_cache.h" +#include "vattr_spi.h" + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +/*** secret slapd stuff ***/ + +/* + these are required here because they are not available + in any public header. They must exactly match their + counterparts in the server or they will fail to work + correctly. +*/ + +/*** from proto-slap.h ***/ + +int slapd_log_error_proc( char *subsystem, char *fmt, ... ); + +/*** from ldaplog.h ***/ + +/* edited ldaplog.h for LDAPDebug()*/ +#ifndef _LDAPLOG_H +#define _LDAPLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define LDAP_DEBUG_TRACE 0x00001 /* 1 */ +#define LDAP_DEBUG_ANY 0x04000 /* 16384 */ +#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */ + +/* debugging stuff */ +# ifdef _WIN32 + extern int *module_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( *module_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# else /* _WIN32 */ + extern int slapd_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( slapd_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# endif /* Win32 */ + +#ifdef __cplusplus +} +#endif + +#endif /* _LDAP_H */ + +/*** end secrets ***/ + +#define COS_PLUGIN_SUBSYSTEM "cos-plugin" /* used for logging */ + +/* subrelease in the following version info is for odd-ball cos releases + * which do not fit into a general release, this can be used for beta releases + * and other (this version stuff is really to help outside applications which + * may wish to update cos decide whether the cos version they want to update to + * is a higher release than the installed plugin) + * + * note: release origin is 00 for directory server + * sub-release should be: + * 50 for initial RTM products + * from 0 increasing for alpha/beta releases + * from 51 increasing for patch releases + */ +#define COS_VERSION 0x00050050 /* version format: 0x release origin 00 major 05 minor 00 sub-release 00 */ + +/* other function prototypes */ +int cos_init( Slapi_PBlock *pb ); +int cos_compute(computed_attr_context *c,char* type,Slapi_Entry *e,slapi_compute_output_t outputfn); +int cos_start( Slapi_PBlock *pb ); +int cos_close( Slapi_PBlock *pb ); +int cos_post_op( Slapi_PBlock *pb ); + + +static Slapi_PluginDesc pdesc = { "cos", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "class of service plugin" }; + +static void * cos_plugin_identity = NULL; + + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/* +** Plugin identity mgmt +*/ + +void cos_set_plugin_identity(void * identity) +{ + cos_plugin_identity=identity; +} + +void * cos_get_plugin_identity() +{ + return cos_plugin_identity; +} + +int cos_version() +{ + return COS_VERSION; +} + +/* + cos_init + -------- + adds our callbacks to the list +*/ +int cos_init( Slapi_PBlock *pb ) +{ + int ret = 0; + void * plugin_identity=NULL; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_init\n",0,0,0); + + /* + ** Store the plugin identity for later use. + ** Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + PR_ASSERT (plugin_identity); + cos_set_plugin_identity(plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) cos_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, + (void *) cos_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, + (void *) cos_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, + (void *) cos_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, + (void *) cos_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) cos_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM, + "cos_init: failed to register plugin\n" ); + ret = -1; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_init\n",0,0,0); + return ret; +} + + +/* + cos_start + --------- + This function registers the computed attribute evaluator + and inits the cos cache. + It is called after cos_init. +*/ +int cos_start( Slapi_PBlock *pb ) +{ + int ret = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_start\n",0,0,0); + + if( !cos_cache_init() ) + { + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: ready for service\n",0,0,0); + } + else + { + /* problems we are hosed */ + cos_cache_stop(); + LDAPDebug( LDAP_DEBUG_ANY, "cos_start: failed to initialise\n",0,0,0); + ret = -1; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_start\n",0,0,0); + return ret; +} + +/* + cos_close + --------- + closes down the cache +*/ +int cos_close( Slapi_PBlock *pb ) +{ + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_close\n",0,0,0); + + cos_cache_stop(); + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_close\n",0,0,0); + + return 0; +} + +/* + cos_compute + ----------- + called when evaluating named attributes in a search + and attributes remain unfound in the entry, + this function checks the attribute for a match with + those in the class of service definitions, and if a + match is found, adds the attribute and value to the + output list + + returns + 0 on success + 1 on outright failure + -1 when doesn't know about attribute +*/ +int cos_compute(computed_attr_context *c,char* type,Slapi_Entry *e,slapi_compute_output_t outputfn) +{ + int ret = -1; + + return ret; +} + + +/* + cos_post_op + ----------- + Catch all for all post operations that change entries + in some way - this simply notifies the cache of a + change - the cache decides if action is necessary +*/ +int cos_post_op( Slapi_PBlock *pb ) +{ + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_post_op\n",0,0,0); + + cos_cache_change_notify(pb); + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_post_op\n",0,0,0); + return 0; /* always succeed */ +} + diff --git a/ldap/servers/plugins/cos/cos.def b/ldap/servers/plugins/cos/cos.def new file mode 100644 index 00000000..484d9c6a --- /dev/null +++ b/ldap/servers/plugins/cos/cos.def @@ -0,0 +1,11 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7 Class Of Service Plugin' +EXPORTS + cos_init @2 + plugin_init_debug_level @3 + cos_version @4 diff --git a/ldap/servers/plugins/cos/cos_cache.c b/ldap/servers/plugins/cos/cos_cache.c new file mode 100644 index 00000000..165f47d1 --- /dev/null +++ b/ldap/servers/plugins/cos/cos_cache.c @@ -0,0 +1,3566 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + The cos cache keeps in memory all of + the data related to cos. This allows + very fast lookups at the expense of RAM. + All meta data is indexed, allowing fast + binary search lookups. + The cache is not dynamic, in the sense + that it does not iteratively modify + itself as changes are made to the cos + meta-data. Rather, it is designed to + be fast to read, with non-locking + multiple thread access to the cache, + at the expense of modification speed. + This means that when changes do occur, + the cache must be rebuilt from scratch. + However, this is achieved in such a way, + so as to allow cache queries during the + building of the new cache - so once a + cache has been built, there is no down + time. + Of course, the configuration of the cos meta + data is likely to be a thing which does not + happen often. Any other use, is probably a + mis-use of the mechanism, and certainly will + suffer from performance problems. +*/ + +#include <stdio.h> +#include <string.h> +#include "portable.h" +#include "slapi-plugin.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" + +/* this is naughty, but the api for backend state change is currently here */ +#include "slapi-private.h" + +/* include NSPR header files */ +#include "prthread.h" +#include "prlock.h" +#include "prerror.h" +#include "prcvar.h" +#include "prio.h" +#include "vattr_spi.h" + +#include "cos_cache.h" + +#include "views.h" +static void **views_api; + +/*** secret functions and structs in slapd ***/ + +/* + these are required here because they are not available + in any public header. They must exactly match their + counterparts in the server or they will fail to work + correctly. +*/ + +/*** from slap.h ***/ + +struct objclass { + char *oc_name; /* NAME */ + char *oc_desc; /* DESC */ + char *oc_oid; /* object identifier */ + char *oc_superior; /* SUP -- XXXmcs: should be an array */ + PRUint8 oc_kind; /* ABSTRACT/STRUCTURAL/AUXILIARY */ + PRUint8 oc_flags; /* misc. flags, e.g., OBSOLETE */ + char **oc_required; + char **oc_allowed; + char **oc_orig_required; /* MUST */ + char **oc_orig_allowed; /* MAY */ + char **oc_origin; /* X-ORIGIN extension */ + struct objclass *oc_next; +}; + +/*** from proto-slap.h ***/ + +int config_get_schemacheck(); +void oc_lock_read( void ); +void oc_unlock( void ); +struct objclass* g_get_global_oc_nolock(); +int slapd_log_error_proc( char *subsystem, char *fmt, ... ); + +/*** from ldaplog.h ***/ + +/* edited ldaplog.h for LDAPDebug()*/ +#ifndef _LDAPLOG_H +#define _LDAPLOG_H + +/* defined in cos.c */ +void * cos_get_plugin_identity(); + + +#ifdef __cplusplus +extern "C" { +#endif + +#define LDAP_DEBUG_TRACE 0x00001 /* 1 */ +#define LDAP_DEBUG_ANY 0x04000 /* 16384 */ +#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */ + +/* debugging stuff */ +# ifdef _WIN32 + extern int *module_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( *module_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# else /* _WIN32 */ + extern int slapd_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( slapd_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# endif /* Win32 */ + +#ifdef __cplusplus +} +#endif + +#endif /* _LDAP_H */ + +/*** end secrets ***/ + +#define COS_PLUGIN_SUBSYSTEM "cos-plugin" /* used for logging */ + +#define COSTYPE_BADTYPE 0 +#define COSTYPE_CLASSIC 1 +#define COSTYPE_POINTER 2 +#define COSTYPE_INDIRECT 3 +#define COS_DEF_ERROR_NO_TEMPLATES -2 + +/* the global plugin handle */ +static volatile vattr_sp_handle *vattr_handle = NULL; + +static cos_cache_notify_flag = 0; + +/* service definition cache structs */ + +/* cosIndexedLinkedList: provides an object oriented type interface to + link lists where each element contains an index for the entire + list. All structures that contain this one must specify this one + as the first member otherwise the supporting functions will not work. + + {PARPAR} The indexing ability is not currently used since the + fastest lookup is achieved via a cache level index of all attributes, + however this mechanism may prove useful in the future +*/ +struct _cosIndexedLinkedList +{ + void *pNext; + void **index; +}; +typedef struct _cosIndexedLinkedList cosIndexedLinkedList; + +struct _cosAttrValue +{ + cosIndexedLinkedList list; + char *val; +}; +typedef struct _cosAttrValue cosAttrValue; + +struct _cosAttribute +{ + cosIndexedLinkedList list; + char *pAttrName; + cosAttrValue *pAttrValue; + cosAttrValue *pObjectclasses; + int attr_override; + int attr_operational; + int attr_operational_default; + int attr_cos_merge; + void *pParent; +}; +typedef struct _cosAttribute cosAttributes; + +struct _cosTemplate +{ + cosIndexedLinkedList list; + cosAttrValue *pDn; + cosAttrValue *pObjectclasses; + cosAttributes *pAttrs; + char *cosGrade; + int template_default; + void *pParent; + unsigned long cosPriority; +}; + +typedef struct _cosTemplate cosTemplates; + +struct _cosDefinition +{ + cosIndexedLinkedList list; + int cosType; + cosAttrValue *pDn; + cosAttrValue *pCosTargetTree; + cosAttrValue *pCosTemplateDn; + cosAttrValue *pCosSpecifier; + cosAttrValue *pCosAttrs; + cosAttrValue *pCosOverrides; + cosAttrValue *pCosOperational; + cosAttrValue *pCosOpDefault; + cosAttrValue *pCosMerge; + cosTemplates *pCosTmps; +}; +typedef struct _cosDefinition cosDefinitions; + +struct _cos_cache +{ + cosDefinitions *pDefs; + cosAttributes **ppAttrIndex; + int attrCount; + char **ppTemplateList; + int templateCount; + int refCount; + int vattr_cacheable; +}; +typedef struct _cos_cache cosCache; + +/* cache manipulation function prototypes*/ +static cosCache *pCache; /* always the current global cache, only use getref to get */ + +/* the place to start if you want a new cache */ +static int cos_cache_create(); + +/* cache index related functions */ +static int cos_cache_index_all(cosCache *pCache); +static int cos_cache_attr_compare(const void *e1, const void *e2); +static int cos_cache_template_index_compare(const void *e1, const void *e2); +static int cos_cache_string_compare(const void *e1, const void *e2); +static int cos_cache_template_index_bsearch(const char *dn); +static int cos_cache_attr_index_bsearch( const cosCache *pCache, const cosAttributes *key, int lower, int upper ); + +/* the multi purpose list creation function, pass it something and it links it */ +static void cos_cache_add_ll_entry(void **attrval, void *theVal, int ( *compare )(const void *elem1, const void *elem2 )); + +/* cosAttrValue manipulation */ +static int cos_cache_add_attrval(cosAttrValue **attrval, char *val); +static void cos_cache_del_attrval_list(cosAttrValue **pVal); +static int cos_cache_attrval_count(cosAttrValue *pVal); +static int cos_cache_attrval_exists(cosAttrValue *pAttrs, const char *val); + +/* cosAttributes manipulation */ +static int cos_cache_add_attr(cosAttributes **pAttrs, char *name, cosAttrValue *val); +static void cos_cache_del_attr_list(cosAttributes **pAttrs); +static int cos_cache_find_attr(cosCache *pCache, char *type); +static int cos_cache_total_attr_count(cosCache *pCache); +static int cos_cache_cos_2_slapi_valueset(cosAttributes *pAttr, Slapi_ValueSet **out_vs); +static int cos_cache_cmp_attr(cosAttributes *pAttr, Slapi_Value *test_this, int *result); + +/* cosTemplates manipulation */ +static int cos_cache_add_dn_tmpls(char *dn, cosAttrValue *pCosSpecifier, cosAttrValue *pAttrs, cosTemplates **pTmpls); +static int cos_cache_add_tmpl(cosTemplates **pTemplates, cosAttrValue *dn, cosAttrValue *objclasses, cosAttrValue *pCosSpecifier, cosAttributes *pAttrs,cosAttrValue *cosPriority); +#if 0 +static int cos_cache_del_tmpl(cosTemplates *pTemplates, char *dn); +#endif + +/* cosDefinitions manipulation */ +static int cos_cache_build_definition_list(cosDefinitions **pDefs, int *vattr_cacheable); +static int cos_cache_add_dn_defs(char *dn, cosDefinitions **pDefs, int *vattr_cacheable); +static int cos_cache_add_defn(cosDefinitions **pDefs, cosAttrValue **dn, int cosType, cosAttrValue **tree, cosAttrValue **tmpDn, cosAttrValue **spec, cosAttrValue **pAttrs, cosAttrValue **pOverrides, cosAttrValue **pOperational, cosAttrValue **pCosMerge, cosAttrValue **pCosOpDefault); +static int cos_cache_entry_is_cos_related( Slapi_Entry *e); +#if 0 +static int cos_cache_del_defn(cosDefinitions *pDefs, char *dn); +#endif + +/* schema checking */ +static int cos_cache_schema_check(cosCache *pCache, int cache_attr_index, Slapi_Attr *pObjclasses); +static int cos_cache_schema_build(cosCache *pCache); +static void cos_cache_del_schema(cosCache *pCache); + +/* special cos scheme implimentations (special = other than cos classic) */ +static int cos_cache_follow_pointer( vattr_context *context, const char *dn, char *type, Slapi_ValueSet **out_vs, Slapi_Value *test_this, int *result, int flags ); + + +/* this dude is the thread function which performs dynamic config of the cache */ +static void cos_cache_wait_on_change(void *arg); + +/* this gets called when a backend changes state */ +void cos_cache_backend_state_change(void *handle, char *be_name, + int old_be_state, int new_be_state); + +/* operation callbacks for vattr service */ +static int cos_cache_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint); +static int cos_cache_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint); +static int cos_cache_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags); +static int cos_cache_query_attr(cos_cache *ptheCache, vattr_context *context, Slapi_Entry *e, char *type, Slapi_ValueSet **out_attr, Slapi_Value *test_this, int *result, int *ops); +static void cos_cache_query_attr_free(struct berval ***vals); /* deprecated */ + + +/* + compares s2 to s1 starting from end of string until the beginning of either + matches result in the s2 value being clipped from s1 with a NULL char + and 1 being returned as opposed to 0 +*/ +static int cos_cache_backwards_stricmp_and_clip(char*s1,char*s2); + +/* module level thread control stuff */ + +static PRThread *cos_tid = NULL; +static int keeprunning = 0; +static int started = 0; + +static Slapi_Mutex *cache_lock; +static Slapi_Mutex *change_lock; +static Slapi_Mutex *start_lock; +static Slapi_Mutex *stop_lock; +static Slapi_CondVar *something_changed = NULL; +static Slapi_CondVar *start_cond = NULL; + + +/* + cos_cache_init + -------------- + starts up the thread which waits for changes and + fires off the cache re-creation when one is detected + also registers vattr callbacks +*/ +int cos_cache_init() +{ + int ret = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_init\n",0,0,0); + + slapi_vattrcache_cache_none(); + cache_lock = slapi_new_mutex(); + change_lock = slapi_new_mutex(); + stop_lock = slapi_new_mutex(); + something_changed = slapi_new_condvar(change_lock); + keeprunning =1; + start_lock = slapi_new_mutex(); + start_cond = slapi_new_condvar(start_lock); + started = 0; + + if ( stop_lock == NULL || + change_lock == NULL || + cache_lock == NULL || + stop_lock == NULL || + start_lock == NULL || + start_cond == NULL || + something_changed == NULL) + { + slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM, + "cos_cache_init: cannot create mutexes\n" ); + ret = -1; + goto out; + } + + /* grab the views interface */ + if(slapi_apib_get_interface(Views_v1_0_GUID, &views_api)) + { + /* lets be tolerant if views is disabled */ + views_api = 0; + } + + if (slapi_vattrspi_register((vattr_sp_handle **)&vattr_handle, + cos_cache_vattr_get, + cos_cache_vattr_compare, + cos_cache_vattr_types) != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM, + "cos_cache_init: cannot register as service provider\n" ); + ret = -1; + goto out; + } + + if ((cos_tid = PR_CreateThread (PR_USER_THREAD, + cos_cache_wait_on_change, + NULL, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL ) + { + slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM, + "cos_cache_init: PR_CreateThread failed\n" ); + ret = -1; + goto out; + } + + /* wait for that thread to get started */ + if (ret == 0) { + slapi_lock_mutex(start_lock); + while (!started) { + while (slapi_wait_condvar(start_cond, NULL) == 0); + } + slapi_unlock_mutex(start_lock); + } + + +out: + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_init\n",0,0,0); + return ret; +} + +/* + cos_cache_wait_on_change + ------------------------ + sit around waiting on a notification that something has + changed, then fires off the cache re-creation + + The way this stuff is written, we can look for the + template for a definiton, before the template has been added--I think + that's OK--we'll see it when it arrives--get this error message: + "skipping cos definition cn=cosPointerGenerateSt,ou=People,o=cosAll--no templates found" + +*/ +static void cos_cache_wait_on_change(void *arg) +{ + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_wait_on_change thread\n",0,0,0); + + slapi_lock_mutex(stop_lock); + slapi_lock_mutex(change_lock); + + /* first register our backend state change func (we'll use func pointer as handle) */ + slapi_register_backend_state_change((void *)cos_cache_backend_state_change, cos_cache_backend_state_change); + + pCache = 0; + + /* create initial cache */ + cos_cache_create(); + + slapi_lock_mutex(start_lock); + started = 1; + slapi_notify_condvar(start_cond, 1); + slapi_unlock_mutex(start_lock); + + while(keeprunning) + { + slapi_unlock_mutex(change_lock); + slapi_lock_mutex(change_lock); + if ( !cos_cache_notify_flag && keeprunning) { + /* + * Nothing to do right now, so go to sleep--as + * we have the mutex, we are sure to wait before any modify + * thread notifies our condvar, and so we will not miss any + * notifications, including the shutdown notification. + */ + slapi_wait_condvar( something_changed, NULL ); + } else { + /* Something to do...do it below */ + } + /* + * We're here because: + * 1. we were asleep and got a signal, on our condvar OR + * 2. we were about to wait on the condvar and noticed that a modfiy + * thread + * had passed, setting the cos_cache_notify_flag and notifying us-- + * we did not wait in that case as we would have missed the notify + * (notify when noone is waiting == no-op). + * before we go running off doing lots of stuff lets check if we should stop + */ + if(keeprunning) { + cos_cache_create(); + } + cos_cache_notify_flag = 0; /* Dealt with it */ + }/* while */ + + /* shut down the cache */ + slapi_unlock_mutex(change_lock); + slapi_unlock_mutex(stop_lock); + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_wait_on_change thread exit\n",0,0,0); +} + +/* + cos_cache_create + --------------------- + Walks the definitions in the DIT and creates the cache. + Once created, it swaps the new cache for the old one, + releasing its refcount to the old cache and allowing it + to be destroyed. +*/ +static int cos_cache_create() +{ + int ret = -1; + cosCache *pNewCache; + static int firstTime = 1; + int cache_built = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_create\n",0,0,0); + + pNewCache = (cosCache*)slapi_ch_malloc(sizeof(cosCache)); + if(pNewCache) + { + pNewCache->pDefs = 0; + pNewCache->refCount = 1; /* 1 is for us */ + pNewCache->vattr_cacheable = 0; /* default is not cacheable */ + + ret = cos_cache_build_definition_list(&(pNewCache->pDefs), &(pNewCache->vattr_cacheable)); + if(!ret) + { + /* OK, we have a cache, lets add indexing for + that faster than slow feeling */ + + ret = cos_cache_index_all(pNewCache); + if(ret == 0) + { + /* right, indexed cache, lets do our duty for the schema */ + + ret = cos_cache_schema_build(pNewCache); + if(ret == 0) + { + /* now to swap the new cache for the old cache */ + cosCache *pOldCache; + + slapi_lock_mutex(cache_lock); + + /* turn off caching until the old cache is done */ + if(pCache) + { + slapi_vattrcache_cache_none(); + + /* + * be sure not to uncache other stuff + * like roles if there is no change in + * state + */ + if(pCache->vattr_cacheable) + slapi_entrycache_vattrcache_watermark_invalidate(); + } + else + { + if(pNewCache && pNewCache->vattr_cacheable) + { + slapi_vattrcache_cache_all(); + } + } + + pOldCache = pCache; + pCache = pNewCache; + + slapi_unlock_mutex(cache_lock); + + if(pOldCache) + cos_cache_release(pOldCache); + + cache_built = 1; + } + else + { + /* we should not go on without proper schema checking */ + cos_cache_release(pNewCache); + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_create: failed to cache the schema\n",0,0,0); + } + } + else + { + /* currently we cannot go on without the indexes */ + cos_cache_release(pNewCache); + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_create: failed to index cache\n",0,0,0); + } + } + else + { + if(firstTime) + { + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_create: cos disabled\n",0,0,0); + firstTime = 0; + } + + slapi_ch_free((void**)&pNewCache); + } + } + else + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_create: memory allocation failure\n",0,0,0); + + + /* make sure we have a new cache */ + if(!cache_built) + { + /* we do not have a new cache, must make sure the old cache is destroyed */ + + cosCache *pOldCache; + + slapi_lock_mutex(cache_lock); + + slapi_vattrcache_cache_none(); + + /* + * be sure not to uncache other stuff + * like roles if there is no change in + * state + */ + if(pCache && pCache->vattr_cacheable) + slapi_entrycache_vattrcache_watermark_invalidate(); + + pOldCache = pCache; + pCache = NULL; + + slapi_unlock_mutex(cache_lock); + + if(pOldCache) + cos_cache_release(pOldCache); /* release our reference to the old cache */ + + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_create\n",0,0,0); + return ret; +} + + +/* + cos_cache_build_definition_list + ------------------------------- + builds the list of cos definitions by searching for them throughout the DIT +*/ +static int cos_cache_build_definition_list(cosDefinitions **pDefs, int *vattr_cacheable) +{ + int ret = 0; + Slapi_PBlock *pSuffixSearch = 0; + Slapi_Entry **pSuffixList = 0; + Slapi_Attr *suffixAttr; + struct berval **suffixVals; + char *attrType = 0; + char *attrs[2]; + int suffixIndex = 0; + int valIndex = 0; + int cos_def_available = 0; + static int firstTime = 1; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_build_definition_list\n",0,0,0); + + /* + the class of service definitions may be anywhere in the DIT, + so our first task is to find them. + */ + + attrs[0] = "namingcontexts"; + attrs[1] = 0; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: Building class of service cache after status change.\n",0,0,0); + + /* + * XXXrbyrne: this looks really ineficient--should be using + * slapi_get_next_suffix(), rather than searching for namingcontexts. + */ + + pSuffixSearch = slapi_search_internal("",LDAP_SCOPE_BASE,"(objectclass=*)",NULL,attrs,0); + if(pSuffixSearch) + slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_RESULT, &ret); + + if(pSuffixSearch && ret == LDAP_SUCCESS) + { + /* iterate through the suffixes and search for cos definitions */ + slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &pSuffixList); + if(pSuffixList) + { + while(pSuffixList[suffixIndex]) + { + if(!slapi_entry_first_attr(pSuffixList[suffixIndex], &suffixAttr)) + { + do + { + attrType = 0; + slapi_attr_get_type(suffixAttr, &attrType); + if(attrType && !slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"namingcontexts")) + { + if(!slapi_attr_get_bervals_copy(suffixAttr, &suffixVals)) + { + valIndex = 0; + + if(suffixVals) + { + while(suffixVals[valIndex]) + { + /* here's a suffix, lets search it... */ + if(suffixVals[valIndex]->bv_val) + if(!cos_cache_add_dn_defs(suffixVals[valIndex]->bv_val ,pDefs, vattr_cacheable)) + cos_def_available = 1; + + valIndex++; + } + + + ber_bvecfree( suffixVals ); + suffixVals = NULL; + } + } + } + + } while(!slapi_entry_next_attr(pSuffixList[suffixIndex], suffixAttr, &suffixAttr)); + } + suffixIndex++; + } + } + } + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_build_definition_list: failed to find suffixes\n",0,0,0); + ret = -1; + } + + if(cos_def_available == 0) + { + if(firstTime) + { + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_build_definition_list: Found no cos definitions, cos disabled while waiting for updates\n",0,0,0); + firstTime = 0; + } + + ret = -1; + } + else + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: Class of service cache built.\n",0,0,0); + + /* clean up */ + if(pSuffixSearch) + { + slapi_free_search_results_internal(pSuffixSearch); + slapi_pblock_destroy(pSuffixSearch); + } + + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_build_definition_list\n",0,0,0); + return ret; +} + + +/* struct to support search callback API */ +struct dn_defs_info { + cosDefinitions **pDefs; + int vattr_cacheable; + int ret; +}; + +/* + * Currently, always returns 0 to continue the search for definitions, even + * if a particular attempt to add a definition fails: info.ret gets set to + * zero only if we succed to add a def. +*/ +static int cos_dn_defs_cb (Slapi_Entry* e, void *callback_data) { + struct dn_defs_info *info; + cosAttrValue **pSneakyVal = 0; + cosAttrValue *pObjectclass = 0; + cosAttrValue *pCosTargetTree = 0; + cosAttrValue *pCosTemplateDn = 0; + cosAttrValue *pCosSpecifier = 0; + cosAttrValue *pCosAttribute = 0; + cosAttrValue *pCosOverrides = 0; + cosAttrValue *pCosOperational = 0; + cosAttrValue *pCosOpDefault = 0; + cosAttrValue *pCosMerge = 0; + cosAttrValue *pDn = 0; + struct berval **dnVals; + int cosType = 0; + int valIndex = 0; + Slapi_Attr *dnAttr; + char *attrType = 0; + char *attrs[7]; + + attrs[0] = "objectclass"; + attrs[1] = "cosTargetTree"; + attrs[2] = "cosTemplateDn"; + attrs[3] = "cosSpecifier"; + attrs[4] = "cosAttribute"; + attrs[5] = 0; + info=(struct dn_defs_info *)callback_data; + + + /* assume cacheable */ + info->vattr_cacheable = -1; + + cos_cache_add_attrval(&pDn, slapi_entry_get_dn(e)); + if(!slapi_entry_first_attr(e, &dnAttr)) + { + do + { + attrType = 0; + /* we need to fill in the details of the definition now */ + slapi_attr_get_type(dnAttr, &attrType); + if(attrType) + { + pSneakyVal = 0; + if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"objectclass")) + pSneakyVal = &pObjectclass; + else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosTargetTree")) + pSneakyVal = &pCosTargetTree; + else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosTemplateDn")) + pSneakyVal = &pCosTemplateDn; + else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosSpecifier")) + pSneakyVal = &pCosSpecifier; + else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosAttribute")) + pSneakyVal = &pCosAttribute; + else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosIndirectSpecifier")) + pSneakyVal = &pCosSpecifier; + if(pSneakyVal) + { + /* It's a type we're interested in */ + if(!slapi_attr_get_bervals_copy(dnAttr, &dnVals)) + { + valIndex = 0; + if(dnVals) + { + while(dnVals[valIndex]) + { + if(dnVals[valIndex]->bv_val) + { + /* + parse any overide or default values + and deal with them + */ + if(pSneakyVal == &pCosAttribute) + { + cosAttrValue *pTmpTargetTree = 0; + int qualifier_hit = 0; + int op_qualifier_hit = 0; + int merge_schemes_qualifier_hit = 0; + int override_qualifier_hit =0; + int default_qualifier_hit = 0; + int operational_default_qualifier_hit = 0; + do + { + qualifier_hit = 0; + + if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " operational")) + { + /* matched */ + op_qualifier_hit = 1; + qualifier_hit = 1; + } + + if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " merge-schemes")) + { + /* matched */ + merge_schemes_qualifier_hit = 1; + qualifier_hit = 1; + } + + if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " override")) + { + /* matched */ + override_qualifier_hit = 1; + qualifier_hit = 1; + } + + if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " default")) { + default_qualifier_hit = 1; + qualifier_hit = 1; + } + + if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " operational-default")) { + operational_default_qualifier_hit = 1; + qualifier_hit = 1; + } + } + while(qualifier_hit == 1); + + /* + * At this point, dnVals[valIndex]->bv_val + * is the value of cosAttribute, stripped of + * any qualifiers, so add this pure attribute type to + * the appropriate lists. + */ + + if ( op_qualifier_hit ) { + cos_cache_add_attrval(&pCosOperational, + dnVals[valIndex]->bv_val); + } + if ( merge_schemes_qualifier_hit ) { + cos_cache_add_attrval(&pCosMerge, + dnVals[valIndex]->bv_val); + } + if ( override_qualifier_hit ) { + cos_cache_add_attrval(&pCosOverrides, + dnVals[valIndex]->bv_val); + } + if ( default_qualifier_hit ) { + /* attr is added below in pSneakyVal, in any case */ + } + + if ( operational_default_qualifier_hit ) { + cos_cache_add_attrval(&pCosOpDefault, + dnVals[valIndex]->bv_val); + } + + if(!pCosTargetTree) + { + /* get the parent of the definition */ + + char *parent = slapi_dn_parent(pDn->val); + slapi_dn_normalize( parent ); + + cos_cache_add_attrval(&pCosTargetTree, parent); + if(!pCosTemplateDn) + cos_cache_add_attrval(&pCosTemplateDn, parent); + + slapi_ch_free((void**)&parent); + } + + pTmpTargetTree = pCosTargetTree; + + slapi_vattrspi_regattr((vattr_sp_handle *)vattr_handle, dnVals[valIndex]->bv_val, NULL, NULL); + } /* if(attrType is cosAttribute) */ + + /* + * Add the attributetype to the appropriate + * list. + */ + cos_cache_add_attrval(pSneakyVal, + dnVals[valIndex]->bv_val); + }/*if(dnVals[valIndex]->bv_val)*/ + + valIndex++; + }/* while(dnVals[valIndex]) */ + + ber_bvecfree( dnVals ); + dnVals = NULL; + }/*if(dnVals)*/ + } + }/*if(pSneakyVal)*/ + }/*if(attrType)*/ + + } while(!slapi_entry_next_attr(e, dnAttr, &dnAttr)); + + /* + determine the type of class of service scheme + */ + + if(pObjectclass) + { + if(cos_cache_attrval_exists(pObjectclass, "cosDefinition")) + { + cosType = COSTYPE_CLASSIC; + } + else if(cos_cache_attrval_exists(pObjectclass, "cosClassicDefinition")) + { + cosType = COSTYPE_CLASSIC; + + } + else if(cos_cache_attrval_exists(pObjectclass, "cosPointerDefinition")) + { + cosType = COSTYPE_POINTER; + + } + else if(cos_cache_attrval_exists(pObjectclass, "cosIndirectDefinition")) + { + cosType = COSTYPE_INDIRECT; + + } + else + cosType = COSTYPE_BADTYPE; + } + + /* + we should now have a full definition, + do some sanity checks because we don't + want bogus entries in the cache + then ship it + */ + + /* these must exist */ + if( pDn && + pObjectclass && + + ( + (cosType == COSTYPE_CLASSIC && + pCosTemplateDn && + pCosSpecifier && + pCosAttribute ) + || + (cosType == COSTYPE_POINTER && + pCosTemplateDn && + pCosAttribute ) + || + (cosType == COSTYPE_INDIRECT && + pCosSpecifier && + pCosAttribute ) + ) + ) + { + int rc = 0; + /* + we'll leave the referential integrity stuff + up to the referint plug-in and assume all + is good - if it's not then we just have a + useless definition and we'll nag copiously later. + */ + char *pTmpDn = slapi_ch_strdup(pDn->val); /* because dn gets hosed on error */ + char ebuf[ BUFSIZ ]; + + if(!(rc = cos_cache_add_defn(info->pDefs, &pDn, cosType, + &pCosTargetTree, &pCosTemplateDn, + &pCosSpecifier, &pCosAttribute, + &pCosOverrides, &pCosOperational, + &pCosMerge, &pCosOpDefault))) { + info->ret = 0; /* we have succeeded to add the defn*/ + } else { + /* + * Failed but we will continue the search for other defs + * Don't reset info->ret....it keeps track of any success + */ + if ( rc == COS_DEF_ERROR_NO_TEMPLATES) { + LDAPDebug(LDAP_DEBUG_ANY, "skipping cos definition %s" + "--no templates found\n", + escape_string(pTmpDn, ebuf),0,0); + } else { + LDAPDebug(LDAP_DEBUG_ANY, "skipping cos definition %s\n" + ,escape_string(pTmpDn, ebuf),0,0); + } + } + + slapi_ch_free_string(&pTmpDn); + } + else + { + /* + this definition is brain dead - bail + if we have a dn use it to report, if not then *really* bad + things are going on + */ + if(pDn) + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_dn_defs: incomplete cos definition detected in %s, discarding from cache.\n",pDn->val,0,0); + } + else + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_dn_defs: incomplete cos definition detected, no DN to report, discarding from cache.\n",0,0,0); + + if(pCosTargetTree) + cos_cache_del_attrval_list(&pCosTargetTree); + if(pCosTemplateDn) + cos_cache_del_attrval_list(&pCosTemplateDn); + if(pCosSpecifier) + cos_cache_del_attrval_list(&pCosSpecifier); + if(pCosAttribute) + cos_cache_del_attrval_list(&pCosAttribute); + if(pDn) + cos_cache_del_attrval_list(&pDn); + } + }/*if(!slapi_entry_first_attr(e, &dnAttr))*/ + /* we don't keep the objectclasses, so lets free them */ + if(pObjectclass) { + cos_cache_del_attrval_list(&pObjectclass); + } + /* This particular definition may not have yielded anything + * worth caching (eg. no template was found for it) but + * that should not cause us to abort the search for other more well behaved + * definitions. + * return info->ret; + */ + return (0); + +} + +/* + cos_cache_add_dn_defs + ------------------------- + takes a dn as argument and searches the dn for cos definitions, + adding any found to the definition list. Change to use search callback API. + + Returns: 0: found at least one definition entry that got added to the + cache successfully. + non-zero: added no cos defs to the cache. +*/ + +#define DN_DEF_FILTER "(&(|(objectclass=cosSuperDefinition)(objectclass=cosDefinition))(objectclass=ldapsubentry))" + +static int cos_cache_add_dn_defs(char *dn, cosDefinitions **pDefs, int *vattr_cacheable) +{ + Slapi_PBlock *pDnSearch = 0; + struct dn_defs_info info; + pDnSearch = slapi_pblock_new(); + if (pDnSearch) { + info.ret=-1; /* assume no good defs */ + info.pDefs=pDefs; + info.vattr_cacheable = 0; /* assume not cacheable */ + slapi_search_internal_set_pb(pDnSearch, dn, LDAP_SCOPE_SUBTREE, + DN_DEF_FILTER,NULL,0, + NULL,NULL,cos_get_plugin_identity(),0); + slapi_search_internal_callback_pb(pDnSearch, + &info /* callback_data */, + NULL/* result_callback */, + cos_dn_defs_cb, + NULL /* referral_callback */); + slapi_pblock_destroy (pDnSearch); + } + + *vattr_cacheable = info.vattr_cacheable; + + return info.ret; +} + + + +/* struct to support call back API */ + +struct tmpl_info { + cosAttrValue *pCosSpecifier; + cosAttrValue *pAttrs; + cosTemplates **pTmpls; + int ret; +}; + + +/* + * Currently, always returns 0 to continue the search for templates, even + * if a particular attempt to add a template fails: info.ret gets set to + * zero only if we succed to add at least one tmpl. +*/ +static int cos_dn_tmpl_entries_cb (Slapi_Entry* e, void *callback_data) { + cosAttrValue *pDn = 0; + cosAttrValue *pCosPriority = 0; + cosAttributes *pAttributes = 0; + cosAttrValue *pObjectclass = 0; + cosAttrValue *pCosAttribute = 0; + Slapi_Attr *dnAttr; + struct berval **dnVals; + int itsAnAttr = 0; + int valIndex = 0; + cosAttrValue **pSneakyVal = 0; + char *attrType = 0; + struct tmpl_info *info; + info = (struct tmpl_info *)callback_data; + + pDn = 0; + cos_cache_add_attrval(&pDn, slapi_entry_get_dn(e)); + pAttributes = 0; + pObjectclass = 0; + pCosPriority = 0; + + if(!slapi_entry_first_attr(e, &dnAttr)) + { + int attrs_present = 0; + + do + { + attrType = 0; + pCosAttribute = 0; + + /* we need to fill in the details of the template now */ + slapi_attr_get_type(dnAttr, &attrType); + + if(attrType) + { + itsAnAttr = 0; + pSneakyVal = 0; + + if(!slapi_utf8casecmp((unsigned char*)attrType, + (unsigned char*)"objectclass")) + pSneakyVal = &pObjectclass; + + if(!slapi_utf8casecmp((unsigned char*)attrType, + (unsigned char*)"cosPriority")) + pSneakyVal = &pCosPriority; + + if(pSneakyVal == NULL) + { + /* look for the atrribute in the dynamic attributes */ + if(cos_cache_attrval_exists(info->pAttrs, attrType)) + { + pSneakyVal = &pCosAttribute; + itsAnAttr = 1; + attrs_present = 1; + } + } + + if(pSneakyVal) + { + if(!slapi_attr_get_bervals_copy(dnAttr, &dnVals)) + { + valIndex = 0; + + if(dnVals) + { + while(dnVals[valIndex]) + { + if(dnVals[valIndex]->bv_val) + cos_cache_add_attrval(pSneakyVal, + dnVals[valIndex]->bv_val); + + valIndex++; + } + + if(itsAnAttr) + { + /* got all vals, add this attribute to the attribute list */ + cos_cache_add_attr(&pAttributes, attrType, + *pSneakyVal); + } + + ber_bvecfree( dnVals ); + dnVals = NULL; + } + } + } + } + + } while(!slapi_entry_next_attr(e, dnAttr, &dnAttr)); + + /* + we should now have a full template, + do some sanity checks because we don't + want bogus entries in the cache if we can help it + - then ship it + */ + + /* these must exist */ + if( + attrs_present && + pObjectclass && + pAttributes && + pDn + ) + { + /* + we'll leave the referential integrity stuff + up to the referint plug-in if set up and assume all + is good - if it's not then we just have a + useless definition and we'll nag copiously later. + */ + + if(!cos_cache_add_tmpl(info->pTmpls, pDn, pObjectclass, + info->pCosSpecifier, pAttributes,pCosPriority)){ + info->ret = 0; /* we have succeed to add the tmpl */ + } else { + /* Don't reset info->ret....it keeps track of any success */ + LDAPDebug(LDAP_DEBUG_ANY, "cos_cache_add_dn_tmpls:" + "could not cache cos template %s\n",pDn,0,0); + } + } + else + { + /* + this template is brain dead - bail + if we have a dn use it to report, if not then *really* bad + things are going on + - of course it might not be a template, so lets + not tell the world unless the world wants to know, + we'll make it a plugin level message + */ + if(pDn) + { + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_dn_tmpls: incomplete cos template detected in %s, discarding from cache.\n",pDn->val,0,0); + } + else + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_dn_tmpls: incomplete cos template detected, no DN to report, discarding from cache.\n",0,0,0); + + if(pObjectclass) + cos_cache_del_attrval_list(&pObjectclass); + if(pCosAttribute) + cos_cache_del_attrval_list(&pCosAttribute); + if(pDn) + cos_cache_del_attrval_list(&pDn); + if(pAttributes) + cos_cache_del_attr_list(&pAttributes); + } + } + /* + * Always contine the search even if a particular attempt + * to add a template failed. + */ + return 0; +} + +/* + cos_cache_add_dn_tmpls + ------------------------- + takes a dn as argument and searches the dn for cos templates, + adding any found to the template list + This is the new version using call back search API + + Returns: zero for success--found at least one good tmpl for this def. + non-zero: failed to add any templs for this def. +*/ + +#define TMPL_FILTER "(&(objectclass=costemplate)(|(objectclass=costemplate)(objectclass=ldapsubentry)))" + +static int cos_cache_add_dn_tmpls(char *dn, cosAttrValue *pCosSpecifier, cosAttrValue *pAttrs, cosTemplates **pTmpls) +{ + void *plugin_id; + int scope; + struct tmpl_info info; + Slapi_PBlock *pDnSearch = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_dn_tmpls\n",0,0,0); + + /* no cos specifier means this is an indirect scheme */ + if(pCosSpecifier) + scope = LDAP_SCOPE_ONELEVEL; + else + scope = LDAP_SCOPE_BASE; + + /* Use new internal operation API */ + pDnSearch = slapi_pblock_new(); + plugin_id=cos_get_plugin_identity(); + if (pDnSearch) { + info.pAttrs=pAttrs; + info.pTmpls=pTmpls; + info.pCosSpecifier=pCosSpecifier; + info.ret=-1; /* assume no good tmpls */ + slapi_search_internal_set_pb(pDnSearch, dn, scope, + TMPL_FILTER,NULL,0, + NULL,NULL,plugin_id,0); + slapi_search_internal_callback_pb(pDnSearch, + &info /* callback_data */, + NULL/* result_callback */, + cos_dn_tmpl_entries_cb, + NULL /* referral_callback */); + slapi_pblock_destroy (pDnSearch); + } + /* + * info.ret comes out zero only if we succeed to add at least one + * tmpl to the cache. + */ + return (info.ret); +} + +/* + cos_cache_add_defn + ------------------ + Add a cos definition to the list and create the template + cache for this definition + returns: 0: successfully added the definition to the cache + non-zero: failed to add the definition to the cache (eg. because + there was no template for it.) + ret==COS_DEF_ERROR_NO_TEMPLATES then no templs were found + for thsi def. We make a special case of this and pass the + back the error so we can roll two messages into one--this + is to reduce the number of error messages at cos definiton + load time--it is common to see the defs before the tmpls + arrive. + +*/ +static int cos_cache_add_defn( + cosDefinitions **pDefs, + cosAttrValue **dn, + int cosType, + cosAttrValue **tree, + cosAttrValue **tmpDn, + cosAttrValue **spec, + cosAttrValue **pAttrs, + cosAttrValue **pOverrides, + cosAttrValue **pOperational, + cosAttrValue **pCosMerge, + cosAttrValue **pCosOpDefault + ) +{ + int ret = 0; + int tmplCount = 0; + cosDefinitions *theDef = 0; + cosAttrValue *pTmpTmplDn = *tmpDn; + cosAttrValue *pDummyAttrVal = 0; + cosAttrValue *pAttrsIter = 0; + cosAttributes *pDummyAttributes = 0; + cosAttrValue *pSpecsIter = *spec; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_defn\n",0,0,0); + + /* we don't want cosspecifiers that can be supplied by the same scheme */ + while( pSpecsIter ) + { + if( cos_cache_attrval_exists(*pAttrs, pSpecsIter->val ) ) + { + /* no, this is not sane, lets reject the whole darn scheme in disgust */ + LDAPDebug( LDAP_DEBUG_ANY, "cos definition %s supplies its own cosspecifier, rejecting scheme\n",(*dn)->val,0,0); + ret = -1; + } + + pSpecsIter = pSpecsIter->list.pNext; + } + + /* create the definition */ + if(0 == ret) + { + theDef = (cosDefinitions*) slapi_ch_malloc(sizeof(cosDefinitions)); + if(theDef) + { + theDef->pCosTmps = NULL; + + /* process each template in turn */ + + LDAPDebug( LDAP_DEBUG_PLUGIN, "Processing cosDefinition %s\n",(*dn)->val,0,0); + + while(pTmpTmplDn && cosType != COSTYPE_INDIRECT) + { + /* create the template */ + if(!cos_cache_add_dn_tmpls(pTmpTmplDn->val, *spec, *pAttrs, &(theDef->pCosTmps))) + tmplCount++; + + pTmpTmplDn = pTmpTmplDn->list.pNext; + } + + if(tmplCount == 0 && cosType != COSTYPE_INDIRECT) + { + /* without our golden templates we are nothing + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_defn:" + "no templates for cos definition at %s.\n",(*dn)->val,0,0);*/ + ret = COS_DEF_ERROR_NO_TEMPLATES; + } + else + { + if(cosType == COSTYPE_INDIRECT) + { + /* + Indirect cos schemes have no templates, + however, in order to take advantage of existing code + which is optimized to do a binary search on attributes + which are found through their templates, we add a dummy + template and dummy attributes. The value of the attributes + will be ignored when later assessing a query. + */ + pAttrsIter = *pAttrs; + + while(pAttrsIter) + { + pDummyAttrVal = NULL; + cos_cache_add_attrval(&pDummyAttrVal, "not used"); + cos_cache_add_attr(&pDummyAttributes, pAttrsIter->val, pDummyAttrVal); + + pAttrsIter = pAttrsIter->list.pNext; + } + + cos_cache_add_attrval(tmpDn, "cn=dummy,"); + + cos_cache_add_tmpl(&(theDef->pCosTmps), *tmpDn, NULL, *spec, pDummyAttributes,NULL); + *tmpDn = 0; + } + + theDef->pDn = *dn; + theDef->cosType = cosType; + theDef->pCosTargetTree = *tree; + theDef->pCosTemplateDn = *tmpDn; + theDef->pCosSpecifier = *spec; + theDef->pCosAttrs = *pAttrs; + theDef->pCosOverrides = *pOverrides; + theDef->pCosOperational = *pOperational; + theDef->pCosMerge = *pCosMerge; + theDef->pCosOpDefault = *pCosOpDefault; + + cos_cache_add_ll_entry((void**)pDefs, theDef, NULL); + LDAPDebug( LDAP_DEBUG_PLUGIN, "Added cosDefinition %s\n",(*dn)->val,0,0); + } + } + else + { + ret = -1; + } + } + + if(ret == -1) + { + if(dn) + cos_cache_del_attrval_list(dn); + if(tree) + cos_cache_del_attrval_list(tree); + if(tmpDn) + cos_cache_del_attrval_list(tmpDn); + if(spec) + cos_cache_del_attrval_list(spec); + if(pAttrs) + cos_cache_del_attrval_list(pAttrs); + if(theDef) + slapi_ch_free((void**)&theDef); + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_defn\n",0,0,0); + return ret; +} + +/* + cos_cache_del_attrval_list + -------------------------- + walks the list deleting as it goes +*/ +static void cos_cache_del_attrval_list(cosAttrValue **pVal) +{ + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_del_attrval_list\n",0,0,0); + + while(*pVal) + { + cosAttrValue *pTmp = (*pVal)->list.pNext; + + slapi_ch_free((void**)&((*pVal)->val)); + slapi_ch_free((void**)&(*pVal)); + *pVal = pTmp; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_del_attrval_list\n",0,0,0); +} + + +/* + cos_cache_add_attrval + --------------------- + adds a value to an attribute value list +*/ +static int cos_cache_add_attrval(cosAttrValue **attrval, char *val) +{ + int ret = 0; + cosAttrValue *theVal; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_attrval\n",0,0,0); + + /* create the attrvalue */ + theVal = (cosAttrValue*) slapi_ch_malloc(sizeof(cosAttrValue)); + if(theVal) + { + theVal->val = slapi_ch_strdup(val); + if(theVal->val) + { + cos_cache_add_ll_entry((void**)attrval, theVal, NULL); + } + else + { + slapi_ch_free((void**)&theVal); + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attrval: failed to allocate memory\n",0,0,0); + ret = -1; + } + } + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attrval: failed to allocate memory\n",0,0,0); + ret = -1; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_attrval\n",0,0,0); + return ret; +} + +/* + cos_cache_add_ll_entry - RECURSIVE for sorted lists + --------------------------------------------------- + if a compare function is passed as argument, the element + is added to the linked list in the sorted order according + to that compare functions algorithm. This prepares the list + for ultra fast indexing - requiring only to walk the list once + to build the index. + if no compare function is passed, the element is added + to the head of the linked list + the index is created after the linked list is complete, + and so is always null until explicitly indexed + + *NOTE* this function assumes and *requires* that the structures + passed to it in "attrval" and "theVal" have a cosIndexedLinkedList + member, and it is the *first* member of the structure. This + is safe because this is a module level function, and all functions + which call this one are part of the same sub-system. + + The compare function will also make a similar assumption, but + likely one that recognizes the full structure or type, it is + the responsibility of the caller to match input structures with + the appropriate compare function. + + WARNING: current recursive sorting code never used, never tested +*/ +static void cos_cache_add_ll_entry(void** attrval, void *theVal, int ( *compare )(const void *elem1, const void *elem2 )) +{ + static cosIndexedLinkedList *pLastList = 0; + static cosIndexedLinkedList *first_element; + static int call_count = 0; + + call_count++; + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_ll_entry - recursion level %d\n",call_count,0,0); + + + /* + need to save the first element of the list + we update this first element whenever an entry + is added to the start of the list, this way + we can ensure that the head of the list is always + *attrval - callers pass us the head of the list + and can expect that what they get back is also + the head of the list + */ + if(call_count == 1) + first_element = *attrval; + + if(*attrval) + { + if(compare == NULL) + { + /* we dont want this list sorted */ + /* push this to the start of the list (because its quick) */ + ((cosIndexedLinkedList*)theVal)->pNext = *attrval; + ((cosIndexedLinkedList*)theVal)->index = NULL; + *attrval = theVal; + } + else + { + /* insert this in the list in sorted order + (because its quicker for building indexes later) */ + int comp_ret = 0; + + comp_ret = compare(*attrval, theVal); + if(comp_ret == 0 || comp_ret > 0) + { + /* insert prior to this element */ + if(pLastList) + pLastList->pNext = theVal; + else + first_element = theVal; + + ((cosIndexedLinkedList*)theVal)->pNext = *attrval; + ((cosIndexedLinkedList*)theVal)->index = NULL; + } + else + { + /* still looking - recurse on next element */ + pLastList = (cosIndexedLinkedList*)attrval; + cos_cache_add_ll_entry(&(((cosIndexedLinkedList*)attrval)->pNext), theVal, compare); + } + + if(call_count == 1) + *attrval = first_element; + } + } + else + { + /* new or end of list */ + ((cosIndexedLinkedList*)theVal)->pNext = NULL; + ((cosIndexedLinkedList*)theVal)->index = NULL; + if(call_count == 1) /* if new list */ + *attrval = theVal; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_ll_entry - recursion level %d\n",call_count,0,0); + call_count--; +} + + +/* + cos_cache_getref + ---------------- + retrieves a reference to the current cache and adds to the cache reference count +*/ +int cos_cache_getref(cos_cache **pptheCache) +{ + int ret = -1; + static int first_time = 1; + cosCache **ppCache = (cosCache**)pptheCache; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_getref\n",0,0,0); + + if(first_time) + { + first_time = 0; + /* first customer, create the cache */ + slapi_lock_mutex(change_lock); + if(pCache == NULL) + { + if(cos_cache_create()) + { + /* there was a problem or no COS definitions were found */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_getref: no cos cache created\n",0,0,0); + } + } + slapi_unlock_mutex(change_lock); + } + + slapi_lock_mutex(cache_lock); + + *ppCache = pCache; + + if(pCache) + ret = ++((*ppCache)->refCount); + + slapi_unlock_mutex(cache_lock); + + + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_getref\n",0,0,0); + return ret; +} + +/* + cos_cache_addref + ---------------- + adds 1 to the cache reference count +*/ +int cos_cache_addref(cos_cache *ptheCache) +{ + int ret; + cosCache *pCache = (cosCache*)ptheCache; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_addref\n",0,0,0); + + slapi_lock_mutex(cache_lock); + + if(pCache) + ret = ++(pCache->refCount); + + slapi_unlock_mutex(cache_lock); + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_addref\n",0,0,0); + + return ret; +} + + +/* + cos_cache_release + ----------------- + subtracts 1 from the cache reference count, if the count falls + below 1, the cache is destroyed. +*/ +int cos_cache_release(cos_cache *ptheCache) +{ + int ret = 0; + int destroy = 0; + cosCache *pOldCache = (cosCache*)ptheCache; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_release\n",0,0,0); + + slapi_lock_mutex(cache_lock); + + if(pOldCache) + { + ret = --(pOldCache->refCount); + if(ret == 0) + destroy = 1; + } + + slapi_unlock_mutex(cache_lock); + + if(destroy) + { + cosDefinitions *pDef = pOldCache->pDefs; + + /* now is the first time it is + * safe to assess whether + * vattr caching can be turned on + */ + if(pCache && pCache->vattr_cacheable) + { + slapi_vattrcache_cache_all(); + } + + /* destroy the cache here - no locking required, no references outstanding */ + + if(pDef) + cos_cache_del_schema(pOldCache); + + while(pDef) + { + cosDefinitions *pTmpD = pDef; + cosTemplates *pCosTmps = pDef->pCosTmps; + + while(pCosTmps) + { + cosTemplates *pTmpT = pCosTmps; + + pCosTmps = pCosTmps->list.pNext; + + cos_cache_del_attr_list(&(pTmpT->pAttrs)); + cos_cache_del_attrval_list(&(pTmpT->pObjectclasses)); + cos_cache_del_attrval_list(&(pTmpT->pDn)); + slapi_ch_free((void**)&(pTmpT->cosGrade)); + slapi_ch_free((void**)&pTmpT); + } + + pDef = pDef->list.pNext; + + cos_cache_del_attrval_list(&(pTmpD->pDn)); + cos_cache_del_attrval_list(&(pTmpD->pCosTargetTree)); + cos_cache_del_attrval_list(&(pTmpD->pCosTemplateDn)); + cos_cache_del_attrval_list(&(pTmpD->pCosSpecifier)); + cos_cache_del_attrval_list(&(pTmpD->pCosAttrs)); + cos_cache_del_attrval_list(&(pTmpD->pCosOverrides)); + cos_cache_del_attrval_list(&(pTmpD->pCosOperational)); + cos_cache_del_attrval_list(&(pTmpD->pCosMerge)); + cos_cache_del_attrval_list(&(pTmpD->pCosOpDefault)); + slapi_ch_free((void**)&pTmpD); + } + + if(pOldCache->ppAttrIndex) + slapi_ch_free((void**)&(pOldCache->ppAttrIndex)); + if(pOldCache->ppTemplateList) + slapi_ch_free((void**)&(pOldCache->ppTemplateList)); + slapi_ch_free((void**)&pOldCache); + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_release\n",0,0,0); + + return ret; +} + +/* + cos_cache_attrval_count + ----------------------- + counts the number of values in the list +*/ + +static int cos_cache_attrval_count(cosAttrValue *pVal) +{ + int ret = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_attrval_count\n",0,0,0); + + while(pVal) + { + ret++; + pVal = pVal->list.pNext; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_attrval_count\n",0,0,0); + return ret; +} + +/* + cos_cache_del_attr_list + ----------------------- + walk the list deleting as we go +*/ +static void cos_cache_del_attr_list(cosAttributes **pAttrs) +{ + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_del_attr_list\n",0,0,0); + + while(*pAttrs) + { + cosAttributes *pTmp = (*pAttrs)->list.pNext; + + cos_cache_del_attrval_list(&((*pAttrs)->pAttrValue)); + slapi_ch_free((void**)&((*pAttrs)->pAttrName)); + slapi_ch_free((void**)&(*pAttrs)); + *pAttrs = pTmp; + } + + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_del_attr_list\n",0,0,0); +} + + +/* + cos_cache_del_schema + -------------------- + delete the object class lists used for schema checking +*/ +static void cos_cache_del_schema(cosCache *pCache) +{ + char *pLastName = 0; + cosAttrValue *pLastRef = 0; + int attr_index = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_del_schema\n",0,0,0); + + if(pCache && pCache->attrCount && pCache->ppAttrIndex) + { + pLastName = pCache->ppAttrIndex[0]->pAttrName; + pLastRef = pCache->ppAttrIndex[0]->pObjectclasses; + + for(attr_index=1; attr_index<pCache->attrCount; attr_index++) + { + if(slapi_utf8casecmp((unsigned char*)pCache->ppAttrIndex[attr_index]->pAttrName, (unsigned char*)pLastName)) + { + /* remember what went before */ + pLastName = pCache->ppAttrIndex[attr_index]->pAttrName; + + /* then blow it away */ + cos_cache_del_attrval_list(&(pCache->ppAttrIndex[attr_index]->pObjectclasses)); + } + } + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_del_schema\n",0,0,0); +} + + +/* + cos_cache_add_attr + ------------------ + Adds an attribute to the list +*/ +static int cos_cache_add_attr(cosAttributes **pAttrs, char *name, cosAttrValue *val) +{ + int ret =0; + cosAttributes *theAttr; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_attr\n",0,0,0); + + /* create the attribute */ + theAttr = (cosAttributes*) slapi_ch_malloc(sizeof(cosAttributes)); + if(theAttr) + { + theAttr->pAttrValue = val; + theAttr->pObjectclasses = 0; /* schema issues come later */ + theAttr->pAttrName = slapi_ch_strdup(name); + if(theAttr->pAttrName) + { + cos_cache_add_ll_entry((void**)pAttrs, theAttr, NULL); + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_attr: Added attribute %s\n",name,0,0); + } + else + { + slapi_ch_free((void**)&theAttr); + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attr: failed to allocate memory\n",0,0,0); + ret = -1; + } + } + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attr: failed to allocate memory\n",0,0,0); + ret = -1; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_attr\n",0,0,0); + return ret; +} + + +/* + cos_cache_add_tmpl + ------------------ + Adds a template to the list +*/ +static int cos_cache_add_tmpl(cosTemplates **pTemplates, cosAttrValue *dn, cosAttrValue *objclasses, cosAttrValue *pCosSpecifier, cosAttributes *pAttrs,cosAttrValue *cosPriority) +{ + int ret =0; + cosTemplates *theTemp; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_tmpl\n",0,0,0); + + /* create the attribute */ + theTemp = (cosTemplates*) slapi_ch_malloc(sizeof(cosTemplates)); + if(theTemp) + { + char *grade = (char*)slapi_ch_malloc(strlen(dn->val)+1); + int grade_index = 0; + int index = 0; + int template_default = 0; + + slapi_dn_normalize(dn->val); + + /* extract the cos grade */ + while(dn->val[index] != '=' && dn->val[index] != '\0') + index++; + + if(dn->val[index] == '=') + { + int quotes = 0; + + index++; + + /* copy the grade (supports one level of quote nesting in rdn) */ + while(dn->val[index] != ',' || dn->val[index-1] == '\\' || quotes == 1) + { + if(dn->val[index] == '"') + { + if(quotes == 0) + quotes = 1; + else + quotes = 0; + } + else + { + if(dn->val[index] != '\\') /* skip escape chars */ + { + grade[grade_index] = dn->val[index]; + grade_index++; + } + } + + index++; + } + + grade[grade_index] = '\0'; + + /* ok, grade copied, is it the default template? */ + + if(pCosSpecifier) /* some schemes don't have one */ + { + char tmpGrade[BUFSIZ]; + + if (strlen(pCosSpecifier->val) < (BUFSIZ - 9)) { /* 9 for "-default" */ + strcpy(tmpGrade, pCosSpecifier->val); + strcat(tmpGrade, "-default"); + if(!slapi_utf8casecmp((unsigned char*)grade, (unsigned char*)tmpGrade)) + template_default = 1; + } + else + { + /* + * We shouldn't pass ever through this code as we expect + * pCosSpecifier values to be reasonably smaller than BUFSIZ + * + * only 2 lines of code -> no need to set an indirect char * + * duplicate the lines of code for clearness instead + */ + char * newTmpGrade = (char*) slapi_ch_malloc( + strlen((pCosSpecifier->val) + 9)); + strcpy(newTmpGrade, pCosSpecifier->val); + strcat(newTmpGrade, "-default"); + if(!slapi_utf8casecmp((unsigned char*)grade, (unsigned char*)newTmpGrade)) + template_default = 1; + slapi_ch_free((void**)&newTmpGrade); + } + } + + } + else + { + /* mmm, should never get here, it means the dn is whacky */ + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_tmpl: malformed dn detected: %s\n",dn,0,0); + grade[0] = '\0'; + } + + /* now fill in the blanks */ + theTemp->pDn = dn; + theTemp->pObjectclasses = objclasses; + theTemp->pAttrs = pAttrs; + theTemp->cosGrade = slapi_ch_strdup(grade); + theTemp->template_default = template_default; + theTemp->cosPriority = (unsigned long)-1; + + if(cosPriority) + { + theTemp->cosPriority = atol(cosPriority->val); + cos_cache_del_attrval_list(&cosPriority); + } + + cos_cache_add_ll_entry((void**)pTemplates, theTemp, NULL); + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_tmpl: Added template %s\n",dn->val,0,0); + + slapi_ch_free((void**)&grade); + } + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_tmpl: failed to allocate memory\n",0,0,0); + ret = -1; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_tmpl\n",0,0,0); + return ret; +} + +/* + cos_cache_attrval_exists + ------------------------ + performs linear search on the list for a + cosAttrValue that matches val + however, if the "index" member of cosAttrValue + is non-null then a binary search is performed + on the index {PARPAR: TO BE DONE} + + return 1 on found, 0 otherwise +*/ +static int cos_cache_attrval_exists(cosAttrValue *pAttrs, const char *val) +{ + int ret = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_attrval_exists\n",0,0,0); + + while(pAttrs) + { + if(!slapi_utf8casecmp((unsigned char*)pAttrs->val,(unsigned char*)val)) + { + ret = 1; + break; + } + + pAttrs = pAttrs->list.pNext; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_attrval_exists\n",0,0,0); + return ret; +} + + + +static int cos_cache_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint) +{ + int ret = -1; + cos_cache *pCache = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_vattr_get\n",0,0,0); + + if(cos_cache_getref(&pCache) < 1) + { + /* problems we are hosed */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_vattr_get: failed to get class of service reference\n",0,0,0); + goto bail; + } + + ret = cos_cache_query_attr(pCache, c, e, type, results, NULL, NULL, NULL); + if(ret == 0) + { + *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; + *actual_type_name = slapi_ch_strdup(type); + *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; + } + cos_cache_release(pCache); + +bail: + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_vattr_get\n",0,0,0); + return ret; +} + + +static int cos_cache_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint) +{ + int ret = -1; + cos_cache *pCache = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_vattr_compare\n",0,0,0); + + if(cos_cache_getref(&pCache) < 1) + { + /* problems we are hosed */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_vattr_compare: failed to get class of service reference\n",0,0,0); + goto bail; + } + + ret = cos_cache_query_attr(pCache, c, e, type, NULL, test_this, result, NULL); + + cos_cache_release(pCache); + +bail: + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_vattr_compare\n",0,0,0); + return ret; +} + +/* + * this imp is damn slow + * + */ +static int cos_cache_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e, + vattr_type_list_context *type_context,int flags) +{ + int ret = 0; + int index = 0; + cosCache *pCache; + char *lastattr = "thisisfakeforcos"; + int props = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_vattr_types\n",0,0,0); + + if(cos_cache_getref((cos_cache **)&pCache) < 1) + { + /* problems we are hosed */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_vattr_types: failed to get class of service reference\n",0,0,0); + goto bail; + } + + while(index < pCache->attrCount ) + { + if(slapi_utf8casecmp( + (unsigned char *)pCache->ppAttrIndex[index]->pAttrName, + (unsigned char *)lastattr)) + { + lastattr = pCache->ppAttrIndex[index]->pAttrName; + + if(1 == cos_cache_query_attr(pCache, NULL, e, lastattr, NULL, NULL, + NULL, &props)) + { + /* entry contains this attr */ + vattr_type_thang thang = {0}; + + thang.type_name = lastattr; + thang.type_flags = props; + + slapi_vattrspi_add_type(type_context,&thang,0); + } + + } + + index++; + } + + cos_cache_release(pCache); + +bail: + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_vattr_types\n",0,0,0); + return ret; +} + + +/* + cos_cache_query_attr + -------------------- + queries the cache to determine if this entry + should have an attribute of "type", and if so, + returns the attribute values in "vals" - which + should be subsequently freed by a call to + cos_cache_query_attr_free() + + returns + 0 on success, we added a computed attribute + 1 on outright failure + -1 when doesn't know about attribute + + {PARPAR} must also check the attribute does not exist if we are not + overriding and allow the DS logic to pick it up by denying knowledge + of attribute +*/ +static int cos_cache_query_attr(cos_cache *ptheCache, vattr_context *context, Slapi_Entry *e, char *type, Slapi_ValueSet **out_attr, Slapi_Value *test_this, int *result, int *props) +{ + int ret = -1; + cosCache *pCache = (cosCache*)ptheCache; + char *pDn = 0; + Slapi_Attr *pObjclasses = 0; + int attr_index = 0; /* for looping through attributes */ + int attr_matched_index = 0; /* for identifying the matched attribute */ + int hit = 0; + cosAttributes *pDefAttr = 0; + Slapi_ValueSet* results = 0; + Slapi_Value *val; +/* int type_name_disposition; + char *actual_type_name; + int flags = 0; + int free_flags;*/ + Slapi_Attr *pTmpVals; + int using_default = 0; + int entry_has_value = 0; + int merge_mode = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_query_attr\n",0,0,0); + + + if(out_attr) + *out_attr = 0; + + /* + to perform this operation we need to know: + + * if we know about the attribute, if not just exit + -- + * dn,to determine its relevancy to any cos definition, + it must be a child of cosTargetTree + -- + * objectclasses,to determine if the cos attribute will + violate schema (only when schema checking is on) + -- + * class of service specifier, for matching definitions + - cosSpecifier is the attribute name and is used to + determine the cosDefinition to use, its value determines + the template to use + -- + * the cosAttribute(s) (from the cosDefinition(s)) that match + the attribute name. + ($$)If these have a postfix of "default", then it is the same + as no postfix i.e. this acts as the default value. If it has + a postfix of "override", then the value in the matching template + is used regardless of any value stored in the entry. This has + been worked out previously so we can use a bool indicator in + the cosDefinition structure to determine what to do. + -- + * the value of the attribute in the entry - + if present it overrides any default template value (see $$) + + Also this ordering ensures least work done to fail (in this + implementation) + */ + + /** attribute **/ + /* + lets be sure we need to do something + most of the time we probably don't + */ + attr_index = cos_cache_find_attr(pCache, type); + if(attr_index == -1) + { + /* we don't know about this attribute */ + goto bail; + } + + /* + if there is a value in the entry the outcome + of the cos attribute resolution may be different + */ + slapi_entry_attr_find(e, type, &pTmpVals); + if(pTmpVals) + entry_has_value = 1; + + /** dn **/ + pDn = slapi_entry_get_dn(e); + + if(pDn == 0) + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: failed to get entry dn\n",0,0,0); + ret = 1; + goto bail; + } + + slapi_dn_normalize( pDn ); + + /** objectclasses **/ + if(pCache->ppAttrIndex[attr_index]->attr_operational == 0 && config_get_schemacheck() && + pCache->ppAttrIndex[attr_index]->attr_operational_default == 0) + { + /* does this entry violate schema? */ + + if(slapi_entry_attr_find( e, "objectclass", &pObjclasses )) + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: failed to get objectclass from %s\n",pDn,0,0); + goto bail; + } + + if(!cos_cache_schema_check(pCache, attr_index, pObjclasses)) + { + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_query_attr: cos attribute %s failed schema check on dn: %s\n",type,pDn,0); + goto bail; + } + } + + /** class of service specifier **/ + /* + now we need to iterate through the attributes to discover + if one fits all the criteria, we'll take the first that does + and blow off the rest + */ + do + { + /* for convenience, define some pointers */ + cosAttributes *pAttr = pCache->ppAttrIndex[attr_index]; + cosTemplates *pTemplate = (cosTemplates*)pAttr->pParent; + cosDefinitions *pDef = (cosDefinitions*)pTemplate->pParent; + cosAttrValue *pTargetTree = pDef->pCosTargetTree; + + /* now for the tests */ + + /* would we be allowed to supply this attribute if we had one? */ + if(entry_has_value && pAttr->attr_override == 0 && pAttr->attr_operational == 0) + { + /* answer: no, move on to the next attribute */ + attr_index++; + continue; + } + + /* if we are in merge_mode, can the attribute be merged? */ + if(merge_mode && pAttr->attr_cos_merge == 0) + { + /* answer: no, move on to the next attribute */ + attr_index++; + continue; + } + + /* is this entry a child of the target tree(s)? */ + do + { + if(pTargetTree) + slapi_dn_normalize( pTargetTree->val ); + + if( pTargetTree->val == 0 || + slapi_dn_issuffix(pDn, pTargetTree->val) != 0 || + (views_api && views_entry_exists(views_api, pTargetTree->val, e)) /* might be in a view */ + ) + { + cosAttrValue *pSpec = pDef->pCosSpecifier; + Slapi_ValueSet *pAttrSpecs = 0; + + + /* Does this entry have a correct cosSpecifier? */ + do + { + Slapi_ValueSet *results = 0; + int type_name_disposition = 0; + char *actual_type_name = 0; + int free_flags = 0; + + if(pSpec && pSpec->val) { + slapi_vattr_values_get_sp(context, e, pSpec->val, &pAttrSpecs, &type_name_disposition, &actual_type_name, 0, &free_flags); + /* MAB: We need to free actual_type_name here !!! + XXX BAD--should use slapi_vattr_values_free() */ + slapi_ch_free((void **) &actual_type_name); + } + + if(pAttrSpecs || pDef->cosType == COSTYPE_POINTER) + { + int index = 0; + + /* does the cosSpecifier value correspond to this template? */ + if(pDef->cosType == COSTYPE_INDIRECT) + { + /* + it always does correspond for indirect schemes (it's a dummy value) + now we must follow the dn of our pointer and retrieve a value to + return + Note: we support one dn only, the result of multiple pointers is undefined + */ + Slapi_Value *indirectdn; + int pointer_flags = 0; + + slapi_valueset_first_value( pAttrSpecs, &indirectdn ); + + if(props) + pointer_flags = *props; + + if( indirectdn != NULL && + !cos_cache_follow_pointer( context, (char*)slapi_value_get_string(indirectdn), type, out_attr, test_this, result, pointer_flags)) + hit = 1; + } + else + { + if(pDef->cosType != COSTYPE_POINTER) + index = slapi_valueset_first_value( pAttrSpecs, &val ); + + while(pDef->cosType == COSTYPE_POINTER || val) + { + if(pDef->cosType == COSTYPE_POINTER || !slapi_utf8casecmp((unsigned char*)pTemplate->cosGrade, (unsigned char*)slapi_value_get_string(val))) + { + /* we have a hit */ + + + if(out_attr) + { + if(cos_cache_cos_2_slapi_valueset(pAttr, out_attr) == 0) + hit = 1; + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: could not create values to return\n",0,0,0); + goto bail; + } + + if(pAttr->attr_cos_merge) + { + merge_mode = 1; + attr_matched_index = attr_index; + } + } + else + { + if(test_this && result) + { + /* compare op */ + if(cos_cache_cmp_attr(pAttr, test_this, result)) + { + hit = 1; + } + } + else + { + /* well, this must be a request for type only */ + hit = 1; + } + } + + break; + } + + if(pDef->cosType != COSTYPE_POINTER) + index = slapi_valueset_next_value( pAttrSpecs, index, &val ); + } + } + } + + if(pSpec) + pSpec = pSpec->list.pNext; + + } while(hit == 0 && pSpec); + + /* MAB: We need to free pAttrSpecs here !!! + XXX BAD--should use slapi_vattr_values_free()*/ + slapi_valueset_free(pAttrSpecs); + + /* is the cosTemplate the default template? */ + if(hit == 0 && pTemplate->template_default && !pDefAttr) + { + /* then lets save the attr in case we need it later */ + pDefAttr = pAttr; + } + } + + pTargetTree = pTargetTree->list.pNext; + + } while(hit == 0 && pTargetTree); + + + if(hit==0 || merge_mode) + attr_index++; + + } while( + (hit == 0 || merge_mode) && + pCache->attrCount > attr_index && + !slapi_utf8casecmp((unsigned char*)type, (unsigned char*)pCache->ppAttrIndex[attr_index]->pAttrName) + ); + + if(!merge_mode) + attr_matched_index = attr_index; + + /* should we use a default value? */ + if(hit == 0 && pDefAttr) + { + /* we have a hit */ + + using_default = 1; + + if(out_attr) + { + if(cos_cache_cos_2_slapi_valueset(pDefAttr, out_attr) == 0) + hit = 1; + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: could not create values to return\n",0,0,0); + goto bail; + } + } + else + { + if(test_this && result) + { + /* compare op */ + if(cos_cache_cmp_attr(pDefAttr, test_this, result)) + hit = 1; + } + else + { + /* well, this must be a request for type only and the entry gets default template value */ + hit = 1; + } + } + } + + if(hit == 1 && out_attr == NULL && test_this == NULL) + ret = 1; + else if(hit == 1) + ret = 0; + + if(props) + *props = 0; + + if(hit == 1 && props && pDefAttr) { + if ( + ((using_default && pDefAttr->attr_operational == 1) || + (!using_default && pCache->ppAttrIndex[attr_matched_index]->attr_operational == 1)) || + ((using_default && pDefAttr->attr_operational_default == 1) || + (!using_default && pCache->ppAttrIndex[attr_matched_index]->attr_operational_default == 1)) ) + { + /* this is an operational attribute, lets mark it so */ + *props |= SLAPI_ATTR_FLAG_OPATTR; + } + } + +bail: + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_query_attr\n",0,0,0); + return ret; +} + +/* + cos_cache_query_attr_free + ------------------------- + frees the memory allocated for the data returned + by cos_cache_query_attr +*/ +static void cos_cache_query_attr_free(struct berval ***vals) +{ + int index = 0; + + while((*vals)[index]) + { + slapi_ch_free((void**)&((*vals)[index])); + index++; + } + + slapi_ch_free((void**)*vals); +} + +/* + cos_cache_find_attr + ------------------- + searches for the attribute "type", and if found returns the index + of the first occurrance of the attribute in the cache top level + indexed attribute list. +*/ +static int cos_cache_find_attr(cosCache *pCache, char *type) +{ + int ret = -1; /* assume failure */ + cosAttributes attr; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_find_attr\n",0,0,0); + + attr.pAttrName = type; + + if(pCache->attrCount-1 != 0) + ret = cos_cache_attr_index_bsearch(pCache, &attr, 0, pCache->attrCount-1); + else + { + /* only one attribute (that will fool our bsearch) lets check it here */ + if(!slapi_utf8casecmp((unsigned char*)type, (unsigned char*)(pCache->ppAttrIndex)[0]->pAttrName)) + { + ret = 0; + } + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_find_attr\n",0,0,0); + return ret; +} + + +/* + cos_cache_schema_check + ---------------------- + check those object classes which match in the input list and the + cached set for allowed attribute types + + return non-null for schema matches, zero otherwise +*/ +static int cos_cache_schema_check(cosCache *pCache, int attr_index, Slapi_Attr *pObjclasses) +{ + int ret = 0; /* assume failure */ + Slapi_Value *val; + int hint; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_schema_check\n",0,0,0); + + hint = slapi_attr_first_value( pObjclasses, &val ); + while(hint != -1) + { + ret = cos_cache_attrval_exists(pCache->ppAttrIndex[attr_index]->pObjectclasses, (char*) slapi_value_get_string(val)); + if(ret) + break; + + hint = slapi_attr_next_value( pObjclasses, hint, &val ); + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_schema_check\n",0,0,0); + return ret; +} + +/* + cos_cache_schema_build + ---------------------- + For each attribute in our global cache add the objectclasses which allow it. + This may be referred to later to check schema is not being violated. +*/ +static int cos_cache_schema_build(cosCache *pCache) +{ + int ret = 0; /* we assume success, with operational attributes not supplied in schema we might fail otherwise */ + struct objclass *oc; + char *pLastName = 0; + cosAttrValue *pLastRef = 0; + int attr_index = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_schema_build\n",0,0,0); + + if(!config_get_schemacheck()) + ret = 0; + + /* + it is expected that in all but the most hard core cases, the number of + objectclasses will out number the attributes we look after - so we make + the objectclasses the outside loop. However, even if they are not, we + perform binary searches on the attribute list anyway, so it should be + considerably faster to search than the linked list of objectclasses (even + with the string comparisons going on) + */ + oc_lock_read(); + for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) + { + char **pppAttrs[2]; + int index; + int attrType = 0; + + pppAttrs[0] = oc->oc_required; + pppAttrs[1] = oc->oc_allowed; + + /* we need to check both required and allowed attributes I think */ + while(attrType < 2) + { + if(pppAttrs[attrType]) + { + index = 0; + + while(pppAttrs[attrType][index]) + { + attr_index = cos_cache_find_attr(pCache, pppAttrs[attrType][index]); + if(attr_index != -1) + { + /* + this attribute is one of ours, add this + objectclass to the objectclass list + note the index refers to the first + occurrence of this attribute in the list, + later we will copy over references to this + list to all the other attribute duplicates. + */ + + cos_cache_add_attrval(&(pCache->ppAttrIndex[attr_index]->pObjectclasses), oc->oc_name); + ret = 0; + } + index++; + } + } + + attrType++; + } + } + oc_unlock(); + + /* + OK, now we need to add references to the real + lists to the duplicate attribute entries. + (this allows the schema check to be a little + less complex and just a little quicker) + */ + pLastName = pCache->ppAttrIndex[0]->pAttrName; + pLastRef = pCache->ppAttrIndex[0]->pObjectclasses; + + for(attr_index=1; attr_index<pCache->attrCount; attr_index++) + { + if(!slapi_utf8casecmp((unsigned char*)pCache->ppAttrIndex[attr_index]->pAttrName, (unsigned char*)pLastName)) + { + /* copy over reference */ + pCache->ppAttrIndex[attr_index]->pObjectclasses = pLastRef; + } + else + { + /* remember what went before */ + pLastName = pCache->ppAttrIndex[attr_index]->pAttrName; + pLastRef = pCache->ppAttrIndex[attr_index]->pObjectclasses; + } + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_schema_build\n",0,0,0); + return ret; +} + + +/* + cos_cache_index_all + ------------------- + Indexes every attribute in the cache for fast binary lookup + on attributes from the top level of the cache. + Also fixes up all parent pointers so that a single attribute + lookup will allow access to all information regarding that attribute. + Attributes that appear more than once in the cache will also + be indexed more than once - this means that a pure binary + search is not possible, but it is possible to make use of a + duplicate entry aware binary search function - which are rare beasts, + so we'll need to provide cos_cache_attr_bsearch() + + This is also a handy time to mark the attributes as overides if + necessary +*/ + +static int cos_cache_index_all(cosCache *pCache) +{ + int ret = -1; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_index_all\n",0,0,0); + + /* + first fixup the index array so we can use qsort() + also fixup the parent pointers + */ + + pCache->ppTemplateList = 0; + pCache->templateCount = 0; + pCache->ppAttrIndex = 0; + + pCache->attrCount = cos_cache_total_attr_count(pCache); + if(pCache->attrCount && pCache->templateCount) + { + int tmpindex = 0; + int cmpindex = 0; + int actualCount = 0; + + pCache->ppAttrIndex = (cosAttributes**)slapi_ch_malloc(sizeof(cosAttributes*) * pCache->attrCount); + pCache->ppTemplateList = (char**)slapi_ch_calloc((pCache->templateCount + 1) * 2, sizeof(char*)); + if(pCache->ppAttrIndex && pCache->ppTemplateList) + { + int attrcount = 0; + cosDefinitions *pDef = pCache->pDefs; + cosAttrValue *pAttrVal = 0; + + while(pDef) + { + cosTemplates *pCosTmps = pDef->pCosTmps; + + while(pCosTmps) + { + cosAttributes *pAttrs = pCosTmps->pAttrs; + + pCosTmps->pParent = pDef; + + while(pAttrs) + { + pAttrs->pParent = pCosTmps; + (pCache->ppAttrIndex)[attrcount] = pAttrs; + + if(cos_cache_attrval_exists(pDef->pCosOverrides, pAttrs->pAttrName)) + pAttrs->attr_override = 1; + else + pAttrs->attr_override = 0; + + if(cos_cache_attrval_exists(pDef->pCosOperational, pAttrs->pAttrName)) + pAttrs->attr_operational = 1; + else + pAttrs->attr_operational = 0; + + if(cos_cache_attrval_exists(pDef->pCosMerge, pAttrs->pAttrName)) + pAttrs->attr_cos_merge = 1; + else + pAttrs->attr_cos_merge = 0; + + if(cos_cache_attrval_exists(pDef->pCosOpDefault, pAttrs->pAttrName)) + pAttrs->attr_operational_default = 1; + else + pAttrs->attr_operational_default = 0; + + attrcount++; + + pAttrs = pAttrs->list.pNext; + } + + pCosTmps = pCosTmps->list.pNext; + } + + /* + we need to build the template dn list too, + we are going to take care that we do not + add duplicate dns or dns that have + ancestors elsewhere in the list since this + list will be binary searched (with a special + BS alg) to find an ancestor tree for a target + that has been modified - that comes later in + this function however - right now we'll just + slap them in the list + */ + pAttrVal = pDef->pCosTemplateDn; + + while(pAttrVal) + { + slapi_dn_normalize(pAttrVal->val); + pCache->ppTemplateList[tmpindex] = pAttrVal->val; + + tmpindex++; + pAttrVal = pAttrVal->list.pNext; + } + + pDef = pDef->list.pNext; + } + + /* now sort the index array */ + qsort(pCache->ppAttrIndex, attrcount, sizeof(cosAttributes*), cos_cache_attr_compare); + qsort(pCache->ppTemplateList, tmpindex, sizeof(char*), cos_cache_string_compare); + + /* + now we have the sorted template dn list, we can get rid of + duplicates and entries that have an ancestor elsewhere in + the list - all this in the name of faster searches + */ + + /* first go through zapping the useless PARPAR - THIS DOES NOT WORK */ + tmpindex = 1; + cmpindex = 0; + actualCount = pCache->templateCount; + + while(tmpindex < pCache->templateCount) + { + if( + !slapi_utf8casecmp((unsigned char*)pCache->ppTemplateList[tmpindex],(unsigned char*)pCache->ppTemplateList[cmpindex]) || + slapi_dn_issuffix(pCache->ppTemplateList[tmpindex], pCache->ppTemplateList[cmpindex]) + ) + { + /* this guy is a waste of space */ + pCache->ppTemplateList[tmpindex] = 0; + actualCount--; + } + else + cmpindex = tmpindex; + + tmpindex++; + } + + /* now shuffle everything up to the front to cover the bald spots */ + tmpindex = 1; + cmpindex = 0; + + while(tmpindex < pCache->templateCount) + { + if(pCache->ppTemplateList[tmpindex] != 0) + { + if(cmpindex) + { + pCache->ppTemplateList[cmpindex] = pCache->ppTemplateList[tmpindex]; + pCache->ppTemplateList[tmpindex] = 0; + cmpindex++; + } + } + else + { + if(cmpindex == 0) + cmpindex = tmpindex; + } + + tmpindex++; + } + + pCache->templateCount = actualCount; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: cos cache index built\n",0,0,0); + + ret = 0; + } + else + { + if(pCache->ppAttrIndex) + slapi_ch_free((void**)(&pCache->ppAttrIndex)); + + if(pCache->ppTemplateList) + slapi_ch_free((void**)(&pCache->ppTemplateList)); + + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_index_all: failed to allocate index memory\n",0,0,0); + } + } + else + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_index_all: no attributes to index\n",0,0,0); + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_index_all\n",0,0,0); + return ret; +} + + +/* + cos_cache_total_attr_count + -------------------------- + walks the entire cache counting all attributes + note: this is coded so that it may be called + prior to the cache indexing of attributes - in + fact it is called by the code that creates the + index. Once indexing has been performed, it is + *much* *much* faster to get the count from the + cache object itself - cosCache::attrCount. + + Additionally - a side effect is that the template + target trees are counted and placed in the cache level + target tree count - probably should be renamed, + but lets let it slide for now + + returns the number of attributes counted +*/ +static int cos_cache_total_attr_count(cosCache *pCache) +{ + int count = 0; + cosDefinitions *pDef = pCache->pDefs; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_total_attr_count\n",0,0,0); + + pCache->templateCount = 0; + + while(pDef) + { + cosTemplates *pCosTmps = pDef->pCosTmps; + + while(pCosTmps) + { + cosAttributes *pAttrs = pCosTmps->pAttrs; + + while(pAttrs) + { + count++; + pAttrs = pAttrs->list.pNext; + } + + pCache->templateCount++; + pCosTmps = pCosTmps->list.pNext; + } + + pDef = pDef->list.pNext; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_total_attr_count\n",0,0,0); + return count; +} + + +/* + cos_cache_XXX_compare + --------------------- + this set of functions are passed to sorting and searching + functions to provide an ordering comparison between to structures +*/ +#if 0 +int cos_cache_attrval_compare(const void *e1, const void *e2) +{ + return slapi_utf8casecmp((unsigned char*)(*(cosAttrValue**)e1)->val,(unsigned char*)(*(cosAttrValue**)e2)->val); +} +#endif + +static int cos_cache_attr_compare(const void *e1, const void *e2) +{ + int com_Result; + cosAttributes *pAttr = (*(cosAttributes**)e1); + cosTemplates *pTemplate = (cosTemplates*)pAttr->pParent; + cosDefinitions *pDef = (cosDefinitions*)pTemplate->pParent; + cosAttrValue *pcostt = pDef->pCosTargetTree; + cosAttributes *pAttr1 = (*(cosAttributes**)e2); + cosTemplates *pTemplate1 = (cosTemplates*)pAttr1->pParent; + cosDefinitions *pDef1 = (cosDefinitions*)pTemplate1->pParent; + cosAttrValue *pcostt1 = pDef1->pCosTargetTree; + + /* Now compare the names of the attributes */ + com_Result = slapi_utf8casecmp((unsigned char*)(*(cosAttributes**)e1)->pAttrName,(unsigned char*)(*(cosAttributes**)e2)->pAttrName); + if(0 == com_Result) + /* Now compare the definition Dn parents */ + com_Result = slapi_utf8casecmp((unsigned char*)pcostt1->val,(unsigned char*)pcostt->val); + if(0 == com_Result) + /* Now compare the cosPririoties */ + com_Result = pTemplate->cosPriority - pTemplate1->cosPriority; + /* Now compare the prirority */ + if(0 == com_Result) + return -1; + return com_Result; +} + +#if 0 +int cos_cache_tmpl_compare(const void *e1, const void *e2) +{ + return slapi_utf8casecmp((unsigned char*)(*(cosTemplates**)e1)->cosGrade,(unsigned char*)(*(cosTemplates**)e2)->cosGrade); +} +#endif + +static int cos_cache_string_compare(const void *e1, const void *e2) +{ + return slapi_utf8casecmp((*(unsigned char**)e1),(*(unsigned char**)e2)); +} + +static int cos_cache_template_index_compare(const void *e1, const void *e2) +{ + int ret = 0; + + if(0 == slapi_dn_issuffix((const char*)e1,*(const char**)e2)) + ret = slapi_utf8casecmp(*(unsigned char**)e2,(unsigned char*)e1); + else + ret = 0; + + return ret; +} + +/* + cos_cache_template_index_bsearch + -------------------------------- + searches the template dn index for a match +*/ +static int cos_cache_template_index_bsearch(const char *dn) +{ + int ret = 0; + cosCache *pCache; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_template_index_bsearch\n",0,0,0); + + if(-1 != cos_cache_getref((cos_cache**)&pCache)) + { + if(bsearch(dn, pCache->ppTemplateList, pCache->templateCount, sizeof(char*), cos_cache_template_index_compare)) + ret = 1; + + cos_cache_release((cos_cache*)pCache); + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_template_index_bsearch\n",0,0,0); + + return ret; +} + +/* + cos_cache_attr_index_bsearch - RECURSIVE + ---------------------------------------- + performs a binary search on the cache attribute index + return -1 if key is not found + the index into attribute index array of the first occurrance + of that attribute type otherwise +*/ +static int cos_cache_attr_index_bsearch( const cosCache *pCache, const cosAttributes *key, int lower, int upper ) +{ + int ret = -1; + int index = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_attr_index_bsearch\n",0,0,0); + + if(upper >= lower) + { + if(upper != 0) + index = ((upper-lower)/2) + lower; + else + index = 0; + + ret = slapi_utf8casecmp((unsigned char*)key->pAttrName, (unsigned char*)(pCache->ppAttrIndex)[index]->pAttrName); + if(ret == 0) + { + /* + we have a match, backtrack to the + first occurrance of this attribute + type + */ + do + { + index--; + if(index >= 0) + ret = slapi_utf8casecmp((unsigned char*)key->pAttrName, (unsigned char*)(pCache->ppAttrIndex)[index]->pAttrName); + } while(index >= 0 && ret == 0); + + index++; + } + else + { + /* seek elsewhere */ + if(ret < 0) + { + /* take the low road */ + index = cos_cache_attr_index_bsearch(pCache, key, lower, index-1); + } + else + { + /* go high */ + index = cos_cache_attr_index_bsearch(pCache, key, index+1, upper); + } + } + } + else + index = -1; + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_attr_index_bsearch\n",0,0,0); + return index; +} + + +static int cos_cache_cmp_attr(cosAttributes *pAttr, Slapi_Value *test_this, int *result) +{ + int ret = 0; + int index = 0; + cosAttrValue *pAttrVal = pAttr->pAttrValue; + char *the_cmp = (char *)slapi_value_get_string(test_this); + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_cmp_attr\n",0,0,0); + + *result = 0; + + while( pAttrVal ) + { + if(!slapi_utf8casecmp((unsigned char*)the_cmp, (unsigned char*)pAttrVal->val)) + { + /* compare match */ + *result = 1; + break; + } + + pAttrVal = pAttrVal->list.pNext; + index++; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_cmp_attr\n",0,0,0); + return ret; +} + + +/* + cos_cache_cos_2_slapi_attr + ---------------------- + converts a cosAttributes structure to a Slapi_Attribute +*/ +static int cos_cache_cos_2_slapi_valueset(cosAttributes *pAttr, Slapi_ValueSet **out_vs) +{ + int ret = 0; + int index = 0; + cosAttrValue *pAttrVal = pAttr->pAttrValue; + int add_mode = 0; + static Slapi_Attr *attr = 0; /* allocated once, never freed */ + static done_once = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_cos_2_slapi_attr\n",0,0,0); + + /* test out_vs for existing values */ + if(*out_vs) + { + add_mode = 1; + if(!done_once) + { + attr = slapi_attr_new(); /* lord knows why this is needed by slapi_valueset_find*/ + slapi_attr_init(attr, "cos-bogus"); + done_once = 1; + } + } + else + *out_vs = slapi_valueset_new(); + + if(*out_vs) + { + if(!add_mode) + slapi_valueset_init(*out_vs); + + while( pAttrVal ) + { + Slapi_Value *val = slapi_value_new_string(pAttrVal->val); + if(val) { + if(!add_mode || !slapi_valueset_find(attr, *out_vs, val)) { + slapi_valueset_add_value_ext(*out_vs, val, SLAPI_VALUE_FLAG_PASSIN); + } + else { + slapi_value_free(&val); + } + } + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_cos_2_slapi_attr: memory allocation error\n",0,0,0); + ret = -1; + goto bail; + } + + pAttrVal = pAttrVal->list.pNext; + index++; + } + } + else + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_cos_2_slapi_attr: memory allocation error\n",0,0,0); + ret = -1; + } + +bail: + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_cos_2_slapi_attr\n",0,0,0); + return ret; +} + + +/* + cos_cache_change_notify + ----------------------- + determines if the change effects the cache and if so + signals a rebuild. + + XXXrbyrne This whole mechanism needs to be revisited--it means that + the modifying client gets his LDAP response, and an unspecified and + variable + period of time later, his mods get taken into account in the cos cache. + This makes it hard to program reliable admin tools for COS--DSAME + has already indicated this is an issue for them. + Additionally, it regenerates the _whole_ cache even for eeny weeny mods-- + does it really neeed to ? Additionally, in order to ensure we + do not miss any mods, we may tend to regen the cache, even if we've already + taken a mod into account in an earlier regeneration--currently there is no + way to know we've already dealt with the mod. + The right thing is something like: figure out what's being changed + and change only that in the cos cache and do it _before_ the response + goes to the client....or do a task that he can poll. +*/ +void cos_cache_change_notify(Slapi_PBlock *pb) +{ + char *dn; + Slapi_Attr *pObjclasses = 0; + int index = 0; + int do_update = 0; + struct slapi_entry *e; + Slapi_Backend *be=NULL; + int rc = 0; + int optype = -1; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_change_notify\n",0,0,0); + + /* Don't update local cache when remote entries */ + /* are updated. */ + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + if ( ( be!=NULL ) && (slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA))) + goto bail; + + /* need to work out if a cache rebuild is necessary */ + if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn )) + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_change_notify: failed to get dn of changed entry",0,0,0); + goto bail; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &rc); + if (0 != rc) { + /* The operation did not succeed. As far as the cos cache is concerned, no need to update anything */ + goto bail; + } + + /* + * For DELETE, MODIFY, MODRDN: see if the pre-op entry was cos significant. + * For ADD, MODIFY, MODRDN: see if the post-op was cos significant. + * Touching a cos significant entry triggers the update + * of the whole cache. + */ + slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &optype ); + if ( optype == SLAPI_OPERATION_DELETE || + optype == SLAPI_OPERATION_MODIFY || + optype == SLAPI_OPERATION_MODRDN ) { + + slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &e); + if ( cos_cache_entry_is_cos_related(e)) { + do_update = 1; + } + } + if ( !do_update && + (optype == SLAPI_OPERATION_ADD || + optype == SLAPI_OPERATION_MODIFY || + optype == SLAPI_OPERATION_MODRDN )) { + + /* Adds have null pre-op entries */ + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e); + if ( cos_cache_entry_is_cos_related(e)) { + do_update = 1; + } + } + + /* + * Check if this was an entry in a template tree (dn contains + * the old dn value). + * It's only relevant for indirect templates, which will + * not usually contain the "objectclass: costemplate" pair + * and so will not get detected by the above code. + * In fact, everything would still work fine if + * we just ignored a mod of one of these indirect templates, + * as we do not cache values from them, but the advantage of + * triggering an update here is that + * we can maintain the invariant that we only ever cache + * definitions that have _valid_ templates--the active cache + * stays lean in the face of errors. + */ + if( !do_update && cos_cache_template_index_bsearch(dn)) { + LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_change_notify:" + "updating due to indirect template change(%s)\n", + dn,0,0); + do_update = 1; + } + + /* Do the update if required */ + if(do_update) + { + slapi_lock_mutex(change_lock); + slapi_notify_condvar( something_changed, 1 ); + cos_cache_notify_flag = 1; + slapi_unlock_mutex(change_lock); + } + +bail: + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_change_notify\n",0,0,0); +} + +/* + cos_cache_stop + -------------- + notifies the cache thread we are stopping +*/ +void cos_cache_stop() +{ + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_stop\n",0,0,0); + + /* first deregister our state change func */ + slapi_unregister_backend_state_change((void *)cos_cache_backend_state_change); + + slapi_lock_mutex(change_lock); + keeprunning = 0; + slapi_notify_condvar( something_changed, 1 ); + slapi_unlock_mutex(change_lock); + + /* wait on shutdown */ + slapi_lock_mutex(stop_lock); + + /* release the caches reference to the cache */ + cos_cache_release(pCache); + + slapi_destroy_mutex(cache_lock); + slapi_destroy_mutex(change_lock); + slapi_destroy_condvar(something_changed); + + slapi_unlock_mutex(stop_lock); + slapi_destroy_mutex(stop_lock); + slapi_destroy_condvar(start_cond); + slapi_destroy_mutex(start_lock); + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_stop\n",0,0,0); +} + +/* + cos_cache_backwards_stricmp_and_clip + ------------------------------------ + compares s2 to s1 starting from end of the strings until the beginning of + either matches result in the s2 value being clipped from s1 with a NULL char + and 1 being returned as opposed to 0 + +*/ +static int cos_cache_backwards_stricmp_and_clip(char*s1,char*s2) +{ + int ret = 0; + int s1len = 0; + int s2len = 0; + + LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_backwards_stricmp_and_clip\n",0,0,0); + + s1len = strlen(s1); + s2len = strlen(s2); + + if(s1len > s2len && s2len > 0) + { + while(s1len > -1 && s2len > -1) + { + s1len--; + s2len--; + + if(s1[s1len] != s2[s2len]) + break; + else + { + if(s2len == 0) + { + /* hit! now clip */ + ret = 1; + s1[s1len] = '\0'; + } + } + } + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_backwards_stricmp_and_clip\n",0,0,0); + return ret; +} + + +static int cos_cache_follow_pointer( vattr_context *c, const char *dn, char *type, Slapi_ValueSet **out_vs, Slapi_Value *test_this, int *result, int flags) +{ + int ret = -1; /* assume failure */ + Slapi_PBlock *pDnSearch = 0; + Slapi_Entry **pEntryList = 0; + char *attrs[2]; + int entryIndex = 0; + int op = 0; + int type_test = 0; + int type_name_disposition = 0; + char *actual_type_name = 0; + int free_flags = 0; + Slapi_ValueSet *tmp_vs = 0; + + attrs[0] = type; + attrs[1] = 0; + + /* Use new internal operation API */ + pDnSearch = slapi_pblock_new(); + if (pDnSearch) { + slapi_search_internal_set_pb(pDnSearch, dn, LDAP_SCOPE_BASE,"(|(objectclass=*)(objectclass=ldapsubentry))",attrs, + 0,NULL,NULL,cos_get_plugin_identity(),0); + slapi_search_internal_pb(pDnSearch); + slapi_pblock_get( pDnSearch, SLAPI_PLUGIN_INTOP_RESULT, &ret); + } + + if(pDnSearch && (ret == LDAP_SUCCESS)) + { + ret = -1; + + slapi_pblock_get( pDnSearch, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &pEntryList); + if(pEntryList) + { + if(out_vs) /* if this set, a value is required */ + op = 1; + else if(test_this && result) /* compare op */ + op = 2; + else + { + /* requires only type present */ + op = 1; + type_test = 1; + } + + switch(op) + { + case 1: + /* straight value return or type test */ + if(type_test) + out_vs = &tmp_vs; + + ret = slapi_vattr_values_get_sp(c, pEntryList[0], type, out_vs,&type_name_disposition, &actual_type_name, flags, &free_flags); + + if(actual_type_name) + slapi_ch_free((void **) &actual_type_name); + + if(type_test && free_flags == SLAPI_VIRTUALATTRS_RETURNED_COPIES) + slapi_valueset_free(*out_vs); + + break; + + case 2: + /* this must be a compare op */ + ret = slapi_vattr_value_compare_sp(c, pEntryList[0],type, test_this, result, flags); + break; + + default: + goto bail; + } + } + } + +bail: + /* clean up */ + if(pDnSearch) + { + slapi_free_search_results_internal(pDnSearch); + slapi_pblock_destroy(pDnSearch); + } + + return ret; +} + + +/* + * cos_cache_backend_state_change() + * -------------------------------- + * This is called when a backend changes state + * We simply signal to rebuild the cos cache in this case + * + */ +void cos_cache_backend_state_change(void *handle, char *be_name, + int old_be_state, int new_be_state) +{ + slapi_lock_mutex(change_lock); + slapi_notify_condvar( something_changed, 1 ); + slapi_unlock_mutex(change_lock); +} + +/* + * returns non-zero: entry is cos significant (note does not detect indirect + * template entries). + * 0 : entry is not cos significant. +*/ +static int cos_cache_entry_is_cos_related( Slapi_Entry *e) { + + int rc = 0; + Slapi_Attr *pObjclasses = NULL; + + if ( e == NULL ) { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_change_notify:" + "modified entry is NULL--updating cache just in case!", + 0,0,0); + rc = 1; + } else { + + if(slapi_entry_attr_find( e, "objectclass", &pObjclasses )) + { + LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_change_notify:" + " failed to get objectclass from %s", + slapi_entry_get_dn(e),0,0); + rc = 0; + } else { + + Slapi_Value *val = NULL; + int index = 0; + char *pObj; + + /* check out the object classes to see if this was a cosDefinition */ + + index = slapi_attr_first_value( pObjclasses, &val ); + while(!rc && val) + { + pObj = (char*)slapi_value_get_string(val); + + /* + * objectclasses are ascii--maybe strcasecmp() is faster than + * slapi_utf8casecmp() + */ + if( !strcasecmp(pObj, "cosdefinition") || + !strcasecmp(pObj, "cossuperdefinition") || + !strcasecmp(pObj, "costemplate") + ) + { + rc = 1; + } + + index = slapi_attr_next_value( pObjclasses, index, &val ); + } + } + } + return(rc); +} diff --git a/ldap/servers/plugins/cos/cos_cache.h b/ldap/servers/plugins/cos/cos_cache.h new file mode 100644 index 00000000..ef47a9ab --- /dev/null +++ b/ldap/servers/plugins/cos/cos_cache.h @@ -0,0 +1,19 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#if !defined( _COS_CACHE_H ) +#define _COS_CACHE_H + +typedef void cos_cache; + +int cos_cache_init(); +void cos_cache_stop(); +int cos_cache_getref(cos_cache **ppCache); +int cos_cache_addref(cos_cache *pCache); +int cos_cache_release(cos_cache *pCache); +void cos_cache_change_notify(Slapi_PBlock *pb); + +#endif /* _COS_CACHE_H */ diff --git a/ldap/servers/plugins/cos/dllmain.c b/ldap/servers/plugins/cos/dllmain.c new file mode 100644 index 00000000..fabf8677 --- /dev/null +++ b/ldap/servers/plugins/cos/dllmain.c @@ -0,0 +1,96 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/distrib/Makefile b/ldap/servers/plugins/distrib/Makefile new file mode 100644 index 00000000..3144e8cb --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile @@ -0,0 +1,109 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# +# GNU Makefile for Directory Server distribution plugin +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libdistrib +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libdistrib.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd + +DIS_OBJS= \ + distrib.o + + +OBJS = $(addprefix $(OBJDEST)/, $(DIS_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBDIS_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +# The sample distribution plugin is not part of DS. +# So we generate the shared library outside of $(LIBDIR) +# so that it's not retreived by the packaging makefiles. +#LIBDIS = $(addprefix $(LIBDIR)/, $(DIS_DLL).$(DLL_SUFFIX)) +LIBDIS = $(addprefix $(OBJDEST)/, $(DIS_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += \ + $(LIBSLAPD_DEP) \ + $(LDAP_LIBUTIL_DEP) \ + $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += \ + $(LDAPSDK_DEP) \ + $(SECURITY_DEP) +EXTRA_LIBS += \ + $(LIBSLAPD) \ + $(LDAP_SDK_LIBLDAP_DLL) \ + $(LIBUTIL) \ + $(NSPRLINK) \ + $(LDAP_COMMON_LIBS) +endif +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += \ + $(LIBSLAPD_DEP) \ + $(LDAP_LIBUTIL_DEP) \ + $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += \ + $(LDAPSDK_DEP) +EXTRA_LIBS += \ + $(LIBSLAPDLINK) \ + $(LDAP_SDK_LIBLDAP_DLL) \ + $(LIBUTIL) \ + $(NSPRLINK) \ + $(LDAP_COMMON_LIBS) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libdistrib.def" +CFLAGS+= /WX +endif # WINNT + +ifeq ($(ARCH), AIX) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBDIS) + +$(LIBDIS): $(OBJS) $(LIBDIS_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBDIS_DLL_OBJ) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBDIS_DLL_OBJ) +endif + $(RM) $(LIBDIS) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/distrib/Makefile.AIX b/ldap/servers/plugins/distrib/Makefile.AIX new file mode 100644 index 00000000..d155626d --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.AIX @@ -0,0 +1,44 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# AIX Makefile for Directory Server plug-in examples +# NOTE: Make sure to set the DSLIB variable to the path +# to the libslapd_shr.a file (for example, +# DSLIB = /usr/netscape/suitespot/lib/libslapd_shr.a + +CC = xlC_r +LD = ld + +# Set this to the path to the libslapd_shr.a file +DSLIB = + +INCLUDE_FLAGS= -I../../include +CFLAGS= $(INCLUDE_FLAGS) -qarch=com +LIBPATH=/usr/lib/threads:/usr/lpp/xlC/lib:/usr/lib:/lib:..:../../../../lib +EXTRA_LIBS= -bI:/usr/lib/lowsys.exp -lC_r -lC -lpthreads -lc_r -lm \ + /usr/lib/libc.a $(DSLIB) +LDFLAGS= -bE:libtest-plugin_shr.exp -bM:SRE -bnoentry -blibpath:$(LIBPATH) \ + $(EXTRA_LIBS) + +OBJS = distrib + +all: libtest-plugin_shr.a + + +libtest-plugin_shr.a: $(OBJS) + rm -f libtest-plugin_shr.exp + echo "#!" > libtest-plugin_shr.exp + nm -B -C -g $(OBJS) | \ + awk '/ [B,T,D] / {print $$3}' | \ + sed -e 's/^\.//' | sort -u >> libtest-plugin_shr.exp + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin_shr.a + diff --git a/ldap/servers/plugins/distrib/Makefile.BSDI b/ldap/servers/plugins/distrib/Makefile.BSDI new file mode 100644 index 00000000..c8016c9e --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.BSDI @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.HPUX b/ldap/servers/plugins/distrib/Makefile.HPUX new file mode 100644 index 00000000..34c24521 --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.HPUX @@ -0,0 +1,27 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# HPUX Makefile for Directory Server plug-in examples + +CC = cc +LD = ld + +INCLUDE = -I../../include +CFLAGS=$(INCLUDE) -D_HPUX_SOURCE -Aa +DA1.0 +z +LDFLAGS = -b + +OBJS = distrib.o + +all: libtest-plugin.sl + +libtest-plugin.sl: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.sl diff --git a/ldap/servers/plugins/distrib/Makefile.HPUX64 b/ldap/servers/plugins/distrib/Makefile.HPUX64 new file mode 100644 index 00000000..d6a09339 --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.HPUX64 @@ -0,0 +1,27 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# HPUX Makefile for Directory Server plug-in examples + +CC = cc +LD = ld + +INCLUDE = -I../../include +CFLAGS=$(INCLUDE) -D_HPUX_SOURCE -Aa +DA2.0W +z +LDFLAGS = -b + +OBJS = distrib.o + +all: libtest-plugin.sl + +libtest-plugin.sl: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.sl diff --git a/ldap/servers/plugins/distrib/Makefile.IRIX b/ldap/servers/plugins/distrib/Makefile.IRIX new file mode 100644 index 00000000..f2ffc0c7 --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.IRIX @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# IRIX Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_SGI_MP_SOURCE -fullwarn +LDFLAGS = -32 -shared + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.Linux b/ldap/servers/plugins/distrib/Makefile.Linux new file mode 100644 index 00000000..b5fe839f --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.Linux @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = gcc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.OSF1 b/ldap/servers/plugins/distrib/Makefile.OSF1 new file mode 100644 index 00000000..2c2c4660 --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.OSF1 @@ -0,0 +1,29 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# OSF1 Makefile for Directory Server plug-in examples + +CC = cc +LD = ld + +INCLUDE = -I../../include +CFLAGS = $(INCLUDE) -DIS_64 -ieee_with_inexact -pthread -DOSF1 +LDFLAGS = -shared -all -expect_unresolved "*" -taso + + +OBJS = distrib.o + +all: libtest-plugin.so + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.ReliantUNIX b/ldap/servers/plugins/distrib/Makefile.ReliantUNIX new file mode 100644 index 00000000..c8016c9e --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.ReliantUNIX @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.SOLARIS b/ldap/servers/plugins/distrib/Makefile.SOLARIS new file mode 100644 index 00000000..c8016c9e --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.SOLARIS @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.SOLARIS64 b/ldap/servers/plugins/distrib/Makefile.SOLARIS64 new file mode 100644 index 00000000..170d2b47 --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.SOLARIS64 @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC -xarch=v9 +LDFLAGS = -G -xarch=v9 + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.SOLARISx86 b/ldap/servers/plugins/distrib/Makefile.SOLARISx86 new file mode 100644 index 00000000..c8016c9e --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.SOLARISx86 @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.UnixWare b/ldap/servers/plugins/distrib/Makefile.UnixWare new file mode 100644 index 00000000..c8016c9e --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.UnixWare @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.UnixWareUDK b/ldap/servers/plugins/distrib/Makefile.UnixWareUDK new file mode 100644 index 00000000..c8016c9e --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.UnixWareUDK @@ -0,0 +1,30 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +# SOLARIS Makefile for Directory Server plug-in examples +# + +CC = cc +LD = ld + +INCLUDE_FLAGS = -I../../include +CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC +LDFLAGS = -G + +OBJS = distrib.o + +all: libtest-plugin.so + + +libtest-plugin.so: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +clean: + -rm -f $(OBJS) libtest-plugin.so + diff --git a/ldap/servers/plugins/distrib/Makefile.WINNT b/ldap/servers/plugins/distrib/Makefile.WINNT new file mode 100644 index 00000000..ddb02e29 --- /dev/null +++ b/ldap/servers/plugins/distrib/Makefile.WINNT @@ -0,0 +1,38 @@ +# Makefile for Directory Server plug-in +# + +CC = cl +LD = link + + +TARGET=libdistrib + +OBJS=distrib.obj + + +INC = ..\..\include +CFLAGS = /nologo -I $(INC) /c +LDFLAGS = /dll /nologo +LIBS=/DEFAULTLIB:kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib ..\..\lib\libslapd.lib ..\..\lib\libnspr4.lib + + +all: \ + init \ + $(TARGET).dll + +init: + "c:\program files\microsoft visual studio\vc98\bin\vcvars32.bat" + + +$(TARGET).dll: $(OBJS) + $(LD) $(LDFLAGS) /def:$(TARGET).def /out:$(TARGET).dll $(LIBS) $(OBJS) + -rm -f $(OBJS2) *~ + +.c.obj: + $(CC) $(CFLAGS) $< + +clean: + del -f $(OBJS) $(TARGET).dll *~ + + + diff --git a/ldap/servers/plugins/distrib/README b/ldap/servers/plugins/distrib/README new file mode 100644 index 00000000..3ee6ff88 --- /dev/null +++ b/ldap/servers/plugins/distrib/README @@ -0,0 +1,23 @@ + ---------------------------- + Sample pluggable distribution logic + for Netscape Directory Server + ---------------------------- + +This directory contains code for some sample server plug-ins intended for +use with the Netscape Directory Server 7. + + NOTE: Before you compile and run these examples, make sure + to change any server-specific data in the examples to + values applicable to your Directory Server. + +distrib.c +---------- +This is an example of a distribution function that can be used +to distribute a flat namespace into several backends + +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ diff --git a/ldap/servers/plugins/distrib/distrib.c b/ldap/servers/plugins/distrib/distrib.c new file mode 100644 index 00000000..fd8ea5b6 --- /dev/null +++ b/ldap/servers/plugins/distrib/distrib.c @@ -0,0 +1,222 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ +#include <ctype.h> +#include <string.h> +#include "slapi-plugin.h" + +/* + * These are examples of distribution function as declared in mapping tree node + * This function will be called for every operations + * reaching this node, including subtree search operations that are started + * above this node + * + * Parameters : + * . pb is the pblock of the operation + * . target_dn is the target DN of the operation + * . mtn_be_names is the list of names of backends declared for this node + * . be_count is the number of backends declared + * . node_dn is the node where the distribution function is set + * + * The return value of the functions should be the indice of the backend + * in the mtn_be_names table + * For search operation, the value SLAPI_BE_ALL_BACKENDS can be used to + * specify that all backends must be searched + * The use of value SLAPI_BE_ALL_BACKENDS for operation other than search + * is not supported and may give random results + * + */ + +/* + * Distribute the entries based on the first letter of their rdn + * + * . Entries starting with anything other that a-z or A-Z will always + * go in backend 0 + * . Entries starting with letter (a-z or A-Z) will be shared between + * the backends depending following the alphabetic order + * Example : if 3 backends are used, entries starting with A-I will go + * in backend 0, entries starting with J-R will go in backend 1, entries + * starting with S-Z will go in backend 2 + * + * Of course this won't work for all locales... + * + * This example only works for a flat namespace below the node DN + */ +int alpha_distribution(Slapi_PBlock *pb, Slapi_DN * target_dn, + char **mtn_be_names, int be_count, Slapi_DN * node_dn) +{ + unsigned long op_type; + Slapi_Operation *op; + char *rdn_type; + char *rdn_value; + Slapi_RDN *rdn = NULL; + char c; + + /* first check the operation type + * searches at node level or above it should go in all backends + * searches below node level should go in only one backend + */ + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + op_type = slapi_op_get_type(op); + if ((op_type == SLAPI_OPERATION_SEARCH) && + slapi_sdn_issuffix(node_dn, target_dn)) + return SLAPI_BE_ALL_BACKENDS; + + /* now choose the backend + * anything starting with a char different from a-z or A-Z will + * go in backend 0 + */ + + /* get the first char of first value of rdn */ + rdn = slapi_rdn_new(); + slapi_sdn_get_rdn(target_dn, rdn); + slapi_rdn_get_first(rdn, &rdn_type, &rdn_value); + c = rdn_value[0]; + + if (!(((c >= 'a') && (c <= 'z')) || + ((c >= 'A') && (c <= 'Z')) )) + { + return 0; + } + + slapi_rdn_free(&rdn); + + /* for entries with rdn starting with alphabetic characters + * use the formula : (c - 'A') * be_count/26 + * to calculate the backend number + */ + return (toupper(c) - 'A') * be_count/26; +} + +/* + * Distribute the entries based on a simple hash algorithme + */ +int hash_distribution(Slapi_PBlock *pb, Slapi_DN * target_dn, + char **mtn_be_names, int be_count, Slapi_DN * node_dn) +{ + unsigned long op_type; + Slapi_Operation *op; + char *rdn_type; + char *rdn_value; + Slapi_RDN *rdn = NULL; + int hash_value; + + /* first check the operation type + * searches at node level or above it should go in all backends + * searches below node level should go in only one backend + */ + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + op_type = slapi_op_get_type(op); + if ((op_type == SLAPI_OPERATION_SEARCH) && + slapi_sdn_issuffix(node_dn, target_dn)) + return SLAPI_BE_ALL_BACKENDS; + + /* now choose the backend + */ + + /* get the rdn and hash it to compute the backend number + * use a simple hash for this example + */ + rdn = slapi_rdn_new(); + slapi_sdn_get_rdn(target_dn, rdn); + slapi_rdn_get_first(rdn, &rdn_type, &rdn_value); + + /* compute the hash value */ + hash_value = 0; + while (*rdn_value) + { + hash_value += *rdn_value; + rdn_value++; + } + hash_value = hash_value % be_count; + + slapi_rdn_free(&rdn); + + /* use the hash_value as the returned backend number */ + return hash_value; +} + +/* + * This plugin allows to use a local backend in conjonction with + * a chaining backend + * The ldbm backend is considered a read-only replica of the data + * The chaining backend point to a red-write replica of the data + * This distribution logic forward the update request to the chaining + * backend, and send the search request to the local dbm database + * + * The mechanism for updating the local read-only replica is not + * taken into account by this plugin + * + * To be able to use it one must define one ldbm backend and one chaining + * backend in the mapping tree node + * + */ +int chaining_distribution(Slapi_PBlock *pb, Slapi_DN * target_dn, + char **mtn_be_names, int be_count, Slapi_DN * node_dn) +{ + char * requestor_dn; + unsigned long op_type; + Slapi_Operation *op; + int repl_op = 0; + int local_backend = -1; + int chaining_backend = -1; + int i; + char * name; + + /* first, we have to decide which backend is the local backend + * and which is the chaining one + * For the purpose of this example use the backend name : + * the backend with name starting with ldbm is local + * the bakend with name starting with chaining is remote + */ + local_backend = -1; + chaining_backend = -1; + for (i=0; i<be_count; i++) + { + name = mtn_be_names[i]; + if ((0 == strncmp(name, "ldbm", 4)) || + (0 == strncmp(name, "user", 4))) + local_backend = i; + else if (0 == strncmp(name, "chaining", 8)) + chaining_backend = i; + } + + /* Check the operation type + * read-only operation will go to the local backend + * updates operation will go to the chaining backend + */ + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + op_type = slapi_op_get_type(op); + if ((op_type == SLAPI_OPERATION_SEARCH) || + (op_type == SLAPI_OPERATION_BIND) || + (op_type == SLAPI_OPERATION_UNBIND) || + (op_type == SLAPI_OPERATION_COMPARE)) + return local_backend; + + /* if the operation is done by directory manager + * use local database even for updates because it is an administrative + * operation + * remarks : one could also use an update DN in the same way + * to let update operation go to the local backend when they are done + * by specific administrator user but let all the other user + * go to the read-write replica + */ + slapi_pblock_get( pb, SLAPI_REQUESTOR_DN, &requestor_dn ); + if (slapi_dn_isroot(requestor_dn)) + return local_backend; + + /* if the operation is a replicated operation + * use local database even for updates to avoid infinite loops + */ + slapi_pblock_get (pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op); + if (repl_op) + return local_backend; + + /* all other case (update while not directory manager) : + * use the chaining backend + */ + return chaining_backend; +} diff --git a/ldap/servers/plugins/distrib/distrib.dsp b/ldap/servers/plugins/distrib/distrib.dsp new file mode 100644 index 00000000..ebf11f1e --- /dev/null +++ b/ldap/servers/plugins/distrib/distrib.dsp @@ -0,0 +1,116 @@ +# Microsoft Developer Studio Project File - Name="distrib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=distrib - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "distrib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "distrib.mak" CFG="distrib - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "distrib - Win32 Release" (based on\ + "Win32 (x86) Dynamic-Link Library") +!MESSAGE "distrib - Win32 Debug" (based on\ + "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "distrib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\..\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_WIN32" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib ..\..\lib\libslapd.lib /nologo /subsystem:windows /dll /machine:I386 + +!ELSEIF "$(CFG)" == "distrib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /Gm /GX /Zi /Od /I "..\..\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_WIN32" /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib ..\..\lib\libslapd.lib /nologo /subsystem:windows /dll /debug /machine:I386 + +!ENDIF + +# Begin Target + +# Name "distrib - Win32 Release" +# Name "distrib - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\dllmain.c +# End Source File +# Begin Source File + +SOURCE=.\distrib.c +# End Source File +# Begin Source File + +SOURCE=.\libdistrib.def +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/ldap/servers/plugins/distrib/dllmain.c b/ldap/servers/plugins/distrib/dllmain.c new file mode 100644 index 00000000..bce5eed7 --- /dev/null +++ b/ldap/servers/plugins/distrib/dllmain.c @@ -0,0 +1,101 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ +/* + * Copyright (C) 2000 Sun Microsystems Inc. + * + * Use of this Source Code is subject to the terms of the applicable license + * agreement from Sun Microsystems Inc. + * + * The copyright notice(s) in this Source Code does not indicate actual or + * intended publication of this Source Code. + */ + + /* + * Microsoft Windows specifics + */ +#include "ldap.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/distrib/libdistrib.def b/ldap/servers/plugins/distrib/libdistrib.def new file mode 100644 index 00000000..baef2027 --- /dev/null +++ b/ldap/servers/plugins/distrib/libdistrib.def @@ -0,0 +1,10 @@ +;------------------------------------------------------------------------- +; PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +; license terms. Copyright 2001 Sun Microsystems, Inc. +; Some preexisting portions Copyright 2001 Netscape Communications Corp. +; All rights reserved. +;------------------------------------------------------------------------- +DESCRIPTION 'Netscape Directory Server 7 distribution logic example' +EXPORTS + alpha_distribution @1 + hash_distribution @2 diff --git a/ldap/servers/plugins/http/Makefile b/ldap/servers/plugins/http/Makefile new file mode 100644 index 00000000..d23c26a5 --- /dev/null +++ b/ldap/servers/plugins/http/Makefile @@ -0,0 +1,80 @@ +# +# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +# license terms. Copyright 2001 Sun Microsystems, Inc. +# Some preexisting portions Copyright 2001 Netscape Communications Corp. +# All rights reserved. +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libhttpclient +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./http.def +endif + +HTTP_OBJS = http_client.o http_impl.o + +OBJS = $(addprefix $(OBJDEST)/, $(HTTP_OBJS)) + +HTTP_DLL = http-client-plugin + +INCLUDES += -I../../slapd -I../../../include + +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +CFLAGS+=-D_WIN32 -DXP_WIN -DXP_WIN32 +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(SECURITYLINK) +HTTP_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), AIX) +LD=ld +EXTRA_LIBS += $(LIBSLAPD) +endif + +HTTP= $(addprefix $(LIBDIR)/, $(HTTP_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(HTTP) + +ifeq ($(ARCH), WINNT) +$(HTTP): $(OBJS) $(HTTP_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(HTTP_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(HTTP): $(OBJS) $(HTTP_DLL_OBJ) + $(LINK_DLL) $(HTTP_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(HTTP_DLL_OBJ) +endif + $(RM) $(HTTP) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) diff --git a/ldap/servers/plugins/http/dllmain.c b/ldap/servers/plugins/http/dllmain.c new file mode 100644 index 00000000..ef1f637a --- /dev/null +++ b/ldap/servers/plugins/http/dllmain.c @@ -0,0 +1,98 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + +#ifdef _WIN32 + +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ + +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/http/http.def b/ldap/servers/plugins/http/http.def new file mode 100644 index 00000000..ac2f3e05 --- /dev/null +++ b/ldap/servers/plugins/http/http.def @@ -0,0 +1,13 @@ +;------------------------------------------------------------------------- +; PROPRIETARY/CONFIDENTIAL. Use of this product is subject to +; license terms. Copyright 2001 Sun Microsystems, Inc. +; Some preexisting portions Copyright 2001 Netscape Communications Corp. +; All rights reserved. +;------------------------------------------------------------------------- +DESCRIPTION 'Netscape Directory Server Http Client' +EXPORTS + http_client_init @2 + plugin_init_debug_level @3 + http_client_version @4 + + diff --git a/ldap/servers/plugins/http/http_client.c b/ldap/servers/plugins/http/http_client.c new file mode 100644 index 00000000..2fbbdd52 --- /dev/null +++ b/ldap/servers/plugins/http/http_client.c @@ -0,0 +1,290 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ + +/** + * Simple Http Client API broker plugin + */ + +#include <stdio.h> +#include <string.h> + +#include "portable.h" +#include "nspr.h" + +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "dirlite_strings.h" +#include "dirver.h" + +#include "http_client.h" +#include "http_impl.h" + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +/*** from proto-slap.h ***/ + +int slapd_log_error_proc( char *subsystem, char *fmt, ... ); + +/*** from ldaplog.h ***/ + +/* edited ldaplog.h for LDAPDebug()*/ +#ifndef _LDAPLOG_H +#define _LDAPLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define LDAP_DEBUG_TRACE 0x00001 /* 1 */ +#define LDAP_DEBUG_ANY 0x04000 /* 16384 */ +#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */ + +/* debugging stuff */ +# ifdef _WIN32 + extern int *module_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( *module_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# else /* _WIN32 */ + extern int slapd_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( slapd_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# endif /* Win32 */ + +#ifdef __cplusplus +} +#endif + +#endif /* _LDAP_H */ + +#define HTTP_PLUGIN_SUBSYSTEM "http-client-plugin" /* used for logging */ +#define HTTP_PLUGIN_VERSION 0x00050050 + +#define HTTP_SUCCESS 0 +#define HTTP_FAILURE -1 + +/** + * Implementation functions + */ +static void *api[7]; + +/** + * Plugin identifiers + */ +static Slapi_PluginDesc pdesc = { "http-client", + PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, + "HTTP Client plugin" }; + +static Slapi_ComponentId *plugin_id = NULL; + +/** + ** + ** Http plug-in management functions + ** + **/ +int http_client_init(Slapi_PBlock *pb); +static int http_client_start(Slapi_PBlock *pb); +static int http_client_close(Slapi_PBlock *pb); + +/** + * our functions + */ +static void _http_init(Slapi_ComponentId *plugin_id); +static int _http_get_text(char *url, char **data, int *bytesRead); +static int _http_get_binary(char *url, char **data, int *bytesRead); +static int _http_get_redirected_uri(char *url, char **data, int *bytesRead); +static int _http_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead); +static void _http_shutdown( void ); + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/** + * + * Get the presence plug-in version + * + */ +int http_client_version() +{ + return HTTP_PLUGIN_VERSION; +} + +int http_client_init(Slapi_PBlock *pb) +{ + int status = HTTP_SUCCESS; + PRUint32 nssFlags = 0; + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> http_client_init -- BEGIN\n",0,0,0); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) http_client_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) http_client_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "http_client_init: failed to register plugin\n" ); + status = HTTP_FAILURE; + } + + /* Retrieve and save the plugin identity to later pass to + internal operations */ + if (slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id) != 0) { + slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "http_client_init: Failed to retrieve SLAPI_PLUGIN_IDENTITY\n"); + return HTTP_FAILURE; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- http_client_init -- END\n",0,0,0); + return status; +} + +static int http_client_start(Slapi_PBlock *pb) +{ + int status = HTTP_SUCCESS; + /** + * do some init work here + */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> http_client_start -- BEGIN\n",0,0,0); + + api[0] = 0; /* reserved for api broker use, must be zero */ + api[1] = (void *)_http_init; + api[2] = (void *)_http_get_text; + api[3] = (void *)_http_get_binary; + api[4] = (void *)_http_get_redirected_uri; + api[5] = (void *)_http_shutdown; + api[6] = (void *)_http_post; + + if( slapi_apib_register(HTTP_v1_0_GUID, api) ) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "http_client_start: failed to register functions\n" ); + status = HTTP_FAILURE; + } + + _http_init(plugin_id); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- http_client_start -- END\n",0,0,0); + return status; +} + +static int http_client_close(Slapi_PBlock *pb) +{ + int status = HTTP_SUCCESS; + /** + * do cleanup + */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> http_client_close -- BEGIN\n",0,0,0); + + slapi_apib_unregister(HTTP_v1_0_GUID); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- http_client_close -- END\n",0,0,0); + + return status; +} + +/** + * perform http initialization here + */ +static void _http_init(Slapi_ComponentId *plugin_id) +{ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_init -- BEGIN\n",0,0,0); + + http_impl_init(plugin_id); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_init -- END\n",0,0,0); +} + +/** + * This method gets the data in a text format based on the + * URL send. + */ +static int _http_get_text(char *url, char **data, int *bytesRead) +{ + int status = HTTP_SUCCESS; + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_get_text -- BEGIN\n",0,0,0); + + status = http_impl_get_text(url, data, bytesRead); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_get_text -- END\n",0,0,0); + return status; +} + +/** + * This method gets the data in a binary format based on the + * URL send. + */ +static int _http_get_binary(char *url, char **data, int *bytesRead) +{ + int status = HTTP_SUCCESS; + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_get_binary -- BEGIN\n",0,0,0); + + status = http_impl_get_binary(url, data, bytesRead); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_get_binary -- END\n",0,0,0); + return status; +} + +/** + * This method intercepts the redirected URI and returns the location + * information. + */ +static int _http_get_redirected_uri(char *url, char **data, int *bytesRead) +{ + int status = HTTP_SUCCESS; + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_get_redirected_uri -- BEGIN\n",0,0,0); + + status = http_impl_get_redirected_uri(url, data, bytesRead); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_get_redirected_uri -- END\n",0,0,0); + return status; +} + +/** + * This method posts the data based on the URL send. + */ +static int _http_post(char *url, httpheader ** httpheaderArray, char *body, char **data, int *bytesRead) +{ + int status = HTTP_SUCCESS; + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_post -- BEGIN\n",0,0,0); + + status = http_impl_post(url, httpheaderArray, body, data, bytesRead); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_post -- END\n",0,0,0); + return status; +} + +/** + * perform http shutdown here + */ +static void _http_shutdown( void ) +{ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_shutdown -- BEGIN\n",0,0,0); + + http_impl_shutdown(); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_shutdown -- END\n",0,0,0); +} + diff --git a/ldap/servers/plugins/http/http_client.h b/ldap/servers/plugins/http/http_client.h new file mode 100644 index 00000000..d849e18d --- /dev/null +++ b/ldap/servers/plugins/http/http_client.h @@ -0,0 +1,64 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ + +#ifndef _HTTP_CLIENT_H_ +#define _HTTP_CLIENT_H_ + +/* Error codes */ +#define HTTP_CLIENT_ERROR_BAD_URL -1 +#define HTTP_CLIENT_ERROR_NET_ADDR -2 +#define HTTP_CLIENT_ERROR_SOCKET_CREATE -3 +#define HTTP_CLIENT_ERROR_CONNECT_FAILED -4 +#define HTTP_CLIENT_ERROR_SEND_REQ -5 +#define HTTP_CLIENT_ERROR_BAD_RESPONSE -6 +#define HTTP_CLIENT_ERROR_SSLSOCKET_CREATE -7 + #define HTTP_CLIENT_ERROR_NSS_INITIALIZE -8 + +/*Structure to store HTTP Headers */ +typedef struct { + char *name; + char *value; +} httpheader; + + +/* mechanics */ + + +typedef void (*api_http_init)(Slapi_ComponentId *plugin_id); +typedef int (*api_http_get_text)(char *url, char **data, int *bytesRead); +typedef int (*api_http_get_binary)(char *url, char **data, int *bytesRead); +typedef int (*api_http_get_redirected_uri)(char *url, char **data, int *bytesRead); +typedef void (*api_http_shutdown)(); +typedef int (*api_http_post)(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead); + +/* API ID for http_apib_get_interface */ + +#define HTTP_v1_0_GUID "811c5ea2-fef4-4f1c-9ab4-fcf746cd6efc" + +/* API */ + +/* the api broker reserves api[0] for its use */ + +#define http_init(api) \ + ((api_http_init*)(api))[1](Slapi_ComponentId *plugin_id) + +#define http_get_text(api, url, data, bytesRead) \ + ((api_http_get_text*)(api))[2]( url, data, bytesRead) + +#define http_get_binary(api, url, data, bytesRead) \ + ((api_http_get_binary*)(api))[3](url, data, bytesRead) + +#define http_get_redirected_uri(api, url, data, bytesRead) \ + ((api_http_get_redirected_uri*)(api))[4](url, data, bytesRead) + +#define http_shutdown(api) \ + ((api_http_shutdown*)(api))[5]() + +#define http_post(api, url, httpheaderArray, body, data, bytesRead) \ + ((api_http_post*)(api))[6](url, httpheaderArray, body, data, bytesRead) + +#endif /*_HTTP_CLIENT_H_*/ diff --git a/ldap/servers/plugins/http/http_impl.c b/ldap/servers/plugins/http/http_impl.c new file mode 100644 index 00000000..bad8315c --- /dev/null +++ b/ldap/servers/plugins/http/http_impl.c @@ -0,0 +1,1479 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ +/** + * Implementation of a Simple HTTP Client + */ +#include <stdio.h> +#include <string.h> + +#include "nspr.h" +#include "nss.h" +#include "pk11func.h" +#include "ssl.h" +#include "prprf.h" +#include "plstr.h" +#include "slapi-plugin.h" +#include "http_client.h" +#include "secerr.h" +#include "sslerr.h" +#include "slapi-private.h" +#include "slapi-plugin-compat4.h" +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +/*** from proto-slap.h ***/ + +int slapd_log_error_proc( char *subsystem, char *fmt, ... ); +char *config_get_instancedir(); + +/*** from ldaplog.h ***/ + +/* edited ldaplog.h for LDAPDebug()*/ +#ifndef _LDAPLOG_H +#define _LDAPLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef BUILD_STANDALONE +#define slapi_log_error(a,b,c,d) printf((c),(d)) +#define stricmp strcasecmp +#endif + +#define LDAP_DEBUG_TRACE 0x00001 /* 1 */ +#define LDAP_DEBUG_ANY 0x04000 /* 16384 */ +#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */ + +/* debugging stuff */ +# ifdef _WIN32 + extern int *module_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( *module_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# else /* _WIN32 */ + extern int slapd_ldap_debug; +# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( slapd_ldap_debug & level ) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } +# endif /* Win32 */ + +#ifdef __cplusplus +} +#endif + +#endif /* _LDAP_H */ + +#define HTTP_PLUGIN_SUBSYSTEM "http-client-plugin" /* used for logging */ + +#define HTTP_IMPL_SUCCESS 0 +#define HTTP_IMPL_FAILURE -1 + +#define HTTP_REQ_TYPE_GET 1 +#define HTTP_REQ_TYPE_REDIRECT 2 +#define HTTP_REQ_TYPE_POST 3 + +#define HTTP_GET "GET" +#define HTTP_POST "POST" +#define HTTP_PROTOCOL "HTTP/1.0" +#define HTTP_CONTENT_LENGTH "Content-length:" +#define HTTP_CONTENT_TYPE_URL_ENCODED "Content-type: application/x-www-form-urlencoded" +#define HTTP_GET_STD_LEN 18 +#define HTTP_POST_STD_LEN 85 +#define HTTP_DEFAULT_BUFFER_SIZE 4096 +#define HTTP_RESPONSE_REDIRECT (retcode == 302 || retcode == 301) + +/** + * Error strings used for logging error messages + */ +#define HTTP_ERROR_BAD_URL " Badly formatted URL" +#define HTTP_ERROR_NET_ADDR " NetAddr initialization failed" +#define HTTP_ERROR_SOCKET_CREATE " Creation of socket failed" +#define HTTP_ERROR_SSLSOCKET_CREATE " Creation of SSL socket failed" +#define HTTP_ERROR_CONNECT_FAILED " Couldn't connect to remote host" +#define HTTP_ERROR_SEND_REQ " Send request failed" +#define HTTP_ERROR_BAD_RESPONSE " Invalid response from remote host" + +#define HTTP_PLUGIN_DN "cn=HTTP Client,cn=plugins,cn=config" +#define CONFIG_DN "cn=config" +#define ATTR_CONNECTION_TIME_OUT "nsHTTPConnectionTimeOut" +#define ATTR_READ_TIME_OUT "nsHTTPReadTimeOut" +#define ATTR_RETRY_COUNT "nsHTTPRetryCount" +#define ATTR_DS_SECURITY "nsslapd-security" +#define ATTR_INSTANCE_PATH "nsslapd-errorlog" + +/*static Slapi_ComponentId *plugin_id = NULL;*/ + +typedef struct { + int retryCount; + int connectionTimeOut; + int readTimeOut; + int nssInitialized; + char *DS_sslOn; +} httpPluginConfig; + +httpPluginConfig *httpConfig; + +/** + * Public functions + */ +int http_impl_init(Slapi_ComponentId *plugin_id); +int http_impl_get_text(char *url, char **data, int *bytesRead); +int http_impl_get_binary(char *url, char **data, int *bytesRead); +int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead); +int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead); +void http_impl_shutdown(); + +/** + * Http handling functions + */ +static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType); +static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType); +static void setTCPNoDelay(PRFileDesc* fd); +static PRStatus sendGetReq(PRFileDesc *fd, const char *path); +static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body); +static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType); +static PRStatus getChar(PRFileDesc *fd, char *buf); +static PRInt32 http_read(PRFileDesc *fd, char *buf, int size); +static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead); +static PRBool isWhiteSpace(char ch); +static PRStatus sendFullData( PRFileDesc *fd, char *buf, int timeOut); + +/** + * Helper functions to parse URL + */ +static PRStatus parseURI(const char *url, char **host, PRInt32 *port, char **path, int *sslOn); +static void toLowerCase(char* str); +static PRStatus parseAtPort(const char* url, PRInt32 *port, char **path); +static PRStatus parseAtPath(const char *url, char **path); +static PRInt32 getPort(const char* src); +static PRBool isAsciiSpace(char aChar); +static PRBool isAsciiDigit(char aChar); +static char * isHttpReq(const char *url, int *sslOn); + +/*To get config from entry*/ +static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn); +static int parseHTTPConfigEntry(Slapi_Entry *e); +static int parseConfigEntry(Slapi_Entry *e); + +static int nssReinitializationRequired(); + +/*SSL functions */ +PRFileDesc* setupSSLSocket(PRFileDesc* fd); + +/*SSL callback functions */ +SECStatus badCertHandler(void *arg, PRFileDesc *socket); +SECStatus authCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer); +SECStatus getClientAuthData(void *arg, PRFileDesc *socket,struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey); +SECStatus handshakeCallback(PRFileDesc *socket, void *arg); + +static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType) +{ + int status = HTTP_IMPL_SUCCESS; + int retrycnt = 0; + int i = 1; + + retrycnt = httpConfig->retryCount; + + if (retrycnt == 0) { + LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Retry Count cannot be read. Setting to default value of 3 \n", 0,0,0); + retrycnt = 3; + } + status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType); + if (status != HTTP_IMPL_SUCCESS) { + LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Failed to perform http request \n", 0,0,0); + while (retrycnt > 0) { + LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Retrying http request %d.\n", i,0,0); + status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType); + if (status == HTTP_IMPL_SUCCESS) { + break; + } + retrycnt--; + i++; + } + if (status != HTTP_IMPL_SUCCESS) { + LDAPDebug( LDAP_DEBUG_ANY, "doRequestRetry: Failed to perform http request after %d attempts.\n", i,0,0); + LDAPDebug( LDAP_DEBUG_ANY, "doRequestRetry: Verify plugin URI configuration and contact Directory Administrator.\n",0,0,0); + } + + } + return status; +} + +static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType) +{ + PRStatus status = PR_SUCCESS; + + char *host = NULL; + char *path = NULL; + char *val = NULL; + char *defaultprefix = NULL; + PRFileDesc *fd = NULL; + PRNetAddr addr; + PRInt32 port; + PRInt32 errcode = 0; + PRInt32 http_connection_time_out = 0; + PRInt32 sslOn; + PRInt32 nssStatus; + PRUint32 nssFlags = 0; + char certDir[1024]; + char certPref[1024]; + char keyPref[1024]; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> doRequest -- BEGIN\n",0,0,0); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> url=[%s] \n",url,0,0); + + /* Parse the URL and initialize the host, port, path */ + if (parseURI(url, &host, &port, &path, &sslOn) == PR_FAILURE) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: %s \n", HTTP_ERROR_BAD_URL); + status = PR_FAILURE; + goto bail; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> host=[%s] port[%d] path[%s] \n",host,port,path); + + /* Initialize the Net Addr */ + if (PR_StringToNetAddr(host, &addr) == PR_FAILURE) { + char buf[PR_NETDB_BUF_SIZE]; + PRHostEnt ent; + + status = PR_GetIPNodeByName(host, PR_AF_INET, PR_AI_DEFAULT, buf, sizeof(buf), &ent); + if (status == PR_SUCCESS) { + PR_EnumerateHostEnt(0, &ent, (PRUint16)port, &addr); + } else { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: %s\n", HTTP_ERROR_NET_ADDR); + status = HTTP_CLIENT_ERROR_NET_ADDR; + goto bail; + } + } else { + addr.inet.port = (PRUint16)port; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully created NetAddr \n",0,0,0); + + /* open a TCP connection to the server */ + fd = PR_NewTCPSocket(); + if (!fd) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: %s\n", HTTP_ERROR_SOCKET_CREATE); + status = HTTP_CLIENT_ERROR_SOCKET_CREATE; + goto bail; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully created New TCP Socket \n",0,0,0); + + /* immediately send the response */ + setTCPNoDelay(fd); + + if (sslOn) { + + /* Have to reinitialize NSS is the DS security is set to off. + This is because the HTTPS required the cert dbs to be created. + The default prefixes are used as per DS norm */ + + if (PL_strcasecmp(httpConfig->DS_sslOn, "off") == 0) { + if (!httpConfig->nssInitialized) { + if (nssReinitializationRequired()) + { + NSS_Shutdown(); + nssFlags &= (~NSS_INIT_READONLY); + val = config_get_instancedir(); + strcpy(certDir, val); + defaultprefix = strrchr(certDir, '/'); + if (!defaultprefix) + defaultprefix = strrchr(certDir, '\\'); + if (!defaultprefix) /* still could not find it . . . */ + goto bail; /* . . . can't do anything */ + defaultprefix++; + sprintf(certPref, "%s-",defaultprefix); + strcpy(keyPref, certPref); + *defaultprefix= '\0'; + sprintf(certDir, "%salias", certDir); + nssStatus = NSS_Initialize(certDir, certPref, keyPref, "secmod.db", nssFlags); + slapi_ch_free((void **)&val); + + if (nssStatus != 0) { + slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: Unable to initialize NSS Cert/Key Database\n"); + status = HTTP_CLIENT_ERROR_NSS_INITIALIZE; + goto bail; + } + } + httpConfig->nssInitialized = 1; + } + } + + NSS_SetDomesticPolicy(); + + fd = setupSSLSocket(fd); + if (fd == NULL) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: %s\n", HTTP_ERROR_SSLSOCKET_CREATE); + status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE; + goto bail; + } + + if (SSL_SetURL(fd, host) != 0) { + errcode = PR_GetError(); + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: SSL_SetURL -> NSPR Error code (%d) \n", errcode); + status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE; + goto bail; + } + + } + + http_connection_time_out = httpConfig->connectionTimeOut; + /* connect to the host */ + if (PR_Connect(fd, &addr, PR_MillisecondsToInterval(http_connection_time_out)) == PR_FAILURE) { + errcode = PR_GetError(); + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: %s (%s:%d) -> NSPR Error code (%d)\n", + HTTP_ERROR_CONNECT_FAILED, host, addr.inet.port, errcode); + status = HTTP_CLIENT_ERROR_CONNECT_FAILED; + goto bail; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully connected to host [%s] \n",host,0,0); + + /* send the request to the server */ + if (reqType == HTTP_REQ_TYPE_POST) { + if (sendPostReq(fd, path, httpheaderArray, body) == PR_FAILURE) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest-sendPostReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path); + status = HTTP_CLIENT_ERROR_SEND_REQ; + goto bail; + } + } + else { + if (sendGetReq(fd, path) == PR_FAILURE) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest-sendGetReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path); + status = HTTP_CLIENT_ERROR_SEND_REQ; + goto bail; + } + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully sent the request [%s] \n",path,0,0); + + /* read the response */ + if (processResponse(fd, buf, bytesRead, reqType) == PR_FAILURE) { + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: %s (%s)\n", HTTP_ERROR_BAD_RESPONSE, url); + status = HTTP_CLIENT_ERROR_BAD_RESPONSE; + goto bail; + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully read the response\n",0,0,0); +bail: + if (host) { + PR_Free(host); + } + if (path) { + PR_Free(path); + } + if (fd) { + PR_Close(fd); + fd = NULL; + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- doRequest -- END\n",0,0,0); + return status; +} + +static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType) +{ + PRStatus status = PR_SUCCESS; + char *location = NULL; + char *protocol = NULL; + char *statusNum = NULL; + char *statusString = NULL; + char *headers = NULL; + + char tmp[HTTP_DEFAULT_BUFFER_SIZE]; + int pos=0; + char ch; + int index; + int retcode; + + PRBool doneParsing = PR_FALSE; + PRBool isRedirect = PR_FALSE; + char name[HTTP_DEFAULT_BUFFER_SIZE]; + char value[HTTP_DEFAULT_BUFFER_SIZE]; + PRBool atEOL = PR_FALSE; + PRBool inName = PR_TRUE; + + /* PKBxxx: If we are getting a redirect and the response is more the + * the HTTP_DEFAULT_BUFFER_SIZE, it will cause the server to crash. A 4k + * buffer should be good enough. + */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> processResponse -- BEGIN\n",0,0,0); + + headers = (char *)PR_Calloc(1, 4 * HTTP_DEFAULT_BUFFER_SIZE); + /* Get protocol string */ + index = 0; + while (1) { + status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + /* Error : */ + goto bail; + } + ch = (char)headers[pos]; + pos++; + if (!isWhiteSpace(ch)) { + tmp[index++] = ch; + } else { + break; + } + } + tmp[index] = '\0'; + protocol = PL_strdup(tmp); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> protocol=[%s] \n",protocol,0,0); + + /* Get status num */ + index = 0; + while (1) { + status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + /* Error : */ + goto bail; + } + ch = (char)headers[pos]; + pos++; + if (!isWhiteSpace(ch)) { + tmp[index++] = ch; + } else { + break; + } + } + tmp[index] = '\0'; + statusNum = PL_strdup(tmp); + retcode=atoi(tmp); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> statusNum=[%s] \n",statusNum,0,0); + + if (HTTP_RESPONSE_REDIRECT && (reqType == HTTP_REQ_TYPE_REDIRECT)) { + isRedirect = PR_TRUE; + } + /* Get status string */ + if (ch != '\r') + { + index = 0; + while (ch != '\r') { + status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + /* Error : */ + goto bail; + } + ch = (char)headers[pos]; + pos++; + tmp[index++] = ch; + } + tmp[index] = '\0'; + statusString = PL_strdup(tmp); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> statusString [%s] \n",statusString,0,0); + } + + /** + * Skip CRLF + */ + status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + /* Error : */ + goto bail; + } + ch = (char)headers[pos]; + pos++; + + /** + * loop over response headers + */ + index = 0; + while (!doneParsing) { + status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + /* Error : */ + goto bail; + } + ch = (char)headers[pos]; + pos++; + switch(ch) + { + case ':': + if (inName) { + name[index] = '\0'; + index = 0; + inName = PR_FALSE; + + /* skip whitespace */ + ch = ' '; + + /* status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + goto bail; + } + ch = (char)headers[pos]; + pos++; */ + + while(isWhiteSpace(ch)) { + status = getChar(fd, headers+pos); + if (status == PR_FAILURE) { + /* Error : */ + goto bail; + } + ch = (char)headers[pos]; + pos++; + } + value[index++] = ch; + } else { + value[index++] = ch; + } + break; + case '\r': + if (inName && !atEOL) { + return PR_FALSE; + } + break; + case '\n': + if (atEOL) { + doneParsing = PR_TRUE; + break; + } + if (inName) { + return PR_FALSE; + } + value[index] = '\0'; + index = 0; + inName = PR_TRUE; + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> name=[%s] value=[%s]\n",name,value,0); + if (isRedirect && !PL_strcasecmp(name,"location")) { + location = PL_strdup(value); + } + atEOL = PR_TRUE; + break; + default: + atEOL = PR_FALSE; + if (inName) { + name[index++] = ch; + } else { + value[index++] = ch; + } + break; + } + } + + if (!isRedirect) { + getBody(fd, resBUF, bytesRead); + } else { + *resBUF = PL_strdup(location); + *bytesRead = strlen(location); + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Response Buffer=[%s] bytesRead=[%d] \n",*resBUF,*bytesRead,0); + +bail: + + if (headers) { + PR_Free(headers); + } + if (protocol) { + PL_strfree(protocol); + } + if (statusNum) { + PL_strfree(statusNum); + } + if (statusString) { + PL_strfree(statusString); + } + if (location) { + PL_strfree(location); + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- processResponse -- END\n",0,0,0); + return status; +} + +static int nssReinitializationRequired() +{ + int nssReinitializationRequired = 0; + int err = 0; + int str_len = 0; + float version = 0; + const float DSVERSION = 6.1; + char *str = NULL; + char *value = NULL; + char *ver_value = NULL; + Slapi_Entry **entry = NULL; + Slapi_PBlock *resultpb= NULL; + + resultpb= slapi_search_internal( "", LDAP_SCOPE_BASE, "objectclass=*", NULL, NULL, 0); + slapi_pblock_get( resultpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry ); + slapi_pblock_get( resultpb, SLAPI_PLUGIN_INTOP_RESULT, &err); + if ( err == LDAP_SUCCESS && entry!=NULL && entry[0]!=NULL) + { + value = slapi_entry_attr_get_charptr(entry[0], "vendorVersion"); + if (value == NULL || strncmp(value, "Netscape", strlen("Netscape"))) + { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "nssReinitializationRequired: vendor is not Netscape \n"); + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "or version is earlier than 6.0\n", value); + nssReinitializationRequired = 1; + slapi_free_search_results_internal(resultpb); + slapi_pblock_destroy(resultpb); + slapi_ch_free((void **)&value); + return nssReinitializationRequired; + } + + if ( (str = strstr(value,"/")) != NULL ) + { + str++; + version = atof(str); + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "nssReinitializationRequired: version is %f. \n", version); + } + + + if (str == NULL || version < DSVERSION) + { + nssReinitializationRequired = 1; + } + slapi_ch_free((void **)&value); + + } + slapi_free_search_results_internal(resultpb); + slapi_pblock_destroy(resultpb); + return nssReinitializationRequired; + +} + +static PRStatus sendGetReq(PRFileDesc *fd, const char *path) +{ + PRStatus status = PR_SUCCESS; + char *reqBUF = NULL; + PRInt32 http_connection_time_out = 0; + int buflen = (HTTP_GET_STD_LEN + strlen(path)); + + reqBUF = (char *)PR_Calloc(1, buflen); + + strcpy(reqBUF, HTTP_GET); + strcat(reqBUF, " "); + strcat(reqBUF, path); + strcat(reqBUF, " "); + strcat(reqBUF, HTTP_PROTOCOL); + strcat(reqBUF, "\r\n\r\n\0"); + + http_connection_time_out = httpConfig->connectionTimeOut; + status = sendFullData( fd, reqBUF, http_connection_time_out); + +bail: + if (reqBUF) { + PR_Free(reqBUF); + reqBUF = 0; + } + return status; +} + +static PRStatus sendFullData( PRFileDesc *fd, char *buf, int timeOut) +{ + int dataSent = 0; + int bufLen = strlen(buf); + int retVal = 0; + PRInt32 errcode = 0; + while (dataSent < bufLen) + { + retVal = PR_Send(fd, buf+dataSent, bufLen-dataSent, 0, PR_MillisecondsToInterval(timeOut)); + if (retVal == -1 ) + break; + dataSent += retVal; + } + if (dataSent == bufLen ) + return PR_SUCCESS; + else + { + errcode = PR_GetError(); + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "sendFullData: dataSent=%d bufLen=%d -> NSPR Error code (%d)\n", + dataSent, bufLen, errcode); + LDAPDebug( LDAP_DEBUG_PLUGIN, "---------->NSPR Error code (%d) \n", errcode,0,0); + return PR_FAILURE; + } +} + +static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body) +{ + PRStatus status = PR_SUCCESS; + char body_len_str[20]; + char *reqBUF = NULL; + PRInt32 http_connection_time_out = 0; + int i = 0; + int body_len, buflen = 0; + + body_len = strlen(body); + PR_snprintf(body_len_str, sizeof(body_len_str), "%d", body_len); + + buflen = (HTTP_POST_STD_LEN + strlen(path) + body_len + strlen(body_len_str)); + + for (i = 0; httpheaderArray[i] != NULL; i++) { + + if (httpheaderArray[i]->name != NULL) + { + buflen += strlen(httpheaderArray[i]->name) + 2; + if (httpheaderArray[i]->value != NULL) + buflen += strlen(httpheaderArray[i]->value) + 2; + } + + } + + reqBUF = (char *)PR_Calloc(1, buflen); + + strcpy(reqBUF, HTTP_POST); + strcat(reqBUF, " "); + strcat(reqBUF, path); + strcat(reqBUF, " "); + strcat(reqBUF, HTTP_PROTOCOL); + strcat(reqBUF, "\r\n"); + strcat(reqBUF, HTTP_CONTENT_LENGTH); + strcat(reqBUF, " "); + strcat(reqBUF, body_len_str); + strcat(reqBUF, "\r\n"); + strcat(reqBUF, HTTP_CONTENT_TYPE_URL_ENCODED); + strcat(reqBUF, "\r\n"); + + for (i = 0; httpheaderArray[i] != NULL; i++) { + + if (httpheaderArray[i]->name != NULL) + strcat(reqBUF, httpheaderArray[i]->name); + strcat(reqBUF, ": "); + if (httpheaderArray[i]->value != NULL) + strcat(reqBUF, httpheaderArray[i]->value); + strcat(reqBUF, "\r\n"); + + } + + strcat(reqBUF, "\r\n"); + strcat(reqBUF, body); + strcat(reqBUF, "\0"); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "---------->reqBUF is %s \n",reqBUF,0,0); + http_connection_time_out = httpConfig->connectionTimeOut; + + status = sendFullData( fd, reqBUF, http_connection_time_out); + +bail: + if (reqBUF) { + PR_Free(reqBUF); + reqBUF = 0; + } + return status; +} + + +static PRStatus getChar(PRFileDesc *fd, char *buf) +{ + PRInt32 bytesRead = http_read(fd, buf, 1); + if (bytesRead <=0) { + PRInt32 errcode = PR_GetError(); + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "getChar: NSPR Error code (%d)\n", errcode); + return PR_FAILURE; + } + return PR_SUCCESS; +} + +static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead) +{ + int totalBytesRead = 0; + int size = 4 * HTTP_DEFAULT_BUFFER_SIZE; + int bytesRead = size; + char *data = (char *) PR_Calloc(1, size); + while (bytesRead == size) { + bytesRead = http_read(fd, (data+totalBytesRead), size); + if (bytesRead <= 0) { + /* Read error */ + return PR_FAILURE; + } + if (bytesRead == size) { + /* more data to be read so increase the buffer */ + size = size * 2 ; + data = (char *) PR_Realloc(data, size); + } + totalBytesRead += bytesRead; + } + *buf = data; + *actualBytesRead = totalBytesRead; + + return PR_SUCCESS; +} + +static PRInt32 http_read(PRFileDesc *fd, char *buf, int size) +{ + PRInt32 http_read_time_out = 0; + http_read_time_out = httpConfig->readTimeOut; + return PR_Recv(fd, buf, size, 0, PR_MillisecondsToInterval(http_read_time_out)); +} + +static PRBool isWhiteSpace(char ch) +{ + PRBool b = PR_FALSE; + if (ch == ' ') { + b = PR_TRUE; + } + return b; +} + +static PRStatus parseURI(const char *urlstr, char **host, PRInt32 *port, char **path, int *sslOn) +{ + PRStatus status = PR_SUCCESS; + char *brk; + int len; + static const char delimiters[] = ":/?#"; + char *url = isHttpReq(urlstr, sslOn); + + if (*sslOn) { + *port = 443; + } + else { + *port = 80; + } + if (url == NULL) { + /* Error : */ + status = PR_FAILURE; + goto bail; + } + len = PL_strlen(url); + /* Currently we do not support Ipv6 addresses */ + brk = PL_strpbrk(url, delimiters); + if (!brk) { + *host = PL_strndup(url, len); + toLowerCase(*host); + goto bail; + } + switch (*brk) + { + case '/' : + case '?' : + case '#' : + /* Get the Host, the rest is Path */ + *host = PL_strndup(url, (brk - url)); + toLowerCase(*host); + status = parseAtPath(brk, path); + break; + case ':' : + /* Get the Host and process port, path */ + *host = PL_strndup(url, (brk - url)); + toLowerCase(*host); + status = parseAtPort(brk+1, port, path); + break; + default: + /* Error : HTTP_BAD_URL */ + break; + } + +bail: + if (url) { + PR_Free(url); + } + return status; +} + +static PRStatus parseAtPort(const char* url, PRInt32 *port, char **path) +{ + PRStatus status = PR_SUCCESS; + static const char delimiters[] = "/?#"; + char* brk = PL_strpbrk(url, delimiters); + if (!brk) /* everything is a Port */ + { + *port = getPort(url); + if (*port <= 0) { + /* Error : HTTP_BAD_URL */ + return PR_FAILURE; + } else { + return status; + } + } + + switch (*brk) + { + case '/' : + case '?' : + case '#' : + /* Get the Port, the rest is Path */ + *port = getPort(url); + if (*port <= 0) { + /* Error : HTTP_BAD_URL */ + return PR_FAILURE; + } + status = parseAtPath(brk, path); + break; + default: + /* Error : HTTP_BAD_URL */ + break; + } + return status; +} + +static PRStatus parseAtPath(const char *url, char **path) +{ + PRStatus status = PR_SUCCESS; + char *dir = "%s%s"; + *path = (char *)PR_Calloc(1, (strlen(dir) + 1024)); + + /* Just write the path and check for a starting / */ + if ('/' != *url) { + PR_sscanf(*path, dir, "/", url); + } else { + strcpy(*path, url); + } + if (!*path) { + /* Error : HTTP_BAD_URL */ + status = PR_FAILURE; + } + return status; +} + +static void toLowerCase(char* str) +{ + if (str) { + char* lstr = str; + PRInt8 shift = 'a' - 'A'; + for(; (*lstr != '\0'); ++lstr) { + if ((*(lstr) <= 'Z') && (*(lstr) >= 'A')) { + *(lstr) = *(lstr) + shift; + } + } + } +} + +static PRInt32 getPort(const char* src) +{ + /* search for digits up to a slash or the string ends */ + const char* port = src; + PRInt32 returnValue = -1; + char c; + + /* skip leading white space */ + while (isAsciiSpace(*port)) + port++; + + while ((c = *port++) != '\0') { + /* stop if slash or ? or # reached */ + if (c == '/' || c == '?' || c == '#') + break; + else if (!isAsciiDigit(c)) + return returnValue; + } + return (0 < PR_sscanf(src, "%d", &returnValue)) ? returnValue : -1; +} + + +static PRBool isAsciiSpace(char aChar) +{ + if ((aChar == ' ') || (aChar == '\r') || (aChar == '\n') || (aChar == '\t')) { + return PR_TRUE; + } + return PR_FALSE; +} + +static PRBool isAsciiDigit(char aChar) +{ + if ((aChar >= '0') && (aChar <= '9')) { + return PR_TRUE; + } + return PR_FALSE; +} + +static void setTCPNoDelay(PRFileDesc* fd) +{ + PRStatus status = PR_SUCCESS; + PRSocketOptionData opt; + + opt.option = PR_SockOpt_NoDelay; + opt.value.no_delay = PR_FALSE; + + status = PR_GetSocketOption(fd, &opt); + if (status == PR_FAILURE) { + return; + } + + opt.option = PR_SockOpt_NoDelay; + opt.value.no_delay = PR_TRUE; + status = PR_SetSocketOption(fd, &opt); + if (status == PR_FAILURE) { + return; + } + return; +} + +static char * isHttpReq(const char *url, int *sslOn) +{ + static const char http_protopol_header[] = "http://"; + static const char https_protopol_header[] = "https://"; + char *newstr = NULL; + /* skip leading white space */ + while (isAsciiSpace(*url)) + url++; + + if (strncmp(url, http_protopol_header, strlen(http_protopol_header)) == 0) { + newstr = (char *)PR_Calloc(1, (strlen(url)-strlen(http_protopol_header) + 1)); + strcpy(newstr, url+7); + strcat(newstr,"\0"); + *sslOn = 0; + } + else if (strncmp(url, https_protopol_header, strlen(https_protopol_header)) == 0) { + newstr = (char *)PR_Calloc(1, (strlen(url)-strlen(https_protopol_header) + 1)); + strcpy(newstr, url+8); + strcat(newstr,"\0"); + *sslOn = 1; + } + + return newstr; +} + +PRFileDesc* setupSSLSocket(PRFileDesc* fd) +{ + SECStatus secStatus; + PRFileDesc* sslSocket; + PRSocketOptionData socketOption; + char *certNickname = NULL; + + socketOption.option = PR_SockOpt_Nonblocking; + socketOption.value.non_blocking = PR_FALSE; + if( PR_SetSocketOption(fd, &socketOption) != 0) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "Cannot set socket option NSS \n"); + return NULL; + } + + sslSocket = SSL_ImportFD(NULL, fd); + if (!sslSocket) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: Cannot import to SSL Socket\n" ); + goto sslbail; + } + + slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: setupssl socket created\n" ); + + secStatus = SSL_OptionSet(sslSocket, SSL_SECURITY, 1); + if (SECSuccess != secStatus) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: Cannot set SSL_SECURITY option\n"); + goto sslbail; + } + + secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, 1); + if (SECSuccess != secStatus) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: CAnnot set SSL_HANDSHAKE_AS_CLIENT option\n"); + goto sslbail; + } + + /* Set SSL callback routines. */ + + secStatus = SSL_GetClientAuthDataHook(sslSocket, + (SSLGetClientAuthData) getClientAuthData, + (void *)certNickname); + if (secStatus != SECSuccess) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: SSL_GetClientAuthDataHook Failed\n"); + goto sslbail; + } + + secStatus = SSL_AuthCertificateHook(sslSocket, + (SSLAuthCertificate) authCertificate, + (void *)CERT_GetDefaultCertDB()); + if (secStatus != SECSuccess) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: SSL_AuthCertificateHook Failed\n"); + goto sslbail; + } + + secStatus = SSL_BadCertHook(sslSocket, + (SSLBadCertHandler) badCertHandler, NULL); + if (secStatus != SECSuccess) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: SSL_BadCertHook Failed\n"); + goto sslbail; + } + + secStatus = SSL_HandshakeCallback(sslSocket, + (SSLHandshakeCallback) handshakeCallback, NULL); + if (secStatus != SECSuccess) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "setupSSLSocket: SSL_HandshakeCallback Failed\n"); + goto sslbail; + } + + return sslSocket; + +sslbail: + PR_Close(fd); + return NULL; +} + +SECStatus + authCertificate(void *arg, PRFileDesc *socket, + PRBool checksig, PRBool isServer) +{ + + SECCertUsage certUsage; + CERTCertificate * cert; + void * pinArg; + char * hostName; + SECStatus secStatus; + + if (!arg || !socket) { + slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + " authCertificate: Faulty socket in callback function \n"); + return SECFailure; + } + + /* Define how the cert is being used based upon the isServer flag. */ + + certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; + + cert = SSL_PeerCertificate(socket); + + pinArg = SSL_RevealPinArg(socket); + + secStatus = CERT_VerifyCertNow((CERTCertDBHandle *)arg, + cert, + checksig, + certUsage, + pinArg); + + /* If this is a server, we're finished. */ + if (isServer || secStatus != SECSuccess) { + return secStatus; + } + + hostName = SSL_RevealURL(socket); + + if (hostName && hostName[0]) { + secStatus = CERT_VerifyCertName(cert, hostName); + } else { + PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); + secStatus = SECFailure; + } + + if (hostName) + PR_Free(hostName); + + return secStatus; +} + +SECStatus + badCertHandler(void *arg, PRFileDesc *socket) +{ + + SECStatus secStatus = SECFailure; + PRErrorCode err; + + /* log invalid cert here */ + + if (!arg) { + return secStatus; + } + + *(PRErrorCode *)arg = err = PORT_GetError(); + switch (err) { + case SEC_ERROR_INVALID_AVA: + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_CERT_VALID: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + case SEC_ERROR_CRL_EXPIRED: + case SEC_ERROR_CRL_BAD_SIGNATURE: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + secStatus = SECSuccess; + break; + default: + secStatus = SECFailure; + break; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "Bad certificate: %d\n", err); + + return secStatus; +} + +SECStatus + getClientAuthData(void *arg, + PRFileDesc *socket, + struct CERTDistNamesStr *caNames, + struct CERTCertificateStr **pRetCert, + struct SECKEYPrivateKeyStr **pRetKey) +{ + CERTCertificate * cert; + SECKEYPrivateKey * privKey; + char * chosenNickName = (char *)arg; + void * proto_win = NULL; + SECStatus secStatus = SECFailure; + proto_win = SSL_RevealPinArg(socket); + + if (chosenNickName) { + cert = PK11_FindCertFromNickname(chosenNickName, proto_win); + if (cert) { + privKey = PK11_FindKeyByAnyCert(cert, proto_win); + if (privKey) { + secStatus = SECSuccess; + } else { + CERT_DestroyCertificate(cert); + } + } + } else { /* no nickname given, automatically find the right cert */ + CERTCertNicknames *names; + int i; + + names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(), + SEC_CERT_NICKNAMES_USER, proto_win); + + if (names != NULL) { + for(i = 0; i < names->numnicknames; i++ ) { + + cert = PK11_FindCertFromNickname(names->nicknames[i], + proto_win); + if (!cert) { + continue; + } + + /* Only check unexpired certs */ + if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE) + != secCertTimeValid ) { + CERT_DestroyCertificate(cert); + continue; + } + + secStatus = NSS_CmpCertChainWCANames(cert, caNames); + if (secStatus == SECSuccess) { + privKey = PK11_FindKeyByAnyCert(cert, proto_win); + if (privKey) { + break; + } + secStatus = SECFailure; + break; + } + CERT_FreeNicknames(names); + } /* for loop */ + } + } + + if (secStatus == SECSuccess) { + *pRetCert = cert; + *pRetKey = privKey; + } + + return secStatus; +} + +SECStatus + handshakeCallback(PRFileDesc *socket, void *arg) +{ + slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "----------> Handshake has completed, ready to send data securely.\n"); + return SECSuccess; +} + + +/** + * PUBLIC FUNCTIONS IMPLEMENTATION + */ +int http_impl_init(Slapi_ComponentId *plugin_id) +{ + int status = HTTP_IMPL_SUCCESS; + slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "-> http_impl_init \n"); + httpConfig = NULL; + + httpConfig = (httpPluginConfig *) slapi_ch_calloc(1, sizeof(httpPluginConfig)); + + status = readConfigLDAPurl(plugin_id, HTTP_PLUGIN_DN); + if (status != 0) { + slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "http_impl_start: Unable to get HTTP config information \n"); + return HTTP_IMPL_FAILURE; + } + + status = readConfigLDAPurl(plugin_id, CONFIG_DN); + if (status != 0) { + slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "http_impl_start: Unable to get config information \n"); + return HTTP_IMPL_FAILURE; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "<- http_impl_init \n"); + + return status; + +} + + +int http_impl_get_text(char *url, char **data, int *bytesRead) +{ + int status = HTTP_IMPL_SUCCESS; + status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET); + return status; +} + +int http_impl_get_binary(char *url, char **data, int *bytesRead) +{ + int status = HTTP_IMPL_SUCCESS; + status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET); + return status; +} + +int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead) +{ + int status = HTTP_IMPL_SUCCESS; + status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_REDIRECT); + return status; +} + +int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead) +{ + int status = HTTP_IMPL_SUCCESS; + status = doRequestRetry(url, httpheaderArray, body, data, bytesRead, HTTP_REQ_TYPE_POST); + return status; +} + +void http_impl_shutdown() +{ + int status = HTTP_IMPL_SUCCESS; + /** + * Put cleanup code here + */ +} + +static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn) { + + int rc = LDAP_SUCCESS; + Slapi_DN *sdn = NULL; + int status = HTTP_IMPL_SUCCESS; + Slapi_Entry *entry = NULL; + + sdn = slapi_sdn_new_dn_byref(plugindn); + rc = slapi_search_internal_get_entry(sdn, NULL, &entry, plugin_id); + slapi_sdn_free(&sdn); + if (rc != LDAP_SUCCESS) { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "readConfigLDAPurl: Could not find entry %s (error %d)\n", plugindn, rc); + status = HTTP_IMPL_FAILURE; + return status; + } + if (NULL == entry) + { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "readConfigLDAPurl: No entries found for <%s>\n", plugindn); + + status = HTTP_IMPL_FAILURE; + return status; + } + + if ((PL_strcasecmp(plugindn, HTTP_PLUGIN_DN) == 0)) + status = parseHTTPConfigEntry(entry); + else + status = parseConfigEntry(entry); + + slapi_entry_free(entry); + return status; + +} + +/* Retrieves the plugin configuration info */ + +/* Retrieves security info as well as the path info required for the SSL +config dir */ +static int parseConfigEntry(Slapi_Entry *e) +{ + char *value = NULL; + + value = slapi_entry_attr_get_charptr(e, ATTR_DS_SECURITY); + if (value) { + httpConfig->DS_sslOn = value; + } + + return HTTP_IMPL_SUCCESS; + +} + + +static int parseHTTPConfigEntry(Slapi_Entry *e) +{ + int value = 0; + + + value = slapi_entry_attr_get_int(e, ATTR_RETRY_COUNT); + if (value) { + httpConfig->retryCount = value; + } + + value = slapi_entry_attr_get_int(e, ATTR_CONNECTION_TIME_OUT); + if (value) { + httpConfig->connectionTimeOut = value; + } + else { + LDAPDebug( LDAP_DEBUG_PLUGIN, "parseHTTPConfigEntry: HTTP Connection Time Out cannot be read. Setting to default value of 5000 ms \n", 0,0,0); + httpConfig->connectionTimeOut = 5000; + } + + + value = slapi_entry_attr_get_int(e, ATTR_READ_TIME_OUT); + if (value) { + httpConfig->readTimeOut = value; + } + else { + LDAPDebug( LDAP_DEBUG_PLUGIN, "parseHTTPConfigEntry: HTTP Read Time Out cannot be read. Setting to default value of 5000 ms \n", 0,0,0); + httpConfig->readTimeOut = 5000; + } + + httpConfig->nssInitialized = 0; + + return HTTP_IMPL_SUCCESS; + +} + +/** + * Self Testing + */ +#ifdef BUILD_STANDALONE +int main(int argc, char **argv) +{ + PRStatus status = PR_SUCCESS; + char *buf; + int bytes; + char *host; + PRInt32 port; + char *path; + if (argc < 2) { + printf("URL missing\n"); + return -1; + } + PR_Init(PR_USER_THREAD,PR_PRIORITY_NORMAL, 0); + doRequest(argv[1], &buf, &bytes, 2); + printf( "%s\n", buf ); + return -1; +} +#endif + diff --git a/ldap/servers/plugins/http/http_impl.h b/ldap/servers/plugins/http/http_impl.h new file mode 100644 index 00000000..0bca3ca2 --- /dev/null +++ b/ldap/servers/plugins/http/http_impl.h @@ -0,0 +1,25 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ +#ifndef HTTP_IMPL_H__ +#define HTTP_IMPL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +int http_impl_init(Slapi_ComponentId *plugin_id); +int http_impl_get_text(char *url, char **data, int *bytesRead); +int http_impl_get_binary(char *url, char **data, int *bytesRead); +int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead); +int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead); +void http_impl_shutdown(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ldap/servers/plugins/passthru/Makefile b/ldap/servers/plugins/passthru/Makefile new file mode 100644 index 00000000..11540915 --- /dev/null +++ b/ldap/servers/plugins/passthru/Makefile @@ -0,0 +1,90 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server "Pass Through Authentication" plugin +# +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libpassthru +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libpassthru.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd + +PASSTHRU_OBJS= ptbind.o ptconfig.o ptconn.o ptdebug.o ptpreop.o ptutil.o + +OBJS = $(addprefix $(OBJDEST)/, $(PASSTHRU_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBPASSTHRU_DLL_OBJ = $(addprefix $(OBJDEST)/, ptdllmain.o) +endif + +LIBPASSTHRU= $(addprefix $(LIBDIR)/, $(PASSTHRU_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(NSPRLINK) +endif + + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libpassthru.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(LDAP_SDK_LIBLDAP_DLL) $(NSPRLINK) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBPASSTHRU) + +$(LIBPASSTHRU): $(OBJS) $(LIBPASSTHRU_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBPASSTHRU_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBPASSTHRU_DLL_OBJ) +endif + $(RM) $(LIBPASSTHRU) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(OBJS): passthru.h diff --git a/ldap/servers/plugins/passthru/PT-Notes b/ldap/servers/plugins/passthru/PT-Notes new file mode 100644 index 00000000..2e3cea10 --- /dev/null +++ b/ldap/servers/plugins/passthru/PT-Notes @@ -0,0 +1,30 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# + Pass Through Authentication Plugin Notes + +Key + r required feature + n nice-to-have + ? undecided whether this is a good idea or not + +Missing features: +n U/I for configuration. + +Loose ends: +n Resolve any remaining code that is marked with XXX. +n Put some thought into cases we do not handle (SASL, no DN, no passwd) and + make sure we do the right thing in terms of errors, letting server's + standard mechanism handle the bind, etc. +? Protect against server connecting back to itself recursively. + +Testing: +r Basic tests (all platforms: NT,Sol,IRIX,AIX,HP/UX,OSF/1). +r Controls (both coming (e.g., ?) and going (e.g., password policy). +r SSL connections to remote servers. +r LDAPv2/v3 compatiblity +r Stress tests. diff --git a/ldap/servers/plugins/passthru/libpassthru.def b/ldap/servers/plugins/passthru/libpassthru.def new file mode 100644 index 00000000..b08d907a --- /dev/null +++ b/ldap/servers/plugins/passthru/libpassthru.def @@ -0,0 +1,14 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7 Pass Through Authentication Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + passthruauth_init @1 + plugin_init_debug_level @2 diff --git a/ldap/servers/plugins/passthru/passthru.h b/ldap/servers/plugins/passthru/passthru.h new file mode 100644 index 00000000..fdf30d65 --- /dev/null +++ b/ldap/servers/plugins/passthru/passthru.h @@ -0,0 +1,131 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * passthru.h - Pass Through Authentication shared definitions + * + */ + +#ifndef _PASSTHRU_H_ +#define _PASSTHRU_H_ + +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include "portable.h" +#include "slapi-plugin.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" +#include <nspr.h> + +/* Private API: to get slapd_pr_strerror() and SLAPI_COMPONENT_NAME_NSPR */ +#include "slapi-private.h" + +/* + * macros + */ +#define PASSTHRU_PLUGIN_SUBSYSTEM "passthru-plugin" /* for logging */ + +#define PASSTHRU_ASSERT( expr ) PR_ASSERT( expr ) + +#define PASSTHRU_LDAP_CONN_ERROR( err ) ( (err) == LDAP_SERVER_DOWN || \ + (err) == LDAP_CONNECT_ERROR ) + +#define PASSTHRU_OP_NOT_HANDLED 0 +#define PASSTHRU_OP_HANDLED 1 + +#define PASSTHRU_CONN_TRIES 2 + +/* #define PASSTHRU_VERBOSE_LOGGING */ + +/* defaults */ +#define PASSTHRU_DEF_SRVR_MAXCONNECTIONS 3 +#define PASSTHRU_DEF_SRVR_MAXCONCURRENCY 5 +#define PASSTHRU_DEF_SRVR_TIMEOUT 300 /* seconds */ +#define PASSTHRU_DEF_SRVR_PROTOCOL_VERSION LDAP_VERSION3 +#define PASSTHRU_DEF_SRVR_CONNLIFETIME 0 /* seconds */ +#define PASSTHRU_DEF_SRVR_FAILOVERCONNLIFETIME 300 /* seconds */ + +/* + * structs + */ +typedef struct passthrusuffix { + int ptsuffix_len; + char *ptsuffix_normsuffix; /* not case normalized */ + struct passthrusuffix *ptsuffix_next; +} PassThruSuffix; + +typedef struct passthruconnection { + LDAP *ptconn_ld; + int ptconn_ldapversion; + int ptconn_usecount; +#define PASSTHRU_CONNSTATUS_OK 0 +#define PASSTHRU_CONNSTATUS_DOWN 1 +#define PASSTHRU_CONNSTATUS_STALE 2 + int ptconn_status; + time_t ptconn_opentime; + struct passthruconnection *ptconn_prev; + struct passthruconnection *ptconn_next; +} PassThruConnection; + +typedef struct passthruserver { + char *ptsrvr_url; /* copy from argv[i] */ + char *ptsrvr_hostname; + int ptsrvr_port; + int ptsrvr_secure; /* use SSL? */ + int ptsrvr_ldapversion; + int ptsrvr_maxconnections; + int ptsrvr_maxconcurrency; + int ptsrvr_connlifetime; /* in seconds */ + struct timeval *ptsrvr_timeout; /* for ldap_result() */ + PassThruSuffix *ptsrvr_suffixes; + Slapi_CondVar *ptsrvr_connlist_cv; + Slapi_Mutex *ptsrvr_connlist_mutex; /* protects connlist */ + int ptsrvr_connlist_count; + PassThruConnection *ptsrvr_connlist; + struct passthruserver *ptsrvr_next; +} PassThruServer; + +typedef struct passthruconfig { + PassThruServer *ptconfig_serverlist; +} PassThruConfig; + + +/* + * public functions + */ +/* + * ptbind.c: + */ +int passthru_simple_bind_s( Slapi_PBlock *pb, PassThruServer *srvr, int tries, + char *dn, struct berval *creds, LDAPControl **reqctrls, int *lderrnop, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp ); + +/* + * ptconfig.c: + */ +int passthru_config( int argc, char **argv ); +PassThruConfig *passthru_get_config( void ); + +/* + * ptconn.c: + */ +int passthru_dn2server( PassThruConfig *cfg, char *normdn, + PassThruServer **srvrp ); +int passthru_get_connection( PassThruServer *srvr, LDAP **ldp ); +void passthru_release_connection( PassThruServer *srvr, LDAP *ld, int dispose ); +void passthru_close_all_connections( PassThruConfig *cfg ); + +/* + * ptutil.c: + */ +struct berval **passthru_strs2bervals( char **ss ); +char ** passthru_bervals2strs( struct berval **bvs ); +void passthru_free_bervals( struct berval **bvs ); +char *passthru_urlparse_err2string( int err ); + +#endif /* _PASSTHRU_H_ */ diff --git a/ldap/servers/plugins/passthru/ptbind.c b/ldap/servers/plugins/passthru/ptbind.c new file mode 100644 index 00000000..f9da57a1 --- /dev/null +++ b/ldap/servers/plugins/passthru/ptbind.c @@ -0,0 +1,144 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * ptbind.c - LDAP bind-related code for Pass Through Authentication + * + */ + +#include "passthru.h" + +static int +passthru_simple_bind_once_s( PassThruServer *srvr, char *dn, + struct berval *creds, LDAPControl **reqctrls, int *lderrnop, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp ); + + +/* + * Attempt to chain a bind request off to "srvr." We return an LDAP error + * code that indicates whether we successfully got a response from the + * other server or not. If we succeed, we return LDAP_SUCCESS and *lderrnop + * is set to the result code from the remote server. + * + * Note that in the face of "ldap server down" or "ldap connect failed" errors + * we make up to "tries" attempts to bind to the remote server. Since we + * are only interested in recovering silently when the remote server is up + * but decided to close our connection, we retry without pausing between + * attempts. + */ +int +passthru_simple_bind_s( Slapi_PBlock *pb, PassThruServer *srvr, int tries, + char *dn, struct berval *creds, LDAPControl **reqctrls, int *lderrnop, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp ) +{ + int rc; + + PASSTHRU_ASSERT( srvr != NULL ); + PASSTHRU_ASSERT( tries > 0 ); + PASSTHRU_ASSERT( creds != NULL ); + PASSTHRU_ASSERT( lderrnop != NULL ); + PASSTHRU_ASSERT( refurlsp != NULL ); + + do { + /* + * check to see if operation has been abandoned... + */ + if ( slapi_op_abandoned( pb )) { + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "operation abandoned\n" ); + rc = LDAP_USER_CANCELLED; + } else { + rc = passthru_simple_bind_once_s( srvr, dn, creds, reqctrls, + lderrnop, matcheddnp, errmsgp, refurlsp, resctrlsp ); + } + } while ( PASSTHRU_LDAP_CONN_ERROR( rc ) && --tries > 0 ); + + return( rc ); +} + + +/* + * like passthru_simple_bind_s() but only makes one attempt. + */ +static int +passthru_simple_bind_once_s( PassThruServer *srvr, char *dn, + struct berval *creds, LDAPControl **reqctrls, int *lderrnop, + char **matcheddnp, char **errmsgp, struct berval ***refurlsp, + LDAPControl ***resctrlsp ) +{ + int rc, msgid; + char **referrals; + struct timeval tv, *timeout; + LDAPMessage *result; + LDAP *ld; + + /* + * Grab an LDAP connection to use for this bind. + */ + ld = NULL; + if (( rc = passthru_get_connection( srvr, &ld )) != LDAP_SUCCESS ) { + goto release_and_return; + } + + /* + * Send the bind operation (need to retry on LDAP_SERVER_DOWN) + */ + if (( rc = ldap_sasl_bind( ld, dn, LDAP_SASL_SIMPLE, creds, reqctrls, + NULL, &msgid )) != LDAP_SUCCESS ) { + goto release_and_return; + } + + /* + * determine timeout value (how long we will wait for a response) + * if timeout is NULL or zero'd, we wait indefinitely. + */ + if ( srvr->ptsrvr_timeout == NULL || ( srvr->ptsrvr_timeout->tv_sec == 0 + && srvr->ptsrvr_timeout->tv_usec == 0 )) { + timeout = NULL; + } else { + tv = *srvr->ptsrvr_timeout; /* struct copy */ + timeout = &tv; + } + + /* + * Wait for a result. + */ + rc = ldap_result( ld, msgid, 1, timeout, &result ); + + /* + * Interpret the result. + */ + if ( rc == 0 ) { /* timeout */ + /* + * Timed out waiting for a reply from the server. + */ + rc = LDAP_TIMEOUT; + } else if ( rc < 0 ) { + /* + * Some other error occurred (no result received). + */ + rc = ldap_get_lderrno( ld, matcheddnp, errmsgp ); + } else { + /* + * Got a result from remote server -- parse it. + */ + rc = ldap_parse_result( ld, result, lderrnop, matcheddnp, errmsgp, + &referrals, resctrlsp, 1 ); + if ( referrals != NULL ) { + *refurlsp = passthru_strs2bervals( referrals ); + ldap_value_free( referrals ); + } + } + + +release_and_return: + if ( ld != NULL ) { + passthru_release_connection( srvr, ld, PASSTHRU_LDAP_CONN_ERROR( rc )); + } + + return( rc ); +} diff --git a/ldap/servers/plugins/passthru/ptconfig.c b/ldap/servers/plugins/passthru/ptconfig.c new file mode 100644 index 00000000..c3653f66 --- /dev/null +++ b/ldap/servers/plugins/passthru/ptconfig.c @@ -0,0 +1,301 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * ptconfig.c - configuration-related code for Pass Through Authentication + * + */ + +#include "passthru.h" + +/* + * Configuration is a bit complicated to fit into a single slapd config file + * line, but for now that's how it works. The format is: + * + * plugin preoperation on PTA NSHOME/passthru-plugin.so passthruauth_init ARGS + * + * where each ARGS provides configuration for one host. Each ARG should + * be of the form: + * + * "ldap://hosts/suffixes maxconns,maxconcurrency,timeout,ldver,connlifetime" + * OR + * "ldaps://hosts/suffixes maxconns,maxconcurrency,timeout,ldver,connlifetime" + * + * where: + * hosts is a space-separated list of remote servers (with optional port + * numbers) to be used. Each one is tried in order when opening an + * LDAP connection. + * suffixes is a semicolon separated list of DNs (if a DN contains a + * semicolon it must be represented \3B), + * maxconns is a limit on how many connections will be made, + * maxconcurrency is a limit on how many operations can share a connection, + * timeout is a time limit in seconds for bind operations to complete (use + * 0 to specify an infinite limit). + * ldver is the LDAP protocol version to use to talk to the server (2 or 3) + * connlifetime is a time limit time in seconds for a connection to be + * used before it is closed and reopened (use 0 to specify an infinite + * limit). connlifetime can be omitted in which case a default value + * is used; this is for compatibility with DS 4.0 which did not support + * connlifetime. + */ + + +/* + * function prototypes + */ +static char **get_backend_suffixes( void ); +static int is_underneath_backend_suffix( char *normdn, char **besuffixes ); + +/* + * static variables + */ +/* for now, there is only one configuration and it is global to the plugin */ +static PassThruConfig theConfig; +static int inited = 0; + + +/* + * Read configuration and create a configuration data structure. + * This is called after the server has configured itself so we can check + * for things like collisions between our suffixes and backend's suffixes. + * Returns an LDAP error code (LDAP_SUCCESS if all goes well). + * XXXmcs: this function leaks memory if any errors occur. + */ +int +passthru_config( int argc, char **argv ) +{ + int i, j, rc, tosecs, using_def_connlifetime; + char *p, **suffixarray; + PassThruServer *prevsrvr, *srvr; + PassThruSuffix *suffix, *prevsuffix; + LDAPURLDesc *ludp; + + if ( inited ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "only one pass through plugin instance can be used\n" ); + return( LDAP_PARAM_ERROR ); + } + + inited = 1; + + /* + * It doesn't make sense to configure a pass through plugin without + * providing at least one remote server. Return an error if attempted. + */ + if ( argc < 1 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "no pass through servers found in configuration" + " (at least one must be listed)\n" ); + return( LDAP_PARAM_ERROR ); + } + + /* + * Parse argv[] values. + */ + prevsrvr = NULL; + for ( i = 0; i < argc; ++i ) { + srvr = (PassThruServer *)slapi_ch_calloc( 1, sizeof( PassThruServer )); + srvr->ptsrvr_url = slapi_ch_strdup( argv[i] ); + + if (( p = strchr( srvr->ptsrvr_url, ' ' )) == NULL ) { + /* + * use defaults for maxconnections, maxconcurrency, timeout, + * LDAP version, and connlifetime. + */ + srvr->ptsrvr_maxconnections = PASSTHRU_DEF_SRVR_MAXCONNECTIONS; + srvr->ptsrvr_maxconcurrency = PASSTHRU_DEF_SRVR_MAXCONCURRENCY; + srvr->ptsrvr_timeout = (struct timeval *)slapi_ch_calloc( 1, + sizeof( struct timeval )); + srvr->ptsrvr_timeout->tv_sec = PASSTHRU_DEF_SRVR_TIMEOUT; + srvr->ptsrvr_ldapversion = PASSTHRU_DEF_SRVR_PROTOCOL_VERSION; + using_def_connlifetime = 1; + } else { + /* + * parse parameters. format is: + * maxconnections,maxconcurrency,timeout,ldapversion + * OR maxconnections,maxconcurrency,timeout,ldapversion,lifetime + */ + *p++ = '\0'; + rc = sscanf( p, "%d,%d,%d,%d,%d", &srvr->ptsrvr_maxconnections, + &srvr->ptsrvr_maxconcurrency, &tosecs, + &srvr->ptsrvr_ldapversion, &srvr->ptsrvr_connlifetime ); + if ( rc < 4 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "server parameters should be in the form " + "\"maxconnections,maxconcurrency,timeout,ldapversion," + "connlifetime\" (got \"%s\")\n", p ); + return( LDAP_PARAM_ERROR ); + } else if ( rc < 5 ) { + using_def_connlifetime = 1; + srvr->ptsrvr_connlifetime = PASSTHRU_DEF_SRVR_CONNLIFETIME; + } else { + using_def_connlifetime = 0; + } + + if ( srvr->ptsrvr_ldapversion != LDAP_VERSION2 + && srvr->ptsrvr_ldapversion != LDAP_VERSION3 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "LDAP protocol version should be %d or %d (got %d)\n", + LDAP_VERSION2, LDAP_VERSION3, + srvr->ptsrvr_ldapversion ); + return( LDAP_PARAM_ERROR ); + } + + if ( srvr->ptsrvr_maxconnections <= 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "maximum connections must be greater than " + "zero (got %d)\n", srvr->ptsrvr_maxconnections ); + return( LDAP_PARAM_ERROR ); + } + + if ( srvr->ptsrvr_maxconcurrency <= 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "maximum concurrency must be greater than " + "zero (got %d)\n", srvr->ptsrvr_maxconcurrency ); + return( LDAP_PARAM_ERROR ); + } + + if ( tosecs <= 0 ) { + srvr->ptsrvr_timeout = NULL; + } else { + srvr->ptsrvr_timeout = (struct timeval *)slapi_ch_calloc( 1, + sizeof( struct timeval )); + srvr->ptsrvr_timeout->tv_sec = tosecs; + } + } + + /* + * parse the LDAP URL + */ + if (( rc = ldap_url_parse( srvr->ptsrvr_url, &ludp )) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "unable to parse LDAP URL \"%s\" (%s)\n", + srvr->ptsrvr_url, passthru_urlparse_err2string( rc )); + return( LDAP_PARAM_ERROR ); + } + + if ( ludp->lud_dn == NULL || *ludp->lud_dn == '\0' ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "missing suffix in LDAP URL \"%s\"\n", + srvr->ptsrvr_url ); + return( LDAP_PARAM_ERROR ); + } + + srvr->ptsrvr_hostname = slapi_ch_strdup( ludp->lud_host ); + srvr->ptsrvr_port = ludp->lud_port; + srvr->ptsrvr_secure = + (( ludp->lud_options & LDAP_URL_OPT_SECURE ) != 0 ); + + /* + * If a space-separated list of hosts is configured for failover, + * use a different (non infinite) default for connection lifetime. + */ + if ( using_def_connlifetime && + strchr( srvr->ptsrvr_hostname, ' ' ) != NULL ) { + srvr->ptsrvr_connlifetime = + PASSTHRU_DEF_SRVR_FAILOVERCONNLIFETIME; + } + + /* + * split the DN into multiple suffixes (separated by ';') + */ + if (( suffixarray = ldap_str2charray( ludp->lud_dn, ";" )) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "unable to parse suffix string \"%s\" within \"%s\"\n", + ludp->lud_dn, srvr->ptsrvr_url ); + return( LDAP_PARAM_ERROR ); + } + + /* + * free our LDAP URL descriptor + */ + ldap_free_urldesc( ludp ); + ludp = NULL; + + /* + * reorganize the suffixes into a linked list and normalize them + */ + prevsuffix = NULL; + for ( j = 0; suffixarray[ j ] != NULL; ++j ) { + + /* + * allocate a new PassThruSuffix structure and fill it in. + */ + suffix = (PassThruSuffix *)slapi_ch_malloc( + sizeof( PassThruSuffix )); + suffix->ptsuffix_normsuffix = + slapi_dn_normalize( suffixarray[ j ] ); + suffixarray[ j ] = NULL; + suffix->ptsuffix_len = strlen( suffix->ptsuffix_normsuffix ); + suffix->ptsuffix_next = NULL; + + /* + * add to end of list + */ + if ( prevsuffix == NULL ) { + srvr->ptsrvr_suffixes = suffix; + } else { + prevsuffix->ptsuffix_next = suffix; + } + prevsuffix = suffix; + } + ldap_memfree( suffixarray ); + + /* + * create mutexes and condition variables for this server + */ + if (( srvr->ptsrvr_connlist_mutex = slapi_new_mutex()) == NULL || + ( srvr->ptsrvr_connlist_cv = slapi_new_condvar( + srvr->ptsrvr_connlist_mutex )) == NULL ) { + return( LDAP_LOCAL_ERROR ); + } + + /* + * add this server to the end of our list + */ + if ( prevsrvr == NULL ) { + theConfig.ptconfig_serverlist = srvr; + } else { + prevsrvr->ptsrvr_next = srvr; + } + prevsrvr = srvr; + +#ifdef PASSTHRU_VERBOSE_LOGGING + /* + * log configuration for debugging purposes + */ + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "PTA server host: \"%s\", port: %d, secure: %d," + " maxconnections: %d, maxconcurrency: %d, timeout: %d," + " ldversion: %d, connlifetime: %d\n", + srvr->ptsrvr_hostname, srvr->ptsrvr_port, + srvr->ptsrvr_secure, srvr->ptsrvr_maxconnections, + srvr->ptsrvr_maxconcurrency, + srvr->ptsrvr_timeout == NULL ? -1 + : srvr->ptsrvr_timeout->tv_sec, srvr->ptsrvr_ldapversion, + srvr->ptsrvr_connlifetime ); + for ( prevsuffix = srvr->ptsrvr_suffixes; prevsuffix != NULL; + prevsuffix = prevsuffix->ptsuffix_next ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + " normalized suffix: \"%s\"\n", + prevsuffix->ptsuffix_normsuffix ); + } +#endif + + } + + return( LDAP_SUCCESS ); +} + + +/* + * Get the pass though configuration data. For now, there is only one + * configuration and it is global to the plugin. + */ +PassThruConfig * +passthru_get_config( void ) +{ + return( &theConfig ); +} diff --git a/ldap/servers/plugins/passthru/ptconn.c b/ldap/servers/plugins/passthru/ptconn.c new file mode 100644 index 00000000..56e2e0cc --- /dev/null +++ b/ldap/servers/plugins/passthru/ptconn.c @@ -0,0 +1,420 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * ptconn.c - LDAP connection-related code for Pass Through Authentication + * + */ + +#include "passthru.h" + +/* + * function prototypes + */ +static int dn_is_underneath_suffix( PassThruSuffix *suffix, char *normdn, + int dnlen ); +static void close_and_dispose_connection( PassThruConnection *conn ); +static void check_for_stale_connections( PassThruServer *srvr ); + + +/* + * Most of the complicated connection-related code lives in this file. Some + * general notes about how we manage our connections to "remote" LDAP servers: + * + * 1) Each server we have a relationship with is managed independently. + * + * 2) We may simultaneously issue multiple bind requests on a single LDAP + * connection. Each server has a "maxconcurrency" configuration + * parameter associated with it that caps the number of outstanding + * binds per connection. For each connection we maintain a "usecount" + * which is used to track the number of threads using the connection. + * + * 3) We may open more than one connection to a server. This is only done + * when "maxconcurrency" is exceeded for all the connections we already + * have open. Each server has a "maxconnections" configuration + * parameter associated with it that caps the number of connections. + * We also maintain a "connlist_count" for each server so we know when + * we have reached the maximum number of open connections allowed. + * + * 4) If no connection is available to service a request (and we have + * reached the limit of how many we are supposed to open), threads + * go to sleep on a condition variable and one is woken up each time + * a connection's "usecount" is decremented. + * + * 5) If we see an LDAP_CONNECT_ERROR or LDAP_SERVER_DOWN error on a + * session handle, we mark its status as PASSTHRU_CONNSTATUS_DOWN and + * close it as soon as all threads using it release it. Connections + * marked as "down" are not counted against the "maxconnections" limit. + * + * 6) We close and reopen connections that have been open for more than + * the server's configured connection lifetime. This is done to ensure + * that we reconnect to a primary server after failover occurs. If no + * lifetime is configured or it is set to 0, we never close and reopen + * connections. + */ + + +/* + * Given a normalized target dn, see if it we should "pass through" + * authentication to another LDAP server. The answer is "yes" if the + * target dn resides under one of the suffixes we have that is associated + * with an LDAP server we know about. + * + * This function assumes that normdn is normalized and the the suffixes in the + * cfg structure have also been normalized. + * + * Returns an LDAP error code, typically: + * LDAP_SUCCESS should pass though; *srvrp set. + * LDAP_NO_SUCH_OBJECT let this server handle the bind. + */ +int +passthru_dn2server( PassThruConfig *cfg, char *normdn, PassThruServer **srvrp ) +{ + PassThruServer *ptsrvr; + PassThruSuffix *ptsuffix; + int dnlen; + + PASSTHRU_ASSERT( cfg != NULL ); + PASSTHRU_ASSERT( normdn != NULL ); + PASSTHRU_ASSERT( srvrp != NULL ); + + dnlen = strlen( normdn ); + + for ( ptsrvr = cfg->ptconfig_serverlist; ptsrvr != NULL; + ptsrvr = ptsrvr->ptsrvr_next ) { + for ( ptsuffix = ptsrvr->ptsrvr_suffixes; ptsuffix != NULL; + ptsuffix = ptsuffix->ptsuffix_next ) { + if ( dn_is_underneath_suffix( ptsuffix, normdn, dnlen )) { + *srvrp = ptsrvr; + return( LDAP_SUCCESS ); /* got it */ + } + } + } + + *srvrp = NULL; + return( LDAP_NO_SUCH_OBJECT ); /* no match */ +} + + +/* + * Get an LDAP session handle for communicating with srvr. + * + * Returns an LDAP eror code, typically: + * LDAP_SUCCESS + * other + */ +int +passthru_get_connection( PassThruServer *srvr, LDAP **ldp ) +{ + int rc; + PassThruConnection *conn, *connprev; + LDAP *ld; + + PASSTHRU_ASSERT( srvr != NULL ); + PASSTHRU_ASSERT( ldp != NULL ); + + check_for_stale_connections( srvr ); + + slapi_lock_mutex( srvr->ptsrvr_connlist_mutex ); + rc = LDAP_SUCCESS; /* optimistic */ + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthru_get_connection server %s:%d conns: %d maxconns: %d\n", + srvr->ptsrvr_hostname, srvr->ptsrvr_port, srvr->ptsrvr_connlist_count, + srvr->ptsrvr_maxconnections ); + + for ( ;; ) { + /* + * look for an available, already open connection + */ + connprev = NULL; + for ( conn = srvr->ptsrvr_connlist; conn != NULL; + conn = conn->ptconn_next ) { + if ( conn->ptconn_status == PASSTHRU_CONNSTATUS_OK + && conn->ptconn_usecount < srvr->ptsrvr_maxconcurrency ) { +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= passthru_get_connection server found " + "conn 0x%x to use)\n", conn->ptconn_ld ); +#endif + goto unlock_and_return; /* found one */ + } + connprev = conn; + } + + if ( srvr->ptsrvr_connlist_count < srvr->ptsrvr_maxconnections ) { + /* + * we have not exceeded the maximum number of connections allowed, + * so we initialize a new one and add it to the end of our list. + */ + if (( ld = slapi_ldap_init( srvr->ptsrvr_hostname, + srvr->ptsrvr_port, srvr->ptsrvr_secure, 1 )) == NULL ) { +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= passthru_get_connection slapi_ldap_init failed\n" ); +#endif + rc = LDAP_LOCAL_ERROR; + goto unlock_and_return; + } + + /* + * set protocol version to correct value for this server + */ + if ( ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, + &srvr->ptsrvr_ldapversion ) != 0 ) { + slapi_ldap_unbind( ld ); + } + + conn = (PassThruConnection *)slapi_ch_malloc( + sizeof( PassThruConnection )); + conn->ptconn_ld = ld; + conn->ptconn_status = PASSTHRU_CONNSTATUS_OK; + time( &conn->ptconn_opentime ); + conn->ptconn_ldapversion = srvr->ptsrvr_ldapversion; + conn->ptconn_usecount = 0; + conn->ptconn_next = NULL; + conn->ptconn_prev = connprev; + if ( connprev == NULL ) { + srvr->ptsrvr_connlist = conn; + conn->ptconn_prev = NULL; + } else { + connprev->ptconn_next = conn; + conn->ptconn_prev = connprev; + } + + ++srvr->ptsrvr_connlist_count; + +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= passthru_get_connection added new conn 0x%x, " + "conn count now %d\n", ld, srvr->ptsrvr_connlist_count ); +#endif + goto unlock_and_return; /* got a new one */ + } + +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "... passthru_get_connection waiting for conn to free up\n" ); +#endif + slapi_wait_condvar( srvr->ptsrvr_connlist_cv, NULL ); + +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "... passthru_get_connection awake again\n" ); +#endif + } + +unlock_and_return: + if ( conn != NULL ) { + ++conn->ptconn_usecount; + *ldp = conn->ptconn_ld; + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= passthru_get_connection ld=0x%x (concurrency now %d)\n", + *ldp, conn->ptconn_usecount ); + } else { + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= passthru_get_connection error %d\n", rc ); + } + + slapi_unlock_mutex( srvr->ptsrvr_connlist_mutex ); + return( rc ); +} + + +/* + * Mark the connection ld is associated with as free to be used again. + * If dispose is non-zero, we mark the connection as "bad" and dispose + * of it and its ld once the use count becomes zero. + */ +void +passthru_release_connection( PassThruServer *srvr, LDAP *ld, int dispose ) +{ + PassThruConnection *conn, *connprev; + + PASSTHRU_ASSERT( srvr != NULL ); + PASSTHRU_ASSERT( ld != NULL ); + +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthru_release_connection ld=0x%x%s\n", ld, + dispose ? " (disposing)" : "" ); +#endif + + slapi_lock_mutex( srvr->ptsrvr_connlist_mutex ); + + /* + * find the connection structure this ld is part of + */ + connprev = NULL; + for ( conn = srvr->ptsrvr_connlist; conn != NULL; + conn = conn->ptconn_next ) { + if ( ld == conn->ptconn_ld ) { + break; + } + connprev = conn; + } + + if ( conn == NULL ) { /* ld not found -- unexpected */ + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthru_release_connection ld=0x%x not found\n", ld ); + } else { + PASSTHRU_ASSERT( conn->ptconn_usecount > 0 ); + --conn->ptconn_usecount; + if ( dispose ) { + conn->ptconn_status = PASSTHRU_CONNSTATUS_DOWN; + } + + if ( conn->ptconn_status != PASSTHRU_CONNSTATUS_OK + && conn->ptconn_usecount == 0 ) { + /* + * remove from server's connection list + */ + if ( connprev == NULL ) { + srvr->ptsrvr_connlist = conn->ptconn_next; + } else { + connprev->ptconn_next = conn->ptconn_next; + } + --srvr->ptsrvr_connlist_count; + + /* + * close connection and free memory + */ + close_and_dispose_connection( conn ); + } + } + + /* + * wake up a thread that is waiting for a connection (there may not be + * any but the slapi_notify_condvar() call should be cheap in any event). + */ + slapi_notify_condvar( srvr->ptsrvr_connlist_cv, 0 ); + + /* + * unlock and return + */ + slapi_unlock_mutex( srvr->ptsrvr_connlist_mutex ); +} + + +/* + * close all open connections in preparation for server shutdown, etc. + */ +void +passthru_close_all_connections( PassThruConfig *cfg ) +{ + PassThruServer *srvr; + PassThruConnection *conn, *nextconn; + + PASSTHRU_ASSERT( cfg != NULL ); + + for ( srvr = cfg->ptconfig_serverlist; srvr != NULL; + srvr = srvr->ptsrvr_next ) { + for ( conn = srvr->ptsrvr_connlist; conn != NULL; conn = nextconn ) { + nextconn = conn->ptconn_next; + close_and_dispose_connection( conn ); + } + } +} + + +/* + * return non-zero value if normdn falls underneath a suffix + */ +static int +dn_is_underneath_suffix( PassThruSuffix *suffix, char *normdn, int dnlen ) +{ + PASSTHRU_ASSERT( suffix != NULL ); + PASSTHRU_ASSERT( normdn != NULL ); + PASSTHRU_ASSERT( dnlen >= 0 ); + + return ( suffix->ptsuffix_len <= dnlen && + slapi_UTF8CASECMP( suffix->ptsuffix_normsuffix, + normdn + ( dnlen - suffix->ptsuffix_len )) == 0 ); +} + + +/* + * Unbind from server and dispose of a connection. + */ +static void +close_and_dispose_connection( PassThruConnection *conn ) +{ + PASSTHRU_ASSERT( conn != NULL ); + PASSTHRU_ASSERT( conn->ptconn_ld != NULL ); + + slapi_ldap_unbind( conn->ptconn_ld ); + conn->ptconn_ld = NULL; + slapi_ch_free( (void **)&conn ); +} + + +/* + * Close (or mark to be closed) any connections for this srvr that have + * exceeded the maximum connection lifetime. + */ +static void +check_for_stale_connections( PassThruServer *srvr ) +{ + PassThruConnection *conn, *prevconn, *nextconn; + time_t curtime; + + PASSTHRU_ASSERT( srvr != NULL ); + +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "check_for_stale_connections: server %s (lifetime %d secs)\n", + srvr->ptsrvr_url, srvr->ptsrvr_connlifetime ); +#endif + + + if ( srvr->ptsrvr_connlifetime <= 0 ) { + return; + } + + time( &curtime ); + + slapi_lock_mutex( srvr->ptsrvr_connlist_mutex ); + + prevconn = NULL; + for ( conn = srvr->ptsrvr_connlist; conn != NULL; conn = nextconn ) { + nextconn = conn->ptconn_next; + + if ( curtime - conn->ptconn_opentime > srvr->ptsrvr_connlifetime ) { + if ( conn->ptconn_usecount == 0 ) { + /* + * connection is idle and stale -- remove from server's list + */ +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "check_for_stale_connections: discarding idle, " + "stale connection 0x%x\n", conn->ptconn_ld ); +#endif + if ( prevconn == NULL ) { + srvr->ptsrvr_connlist = nextconn; + } else { + prevconn->ptconn_next = nextconn; + } + --srvr->ptsrvr_connlist_count; + close_and_dispose_connection( conn ); + } else { + /* + * connection is stale but in use -- mark to be disposed later + */ +#ifdef PASSTHRU_VERBOSE_LOGGING + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "check_for_stale_connections: marking connection 0x%x " + "stale (use count %d)\n", conn->ptconn_ld, + conn->ptconn_usecount ); +#endif + conn->ptconn_status = PASSTHRU_CONNSTATUS_STALE; + prevconn = conn; + } + } else { + prevconn = conn; + } + } + + slapi_unlock_mutex( srvr->ptsrvr_connlist_mutex ); +} diff --git a/ldap/servers/plugins/passthru/ptdebug.c b/ldap/servers/plugins/passthru/ptdebug.c new file mode 100644 index 00000000..0f01c4a7 --- /dev/null +++ b/ldap/servers/plugins/passthru/ptdebug.c @@ -0,0 +1,23 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * ptdebug.c - debugging-related code for Pass Through Authentication + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "passthru.h" + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif diff --git a/ldap/servers/plugins/passthru/ptdllmain.c b/ldap/servers/plugins/passthru/ptdllmain.c new file mode 100644 index 00000000..0e9eaccf --- /dev/null +++ b/ldap/servers/plugins/passthru/ptdllmain.c @@ -0,0 +1,131 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "ldap.h" +#include "lber.h" +#include "passthru.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif + +#ifdef LDAP_DEBUG +#ifndef _WIN32 +#include <stdarg.h> +#include <stdio.h> + +void LDAPDebug( int level, char* fmt, ... ) +{ + static char debugBuf[1024]; + + if (module_ldap_debug && (*module_ldap_debug & level)) + { + va_list ap; + va_start (ap, fmt); + _snprintf (debugBuf, sizeof(debugBuf), fmt, ap); + va_end (ap); + + OutputDebugString (debugBuf); + } +} +#endif +#endif + +#ifndef _WIN32 + +/* The 16-bit version of the RTL does not implement perror() */ + +#include <stdio.h> + +void perror( const char *msg ) +{ + char buf[128]; + wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ; + OutputDebugString( buf ); +} + +#endif diff --git a/ldap/servers/plugins/passthru/ptpreop.c b/ldap/servers/plugins/passthru/ptpreop.c new file mode 100644 index 00000000..aaad0621 --- /dev/null +++ b/ldap/servers/plugins/passthru/ptpreop.c @@ -0,0 +1,252 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * ptpreop.c - bind pre-operation plugin for Pass Through Authentication + * + */ + +#include "passthru.h" + +static Slapi_PluginDesc pdesc = { "passthruauth", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "pass through authentication plugin" }; + +/* + * function prototypes + */ +static int passthru_bindpreop( Slapi_PBlock *pb ); +static int passthru_bindpreop_start( Slapi_PBlock *pb ); +static int passthru_bindpreop_close( Slapi_PBlock *pb ); + + +/* + * Plugin initialization function (which must be listed in the appropriate + * slapd config file). + */ +int +passthruauth_init( Slapi_PBlock *pb ) +{ + PASSTHRU_ASSERT( pb != NULL ); + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthruauth_init\n" ); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *)SLAPI_PLUGIN_VERSION_01 ) != 0 + || slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 + || slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, + (void *)passthru_bindpreop_start ) != 0 + || slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, + (void *)passthru_bindpreop ) != 0 + || slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, + (void *)passthru_bindpreop_close ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "passthruauth_init failed\n" ); + return( -1 ); + } + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= passthruauth_init succeeded\n" ); + + return( 0 ); +} + + +/* + * passthru_bindpreop_start() is called before the directory server + * is fully up. We parse our configuration and initialize any mutexes, etc. + */ +static int +passthru_bindpreop_start( Slapi_PBlock *pb ) +{ + int argc, rc; + char **argv; + + PASSTHRU_ASSERT( pb != NULL ); + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthru_bindpreop_start\n" ); + + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0 || + slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "unable to get arguments\n" ); + return( -1 ); + } + + if (( rc = passthru_config( argc, argv )) != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "configuration failed (%s)\n", ldap_err2string( rc )); + return( -1 ); + } + + return( 0 ); +} + + +/* + * Called right before the Directory Server shuts down. + */ +static int +passthru_bindpreop_close( Slapi_PBlock *pb ) +{ + PASSTHRU_ASSERT( pb != NULL ); + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthru_bindpreop_close\n" ); + + /* + * close all our open connections. + * XXXmcs: free any memory, mutexes, etc. + */ + passthru_close_all_connections( passthru_get_config() ); + + return( 0 ); +} + + +static int +passthru_bindpreop( Slapi_PBlock *pb ) +{ + int rc, method; + char *normbinddn, *matcheddn; + char *libldap_errmsg, *pr_errmsg, *errmsg; + PassThruConfig *cfg; + PassThruServer *srvr; + struct berval *creds, **urls; + LDAPControl **reqctrls, **resctrls; + + PASSTHRU_ASSERT( pb != NULL ); + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "=> passthru_bindpreop\n" ); + + /* + * retrieve parameters for bind operation + */ + if ( slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ) != 0 || + slapi_pblock_get( pb, SLAPI_BIND_TARGET, &normbinddn ) != 0 || + slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &creds ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= not handled (unable to retrieve bind parameters)\n" ); + return( PASSTHRU_OP_NOT_HANDLED ); + } + if ( normbinddn == NULL ) { + normbinddn = ""; + } + + /* + * We only handle simple bind requests that include non-NULL binddn and + * credentials. Let the Directory Server itself handle everything else. + */ + if ( method != LDAP_AUTH_SIMPLE || *normbinddn == '\0' + || creds->bv_len == 0 ) { + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= not handled (not simple bind or NULL dn/credentials)\n" ); + return( PASSTHRU_OP_NOT_HANDLED ); + } + + /* + * Get pass through authentication configuration. + */ + cfg = passthru_get_config(); + + /* + * Check to see if the target DN is one we should "pass through" to + * another server. + */ + if ( passthru_dn2server( cfg, normbinddn, &srvr ) != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= not handled (not one of our suffixes)\n" ); + return( PASSTHRU_OP_NOT_HANDLED ); + } + + /* + * We are now committed to handling this bind request. + * Chain it off to another server. + */ + matcheddn = errmsg = libldap_errmsg = pr_errmsg = NULL; + urls = NULL; + resctrls = NULL; + if ( slapi_pblock_get( pb, SLAPI_REQCONTROLS, &reqctrls ) != 0 ) { + rc = LDAP_OPERATIONS_ERROR; + errmsg = "unable to retrieve bind controls"; + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, "%s\n", + errmsg ); + } else { + int lderrno; + + if (( rc = passthru_simple_bind_s( pb, srvr, PASSTHRU_CONN_TRIES, + normbinddn, creds, reqctrls, &lderrno, &matcheddn, + &libldap_errmsg, &urls, &resctrls )) == LDAP_SUCCESS ) { + rc = lderrno; + errmsg = libldap_errmsg; + } else if ( rc != LDAP_USER_CANCELLED ) { /* not abandoned */ + PRErrorCode prerr = PR_GetError(); + pr_errmsg = PR_smprintf( "error %d - %s %s (" + SLAPI_COMPONENT_NAME_NSPR " error %d - %s)", + rc, ldap_err2string( rc ), srvr->ptsrvr_url, + prerr, slapd_pr_strerror(prerr)); + if ( NULL != pr_errmsg ) { + errmsg = pr_errmsg; + } else { + errmsg = ldap_err2string( rc ); + } + rc = LDAP_OPERATIONS_ERROR; + } + } + + /* + * If bind succeeded, change authentication information associated + * with this connection. + */ + if ( rc == LDAP_SUCCESS ) { + char *ndn = slapi_ch_strdup( normbinddn ); + if (slapi_pblock_set(pb, SLAPI_CONN_DN, ndn) != 0 || + slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, + SLAPD_AUTH_SIMPLE) != 0) { + slapi_ch_free((void **)&ndn); + rc = LDAP_OPERATIONS_ERROR; + errmsg = "unable to set connection DN or AUTHTYPE"; + slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, + "%s\n", errmsg ); + } + } + + if ( rc != LDAP_USER_CANCELLED ) { /* not abandoned */ + /* + * Send a result to our client. + */ + if ( resctrls != NULL ) { + (void)slapi_pblock_set( pb, SLAPI_RESCONTROLS, &resctrls ); + } + slapi_send_ldap_result( pb, rc, matcheddn, errmsg, 0, urls ); + } + + /* + * Clean up -- free allocated memory, etc. + */ + if ( urls != NULL ) { + passthru_free_bervals( urls ); + } + if ( libldap_errmsg != NULL ) { + ldap_memfree( errmsg ); + } + if ( pr_errmsg != NULL ) { + PR_smprintf_free( pr_errmsg ); + } + if ( resctrls != NULL ) { + ldap_controls_free( resctrls ); + } + if ( matcheddn != NULL ) { + ldap_memfree( matcheddn ); + } + + slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM, + "<= handled (error %d - %s)\n", rc, ldap_err2string( rc )); + + return( PASSTHRU_OP_HANDLED ); +} diff --git a/ldap/servers/plugins/passthru/ptutil.c b/ldap/servers/plugins/passthru/ptutil.c new file mode 100644 index 00000000..4a1b307b --- /dev/null +++ b/ldap/servers/plugins/passthru/ptutil.c @@ -0,0 +1,111 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * ptutil.c - utility functions for Pass Through Authentication + * + */ + +#include "passthru.h" + + +/* + * Convert a char * array into a struct berval * array. + * Always succeeds. + */ +struct berval ** +passthru_strs2bervals( char **ss ) +{ + int i; + struct berval **bvs; + + if ( ss == NULL || ss[0] == NULL ) { + return( NULL ); + } + + for ( i = 0; ss[i] != NULL; ++i ) { + ; + } + + bvs = (struct berval **)slapi_ch_calloc( i + 1, sizeof( struct berval * )); + for ( i = 0; ss[i] != NULL; ++i ) { + bvs[i] = (struct berval *)slapi_ch_malloc( sizeof( struct berval )); + bvs[i]->bv_val = slapi_ch_strdup( ss[i] ); + bvs[i]->bv_len = strlen( ss[i] ); + } + + return( bvs ); +} + + +/* + * Convert a struct berval * array into a char * array. + * Always succeeds. + */ +char ** +passthru_bervals2strs( struct berval **bvs ) +{ + int i; + char **strs; + + if ( bvs == NULL || bvs[0] == NULL ) { + return( NULL ); + } + + for ( i = 0; bvs[i] != NULL; ++i ) { + ; + } + + strs = (char **)slapi_ch_calloc( i + 1, sizeof( char * )); + for ( i = 0; bvs[i] != NULL; ++i ) { + strs[i] = slapi_ch_strdup( bvs[i]->bv_val ); + } + + return( strs ); +} + + +void +passthru_free_bervals( struct berval **bvs ) +{ + int i; + + if ( bvs != NULL ) { + for ( i = 0; bvs[ i ] != NULL; ++i ) { + slapi_ch_free( (void **)&bvs[ i ] ); + } + } + slapi_ch_free( (void **)&bvs ); +} + + +char * +passthru_urlparse_err2string( int err ) +{ + char *s; + + switch( err ) { + case 0: + s = "no error"; + break; + case LDAP_URL_ERR_NOTLDAP: + s = "missing ldap:// or ldaps://"; + break; + case LDAP_URL_ERR_NODN: + s = "missing suffix"; + break; + case LDAP_URL_ERR_BADSCOPE: + s = "invalid search scope"; + break; + case LDAP_URL_ERR_MEM: + s = "unable to allocate memory"; + break; + case LDAP_URL_ERR_PARAM: + s = "bad parameter to an LDAP URL function"; + break; + } + + return( s ); +} diff --git a/ldap/servers/plugins/presence/Makefile b/ldap/servers/plugins/presence/Makefile new file mode 100644 index 00000000..9b15bc96 --- /dev/null +++ b/ldap/servers/plugins/presence/Makefile @@ -0,0 +1,85 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libpresence +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./presence.def +endif + +PRESENCE_OBJS = presence.o +OBJS = $(addprefix $(OBJDEST)/, $(PRESENCE_OBJS)) + +PRESENCE_DLL = presence-plugin + +INCLUDES += -I../http -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS_DEP += $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +EXTRA_LIBS += $(LDAP_COMMON_LIBS) +PRESENCE_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS_DEP += $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL) +EXTRA_LIBS += $(LDAP_COMMON_LIBS) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS_DEP += $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +EXTRA_LIBS += $(LDAP_COMMON_LIBS) +endif + +PRESENCE= $(addprefix $(LIBDIR)/, $(PRESENCE_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(PRESENCE) + +ifeq ($(ARCH), WINNT) +$(PRESENCE): $(OBJS) $(PRESENCE_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(PRESENCE_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(PRESENCE): $(OBJS) $(PRESENCE_DLL_OBJ) + $(LINK_DLL) $(PRESENCE_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(PRESENCE_DLL_OBJ) +endif + $(RM) $(PRESENCE) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) diff --git a/ldap/servers/plugins/presence/dllmain.c b/ldap/servers/plugins/presence/dllmain.c new file mode 100644 index 00000000..fabf8677 --- /dev/null +++ b/ldap/servers/plugins/presence/dllmain.c @@ -0,0 +1,96 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/presence/images/aim-offline.gif b/ldap/servers/plugins/presence/images/aim-offline.gif Binary files differnew file mode 100644 index 00000000..7403ea29 --- /dev/null +++ b/ldap/servers/plugins/presence/images/aim-offline.gif diff --git a/ldap/servers/plugins/presence/images/aim-online.gif b/ldap/servers/plugins/presence/images/aim-online.gif Binary files differnew file mode 100644 index 00000000..d90c2910 --- /dev/null +++ b/ldap/servers/plugins/presence/images/aim-online.gif diff --git a/ldap/servers/plugins/presence/images/icq-disabled.gif b/ldap/servers/plugins/presence/images/icq-disabled.gif Binary files differnew file mode 100644 index 00000000..78b748cd --- /dev/null +++ b/ldap/servers/plugins/presence/images/icq-disabled.gif diff --git a/ldap/servers/plugins/presence/images/icq-offline.gif b/ldap/servers/plugins/presence/images/icq-offline.gif Binary files differnew file mode 100644 index 00000000..40b35c16 --- /dev/null +++ b/ldap/servers/plugins/presence/images/icq-offline.gif diff --git a/ldap/servers/plugins/presence/images/icq-online.gif b/ldap/servers/plugins/presence/images/icq-online.gif Binary files differnew file mode 100644 index 00000000..bd5452c1 --- /dev/null +++ b/ldap/servers/plugins/presence/images/icq-online.gif diff --git a/ldap/servers/plugins/presence/images/yahoo-offline.gif b/ldap/servers/plugins/presence/images/yahoo-offline.gif Binary files differnew file mode 100644 index 00000000..315a2261 --- /dev/null +++ b/ldap/servers/plugins/presence/images/yahoo-offline.gif diff --git a/ldap/servers/plugins/presence/images/yahoo-online.gif b/ldap/servers/plugins/presence/images/yahoo-online.gif Binary files differnew file mode 100644 index 00000000..1c2b16d8 --- /dev/null +++ b/ldap/servers/plugins/presence/images/yahoo-online.gif diff --git a/ldap/servers/plugins/presence/presence.c b/ldap/servers/plugins/presence/presence.c new file mode 100644 index 00000000..6c75e745 --- /dev/null +++ b/ldap/servers/plugins/presence/presence.c @@ -0,0 +1,1204 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/** + * IM Presence plug-in + */ +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include "portable.h" +#include "nspr.h" +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "dirlite_strings.h" +#include "dirver.h" +#include "vattr_spi.h" +#include "plhash.h" +#include "ldif.h" + +#include "http_client.h" + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +/*** from proto-slap.h ***/ + +int slapd_log_error_proc( char *subsystem, char *fmt, ... ); + +/*** from ldaplog.h ***/ + +/* edited ldaplog.h for LDAPDebug()*/ +#ifndef _LDAPLOG_H +#define _LDAPLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define LDAP_DEBUG_TRACE 0x00001 /* 1 */ +#define LDAP_DEBUG_ANY 0x04000 /* 16384 */ +#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */ + +/* debugging stuff */ +# ifdef _WIN32 + extern int *module_ldap_debug; +# define LDAPDebugLevelIsSet( level ) ( *module_ldap_debug & level ) +# else /* _WIN32 */ + extern int slapd_ldap_debug; +# define LDAPDebugLevelIsSet( level ) ( slapd_ldap_debug & level ) +# endif /* Win32 */ + +#define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ + { \ + if ( LDAPDebugLevelIsSet( level )) { \ + slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ + } \ + } + +#ifdef __cplusplus +} +#endif + +#endif /* _LDAP_H */ + +#define PRESENCE_PLUGIN_SUBSYSTEM "presence-plugin" +#define PRESENCE_PLUGIN_VERSION 0x00050050 + +/** + * this may become unnecessary when we are able to get + * the plug-in DN dynamically (pete?) + */ +#define PRESENCE_DN "cn=Presence,cn=plugins,cn=config" /* temporary */ + +#define PRESENCE_SUCCESS 0 +#define PRESENCE_FAILURE -1 + +/** + * Presence vendor specific config parameters + */ + +#define NS_IM_ID "nsIM-ID" + +#define NS_IM_URL_TEXT "nsIM-URLText" +#define NS_IM_URL_GRAPHIC "nsIM-URLGraphic" + +#define NS_IM_ON_VALUE_MAP_TEXT "nsIM-OnValueMapText" +#define NS_IM_OFF_VALUE_MAP_TEXT "nsIM-OffValueMapText" + +#define NS_IM_ON_VALUE_MAP_GRAPHIC "nsIM-OnValueMapGraphic" +#define NS_IM_OFF_VALUE_MAP_GRAPHIC "nsIM-OffValueMapGraphic" +#define NS_IM_DISABLED_VALUE_MAP_GRAPHIC "nsIM-disabledValueMapGraphic" + +#define NS_IM_REQUEST_METHOD "nsIM-RequestMethod" + +#define NS_IM_URL_TEXT_RETURN_TYPE "nsIM-URLTextReturnType" +#define NS_IM_URL_GRAPHIC_RETURN_TYPE "nsIM-URLGraphicReturnType" + +#define NS_IM_STATUS_TEXT "nsIM-StatusText" +#define NS_IM_STATUS_GRAPHIC "nsIM-StatusGraphic" + +#define PRESENCE_STRING 1 +#define PRESENCE_BINARY 2 + +#define PRESENCE_TEXT_RETURN_TYPE "TEXT" +#define PRESENCE_BINARY_RETURN_TYPE "BINARY" + +#define PRESENCE_REQUEST_METHOD_GET "GET" +#define PRESENCE_REQUEST_METHOD_REDIRECT "REDIRECT" + +#define PRESENCE_RETURNED_ON_TEXT "ONLINE" +#define PRESENCE_RETURNED_OFF_TEXT "OFFLINE" +#define PRESENCE_RETURNED_ERROR_TEXT "ERROR" + +static Slapi_PluginDesc pdesc = { "IM Presence", + PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, + "presence plugin" }; + +/** + * struct used to pass the argument to PL_Enumerator Callback + */ +struct _vattrtypes +{ + Slapi_Entry *entry; + vattr_type_list_context *context; +}; + +/** + * This structure holds the mapping between the virtual attributes and + * the IM IDs. This information is used to find out whether this plugin + * should service the attributes it was asked to. Also, it stores the + * syntax of the attribute. 1 is String and 2 is binary. + */ +struct _vattrmap { + char *imID; + int syntax; +}; +typedef struct _vattrmap _Vmap; + +/** + * struct to store the config values for each presence vendor + */ +struct _defs { + char *textURL; + char *graphicURL; + char *onTextMap; + char *offTextMap; + Slapi_Attr *onGraphicMap; + Slapi_Attr *offGraphicMap; + Slapi_Attr *disabledGraphicMap; + char *requestMethod; + char *textReturnType; + char *graphicReturnType; +}; +typedef struct _defs _ConfigEntry; + +static vattr_sp_handle *_VattrHandle = NULL; +static void *_PluginID = NULL; +static void *_PluginDN = NULL; +static PLHashTable *_IdVattrMapTable = NULL; +static PLHashTable *_IdConfigMapTable = NULL; +static void **_HttpAPI = NULL; + +/** + * + * Presence plug-in management functions + * + */ +int presence_init(Slapi_PBlock *pb); +int presence_start(Slapi_PBlock *pb); +int presence_close(Slapi_PBlock *pb); + +/** + * + * Vattr operation callbacks functions + * + */ +static int presence_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint); +static int presence_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint); +static int presence_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags); + +/** + * + * Local operation functions + * + */ +static int loadPluginConfig(); +static int parseConfigEntry(Slapi_Entry *e); +static int imIDExists(Slapi_Entry *e, char *type, char **value, _Vmap **map, _ConfigEntry **entry); +static int makeHttpRequest(char *id, _Vmap *map, _ConfigEntry *info, char **buf, int *size); +static char * replaceIdWithValue(char *str, char *id, char *value); +static int setIMStatus(char *id, _Vmap *map, _ConfigEntry *info, char *returnedBUF, int size, Slapi_ValueSet **results); +static int setTypes(PLHashEntry *he, PRIntn i, void *arg); + +static void deleteMapTables(); +static PRIntn destroyHashEntry(PLHashEntry *he, PRIntn index, void *arg); +static void logGraphicAttributeValue( Slapi_Attr *attr, const char *attrname ); +static void toLowerCase(char* str); + +/** + * utility function + */ +void printMapTable(); +PRIntn printIdVattrMapTable(PLHashEntry *he, PRIntn i, void *arg); +PRIntn printIdConfigMapTable(PLHashEntry *he, PRIntn i, void *arg); + +/** + * set the debug level + */ +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/** + * + * Get the presence plug-in version + * + */ +int presence_version() +{ + return PRESENCE_PLUGIN_VERSION; +} + +/** + * Plugin identity mgmt + */ +void setPluginID(void * pluginID) +{ + _PluginID=pluginID; +} + +void * getPluginID() +{ + return _PluginID; +} + +void setPluginDN(void *pluginDN) +{ + _PluginDN = pluginDN; +} + +void * getPluginDN() +{ + return _PluginDN; +} + +/* + presence_init + ------------- + adds our callbacks to the list +*/ +int presence_init( Slapi_PBlock *pb ) +{ + int status = PRESENCE_SUCCESS; + char * plugin_identity=NULL; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_init -- BEGIN\n",0,0,0); + + /** + * Store the plugin identity for later use. + * Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + PR_ASSERT (plugin_identity); + setPluginID(plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) presence_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) presence_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "presence_init: failed to register plugin\n" ); + status = PRESENCE_FAILURE; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_init -- END\n",0,0,0); + return status; +} + +/* + presence_start + -------------- + This function registers the computed attribute evaluator + and loads the configuration parameters in the local cache. + It is called after presence_init. +*/ +int presence_start( Slapi_PBlock *pb ) +{ + char * plugindn = NULL; + char * httpRootDir = NULL; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_start -- begin\n",0,0,0); + + if(slapi_apib_get_interface(HTTP_v1_0_GUID, &_HttpAPI)) + { + /** + * error cannot proceeed + */ + return PRESENCE_FAILURE; + } + + /** + * register our vattr callbacks + */ + if (slapi_vattrspi_register((vattr_sp_handle **)&_VattrHandle, + presence_vattr_get, + presence_vattr_compare, + presence_vattr_types) != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "presence_start: cannot register as service provider\n" ); + return PRESENCE_FAILURE; + } + + /** + * Get the plug-in target dn from the system + * and store it for future use. This should avoid + * hardcoding of DN's in the code. + */ + slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn); + if (plugindn == NULL || strlen(plugindn) == 0) + { + /** + * This is not required as the above statement + * should work and give you a valid DN for this + * plugin. ??? remove it later + */ + plugindn = PRESENCE_DN; + } + setPluginDN(plugindn); + + /** + * Load the config info for our plug-in in memory + * In the 6.0 release this information will be stored + * statically and if any change is done to this info a server + * restart is necessary :-(. Probably if time permits then + * state change plug-in would be used to notify the state + * change. We also register the virtual attributes we are + * interested in here. + */ + if (loadPluginConfig() != PRESENCE_SUCCESS) + { + slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "presence_start: unable to load plug-in configuration\n" ); + return PRESENCE_FAILURE; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "presence: ready for service\n",0,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_start -- end\n",0,0,0); + + return PRESENCE_SUCCESS; +} + +/* + presence_close + -------------- + closes down the cache +*/ +int presence_close( Slapi_PBlock *pb ) +{ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_close\n",0,0,0); + + deleteMapTables(); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_close\n",0,0,0); + + return PRESENCE_SUCCESS; +} + +static int presence_vattr_get(vattr_sp_handle *handle, + vattr_context *c, + Slapi_Entry *e, + char *type, + Slapi_ValueSet** results, + int *type_name_disposition, + char** actual_type_name, + int flags, + int *free_flags, + void *hint) +{ + + int status = PRESENCE_SUCCESS; + char *id = NULL; + char *returnedBUF = NULL; + int size = 0; + _Vmap *map = NULL; + _ConfigEntry *info = NULL; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_get \n",0,0,0); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Type=[%s] \n",type,0,0); + + if (imIDExists(e, type, &id, &map, &info) != PRESENCE_SUCCESS) + { + /** + * we didn't find any valid matching nsimid in this + * entry so since we cannot process a request without + * a valid nsimid we just return. + */ + status = PRESENCE_FAILURE; + goto cleanup; + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> ID=[%s] \n",id,0,0); + + /** + * Now since we got a valid id we do a quick schema check + * if schema checking is on to make sure that there is no + * schema violation ? + */ + /* do_schema_check() */ + + /** + * At this stage we have a valid attribute and we have to + * get its value from the IM Server. so make an Http request + * depending on whether it is a request for Text or Graphic + */ + + status = makeHttpRequest(id, map, info, &returnedBUF, &size); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> size=[%d] \n",size,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> buffer=[%s]\n",(returnedBUF) ? returnedBUF : "NULL",0,0); + + + if(status == PRESENCE_SUCCESS) + { + status = setIMStatus(id, map, info, returnedBUF, size, results); + } + else + { + /** + * Report all HTTP failures as a single predefined value of the + * attribute + */ + Slapi_Value *value = + slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT); + if (!*results) { + *results = slapi_valueset_new(); + } + slapi_valueset_add_value(*results, value); + slapi_value_free(&value); /* slapi_valueset_add_value copies value */ + /** + * It's a success only in the sense that we are returning a value + */ + status = PRESENCE_SUCCESS; + } + if(status == PRESENCE_SUCCESS) + { + *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; + *actual_type_name = slapi_ch_strdup(type); + *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; + } + +cleanup: + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Processed ID=[%s] \n",id,0,0); + if (id != NULL ) { + slapi_ch_free((void **)&id); + } + if (returnedBUF != NULL ) { + PR_Free(returnedBUF); + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_get \n",0,0,0); + return status; +} + + +static int presence_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint) +{ + int status = PRESENCE_SUCCESS; + /** + * not yet implemented ??? + */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_compare \n",0,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_compare \n",0,0,0); + + return status; +} + +static int presence_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags) +{ + int status = PRESENCE_SUCCESS; + struct _vattrtypes args; + args.entry = e; + args.context = type_context; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_types\n",0,0,0); + + PL_HashTableEnumerateEntries(_IdVattrMapTable, setTypes, &args); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_types\n",0,0,0); + return status; +} + +static int loadPluginConfig() +{ + int status = PRESENCE_SUCCESS; + int result; + int i; + Slapi_PBlock *search_pb; + Slapi_Entry **entries = NULL; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> loadPluginConfig\n",0,0,0); + + search_pb = slapi_pblock_new(); + + slapi_search_internal_set_pb(search_pb, PRESENCE_DN, LDAP_SCOPE_ONELEVEL, + "objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0); + slapi_search_internal_pb(search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if (status != PRESENCE_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "Error getting level1 presence configurations<%s>\n", getPluginDN()); + status = PRESENCE_FAILURE; + goto cleanup; + } + + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || entries[0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "No entries found for <%s>\n", getPluginDN()); + + status = PRESENCE_FAILURE; + goto cleanup; + } + + _IdVattrMapTable = PL_NewHashTable( 0, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + NULL, + NULL + ); + + _IdConfigMapTable = PL_NewHashTable(0, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + NULL, + NULL + ); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> parseConfigEntry \n",0,0,0); + + for (i = 0; (entries[i] != NULL); i++) + { + status = parseConfigEntry(entries[i]); + if (status != PRESENCE_SUCCESS) + { + deleteMapTables(); + goto cleanup; + } + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- parseConfigEntry \n",0,0,0); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- loadPluginConfig\n",0,0,0); + +cleanup: + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + return status; +} + +static int parseConfigEntry(Slapi_Entry *e) +{ + char *key = NULL; + char *value = NULL; + _ConfigEntry *entry = NULL; + _Vmap *map = NULL; + Slapi_Attr *attr = NULL; + + key = slapi_entry_attr_get_charptr(e, NS_IM_ID); + if (!key) { + /** + * IM Id not defined in the config, unfortunately + * cannot do anything without it so better not to + * load the plug-in. + */ + return PRESENCE_FAILURE; + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> key [%s] \n",key,0,0); + /** + * Now create the config entry which will hold all the + * attributes of a presence vendor + */ + entry = (_ConfigEntry*) slapi_ch_calloc(1, sizeof(_ConfigEntry)); + + /** + * Next 2 are the virtual attributes for which this plug-in + * is responsible. Register them with the vattr system so + * that the system can call us whenever their + * values are requested. Also update these entries in the + * map table for later access. + */ + value = slapi_entry_attr_get_charptr(e, NS_IM_STATUS_TEXT); + if (value) { + slapi_vattrspi_regattr(_VattrHandle, value, "", NULL); + map = (_Vmap*) slapi_ch_calloc(1, sizeof(_Vmap)); + map->imID = key; + map->syntax = PRESENCE_STRING; + toLowerCase(value); + PL_HashTableAdd(_IdVattrMapTable, value, map); + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusText [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_STATUS_GRAPHIC); + if (value) { + slapi_vattrspi_regattr(_VattrHandle, value, "", NULL); + map = (_Vmap*) slapi_ch_calloc(1, sizeof(_Vmap)); + map->imID = key; + map->syntax = PRESENCE_BINARY; + toLowerCase(value); + PL_HashTableAdd(_IdVattrMapTable, value, map); + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusGraphic [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_URL_TEXT); + if (value) { + entry->textURL = value; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLText [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_URL_GRAPHIC); + if (value) { + entry->graphicURL = value; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusGraphic [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_ON_VALUE_MAP_TEXT); + if (value) { + entry->onTextMap = value; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMOnValueMapText [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_OFF_VALUE_MAP_TEXT); + if (value) { + entry->offTextMap = value; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMOffValueMapText [%s] \n",value,0,0); + + /** + * Next 3 are binary syntax types so needs special handling + */ + slapi_entry_attr_find(e, NS_IM_ON_VALUE_MAP_GRAPHIC, &attr); + if (attr) { + entry->onGraphicMap = slapi_attr_dup(attr); + logGraphicAttributeValue(attr,NS_IM_ON_VALUE_MAP_GRAPHIC); + } + + slapi_entry_attr_find(e, NS_IM_OFF_VALUE_MAP_GRAPHIC, &attr); + if (attr) { + entry->offGraphicMap = slapi_attr_dup(attr); + logGraphicAttributeValue(attr,NS_IM_OFF_VALUE_MAP_GRAPHIC); + } + + slapi_entry_attr_find(e, NS_IM_DISABLED_VALUE_MAP_GRAPHIC, &attr); + if (attr) { + entry->disabledGraphicMap = slapi_attr_dup(attr); + logGraphicAttributeValue(attr,NS_IM_DISABLED_VALUE_MAP_GRAPHIC); + } + + value = slapi_entry_attr_get_charptr(e, NS_IM_REQUEST_METHOD); + if (value) { + entry->requestMethod = value; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMRequestMethod [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_URL_TEXT_RETURN_TYPE); + if (value) { + entry->textReturnType = value; + } + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLTextReturnType [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, NS_IM_URL_GRAPHIC_RETURN_TYPE); + if (value) { + entry->graphicReturnType = value; + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLGraphicReturnType [%s] \n",value,0,0); + + /** + * Finally add the entry to the map table + */ + PL_HashTableAdd(_IdConfigMapTable, key, entry); + + return PRESENCE_SUCCESS; +} + +/** + * this function goes thru the valid stored ids + * and return the correct one for which we have to + * do further processing + */ +static int imIDExists(Slapi_Entry *e, char *type, char **value, _Vmap **map, _ConfigEntry **entry) +{ + int status = PRESENCE_SUCCESS; + char *tValue = NULL; + _ConfigEntry *tEntry = NULL; + _Vmap *tMap = NULL; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> imIDExists \n",0,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Type [%s] \n",type,0,0); + + /** + * The public function PL_HashTableLookup modifies the + * the table while reading. so using this private function + * which just does a lookup and doesn't modifies the + * hashtable + */ + toLowerCase(type); + tMap = PL_HashTableLookupConst(_IdVattrMapTable, type); + if (!tMap) + { + /** + * this should not happen but no harm we just return + */ + status = PRESENCE_FAILURE; + slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "No hashtable for vattr types\n"); + goto bail; + } + /** + * We found a matching id in the map table + * now see if that id exists in the Slapi_Entry + */ + tValue = slapi_entry_attr_get_charptr(e, tMap->imID); + if (!tValue) + { + /** + * we don't do anything here but just return + */ + status = PRESENCE_FAILURE; + goto bail; + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Value [%s] \n",tValue,0,0); + + tEntry = PL_HashTableLookupConst(_IdConfigMapTable, tMap->imID); + *value = tValue; + *entry = tEntry; + *map = tMap; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- imIDExists \n",0,0,0); + +bail: + return status; +} + +static int makeHttpRequest(char *id, _Vmap *map, _ConfigEntry *info, char **BUF, int *size) +{ + int status = PRESENCE_SUCCESS; + char *buf = NULL; + char *url = NULL; + char *urltosend = NULL; + int bytesRead; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> makeHttpRequest:: \n",0,0,0); + + if (map->syntax == PRESENCE_STRING) { + url = info->textURL; + } else { + url = info->graphicURL; + } + if (url == NULL) { + status = PRESENCE_FAILURE; + goto bail; + } + urltosend = replaceIdWithValue(url, map->imID, id); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> URL [%s] \n",urltosend,0,0); + /** + * make an actual HTTP call now + */ + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> RequestMethod [%s] \n", info->requestMethod,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Syntax [%d] \n", map->syntax,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> TextReturnType [%s] \n", info->textReturnType,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> GraphicReturnType [%s] \n", info->graphicReturnType,0,0); + if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_GET)) { + if (map->syntax == PRESENCE_STRING) { + if (!strcasecmp(info->textReturnType, PRESENCE_TEXT_RETURN_TYPE)) { + status = http_get_text(_HttpAPI, urltosend, &buf, &bytesRead); + } else { + status = http_get_binary(_HttpAPI, urltosend, &buf, &bytesRead); + } + } else { + if (!strcasecmp(info->graphicReturnType, PRESENCE_TEXT_RETURN_TYPE)) { + status = http_get_text(_HttpAPI, urltosend, &buf, &bytesRead); + } else { + status = http_get_binary(_HttpAPI, urltosend, &buf, &bytesRead); + } + } + } else if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_REDIRECT)) { + status = http_get_redirected_uri(_HttpAPI, urltosend, &buf, &bytesRead); + } else { + /** + * error : unknown method + * probably we should check at the time of loading + * of the plugin itself that the config values are + * properly checked and throw warning/errors in case + * of any invalid entry + */ + slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM, + "Unknown request type <%s>\n", info->requestMethod); + status = PRESENCE_FAILURE; + goto bail; + } + if (buf && status == PRESENCE_SUCCESS) + { + *BUF = buf; + *size = bytesRead; + } + +bail: + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- makeHttpRequest:: <%d>\n",status,0,0); + + slapi_ch_free((void**)&urltosend); + return status; +} + +/** + * This function replaces the occurrence of $ns[<vendor>]imid with its + * actual value + * e.g. + * URL : http://opi.yahoo.com/online?u=$nsyimid + * after replacing + * newURL : http://opi.yahoo.com/online?u=srajam + */ +static char * replaceIdWithValue(char *str, char *id, char *value) +{ + int i=0; + int k=0; + char *newstr = NULL; + char c; + if (!str || !id || !value) + { + return NULL; + } + /* extra space for userids */ + newstr = (char *)slapi_ch_malloc(strlen(str) + strlen(value)); + while ((c=str[i]) != '\0') + { + if (c == '$') + { + int j = 0; + i++; /*skip one char */ + /** + * we found the begining of the string to be + * substituted. Now skip the chars we want to replace + */ + while (str[i] != '\0' && id[j] != '\0' && + (toupper(str[i]) == toupper(id[j]))) + { + i++; + j++; + } + j=0; + while (value[j] != '\0') + { + newstr[k++] = value[j++]; + } + } + else + { + newstr[k++]=c; + i++; + } + } + + newstr[k] = '\0'; + return newstr; +} + +static int setIMStatus(char *id, _Vmap *map, _ConfigEntry *info, + char *returnedBUF, int size, Slapi_ValueSet **results) +{ + int status = PRESENCE_SUCCESS; + char *ontxtmap = NULL; + char *offtxtmap = NULL; + Slapi_Value *value = NULL; + Slapi_Value *value1 = NULL; + Slapi_Value *value2 = NULL; + struct berval bval; + Slapi_Attr *attr = NULL; + const struct berval *tmp = NULL; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> setIMStatus \n", 0,0,0); + /** + * we got some data back so lets try to map it to + * the existing set of on/off data + * + * first we need to take a look at the + * returned type and depending upon that parse + * the data + */ + + if (map->syntax == PRESENCE_STRING) { + /** + * we had send a request for text + * but chances are we might end up + * getting an image back. So we need + * to compare it to existing set of + * images that we have in store ??? + */ + if (!strcasecmp(info->textReturnType, PRESENCE_TEXT_RETURN_TYPE)) { + /* return value is in text format */ + ontxtmap = replaceIdWithValue(info->onTextMap, map->imID, id); + offtxtmap = replaceIdWithValue(info->offTextMap, map->imID, id); + if (!strcasecmp(ontxtmap, returnedBUF)) { + /** + * set the on value + */ + value = slapi_value_new_string(PRESENCE_RETURNED_ON_TEXT); + } else if (!strcasecmp(offtxtmap, returnedBUF)) { + /** + * set the off value + */ + value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT); + } else { + value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT); + } + } else if (!strcasecmp(info->textReturnType, PRESENCE_BINARY_RETURN_TYPE)) { + /** + * call binary compare method + */ + bval.bv_len = size; + bval.bv_val = returnedBUF; + value1 = slapi_value_new_berval(&bval); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> returned size [%d] \n", bval.bv_len,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> returned value [%s] \n", bval.bv_val,0,0); + + attr = info->onGraphicMap; + if (attr) { + slapi_attr_first_value(attr, &value2); + tmp = slapi_value_get_berval(value2); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Stored size [%d] \n", tmp->bv_len,0,0); + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Stored value [%s] \n", tmp->bv_val,0,0); + if (!slapi_value_compare(attr, value1, value2)) { + value = slapi_value_new_string(PRESENCE_RETURNED_ON_TEXT); + } + } + if (!value) { + attr = info->offGraphicMap; + if (attr) { + slapi_attr_first_value(attr, &value2); + if (!slapi_value_compare(attr, value1, value2)) { + value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT); + } + } + } + if (!value) { + attr = info->disabledGraphicMap; + if (attr) { + slapi_attr_first_value(attr, &value2); + if (!slapi_value_compare(attr, value1, value2)) { + value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT); + } + } + } + if (!value) { + /* some error */ + value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT); + } + } else { + /** + * set the error condition + */ + value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT); + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0); + } else { + /** + * we had send a request for image + * so whatever we get back we just + * return instead of analyzing it + */ + if (!strcasecmp(info->graphicReturnType, PRESENCE_TEXT_RETURN_TYPE)) { + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0); + if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_REDIRECT)) { + /** + * a redirect case in which we should probably have a + * gif in store so return that value + * + * for now + */ + + ontxtmap = replaceIdWithValue(info->onTextMap, map->imID, id); + offtxtmap = replaceIdWithValue(info->offTextMap, map->imID, id); + if (!strcasecmp(ontxtmap, returnedBUF)) { + /** + * set the on value + */ + attr = info->onGraphicMap; + } else if (!strcasecmp(offtxtmap, returnedBUF)) { + /** + * set the off value + */ + attr = info->offGraphicMap; + } else { + attr = info->disabledGraphicMap; + } + if (attr) { + slapi_attr_first_value(attr, &value); + } + } else { + /** + * for now just set the returned value + * should not happen in our case + * ERROR + */ + } + } else { + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0); + bval.bv_len = size; + bval.bv_val = returnedBUF; + value = slapi_value_new_berval(&bval); + } + } + if (!*results) { + *results = slapi_valueset_new(); + } + + slapi_valueset_add_value(*results, value); + + if (ontxtmap) { + slapi_ch_free((void **)&ontxtmap); + } + if (offtxtmap) { + slapi_ch_free((void **)&offtxtmap); + } + if (value && map->syntax == PRESENCE_STRING) { + slapi_value_free(&value); + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- setIMStatus \n", 0,0,0); + + return status; +} + +static int setTypes(PLHashEntry *he, PRIntn i, void *arg) +{ + int status; + int props = SLAPI_ATTR_FLAG_OPATTR; + Slapi_Attr *attr = NULL; + Slapi_ValueSet *results = NULL; + int type_name_disposition = 0; + char *actual_type_name = 0; + int free_flags = 0; + + struct _vattrtypes *args = arg; + char *type = (char *)he->key; + _Vmap *map = (_Vmap *)he->value; + char *id = map->imID; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "--> setTypes \n", 0,0,0); + + status = slapi_vattr_values_get_sp(NULL, args->entry, id, &results, &type_name_disposition, &actual_type_name, 0, &free_flags); + if(status == PRESENCE_SUCCESS) + { + /* entry contains this attr */ + vattr_type_thang thang = {0}; + + thang.type_name = type; + thang.type_flags = props; + + slapi_vattrspi_add_type(args->context,&thang,0); + + slapi_vattr_values_free(&results, &actual_type_name, free_flags); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> ID [%s] Type[%s]\n", actual_type_name,type,0); + } + LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- setTypes \n", 0,0,0); + + return HT_ENUMERATE_NEXT; +} + + +static void +logGraphicAttributeValue( Slapi_Attr *attr, const char *attrname ) +{ + Slapi_Value *val = NULL; + const struct berval *v = NULL; + + if ( LDAPDebugLevelIsSet( LDAP_DEBUG_PLUGIN )) { + slapi_attr_first_value(attr, &val); + v = slapi_value_get_berval(val); + if (v) { + char *ldifvalue; + size_t attrnamelen = strlen( attrname ); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> %s size [%d] \n", + attrname,v->bv_len,0); + + ldifvalue = ldif_type_and_value_with_options( + (char *)attrname, /* XXX: had to cast away const */ + v->bv_val, v->bv_len, 0 ); + if ( NULL != ldifvalue ) { + LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> %s value [\n%s]\n", + attrname,ldifvalue,0); + slapi_ch_free_string( &ldifvalue ); + } + } + } +} + + +static void deleteMapTables() +{ + PL_HashTableEnumerateEntries(_IdConfigMapTable, destroyHashEntry, 0); + if (_IdConfigMapTable) + { + PL_HashTableDestroy(_IdConfigMapTable); + } + + PL_HashTableEnumerateEntries(_IdVattrMapTable, destroyHashEntry, 0); + if (_IdVattrMapTable) + { + PL_HashTableDestroy(_IdVattrMapTable); + } + return; +} + +static PRIntn destroyHashEntry(PLHashEntry *he, PRIntn index, void *arg) +{ + void *value = NULL; + if (he == NULL) + { + return HT_ENUMERATE_NEXT; + } + value = he->value; + if (value) + { + slapi_ch_free(&value); + } + return HT_ENUMERATE_REMOVE; +} + +static void toLowerCase(char* str) +{ + if (str) { + char* lstr = str; + for(; (*lstr != '\0'); ++lstr) { + *lstr = tolower(*lstr); + } + } +} + + +/** + * utility function to print the array + */ +void printMapTable() +{ + PL_HashTableEnumerateEntries(_IdVattrMapTable, printIdVattrMapTable, 0); + PL_HashTableEnumerateEntries(_IdConfigMapTable, printIdConfigMapTable, 0); +} + +PRIntn printIdVattrMapTable(PLHashEntry *he, PRIntn i, void *arg) +{ + char *key = (char *)he->key; + _Vmap *map = (_Vmap *)he->value; + printf("<---- Key -------> %s\n", key); + printf("<---- ImId ------> %s\n", map->imID); + printf("<---- syntax ----> %d\n", map->syntax); + return HT_ENUMERATE_NEXT; +} + +PRIntn printIdConfigMapTable(PLHashEntry *he, PRIntn i, void *arg) +{ + char *key = (char *)he->key; + _ConfigEntry *value = (_ConfigEntry *)he->value; + printf("<- Key ---------------------> %s\n", key); + printf("<---- text_url -------------> %s\n", value->textURL); + printf("<---- graphic_url ----------> %s\n", value->graphicURL); + printf("<---- on_text_map ----------> %s\n", value->onTextMap); + printf("<---- off_text_map ---------> %s\n", value->offTextMap); + printf("<---- request_method -------> %s\n", value->requestMethod); + printf("<---- text_return_type -----> %s\n", value->textReturnType); + printf("<---- graphic_return_type --> %s\n", value->graphicReturnType); + + return HT_ENUMERATE_NEXT; +} + diff --git a/ldap/servers/plugins/presence/presence.def b/ldap/servers/plugins/presence/presence.def new file mode 100644 index 00000000..4d083d26 --- /dev/null +++ b/ldap/servers/plugins/presence/presence.def @@ -0,0 +1,11 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 6.2.1 Presence Plugin' +EXPORTS + presence_init @2 + plugin_init_debug_level @3 + presence_version @4 diff --git a/ldap/servers/plugins/presence/presence.ldif b/ldap/servers/plugins/presence/presence.ldif new file mode 100644 index 00000000..67bb977b --- /dev/null +++ b/ldap/servers/plugins/presence/presence.ldif @@ -0,0 +1,44 @@ +dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-onvaluemapgraphic +nsim-onvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/icq-online.gif + +dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-offvaluemapgraphic +nsim-offvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/icq-offline.gif + +dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-disabledvaluemapgraphic +nsim-disabledvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/icq-disabled.gif + +dn:cn=AIM Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-onvaluemapgraphic +nsim-onvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/aim-online.gif + +dn:cn=AIM Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-offvaluemapgraphic +nsim-offvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/aim-offline.gif + +dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-disabledvaluemapgraphic +nsim-disabledvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/aim-offline.gif + +dn:cn=Yahoo Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-offvaluemapgraphic +nsim-offvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/yahoo-offline.gif + +dn:cn=Yahoo Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-onvaluemapgraphic +nsim-onvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/yahoo-online.gif + +dn:cn=Yahoo Presence,cn=Presence,cn=plugins,cn=config +changeType:modify +replace:nsim-disabledvaluemapgraphic +nsim-disabledvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/yahoo-offline.gif diff --git a/ldap/servers/plugins/pwdstorage/Makefile b/ldap/servers/plugins/pwdstorage/Makefile new file mode 100644 index 00000000..efad0788 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/Makefile @@ -0,0 +1,115 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server password_storaged-plugin.so password storage scheme plugins +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libpwdstorage +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libpwdstorage.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd + +PWD_OBJS= \ + pwd_init.o \ + clear_pwd.o \ + crypt_pwd.o \ + ns-mta-md5_pwd.o \ + sha_pwd.o \ + ssha_pwd.o \ + md5c.o + + +OBJS = $(addprefix $(OBJDEST)/, $(PWD_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBPWD_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +LIBPWD = $(addprefix $(LIBDIR)/, $(PWD_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += \ + $(LIBSLAPD_DEP) \ + $(LDAP_LIBUTIL_DEP) \ + $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += \ + $(LDAPSDK_DEP) \ + $(SECURITY_DEP) +EXTRA_LIBS += \ + $(LIBSLAPD) \ + $(LDAP_SDK_LIBLDAP_DLL) \ + $(LIBUTIL) \ + $(NSPRLINK) \ + $(LDAP_COMMON_LIBS) \ + $(SECURITYLINK) +endif +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += \ + $(LIBSLAPD_DEP) \ + $(LDAP_LIBUTIL_DEP) \ + $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += \ + $(LDAPSDK_DEP) \ + $(SECURITY_DEP) +EXTRA_LIBS += \ + $(LIBSLAPDLINK) \ + $(LDAP_SDK_LIBLDAP_DLL) \ + $(LIBUTIL) \ + $(NSPRLINK) \ + $(LDAP_COMMON_LIBS) \ + $(SECURITYLINK) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libpwdstorage.def" +CFLAGS+= /WX +endif # WINNT + +ifeq ($(ARCH), AIX) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBPWD) + +$(LIBPWD): $(OBJS) $(LIBPWD_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBPWD_DLL_OBJ) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBPWD_DLL_OBJ) +endif + $(RM) $(LIBPWD) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/pwdstorage/clear_pwd.c b/ldap/servers/plugins/pwdstorage/clear_pwd.c new file mode 100644 index 00000000..4b2a3ca5 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/clear_pwd.c @@ -0,0 +1,27 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * slapd hashed password routines + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "pwdstorage.h" + +int +clear_pw_cmp( char *userpwd, char *dbpwd ) +{ + return( strcmp( userpwd, dbpwd )); +} + +char * +clear_pw_enc( char *pwd ) +{ + return( slapi_ch_strdup( pwd )); +} diff --git a/ldap/servers/plugins/pwdstorage/crypt_pwd.c b/ldap/servers/plugins/pwdstorage/crypt_pwd.c new file mode 100644 index 00000000..df179ef6 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/crypt_pwd.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * slapd hashed password routines + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#ifdef _WIN32 +char *crypt(char *key, char *salt); +#else +#include <sys/socket.h> +#if defined( hpux ) || defined ( AIX ) || defined (LINUX) || defined (OSF1) +#define __USE_XOPEN /* linux */ +#include <unistd.h> +#else /* hpux */ +#include <crypt.h> +#endif /* hpux */ +#endif /* _WIN32 */ + +#include "pwdstorage.h" + +static PRLock *cryptlock; /* Some implementations of crypt are not thread safe. ie. ours & Irix */ + +/* characters used in crypt encoding */ +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + + +void +crypt_init() +{ + cryptlock = PR_NewLock(); +} + +int +crypt_pw_cmp( char *userpwd, char *dbpwd ) +{ + int rc; + char *cp; + PR_Lock(cryptlock); + /* we use salt (first 2 chars) of encoded password in call to crypt() */ + cp = crypt( userpwd, dbpwd ); + if (cp) { + rc= strcmp( dbpwd, cp); + } else { + rc = -1; + } + PR_Unlock(cryptlock); + return rc; +} + +char * +crypt_pw_enc( char *pwd ) +{ + char *cry, salt[3]; + char *enc= NULL; + long v; + static unsigned int seed = 0; + + if ( seed == 0) + { + seed = (unsigned int)slapi_rand(); + } + v = slapi_rand_r(&seed); + + salt[0] = itoa64[v & 0x3f]; + v >>= 6; + salt[1] = itoa64[v & 0x3f]; + salt[2] = '\0'; + + PR_Lock(cryptlock); + cry = crypt( pwd, salt ); + if ( cry != NULL ) + { + enc = slapi_ch_malloc( 3 + CRYPT_NAME_LEN + strlen( cry )); + if ( enc != NULL ) + { + sprintf( enc, "%c%s%c%s", PWD_HASH_PREFIX_START, CRYPT_SCHEME_NAME, PWD_HASH_PREFIX_END, cry ); + } + } + PR_Unlock(cryptlock); + return( enc ); +} + diff --git a/ldap/servers/plugins/pwdstorage/dllmain.c b/ldap/servers/plugins/pwdstorage/dllmain.c new file mode 100644 index 00000000..71530805 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/dllmain.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + /* + * Microsoft Windows specifics for LIBPWDSTORAGE DLL + */ +#include "ldap.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/pwdstorage/libpwdstorage.def b/ldap/servers/plugins/pwdstorage/libpwdstorage.def new file mode 100644 index 00000000..e19305d5 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/libpwdstorage.def @@ -0,0 +1,24 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7 password storage scheme Plugin' +EXPORTS + sha_pwd_storage_scheme_init @2 + ssha_pwd_storage_scheme_init @3 + crypt_pwd_storage_scheme_init @4 + clear_pwd_storage_scheme_init @5 + ns_mta_md5_pwd_storage_scheme_init @6 + clear_pw_cmp @7 + crypt_pw_cmp @8 + ns_mta_md5_pw_cmp @9 + sha1_pw_cmp @10 + sha1_pw_enc @11 + salted_sha1_pw_enc @12 + crypt_pw_enc @13 + clear_pw_enc @14 + mta_MD5Init @15 + mta_MD5Update @16 + mta_MD5Final @17 diff --git a/ldap/servers/plugins/pwdstorage/md5.h b/ldap/servers/plugins/pwdstorage/md5.h new file mode 100644 index 00000000..6f7ec036 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/md5.h @@ -0,0 +1,63 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * MD5 algorithm used by Netscape Mail Server + */ + +/* MD5 code taken from reference implementation published in RFC 1321 */ + +#ifndef _RFC1321_MD5_H_ +#define _RFC1321_MD5_H_ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +#include "nspr.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef unsigned char * POINTER; +typedef PRUint16 UINT2; +typedef PRUint32 UINT4; + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} mta_MD5_CTX; + +void mta_MD5Init (mta_MD5_CTX *); +void mta_MD5Update (mta_MD5_CTX *, const unsigned char *, unsigned int); +void mta_MD5Final (unsigned char [16], mta_MD5_CTX *); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* end of _RFC1321_MD5_H_ */ + diff --git a/ldap/servers/plugins/pwdstorage/md5c.c b/ldap/servers/plugins/pwdstorage/md5c.c new file mode 100644 index 00000000..d78b772c --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/md5c.c @@ -0,0 +1,337 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* MD5 code taken from reference implementation published in RFC 1321 */ + +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +#include "md5.h" + +/* Constants for MD5Transform routine. */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform (UINT4 [4], const unsigned char [64]); +static void Encode (unsigned char *, const UINT4 *, unsigned int); +static void Decode (UINT4 *, const unsigned char *, unsigned int); +static void MD5_memcpy (POINTER, const POINTER, unsigned int); +static void MD5_memset (POINTER, int, unsigned int); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~(x)) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~(z)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~(z)))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void mta_MD5Init (context) +mta_MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void mta_MD5Update (context, input, inputLen) +mta_MD5_CTX *context; /* context */ +const unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void mta_MD5Final (digest, context) +unsigned char digest[16]; /* message digest */ +mta_MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + mta_MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + mta_MD5Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +UINT4 state[4]; +const unsigned char block[64]; +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +const UINT4 *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +UINT4 *output; +const unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (output, input, len) +POINTER output; +const POINTER input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (output, value, len) +POINTER output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} diff --git a/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu new file mode 100644 index 00000000..7cdd74b3 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu @@ -0,0 +1,405 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * slapd hashed password routines + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "pwd.h" + + +/* + * Netscape Mail Server MD5 support (compare-only; no support for encoding) + */ + +static char * ns_mta_hextab = "0123456789abcdef"; + +static void +ns_mta_hexify(char *buffer, char *str, int len) +{ + char *pch = str; + char ch; + int i; + + for(i = 0;i < len; i ++) { + ch = pch[i]; + buffer[2*i] = ns_mta_hextab[(ch>>4)&15]; + buffer[2*i+1] = ns_mta_hextab[ch&15]; + } + + return; +} + +static char * +ns_mta_hash_alg(char *buffer, char *salt, char *passwd) +{ + mta_MD5_CTX context; + char saltstr[2048]; + unsigned char digest[16]; + + sprintf(saltstr,"%s%c%s%c%s",salt,89,passwd,247,salt); + + mta_MD5Init(&context); + mta_MD5Update(&context,(unsigned char *)saltstr,strlen(saltstr)); + mta_MD5Final(digest,&context); + ns_mta_hexify(buffer,(char*)digest,16); + buffer[32] = '\0'; + return(buffer); + +} + +int +ns_mta_md5_pw_cmp(char * clear, char *mangled) +{ + char mta_hash[33]; + char mta_salt[33]; + char buffer[65]; + + strncpy(mta_hash,mangled,32); + strncpy(mta_salt,&mangled[32],32); + + mta_hash[32] = mta_salt[32] = 0; + + return( strcmp(mta_hash,ns_mta_hash_alg(buffer,mta_salt,clear))); +} + + +/* MD5 code taken from reference implementation published in RFC 1321 */ + +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +#include "pw.h" + +/* Constants for MD5Transform routine. */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform (UINT4 [4], const unsigned char [64]); +static void Encode (unsigned char *, const UINT4 *, unsigned int); +static void Decode (UINT4 *, const unsigned char *, unsigned int); +static void MD5_memcpy (POINTER, const POINTER, unsigned int); +static void MD5_memset (POINTER, int, unsigned int); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~(x)) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~(z)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~(z)))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void mta_MD5Init (context) +mta_MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void mta_MD5Update (context, input, inputLen) +mta_MD5_CTX *context; /* context */ +const unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void mta_MD5Final (digest, context) +unsigned char digest[16]; /* message digest */ +mta_MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + mta_MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + mta_MD5Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +UINT4 state[4]; +const unsigned char block[64]; +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +const UINT4 *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +UINT4 *output; +const unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (output, input, len) +POINTER output; +const POINTER input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (output, value, len) +POINTER output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} + diff --git a/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c new file mode 100644 index 00000000..f3c11a10 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c @@ -0,0 +1,81 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * slapd hashed password routines + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "pwdstorage.h" + +#include "md5.h" /* JCM - This is a core server header... These functions could be made part of the slapi API. */ + + +/* + * Netscape Mail Server MD5 support (compare-only; no support for encoding) + */ + +static char * ns_mta_hextab = "0123456789abcdef"; + +static void +ns_mta_hexify(char *buffer, char *str, int len) +{ + char *pch = str; + char ch; + int i; + + for(i = 0;i < len; i ++) { + ch = pch[i]; + buffer[2*i] = ns_mta_hextab[(ch>>4)&15]; + buffer[2*i+1] = ns_mta_hextab[ch&15]; + } + + return; +} + +static char * +ns_mta_hash_alg(char *buffer, char *salt, char *passwd) +{ + mta_MD5_CTX context; + char *saltstr; + unsigned char digest[16]; + + + if ( (saltstr = slapi_ch_malloc(strlen(salt)*2 + strlen(passwd) + 3)) + == NULL ) { + return( NULL ); + } + + sprintf(saltstr,"%s%c%s%c%s",salt,89,passwd,247,salt); + + mta_MD5Init(&context); + mta_MD5Update(&context,(unsigned char *)saltstr,strlen(saltstr)); + mta_MD5Final(digest,&context); + ns_mta_hexify(buffer,(char*)digest,16); + buffer[32] = '\0'; + slapi_ch_free((void**)&saltstr); + return(buffer); + +} + +int +ns_mta_md5_pw_cmp(char * clear, char *mangled) +{ + char mta_hash[33]; + char mta_salt[33]; + char buffer[65]; + + strncpy(mta_hash,mangled,32); + strncpy(mta_salt,&mangled[32],32); + + mta_hash[32] = mta_salt[32] = 0; + + return( strcmp(mta_hash,ns_mta_hash_alg(buffer,mta_salt,clear))); +} + diff --git a/ldap/servers/plugins/pwdstorage/pwd_init.c b/ldap/servers/plugins/pwdstorage/pwd_init.c new file mode 100644 index 00000000..4ee5138b --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/pwd_init.c @@ -0,0 +1,146 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "pwdstorage.h" +#include "dirver.h" + +static Slapi_PluginDesc sha_pdesc = { "sha-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Secure Hashing Algorithm (SHA)" }; + +static Slapi_PluginDesc ssha_pdesc = { "ssha-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Salted Secure Hashing Algorithm (SSHA)" }; + +static Slapi_PluginDesc crypt_pdesc = { "crypt-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Unix crypt algorithm (CRYPT)" }; + +static Slapi_PluginDesc clear_pdesc = { "clear-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "No encryption (CLEAR)" }; + +static Slapi_PluginDesc ns_mta_md5_pdesc = { "NS-MTA-MD5-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Netscape MD5 (NS-MTA-MD5)" }; + +static char *plugin_name = "NSPwdStoragePlugin"; + +int +sha_pwd_storage_scheme_init( Slapi_PBlock *pb ) +{ + int rc; + char *name; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> sha_pwd_storage_scheme_init\n" ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&sha_pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, + (void *) sha1_pw_enc); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, + (void *) sha1_pw_cmp ); + name = slapi_ch_strdup("SHA"); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, + name ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= sha_pwd_storage_scheme_init %d\n\n", rc ); + + return( rc ); +} + +int +ssha_pwd_storage_scheme_init( Slapi_PBlock *pb ) +{ + int rc; + char *name; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> ssha_pwd_storage_scheme_init\n" ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&ssha_pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, + (void *) salted_sha1_pw_enc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, + (void *) sha1_pw_cmp ); + name = slapi_ch_strdup("SSHA"); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, + name ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= ssha_pwd_storage_scheme_init %d\n\n", rc ); + return( rc ); +} + +int +crypt_pwd_storage_scheme_init( Slapi_PBlock *pb ) +{ + int rc; + char *name; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> crypt_pwd_storage_scheme_init\n" ); + + crypt_init(); + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&crypt_pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, + (void *) crypt_pw_enc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, + (void *) crypt_pw_cmp ); + name = slapi_ch_strdup("CRYPT"); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, + name ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= crypt_pwd_storage_scheme_init %d\n\n", rc ); + return( rc ); +} + +int +clear_pwd_storage_scheme_init( Slapi_PBlock *pb ) +{ + int rc; + char *name; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> clear_pwd_storage_scheme_init\n" ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&clear_pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, + (void *) clear_pw_enc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, + (void *) clear_pw_cmp ); + name = slapi_ch_strdup("CLEAR"); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, + name ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= clear_pwd_storage_scheme_init %d\n\n", rc ); + return( rc ); +} + +int +ns_mta_md5_pwd_storage_scheme_init( Slapi_PBlock *pb ) +{ + int rc; + char *name; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> ns_mta_md5_pwd_storage_scheme_init\n" ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&ns_mta_md5_pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, + (void *) NULL ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, + (void *) ns_mta_md5_pw_cmp ); + name = slapi_ch_strdup("NS-MTA-MD5"); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, + name ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= ns_mta_md5_pwd_storage_scheme_init %d\n\n", rc ); + return( rc ); +} diff --git a/ldap/servers/plugins/pwdstorage/pwdstorage.h b/ldap/servers/plugins/pwdstorage/pwdstorage.h new file mode 100644 index 00000000..0e938cb9 --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/pwdstorage.h @@ -0,0 +1,99 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#ifndef _PWDSTORAGE_H +#define _PWDSTORAGE_H + +#include "slapi-plugin.h" +#include <ssl.h> +#include "nspr.h" +#include "ldif.h" +#include "md5.h" + +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +#define PWD_HASH_PREFIX_START '{' +#define PWD_HASH_PREFIX_END '}' + +#define SHA1_SCHEME_NAME "SHA" +#define SHA1_NAME_LEN 3 +#define SALTED_SHA1_SCHEME_NAME "SSHA" +#define SALTED_SHA1_NAME_LEN 4 +#define CRYPT_SCHEME_NAME "crypt" +#define CRYPT_NAME_LEN 5 +#define NS_MTA_MD5_SCHEME_NAME "NS-MTA-MD5" +#define NS_MTA_MD5_NAME_LEN 10 +#define CLEARTEXT_SCHEME_NAME "clear" +#define CLEARTEXT_NAME_LEN 5 + +SECStatus sha1_salted_hash(unsigned char *hash_out, char *pwd, struct berval *salt); +int sha1_pw_cmp( char *userpwd, char *dbpwd ); +char * sha1_pw_enc( char *pwd ); +char * salted_sha1_pw_enc( char *pwd ); +int clear_pw_cmp( char *userpwd, char *dbpwd ); +char *clear_pw_enc( char *pwd ); +void crypt_init(); +int crypt_pw_cmp( char *userpwd, char *dbpwd ); +char *crypt_pw_enc( char *pwd ); +int ns_mta_md5_pw_cmp( char *userpwd, char *dbpwd ); + + +#if !defined(NET_SSL) +/******************************************/ +/* + * Some of the stuff below depends on a definition for uint32, so + * we include one here. Other definitions appear in nspr/prtypes.h, + * at least. All the platforms we support use 32-bit ints. + */ +typedef unsigned int uint32; + + +/******************************************/ +/* + * The following is from ds.h, which the libsec sec.h stuff depends on (see + * comment below). + */ +/* +** A status code. Status's are used by procedures that return status +** values. Again the motivation is so that a compiler can generate +** warnings when return values are wrong. Correct testing of status codes: +** +** DSStatus rv; +** rv = some_function (some_argument); +** if (rv != DSSuccess) +** do_an_error_thing(); +** +*/ +typedef enum DSStatusEnum { + DSWouldBlock = -2, + DSFailure = -1, + DSSuccess = 0 +} DSStatus; + + +/******************************************/ +/* + * All of the SHA1-related defines are from libsec's "sec.h" -- including + * it directly pulls in way too much stuff that we conflict with. Ugh. + */ + +/* + * Number of bytes each hash algorithm produces + */ +#define SHA1_LENGTH 20 + +/******************************************/ +/* +** SHA-1 secure hash function +*/ + +/* +** Hash a null terminated string "src" into "dest" using SHA-1 +*/ +DSStatus SHA1_Hash(unsigned char *dest, char *src); + +#endif /* !defined(NET_SSL) */ + +#endif /* _PWDSTORAGE_H */ diff --git a/ldap/servers/plugins/pwdstorage/sha_pwd.c b/ldap/servers/plugins/pwdstorage/sha_pwd.c new file mode 100644 index 00000000..c8cf435d --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/sha_pwd.c @@ -0,0 +1,111 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * slapd hashed password routines + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "pwdstorage.h" + +#if defined(NET_SSL) +#include <sechash.h> +#endif /* NET_SSL */ + +#define SHA1_SALT_LENGTH 8 /* number of bytes of data in salt */ +#define NOT_FIRST_TIME (time_t)1 /* not the first logon */ + +static char *hasherrmsg = "pw_cmp: %s userPassword \"%s\" is the wrong length or is not properly encoded BASE64\n"; + +static char *plugin_name = "NSPwdStoragePlugin"; + +#define DS40B1_SALTED_SHA_LENGTH 18 +/* Directory Server 4.0 Beta 1 implemented a scheme that stored + * 8 bytes of salt plus the first 10 bytes of the SHA-1 digest. + * It's obsolescent now, but we still handle such stored values. + */ + +int +sha1_pw_cmp (char *userpwd, char *dbpwd ) +{ + /* + * SHA1 passwords are stored in the database as SHA1_LENGTH bytes of + * hash, followed by zero or more bytes of salt, all BASE64 encoded. + */ + int result = 1; /* failure */ + unsigned char userhash[SHA1_LENGTH]; + unsigned char quick_dbhash[SHA1_LENGTH + SHA1_SALT_LENGTH + 3]; + unsigned char *dbhash = quick_dbhash; + struct berval salt; + int hash_len; /* must be a signed valued -- see below */ + + /* + * Decode hash stored in database. + * + * Note that ldif_base64_decode() returns a value less than zero to + * indicate that a decoding error occurred, so it is critical that + * hash_len be a signed value. + */ + hash_len = (((strlen(dbpwd) + 3) / 4) * 3); /* maybe less */ + if ( hash_len > sizeof(quick_dbhash) ) { /* get more space: */ + dbhash = (unsigned char*) slapi_ch_malloc( hash_len ); + if ( dbhash == NULL ) goto loser; + } + hash_len = ldif_base64_decode( dbpwd, dbhash ); + if ( hash_len >= SHA1_LENGTH ) { + salt.bv_val = (void*)(dbhash + SHA1_LENGTH); + salt.bv_len = hash_len - SHA1_LENGTH; + } else if ( hash_len == DS40B1_SALTED_SHA_LENGTH ) { + salt.bv_val = (void*)dbhash; + salt.bv_len = 8; + } else { /* unsupported, invalid BASE64 (hash_len < 0), or similar */ + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, hasherrmsg, SHA1_SCHEME_NAME, dbpwd ); + goto loser; + } + + /* SHA1 hash the user's key */ + if ( sha1_salted_hash( userhash, userpwd, &salt ) != SECSuccess ) { + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "sha1_pw_cmp: SHA1_Hash() failed\n"); + goto loser; + } + + /* the proof is in the comparison... */ + result = ( hash_len == DS40B1_SALTED_SHA_LENGTH ) ? + ( memcmp( userhash, dbhash + 8, hash_len - 8 )) : + ( memcmp( userhash, dbhash, SHA1_LENGTH )); + + loser: + if ( dbhash && dbhash != quick_dbhash ) slapi_ch_free( (void**)&dbhash ); + return result; +} + + +char * +sha1_pw_enc( char *pwd ) +{ + unsigned char hash[ SHA1_LENGTH ]; + char *enc; + + /* SHA1 hash the user's key */ + if ( sha1_salted_hash( hash, pwd, NULL ) != SECSuccess ) { + return( NULL ); + } + + if (( enc = slapi_ch_malloc( 3 + SHA1_NAME_LEN + + LDIF_BASE64_LEN( SHA1_LENGTH ))) == NULL ) { + return( NULL ); + } + + sprintf( enc, "%c%s%c", PWD_HASH_PREFIX_START, SHA1_SCHEME_NAME, + PWD_HASH_PREFIX_END ); + (void)ldif_base64_encode( hash, enc + 2 + SHA1_NAME_LEN, + SHA1_LENGTH, -1 ); + + return( enc ); +} diff --git a/ldap/servers/plugins/pwdstorage/ssha_pwd.c b/ldap/servers/plugins/pwdstorage/ssha_pwd.c new file mode 100644 index 00000000..b3c82d6d --- /dev/null +++ b/ldap/servers/plugins/pwdstorage/ssha_pwd.c @@ -0,0 +1,112 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * slapd hashed password routines + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "pwdstorage.h" +#include "prtime.h" +#include "prlong.h" + +#if defined(NET_SSL) +#include <pk11func.h> +#include <pk11pqg.h> +#endif /* NET_SSL */ + +#define SHA1_SALT_LENGTH 8 /* number of bytes of data in salt */ + +static void ssha_rand_array(void *randx, size_t len); + + +/* *************************************************** + Identical function to slapi_rand_array in util.c, but can't use + that here since this module is included in libds_admin, which doesn't + link to libslapd. + *************************************************** */ +static void +ssha_rand_array(void *randx, size_t len) +{ + PK11_RandomUpdate(randx, len); + PK11_GenerateRandom((unsigned char *)randx, (int)len); +} + +/* + * A salted SHA1 hash + * if salt is null, no salt is used (this is for backward compatibility) +*/ +SECStatus +sha1_salted_hash(unsigned char *hash_out, char *pwd, struct berval *salt) +{ + PK11Context *ctx; + unsigned int outLen; + SECStatus rc; + + if (salt && salt->bv_len) { + ctx = PK11_CreateDigestContext(SEC_OID_SHA1); + if (ctx == NULL) { + rc = SECFailure; + } + else { + PK11_DigestBegin(ctx); + PK11_DigestOp(ctx, (unsigned char*)pwd, strlen(pwd)); + PK11_DigestOp(ctx, (unsigned char*)(salt->bv_val), salt->bv_len); + PK11_DigestFinal(ctx, hash_out, &outLen, SHA1_LENGTH); + PK11_DestroyContext(ctx, 1); + if (outLen == SHA1_LENGTH) + rc = SECSuccess; + else + rc = SECFailure; + } + } + else { + /*backward compatibility*/ + rc = PK11_HashBuf(SEC_OID_SHA1, hash_out, (unsigned char *)pwd, strlen(pwd)); + } + + return rc; +} + +char * +salted_sha1_pw_enc( char *pwd ) +{ + unsigned char hash[ SHA1_LENGTH + SHA1_SALT_LENGTH ]; + unsigned char *salt = hash + SHA1_LENGTH; + struct berval saltval; + char *enc; + + saltval.bv_val = (void*)salt; + saltval.bv_len = SHA1_SALT_LENGTH; + + /* generate a new random salt */ + /* Note: the uninitialized salt array provides a little extra entropy + * to the random array generation, but it is not really needed since + * PK11_GenerateRandom takes care of seeding. In any case, it doesn't + * hurt. */ + ssha_rand_array( salt, SHA1_SALT_LENGTH ); + + /* SHA1 hash the user's key */ + if ( sha1_salted_hash( hash, pwd, &saltval ) != SECSuccess ) { + return( NULL ); + } + + if (( enc = PR_Malloc( 3 + SALTED_SHA1_NAME_LEN + + LDIF_BASE64_LEN(sizeof(hash)))) == NULL ) { + return( NULL ); + } + + sprintf( enc, "%c%s%c", PWD_HASH_PREFIX_START, SALTED_SHA1_SCHEME_NAME, + PWD_HASH_PREFIX_END ); + (void)ldif_base64_encode( hash, enc + 2 + SALTED_SHA1_NAME_LEN, + sizeof(hash), -1 ); + + return( enc ); +} + diff --git a/ldap/servers/plugins/referint/Makefile b/ldap/servers/plugins/referint/Makefile new file mode 100644 index 00000000..acf09c42 --- /dev/null +++ b/ldap/servers/plugins/referint/Makefile @@ -0,0 +1,72 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/referint-plugin +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./referint.def +endif + +REFERINT_OBJS = referint.o +OBJS = $(addprefix $(OBJDEST)/, $(REFERINT_OBJS)) + +INCLUDES += -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPLINK_DEP) $(NSPRLINK_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAPLINK) $(NSPRLINK) + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(SECURITY_DEP) +EXTRA_LIBS += $(SECURITYLINK) +endif + +ifeq ($(ARCH), WINNT) +REFERINT_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), AIX) +LD=ld +endif + +REFERINT= $(addprefix $(LIBDIR)/, $(REFERINT_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(REFERINT) + +ifeq ($(ARCH), WINNT) +$(REFERINT): $(OBJS) $(REFERINT_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(REFERINT_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(REFERINT): $(OBJS) $(REFERINT_DLL_OBJ) + $(LINK_DLL) $(REFERINT_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(REFERINT_DLL_OBJ) +endif + $(RM) $(REFERINT) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/referint/dllmain.c b/ldap/servers/plugins/referint/dllmain.c new file mode 100644 index 00000000..96bfcbb0 --- /dev/null +++ b/ldap/servers/plugins/referint/dllmain.c @@ -0,0 +1,95 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c new file mode 100644 index 00000000..e0ad15fd --- /dev/null +++ b/ldap/servers/plugins/referint/referint.c @@ -0,0 +1,808 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include <stdio.h> +#include <string.h> +#include "portable.h" +#include "slapi-plugin.h" +#include "slap.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" + +/* include NSPR header files */ +#include "prthread.h" +#include "prlock.h" +#include "prerror.h" +#include "prcvar.h" +#include "prio.h" + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +#define REFERINT_PLUGIN_SUBSYSTEM "referint-plugin" /* used for logging */ + +#ifdef _WIN32 +#define REFERINT_DEFAULT_FILE_MODE 0 +#else +#define REFERINT_DEFAULT_FILE_MODE S_IRUSR | S_IWUSR +#endif + + +#define MAX_LINE 2048 +#define READ_BUFSIZE 4096 +#define MY_EOF 0 + +/* function prototypes */ +int referint_postop_init( Slapi_PBlock *pb ); +int referint_postop_del( Slapi_PBlock *pb ); +int referint_postop_modrdn( Slapi_PBlock *pb ); +int referint_postop_start( Slapi_PBlock *pb); +int referint_postop_close( Slapi_PBlock *pb); +int update_integrity(char **argv, char *origDN, char *newrDN, int logChanges); +void referint_thread_func(void *arg); +int GetNextLine(char *dest, int size_dest, PRFileDesc *stream); +void writeintegritylog(char *logfilename, char *dn, char *newrdn); +int my_fgetc(PRFileDesc *stream); + +/* global thread control stuff */ + +static PRLock *referint_mutex = NULL; +static PRThread *referint_tid = NULL; +int keeprunning = 0; + +static PRLock *keeprunning_mutex = NULL; +static PRCondVar *keeprunning_cv = NULL; + +static Slapi_PluginDesc pdesc = { "referint", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "referential integrity plugin" }; + +static void* referint_plugin_identity = NULL; + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +int +referint_postop_init( Slapi_PBlock *pb ) +{ + + /* + * Get plugin identity and stored it for later use + * Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &referint_plugin_identity); + PR_ASSERT (referint_plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, + (void *) referint_postop_del ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, + (void *) referint_postop_modrdn ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) referint_postop_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) referint_postop_close ) != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_init failed\n" ); + return( -1 ); + } + + return( 0 ); +} + + +int +referint_postop_del( Slapi_PBlock *pb ) +{ + char *dn; + int rc; + int oprc; + char **argv; + int argc; + int delay; + int logChanges=0; + int isrepop = 0; + + if ( slapi_pblock_get( pb, SLAPI_IS_REPLICATED_OPERATION, &isrepop ) != 0 || + slapi_pblock_get( pb, SLAPI_DELETE_TARGET, &dn ) != 0 || + slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_del: could not get parameters\n" ); + return( -1 ); + } + + /* this plugin should only execute if the delete was successful + and this is not a replicated op + */ + if(oprc != 0 || isrepop) + { + return( 0 ); + } + /* get args */ + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop failed to get argc\n" ); + return( -1 ); + } + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop failed to get argv\n" ); + return( -1 ); + } + + if(argv == NULL){ + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_modrdn, args are NULL\n" ); + return( -1 ); + } + + if (argc >= 3) { + /* argv[0] will be the delay */ + delay = atoi(argv[0]); + + /* argv[2] will be wether or not to log changes */ + logChanges = atoi(argv[2]); + + if(delay == -1){ + /* integrity updating is off */ + rc = 0; + }else if(delay == 0){ + /* no delay */ + /* call function to update references to entry */ + rc = update_integrity(argv, dn, NULL, logChanges); + }else{ + /* write the entry to integrity log */ + writeintegritylog(argv[1],dn, NULL); + rc = 0; + } + } else { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop insufficient arguments supplied\n" ); + return( -1 ); + } + + return( rc ); + +} + +int +referint_postop_modrdn( Slapi_PBlock *pb ) +{ + char *dn; + char *newrdn; + int oprc; + int rc; + char **argv; + int argc = 0; + int delay; + int logChanges=0; + int isrepop = 0; + + if ( slapi_pblock_get( pb, SLAPI_IS_REPLICATED_OPERATION, &isrepop ) != 0 || + slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &dn ) != 0 || + slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ) != 0 || + slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0 ){ + + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_modrdn: could not get parameters\n" ); + return( -1 ); + } + + /* this plugin should only execute if the delete was successful + and this is not a replicated op + */ + if(oprc != 0 || isrepop){ + return( 0 ); + } + /* get args */ + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop failed to get argv\n" ); + return( -1 ); + } + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop failed to get argv\n" ); + return( -1 ); + } + + if(argv == NULL){ + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_modrdn, args are NULL\n" ); + return( -1 ); + } + + if (argc >= 3) { + /* argv[0] will always be the delay */ + delay = atoi(argv[0]); + + /* argv[2] will be wether or not to log changes */ + logChanges = atoi(argv[2]); + } else { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_modrdn insufficient arguments supplied\n" ); + return( -1 ); + } + + if(delay == -1){ + /* integrity updating is off */ + rc = 0; + }else if(delay == 0){ + /* no delay */ + /* call function to update references to entry */ + rc = update_integrity(argv, dn, newrdn, logChanges); + }else{ + /* write the entry to integrity log */ + writeintegritylog(argv[1],dn, newrdn); + rc = 0; + } + + return( rc ); +} + +int isFatalSearchError(int search_result) +{ + + /* Make sure search result is fatal + * Some conditions that happen quite often are not fatal + * for example if you have two suffixes and one is null, you will always + * get no such object, howerever this is not a fatal error. + * Add other conditions to the if statement as they are found + */ + + /* NPCTE fix for bugid 531225, esc 0. <P.R> <30-May-2001> */ + switch(search_result) { + case LDAP_REFERRAL: + case LDAP_NO_SUCH_OBJECT: return 0 ; + } + return 1; + /* end of NPCTE fix for bugid 531225 */ + +} + +int update_integrity(char **argv, char *origDN, char *newrDN, int logChanges){ + + Slapi_PBlock *search_result_pb = NULL; + Slapi_PBlock *mod_result_pb = NULL; + Slapi_Entry **search_entries = NULL; + int search_result; + Slapi_DN *sdn = NULL; + void *node = NULL; + LDAPMod attribute1, attribute2; + const LDAPMod *list_of_mods[3]; + char *values_del[2]; + char *values_add[2]; + char *filter = NULL; + int i, j; + const char *search_base = NULL; + char *newDN=NULL; + char **dnParts=NULL; + int dnsize; + int x; + int rc; + int valcount = 0; + + if ( argv == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop required config file arguments missing\n" ); + rc = -1; + goto free_and_return; + } + + /* for now, just putting attributes to keep integrity on in conf file, + until resolve the other timing mode issue */ + + /* Search each namingContext in turn */ + for ( sdn = slapi_get_first_suffix( &node, 0 ); sdn != NULL; + sdn = slapi_get_next_suffix( &node, 0 )) + { + search_base = slapi_sdn_get_dn( sdn ); + + + for(i=3; argv[i] != NULL; i++) + { + unsigned long filtlen = strlen(argv[i]) + (strlen(origDN) * 3 ) + 4; + filter = (char *)slapi_ch_calloc( filtlen, sizeof(char )); + if (( search_result = ldap_create_filter( filter, filtlen, "(%a=%e)", + NULL, NULL, argv[i], origDN, NULL )) == LDAP_SUCCESS ) { + + /* Don't need any attribute */ + char * attrs[2]; + attrs[0]="1.1"; + attrs[1]=NULL; + + /* Use new search API */ + search_result_pb = slapi_pblock_new(); + slapi_search_internal_set_pb(search_result_pb, search_base, LDAP_SCOPE_SUBTREE, + filter, attrs, 0 /* attrs only */, NULL,NULL,referint_plugin_identity,0); + slapi_search_internal_pb(search_result_pb); + + slapi_pblock_get( search_result_pb, SLAPI_PLUGIN_INTOP_RESULT, &search_result); + } + + + /* if search successfull then do integrity update */ + if(search_result == 0) + { + slapi_pblock_get( search_result_pb,SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &search_entries); + + for(j=0; search_entries[j] != NULL; j++) + { + /* no matter what mode in always going to delete old dn so set that up */ + values_del[0]= origDN; + values_del[1]= NULL; + attribute1.mod_type = argv[i]; + attribute1.mod_op = LDAP_MOD_DELETE; + attribute1.mod_values = values_del; + list_of_mods[0] = &attribute1; + + if(newrDN == NULL){ + /* in delete mode so terminate list of mods cause this is the only one */ + list_of_mods[1] = NULL; + }else if(newrDN != NULL){ + /* in modrdn mode */ + + /* need to put together rdn into a dn */ + dnParts = ldap_explode_dn( origDN, 0 ); + + /* skip original rdn so start at 1*/ + dnsize = 0; + for(x=1; dnParts[x] != NULL; x++) + { + /* +2 for space and comma adding later */ + dnsize += strlen(dnParts[x]) + 2; + } + /* add the newrDN length */ + dnsize += strlen(newrDN) + 1; + + newDN = slapi_ch_calloc(dnsize, sizeof(char)); + strcat(newDN, newrDN); + for(x=1; dnParts[x] != NULL; x++) + { + strcat(newDN, ", "); + strcat(newDN, dnParts[x]); + } + + values_add[0]=newDN; + values_add[1]=NULL; + attribute2.mod_type = argv[i]; + attribute2.mod_op = LDAP_MOD_ADD; + attribute2.mod_values = values_add; + + /* add the new dn to list of mods and terminate list of mods */ + list_of_mods[1] = &attribute2; + list_of_mods[2] = NULL; + + } + + /* try to cleanup entry */ + + /* Use new internal operation API */ + mod_result_pb=slapi_pblock_new(); + slapi_modify_internal_set_pb(mod_result_pb,slapi_entry_get_dn(search_entries[j]), + (LDAPMod **)list_of_mods,NULL,NULL,referint_plugin_identity,0); + slapi_modify_internal_pb(mod_result_pb); + + /* could check the result code here if want to log it or something later + for now, continue no matter what result is */ + + slapi_pblock_destroy(mod_result_pb); + + /* cleanup memory allocated for dnParts and newDN */ + if(dnParts != NULL){ + for(x=0; dnParts[x] != NULL; x++) + { + free(dnParts[x]); + } + free(dnParts); + } + if(newDN != NULL){ + slapi_ch_free((void**)&newDN); + } + + } + + + + }else{ + if(isFatalSearchError(search_result)) + { + /* NPCTE fix for bugid 531225, esc 0. <P.R> <30-May-2001> */ + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop search (base=%s filter=%s) returned error %d\n", search_base,filter,search_result ); + /* end of NPCTE fix for bugid 531225 */ + rc = -1; + goto free_and_return; + } + + } + + slapi_ch_free((void**)&filter); + + if(search_result_pb != NULL){ + slapi_free_search_results_internal(search_result_pb); + slapi_pblock_destroy(search_result_pb); + search_result_pb= NULL; + } + + } + } + /* if got here, then everything good rc = 0 */ + rc = 0; + +free_and_return: + + /* free filter and search_results_pb */ + if(filter != NULL) + { + free(filter); + } + + if(search_result_pb != NULL) + { + slapi_free_search_results_internal(search_result_pb); + free(search_result_pb); + } + + return(rc); +} + +int referint_postop_start( Slapi_PBlock *pb) +{ + + char **argv; + int argc = 0; + + /* get args */ + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop failed to get argv\n" ); + return( -1 ); + } + if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop failed to get argv\n" ); + return( -1 ); + } + + if(argv == NULL){ + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "args were null in referint_postop_start\n" ); + return( -1 ); + } + + /* only bother to start the thread if you are in delay mode. + 0 = no delay, + -1 = integrity off */ + + if (argc >= 1) { + if(atoi(argv[0]) > 0){ + + /* initialize cv and lock */ + + referint_mutex = PR_NewLock(); + keeprunning_mutex = PR_NewLock(); + keeprunning_cv = PR_NewCondVar(keeprunning_mutex); + keeprunning =1; + + if (( referint_tid = PR_CreateThread (PR_USER_THREAD, + referint_thread_func, + (void *)argv, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_start PR_CreateThread failed\n" ); + exit( 1 ); + } + } + } else { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_start insufficient arguments supplied\n" ); + return( -1 ); + } + + return(0); + +} + +int referint_postop_close( Slapi_PBlock *pb) +{ + + /* signal the thread to exit */ + if (NULL != keeprunning_mutex) { + PR_Lock(keeprunning_mutex); + keeprunning=0; + if (NULL != keeprunning_cv) { + PR_NotifyCondVar(keeprunning_cv); + } + PR_Unlock(keeprunning_mutex); + } + + return(0); +} + +void referint_thread_func(void *arg){ + + char **plugin_argv = (char **)arg; + PRFileDesc *prfd; + char *logfilename; + char thisline[MAX_LINE]; + int delay; + int no_changes; + char delimiter[]="\t\n"; + char *ptoken; + char *tmpdn, *tmprdn; + int logChanges=0; + char * iter = NULL; + + if(plugin_argv == NULL){ + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_thread_func not get args \n" ); + return; + } + + + delay = atoi(plugin_argv[0]); + logfilename = plugin_argv[1]; + logChanges = atoi(plugin_argv[2]); + + /* keep running this thread until plugin is signalled to close */ + + while(1){ + + no_changes=1; + + while(no_changes){ + + PR_Lock(keeprunning_mutex); + if(keeprunning == 0){ + PR_Unlock(keeprunning_mutex); + break; + } + PR_Unlock(keeprunning_mutex); + + + PR_Lock(referint_mutex); + if (( prfd = PR_Open( logfilename, PR_RDONLY, + REFERINT_DEFAULT_FILE_MODE )) == NULL ) + { + PR_Unlock(referint_mutex); + /* go back to sleep and wait for this file */ + PR_Lock(keeprunning_mutex); + PR_WaitCondVar(keeprunning_cv, PR_SecondsToInterval(delay)); + PR_Unlock(keeprunning_mutex); + }else{ + no_changes = 0; + } + } + + /* check keep running here, because after break out of no + * changes loop on shutdown, also need to break out of this + * loop before trying to do the changes. The server + * will pick them up on next startup as file still exists + */ + PR_Lock(keeprunning_mutex); + if(keeprunning == 0){ + PR_Unlock(keeprunning_mutex); + break; + } + PR_Unlock(keeprunning_mutex); + + + while( GetNextLine(thisline, MAX_LINE, prfd) ){ + ptoken = ldap_utf8strtok_r(thisline, delimiter, &iter); + tmpdn = slapi_ch_calloc(strlen(ptoken) + 1, sizeof(char)); + strcpy(tmpdn, ptoken); + + ptoken = ldap_utf8strtok_r (NULL, delimiter, &iter); + if(!strcasecmp(ptoken, "NULL")){ + tmprdn = NULL; + }else{ + tmprdn = slapi_ch_calloc(strlen(ptoken) + 1, sizeof(char)); + strcpy(tmprdn, ptoken); + } + + + update_integrity(plugin_argv, tmpdn, tmprdn, logChanges); + + slapi_ch_free((void **) &tmpdn); + slapi_ch_free((void **) &tmprdn); + } + + PR_Close(prfd); + + /* remove the original file */ + if( PR_SUCCESS != PR_Delete(logfilename) ) + { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop_close could not delete \"%s\"\n", + logfilename ); + } + + /* unlock and let other writers back at the file */ + PR_Unlock(referint_mutex); + + /* wait on condition here */ + PR_Lock(keeprunning_mutex); + PR_WaitCondVar(keeprunning_cv, PR_SecondsToInterval(delay)); + PR_Unlock(keeprunning_mutex); + } + + /* cleanup resources allocated in start */ + if (NULL != keeprunning_mutex) { + PR_DestroyLock(keeprunning_mutex); + } + if (NULL != referint_mutex) { + PR_DestroyLock(referint_mutex); + } + if (NULL != keeprunning_cv) { + PR_DestroyCondVar(keeprunning_cv); + } + + +} + +int my_fgetc(PRFileDesc *stream) +{ + static char buf[READ_BUFSIZE] = "\0"; + static int position = READ_BUFSIZE; + int retval; + int err; + + /* check if we need to load the buffer */ + + if( READ_BUFSIZE == position ) + { + memset(buf, '\0', READ_BUFSIZE); + if( ( err = PR_Read(stream, buf, READ_BUFSIZE) ) >= 0) + { + /* it read some data */; + position = 0; + }else{ + /* an error occurred */ + return err; + } + } + + /* try to read some data */ + if( '\0' == buf[position]) + { + /* out of data, return eof */ + retval = MY_EOF; + position = READ_BUFSIZE; + }else{ + retval = buf[position]; + position++; + } + + return retval; + +} + +int +GetNextLine(char *dest, int size_dest, PRFileDesc *stream) { + + char nextchar ='\0'; + int done = 0; + int i = 0; + + while(!done) + { + if( ( nextchar = my_fgetc(stream) ) != 0) + { + if( i < (size_dest - 1) ) + { + dest[i] = nextchar; + i++; + + if(nextchar == '\n') + { + /* end of line reached */ + done = 1; + } + + }else{ + /* no more room in buffer */ + done = 1; + } + + }else{ + /* error or end of file */ + done = 1; + } + } + + dest[i] = '\0'; + + /* return size of string read */ + return i; +} + +void writeintegritylog(char *logfilename, char *dn, char *newrdn){ + PRFileDesc *prfd; + char buffer[MAX_LINE]; + int len_to_write = 0; + int rc; + /* write this record to the file */ + + /* use this lock to protect file data when update integrity is occuring */ + /* should hopefully not be a big issue on concurrency */ + + PR_Lock(referint_mutex); + if (( prfd = PR_Open( logfilename, PR_WRONLY | PR_CREATE_FILE | PR_APPEND, + REFERINT_DEFAULT_FILE_MODE )) == NULL ) + { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop could not write integrity log \"%s\" " + SLAPI_COMPONENT_NAME_NSPR " %d (%s)\n", + logfilename, PR_GetError(), slapd_pr_strerror(PR_GetError()) ); + + PR_Unlock(referint_mutex); + return; + } + + /* make sure we have enough room in our buffer + before trying to write it + */ + + /* add length of dn + 4(two tabs, a newline, and terminating \0) */ + len_to_write = strlen(dn) + 4; + + if(newrdn == NULL) + { + /* add the length of "NULL" */ + len_to_write += 4; + }else{ + /* add the length of the newrdn */ + len_to_write += strlen(newrdn); + } + + if(len_to_write > MAX_LINE ) + { + slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM, + "referint_postop could not write integrity log:" + " line length exceeded. It will not be able" + " to update references to this entry.\n"); + }else{ + PRInt32 rv; + sprintf(buffer, "%s\t%s\t\n", + dn, + (newrdn != NULL) ? newrdn : "NULL"); + if ((rv = PR_Write(prfd,buffer,strlen(buffer))) < 0){ + slapi_log_error(SLAPI_LOG_FATAL,REFERINT_PLUGIN_SUBSYSTEM, + " writeintegritylog: PR_Write failed : The disk" + " may be full or the file is unwritable :: NSPR error - %d\n", + PR_GetError()); + } + } + + /* If file descriptor is closed successfully, PR_SUCCESS */ + + rc = PR_Close(prfd); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL,REFERINT_PLUGIN_SUBSYSTEM, + " writeintegritylog: failed to close the file" + " descriptor prfd; NSPR error - %d\n", + PR_GetError()); + } + PR_Unlock(referint_mutex); + } diff --git a/ldap/servers/plugins/referint/referint.def b/ldap/servers/plugins/referint/referint.def new file mode 100644 index 00000000..75861791 --- /dev/null +++ b/ldap/servers/plugins/referint/referint.def @@ -0,0 +1,12 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7 Referint Plugin' +CODE SHARED READ EXECUTE +DATA SHARED READ WRITE +EXPORTS + referint_postop_init @2 + plugin_init_debug_level @3 diff --git a/ldap/servers/plugins/replication/Makefile b/ldap/servers/plugins/replication/Makefile new file mode 100644 index 00000000..6f5341e8 --- /dev/null +++ b/ldap/servers/plugins/replication/Makefile @@ -0,0 +1,152 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server "Replication" plugin +# +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/replication-plugin +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk +include $(MCOM_ROOT)/ldapserver/ns_usedb.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./replication.def +endif + +CFLAGS += $(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +CFLAGS += /WX +endif + +ifdef TEST_CL5 +CFLAGS += -DTEST_CL5 +endif + +INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(DB_INCLUDE) + +LOCAL_OBJS= \ + cl5_api.o \ + cl5_clcache.o \ + cl5_config.o \ + cl5_init.o \ + csnpl.o\ + legacy_consumer.o \ + llist.o\ + repl5_agmt.o \ + repl5_agmtlist.o \ + repl5_backoff.o \ + repl5_connection.o \ + repl5_inc_protocol.o \ + repl5_init.o\ + repl5_protocol.o \ + repl5_protocol_util.o \ + repl5_replica.o\ + repl5_replica_config.o\ + repl5_ruv.o\ + repl5_schedule.o \ + repl5_tot_protocol.o \ + repl5_total.o\ + repl5_mtnode_ext.o\ + repl5_plugins.o \ + repl_add.o \ + repl_bind.o \ + repl_compare.o \ + repl_connext.o \ + repl_controls.o \ + repl_delete.o \ + repl_entry.o \ + repl_ext.o \ + repl_extop.o \ + repl_globals.o \ + repl_init.o \ + repl_modify.o \ + repl_modrdn.o \ + repl_monitor.o \ + repl_objset.o \ + repl_opext.o \ + repl_ops.o \ + repl_rootdse.o \ + repl_search.o \ + replutil.o \ + urp.o \ + urp_glue.o \ + urp_tombstone.o \ + repl5_replica_hash.o\ + repl5_replica_dnhash.o\ + repl5_updatedn_list.o\ + +LIBREPLICATION_OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS)) + +ifeq ($(ARCH), WINNT) +REPLICATION_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +LIBREPLICATION= $(addprefix $(LIBDIR)/, $(REPLICATION_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAPLINK) $(DB_LIB) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./replication.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBREPLICATION) + +$(LIBREPLICATION): $(LIBREPLICATION_OBJS) $(REPLICATION_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBREPLICATION_OBJS) $(REPLICATION_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) $(LDAP_LIBLDIF) $(NSPRLINK) + +tests: $(TEST_PROGS) + +veryclean: clean + +clean: + $(RM) $(LIBREPLICATION_OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(REPLICATION_DLL_OBJ) +endif + $(RM) $(LIBREPLICATION) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(LIBREPLICATION_OBJS): diff --git a/ldap/servers/plugins/replication/cl4.h b/ldap/servers/plugins/replication/cl4.h new file mode 100644 index 00000000..0dbcece2 --- /dev/null +++ b/ldap/servers/plugins/replication/cl4.h @@ -0,0 +1,65 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl4.h - global declarations used by the 4.0 style changelog module + */ + +#ifndef CL4_H +#define CL4_H + +#include "slapi-private.h" +#include "portable.h" /* GGOODREPL - is this cheating? */ + +#define CONFIG_CHANGELOG_SUFFIX_ATTRIBUTE "nsslapd-changelogsuffix" + +/* A place to store changelog config info */ +typedef struct _chglog4Info chglog4Info; + +/* in cl4.c */ +chglog4Info* changelog4_new (Slapi_Entry *e, char *errorbuf); +void changelog4_free (chglog4Info** cl4); +void changelog4_lock (Object *obj, PRBool write); +void changelog4_unlock (Object *obj); +const char * changelog4_get_dir (const chglog4Info* cl4); +const char * changelog4_get_suffix (const chglog4Info* cl4); +time_t changelog4_get_maxage (const chglog4Info* cl4); +unsigned long changelog4_get_maxentries (const chglog4Info* cl4); +void changelog4_set_dir (chglog4Info* cl4, const char *dir); +void changelog4_set_suffix (chglog4Info* cl4, const char *suffix); +void changelog4_set_maxage (chglog4Info* cl4, const char *maxage); +void changelog4_set_maxentries (chglog4Info* cl4, const char* maxentries); + +/* In cl4_suffix.c */ +char *get_changelog_dataversion(const chglog4Info* cl4); +void set_changelog_dataversion(chglog4Info* cl4, const char *dataversion); + +/* In cl4_config.c */ +int changelog4_config_init(); +void changelog4_config_destroy(); + +/* + * backend configuration information + * Previously, these two typedefs were in ../../slapd/slapi-plugin.h but + * the CL4 code is the only remaining code that references these definitions. + */ +typedef struct config_directive +{ + char *file_name; /* file from which to read directive */ + int lineno; /* line to read */ + int argc; /* number of argvs */ + char **argv; /* directive in agrv format */ +} slapi_config_directive; + +typedef struct be_config +{ + char *type; /* type of the backend */ + char *suffix; /* suffix of the backend */ + int is_private; /* 1 - private, 0 -not */ + int log_change; /* 1 - write change to the changelog; 0 - don't */ + slapi_config_directive *directives;/* configuration directives */ + int dir_count; /* number of directives */ +} slapi_be_config; + +#endif diff --git a/ldap/servers/plugins/replication/cl4_api.c b/ldap/servers/plugins/replication/cl4_api.c new file mode 100644 index 00000000..135b4a5b --- /dev/null +++ b/ldap/servers/plugins/replication/cl4_api.c @@ -0,0 +1,797 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl4_api.h - implementation of the minimal interface to 4.0 changelog necessary to + link 4.0 changelog to 5.0 replication + */ + +#include "repl.h" +#include "cl4_api.h" +#include "csnpl.h" +#include "cl4.h" + +/*** Data Structures ***/ + +/* changelog internal data */ +typedef struct cl4priv +{ + CSNPL *csnPL; /* csn pending list */ + int regID; /* csn function registration id */ +}CL4Private; + +/* callback data to get result of internal operations */ +typedef struct cl4ret +{ + int err; /* error code */ + Slapi_Entry *e; /* target entry */ +}CL4Ret; + +/* Global Data */ +static CL4Private s_cl4Desc; /* represents changelog state */ + +/*** Helper functions forward declarations ***/ +static int _cl4WriteOperation (const slapi_operation_parameters *op); +static void _cl4AssignCSNCallback (const CSN *csn, void *data); +static void _cl4AbortCSNCallback (const CSN *csn, void *data); +static char* _cl4MakeCSNDN (const CSN* csn); +static int _cl4GetEntry (const CSN *csn, Slapi_Entry **entry); +static void _cl4ResultCallback (int err, void *callback_data); +static int _cl4EntryCallback (Slapi_Entry *e, void *callback_data); +static PRBool _cl4CanAssignChangeNumber (const CSN *csn); +static int _cl4ResolveTargetDN (Slapi_Entry *entry, Slapi_DN **newTargetDN); +static int _cl4GetTargetEntry (Slapi_DN *targetDN, const char *uniqueid, Slapi_Entry **entry); +static int _cl4FindTargetDN (const CSN *csn, const char *uniqueid, + const Slapi_DN *targetSDN, Slapi_DN **newTargetDN); +static int _cl4AssignChangeNumber (changeNumber *cnum); +static int _cl4UpdateEntry (const CSN *csn, const char *changeType, const Slapi_DN *newTargetDN, changeNumber cnum); + +/*** API ***/ +int cl4Init () +{ + s_cl4Desc.csnPL = csnplNew (); + if (s_cl4Desc.csnPL == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4Init: failed to create CSN pending list\n"); + return CL4_CSNPL_ERROR; + } + + s_cl4Desc.regID = csnRegisterNewCSNCb(_cl4AssignCSNCallback, NULL, + _cl4AbortCSNCallback, NULL); + + return CL4_SUCCESS; +} + +void cl4Cleanup () +{ + if (s_cl4Desc.regID >= 0) + { + csnRemoveNewCSNCb(s_cl4Desc.regID); + s_cl4Desc.regID = -1; + } + + if (s_cl4Desc.csnPL == NULL) + csnplFree (&s_cl4Desc.csnPL); +} + +int cl4WriteOperation (const slapi_operation_parameters *op) +{ + int rc; + ReplicaId rd; + + if (op == NULL || !IsValidOperation (op)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4WriteEntry: invalid entry\n"); + return CL4_BAD_DATA; + } + + rc = _cl4WriteOperation (op); + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4WriteEntry: failed to write changelog entry\n"); + return rc; + } + + /* the entry is generated by this server - remove the entry from the pending list */ + rd= csn_get_replicaid(op->csn); + if (rd == slapi_get_replicaid ()) + { + rc = csnplRemove (s_cl4Desc.csnPL, op->csn); + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "cl4WriteEntry: failed to remove CSN from the pending list\n"); + rc = CL4_CSNPL_ERROR; + } + } + + return rc; +} + +int cl4ChangeTargetDN (const CSN *csn, const char *newDN) +{ + Slapi_PBlock *pb; + char *changeEntryDN; + Slapi_Mods smods; + int res; + + if (csn == NULL || newDN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4ChangeTargetDN: invalid argument\n"); + return CL4_BAD_DATA; + } + + /* construct dn of the change entry */ + changeEntryDN = _cl4MakeCSNDN (csn); + if (changeEntryDN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "cl4ChangeTargetDN: failed to construct change entry dn\n"); + return CL4_MEMORY_ERROR; + } + + pb = slapi_pblock_new (); + + slapi_mods_init(&smods, 1); + slapi_mods_add(&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_targetdn, + strlen (newDN), newDN); + slapi_modify_internal_set_pb(pb, changeEntryDN, slapi_mods_get_ldapmods_byref(&smods), + NULL, NULL, repl_get_plugin_identity(PLUGIN_LEGACY_REPLICATION), 0); + slapi_modify_internal_pb (pb); + + slapi_mods_done(&smods); + slapi_ch_free ((void**)&changeEntryDN); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + slapi_pblock_destroy(pb); + + if (res != LDAP_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "cl4ChangeTargetDN: an error occured while modifying change entry with csn %s: %s. " + "Logging of changes is disabled.\n", csn_as_string(csn,PR_FALSE,s), ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + return CL4_LDAP_ERROR; + } + + return CL4_SUCCESS; +} + +void cl4AssignChangeNumbers (time_t when, void *arg) +{ + int rc = CL4_SUCCESS; + Slapi_Entry *entry; + CSN *csn = NULL; + Slapi_DN *newTargetDN; + changeNumber cnum; + char *changetype; + + /* we are looping though the entries ready to be commited updating there target dn + and assigning change numbers */ + while (_cl4GetEntry (csn, &entry) == CL4_SUCCESS) + { + /* ONREPL - I think we need to free previous csn */ + csn = csn_new_by_string(slapi_entry_attr_get_charptr (entry, attr_csn)); + /* all conflicts involving this entry have been resolved */ + if (_cl4CanAssignChangeNumber (csn)) + { + /* figure out the name of the target entry that corresponds to change csn */ + rc = _cl4ResolveTargetDN (entry, &newTargetDN); + slapi_entry_free (entry); + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4AssignChangeNumbers: failed to resolve target dn\n"); + break; + } + + _cl4AssignChangeNumber (&cnum); + + changetype = slapi_entry_attr_get_charptr (entry, attr_changetype); + + /* update change entry: write change number and remove csn attribute. + Note that we leave uniqueid in the entry to avoid an extra update. + This is ok since uniqueid is an operational attribute not returned + to the client by default. */ + rc = _cl4UpdateEntry (csn, changetype, newTargetDN, cnum); + if (newTargetDN) + { + slapi_sdn_free (&newTargetDN); + } + + slapi_ch_free ((void**)&changetype); + + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "cl4AssignChangeNumbers: failed to update changelog entry\n"); + break; + } + } + else /* went too far */ + { + slapi_entry_free (entry); + break; + } + } +} + + +/*** Helper Functions ***/ + +/* adds new change record to 4.0 changelog */ +static int _cl4WriteOperation (const slapi_operation_parameters *op) +{ + int rc = CL4_SUCCESS, res; + char *changeEntryDN, *timeStr; + Slapi_Entry *e; + Slapi_PBlock *pb = NULL; + Slapi_Value *values[3]; + char s[CSN_STRSIZE]; + + slapi_log_error (SLAPI_LOG_PLUGIN, repl_plugin_name, + "_cl4WriteEntry: writing change record with csn %s for dn: \"%s\"\n", + csn_as_string(op->csn,PR_FALSE,s), op->target_address.dn); + + /* create change entry dn */ + changeEntryDN = _cl4MakeCSNDN (op->csn); + if (changeEntryDN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4WriteEntry: failed to create entry dn\n"); + return CL4_MEMORY_ERROR; + } + + /* + * Create the entry struct, and fill in fields common to all types + * of change records. + */ + e = slapi_entry_alloc(); + if (e == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4WriteEntry: failed to allocate change entry\n"); + return CL4_MEMORY_ERROR; + } + + slapi_entry_set_dn(e, slapi_ch_strdup (changeEntryDN)); + + /* Set the objectclass attribute */ + values [0] = slapi_value_new (NULL); + values [1] = slapi_value_new (NULL); + values [2] = NULL; + slapi_value_set_string(values[0], "top"); + slapi_value_set_string(values[1], "changelogentry"); + slapi_entry_add_values_sv (e, "objectclass", values); + + /* ONREPL - for now we have to free Slapi_Values since api makes copy; + this will change when a new set of api is added */ + slapi_value_free (&(values[0])); + slapi_value_free (&(values[1])); + + /* Set the changeNumber attribute */ + /* Need to set this because it is required by schema */ + slapi_entry_attr_set_charptr (e, attr_changenumber, "0"); + + /* Set the targetentrydn attribute */ + if (op->operation_type == SLAPI_OPERATION_ADD) /* use raw dn */ + slapi_entry_attr_set_charptr (e, attr_targetdn, slapi_entry_get_dn (op->p.p_add.target_entry)); + else /* use normolized dn */ + slapi_entry_attr_set_charptr (e, attr_targetdn, op->target_address.dn); + + /* ONREPL - set dbid attribute */ + + /* Set the changeTime attribute */ + timeStr = format_localTime (current_time()); + slapi_entry_attr_set_charptr (e, attr_changetime, timeStr); + slapi_ch_free((void**)&timeStr); + + /* + * Finish constructing the entry. How to do it depends on the type + * of modification being logged. + */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (entry2reple(e, op->p.p_add.target_entry) != 0 ) + { + rc = CL4_INTERNAL_ERROR; + goto done; + } + + break; + + case SLAPI_OPERATION_MODIFY: if (mods2reple(e, op->p.p_modify.modify_mods) != 0) + { + rc = CL4_INTERNAL_ERROR; + goto done; + } + + break; + + case SLAPI_OPERATION_MODDN: if (modrdn2reple(e, op->p.p_modrdn.modrdn_newrdn, + op->p.p_modrdn.modrdn_deloldrdn, op->p.p_modrdn.modrdn_mods) != 0) + { + rc = CL4_INTERNAL_ERROR; + goto done; + } + + break; + + case SLAPI_OPERATION_DELETE: /* Set the changetype attribute */ + slapi_entry_attr_set_charptr (e, attr_changetype, "delete"); + break; + } + + pb = slapi_pblock_new (pb); + slapi_add_entry_internal_set_pb (pb, e, NULL, repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_add_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + slapi_pblock_destroy(pb); + + if (res != LDAP_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4WriteEntry: an error occured while adding change entry with csn %s, dn = %s: %s. " + "Logging of changes is disabled.\n", csn_as_string(op->csn,PR_FALSE,s), op->target_address.dn, + ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + rc = CL4_LDAP_ERROR; + } + +done: + if (changeEntryDN) + slapi_ch_free((void **) &changeEntryDN); + + return rc; +} + +static void _cl4AssignCSNCallback (const CSN *csn, void *data) +{ + int rc; + + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4AssignCSNCallback: null csn\n"); + return; + } + + rc = csnplInsert (s_cl4Desc.csnPL, csn); + + if (rc == -1) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4AssignCSNCallback: failed to insert csn %s to the pending list\n", + csn_as_string(csn,PR_FALSE,s)); + } +} + +static void _cl4AbortCSNCallback (const CSN *csn, void *data) +{ + int rc; + + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4AbortCSNCallback: null csn\n"); + return; + } + + rc = csnplRemove (s_cl4Desc.csnPL, csn); + if (rc == -1) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4AbortCSNCallback: failed to remove csn %s from the pending list\n", + csn_as_string(csn,PR_FALSE,s)); + } +} + +/* initial dn format: csn=<csn>,<changelog suffix>. For instance, csn=013744022939465,cn=changelog4 */ +static char* _cl4MakeCSNDN (const CSN* csn) +{ + char *pat, *edn; + char *suffix = changelog4_get_suffix (); + char s[CSN_STRSIZE]; + + if (suffix == NULL) + return NULL; + + /* Construct the dn of this change record */ + pat = "%s=%s,%s"; + edn = slapi_ch_malloc(strlen(pat) + strlen(attr_csn) + strlen(suffix) + CSN_STRSIZE + 1); + if (edn) + sprintf(edn, pat, attr_csn, csn_as_string(csn,PR_FALSE,s), suffix); + slapi_ch_free ((void **)&suffix); + + return edn; +} + +static int _cl4GetEntry (const CSN *csn, Slapi_Entry **entry) +{ + int rc; + char *suffix = changelog4_get_suffix (); + int type; + const char *value; + CL4Ret ret; + char s[CSN_STRSIZE]; + + if (csn == NULL) /* entry with smallest csn */ + { + type = SLAPI_SEQ_FIRST; + value = NULL; + } + else /* entry with next csn */ + { + type = SLAPI_SEQ_NEXT; + value = csn_as_string(csn,PR_FALSE,s); + } + + rc = slapi_seq_callback(suffix, type, attr_csn, (char*)value, NULL, 0, &ret, NULL, + _cl4ResultCallback, _cl4EntryCallback, NULL); + slapi_ch_free ((void**)&suffix); + + if (rc != 0 || ret.err != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetEntry: failed to get next changelog entry\n"); + return CL4_INTERNAL_ERROR; + } + + *entry = ret.e; + return CL4_SUCCESS; +} + +static void _cl4ResultCallback (int err, void *callback_data) +{ + CL4Ret *ret = (CL4Ret *)callback_data; + + if (ret) + { + ret->err = err; + } +} + +static int _cl4EntryCallback (Slapi_Entry *e, void *callback_data) +{ + CL4Ret *ret = (CL4Ret *)callback_data; + + if (ret) + { + ret->e = slapi_entry_dup (e); + } + + return 0; +} + +static PRBool _cl4CanAssignChangeNumber (const CSN *csn) +{ + CSN *commitCSN = NULL; + + /* th CSN is withtin region that can be commited */ + if (csn && csn_compare(csn, commitCSN) < 0) + return PR_TRUE; + + return PR_FALSE; +} + +/* ONREPL - describe algorithm */ +static int _cl4ResolveTargetDN (Slapi_Entry *entry, Slapi_DN **newTargetDN) +{ + int rc; + char *csnStr = slapi_entry_attr_get_charptr (entry, attr_csn); + char *targetdn = slapi_entry_attr_get_charptr (entry, attr_targetdn); + const char *uniqueid = slapi_entry_get_uniqueid (entry); + char *changetype = slapi_entry_attr_get_charptr (entry, attr_changetype); + CSN *csn = csn_new_by_string (csnStr); + Slapi_Entry *targetEntry = NULL; + const Slapi_DN *teSDN; + Slapi_DN *targetSDN; + const CSN *teDNCSN = NULL; + + *newTargetDN = NULL; + + targetSDN = slapi_sdn_new(); + if (strcasecmp (changetype, "add") == 0) /* this is add operation - we have rawdn */ + slapi_sdn_set_dn_byref (targetSDN, targetdn); + else + slapi_sdn_set_ndn_byref (targetSDN, targetdn); + + /* read the entry to which the change was applied */ + rc = _cl4GetTargetEntry (targetSDN, uniqueid, &targetEntry); + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4ResolveTargetDN: failed to get target entry\n"); + goto done; + } + + teDNCSN = entry_get_dncsn(targetEntry); + if (teDNCSN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4ResolveTargetDN: failed to get target entry dn\n"); + rc = CL4_BAD_FORMAT; + goto done; + } + + if (csn_compare(teDNCSN, csn) <= 0) + { + /* the change entry target dn should be the same as target entry dn */ + teSDN = slapi_entry_get_sdn_const(targetEntry); + + /* target dn of change entry is not the same as dn of the target entry - update */ + if (slapi_sdn_compare (teSDN, targetSDN) != 0) + { + *newTargetDN = slapi_sdn_dup (targetSDN); + } + } + else /* the target entry was renamed since this change occur - find the right target dn */ + { + rc = _cl4FindTargetDN (csn, uniqueid, targetSDN, newTargetDN); + } + +done:; + if (csnStr) + slapi_ch_free ((void**)&csnStr); + + if (targetdn) + slapi_ch_free ((void**)&targetdn); + + if (uniqueid) + slapi_ch_free ((void**)&uniqueid); + + if (changetype) + slapi_ch_free ((void**)&changetype); + + if (targetEntry) + slapi_entry_free (targetEntry); + + if (targetSDN) + slapi_sdn_free (&targetSDN); + + return rc; +} + +static int _cl4GetTargetEntry (Slapi_DN *sdn, const char *uniqueid, Slapi_Entry **entry) +{ + Slapi_PBlock *pb; + char filter [128]; + int res, rc = CL4_SUCCESS; + Slapi_Entry **entries = NULL; + + /* read corresponding database entry based on its uniqueid */ + sprintf (filter, "uniqueid=%s", uniqueid); + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, (char*)slapi_sdn_get_ndn(sdn), LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_search_internal_pb (pb); + + if (pb == NULL) + { + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (res == LDAP_NO_SUCH_OBJECT) /* entry not found */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetTargetEntry: entry (%s) not found\n", + slapi_sdn_get_ndn(sdn)); + rc = CL4_NOT_FOUND; + goto done; + } + + if (res != LDAP_SUCCESS) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4ResolveTargetDN: an error occured while searching for directory entry with uniqueid %s: %s. " + "Logging of changes is disabled.\n", uniqueid, ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (entries == NULL || entries [0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetTargetEntry: entry (%s) not found\n", + slapi_sdn_get_ndn(sdn)); + rc = CL4_NOT_FOUND; + goto done; + } + + *entry = slapi_entry_dup (entries[0]); + +done: + if (pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + } + + return rc; +} + +static int _cl4FindTargetDN (const CSN *csn, const char *uniqueid, + const Slapi_DN *targetSDN, Slapi_DN **newTargetDN) +{ + int rc = CL4_SUCCESS; + int res, i; + Slapi_PBlock *pb; + char *suffix = changelog4_get_suffix (); + char filter [128]; + Slapi_Entry **entries; + int minIndex = 0; + CSN *minCSN = NULL, *curCSN; + char *curType; + const Slapi_DN *sdn; + char s[CSN_STRSIZE]; + + *newTargetDN = NULL; + + /* Look for all modifications to the target entry with csn larger than + this csn. We are only interested in rename operations, but change type + is currently not indexed */ + sprintf (filter, "&(uniqueid=%s)(csn>%s)", uniqueid, csn_as_string(csn,PR_FALSE,s)); + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, suffix, LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_ch_free ((void**)&suffix); + if (pb == NULL) + { + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (res == LDAP_NO_SUCH_OBJECT) /* entry not found */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4FindTargetDN: no entries much filter (%s)\n", + filter); + rc = CL4_NOT_FOUND; + goto done; + } + + if (res != LDAP_SUCCESS) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4ResolveTargetDN: an error occured while searching change entries matching filter %s: %s. " + "Logging of changes is disabled.\n", filter, ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (entries == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4FindTargetDN: no entries much filter (%s)\n", + filter); + rc = CL4_NOT_FOUND; + goto done; + } + + i = 0; + + /* find rename operation with smallest csn - its target dn should be the name + of our change entry */ + while (entries[i]) + { + curType = slapi_entry_attr_get_charptr (entries[i], attr_changetype); + if (curType && strcasecmp (curType, "modrdn") == 0) + { + curCSN = csn_new_by_string (slapi_entry_attr_get_charptr (entries[i], attr_csn)); + if (minCSN == NULL || csn_compare (curCSN, minCSN) < 0) + { + minCSN = curCSN; + minIndex = i; + } + } + + if (curType) + slapi_ch_free ((void**)&curType); + + i ++; + } + + if (curCSN == NULL) + { + rc = CL4_NOT_FOUND; + goto done; + } + + /* update targetDN of our entry if necessary */ + sdn = slapi_entry_get_sdn_const(entries[minIndex]); + + /* target dn does not match to renaming operation - rename change entry */ + if (slapi_sdn_compare (sdn, targetSDN) != 0) + *newTargetDN = slapi_sdn_dup (sdn); + +done: + if (pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + } + + return rc; +} + +static int _cl4AssignChangeNumber (changeNumber *cnum) +{ + *cnum = ldapi_assign_changenumber(); + return CL4_SUCCESS; +} + +static int _cl4UpdateEntry (const CSN *csn, const char *changeType, + const Slapi_DN *newDN, changeNumber cnum) +{ + Slapi_PBlock *pb; + char *dn; + const char *dnTemp; + int res; + Slapi_Mods smods; + char cnumbuf[32]; + + if (csn == NULL || changeType == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4UpdateEntry: invalid argument\n"); + return CL4_BAD_DATA; + } + + dn = _cl4MakeCSNDN (csn); + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4UpdateEntry: failed to create entry dn\n"); + return CL4_MEMORY_ERROR; + } + + slapi_mods_init(&smods, 2); + if (newDN) + { + if (strcasecmp (changeType, "add") == 0) + dnTemp = slapi_sdn_get_dn (newDN); + else + dnTemp = slapi_sdn_get_ndn (newDN); + + slapi_mods_add(&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_targetdn, + strlen (dnTemp), dnTemp); + } + /* Set the changeNumber attribute */ + sprintf(cnumbuf, "%lu", cnum); + slapi_mods_add (&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_changenumber, + strlen (cnumbuf), cnumbuf); + pb = slapi_pblock_new (); + slapi_modify_internal_set_pb (pb, dn, slapi_mods_get_ldapmods_byref(&smods), NULL, NULL, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_modify_internal_pb (pb); + slapi_mods_done(&smods); + slapi_ch_free ((void**)&dn); + + if (pb == NULL) + { + return CL4_LDAP_ERROR; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + slapi_pblock_destroy(pb); + if (res != LDAP_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "cl4ChangeTargetDN: an error occured while modifying change entry with csn %s: %s. " + "Logging of changes is disabled.\n", csn_as_string(csn,PR_FALSE,s), ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + return CL4_LDAP_ERROR; + } + + if ( ldapi_get_first_changenumber() == (changeNumber) 0L ) + { + ldapi_set_first_changenumber( cnum ); + } + + ldapi_commit_changenumber(cnum); + return CL4_SUCCESS; +} diff --git a/ldap/servers/plugins/replication/cl4_api.h b/ldap/servers/plugins/replication/cl4_api.h new file mode 100644 index 00000000..c0b20e57 --- /dev/null +++ b/ldap/servers/plugins/replication/cl4_api.h @@ -0,0 +1,67 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl4_api.h - minimal interface to 4.0 changelog necessary to link 4.0 changelog + to 5.0 replication + */ + +#ifndef CL4_API_H +#define CL4_API_H + +#include "repl.h" + +/*** Error Codes ***/ +enum +{ + CL4_SUCCESS, + CL4_BAD_DATA, + CL4_BAD_FORMAT, + CL4_NOT_FOUND, + CL4_MEMORY_ERROR, + CL4_CSNPL_ERROR, + CL4_LDAP_ERROR, + CL4_INTERNAL_ERROR +}; + +/*** APIs ***/ +/* Name: cl4Init + Description: initializes 4.0 changelog subsystem + Parameters: none + Return: ???? + */ +int cl4Init (); + +/* Name: cl4WriteOperation + Description: logs operation to 4.0 changelog; operation must go through CD&R engine first + Parameters: op - operation to be logged + + Return: ???? + */ +int cl4WriteOperation (const slapi_operation_parameters *op); + +/* Name: cl4ChangeTargetDN + Description: modifies change entry target dn; should be called for conflicts due to naming collisions; + raw dn should be passed for add operations; normolized dn otherwise. + Parameters: csn - csn of the change entry to be modified + newDN - new target dn of the entry + Return: ???? + */ +int cl4ChangeTargetDN (const CSN* csn, const char *newDN); + +/* Name: cl4AssignChangeNumbers + Description: this function should be called periodically to assign change numbers to changelog + entries. Intended for use with event queue + Parameters: parameters are not currently used + Return: none + */ +void cl4AssignChangeNumbers (time_t when, void *arg); + +/* Name: cl4Cleanup + Description: frees memory held by 4.0 changelog subsystem + Parameters: none + Return: none + */ +void cl4Clean (); +#endif diff --git a/ldap/servers/plugins/replication/cl4_init.c b/ldap/servers/plugins/replication/cl4_init.c new file mode 100644 index 00000000..6c12f0b0 --- /dev/null +++ b/ldap/servers/plugins/replication/cl4_init.c @@ -0,0 +1,349 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* cl4_init.c - implments initialization/cleanup functions for + 4.0 style changelog + */ + +#include <string.h> + +#include "slapi-plugin.h" +#include "cl4.h" +#include "repl.h" + +/* forward declarations */ +static int changelog4_create_be(); +static int changelog4_start_be (); +static int changelog4_close(); +static int changelog4_remove(); + +/* + * Initialise the 4.0 Changelog + */ +int changelog4_init () +{ + int rc= 0; /* OK */ + Slapi_Backend *rbe; + changeNumber first_change = 0UL, last_change = 0UL; + int lderr; + + if (changelog4_create_be() < 0 ) + { + rc= -1; + } + else + { + rc = changelog4_start_be (); + } + + if(rc == 0) + { + rbe = get_repl_backend(); + if(rbe!=NULL) + { + /* We have a Change Log. Check it's valid. */ + /* changelog has to be started before its + data version can be read */ + const char *sdv= get_server_dataversion(); + const char *cdv= get_changelog_dataversion(); + char *suffix = changelog4_get_suffix (); + if(!cdv || strcmp(sdv,cdv)!=0) + { + + /* The SDV and CDV are not the same. The Change Log is invalid. + It must be removed. */ + /* ONREPL - currently we go through this code when the changelog + is first created because we can't tell new backend from the + existing one.*/ + rc = changelog4_close(); + rc = changelog4_remove(); + + /* now restart the changelog */ + changelog4_start_be (); + + create_entity( suffix, "extensibleobject"); + /* Write the Server Data Version onto the changelog suffix entry */ + /* JCMREPL - And the changelog database version number */ + set_changelog_dataversion(sdv); + slapi_ch_free ((void **)&suffix); + + } + } + } + + if(rc != 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, + "An error occurred configuring the changelog database\n" ); + } + + first_change = replog_get_firstchangenum( &lderr ); + last_change = replog_get_lastchangenum( &lderr ); + ldapi_initialize_changenumbers( first_change, last_change ); + + return rc; +} + +static int + +changelog4_close() +{ + int rc= 0 /* OK */; + Slapi_Backend *rbe= get_repl_backend(); + Slapi_PBlock *pb = slapi_pblock_new (); + IFP closefn = NULL; + + rc = slapi_be_getentrypoint (rbe, SLAPI_PLUGIN_CLOSE_FN, (void**)&closefn, pb); + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: backend close entry point is missing. " + "Replication subsystem disabled.\n"); + slapi_pblock_destroy (pb); + set_repl_backend( NULL ); + return -1; + } + + rc = closefn (pb); + + if (rc != 0) + { + + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, "Error: the changelog database could " + "not be closed. Replication subsystem disabled.\n"); + set_repl_backend( NULL ); + rc = -1; + } + + slapi_pblock_destroy (pb); + return rc; + +} + +static int +changelog4_remove() +{ + int rc= 0 /* OK */; + Slapi_Backend *rbe= get_repl_backend(); + Slapi_PBlock *pb = slapi_pblock_new (); + IFP rmdbfn = NULL; + + rc = slapi_be_getentrypoint (rbe, SLAPI_PLUGIN_DB_RMDB_FN, (void**)&rmdbfn, pb); + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: backend rmdb entry point is missing. " + "Replication subsystem disabled.\n"); + slapi_pblock_destroy (pb); + set_repl_backend( NULL ); + return -1; + } + + rc = rmdbfn (pb); + + if (rc != 0) + { + + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, "Error: the changelog database could " + "not be removed. Replication subsystem disabled.\n"); + rc = -1; + } + else + { + slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name, "New database generation computed. " + "Changelog database removed.\n"); + } + + slapi_pblock_destroy (pb); + return rc; +} + +static Slapi_Backend *repl_backend = NULL; + +Slapi_Backend +*get_repl_backend() +{ + return repl_backend; +} + +void +set_repl_backend(Slapi_Backend *be) +{ + repl_backend = be; +} + + +int changelog4_shutdown () +{ + /* ONREPL - will shutdown the backend */ + int rc = 1; + + return rc; +} + +static void changelog4_init_trimming () +{ + char *cl_maxage = changelog4_get_maxage (); + unsigned long cl_maxentries = changelog4_get_maxentries (); + time_t ageval = age_str2time (cl_maxage); + + slapi_ch_free ((void **)&cl_maxage); + + init_changelog_trimming(cl_maxentries, ageval ); +} + + + +/* + * Function: changelog4_create_be + * Arguments: none + * Returns: 0 on success, non-0 on error + * Description: configures changelog backend instance. + */ + +static int +changelog4_create_be() +{ + int i, dir_count = 5; + Slapi_Backend *rbe; + slapi_be_config config; + char *cl_dir = changelog4_get_dir (); + char *cl_suffix; + + if ( cl_dir == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: no directory specified for changelog database.\n"); + return -1; + } + + cl_suffix = changelog4_get_suffix (); + + if ( cl_suffix == NULL ) { + slapi_ch_free ((void **)&cl_dir); + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: no suffix specified for changelog database.\n"); + return -1; + } + + /* setup configuration parameters for backend initialization */ + config.type = CHANGELOG_LDBM_TYPE; + config.suffix = cl_suffix; + config.is_private = 1; /* yes */ + config.log_change = 0; /* no */ + config.directives = (slapi_config_directive*)slapi_ch_calloc( + dir_count, sizeof(slapi_config_directive)); + config.dir_count = dir_count; + + for (i = 0; i < dir_count; i++) + { + config.directives[i].file_name = "(internal)"; + config.directives[i].lineno = 0; + } + + /* setup indexes */ + config.directives[0].argv = NULL; + config.directives[0].argc = 3; + charray_add( &(config.directives[0].argv), slapi_ch_strdup( "index" )); + charray_add( &(config.directives[0].argv), slapi_ch_strdup( attr_changenumber )); + charray_add( &(config.directives[0].argv), slapi_ch_strdup( "eq" )); + + /* Set up the database directory */ + config.directives[1].argv = NULL; + config.directives[1].argc = 2; + charray_add( &(config.directives[1].argv), slapi_ch_strdup( "directory" )); + charray_add( &(config.directives[1].argv), slapi_ch_strdup( cl_dir )); + + /* Override the entry cache size */ + config.directives[2].argv = NULL; + config.directives[2].argc = 2; + charray_add( &(config.directives[2].argv), slapi_ch_strdup( "cachesize" )); + charray_add( &(config.directives[2].argv), slapi_ch_strdup( "10" )); + + /* Override the database cache size */ + config.directives[3].argv = NULL; + config.directives[3].argc = 2; + charray_add( &(config.directives[3].argv), slapi_ch_strdup( "dbcachesize" )); + charray_add( &(config.directives[3].argv), slapi_ch_strdup( "1000000" )); + + /* Override the allids threshold */ + config.directives[4].argv = NULL; + config.directives[4].argc = 2; + charray_add( &(config.directives[4].argv), slapi_ch_strdup( "allidsthreshold" )); + /* assumes sizeof(int) >= 32 bits */ + charray_add( &(config.directives[4].argv), slapi_ch_strdup( "2147483647" )); + + /* rbe = slapi_be_create_instance(&config, LDBM_TYPE); */ + rbe= NULL; + + /* free memory allocated to argv */ + for (i = 0; i < dir_count; i++) + { + charray_free (config.directives[i].argv); + } + + slapi_ch_free ((void **)&config.directives); + slapi_ch_free ((void **)&cl_dir); + slapi_ch_free ((void **)&cl_suffix); + + if (rbe == NULL) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: failed to create changelog backend. " + "Replication disabled.\n"); + return -1; + } + + set_repl_backend (rbe); + + changelog4_init_trimming (); + + return 0; +} + +/* Name: changelog4_start_be + * Parameters: none + * Return: 0 if successful, non 0 otherwise + * Description: starts the changelog backend; backend must be configured + * first via call to changelog4_create_be + */ +static int +changelog4_start_be () +{ + int rc; + IFP startfn = NULL; + Slapi_PBlock *pb; + Slapi_Backend *rbe = get_repl_backend (); + + if (rbe) + { + pb = slapi_pblock_new(); + rc = slapi_be_getentrypoint(rbe, SLAPI_PLUGIN_START_FN, (void**)&startfn, pb); + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: backend start entry point is missing. " + "Replication subsystem disabled.\n"); + slapi_pblock_destroy (pb); + set_repl_backend( NULL ); + return -1; + } + + rc = startfn (pb); + slapi_pblock_destroy (pb); + + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: Failed to start changelog backend. " + "Replication subsystem disabled.\n"); + set_repl_backend( NULL ); + return -1; + } + } + + return 0; +} + diff --git a/ldap/servers/plugins/replication/cl5.h b/ldap/servers/plugins/replication/cl5.h new file mode 100644 index 00000000..a80c666b --- /dev/null +++ b/ldap/servers/plugins/replication/cl5.h @@ -0,0 +1,38 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5.h - changelog related function */ + +#ifndef CL5_H +#define CL5_H + +#include "cl5_api.h" /* changelog access APIs */ + +typedef struct changelog5Config +{ + char *dir; +/* These 2 parameters are needed for changelog trimming. Already present in 5.0 */ + char *maxAge; + int maxEntries; +/* the changelog DB configuration parameters are defined as CL5DBConfig in cl5_api.h */ + CL5DBConfig dbconfig; +}changelog5Config; + +/* initializes changelog*/ +int changelog5_init(); +/* cleanups changelog data */ +void changelog5_cleanup(); +/* initializes changelog configurationd */ +int changelog5_config_init(); +/* cleanups config data */ +void changelog5_config_cleanup(); +/* reads changelog configuration */ +int changelog5_read_config (changelog5Config *config); +/* cleanups the content of the config structure */ +void changelog5_config_done (changelog5Config *config); +/* frees the content and the config structure */ +void changelog5_config_free (changelog5Config **config); + +#endif diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c new file mode 100644 index 00000000..792d3646 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_api.c @@ -0,0 +1,6512 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* cl5_api.c - implementation of 5.0 style changelog API */ + +#include <errno.h> +#include <sys/stat.h> +#if defined( OS_solaris ) || defined( hpux ) +#include <sys/types.h> +#include <sys/statvfs.h> +#endif +#if defined( linux ) +#include <sys/vfs.h> +#endif + + +#include "cl5_api.h" +#include "plhash.h" + +#include "db.h" +#include "cl5_clcache.h" /* To use the Changelog Cache */ +#include "repl5.h" /* for agmt_get_consumer_rid() */ + +#define CL5_TYPE "Changelog5" /* changelog type */ +#define VERSION_SIZE 127 /* size of the buffer to hold changelog version */ +#define GUARDIAN_FILE "guardian" /* name of the guardian file */ +#define VERSION_FILE "DBVERSION" /* name of the version file */ +#define MAX_TRIALS 50 /* number of retries on db operations */ +#define V_5 5 /* changelog entry version */ +#define CHUNK_SIZE 64*1024 +#define DBID_SIZE 64 +#define FILE_SEP "_" /* separates parts of the db file name */ + +#define T_CSNSTR "csn" +#define T_UNIQUEIDSTR "nsuniqueid" +#define T_PARENTIDSTR "parentuniqueid" +#define T_NEWSUPERIORDNSTR "newsuperiordn" +#define T_NEWSUPERIORIDSTR "newsuperioruniqueid" +#define T_REPLGEN "replgen" + +#define ENTRY_COUNT_TIME 111 /* this time is used to construct csn + used to store/retrieve entry count */ +#define PURGE_RUV_TIME 222 /* this time is used to construct csn + used to store purge RUV vector */ +#define MAX_RUV_TIME 333 /* this time is used to construct csn + used to store upper boundary RUV vector */ + +#define DB_EXTENSION_DB3 "db3" +#define DB_EXTENSION "db4" + +#define HASH_BACKETS_COUNT 16 /* number of buckets in a hash table */ + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4100 +#define DEFAULT_DB_OP_FLAGS DB_AUTO_COMMIT +#define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval) \ +{ \ + if (((oflags) & DB_INIT_TXN) && ((oflags) & DB_INIT_LOG)) \ + { \ + (rval) = (db)->open((db), (txnid), (file), (database), (type), (flags)|DB_AUTO_COMMIT, (mode)); \ + } \ + else \ + { \ + (rval) = (db)->open((db), (txnid), (file), (database), (type), (flags), (mode)); \ + } \ +} +#else /* older then db 41 */ +#define DEFAULT_DB_OP_FLAGS 0 +#define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval) \ + (rval) = (db)->open((db), (file), (database), (type), (flags), (mode)) +#endif + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4000 +#define DB_ENV_SET_REGION_INIT(env) (env)->set_flags((env), DB_REGION_INIT, 1) +#define DB_ENV_SET_TAS_SPINS(env, tas_spins) \ + (env)->set_tas_spins((env), (tas_spins)) +#define TXN_BEGIN(env, parent_txn, tid, flags) \ + (env)->txn_begin((env), (parent_txn), (tid), (flags)) +#define TXN_COMMIT(txn, flags) (txn)->commit((txn), (flags)) +#define TXN_ABORT(txn) (txn)->abort(txn) +#define TXN_CHECKPOINT(env, kbyte, min, flags) \ + (env)->txn_checkpoint((env), (kbyte), (min), (flags)) +#define MEMP_STAT(env, gsp, fsp, flags, malloc) \ + (env)->memp_stat((env), (gsp), (fsp), (flags)) +#define MEMP_TRICKLE(env, pct, nwrotep) \ + (env)->memp_trickle((env), (pct), (nwrotep)) +#define LOG_ARCHIVE(env, listp, flags, malloc) \ + (env)->log_archive((env), (listp), (flags)) +#define LOG_FLUSH(env, lsn) (env)->log_flush((env), (lsn)) +#define LOCK_DETECT(env, flags, atype, aborted) \ + (env)->lock_detect((env), (flags), (atype), (aborted)) + +#else /* older than db 4.0 */ +#define DB_ENV_SET_REGION_INIT(env) db_env_set_region_init(1) +#define DB_ENV_SET_TAS_SPINS(env, tas_spins) \ + db_env_set_tas_spins((tas_spins)) +#define TXN_BEGIN(env, parent_txn, tid, flags) \ + txn_begin((env), (parent_txn), (tid), (flags)) +#define TXN_COMMIT(txn, flags) txn_commit((txn), (flags)) +#define TXN_ABORT(txn) txn_abort((txn)) +#define TXN_CHECKPOINT(env, kbyte, min, flags) \ + txn_checkpoint((env), (kbyte), (min), (flags)) +#define MEMP_TRICKLE(env, pct, nwrotep) memp_trickle((env), (pct), (nwrotep)) +#define LOG_FLUSH(env, lsn) log_flush((env), (lsn)) +#define LOCK_DETECT(env, flags, atype, aborted) \ + lock_detect((env), (flags), (atype), (aborted)) + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300 +#define MEMP_STAT(env, gsp, fsp, flags, malloc) memp_stat((env), (gsp), (fsp)) +#define LOG_ARCHIVE(env, listp, flags, malloc) \ + log_archive((env), (listp), (flags)) + +#else /* older than db 3.3 */ +#define MEMP_STAT(env, gsp, fsp, flags, malloc) \ + memp_stat((env), (gsp), (fsp), (malloc)) +#define LOG_ARCHIVE(env, listp, flags, malloc) \ + log_archive((env), (listp), (flags), (malloc)) +#endif +#endif +/* + * The defult thread stacksize for nspr21 is 64k. For OSF, we require + * a larger stacksize as actual storage allocation is higher i.e + * pointers are allocated 8 bytes but lower 4 bytes are used. + * The value 0 means use the default stacksize. + */ +#if defined (OSF1) || defined (__LP64__) || defined (_LP64) /* 64-bit architectures need bigger stacks */ +#define DEFAULT_THREAD_STACKSIZE 131072L +#else +#define DEFAULT_THREAD_STACKSIZE 0 +#endif + +#ifdef _WIN32 +#define FILE_CREATE_MODE S_IREAD | S_IWRITE +#define DIR_CREATE_MODE 0755 +#else /* _WIN32 */ +#define FILE_CREATE_MODE S_IRUSR | S_IWUSR +#define DIR_CREATE_MODE 0755 +#endif + +#define NO_DISK_SPACE 1024 +#define MIN_DISK_SPACE 10485760 /* 10 MB */ + +/***** Data Definitions *****/ + +/* possible changelog open modes */ +typedef enum +{ + CL5_OPEN_NONE, /* nothing specified */ + CL5_OPEN_NORMAL, /* open for normal read/write use */ + CL5_OPEN_RESTORE_RECOVER, /* restore from archive and recover */ + CL5_OPEN_RESTORE, /* restore, but no recovery */ + CL5_OPEN_LDIF2CL, /* open as part of ldif2cl: no locking, + recovery, checkpointing */ + CL5_OPEN_CLEAN_RECOVER /* remove env after recover open (upgrade) */ +} CL5OpenMode; + +#define DB_FILE_DELETED 0x1 +#define DB_FILE_INIT 0x2 +/* this structure represents one changelog file, Each changelog file contains + changes applied to a single backend. Files are named by the database id */ +typedef struct cl5dbfile +{ + char *name; /* file name (with the extension) */ + char *replGen; /* replica generation of the data */ + char *replName; /* replica name */ + DB *db; /* db handle to the changelog file*/ + int entryCount; /* number of entries in the file */ + int flags; /* currently used to mark the file as deleted + * or as initialized */ + RUV *purgeRUV; /* ruv to which the file has been purged */ + RUV *maxRUV; /* ruv that marks the upper boundary of the data */ + char *semaName; /* semaphore name */ + PRSem *sema; /* semaphore for max concurrent cl writes */ +}CL5DBFile; + +/* structure that allows to iterate through entries to be sent to a consumer + that originated on a particular supplier. */ +struct cl5replayiterator +{ + Object *fileObj; + CLC_Buffer *clcache; /* changelog cache */ + ReplicaId consumerRID; /* consumer's RID */ + const RUV *consumerRuv; /* consumer's update vector */ + Object *supplierRuvObj;/* supplier's update vector object */ +}; + +typedef struct cl5iterator +{ + DBC *cursor; /* current position in the db file */ + Object *file; /* handle to release db file object */ +}CL5Iterator; + +/* changelog trimming configuration */ +typedef struct cl5trim +{ + time_t maxAge; /* maximum entry age in seconds */ + int maxEntries; /* maximum number of entries across all changelog files */ + PRLock* lock; /* controls access to trimming configuration */ +} CL5Trim; + +/* this structure defines 5.0 changelog internals */ +typedef struct cl5desc +{ + char *dbDir; /* absolute path to changelog directory */ + DB_ENV *dbEnv; /* db environment shared by all db files */ + int dbEnvOpenFlags;/* openflag used for env->open */ + Objset *dbFiles; /* ref counted set of changelog files (CL5DBFile) */ + PRLock *fileLock; /* ensures that changelog file is not added twice */ + CL5OpenMode dbOpenMode; /* how we open db */ + CL5DBConfig dbConfig; /* database configuration params */ + CL5Trim dbTrim; /* trimming parameters */ + CL5State dbState; /* changelog current state */ + PRRWLock *stLock; /* lock that controls access to the changelog state */ + PRBool dbRmOnClose;/* indicates whether changelog should be removed when + it is closed */ + PRBool fatalError; /* bad stuff happened like out of disk space; don't + write guardian file on close - UnUsed so far */ + int threadCount;/* threads that globally access changelog like + deadlock detection, etc. */ + PRLock *clLock; /* Lock associated to clVar, used to notify threads on close */ + PRCondVar *clCvar; /* Condition Variable used to notify threads on close */ +} CL5Desc; + +typedef void (*VFP)(void *); + +int g_get_shutdown(); /* declared in proto-slap.h */ + +/***** Global Variables *****/ +static CL5Desc s_cl5Desc; + +/***** Forward Declarations *****/ + +/* changelog initialization and cleanup */ +static int _cl5Open (const char *dir, const CL5DBConfig *config, CL5OpenMode openMode); +static int _cl5AppInit (PRBool *didRecovery); +static int _cl5DBOpen (); +static void _cl5SetDefaultDBConfig (); +static void _cl5SetDBConfig (const CL5DBConfig *config); +static void _cl5InitDBEnv(DB_ENV *dbEnv); +static int _cl5CheckDBVersion (); +static int _cl5ReadDBVersion (const char *dir, char *clVersion); +static int _cl5WriteDBVersion (); +static int _cl5CheckGuardian (); +static int _cl5ReadGuardian (char *buff); +static int _cl5WriteGuardian (); +static int _cl5RemoveGuardian (); +static void _cl5Close (); +static int _cl5Delete (const char *dir, PRBool rmDir); +static void _cl5DBClose (); + +/* thread management */ +static int _cl5DispatchDBThreads (); +static int _cl5AddThread (); +static void _cl5RemoveThread (); +static int _cl5DeadlockMain (void *param); +static int _cl5CheckpointMain (void *param); +static int _cl5TrickleMain (void *param); + +/* functions that work with individual changelog files */ +static int _cl5NewDBFile (const char *replName, const char *replGen, CL5DBFile** dbFile); +static int _cl5DBOpenFile (Object *replica, Object **obj, PRBool checkDups); +static int _cl5DBOpenFileByReplicaName (const char *replName, const char *replGen, + Object **obj, PRBool checkDups); +static void _cl5DBCloseFile (void **data); +static void _cl5DBDeleteFile (Object *obj); +static void _cl5DBFileInitialized (Object *obj); +static int _cl5GetDBFile (Object *replica, Object **obj); +static int _cl5GetDBFileByReplicaName (const char *replName, const char *replGen, + Object **obj); +static int _cl5AddDBFile (CL5DBFile *file, Object **obj); +static int _cl5CompareDBFile (Object *el1, const void *el2); +static int _cl5CopyDBFiles (const char *srcDir, const char *distDir, Object **replicas); +static char* _cl5Replica2FileName (Object *replica); +static char* _cl5MakeFileName (const char *replName, const char *replGen); +static PRBool _cl5FileName2Replica (const char *fileName, Object **replica); +static int _cl5ExportFile (PRFileDesc *prFile, Object *obj); +static PRBool _cl5ReplicaInList (Object *replica, Object **replicas); + +/* data storage and retrieval */ +static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len); +static int _cl5WriteOperation(const char *replName, const char *replGen, + const slapi_operation_parameters *op, PRBool local); +static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid); +static int _cl5GetNextEntry (CL5Entry *entry, void *iterator); +static int _cl5CurrentDeleteEntry (void *iterator); +static PRBool _cl5IsValidIterator (const CL5Iterator *iterator); +static int _cl5GetOperation (Object *replica, slapi_operation_parameters *op); +static const char* _cl5OperationType2Str (int type); +static int _cl5Str2OperationType (const char *str); +static void _cl5WriteString (const char *str, char **buff); +static void _cl5ReadString (char **str, char **buff); +static void _cl5WriteMods (LDAPMod **mods, char **buff); +static void _cl5WriteMod (LDAPMod *mod, char **buff); +static int _cl5ReadMods (LDAPMod ***mods, char **buff); +static int _cl5ReadMod (Slapi_Mod *mod, char **buff); +static int _cl5GetModsSize (LDAPMod **mods); +static int _cl5GetModSize (LDAPMod *mod); +static void _cl5ReadBerval (struct berval *bv, char** buff); +static void _cl5WriteBerval (struct berval *bv, char** buff); +static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size); +static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size); + +/* replay iteration */ +static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator); +static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv, + Object *replica, Object *fileObject, CL5ReplayIterator **iterator); +static int _cl5CheckMissingCSN (const CSN *minCsn, const RUV *supplierRUV, CL5DBFile *file); + +/* changelog trimming */ +static int _cl5TrimInit (); +static void _cl5TrimCleanup (); +static int _cl5TrimMain (void *param); +static void _cl5DoTrimming (); +static void _cl5TrimFile (Object *obj, long *numToTrim); +static PRBool _cl5CanTrim (time_t time, long *numToTrim); +static int _cl5ReadRUV (const char *replGen, Object *obj, PRBool purge); +static int _cl5WriteRUV (CL5DBFile *file, PRBool purge); +static int _cl5ConstructRUV (const char *replGen, Object *obj, PRBool purge); +static int _cl5UpdateRUV (Object *obj, CSN *csn, PRBool newReplica, PRBool purge); +static int _cl5GetRUV2Purge2 (Object *fileObj, RUV **ruv); + +/* db error processing */ +static void _cl5DBLogPrint(const char* prefix, char *buffer); + +/* bakup/recovery, import/export */ +static PRBool _cl5IsLogFile (const char *name); +static int _cl5Recover (int open_flags, DB_ENV *dbEnv); +static int _cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, + char **replGen); +static int _cl5Operation2LDIF (const slapi_operation_parameters *op, const char *replGen, + char **ldifEntry, PRInt32 *lenLDIF); + +/* entry count */ +static int _cl5GetEntryCount (CL5DBFile *file); +static int _cl5WriteEntryCount (CL5DBFile *file); + +/* misc */ +static char* _cl5GetHelperEntryKey (int type, char *csnStr); +static Object* _cl5GetReplica (const slapi_operation_parameters *op, const char* replGen); +static int _cl5FileEndsWith(const char *filename, const char *ext); + +/* Callback function for libdb to spit error info into our log */ +static void dblayer_log_print(const char* prefix, char *buffer) +{ + /* We ignore the prefix since we know who we are anyway */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "libdb: %s\n", buffer); +} + +static PRLock *cl5_diskfull_lock = NULL; +static int cl5_diskfull_flag = 0; + +static void cl5_set_diskfull(); +static void cl5_set_no_diskfull(); + +/***** Module APIs *****/ + +/* Name: cl5Init + Description: initializes changelog module; must be called by a single thread + before any other changelog function. + Parameters: none + Return: CL5_SUCCESS if function is successful; + CL5_SYSTEM_ERROR error if NSPR call fails. + */ +int cl5Init () +{ + s_cl5Desc.stLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "state_lock"); + if (s_cl5Desc.stLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Init: failed to create state lock; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + if ((s_cl5Desc.clLock = PR_NewLock()) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Init: failed to create on close lock; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + + } + if ((s_cl5Desc.clCvar = PR_NewCondVar(s_cl5Desc.clLock)) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Init: failed to create on close cvar; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (( clcache_init (&s_cl5Desc.dbEnv) != 0 )) { + return CL5_SYSTEM_ERROR; + } + + s_cl5Desc.dbState = CL5_STATE_CLOSED; + s_cl5Desc.fatalError = PR_FALSE; + s_cl5Desc.dbRmOnClose = PR_FALSE; + s_cl5Desc.threadCount = 0; + + if (NULL == cl5_diskfull_lock) + { + cl5_diskfull_lock = PR_NewLock (); + } + + return CL5_SUCCESS; +} + +/* Name: cl5Cleanup + Description: performs cleanup of the changelog module; must be called by a single + thread; it closes changelog if it is still open. + Parameters: none + Return: none + */ +void cl5Cleanup () +{ + /* close db if it is still open */ + if (s_cl5Desc.dbState == CL5_STATE_OPEN) + { + cl5Close (); + } + + if (s_cl5Desc.stLock) + PR_DestroyRWLock (s_cl5Desc.stLock); + s_cl5Desc.stLock = NULL; + + if (cl5_diskfull_lock) + { + PR_DestroyLock (cl5_diskfull_lock); + cl5_diskfull_lock = NULL; + } + + memset (&s_cl5Desc, 0, sizeof (s_cl5Desc)); +} + +/* Name: cl5Open + Description: opens changelog; must be called after changelog is + initialized using cl5Init. It is thread safe and the second + call is ignored. + Parameters: dir - changelog dir + config - db configuration parameters; currently not used + Return: CL5_SUCCESS if successfull; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_STATE if changelog is not initialized; + CL5_BAD_DBVERSION if dbversion file is missing or has unexpected data + CL5_SYSTEM_ERROR if NSPR error occured (during db directory creation); + CL5_MEMORY_ERROR if memory allocation fails; + CL5_DB_ERROR if db initialization fails. + */ +int cl5Open (const char *dir, const CL5DBConfig *config) +{ + int rc; + + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Open: null directory\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* prevent state from changing */ + PR_RWLock_Wlock (s_cl5Desc.stLock); + + /* already open - ignore */ + if (s_cl5Desc.dbState == CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Open: changelog already opened; request ignored\n"); + rc = CL5_SUCCESS; + goto done; + } + else if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: invalid state - %d\n", s_cl5Desc.dbState); + rc = CL5_BAD_STATE; + goto done; + } + + rc = _cl5Open (dir, config, CL5_OPEN_NORMAL); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: failed to open changelog\n"); + goto done; + } + + /* dispatch global threads like deadlock detection, trimming, etc */ + rc = _cl5DispatchDBThreads (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: failed to start database monitoring threads\n"); + + _cl5Close (); + } + else + { + s_cl5Desc.dbState = CL5_STATE_OPEN; + } + +done:; + PR_RWLock_Unlock (s_cl5Desc.stLock); + + return rc; +} + +/* Name: cl5Close + Description: closes changelog; waits until all threads are done using changelog; + call is ignored if changelog is already closed. + Parameters: none + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if db is not in the open or closed state; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_DB_ERROR if db shutdown fails + */ +int cl5Close () +{ + int rc = CL5_SUCCESS; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Close: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + PR_RWLock_Wlock (s_cl5Desc.stLock); + + /* already closed - ignore */ + if (s_cl5Desc.dbState == CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Close: changelog closed; request ignored\n"); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_SUCCESS; + } + else if (s_cl5Desc.dbState != CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Close: invalid state - %d\n", s_cl5Desc.dbState); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + /* signal changelog closing to all threads */ + s_cl5Desc.dbState = CL5_STATE_CLOSING; + + PR_Lock(s_cl5Desc.clLock); + PR_NotifyCondVar(s_cl5Desc.clCvar); + PR_Unlock(s_cl5Desc.clLock); + + _cl5Close (); + + s_cl5Desc.dbState = CL5_STATE_CLOSED; + + PR_RWLock_Unlock (s_cl5Desc.stLock); + + return rc; +} + +/* Name: cl5Delete + Description: removes changelog; changelog must be in the closed state. + Parameters: dir - changelog directory + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not in closed state; + CL5_BAD_DATA if invalid directory supplied + CL5_SYSTEM_ERROR if NSPR call fails + */ +int cl5Delete (const char *dir) +{ + int rc; + + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, "cl5Delete: null directory\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Delete: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + PR_RWLock_Wlock (s_cl5Desc.stLock); + + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Delete: invalid state - %d\n", s_cl5Desc.dbState); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + rc = _cl5Delete (dir, PR_TRUE /* remove changelog dir */); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Delete: failed to remove changelog\n"); + } + + PR_RWLock_Unlock (s_cl5Desc.stLock); + return rc; +} + +/* Name: cl5OpenDB + Description: opens changelog file for specified file + Parameters: replica - replica whose file we wish to open + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + */ +int cl5OpenDB (Object *replica) +{ + int rc; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5OpenDB: null replica\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5OpenDB: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5DBOpenFile (replica, NULL /* file object */, PR_TRUE /* check for duplicates */); + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5CloseDB + Description: closes changelog file for the specified replica + Parameters: replica - replica whose file we wish to close + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5CloseDB (Object *replica) +{ + int rc; + Object *obj; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5CloseDB: null replica\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CloseDB: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + rc = objset_remove_obj(s_cl5Desc.dbFiles, obj); + object_release (obj); + } + else + { + Replica *r; + + r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CloseDB: failed to close file for replica at (%s)\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5DeleteDB + Description: asynchronously removes changelog file for the specified replica. + The file is physically removed when it is no longer in use. + This function is called when a backend is removed or reloaded. + Parameters: replica - replica whose file we wish to delete + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5DeleteDB (Object *replica) +{ + Object *obj; + int rc; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5DeleteDB: invalid database id\n"); + return CL5_BAD_DATA; + } + + /* changelog is not initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDB: " + "changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + _cl5DBDeleteFile (obj); + } + else + { + Replica *r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDB: " + "file for replica at (%s) not found\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5DeleteDBSync + Description: The same as cl5DeleteDB except the function does not return + until the file is removed. +*/ +int cl5DeleteDBSync (Object *replica) +{ + Object *obj; + int rc; + CL5DBFile *file; + char fName [MAXPATHLEN + 1]; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5DeleteDBSync: invalid database id\n"); + return CL5_BAD_DATA; + } + + /* changelog is not initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDBSync: " + "changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, file->name); + + _cl5DBDeleteFile (obj); + + /* wait until the file is gone */ + while (PR_Access (fName, PR_ACCESS_EXISTS) == PR_SUCCESS) + { + DS_Sleep (PR_MillisecondsToInterval(100)); + } + + } + else + { + Replica *r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDBSync: " + "file for replica at (%s) not found\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5GetUpperBoundRUV + Description: retrieves vector for that represnts the upper bound of the changes for a replica. + Parameters: r - replica for which the purge vector is requested + ruv - contains a copy of the purge ruv if function is successful; + unchanged otherwise. It is responsobility pf the caller to free + the ruv when it is no longer is in use + Return: CL5_SUCCESS if function is successfull + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND, if changelog file for replica is not found + */ +int cl5GetUpperBoundRUV (Replica *r, RUV **ruv) +{ + int rc; + Object *r_obj, *file_obj; + CL5DBFile *file; + + if (r == NULL || ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetUpperBoundRUV: invalid parameters\n"); + return CL5_BAD_DATA; + } + + /* changelog is not initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetUpperBoundRUV: " + "changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + /* create a temporary replica object because of the interface we have */ + r_obj = object_new (r, NULL); + + rc = _cl5GetDBFile (r_obj, &file_obj); + if (rc == CL5_SUCCESS) + { + file = (CL5DBFile*)object_get_data (file_obj); + PR_ASSERT (file && file->maxRUV); + + *ruv = ruv_dup (file->maxRUV); + + object_release (file_obj); + } + + object_release (r_obj); + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5Backup + Description: makes a backup of the changelog including *.db2, + log files, and dbversion. Can be called with the changelog in either open or + closed state. + Parameters: bkDir - directory to which the data is backed up; + created if it does not exist + replicas - optional list of replicas whose changes should be backed up; + if the list is NULL, entire changelog is backed up. + Return: CL5_SUCCESS if function is successful; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_STATE if changelog has not been initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call or file copy failes. + */ +int cl5Backup (const char *bkDir, Object **replicas) +{ + int rc; + char **list = NULL; + char **logFile; + char srcFile [MAXPATHLEN + 1]; + char destFile[MAXPATHLEN + 1]; + DB_TXN *txn = NULL; + + if (bkDir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Backup: null backup directory\n"); + return CL5_BAD_DATA; + } + + /* changelog must be initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + /* create backup directory if necessary */ + rc = cl5CreateDirIfNeeded (bkDir); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to create backup directory\n"); + goto done; + } + + /* start transaction to tempararily prevent transaction log + from being trimmed + */ + rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL /*pid*/, &txn, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Backup: starting changelog backup from %s to %s ...\n", s_cl5Desc.dbDir, bkDir); + + /* The following files are backed up: *.<dbext>, log files, dbversion file */ + + /* copy db file */ + /* ONREPL currently, list of replicas is ignored because db code can't handle + discrepancy between transaction log and present files; should be fixed before 5.0 ships */ + rc = _cl5CopyDBFiles (s_cl5Desc.dbDir, bkDir, replicas); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup : failed to copy database files from %s to %s\n", s_cl5Desc.dbDir, bkDir); + goto done; + } + + /* copy db log files */ + rc = LOG_ARCHIVE(s_cl5Desc.dbEnv, &list, DB_ARCH_LOG, malloc); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to get list of log files; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + if (list) + { + logFile = list; + while (*logFile) + { + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, *logFile); + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", bkDir, *logFile); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to copy %s\n", *logFile); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + logFile ++; + } + + free(list); + } + + /* now, copy the version file */ + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, VERSION_FILE); + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", bkDir, VERSION_FILE); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to copy %s\n", VERSION_FILE); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + rc = CL5_SUCCESS; + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Backup: changelog backup is finished \n"); +done:; + if (txn && TXN_ABORT (txn) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + } + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5Restore + Description: restores changelog from the backed up copy. Changelog must be ibnitalized and closed. + Parameters: clDir - changelog dir + bkDir - directory that contains the backup + replicas - optional list of replicas whose changes should be recovered; + if the list is NULL, entire changelog is recovered. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call of file copy fails + */ +int cl5Restore (const char *clDir, const char *bkDir, Object **replicas) +{ + int rc; + char srcFile[MAXPATHLEN + 1]; + char destFile[MAXPATHLEN + 1]; + PRDir *prDir; + PRDirEntry *prDirEntry; + int seenLog = 0; /* Tells us if we restored any logfiles */ + + if (clDir == NULL || bkDir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Restore: null parameter\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* prevent state change while recovery is in progress */ + PR_RWLock_Wlock (s_cl5Desc.stLock); + + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: changelog must be closed\n"); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Restore: starting changelog recovery from %s to %s ...\n", bkDir, clDir); + + /* delete current changelog content */ + rc = _cl5Delete (clDir, PR_FALSE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: failed to remove changelog\n"); + goto done; + } + + /* We copy the files over from the staging area */ + prDir = PR_OpenDir(bkDir); + if (prDir == NULL) + { + rc = CL5_SYSTEM_ERROR; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: unable to access backup directory %s; NSPR error - %d\n", + bkDir, PR_GetError ()); + goto done; + } + + while (NULL != (prDirEntry = PR_ReadDir(prDir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == prDirEntry->name) /* NSPR doesn't behave like the docs say it should */ + { + break; + } + + /* Log files have names of the form "log.xxxxx". We detect these by looking for + the prefix "log." and the lack of the ".<dbext>" suffix */ + seenLog |= _cl5IsLogFile(prDirEntry->name); + + /* ONREPL currently, list of replicas is ignored because db code can't handle discrepancy + between transaction log and present files; this should change before 5.0 ships */ + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", clDir, prDirEntry->name); + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", bkDir, prDirEntry->name); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + rc = CL5_SYSTEM_ERROR; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: failed to copy %s\n", prDirEntry->name); + PR_CloseDir(prDir); + goto done; + } + } + + PR_CloseDir(prDir); + + /* now open and close changelog to create all necessary files */ + if (seenLog) + rc = _cl5Open (clDir, NULL, CL5_OPEN_RESTORE_RECOVER); + else + rc = _cl5Open (clDir, NULL, CL5_OPEN_RESTORE); + + if (rc == CL5_SUCCESS) + { + _cl5Close (); + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Restore: changelog recovery is finished \n"); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: failed open changelog after recovery\n"); + } + +done:; + PR_RWLock_Unlock (s_cl5Desc.stLock); + return rc; +} + +/* Name: cl5ExportLDIF + Description: dumps changelog to an LDIF file; changelog can be open or closed. + Parameters: clDir - changelog dir + ldifFile - full path to ldif file to write + replicas - optional list of replicas whose changes should be exported; + if the list is NULL, entire changelog is exported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is not initialized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ExportLDIF (const char *ldifFile, Object **replicas) +{ + int i; + int rc; + PRFileDesc *prFile = NULL; + Object *obj; + + if (ldifFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ExportLDIF: null ldif file name\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ExportLDIF: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + prFile = PR_Open (ldifFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600); + if (prFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ExportLDIF: failed to open (%s) file; NSPR error - %d\n", + ldifFile, PR_GetError ()); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5ExportLDIF: starting changelog export to (%s) ...\n", ldifFile); + + if (replicas) /* export only selected files */ + { + for (i = 0; replicas[i]; i++) + { + rc = _cl5GetDBFile (replicas[i], &obj); + if (rc == CL5_SUCCESS) + { + rc = _cl5ExportFile (prFile, obj); + object_release (obj); + } + else + { + Replica *r = (Replica*)object_get_data (replicas[i]); + + PR_ASSERT (r); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5ExportLDIF: " + "failed to locate changelog file for replica at (%s)\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + } + } + else /* export all files */ + { + for (obj = objset_first_obj(s_cl5Desc.dbFiles); obj; + obj = objset_next_obj(s_cl5Desc.dbFiles, obj)) + { + rc = _cl5ExportFile (prFile, obj); + object_release (obj); + } + } + + rc = CL5_SUCCESS; +done:; + + _cl5RemoveThread (); + + if (rc == CL5_SUCCESS) + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5ExportLDIF: changelog export is finished.\n"); + + if (prFile) + PR_Close (prFile); + + return rc; +} + +/* Name: cl5ImportLDIF + Description: imports ldif file into changelog; changelog must be in the closed state + Parameters: clDir - changelog dir + ldifFile - absolute path to the ldif file to import + replicas - optional list of replicas whose data should be imported; + if the list is NULL, all data in the file is imported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not inititalized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ImportLDIF (const char *clDir, const char *ldifFile, Object **replicas) +{ + FILE *file; + int rc; + char *buff; + int lineno = 0; + slapi_operation_parameters op; + Object *replica = NULL; + char *replGen = NULL; + + /* validate params */ + if (ldifFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: null ldif file name\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that nobody change changelog state while import is in progress */ + PR_RWLock_Wlock (s_cl5Desc.stLock); + + /* make sure changelog is closed */ + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: invalid state - %d \n", s_cl5Desc.dbState); + + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + /* open LDIF file */ + file = fopen (ldifFile, "r"); /* XXXggood Does fopen reliably work if > 255 files open? */ + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to open (%s) ldif file; system error - %d\n", + ldifFile, errno); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + /* remove changelog */ + rc = _cl5Delete (clDir, PR_FALSE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to remove changelog\n"); + goto done; + } + + /* open changelog */ + rc = _cl5Open (clDir, NULL, CL5_OPEN_LDIF2CL); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to open changelog\n"); + goto done; + } + + /* read entries and write them to changelog */ + while ((buff = ldif_get_entry( file, &lineno )) != NULL) + { + rc = _cl5LDIF2Operation (buff, &op, &replGen); + slapi_ch_free ((void**)&buff); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to convert LDIF fragment to LDAP operation; " + "end of fragment line number - %d\n", lineno); + goto done; + } + + /* if we perform selective import, check if the operation should be wriiten to changelog */ + replica = _cl5GetReplica (&op, replGen); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to locate replica for target dn (%s) and " + "replica generation %s\n", op.target_address.dn, replGen); + + slapi_ch_free ((void**)&replGen); + operation_parameters_done (&op); + goto done; + } + + if (!replicas || _cl5ReplicaInList (replica, replicas)) + { + /* write operation creates the file if it does not exist */ + rc = _cl5WriteOperation (replica_get_name ((Replica*)object_get_data(replica)), + replGen, &op, 1); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to write operation to the changelog\n"); + object_release (replica); + slapi_ch_free ((void**)&replGen); + operation_parameters_done (&op); + goto done; + } + } + + object_release (replica); + slapi_ch_free ((void**)&replGen); + operation_parameters_done (&op); + } + +done:; + _cl5Close (); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return rc; +} + +/* Name: cl5GetState + Description: returns database state + Parameters: none + Return: changelog state + */ +int cl5GetState () +{ + return s_cl5Desc.dbState; +} + +/* Name: cl5ConfigTrimming + Description: sets changelog trimming parameters; changelog must be open. + Parameters: maxEntries - maximum number of entries in the chnagelog (in all files); + maxAge - maximum entry age; + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if changelog is not open + */ +int cl5ConfigTrimming (int maxEntries, const char *maxAge) +{ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ConfigTrimming: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure changelog is not closed while trimming configuration + is updated.*/ + _cl5AddThread (); + + PR_Lock (s_cl5Desc.dbTrim.lock); + + if (maxAge) + { + /* don't ignore this argument */ + if (strcmp (maxAge, CL5_STR_IGNORE) != 0) + { + s_cl5Desc.dbTrim.maxAge = age_str2time (maxAge); + } + } + else + { + /* unlimited */ + s_cl5Desc.dbTrim.maxAge = 0; + } + + if (maxEntries != CL5_NUM_IGNORE) + { + s_cl5Desc.dbTrim.maxEntries = maxEntries; + } + + PR_Unlock (s_cl5Desc.dbTrim.lock); + + _cl5RemoveThread (); + + return CL5_SUCCESS; +} + +/* Name: cl5GetOperation + Description: retireves operation specified by its csn and databaseid + Parameters: op - must contain csn and databaseid; the rest of data is + filled if function is successfull + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_NOTFOUND if entry was not found; + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if db data format does not match entry format. + */ +int cl5GetOperation (Object *replica, slapi_operation_parameters *op) +{ + int rc; + char *agmt_name; + + agmt_name = get_thread_private_agmtname(); + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetOperation: NULL replica\n"); + return CL5_BAD_DATA; + } + + if (op == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetOperation: NULL operation\n"); + return CL5_BAD_DATA; + } + + if (op->csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "%s: cl5GetOperation: operation contains no CSN\n", agmt_name); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: cl5GetOperation: changelog is not initialized\n", agmt_name); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetOperation (replica, op); + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5GetFirstOperation + Description: retrieves first operation for a particular database + replica - replica for which the operation should be retrieved. + Parameters: op - buffer to store the operation; + iterator - to be passed to the call to cl5GetNextOperation + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if operation is NULL + CL5_BAD_STATE, if changelog is not open + CL5_DB_ERROR, if db call fails + */ +int cl5GetFirstOperation (Object *replica, slapi_operation_parameters *op, void **iterator) +{ + int rc; + CL5Entry entry; + Object *obj; + char *agmt_name; + + if (replica == NULL || op == NULL || iterator == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetFirstOperation: invalid argument\n"); + return CL5_BAD_DATA; + } + + *iterator = NULL; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + agmt_name = get_thread_private_agmtname(); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: cl5GetFirstOperation: changelog is not initialized\n", agmt_name); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc != CL5_SUCCESS) + { + _cl5RemoveThread (); + return rc; + } + + entry.op = op; + /* Callers of this function should cl5_operation_parameters_done(op) */ + rc = _cl5GetFirstEntry (obj, &entry, iterator, NULL); + object_release (obj); + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5GetNextOperation + Description: retrieves the next op from the changelog as defined by the iterator; + changelog must be open. + Parameters: op - returned operation, if function is successful + iterator - in: identifies op to retrieve; out: identifies next op + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if op is NULL + CL5_BAD_STATE, if changelog is not open + CL5_NOTFOUND, empty changelog + CL5_DB_ERROR, if db call fails + */ +int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator) +{ + CL5Entry entry; + + if (op == NULL || iterator == NULL || !_cl5IsValidIterator (iterator)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetNextOperation: invalid argument\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState != CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetNextOperation: changelog is not open\n"); + return CL5_BAD_STATE; + } + + /* we don't need to increment thread count since cl5GetFirstOperation + locked the file through which we are iterating */ + entry.op = op; + /* Callers of this function should cl5_operation_parameters_done(op) */ + return _cl5GetNextEntry (&entry, iterator); +} + +/* Name: cl5DestroyIterator + Description: destroys iterator once iteration through changelog is done + Parameters: iterator - iterator to destroy + Return: none + */ +void cl5DestroyIterator (void *iterator) +{ + CL5Iterator *it = (CL5Iterator*)iterator; + + if (it == NULL) + return; + + /* close cursor */ + if (it->cursor) + it->cursor->c_close (it->cursor); + + if (it->file) + object_release (it->file); + + slapi_ch_free ((void**)&it); +} + +/* Name: cl5WriteOperation + Description: writes operation to changelog + Parameters: replName - name of the replica to which operation applies + replGen - replica generation for the operation + !!!Note that we pass name and generation rather than + replica object since generation can change while operation + is in progress (if the data is reloaded). !!! + op - operation to write + local - this is a non-replicated operation + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_MEMORY_ERROR if memory allocation failed; + CL5_DB_ERROR if any other db error occured; + */ +int cl5WriteOperation(const char *replName, const char *replGen, + const slapi_operation_parameters *op, PRBool local) +{ + int rc; + + if (op == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5WriteOperation: NULL operation passed\n"); + return CL5_BAD_DATA; + } + + if (!IsValidOperation (op)) + { + return CL5_BAD_DATA; + } + + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5WriteOperation: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5WriteOperation(replName, replGen, op, local); + + /* update the upper bound ruv vector */ + if (rc == CL5_SUCCESS) + { + Object *file_obj = NULL; + + if ( _cl5GetDBFileByReplicaName (replName, replGen, &file_obj) == CL5_SUCCESS) { + rc = _cl5UpdateRUV (file_obj, op->csn, PR_FALSE, PR_FALSE); + object_release (file_obj); + } + + } + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5CreateReplayIterator + Description: creates an iterator that allows to retireve changes that should + to be sent to the consumer identified by ruv. The iteration is peformed by + repeated calls to cl5GetNextOperationToReplay. + Parameters: replica - replica whose data we wish to iterate; + ruv - consumer ruv; + iterator - iterator to be passed to cl5GetNextOperationToReplay call + Return: CL5_SUCCESS, if function is successfull; + CL5_MISSING_DATA, if data that should be in the changelog is missing + CL5_PURGED_DATA, if some data that consumer needs has been purged. + Note that the iterator can be non null if the supplier contains + some data that needs to be sent to the consumer + CL5_NOTFOUND if the consumer is up to data with respect to the supplier + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if db has not been open; + CL5_DB_ERROR if any other db error occured; + CL5_MEMORY_ERROR if memory allocation fails. + Algorithm: Build a list of csns from consumer's and supplier's ruv. For each element + of the consumer's ruv put max csn into the csn list. For each element + of the supplier's ruv not in the consumer's ruv put min csn from the + supplier's ruv into the list. The list contains, for each known replica, + the starting point for changes to be sent to the consumer. + Sort the list in accending order. + Build a hash which contains, for each known replica, whether the + supplier can bring the consumer up to data with respect to that replica. + The hash is used to decide whether a change can be sent to the consumer + Find the replica with the smallest csn in the list for which + we can bring the consumer up to date. + Position the db cursor on the change entry that corresponds to this csn. + Hash entries are created for each replica traversed so far. sendChanges + flag is set to FALSE for all repolicas except the last traversed. + + */ +int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *consumerRuv, + CL5ReplayIterator **iterator) +{ + int rc; + Object *replica; + Object *obj = NULL; + + replica = prp->replica_object; + if (replica == NULL || consumerRuv == NULL || iterator == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateReplayIterator: invalid parameter\n"); + return CL5_BAD_DATA; + } + + *iterator = NULL; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateReplayIterator: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS ) return rc; + + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + /* iterate through the ruv in csn order to find first master for which + we can replay changes */ + ReplicaId consumerRID = agmt_get_consumer_rid ( prp->agmt, prp->conn ); + rc = _cl5PositionCursorForReplay (consumerRID, consumerRuv, replica, obj, iterator); + if (rc != CL5_SUCCESS) + { + if (obj) + object_release (obj); + } + } + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5GetNextOperationToReplay + Description: retrieves next operation to be sent to a particular consumer and + that was created on a particular master. Consumer and master info + is encoded in the iterator parameter that must be created by call + to cl5CreateReplayIterator. + Parameters: iterator - iterator that identifies next entry to retrieve; + op - operation retrieved if function is successful + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_NOTFOUND if end of iteration list is reached + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if data in db is of unrecognized format; + CL5_MEMORY_ERROR if memory allocation fails. + Algorithm: Iterate through changelog entries until a change is found that + originated at the replica for which we are sending changes + (based on the information in the iteration hash) and + whose csn is larger than the csn already seen by the consumer + If change originated at the replica not in the hash, + determine whether we should send changes originated at the replica + and add replica entry into the hash. We can send the changes for + the replica if the current csn is smaller or equal to the csn + in the consumer's ruv (if present) or if it is equal to the min + csn in the supplier's ruv. + */ +int +cl5GetNextOperationToReplay (CL5ReplayIterator *iterator, CL5Entry *entry) +{ + CSN *csn; + char *key, *data; + size_t keylen, datalen; + char *agmt_name; + int rc = 0; + + agmt_name = get_thread_private_agmtname(); + + if (entry == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: cl5GetNextOperationToReplay: invalid parameter passed\n", agmt_name); + return CL5_BAD_DATA; + } + + rc = clcache_get_next_change (iterator->clcache, (void **)&key, &keylen, (void **)&data, &datalen, &csn); + + if (rc == DB_NOTFOUND) + { + /* + * Abort means we've figured out that we've passed the replica Min CSN, + * so we should stop looping through the changelog + */ + return CL5_NOTFOUND; + } + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, NULL, "%s: cl5GetNextOperationToReplay: " + "failed to read next entry; DB error %d\n", agmt_name, rc); + return CL5_DB_ERROR; + } + + /* there is an entry we should return */ + /* Callers of this function should cl5_operation_parameters_done(op) */ + if ( 0 != cl5DBData2Entry ( data, datalen, entry ) ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: cl5GetNextOperationToReplay: failed to format entry rc=%d\n", agmt_name, rc); + return rc; + } + + return CL5_SUCCESS; +} + +/* Name: cl5DestroyReplayIterator + Description: destorys iterator + Parameters: iterator - iterator to destory + Return: none + */ +void cl5DestroyReplayIterator (CL5ReplayIterator **iterator) +{ + if (iterator == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5DestroyReplayIterator: invalid iterartor passed\n"); + return; + } + + clcache_return_buffer ( &(*iterator)->clcache ); + + if ((*iterator)->fileObj) + object_release ((*iterator)->fileObj); + + /* release supplier's ruv */ + if ((*iterator)->supplierRuvObj) + object_release ((*iterator)->supplierRuvObj); + + slapi_ch_free ((void **)iterator); +} + +/* Name: cl5DeleteOnClose + Description: marks changelog for deletion when it is closed + Parameters: flag; if flag = 1 then delete else don't + Return: none + */ +void cl5DeleteOnClose (PRBool rm) +{ + s_cl5Desc.dbRmOnClose = rm; +} + +/* Name: cl5GetDir + Description: returns changelog directory + Parameters: none + Return: copy of the directory; caller needs to free the string + */ + char *cl5GetDir () +{ + if (s_cl5Desc.dbDir == NULL) + { + return NULL; + } + else + { + return slapi_ch_strdup (s_cl5Desc.dbDir); + } +} + +/* Name: cl5Exist + Description: checks if a changelog exists in the specified directory; + We consider changelog to exist if it contains the dbversion file. + Parameters: clDir - directory to check + Return: 1 - if changelog exists; 0 - otherwise + */ +PRBool cl5Exist (const char *clDir) +{ + char fName [MAXPATHLEN + 1]; + int rc; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", clDir, VERSION_FILE); + rc = PR_Access (fName, PR_ACCESS_EXISTS); + + return (rc == PR_SUCCESS); +} + +/* Name: cl5GetOperationCount + Description: returns number of entries in the changelog. The changelog must be + open for the value to be meaningful. + Parameters: replica - optional parameter that specifies the replica whose operations + we wish to count; if NULL all changelog entries are counted + Return: number of entries in the changelog + */ + +int cl5GetOperationCount (Object *replica) +{ + Object *obj; + CL5DBFile *file; + int count = 0; + int rc; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetOperationCount: changelog is not initialized\n"); + return -1; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return -1; + + if (replica == NULL) /* compute total entry count */ + { + obj = objset_first_obj (s_cl5Desc.dbFiles); + while (obj) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + count += file->entryCount; + obj = objset_next_obj (s_cl5Desc.dbFiles, obj); + } + } + else /* return count for particular db */ + { + /* select correct db file */ + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + count = file->entryCount; + object_release (obj); + } + else + { + count = 0; + } + } + + _cl5RemoveThread (); + return count; +} + +/***** Helper Functions *****/ + +/* this call happens under state lock */ +static int _cl5Open (const char *dir, const CL5DBConfig *config, CL5OpenMode openMode) +{ + int rc; + PRBool didRecovery; + + PR_ASSERT (dir); + + /* setup db configuration parameters */ + if (config) + { + _cl5SetDBConfig (config); + } + else + { + _cl5SetDefaultDBConfig (); + } + + /* initialize trimming */ + rc = _cl5TrimInit (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to initialize trimming\n"); + goto done; + } + + /* create the changelog directory if it does not exist */ + rc = cl5CreateDirIfNeeded (dir); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to create changelog directory (%s)\n", dir); + goto done; + } + + s_cl5Desc.dbDir = slapi_ch_strdup (dir); + + /* check database version */ + rc = _cl5CheckDBVersion (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5Open: invalid db version\n"); + goto done; + } + + s_cl5Desc.dbOpenMode = openMode; + + /* initialize db environment */ + rc = _cl5AppInit (&didRecovery); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to initialize db environment\n"); + goto done; + } + + /* open database files */ + rc = _cl5DBOpen (!didRecovery); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to open changelog database\n"); + + goto done; + } + +done:; + + if (rc != CL5_SUCCESS) + { + _cl5Close (); + } + + return rc; +} + +int cl5CreateDirIfNeeded (const char *dirName) +{ + int rc; + char buff [MAXPATHLEN + 1]; + char *t; + + PR_ASSERT (dirName); + + rc = PR_Access(dirName, PR_ACCESS_EXISTS); + if (rc == PR_SUCCESS) + { + return CL5_SUCCESS; + } + + /* directory does not exist - try to create */ + strncpy (buff, dirName, MAXPATHLEN); + t = strchr (buff, '/'); + + /* skip first slash */ + if (t) + { + t = strchr (t+1, '/'); + } + + while (t) + { + *t = '\0'; + if (PR_Access (buff, PR_ACCESS_EXISTS) != PR_SUCCESS) + { + rc = PR_MkDir (buff, DIR_CREATE_MODE); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateDirIfNeeded: failed to create dir (%s); NSPR error - %d\n", + dirName, PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + } + + *t++ = FILE_PATHSEP; + + t = strchr (t, '/'); + } + + /* last piece */ + rc = PR_MkDir (buff, DIR_CREATE_MODE); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateDirIfNeeded: failed to create dir; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5RemoveEnv () +{ + DB_ENV *dbEnv = NULL; + int rc = 0; + + if ((rc = db_env_create(&dbEnv, 0)) != 0) + dbEnv = NULL; + + if (dbEnv == NULL) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5RemoveEnv: failed to allocate db environment; " + "db error - %d %s\n", rc, errstr ? errstr : "unknown"); + return CL5_MEMORY_ERROR; + } + rc = dbEnv->remove(dbEnv, s_cl5Desc.dbDir, DB_FORCE); + if (0 != rc) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: failed to remove db environment; " + "db error - %d %s\n", rc, errstr ? errstr : "unknown"); + return CL5_DB_ERROR; + } + return CL5_SUCCESS; +} + +static int _cl5AppInit (PRBool *didRecovery) +{ + int rc; + unsigned int flags = DB_CREATE | DB_INIT_MPOOL | DB_THREAD; + DB_ENV *dbEnv; + if ((rc = db_env_create(&dbEnv, 0)) != 0) + dbEnv = NULL; + + if (dbEnv == NULL) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: failed to allocate db environment; db error - %d (%s)\n", + rc, errstr ? errstr : "unknown"); + return CL5_MEMORY_ERROR; + } + + _cl5InitDBEnv (dbEnv); + + if (didRecovery) + *didRecovery = PR_FALSE; + + /* decide how two open based on the mode in which db is open */ + switch (s_cl5Desc.dbOpenMode) + { + case CL5_OPEN_NORMAL: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG; + /* check if need to initiate recovery */ + rc = _cl5CheckGuardian (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5AppInit: recovering changelog after disorderly shutdown\n"); + flags |= DB_RECOVER; + } + break; + + case CL5_OPEN_RESTORE: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG; + break; + + case CL5_OPEN_CLEAN_RECOVER: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_RECOVER; + break; + + case CL5_OPEN_RESTORE_RECOVER: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_RECOVER_FATAL; + break; + + case CL5_OPEN_LDIF2CL: + /* ONREPL - don't think we need any extra flags here */ + break; + default: + /* fixme? CL5_OPEN_NONE */ + break; + } + + if (!s_cl5Desc.dbConfig.durableTrans) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3200 + dbEnv->set_flags(dbEnv, DB_TXN_NOSYNC, 1); +#else + flags |= DB_TXN_NOSYNC; +#endif + } + + dbEnv->set_errcall(dbEnv, dblayer_log_print); + + /* do recovery if necessary */ + if ((flags & DB_RECOVER) || (flags & DB_RECOVER_FATAL)) + { + if (CL5_OPEN_CLEAN_RECOVER == s_cl5Desc.dbOpenMode) + _cl5RemoveEnv(); + + rc = _cl5Recover (flags, dbEnv); + if (rc != CL5_SUCCESS) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5AppInit: failed to recover changelog; db error - %d %s\n", + rc, errstr ? errstr : "unknown"); + + slapi_ch_free ((void **)&dbEnv); + + return rc; + } + + if (didRecovery) + *didRecovery = PR_TRUE; + flags &= ~(DB_RECOVER | DB_RECOVER_FATAL); + /* Need to reset the env */ + /* Does this leak the dbEnv? */ + if ((rc = db_env_create(&dbEnv, 0)) != 0) + dbEnv = NULL; + + if (dbEnv == NULL) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: failed to allocate db environment after recovery; " + "db error - %d %s\n", rc, errstr ? errstr : "unknown"); + return CL5_MEMORY_ERROR; + } + _cl5InitDBEnv (dbEnv); + } + + rc = dbEnv->open(dbEnv, s_cl5Desc.dbDir, flags, + s_cl5Desc.dbConfig.fileMode); + if (rc == 0) + { + s_cl5Desc.dbEnv = dbEnv; + s_cl5Desc.dbEnvOpenFlags = flags; + return CL5_SUCCESS; + } + else + { + char *errstr = db_strerror(rc); + char flagstr[20]; + + flagstr[0] = 0; + /* EINVAL return means bad flags - let's see what the flags are */ + if (rc == EINVAL) + { + sprintf(flagstr, "%u", flags); + } + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: db environment open failed; db error - %d %s %s\n", + rc, errstr ? errstr : "unknown", flagstr); + slapi_ch_free ((void **)&dbEnv); + return CL5_DB_ERROR; + } +} + +static int _cl5DBOpen () +{ + PRBool dbFile; + PRDir *dir; + PRDirEntry *entry = NULL; + int rc; + Object *replica; + + /* create lock that guarantees that each file is only added once to the list */ + s_cl5Desc.fileLock = PR_NewLock (); + + /* loop over all db files and open them; file name format is cl5_<dbid>.<dbext> */ + dir = PR_OpenDir(s_cl5Desc.dbDir); + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DBOpen: failed to open changelog dir; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + + } + + /* initialize set of db file objects */ + s_cl5Desc.dbFiles = objset_new(NULL); + while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == entry->name) + { + break; + } + + dbFile = _cl5FileName2Replica (entry->name, &replica); + if (dbFile) /* this is db file, not a log or dbversion; those are just skipped */ + { + /* we only open files for existing replicas */ + if (replica) + { + rc = _cl5DBOpenFile (replica, NULL /* file object */, + PR_FALSE /* check for duplicates */); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: " + "Error opening file %s\n", + entry->name); + return rc; + } + + object_release (replica); + } + else /* there is no matching replica for the file - remove */ + { + char fullpathname[MAXPATHLEN]; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: " + "file %s has no matching replica; removing\n", entry->name); + + PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name); + if (PR_Delete(fullpathname) != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: " + "failed to remove (%s) file; NSPR error - %d\n", + entry->name, PR_GetError ()); + + } + } + } + } + + PR_CloseDir(dir); + + return CL5_SUCCESS; +} + +/* this function assumes that the entry was validated + using IsValidOperation + + Data in db format: + ------------------ + <1 byte version><1 byte change_type><sizeof time_t time><null terminated csn> + <null terminated uniqueid><null terminated targetdn> + [<null terminated newrdn><1 byte deleteoldrdn>][<4 byte mod count><mod1><mod2>....] + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte value count> + <4 byte value size><value1><4 byte value size><value2> +*/ +static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len) +{ + int size = 1 /* version */ + 1 /* operation type */ + sizeof (time_t); + char *pos; + PRUint32 t; + slapi_operation_parameters *op; + LDAPMod **add_mods = NULL; + char *rawDN = NULL; + char s[CSN_STRSIZE]; + + PR_ASSERT (entry && entry->op && data && len); + + op = entry->op; + + /* compute size of the buffer needed to hold the data */ + size += CSN_STRSIZE; + size += strlen (op->target_address.uniqueid) + 1; + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid) + size += strlen (op->p.p_add.parentuniqueid) + 1; + else + size ++; /* we just store NULL char */ + slapi_entry2mods (op->p.p_add.target_entry, &rawDN/* dn */, &add_mods); + size += strlen (rawDN) + 1; + size += _cl5GetModsSize (add_mods); + break; + + case SLAPI_OPERATION_MODIFY: size += strlen (op->target_address.dn) + 1; + size += _cl5GetModsSize (op->p.p_modify.modify_mods); + break; + + case SLAPI_OPERATION_MODRDN: size += strlen (op->target_address.dn) + 1; + /* 1 for deleteoldrdn */ + size += strlen (op->p.p_modrdn.modrdn_newrdn) + 2; + if (op->p.p_modrdn.modrdn_newsuperior_address.dn) + size += strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn) + 1; + else + size ++; /* for NULL char */ + if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + size += strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + 1; + else + size ++; /* for NULL char */ + size += _cl5GetModsSize (op->p.p_modrdn.modrdn_mods); + break; + + case SLAPI_OPERATION_DELETE: size += strlen (op->target_address.dn) + 1; + break; + } + + /* allocate data buffer */ + (*data) = (char *) slapi_ch_malloc (size); + if ((*data) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Entry2DBData: failed to allocate data buffer\n"); + return CL5_MEMORY_ERROR; + } + + /* fill in the data buffer */ + pos = *data; + /* write a byte of version */ + (*pos) = V_5; + pos ++; + /* write change type */ + (*pos) = (unsigned char)op->operation_type; + pos ++; + /* write time */ + t = PR_htonl((PRUint32)entry->time); + memcpy (pos, &t, sizeof (t)); + pos += sizeof (t); + /* write csn */ + _cl5WriteString (csn_as_string(op->csn,PR_FALSE,s), &pos); + /* write UniqueID */ + _cl5WriteString (op->target_address.uniqueid, &pos); + + /* figure out what else we need to write depending on the operation type */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: _cl5WriteString (op->p.p_add.parentuniqueid, &pos); + _cl5WriteString (rawDN, &pos); + _cl5WriteMods (add_mods, &pos); + slapi_ch_free ((void**)&rawDN); + ldap_mods_free (add_mods, 1); + break; + + case SLAPI_OPERATION_MODIFY: _cl5WriteString (op->target_address.dn, &pos); + _cl5WriteMods (op->p.p_modify.modify_mods, &pos); + break; + + case SLAPI_OPERATION_MODRDN: _cl5WriteString (op->target_address.dn, &pos); + _cl5WriteString (op->p.p_modrdn.modrdn_newrdn, &pos); + *pos = (PRUint8)op->p.p_modrdn.modrdn_deloldrdn; + pos ++; + _cl5WriteString (op->p.p_modrdn.modrdn_newsuperior_address.dn, &pos); + _cl5WriteString (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, &pos); + _cl5WriteMods (op->p.p_modrdn.modrdn_mods, &pos); + break; + + case SLAPI_OPERATION_DELETE: _cl5WriteString (op->target_address.dn, &pos); + break; + } + + (*len) = size; + + return CL5_SUCCESS; +} + +/* + Data in db format: + ------------------ + <1 byte version><1 byte change_type><sizeof time_t time><null terminated dbid> + <null terminated csn><null terminated uniqueid><null terminated targetdn> + [<null terminated newrdn><1 byte deleteoldrdn>][<4 byte mod count><mod1><mod2>....] + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte value count> + <4 byte value size><value1><4 byte value size><value2> +*/ + + +int +cl5DBData2Entry (const char *data, PRUint32 len, CL5Entry *entry) +{ + int rc; + PRUint8 version; + char *pos = (char *)data; + char *strCSN; + PRUint32 thetime; + slapi_operation_parameters *op; + LDAPMod **add_mods; + char *rawDN; + char s[CSN_STRSIZE]; + + PR_ASSERT (data && entry && entry->op); + + /* ONREPL - check that we do not go beyond the end of the buffer */ + + /* read byte of version */ + version = (PRUint8)(*pos); + if (version != V_5) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5DBData2Entry: invalid data version\n"); + return CL5_BAD_FORMAT; + } + + op = entry->op; + + pos += sizeof(version); + + /* read change type */ + op->operation_type = (PRUint8)(*pos); + pos ++; + + /* need to do the copy first, to skirt around alignment problems on + certain architectures */ + memcpy((char *)&thetime,pos,sizeof(thetime)); + entry->time = (time_t)PR_ntohl(thetime); + pos += sizeof (thetime); + + /* read csn */ + _cl5ReadString (&strCSN, &pos); + if (op->csn == NULL || strcmp (strCSN, csn_as_string(op->csn,PR_FALSE,s)) != 0) + { + op->csn = csn_new_by_string (strCSN); + } + slapi_ch_free ((void**)&strCSN); + + /* read UniqueID */ + _cl5ReadString (&op->target_address.uniqueid, &pos); + + /* figure out what else we need to read depending on the operation type */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: _cl5ReadString (&op->p.p_add.parentuniqueid, &pos); + /* richm: need to free parentuniqueid */ + _cl5ReadString (&rawDN, &pos); + op->target_address.dn = rawDN; + /* convert mods to entry */ + rc = _cl5ReadMods (&add_mods, &pos); + slapi_mods2entry (&(op->p.p_add.target_entry), rawDN, add_mods); + ldap_mods_free (add_mods, 1); + break; + + case SLAPI_OPERATION_MODIFY: _cl5ReadString (&op->target_address.dn, &pos); + rc = _cl5ReadMods (&op->p.p_modify.modify_mods, &pos); + break; + + case SLAPI_OPERATION_MODRDN: _cl5ReadString (&op->target_address.dn, &pos); + _cl5ReadString (&op->p.p_modrdn.modrdn_newrdn, &pos); + op->p.p_modrdn.modrdn_deloldrdn = *pos; + pos ++; + _cl5ReadString (&op->p.p_modrdn.modrdn_newsuperior_address.dn, &pos); + _cl5ReadString (&op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, &pos); + rc = _cl5ReadMods (&op->p.p_modrdn.modrdn_mods, &pos); + break; + + case SLAPI_OPERATION_DELETE: _cl5ReadString (&op->target_address.dn, &pos); + rc = CL5_SUCCESS; + break; + + default: rc = CL5_BAD_FORMAT; + slapi_log_error(SLAPI_LOG_FATAL, + repl_plugin_name_cl, + "cl5DBData2Entry: failed to format entry\n"); + break; + } + + return rc; +} + +/* thread management functions */ +static int _cl5DispatchDBThreads () +{ + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5DeadlockMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create deadlock thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5CheckpointMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create checkpoint thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5TrickleMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE) ) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create trickle thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void*)_cl5TrimMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE) ) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create trimming thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5AddThread () +{ + /* lock the state lock so that nobody can change the state + while backup is in progress + */ + PR_RWLock_Rlock (s_cl5Desc.stLock); + + /* open changelog if it is not already open */ + if (s_cl5Desc.dbState != CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AddThread: invalid changelog state - %d\n", s_cl5Desc.dbState); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + /* increment global thread count to make sure that changelog does not close while + backup is in progress */ + PR_AtomicIncrement (&s_cl5Desc.threadCount); + + PR_RWLock_Unlock (s_cl5Desc.stLock); + + return CL5_SUCCESS; +} + +static void _cl5RemoveThread () +{ + PR_ASSERT (s_cl5Desc.threadCount > 0); + PR_AtomicDecrement (&s_cl5Desc.threadCount); +} + +/* data conversion functions */ +static void _cl5WriteString (const char *str, char **buff) +{ + if (str) + { + strcpy (*buff, str); + (*buff) += strlen (str) + 1; + } + else /* just write NULL char */ + { + (**buff) = '\0'; + (*buff) ++; + } +} + +static void _cl5ReadString (char **str, char **buff) +{ + if (str) + { + int len = strlen (*buff); + + if (len) + { + *str = slapi_ch_strdup (*buff); + (*buff) += len + 1; + } + else /* just null char - skip it */ + { + *str = NULL; + (*buff) ++; + } + } + else /* just skip this string */ + { + (*buff) += strlen (*buff) + 1; + } +} + +/* mods format: + ----------- + <4 byte mods count><mod1><mod2>... + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte count> + <4 byte size><value1><4 byte size><value2>... + */ +static void _cl5WriteMods (LDAPMod **mods, char **buff) +{ + PRInt32 i; + char *mod_start; + PRInt32 count; + + if (mods == NULL) + return; + + /* skip mods count */ + mod_start = (*buff) + sizeof (count); + + /* write mods*/ + for (i=0; mods[i]; i++) + { + _cl5WriteMod (mods[i], &mod_start); + } + + count = PR_htonl(i); + memcpy (*buff, &count, sizeof (count)); + + (*buff) = mod_start; +} + +static void _cl5WriteMod (LDAPMod *mod, char **buff) +{ + char *pos; + PRInt32 count; + struct berval *bv; + Slapi_Mod smod; + + slapi_mod_init_byref(&smod, mod); + + pos = *buff; + /* write mod op */ + *pos = (PRUint8)slapi_mod_get_operation (&smod); + pos ++; + /* write attribute name */ + _cl5WriteString (slapi_mod_get_type (&smod), &pos); + + /* write value count */ + count = PR_htonl(slapi_mod_get_num_values(&smod)); + memcpy (pos, &count, sizeof (count)); + pos += sizeof (PRInt32); + + bv = slapi_mod_get_first_value (&smod); + while (bv) + { + _cl5WriteBerval (bv, &pos); + bv = slapi_mod_get_next_value (&smod); + } + + (*buff) = pos; + + slapi_mod_done (&smod); +} + +/* mods format: + ----------- + <4 byte mods count><mod1><mod2>... + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte count> + {<4 byte size><value1><4 byte size><value2>... || + <null terminated str1> <null terminated str2>...} + */ + +static int _cl5ReadMods (LDAPMod ***mods, char **buff) +{ + char *pos = *buff; + int i; + int rc; + PRInt32 mod_count; + Slapi_Mods smods; + Slapi_Mod smod; + + /* need to copy first, to skirt around alignment problems on certain + architectures */ + memcpy((char *)&mod_count,*buff,sizeof(mod_count)); + mod_count = PR_ntohl(mod_count); + pos += sizeof (mod_count); + + slapi_mods_init (&smods , mod_count); + + for (i = 0; i < mod_count; i++) + { + rc = _cl5ReadMod (&smod, &pos); + if (rc != CL5_SUCCESS) + { + slapi_mods_done(&smods); + return rc; + } + + slapi_mods_add_smod(&smods, &smod); + } + + *buff = pos; + + *mods = slapi_mods_get_ldapmods_passout (&smods); + slapi_mods_done(&smods); + + return CL5_SUCCESS; +} + +static int _cl5ReadMod (Slapi_Mod *smod, char **buff) +{ + char *pos = *buff; + int i; + PRInt32 val_count; + char *type; + int op; + struct berval bv; + + op = (*pos) & 0x000000FF; + pos ++; + _cl5ReadString (&type, &pos); + + /* need to do the copy first, to skirt around alignment problems on + certain architectures */ + memcpy((char *)&val_count,pos,sizeof(val_count)); + val_count = PR_ntohl(val_count); + pos += sizeof (PRInt32); + + slapi_mod_init(smod, val_count); + slapi_mod_set_operation (smod, op|LDAP_MOD_BVALUES); + slapi_mod_set_type (smod, type); + slapi_ch_free ((void**)&type); + + for (i = 0; i < val_count; i++) + { + _cl5ReadBerval (&bv, &pos); + slapi_mod_add_value (smod, &bv); + slapi_ch_free((void **) &bv.bv_val); + } + + (*buff) = pos; + + return CL5_SUCCESS; +} + +static int _cl5GetModsSize (LDAPMod **mods) +{ + int size; + int i; + + if (mods == NULL) + return 0; + + size = sizeof (PRInt32); + for (i=0; mods[i]; i++) + { + size += _cl5GetModSize (mods[i]); + } + + return size; +} + +static int _cl5GetModSize (LDAPMod *mod) +{ + int size; + int i; + + size = 1 + strlen (mod->mod_type) + 1 + sizeof (mod->mod_op); + i = 0; + if (mod->mod_op & LDAP_MOD_BVALUES) /* values are in binary form */ + { + while (mod->mod_bvalues != NULL && mod->mod_bvalues[i] != NULL) + { + size += mod->mod_bvalues[i]->bv_len + sizeof (mod->mod_bvalues[i]->bv_len); + i++; + } + } + else /* string data */ + { + PR_ASSERT(0); /* ggood string values should never be used in the server */ + } + + return size; +} + +static void _cl5ReadBerval (struct berval *bv, char** buff) +{ + PRUint32 length = 0; + PRUint32 net_length = 0; + + PR_ASSERT (bv && buff); + + /***PINAKI need to do the copy first, to skirt around alignment problems on + certain architectures */ + /* DBDB : struct berval.bv_len is defined as unsigned long + * But code here expects it to be 32-bits in size. + * On 64-bit machines, this is not the case. + * I changed the code to consistently use 32-bit (4-byte) + * values on the encoded side. This means that it's + * possible to generate a huge berval that will not + * be encoded properly. However, this seems unlikely + * to happen in reality, and I felt that retaining the + * old on-disk format for the changely in the 64-bit + * version of the server was important. + */ + + memcpy((char *)&net_length, *buff, sizeof(net_length)); + length = PR_ntohl(net_length); + *buff += sizeof(net_length); + bv->bv_len = length; + + if (bv->bv_len > 0) { + bv->bv_val = (char*)slapi_ch_malloc (bv->bv_len); + memcpy (bv->bv_val, *buff, bv->bv_len); + *buff += bv->bv_len; + } + else { + bv->bv_val = NULL; + } +} + +static void _cl5WriteBerval (struct berval *bv, char** buff) +{ + PRUint32 length = 0; + PRUint32 net_length = 0; + + length = (PRUint32) bv->bv_len; + net_length = PR_htonl(length); + + memcpy(*buff, &net_length, sizeof (net_length)); + *buff += sizeof (net_length); + memcpy (*buff, bv->bv_val, length); + *buff += length; +} + +/* data format: <value count> <value size> <value> <value size> <value> ..... */ +static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size) +{ + PRInt32 count; + int i; + char *pos; + + PR_ASSERT (bv && buff); + + /* ONREPL - need to check that we don't go beyond the end of the buffer */ + + pos = *buff; + memcpy((char *)&count, pos, sizeof(count)); + count = PR_htonl (count); + pos += sizeof(count); + + /* allocate bervals */ + *bv = (struct berval **)slapi_ch_malloc ((count + 1) * sizeof (struct berval*)); + if (*bv == NULL) + { + return CL5_MEMORY_ERROR; + } + + for (i = 0; i < count; i++) + { + (*bv)[i] = (struct berval *)slapi_ch_malloc (sizeof (struct berval)); + if ((*bv)[i] == NULL) + { + ber_bvecfree(*bv); + return CL5_MEMORY_ERROR; + } + + _cl5ReadBerval ((*bv)[i], &pos); + } + + (*bv)[count] = NULL; + *buff = pos; + + return CL5_SUCCESS; +} + +/* data format: <value count> <value size> <value> <value size> <value> ..... */ +static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size) +{ + PRInt32 count, net_count; + char *pos; + int i; + + PR_ASSERT (bv && buff && size); + + /* compute number of values and size of the buffer to hold them */ + *size = sizeof (count); + for (count = 0; bv[count]; count ++) + { + *size += sizeof (bv[count]->bv_len) + bv[count]->bv_len; + } + + /* allocate buffer */ + *buff = (char*) slapi_ch_malloc (*size); + if (*buff == NULL) + { + *size = 0; + return CL5_MEMORY_ERROR; + } + + /* fill the buffer */ + pos = *buff; + net_count = PR_htonl(count); + memcpy (pos, &net_count, sizeof (net_count)); + pos += sizeof (net_count); + for (i = 0; i < count; i ++) + { + _cl5WriteBerval (bv[i], &pos); + } + + return CL5_SUCCESS; +} + +static int _cl5DeadlockMain (void *param) +{ + PRIntervalTime interval; + int rc; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + interval = PR_MillisecondsToInterval(100); + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + int aborted; + if ((rc = LOCK_DETECT(s_cl5Desc.dbEnv, 0, DB_LOCK_YOUNGEST, &aborted)) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5DeadlockMain: lock_detect failed (%d transaction%s aborted); db error - %d %s\n", + aborted, (aborted == 1)? "":"s", rc, db_strerror(rc)); + } + else if (aborted) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5DeadlockMain: lock_detect succeeded, but %d transaction%s ha%s been aborted\n", + aborted, (aborted == 1)? "":"s", (aborted == 1)? "s":"ve"); + } + + DS_Sleep(interval); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + return 0; +} + +static int _cl5CheckpointMain (void *param) +{ + time_t lastCheckpointCompletion = 0; + PRIntervalTime interval; + int rc = -1; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + + interval = PR_MillisecondsToInterval(1000); + lastCheckpointCompletion = current_time(); + + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + /* Check to see if the checkpoint interval has elapsed */ + if (current_time() - lastCheckpointCompletion > s_cl5Desc.dbConfig.checkpointInterval) + { + rc = TXN_CHECKPOINT(s_cl5Desc.dbEnv, 0, 0, 0); + if (rc == 0) + { + lastCheckpointCompletion = current_time(); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + else if (rc != DB_INCOMPLETE) /* real error happened */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckpointMain: checkpoint failed, db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + + /* According to dboreham, we are doing checkpoint twice + to reduce the number of transaction log files which need + to be retained at any time. */ + rc = TXN_CHECKPOINT(s_cl5Desc.dbEnv, 0, 0, 0); + if (rc == 0) + { + lastCheckpointCompletion = current_time(); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + else if (rc != DB_INCOMPLETE) /* real error happened */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckpointMain: checkpoint failed, db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + + /* check if we should truncate logs */ + if (s_cl5Desc.dbConfig.circularLogging) + { + char **list = NULL; + char **listp = NULL; + int rc = -1; + char filename[MAXPATHLEN + 1]; + + /* find out which log files don't contain active txns */ + rc = LOG_ARCHIVE(s_cl5Desc.dbEnv, &list, 0, malloc); + if (0 == rc && NULL != list) + { + /* zap 'em ! */ + for (listp = list; *listp != NULL; ++listp) + { + PR_snprintf(filename, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir,*listp); + PR_Delete (filename); + } + slapi_ch_free((void **)&list); + } + } + } + + /* sleep for a while */ + /* why aren't we sleeping exactly the right amount of time ? */ + /* answer---because the interval might be changed after the server starts up */ + DS_Sleep(interval); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + return 0; +} + +static int _cl5TrickleMain (void *param) +{ + PRIntervalTime interval; + int pages_written; + int rc; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + interval = PR_MillisecondsToInterval(1000); + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + if ((rc = MEMP_TRICKLE(s_cl5Desc.dbEnv, + s_cl5Desc.dbConfig.tricklePercentage, &pages_written)) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrickleMain: memp_trickle failed; db error - %d %s\n", + rc, db_strerror(rc)); + } + + DS_Sleep(interval); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + + return 0; +} + +/* upgrade from db33 to db41 + * 1. Run recovery on the database environment using the DB_ENV->open method + * 2. Remove any Berkeley DB environment using the DB_ENV->remove method + * 3. extention .db3 -> .db4 ### koko kara !!! + */ +static int _cl5Upgrade3_4(char *fromVersion, char *toVersion) +{ + PRDir *dir = NULL; + PRDirEntry *entry = NULL; + DB *thisdb = NULL; + CL5OpenMode backup; + int rc = 0; + + backup = s_cl5Desc.dbOpenMode; + s_cl5Desc.dbOpenMode = CL5_OPEN_CLEAN_RECOVER; + /* CL5_OPEN_CLEAN_RECOVER does 1 and 2 */ + rc = _cl5AppInit (NULL); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to open the db env\n"); + return rc; + } + + dir = PR_OpenDir(s_cl5Desc.dbDir); + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to open changelog dir %s; NSPR error - %d\n", + s_cl5Desc.dbDir, PR_GetError ()); + goto out; + } + + while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == entry->name) + { + break; + } + if (_cl5FileEndsWith(entry->name, DB_EXTENSION_DB3)) + { + char oName [MAXPATHLEN + 1]; + char nName [MAXPATHLEN + 1]; + char *p = NULL; + char c; + int baselen = 0; + PR_snprintf(oName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name); + p = strstr(oName, DB_EXTENSION_DB3); + if (NULL == p) + { + continue; + } + /* db->rename closes DB; need to create every time */ + rc = db_create(&thisdb, s_cl5Desc.dbEnv, 0); + if (0 != rc) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to get db handle\n"); + goto out; + } + + baselen = p - oName; + c = *p; + *p = '\0'; + PR_snprintf(nName, MAXPATHLEN+1, "%s", oName); + PR_snprintf(nName + baselen, MAXPATHLEN+1-baselen, "%s", DB_EXTENSION); + *p = c; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Upgrade3_4: renaming %s to %s\n", oName, nName); + rc = thisdb->rename(thisdb, (const char *)oName, NULL /* subdb */, + (const char *)nName, 0); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to rename file (%s -> %s); " + "db error - %d %s\n", oName, nName, rc, db_strerror(rc)); + break; + } + } + } + /* update the version file */ + _cl5WriteDBVersion (); + + /* update the guardian file */ + _cl5WriteGuardian (); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Upgrading from %s to %s is successfully done (%s)\n", + fromVersion, toVersion, s_cl5Desc.dbDir); +out: + if (NULL != dir) + { + PR_CloseDir(dir); + } + if (s_cl5Desc.dbEnv) + { + DB_ENV *dbEnv = s_cl5Desc.dbEnv; + dbEnv->close(dbEnv, 0); + s_cl5Desc.dbEnv = NULL; + } + return rc; +} + +static int _cl5CheckDBVersion () +{ + char clVersion [VERSION_SIZE + 1]; + char dbVersion [VERSION_SIZE + 1]; + int rc; + + if (!cl5Exist (s_cl5Desc.dbDir)) + { + /* this is new changelog - write DB version and guardian file */ + rc = _cl5WriteDBVersion (); + if (rc == CL5_SUCCESS) { + rc = _cl5WriteGuardian(); + } + } + else + { + PR_snprintf (clVersion, VERSION_SIZE, "%s/%s/%s", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + rc = _cl5ReadDBVersion (s_cl5Desc.dbDir, dbVersion); + + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckDBVersion: invalid dbversion\n"); + rc = CL5_BAD_DBVERSION; + } + else if (strcasecmp (clVersion, dbVersion) != 0) + { + char prevClVersion [VERSION_SIZE + 1]; + PR_snprintf (prevClVersion, VERSION_SIZE, "%s/%s/%s", + CL5_TYPE, REPL_PLUGIN_NAME, CHANGELOG_DB_VERSION_PREV); + if (strcasecmp (prevClVersion, dbVersion) == 0) + { + /* upgrade */ + rc = _cl5Upgrade3_4(prevClVersion, clVersion); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckDBVersion: upgrade %s -> %s failed\n", + CHANGELOG_DB_VERSION_PREV, CHANGELOG_DB_VERSION); + rc = CL5_BAD_DBVERSION; + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckDBVersion: invalid dbversion\n"); + rc = CL5_BAD_DBVERSION; + } + } + + } + + return rc; +} + +static int _cl5ReadDBVersion (const char *dir, char *clVersion) +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + char buff [BUFSIZ]; + PRInt32 size; + char *tok; + char * iter = NULL; + + if (clVersion) + { + clVersion [0] = '\0'; + } + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", dir, VERSION_FILE); + + file = PR_Open (fName, PR_RDONLY, 777); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadDBVersion: failed to open DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + size = slapi_read_buffer (file, buff, BUFSIZ); + if (size < 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadDBVersion: failed to read DBVERSION; NSPR error - %d\n", + PR_GetError ()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + /* parse the data */ + buff[size]= '\0'; + tok = ldap_utf8strtok_r (buff, "\n", &iter); + if (tok) + { + if (clVersion) + { + strcpy(clVersion, tok); + } + } + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadDBVersion: failed to close DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5WriteDBVersion () +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + char clVersion [VERSION_SIZE + 1]; + PRInt32 len, size; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, VERSION_FILE); + + file = PR_Open (fName, PR_WRONLY | PR_CREATE_FILE, s_cl5Desc.dbConfig.fileMode); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteDBVersion: failed to open DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + /* write changelog version */ + PR_snprintf (clVersion, VERSION_SIZE, "%s/%s/%s\n", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + + len = strlen (clVersion); + size = slapi_write_buffer (file, clVersion, len); + if (size != len) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteDBVersion: failed to write DBVERSION; NSPR error - %d\n", + PR_GetError ()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteDBVersion: failed to close DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +/* for now guardian file is just like dbversion file */ +static int _cl5CheckGuardian () +{ + char plVersion [VERSION_SIZE + 1]; + char dbVersion [VERSION_SIZE + 1]; + int rc; + + /* new changelog - no guardian file */ + if (!cl5Exist(s_cl5Desc.dbDir)) + { + return CL5_SUCCESS; + } + else + { + PR_snprintf (plVersion, VERSION_SIZE, "%s/%s/%s", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + rc = _cl5ReadGuardian (dbVersion); + + if (rc != CL5_SUCCESS || strcasecmp (plVersion, dbVersion) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5CheckGuardian: missing or invalid guardian file\n"); + return (CL5_BAD_FORMAT); + } + + /* remove guardian file */ + rc = _cl5RemoveGuardian (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5CheckGuardian: failed to remove guardian file\n"); + } + } + + return rc; +} + +static int _cl5WriteGuardian () +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + char version [VERSION_SIZE]; + PRInt32 len, size; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE); + + file = PR_Open (fName, PR_WRONLY | PR_CREATE_FILE, s_cl5Desc.dbConfig.fileMode); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteGuardian: failed to open guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + PR_snprintf (version, VERSION_SIZE, "%s/%s/%s\n", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + + len = strlen (version); + size = slapi_write_buffer (file, version, len); + if (size != len) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteGuardian: failed to write guardian file; NSPR error - %d\n", + PR_GetError()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteGuardian: failed to close guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5ReadGuardian (char *buff) +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + PRInt32 size; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE); + + file = PR_Open (fName, PR_RDONLY, 0); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadGuardian: failed to open guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + size = slapi_read_buffer (file, buff, VERSION_SIZE); + if (size <= 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadGuardian: failed to read guardian file; NSPR error - %d\n", + PR_GetError()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + buff [size-1] = '\0'; + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadGuardian: failed to close guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5RemoveGuardian () +{ + char fName [MAXPATHLEN + 1]; + int rc; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE); + + rc = PR_Delete (fName); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5RemoveGuardian: failed to remove guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +/* must be called under the state lock */ +static void _cl5Close () +{ + int rc2 = 0; + PRIntervalTime interval; + + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) /* Don't try to close twice */ + { + + /* close db files */ + _cl5DBClose (); + + /* stop global threads */ + interval = PR_MillisecondsToInterval(100); + while (s_cl5Desc.threadCount > 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "_cl5Close: waiting for threads to exit: %d thread(s) still active\n", + s_cl5Desc.threadCount); + DS_Sleep(interval); + } + + /* cleanup trimming */ + _cl5TrimCleanup (); + + /* shutdown db environment */ + if (s_cl5Desc.dbEnv) + { + DB_ENV *dbEnv = s_cl5Desc.dbEnv; + rc2 = dbEnv->close(dbEnv, 0); + s_cl5Desc.dbEnv = NULL; + } + + /* record successful close by writing guardian file; + we do it in all case accept incomplete open due to an error */ + if (s_cl5Desc.dbState == CL5_STATE_CLOSING || s_cl5Desc.dbOpenMode != CL5_OPEN_NORMAL) + { + _cl5WriteGuardian (); + } + + /* remove changelog if requested */ + if (s_cl5Desc.dbRmOnClose) + { + + if (_cl5Delete (s_cl5Desc.dbDir, 1) != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Close: failed to remove changelog\n"); + } + s_cl5Desc.dbRmOnClose = PR_FALSE; + } + + slapi_ch_free ((void **)&s_cl5Desc.dbDir); + memset (&s_cl5Desc.dbConfig, 0, sizeof (s_cl5Desc.dbConfig)); + s_cl5Desc.fatalError = PR_FALSE; + s_cl5Desc.threadCount = 0; + s_cl5Desc.dbOpenMode = CL5_OPEN_NONE; + } +} + +static void _cl5DBClose () +{ + if (NULL != s_cl5Desc.dbFiles) + { + objset_delete (&s_cl5Desc.dbFiles); + } + if (NULL != s_cl5Desc.fileLock) + { + PR_DestroyLock (s_cl5Desc.fileLock); + } +} + +/* state lock must be locked */ +static int _cl5Delete (const char *clDir, int rmDir) +{ + PRDir *dir; + char filename[MAXPATHLEN + 1]; + PRDirEntry *entry = NULL; + int rc; + + /* remove all files in the directory and the directory */ + dir = PR_OpenDir(clDir); + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to open changelog dir; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + + } + + while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == entry->name) + { + break; + } + PR_snprintf(filename, MAXPATHLEN, "%s/%s", clDir, entry->name); + rc = PR_Delete(filename); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to remove (%s) file; NSPR error - %d\n", + filename, PR_GetError ()); + } + } + + rc = PR_CloseDir(dir); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to close changelog dir (%s); NSPR error - %d\n", + clDir, PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (rmDir) + { + rc = PR_RmDir (clDir); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to remove changelog dir (%s); errno = %d\n", + clDir, errno); + return CL5_SYSTEM_ERROR; + } + } + + return CL5_SUCCESS; +} + +static void _cl5SetDefaultDBConfig () +{ + s_cl5Desc.dbConfig.cacheSize = CL5_DEFAULT_CONFIG_DB_DBCACHESIZE; + s_cl5Desc.dbConfig.durableTrans = CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS; + s_cl5Desc.dbConfig.checkpointInterval = CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL; + s_cl5Desc.dbConfig.circularLogging = CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING; + s_cl5Desc.dbConfig.pageSize = CL5_DEFAULT_CONFIG_DB_PAGE_SIZE; + s_cl5Desc.dbConfig.logfileSize = CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE; + s_cl5Desc.dbConfig.maxTxnSize = CL5_DEFAULT_CONFIG_DB_TXN_MAX; + s_cl5Desc.dbConfig.verbose = CL5_DEFAULT_CONFIG_DB_VERBOSE; + s_cl5Desc.dbConfig.debug = CL5_DEFAULT_CONFIG_DB_DEBUG; + s_cl5Desc.dbConfig.tricklePercentage = CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE; + s_cl5Desc.dbConfig.spinCount = CL5_DEFAULT_CONFIG_DB_SPINCOUNT; + s_cl5Desc.dbConfig.nb_lock_config = CL5_DEFAULT_CONFIG_NB_LOCK; + s_cl5Desc.dbConfig.fileMode = FILE_CREATE_MODE; +} + +static void _cl5SetDBConfig (const CL5DBConfig *config) +{ + /* through CL5DBConfig, we have access to all the LDAP configurable Changelog DB parameters */ + s_cl5Desc.dbConfig.cacheSize = config->cacheSize; + s_cl5Desc.dbConfig.durableTrans = config->durableTrans; + s_cl5Desc.dbConfig.checkpointInterval = config->checkpointInterval; + s_cl5Desc.dbConfig.circularLogging = config->circularLogging; + s_cl5Desc.dbConfig.pageSize = config->pageSize; + s_cl5Desc.dbConfig.logfileSize = config->logfileSize; + s_cl5Desc.dbConfig.maxTxnSize = config->maxTxnSize; + s_cl5Desc.dbConfig.verbose = config->verbose; + s_cl5Desc.dbConfig.debug = config->debug; + s_cl5Desc.dbConfig.tricklePercentage = config->tricklePercentage; + s_cl5Desc.dbConfig.spinCount = config->spinCount; + s_cl5Desc.dbConfig.nb_lock_config = config->nb_lock_config; + s_cl5Desc.dbConfig.maxConcurrentWrites = config->maxConcurrentWrites; + + if (config->spinCount != 0) + { + DB_ENV_SET_TAS_SPINS(s_cl5Desc.dbEnv, config->spinCount); + } + + /* Some other configuration parameters are hardcoded... */ + s_cl5Desc.dbConfig.fileMode = FILE_CREATE_MODE; +} + +#define ONEG 1073741824 /* one giga bytes */ +static void _cl5InitDBEnv(DB_ENV *dbEnv) +{ + dbEnv->set_errpfx(dbEnv, "ns-slapd"); + dbEnv->set_lg_max(dbEnv, s_cl5Desc.dbConfig.logfileSize); + dbEnv->set_tx_max(dbEnv, s_cl5Desc.dbConfig.maxTxnSize); + dbEnv->set_cachesize(dbEnv, s_cl5Desc.dbConfig.cacheSize/ONEG, + s_cl5Desc.dbConfig.cacheSize%ONEG, + 0); + /* Set default number of locks */ + dbEnv->set_lk_max_locks(dbEnv, s_cl5Desc.dbConfig.nb_lock_config); + + if (s_cl5Desc.dbConfig.verbose) + { + int on = 1; + dbEnv->set_verbose(dbEnv, DB_VERB_CHKPOINT, on); + dbEnv->set_verbose(dbEnv, DB_VERB_DEADLOCK, on); + dbEnv->set_verbose(dbEnv, DB_VERB_RECOVERY, on); + dbEnv->set_verbose(dbEnv, DB_VERB_WAITSFOR, on); + } + if (s_cl5Desc.dbConfig.debug) + { + dbEnv->set_errcall(dbEnv, _cl5DBLogPrint); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300 + dbEnv->set_alloc(dbEnv, malloc, realloc, free); +#endif +} + +static void _cl5DBLogPrint(const char* prefix, char *buffer) +{ + /* We ignore the prefix since we know who we are anyway */ + slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5: %s\n", buffer); +} + +static PRBool _cl5IsLogFile (const char *path) +{ + int rc; + + /* Is the filename at least 4 characters long ? */ + if (strlen(path) < 4) + { + return PR_FALSE; /* Not a log file then */ + } + + /* Are the first 4 characters "log." ? */ + rc = strncmp(path,"log.",4); + if (0 == rc) + { + /* Now, are the last 4 characters _not_ .db# ? */ + const char *piece = path + (strlen(path) - 4); + rc = strcmp(piece, DB_EXTENSION); + if (0 != rc) + { + /* Is */ + return PR_TRUE; + } + } + return PR_FALSE; /* Is not */ +} + +static int _cl5Recover (int open_flags, DB_ENV *dbEnv) +{ + /* If we're doing recovery, we MUST open the env single-threaded ! */ + int recover_flags = open_flags & ~DB_THREAD; + int rc; + + rc = dbEnv->open(dbEnv, s_cl5Desc.dbDir, recover_flags, s_cl5Desc.dbConfig.fileMode); + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Recover: appinit failed; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } + + /* Now close it so we can re-open it again... */ + dbEnv->close(dbEnv, 0); + + return CL5_SUCCESS; +} + +/* Trimming helper functions */ +static int _cl5TrimInit () +{ + /* just create the lock while we are singlethreaded */ + s_cl5Desc.dbTrim.lock = PR_NewLock(); + + if (s_cl5Desc.dbTrim.lock == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5InitTrimming: failed to create lock; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + else + { + return CL5_SUCCESS; + } +} + +static void _cl5TrimCleanup () +{ + if (s_cl5Desc.dbTrim.lock) + PR_DestroyLock (s_cl5Desc.dbTrim.lock); + + memset (&s_cl5Desc.dbTrim, 0, sizeof (s_cl5Desc.dbTrim)); +} + +static int _cl5TrimMain (void *param) +{ + PRIntervalTime interval; + time_t timePrev = current_time (); + time_t timeNow; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + interval = PR_SecondsToInterval(CHANGELOGDB_TRIM_INTERVAL); + + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + timeNow = current_time (); + if (timeNow - timePrev >= CHANGELOGDB_TRIM_INTERVAL) + { + /* time to trim */ + timePrev = timeNow; + _cl5DoTrimming (); + } + if (NULL == s_cl5Desc.clLock) + { + /* most likely, emergency */ + break; + } + + PR_Lock(s_cl5Desc.clLock); + PR_WaitCondVar(s_cl5Desc.clCvar, interval); + PR_Unlock(s_cl5Desc.clLock); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + + return 0; +} + +/* We remove an entry if it has been replayed to all consumers and + and the number of entries in the changelog is larger than maxEntries + or age of the entry is larger than maxAge. + Also we can't purge entries which correspond to max csns in the + supplier's ruv. Here is a example where we can get into trouble: + The server is setup with time based trimming and no consumer's + At some point all the entries are trimmed from the changelog. + At a later point a consumer is added and initialized online + Then a change is made on the supplier. + To update the consumer, the supplier would attempt to locate + the last change sent to the consumer in the changelog and will + fail because the change was removed. + + */ + +static void _cl5DoTrimming () +{ + Object *obj; + long numToTrim; + + PR_Lock (s_cl5Desc.dbTrim.lock); + + /* ONREPL We trim file by file which means that some files will be + trimmed more often than other. We might have to fix that by, for + example, randomizing starting point */ + obj = objset_first_obj (s_cl5Desc.dbFiles); + while (obj && _cl5CanTrim ((time_t)0, &numToTrim)) + { + _cl5TrimFile (obj, &numToTrim); + obj = objset_next_obj (s_cl5Desc.dbFiles, obj); + } + + if (obj) + object_release (obj); + + PR_Unlock (s_cl5Desc.dbTrim.lock); + + return; +} + +/* Note that each file contains changes for a single replicated area. + trimming algorithm: +*/ +#define CL5_TRIM_MAX_PER_TRANSACTION 10 + +static void _cl5TrimFile (Object *obj, long *numToTrim) +{ + DB_TXN *txnid; + RUV *ruv = NULL; + CL5Entry entry; + slapi_operation_parameters op = {0}; + void *it; + int finished = 0, totalTrimmed = 0, count; + PRBool abort; + char strCSN[CSN_STRSIZE]; + int rc; + + PR_ASSERT (obj); + + /* construct the ruv up to which we can purge */ + rc = _cl5GetRUV2Purge2 (obj, &ruv); + if (rc != CL5_SUCCESS || ruv == NULL) + { + return; + } + + entry.op = &op; + + while ( !finished && !g_get_shutdown() ) + { + it = NULL; + count = 0; + txnid = NULL; + abort = PR_FALSE; + + /* DB txn lock accessed pages until the end of the transaction. */ + + rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrimFile: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + finished = PR_TRUE; + break; + } + + finished = _cl5GetFirstEntry (obj, &entry, &it, txnid); + while ( !finished ) + { + /* + * This change can be trimmed if it exceeds purge + * parameters and has been seen by all consumers. + */ + if ( (*numToTrim > 0 || _cl5CanTrim (entry.time, numToTrim)) && + ruv_covers_csn_strict (ruv, op.csn) ) + { + rc = _cl5CurrentDeleteEntry (it); + if ( rc == CL5_SUCCESS ) + { + /* update purge vector */ + rc = _cl5UpdateRUV (obj, op.csn, PR_FALSE, PR_TRUE); + } + if ( rc == CL5_SUCCESS) + { + if (*numToTrim > 0) (*numToTrim)--; + count++; + } + else + { + /* The above two functions have logged the error */ + abort = PR_TRUE; + } + + } + else + { + /* The changelog DB is time ordered. If we can not trim + * a CSN, we will not be allowed to trim the rest of the + * CSNs generally. However, the maxcsn of each replica ID + * is always kept in the changelog as an anchor for + * replaying future changes. We have to skip those anchor + * CSNs, otherwise a non-active replica ID could block + * the trim forever. + */ + CSN *maxcsn = NULL; + ReplicaId rid; + + rid = csn_get_replicaid (op.csn); + ruv_get_largest_csn_for_replica (ruv, rid, &maxcsn); + if ( csn_compare (op.csn, maxcsn) != 0 ) + { + /* op.csn is not anchor CSN */ + finished = 1; + } + else + { + slapi_log_error (SLAPI_LOG_REPL, NULL, + "Changelog purge skipped anchor csn %s\n", + csn_as_string (maxcsn, PR_FALSE, strCSN)); + + /* extra read to skip the current record */ + cl5_operation_parameters_done (&op); + finished =_cl5GetNextEntry (&entry, it); + } + if (maxcsn) csn_free (&maxcsn); + } + cl5_operation_parameters_done (&op); + if (finished || abort || count >= CL5_TRIM_MAX_PER_TRANSACTION) + { + /* If we reach CL5_TRIM_MAX_PER_TRANSACTION, + * we close the cursor, + * commit the transaction and restart a new transaction + */ + break; + } + finished = _cl5GetNextEntry (&entry, it); + } + + /* MAB: We need to close the cursor BEFORE the txn commits/aborts. + * If we don't respect this order, we'll screw up the database, + * placing it in DB_RUNRECOVERY mode + */ + cl5DestroyIterator (it); + + if (abort) + { + finished = 1; + rc = TXN_ABORT (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrimFile: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } + } + else + { + rc = TXN_COMMIT (txnid, 0); + if (rc != 0) + { + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrimFile: failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } + else + { + totalTrimmed += count; + } + } + + } /* While (!finished) */ + + if (ruv) + ruv_destroy (&ruv); + + if (totalTrimmed) + { + slapi_log_error (SLAPI_LOG_REPL, NULL, "Trimmed %d changes from the changelog\n", totalTrimmed); + } +} + +static PRBool _cl5CanTrim (time_t time, long *numToTrim) +{ + *numToTrim = 0; + + if (s_cl5Desc.dbTrim.maxAge == 0 && s_cl5Desc.dbTrim.maxEntries == 0) + return PR_FALSE; + + if (s_cl5Desc.dbTrim.maxAge == 0) + { + *numToTrim = cl5GetOperationCount (NULL) - s_cl5Desc.dbTrim.maxEntries; + return ( *numToTrim > 0 ); + } + + if (s_cl5Desc.dbTrim.maxEntries > 0 && + (*numToTrim = cl5GetOperationCount (NULL) - s_cl5Desc.dbTrim.maxEntries) > 0) + return PR_TRUE; + + if (time) + return (current_time () - time > s_cl5Desc.dbTrim.maxAge); + else + return PR_TRUE; +} + +static int _cl5ReadRUV (const char *replGen, Object *obj, PRBool purge) +{ + int rc; + char csnStr [CSN_STRSIZE]; + DBT key={0}, data={0}; + struct berval **vals; + CL5DBFile *file; + char *pos; + char *agmt_name; + + + PR_ASSERT (replGen && obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + agmt_name = get_thread_private_agmtname(); + + if (purge) /* read purge vector entry */ + key.data = _cl5GetHelperEntryKey (PURGE_RUV_TIME, csnStr); + else /* read upper bound vector */ + key.data = _cl5GetHelperEntryKey (MAX_RUV_TIME, csnStr); + + key.size = CSN_STRSIZE; + + data.flags = DB_DBT_MALLOC; + + rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0); + switch (rc) + { + case 0: pos = data.data; + rc = _cl5ReadBervals (&vals, &pos, data.size); + free (data.data); + if (rc != CL5_SUCCESS) + return rc; + + if (purge) + rc = ruv_init_from_bervals(vals, &file->purgeRUV); + else + rc = ruv_init_from_bervals(vals, &file->maxRUV); + + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: _cl5ReadRUV: failed to initialize %s ruv; " + "RUV error %d\n", agmt_name, purge? "purge" : "upper bound", rc); + + return CL5_RUV_ERROR; + } + + ber_bvecfree(vals); + + /* delete the entry; it is re-added when file + is successfully closed */ + file->db->del (file->db, NULL, &key, DEFAULT_DB_OP_FLAGS); + + return CL5_SUCCESS; + + case DB_NOTFOUND: /* RUV is lost - need to construct */ + rc = _cl5ConstructRUV (replGen, obj, purge); + return rc; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: _cl5ReadRUV: failed to get purge RUV; " + "db error - %d %s\n", agmt_name, rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +} + +static int _cl5WriteRUV (CL5DBFile *file, PRBool purge) +{ + int rc; + DBT key={0}, data={0}; + char csnStr [CSN_STRSIZE]; + struct berval **vals; + DB_TXN *txnid = NULL; + + if ((purge && file->purgeRUV == NULL) || (!purge && file->maxRUV == NULL)) + return CL5_SUCCESS; + + if (purge) + { + key.data = _cl5GetHelperEntryKey (PURGE_RUV_TIME, csnStr); + rc = ruv_to_bervals(file->purgeRUV, &vals); + } + else + { + key.data = _cl5GetHelperEntryKey (MAX_RUV_TIME, csnStr); + rc = ruv_to_bervals(file->maxRUV, &vals); + } + + key.size = CSN_STRSIZE; + + rc = _cl5WriteBervals (vals, (char**)&data.data, &data.size); + ber_bvecfree(vals); + if (rc != CL5_SUCCESS) + { + return rc; + } + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_begin(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + rc = file->db->put(file->db, txnid, &key, &data, DEFAULT_DB_OP_FLAGS); + + slapi_ch_free ((void**)&data.data); + if ( rc == 0 ) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_commit (txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + return CL5_SUCCESS; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to write %s RUV for file %s; db error - %d\n", + purge? "purge" : "upper bound", file->name, rc); + + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + cl5_set_diskfull(); + return CL5_DB_ERROR; + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + return CL5_DB_ERROR; + } +} + +/* This is a very slow process since we have to read every changelog entry. + Hopefully, this function is not called too often */ +static int _cl5ConstructRUV (const char *replGen, Object *obj, PRBool purge) +{ + int rc; + CL5Entry entry; + void *iterator = NULL; + slapi_operation_parameters op = {0}; + CL5DBFile *file; + + PR_ASSERT (replGen && obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + /* construct the RUV */ + if (purge) + rc = ruv_init_new (replGen, 0, NULL, &file->purgeRUV); + else + rc = ruv_init_new (replGen, 0, NULL, &file->maxRUV); + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5ConstructRUV: " + "failed to initialize %s RUV for file %s; ruv error - %d\n", + purge? "purge" : "upper bound", file->name, rc); + return CL5_RUV_ERROR; + } + + entry.op = &op; + rc = _cl5GetFirstEntry (obj, &entry, &iterator, NULL); + while (rc == CL5_SUCCESS) + { + if (purge) + rc = ruv_set_csns_keep_smallest(file->purgeRUV, op.csn); + else + rc = ruv_set_csns (file->maxRUV, op.csn, NULL); + + cl5_operation_parameters_done (&op); + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5ConstructRUV: " + "failed to updated %s RUV for file %s; ruv error - %d\n", + purge ? "purge" : "upper bound", file->name, rc); + rc = CL5_RUV_ERROR; + continue; + } + + rc = _cl5GetNextEntry (&entry, iterator); + } + + cl5_operation_parameters_done (&op); + + if (iterator) + cl5DestroyIterator (iterator); + + if (rc == CL5_NOTFOUND) + { + rc = CL5_SUCCESS; + } + else + { + if (purge) + ruv_destroy (&file->purgeRUV); + else + ruv_destroy (&file->maxRUV); + } + + return rc; +} + +static int _cl5UpdateRUV (Object *obj, CSN *csn, PRBool newReplica, PRBool purge) +{ + ReplicaId rid; + int rc = RUV_SUCCESS; /* initialize rc to avoid erroneous logs */ + CL5DBFile *file; + + PR_ASSERT (obj && csn); + + file = (CL5DBFile*)object_get_data (obj); + + /* if purge is TRUE, file->purgeRUV must be set; + if purge is FALSE, maxRUV must be set */ + PR_ASSERT (file && ((purge && file->purgeRUV) || (!purge && file->maxRUV))); + + /* update vector only if this replica is not yet part of RUV */ + if (purge && newReplica) + { + rid = csn_get_replicaid(csn); + if (ruv_contains_replica (file->purgeRUV, rid)) + return CL5_SUCCESS; + else + { + /* if the replica is not part of the purgeRUV yet, add it */ + ruv_add_replica (file->purgeRUV, rid, multimaster_get_local_purl()); + } + } + else + { + if (purge) + rc = ruv_set_csns(file->purgeRUV, csn, NULL); + else + rc = ruv_set_csns(file->maxRUV, csn, NULL); + } + + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5UpdatePurgeRUV: " + "failed to update %s RUV for file %s; ruv error - %d\n", + purge ? "purge" : "upper bound", file->name, rc); + return CL5_RUV_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5EnumConsumerRUV (const ruv_enum_data *element, void *arg) +{ + int rc; + RUV *ruv; + CSN *csn = NULL; + + PR_ASSERT (element && element->csn && arg); + + ruv = (RUV*)arg; + + rc = ruv_get_largest_csn_for_replica(ruv, csn_get_replicaid (element->csn), &csn); + if (rc != RUV_SUCCESS || csn == NULL || csn_compare (element->csn, csn) < 0) + { + ruv_set_max_csn(ruv, element->csn, NULL); + } + + if (csn) + csn_free (&csn); + + return 0; +} + +static int _cl5GetRUV2Purge2 (Object *fileObj, RUV **ruv) +{ + int rc = CL5_SUCCESS; + CL5DBFile *dbFile; + Object *rObj = NULL; + Replica *r = NULL; + Object *agmtObj = NULL; + Repl_Agmt *agmt; + Object *consRUVObj, *supRUVObj; + RUV *consRUV, *supRUV; + CSN *csn; + + PR_ASSERT (fileObj && ruv); + + dbFile = (CL5DBFile*)object_get_data (fileObj); + PR_ASSERT (dbFile); + + rObj = replica_get_by_name (dbFile->replName); + PR_ASSERT (rObj); + r = (Replica*)object_get_data (rObj); + PR_ASSERT (r); + + /* We start with this replica's RUV. See note in _cl5DoTrimming */ + supRUVObj = replica_get_ruv (r); + PR_ASSERT (supRUVObj); + + supRUV = (RUV*)object_get_data (supRUVObj); + PR_ASSERT (supRUV); + + *ruv = ruv_dup (supRUV); + + object_release (supRUVObj); + + agmtObj = agmtlist_get_first_agreement_for_replica (r); + while (agmtObj) + { + agmt = (Repl_Agmt*)object_get_data (agmtObj); + PR_ASSERT (agmt); + + consRUVObj = agmt_get_consumer_ruv (agmt); + if (consRUVObj) + { + consRUV = (RUV*)object_get_data (consRUVObj); + rc = ruv_enumerate_elements (consRUV, _cl5EnumConsumerRUV, *ruv); + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5GetRUV2Purge2: " + "failed to construct ruv; ruv error - %d\n", rc); + rc = CL5_RUV_ERROR; + object_release (consRUVObj); + object_release (agmtObj); + break; + } + + object_release (consRUVObj); + } + + agmtObj = agmtlist_get_next_agreement_for_replica (r, agmtObj); + } + + /* check if there is any data in the constructed ruv - otherwise get rid of it */ + if (ruv_get_max_csn(*ruv, &csn) != RUV_SUCCESS || csn == NULL) + { + ruv_destroy (ruv); + } + else + { + csn_free (&csn); + } + + if (rObj) + object_release (rObj); + + if (rc != CL5_SUCCESS && ruv) + ruv_destroy (ruv); + + return rc; +} + +static int _cl5GetEntryCount (CL5DBFile *file) +{ + int rc; + char csnStr [CSN_STRSIZE]; + DBT key={0}, data={0}; + DB_BTREE_STAT *stats = NULL; + + PR_ASSERT (file); + + /* read entry count. if the entry is there - the file was successfully closed + last time it was used */ + key.data = _cl5GetHelperEntryKey (ENTRY_COUNT_TIME, csnStr); + key.size = CSN_STRSIZE; + + data.flags = DB_DBT_MALLOC; + + rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0); + switch (rc) + { + case 0: file->entryCount = *(int*)data.data; + free (data.data); + + /* delete the entry. the entry is re-added when file + is successfully closed */ + file->db->del (file->db, NULL, &key, DEFAULT_DB_OP_FLAGS); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetEntryCount: %d changes for replica %s\n", + file->entryCount, file->replName); + return CL5_SUCCESS; + + case DB_NOTFOUND: file->entryCount = 0; +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300 + rc = file->db->stat(file->db, (void*)&stats, 0); +#else + rc = file->db->stat(file->db, (void*)&stats, malloc, 0); +#endif + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetEntryCount: failed to get changelog statistics; " + "db error - %d %s\n", rc, db_strerror(rc)); + return CL5_DB_ERROR; + } + +#ifdef DB30 + file->entryCount = stats->bt_nrecs; +#else /* DB31 */ + file->entryCount = stats->bt_ndata; +#endif + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetEntryCount: %d changes for replica %s\n", + file->entryCount, file->replName); + + free (stats); + return CL5_SUCCESS; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetEntryCount: failed to get count entry; " + "db error - %d %s\n", rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +} + +static int _cl5WriteEntryCount (CL5DBFile *file) +{ + int rc; + DBT key={0}, data={0}; + char csnStr [CSN_STRSIZE]; + DB_TXN *txnid = NULL; + + key.data = _cl5GetHelperEntryKey (ENTRY_COUNT_TIME, csnStr); + key.size = CSN_STRSIZE; + data.data = (void*)&file->entryCount; + data.size = sizeof (file->entryCount); + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_begin(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + rc = file->db->put(file->db, txnid, &key, &data, DEFAULT_DB_OP_FLAGS); + if (rc == 0) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_commit (txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + return CL5_SUCCESS; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: " + "failed to write count entry for file %s; db error - %d %s\n", + file->name, rc, db_strerror(rc)); + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + cl5_set_diskfull(); + return CL5_DB_ERROR; + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + return CL5_DB_ERROR; + } +} + +static const char* _cl5OperationType2Str (int type) +{ + switch (type) + { + case SLAPI_OPERATION_ADD: return T_ADDCTSTR; + case SLAPI_OPERATION_MODIFY: return T_MODIFYCTSTR; + case SLAPI_OPERATION_MODRDN: return T_MODRDNCTSTR; + case SLAPI_OPERATION_DELETE: return T_DELETECTSTR; + default: return NULL; + } +} + +static int _cl5Str2OperationType (const char *str) +{ + if (strcasecmp (str, T_ADDCTSTR) == 0) + return SLAPI_OPERATION_ADD; + + if (strcasecmp (str, T_MODIFYCTSTR) == 0) + return SLAPI_OPERATION_MODIFY; + + if (strcasecmp (str, T_MODRDNCTSTR) == 0) + return SLAPI_OPERATION_MODRDN; + + if (strcasecmp (str, T_DELETECTSTR) == 0) + return SLAPI_OPERATION_DELETE; + + return -1; +} + +static int _cl5Operation2LDIF (const slapi_operation_parameters *op, const char *replGen, + char **ldifEntry, PRInt32 *lenLDIF) +{ + int len = 2; + lenstr *l = NULL; + const char *strType; + char *strDeleteOldRDN; + char *buff, *start; + LDAPMod **add_mods; + char *rawDN; + char strCSN[CSN_STRSIZE]; + + PR_ASSERT (op && replGen && ldifEntry && IsValidOperation (op)); + + strType = _cl5OperationType2Str (op->operation_type); + csn_as_string(op->csn,PR_FALSE,strCSN); + + /* find length of the buffer */ + len += LDIF_SIZE_NEEDED(strlen (T_CHANGETYPESTR), strlen (strType)); + len += LDIF_SIZE_NEEDED(strlen (T_REPLGEN), strlen (replGen)); + len += LDIF_SIZE_NEEDED(strlen (T_CSNSTR), strlen (strCSN)); + len += LDIF_SIZE_NEEDED(strlen (T_UNIQUEIDSTR), strlen (op->target_address.uniqueid)); + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid) + len += LDIF_SIZE_NEEDED(strlen (T_PARENTIDSTR), + strlen (op->p.p_add.parentuniqueid)); + slapi_entry2mods (op->p.p_add.target_entry, &rawDN, &add_mods); + len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (rawDN)); + l = make_changes_string(add_mods, NULL); + len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len); + ldap_mods_free (add_mods, 1); + break; + + case SLAPI_OPERATION_MODIFY: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn)); + l = make_changes_string(op->p.p_modify.modify_mods, NULL); + len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len); + break; + + case SLAPI_OPERATION_MODRDN: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn)); + len += LDIF_SIZE_NEEDED(strlen (T_NEWRDNSTR), + strlen (op->p.p_modrdn.modrdn_newrdn)); + strDeleteOldRDN = (op->p.p_modrdn.modrdn_deloldrdn ? "true" : "false"); + len += LDIF_SIZE_NEEDED(strlen (T_DRDNFLAGSTR), + strlen (strDeleteOldRDN)); + if (op->p.p_modrdn.modrdn_newsuperior_address.dn) + len += LDIF_SIZE_NEEDED(strlen (T_NEWSUPERIORDNSTR), + strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn)); + if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + len += LDIF_SIZE_NEEDED(strlen (T_NEWSUPERIORIDSTR), + strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)); + l = make_changes_string(op->p.p_modrdn.modrdn_mods, NULL); + len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len); + break; + + case SLAPI_OPERATION_DELETE: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn)); + break; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Operation2LDIF: invalid operation type - %d\n", op->operation_type); + + return CL5_BAD_FORMAT; + } + + /* allocate buffer */ + buff = (char*)slapi_ch_malloc (len); + start = buff; + if (buff == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Operation2LDIF: memory allocation failed\n"); + return CL5_MEMORY_ERROR; + } + + /* fill buffer */ + ldif_put_type_and_value(&buff, T_CHANGETYPESTR, (char*)strType, strlen (strType)); + ldif_put_type_and_value(&buff, T_REPLGEN, (char*)replGen, strlen (replGen)); + ldif_put_type_and_value(&buff, T_CSNSTR, (char*)strCSN, strlen (strCSN)); + ldif_put_type_and_value(&buff, T_UNIQUEIDSTR, op->target_address.uniqueid, + strlen (op->target_address.uniqueid)); + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid) + ldif_put_type_and_value(&buff, T_PARENTIDSTR, + op->p.p_add.parentuniqueid, strlen (op->p.p_add.parentuniqueid)); + ldif_put_type_and_value(&buff, T_DNSTR, rawDN, strlen (rawDN)); + ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len); + slapi_ch_free ((void**)&rawDN); + break; + + case SLAPI_OPERATION_MODIFY: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn, + strlen (op->target_address.dn)); + ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len); + break; + + case SLAPI_OPERATION_MODRDN: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn, + strlen (op->target_address.dn)); + ldif_put_type_and_value(&buff, T_NEWRDNSTR, op->p.p_modrdn.modrdn_newrdn, + strlen (op->p.p_modrdn.modrdn_newrdn)); + ldif_put_type_and_value(&buff, T_DRDNFLAGSTR, strDeleteOldRDN, + strlen (strDeleteOldRDN)); + if (op->p.p_modrdn.modrdn_newsuperior_address.dn) + ldif_put_type_and_value(&buff, T_NEWSUPERIORDNSTR, + op->p.p_modrdn.modrdn_newsuperior_address.dn, + strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn)); + if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + ldif_put_type_and_value(&buff, T_NEWSUPERIORIDSTR, + op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, + strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)); + ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len); + break; + + case SLAPI_OPERATION_DELETE: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn, + strlen (op->target_address.dn)); + break; + } + + *buff = '\n'; + buff ++; + *buff = '\0'; + + *ldifEntry = start; + *lenLDIF = buff - start; + + if (l) + lenstr_free(&l); + + return CL5_SUCCESS; +} + +static int +_cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, char **replGen) +{ + int rc; + int vlen; + char *next, *line; + char *type, *value; + Slapi_Mods *mods; + char *rawDN; + + PR_ASSERT (op && ldifEntry && replGen); + + memset (op, 0, sizeof (*op)); + + next = ldifEntry; + while ((line = ldif_getline(&next)) != NULL) + { + char *errmsg = NULL; + + if ( *line == '\n' || *line == '\0' ) + { + break; + } + + /* this call modifies ldifEntry */ + rc = ldif_parse_line(line, &type, &value, &vlen, &errmsg); + if (rc != 0) + { + if ( errmsg != NULL ) { + slapi_log_error(SLAPI_LOG_PARSE, repl_plugin_name_cl, "%s", errmsg); + slapi_ch_free( (void**)&errmsg ); + } + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5LDIF2Operation: warning - failed to parse ldif line\n"); + continue; + } + + if (strcasecmp (type, T_CHANGETYPESTR) == 0) + { + op->operation_type = _cl5Str2OperationType (value); + } + else if (strcasecmp (type, T_REPLGEN) == 0) + { + *replGen = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_CSNSTR) == 0) + { + op->csn = csn_new_by_string(value); + } + else if (strcasecmp (type, T_UNIQUEIDSTR) == 0) + { + op->target_address.uniqueid = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_DNSTR) == 0) + { + PR_ASSERT (op->operation_type); + + if (op->operation_type == SLAPI_OPERATION_ADD) + { + rawDN = slapi_ch_strdup (value); + op->target_address.dn = slapi_ch_strdup(rawDN); + } + else + op->target_address.dn = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_PARENTIDSTR) == 0) + { + op->p.p_add.parentuniqueid = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_NEWRDNSTR) == 0) + { + op->p.p_modrdn.modrdn_newrdn = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_DRDNFLAGSTR) == 0) + { + op->p.p_modrdn.modrdn_deloldrdn = (strcasecmp (value, "true") ? PR_FALSE : PR_TRUE); + } + else if (strcasecmp (type, T_NEWSUPERIORDNSTR) == 0) + { + op->p.p_modrdn.modrdn_newsuperior_address.dn = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_NEWSUPERIORIDSTR) == 0) + { + op->p.p_modrdn.modrdn_newsuperior_address.uniqueid = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_CHANGESTR) == 0) + { + PR_ASSERT (op->operation_type); + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: mods = parse_changes_string(value); + slapi_mods2entry (&(op->p.p_add.target_entry), rawDN, + slapi_mods_get_ldapmods_byref(mods)); + slapi_ch_free ((void**)&rawDN); + slapi_mods_free (&mods); + break; + + case SLAPI_OPERATION_MODIFY: mods = parse_changes_string(value); + PR_ASSERT (mods); + op->p.p_modify.modify_mods = slapi_mods_get_ldapmods_passout (mods); + slapi_mods_free (&mods); + break; + + case SLAPI_OPERATION_MODRDN: mods = parse_changes_string(value); + PR_ASSERT (mods); + op->p.p_modrdn.modrdn_mods = slapi_mods_get_ldapmods_passout (mods); + slapi_mods_free (&mods); + break; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5LDIF2Operation: invalid operation type - %d\n", + op->operation_type); + return CL5_BAD_FORMAT; + } + } + } + + if (IsValidOperation (op)) + return CL5_SUCCESS; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5LDIF2Operation: invalid data format\n"); + return CL5_BAD_FORMAT; +} + +static int _cl5WriteOperation(const char *replName, const char *replGen, + const slapi_operation_parameters *op, PRBool local) +{ + int rc; + int cnt; + DBT key={0}; + DBT * data=NULL; + char csnStr [CSN_STRSIZE]; + PRIntervalTime interval; + CL5Entry entry; + CL5DBFile *file = NULL; + Object *file_obj = NULL; + DB_TXN *txnid = NULL; + + rc = _cl5GetDBFileByReplicaName (replName, replGen, &file_obj); + if (rc == CL5_NOTFOUND) + { + rc = _cl5DBOpenFileByReplicaName (replName, replGen, &file_obj, + PR_TRUE /* check for duplicates */); + if (rc != CL5_SUCCESS) + { + return rc; + } + } + else if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "_cl5WriteOperation: failed to get db file for target dn (%s)", + op->target_address.dn); + return CL5_OBJSET_ERROR; + } + + /* assign entry time - used for trimming */ + entry.time = current_time (); + entry.op = (slapi_operation_parameters *)op; + + /* construct the key */ + key.data = csn_as_string(op->csn, PR_FALSE, csnStr); + key.size = CSN_STRSIZE; + + /* construct the data */ + data = (DBT *) slapi_ch_calloc(1, sizeof(DBT)); + rc = _cl5Entry2DBData (&entry, (char**)&data->data, &data->size); + if (rc != CL5_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to convert entry with csn (%s) " + "to db format\n", csn_as_string(op->csn,PR_FALSE,s)); + goto done; + } + + file = (CL5DBFile*)object_get_data (file_obj); + PR_ASSERT (file); + + /* if this is part of ldif2cl - just write the entry without transaction */ + if (s_cl5Desc.dbOpenMode == CL5_OPEN_LDIF2CL) + { + rc = file->db->put(file->db, NULL, &key, data, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to write entry; db error - %d %s\n", + rc, db_strerror(rc)); + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + cl5_set_diskfull(); + } + rc = CL5_DB_ERROR; + } + goto done; + } + + /* write the entry */ + rc = EAGAIN; + cnt = 0; + + while ((rc == EAGAIN || rc == DB_LOCK_DEADLOCK) && cnt < MAX_TRIALS) + { + if (cnt != 0) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + /* abort previous transaction */ + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } +#endif + /* back off */ + interval = PR_MillisecondsToInterval(slapi_rand() % 100); + DS_Sleep(interval); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + /* begin transaction */ + rc = txn_begin(s_cl5Desc.dbEnv, NULL /*pid*/, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to start transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } +#endif + + if ( file->sema ) + { + PR_WaitSemaphore(file->sema); + } + rc = file->db->put(file->db, txnid, &key, data, DEFAULT_DB_OP_FLAGS); + if ( file->sema ) + { + PR_PostSemaphore(file->sema); + } + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: changelog (%s) DISK FULL; db error - %d %s\n", + s_cl5Desc.dbDir, rc, db_strerror(rc)); + cl5_set_diskfull(); + rc = CL5_DB_ERROR; + goto done; + } + if (cnt != 0) + { + if (rc == 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) succeeded\n", cnt, (char*)key.data); + } + else if ((cnt + 1) >= MAX_TRIALS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) failed (rc=%d)\n", cnt, (char*)key.data, rc); + } + } + cnt ++; + } + + if (rc == 0) /* we successfully added entry */ + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_commit (txnid, 0); +#endif + } + else + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to write entry with csn (%s); " + "db error - %d %s\n", csn_as_string(op->csn,PR_FALSE,s), + rc, db_strerror(rc)); +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + rc = CL5_DB_ERROR; + goto done; + } + + /* update entry count - we assume that all entries are new */ + PR_AtomicIncrement (&file->entryCount); + + /* update purge vector if we have not seen any changes from this replica before */ + _cl5UpdateRUV (file_obj, op->csn, PR_TRUE, PR_TRUE); + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5WriteOperation: successfully written entry with csn (%s)\n", csnStr); + rc = CL5_SUCCESS; +done: + if (data->data) + slapi_ch_free ((void**)&data->data); + slapi_ch_free((void**) &data); + + if (file_obj) + object_release (file_obj); + + return rc; +} + +static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid) +{ + int rc; + DBC *cursor = NULL; + DBT key={0}, data={0}; + CL5Iterator *it; + CL5DBFile *file; + + PR_ASSERT (obj && entry && iterator); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + /* create cursor */ + rc = file->db->cursor(file->db, txnid, &cursor, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetFirstEntry: failed to create cursor; db error - %d %s\n", rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + key.flags = DB_DBT_MALLOC; + data.flags = DB_DBT_MALLOC; + while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) + { + /* skip service entries */ + if (cl5HelperEntry ((char*)key.data, NULL)) + { + free (key.data); + free (data.data); + continue; + } + + /* format entry */ + free (key.data); + rc = cl5DBData2Entry (data.data, data.size, entry); + free (data.data); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetFirstOperation: failed to format entry\n", rc); + goto done; + } + + it = (CL5Iterator*)slapi_ch_malloc (sizeof (CL5Iterator)); + it->cursor = cursor; + object_acquire (obj); + it->file = obj; + *(CL5Iterator**)iterator = it; + + return CL5_SUCCESS; + } + + /* walked of the end of the file */ + if (rc == DB_NOTFOUND) + { + rc = CL5_NOTFOUND; + goto done; + } + + /* db error occured while iterating */ + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetFirstEntry: failed to get entry; db error - %d %s\n", rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + /* successfully retrieved next entry but it was out of range */ + if (rc == CL5_SUCCESS) + { + free (key.data); + free (data.data); + rc = CL5_NOTFOUND; + goto done; + } + +done:; + /* error occured */ + /* We didn't success in assigning this cursor to the iterator, + * so we need to free the cursor here */ + if (cursor) + cursor->c_close(cursor); + + return rc; +} + +static int _cl5GetNextEntry (CL5Entry *entry, void *iterator) +{ + int rc; + CL5Iterator *it; + DBT key={0}, data={0}; + + PR_ASSERT (entry && iterator); + + it = (CL5Iterator*) iterator; + + key.flags = DB_DBT_MALLOC; + data.flags = DB_DBT_MALLOC; + while ((rc = it->cursor->c_get(it->cursor, &key, &data, DB_NEXT)) == 0) + { + if (cl5HelperEntry ((char*)key.data, NULL)) + { + free (key.data); + free (data.data); + continue; + } + + free (key.data); + /* format entry */ + rc = cl5DBData2Entry (data.data, data.size, entry); + free (data.data); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetNextEntry: failed to format entry\n", rc); + } + + return rc; + } + + /* walked of the end of the file or entry is out of range */ + if (rc == 0 || rc == DB_NOTFOUND) + { + return CL5_NOTFOUND; + } + + /* cursor operation failed */ + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetNextEntry: failed to get entry; db error - %d %s\n", rc, db_strerror(rc)); + + return CL5_DB_ERROR; + } + + return rc; +} + +static int _cl5CurrentDeleteEntry (void *iterator) +{ + int rc; + CL5Iterator *it; + CL5DBFile *file; + + PR_ASSERT (iterator); + + it = (CL5Iterator*)iterator; + + rc = it->cursor->c_del (it->cursor, 0); + + if (rc == 0) { + /* decrement entry count */ + file = (CL5DBFile*)object_get_data (it->file); + PR_AtomicDecrement (&file->entryCount); + return CL5_SUCCESS; + } else { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CurrentDeleteEntry failed, err=%d %s\n", + rc, db_strerror(rc)); + /* We don't free(close) the cursor here, as the caller will free it by a call to cl5DestroyIterator */ + /* Freeing it here is a potential bug, as the cursor can't be referenced later once freed */ + return CL5_DB_ERROR; + } +} + +static PRBool _cl5IsValidIterator (const CL5Iterator *iterator) +{ + return (iterator && iterator->cursor && iterator->file); +} + +static int _cl5GetOperation (Object *replica, slapi_operation_parameters *op) +{ + int rc; + DBT key={0}, data={0}; + CL5DBFile *file; + CL5Entry entry; + Object *obj = NULL; + char csnStr[CSN_STRSIZE]; + + rc = _cl5GetDBFile (replica, &obj); + if (rc != CL5_SUCCESS) + { + return rc; + } + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + /* construct the key */ + key.data = csn_as_string(op->csn, PR_FALSE, csnStr); + key.size = CSN_STRSIZE; + + data.flags = DB_DBT_MALLOC; + + rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0); + switch (rc) + { + case 0: entry.op = op; + /* Callers of this function should cl5_operation_parameters_done(op) */ + rc = cl5DBData2Entry (data.data, data.size, &entry); + if (rc == CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "_cl5GetOperation: successfully retrieved operation with csn (%s)\n", + csnStr); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetOperation: failed to convert db data to operation;" + " csn - %s\n", csnStr); + } + goto done; + + case DB_NOTFOUND: slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetOperation: operation for csn (%s) is not found in db that should contain dn (%s)\n", + csnStr, op->target_address.dn); + rc = CL5_NOTFOUND; + goto done; + + default: slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetOperation: failed to get entry for csn (%s); " + "db error - %d %s\n", csnStr, rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + +done:; + if (obj) + object_release (obj); + + if (data.data) + free (data.data); + + return rc; +} + +PRBool cl5HelperEntry (const char *csnstr, CSN *csnp) +{ + CSN *csn; + time_t csnTime; + PRBool retval = PR_FALSE; + + if (csnp) + { + csn = csnp; + } + else + { + csn= csn_new_by_string(csnstr); + } + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5HelperEntry: failed to get csn time; csn error\n"); + return PR_FALSE; + } + csnTime= csn_get_time(csn); + + if (csnTime == ENTRY_COUNT_TIME || csnTime == PURGE_RUV_TIME) + { + retval = PR_TRUE; + } + + if (NULL == csnp) + csn_free(&csn); + return retval; +} + +/* Replay iteration helper functions */ +static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator) +{ + if (iterator == NULL || + iterator->consumerRuv == NULL || iterator->supplierRuvObj == NULL || + iterator->fileObj == NULL) + return PR_FALSE; + + return PR_TRUE; +} + +/* Algorithm: ONREPL!!! + */ +struct replica_hash_entry +{ + ReplicaId rid; /* replica id */ + PRBool sendChanges; /* indicates whether changes should be sent for this replica */ +}; + + +static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv, + Object *replica, Object *fileObj, CL5ReplayIterator **iterator) +{ + CLC_Buffer *clcache = NULL; + CL5DBFile *file; + int i; + CSN **csns = NULL; + CSN *startCSN = NULL; + char csnStr [CSN_STRSIZE]; + int rc = CL5_SUCCESS; + Object *supplierRuvObj = NULL; + RUV *supplierRuv = NULL; + ReplicaId supplierRID; + PRBool newReplica; + PRBool haveChanges = PR_FALSE; + char *agmt_name; + ReplicaId rid; + + PR_ASSERT (consumerRuv && replica && fileObj && iterator); + csnStr[0] = '\0'; + + file = (CL5DBFile*)object_get_data (fileObj); + supplierRID = replica_get_rid((Replica*)object_get_data(replica)); + + /* get supplier's RUV */ + supplierRuvObj = replica_get_ruv((Replica*)object_get_data(replica)); + PR_ASSERT (supplierRuvObj); + supplierRuv = (RUV*)object_get_data (supplierRuvObj); + PR_ASSERT (supplierRuv); + + agmt_name = get_thread_private_agmtname(); + slapi_log_error(SLAPI_LOG_REPL, NULL, "_cl5PositionCursorForReplay (%s): Consumer RUV:\n", agmt_name); + ruv_dump (consumerRuv, agmt_name, NULL); + slapi_log_error(SLAPI_LOG_REPL, NULL, "_cl5PositionCursorForReplay (%s): Supplier RUV:\n", agmt_name); + ruv_dump (supplierRuv, agmt_name, NULL); + + /* + * get the sorted list of SupplierMinCSN (if no ConsumerMaxCSN) + * and ConsumerMaxCSN for those RIDs where consumer is not + * up-to-date. + */ + csns = cl5BuildCSNList (consumerRuv, supplierRuv); + if (csns == NULL) + { + rc = CL5_NOTFOUND; + goto done; + } + + /* iterate over elements of consumer's (and/or supplier's) ruv */ + for (i = 0; csns[i]; i++) + { + CSN *consumerMaxCSN = NULL; + + rid = csn_get_replicaid(csns[i]); + + /* + * Skip CSN that is originated from the consumer. + * If RID==65535, the CSN is originated from a + * legacy consumer. In this case the supplier + * and the consumer may have the same RID. + */ + if (rid == consumerRID && rid != MAX_REPLICA_ID) + continue; + + startCSN = csns[i]; + csn_as_string(startCSN, PR_FALSE, csnStr); + + rc = clcache_get_buffer ( &clcache, file->db, consumerRID, consumerRuv, supplierRuv ); + if ( rc != 0 ) goto done; + + /* This is the first loading of this iteration. For replicas + * already known to the consumer, we exclude the last entry + * sent to the consumer by using DB_NEXT. However, for + * replicas new to the consumer, we include the first change + * ever generated by that replica. + */ + newReplica = ruv_get_largest_csn_for_replica (consumerRuv, rid, &consumerMaxCSN); + csn_free(&consumerMaxCSN); + rc = clcache_load_buffer (clcache, startCSN, (newReplica ? DB_SET : DB_NEXT)); + + /* there is a special case which can occur just after migration - in this case, + the consumer RUV will contain the last state of the supplier before migration, + but the supplier will have an empty changelog, or the supplier changelog will + not contain any entries within the consumer min and max CSN - also, since + the purge RUV contains no CSNs, the changelog has never been purged + ASSUMPTIONS - it is assumed that the supplier had no pending changes to send + to any consumers; that is, we can assume that no changes were lost due to + either changelog purging or database reload - bug# 603061 - richm@netscape.com + */ + if (rc == 0 || (rc == DB_NOTFOUND && !ruv_has_csns(file->purgeRUV))) + { + haveChanges = PR_TRUE; + rc = CL5_SUCCESS; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: CSN %s found, position set for replay\n", agmt_name, csnStr); + break; + } + else if (rc == DB_NOTFOUND) /* entry not found */ + { + /* check whether this csn should be present */ + rc = _cl5CheckMissingCSN (startCSN, supplierRuv, file); + if (rc == CL5_MISSING_DATA) /* we should have had the change but we don't */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: CSN %s not found, seems to be missing\n", agmt_name, csnStr); + break; + } + else /* we are not as up to date or we purged */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: CSN %s not found, we aren't as up to date, or we purged\n", + agmt_name, csnStr); + continue; + } + } + else + { + + /* db error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: Failed to retrieve change with CSN %s; db error - %d %s\n", + agmt_name, csnStr, rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + break; + } + + } /* end for */ + + /* setup the iterator */ + if (haveChanges) + { + *iterator = (CL5ReplayIterator*) slapi_ch_calloc (1, sizeof (CL5ReplayIterator)); + + if (*iterator == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: _cl5PositionCursorForReplay: failed to allocate iterator\n", agmt_name); + rc = CL5_MEMORY_ERROR; + goto done; + } + + /* ONREPL - should we make a copy of both RUVs here ?*/ + (*iterator)->fileObj = fileObj; + (*iterator)->clcache = clcache; clcache = NULL; + (*iterator)->consumerRID = consumerRID; + (*iterator)->consumerRuv = consumerRuv; + (*iterator)->supplierRuvObj = supplierRuvObj; + } + else if (rc == CL5_SUCCESS) + { + /* we have no changes to send */ + rc = CL5_NOTFOUND; + } + +done: + if ( clcache ) + clcache_return_buffer ( &clcache ); + + if (csns) + cl5DestroyCSNList (&csns); + + if (rc != CL5_SUCCESS) + { + if (supplierRuvObj) + object_release (supplierRuvObj); + } + + return rc; +} + +struct ruv_it +{ + CSN **csns; /* csn list */ + int alloc; /* allocated size */ + int pos; /* position in the list */ +}; + +static int ruv_consumer_iterator (const ruv_enum_data *enum_data, void *arg) +{ + struct ruv_it *data = (struct ruv_it*)arg; + + PR_ASSERT (data); + + /* check if we have space for one more element */ + if (data->pos >= data->alloc - 2) + { + data->alloc += 4; + data->csns = (CSN**) slapi_ch_realloc ((void*)data->csns, data->alloc * sizeof (CSN*)); + } + + data->csns [data->pos] = csn_dup (enum_data->csn); + data->pos ++; + + return 0; +} + + +static int ruv_supplier_iterator (const ruv_enum_data *enum_data, void *arg) +{ + int i; + PRBool found = PR_FALSE; + ReplicaId rid; + struct ruv_it *data = (struct ruv_it*)arg; + + PR_ASSERT (data); + + rid = csn_get_replicaid (enum_data->min_csn); + /* check if the replica that generated the csn is already in the list */ + for (i = 0; i < data->pos; i++) + { + if (rid == csn_get_replicaid (data->csns[i])) + { + found = PR_TRUE; + + /* remove datacsn[i] if it is greater or equal to the supplier's maxcsn */ + if ( csn_compare ( data->csns[i], enum_data->csn ) >= 0 ) + { + int j; + + csn_free ( & data->csns[i] ); + for (j = i+1; j < data->pos; j++) + { + data->csns [j-1] = data->csns [j]; + } + data->pos --; + } + break; + } + } + + if (!found) + { + /* check if we have space for one more element */ + if (data->pos >= data->alloc - 2) + { + data->alloc += 4; + data->csns = (CSN**)slapi_ch_realloc ((void*)data->csns, + data->alloc * sizeof (CSN*)); + } + + data->csns [data->pos] = csn_dup (enum_data->min_csn); + data->pos ++; + } + return 0; +} + + + +static int +my_csn_compare(const void *arg1, const void *arg2) +{ + return(csn_compare(*((CSN **)arg1), *((CSN **)arg2))); +} + + + +/* builds CSN ordered list of all csns in the RUV */ +CSN** cl5BuildCSNList (const RUV *consRuv, const RUV *supRuv) +{ + struct ruv_it data; + int count, rc; + CSN **csns; + + PR_ASSERT (consRuv); + + count = ruv_replica_count (consRuv); + csns = (CSN**)slapi_ch_calloc (count + 1, sizeof (CSN*)); + + data.csns = csns; + data.alloc = count + 1; + data.pos = 0; + + /* add consumer elements to the list */ + rc = ruv_enumerate_elements (consRuv, ruv_consumer_iterator, &data); + if (rc == 0 && supRuv) + { + /* add supplier elements to the list */ + rc = ruv_enumerate_elements (supRuv, ruv_supplier_iterator, &data); + } + + /* we have no csns */ + if (data.csns[0] == NULL) + { + /* csns might have been realloced in ruv_supplier_iterator() */ + slapi_ch_free ((void**)&data.csns); + csns = NULL; + } + else + { + csns = data.csns; + data.csns [data.pos] = NULL; + if (rc == 0) + { + qsort (csns, data.pos, sizeof (CSN*), my_csn_compare); + } + else + { + cl5DestroyCSNList (&csns); + } + } + + return csns; +} + +void cl5DestroyCSNList (CSN*** csns) +{ + if (csns && *csns) + { + int i; + + for (i = 0; (*csns)[i]; i++) + { + csn_free (&(*csns)[i]); + } + + slapi_ch_free ((void**)csns); + } +} + +/* A csn should be in the changelog if it is larger than purge vector csn for the same + replica and is smaller than the csn in supplier's ruv for the same replica. + The functions returns + CL5_PURGED if data was purged from the changelog or was never logged + because it was loaded as part of replica initialization + CL5_MISSING if the data erouneously missing + CL5_SUCCESS if that has not and should not been seen by the server + */ +static int _cl5CheckMissingCSN (const CSN *csn, const RUV *supplierRuv, CL5DBFile *file) +{ + ReplicaId rid; + CSN *supplierCsn = NULL; + CSN *purgeCsn = NULL; + int rc = CL5_SUCCESS; + char csnStr [CSN_STRSIZE]; + + PR_ASSERT (csn && supplierRuv && file); + + rid = csn_get_replicaid (csn); + ruv_get_largest_csn_for_replica (supplierRuv, rid, &supplierCsn); + if (supplierCsn == NULL) + { + /* we have not seen any changes from this replica so it is + ok not to have this csn */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "can't locate %s csn: we have not seen any changes for replica %d\n", + csn_as_string (csn, PR_FALSE, csnStr), rid); + return CL5_SUCCESS; + } + + ruv_get_largest_csn_for_replica (file->purgeRUV, rid, &purgeCsn); + if (purgeCsn == NULL) + { + /* changelog never contained any changes for this replica */ + if (csn_compare (csn, supplierCsn) <= 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "the change with %s csn was never logged because it was imported " + "during replica initialization\n", csn_as_string (csn, PR_FALSE, csnStr)); + rc = CL5_PURGED_DATA; /* XXXggood is that the correct return value? */ + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "change with %s csn has not yet been seen by this server; " + " last csn seen from that replica is %s\n", + csn_as_string (csn, PR_FALSE, csnStr), + csn_as_string (supplierCsn, PR_FALSE, csnStr)); + rc = CL5_SUCCESS; + } + } + else /* we have both purge and supplier csn */ + { + if (csn_compare (csn, purgeCsn) < 0) /* the csn is below the purge point */ + { + rc = CL5_PURGED_DATA; + } + else + { + if (csn_compare (csn, supplierCsn) <= 0) /* we should have the data but we don't */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "change with %s csn has been purged by this server; " + "the current purge point for that replica is %s\n", + csn_as_string (csn, PR_FALSE, csnStr), + csn_as_string (purgeCsn, PR_FALSE, csnStr)); + rc = CL5_MISSING_DATA; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "change with %s csn has not yet been seen by this server; " + " last csn seen from that replica is %s\n", + csn_as_string (csn, PR_FALSE, csnStr), + csn_as_string (supplierCsn, PR_FALSE, csnStr)); + rc = CL5_SUCCESS; + } + } + } + + if (supplierCsn) + csn_free (&supplierCsn); + + if (purgeCsn) + csn_free (&purgeCsn); + + return rc; +} + +/* Helper functions that work with individual changelog files */ + +/* file name format : <replica name>_<replica generation>db{2,3} */ +static PRBool _cl5FileName2Replica (const char *file_name, Object **replica) +{ + Replica *r; + char *repl_name, *file_gen, *repl_gen; + int len; + + PR_ASSERT (file_name && replica); + + *replica = NULL; + + /* this is database file */ + if (_cl5FileEndsWith (file_name, DB_EXTENSION) || + _cl5FileEndsWith (file_name, DB_EXTENSION_DB3) ) + { + repl_name = slapi_ch_strdup (file_name); + file_gen = strstr(repl_name, FILE_SEP); + if (file_gen) + { + int extlen = strlen(DB_EXTENSION); + *file_gen = '\0'; + file_gen += strlen (FILE_SEP); + len = strlen (file_gen); + if (len <= extlen + 1) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica " + "invalid file name (%s)\n", file_name); + } + else + { + /* get rid of the file extension */ + file_gen [len - extlen - 1] = '\0'; + *replica = replica_get_by_name (repl_name); + if (*replica) + { + /* check that generation matches the one in replica object */ + r = (Replica*)object_get_data (*replica); + repl_gen = replica_get_generation (r); + PR_ASSERT (repl_gen); + if (strcmp (file_gen, repl_gen) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica " + "replica generation mismatch for replica at (%s), " + "file generation %s, new replica generation %s\n", + slapi_sdn_get_dn (replica_get_root (r)), file_gen, repl_gen); + + object_release (*replica); + *replica = NULL; + } + slapi_ch_free ((void**)&repl_gen); + } + } + slapi_ch_free ((void**)&repl_name); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica " + "malformed file name - %s\n", file_name); + } + + return PR_TRUE; + } + else + return PR_FALSE; +} + +/* file name format : <replica name>_<replica generation>db{2,3} */ +static char* _cl5Replica2FileName (Object *replica) +{ + const char *replName; + char *replGen, *fileName; + Replica *r; + + PR_ASSERT (replica); + + r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + + replName = replica_get_name (r); + replGen = replica_get_generation (r); + + fileName = _cl5MakeFileName (replName, replGen) ; + + slapi_ch_free ((void**)&replGen); + + return fileName; +} + +static char* _cl5MakeFileName (const char *replName, const char *replGen) +{ + char *fileName; + fileName = slapi_ch_malloc (strlen (replName) + strlen (replGen) + + strlen (DB_EXTENSION) + 3/* '_' + '.' + '\0' */); + sprintf (fileName, "%s%s%s.%s", replName, FILE_SEP, replGen, DB_EXTENSION); + + return fileName; +} + +/* open file that corresponds to a particular database */ +static int _cl5DBOpenFile (Object *replica, Object **obj, PRBool checkDups) +{ + int rc; + const char *replName; + char *replGen; + Replica *r; + + PR_ASSERT (replica); + + r = (Replica*)object_get_data (replica); + replName = replica_get_name (r); + PR_ASSERT (replName); + replGen = replica_get_generation (r); + PR_ASSERT (replGen); + + rc = _cl5DBOpenFileByReplicaName (replName, replGen, obj, checkDups); + + slapi_ch_free ((void**)&replGen); + + return rc; +} + +static int _cl5DBOpenFileByReplicaName (const char *replName, const char *replGen, + Object **obj, PRBool checkDups) +{ + int rc = CL5_SUCCESS; + Object *tmpObj; + CL5DBFile *file; + char *file_name; + + PR_ASSERT (replName && replGen); + + if (checkDups) + { + PR_Lock (s_cl5Desc.fileLock); + file_name = _cl5MakeFileName (replName, replGen); + tmpObj = objset_find (s_cl5Desc.dbFiles, _cl5CompareDBFile, file_name); + slapi_ch_free((void **)&file_name); + file_name = NULL; + if (tmpObj) /* this file already exist */ + { + /* if we were asked for file handle - keep the handle */ + if (obj) + { + *obj = tmpObj; + } + else + { + object_release (tmpObj); + } + + rc = CL5_SUCCESS; + goto done; + } + } + + rc = _cl5NewDBFile (replName, replGen, &file); + if (rc == CL5_SUCCESS) + { + /* This creates the file but doesn't set the init flag + * The flag is set later when the purge and max ruvs are set. + * This is to prevent some thread to get file access before the + * structure is fully initialized */ + rc = _cl5AddDBFile (file, &tmpObj); + if (rc == CL5_SUCCESS) + { + /* read purge RUV - done here because it needs file object rather than file pointer */ + rc = _cl5ReadRUV (replGen, tmpObj, PR_TRUE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DBOpenFileByReplicaName: failed to get purge RUV\n"); + goto done; + } + + /* read ruv that represents the upper bound of the changes stored in the file */ + rc = _cl5ReadRUV (replGen, tmpObj, PR_FALSE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DBOpenFileByReplicaName: failed to get upper bound RUV\n"); + goto done; + } + + /* Mark the DB File initialize */ + _cl5DBFileInitialized(tmpObj); + + if (obj) + { + *obj = tmpObj; + } + else + { + object_release (tmpObj); + } + } + } + +done:; + if (rc != CL5_SUCCESS) + { + if (file) + _cl5DBCloseFile ((void**)&file); + } + + if (checkDups) + { + PR_Unlock (s_cl5Desc.fileLock); + } + + return rc; +} + +/* adds file to the db file list */ +static int _cl5AddDBFile (CL5DBFile *file, Object **obj) +{ + int rc; + Object *tmpObj; + + PR_ASSERT (file); + + tmpObj = object_new (file, _cl5DBCloseFile); + rc = objset_add_obj(s_cl5Desc.dbFiles, tmpObj); + if (rc != OBJSET_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5AddDBFile: failed to add db file to the list; " + "repl_objset error - %d\n", rc); + object_release (tmpObj); + return CL5_OBJSET_ERROR; + } + + if (obj) + { + *obj = tmpObj; + } + else + object_release (tmpObj); + + return CL5_SUCCESS; +} + +static int _cl5NewDBFile (const char *replName, const char *replGen, CL5DBFile** dbFile) +{ + int rc; + DB *db = NULL; + char *name; + char *semadir; +#ifdef HPUX + char cwd [PATH_MAX+1]; +#endif + + PR_ASSERT (replName && replGen && dbFile); + + (*dbFile) = (CL5DBFile *)slapi_ch_calloc (1, sizeof (CL5DBFile)); + if (*dbFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5NewDBFile: memory allocation failed\n"); + return CL5_MEMORY_ERROR; + } + + name = _cl5MakeFileName (replName, replGen); + { + /* The subname argument allows applications to have + * subdatabases, i.e., multiple databases inside of a single + * physical file. This is useful when the logical databases + * are both numerous and reasonably small, in order to + * avoid creating a large number of underlying files. + */ + char *subname = NULL; + DB_ENV *dbEnv = s_cl5Desc.dbEnv; + + rc = db_create(&db, dbEnv, 0); + if (0 != rc) { + goto out; + } + + rc = db->set_pagesize( + db, + s_cl5Desc.dbConfig.pageSize); + + if (0 != rc) { + goto out; + } + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 3300 + rc = db->set_malloc(db, malloc); + if (0 != rc) { + goto out; + } +#endif + + DB_OPEN(s_cl5Desc.dbEnvOpenFlags, + db, NULL /* txnid */, name, subname, DB_BTREE, + DB_CREATE | DB_THREAD, s_cl5Desc.dbConfig.fileMode, rc); + } +out: + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5NewDBFile: db_open failed; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + (*dbFile)->db = db; + (*dbFile)->name = name; + (*dbFile)->replName = slapi_ch_strdup (replName); + (*dbFile)->replGen = slapi_ch_strdup (replGen); + + /* + * Considerations for setting up cl semaphore: + * (1) The NT version of SleepyCat uses test-and-set mutexes + * at the DB page level instead of blocking mutexes. That has + * proven to be a killer for the changelog DB, as this DB is + * accessed by multiple a reader threads (the repl thread) and + * writer threads (the server ops threads) usually at the last + * pages of the DB, due to the sequential nature of the changelog + * keys. To avoid the test-and-set mutexes, we could use semaphore + * to serialize the writers and avoid the high mutex contention + * that SleepyCat is unable to avoid. + * (2) [610948] Linux master hangs for 2 hours + * [611239] _cl5DeadlockMain: lock_detect succeeded + * (3) DS 6.2 introduced the semaphore on all platforms (replaced + * the serial lock used on Windows and Linux described above). + * The number of the concurrent writes now is configurable by + * nsslapd-changelogmaxconcurrentwrites (the server needs to + * be restarted). + */ + + semadir = s_cl5Desc.dbDir; +#ifdef HPUX + /* + * HP sem_open() does not allow pathname component "./" or "../" + * in the semaphore name. For simplicity and to avoid doing + * chdir() in multi-thread environment, current working dir + * (log dir) is used to replace the original semaphore dir + * if it contains "./". + */ + if ( strstr ( semadir, "./" ) != NULL && getcwd ( cwd, PATH_MAX+1 ) != NULL ) + { + semadir = cwd; + } +#endif + + if ( semadir != NULL ) + { + (*dbFile)->semaName = slapi_ch_malloc (strlen(semadir) + strlen(replName) + strlen(".sema") + 10); + sprintf ((*dbFile)->semaName, "%s/%s.sema", semadir, replName); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5NewDBFile: semaphore %s\n", (*dbFile)->semaName); + (*dbFile)->sema = PR_OpenSemaphore((*dbFile)->semaName, PR_SEM_CREATE, 0666, s_cl5Desc.dbConfig.maxConcurrentWrites ); + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5NewDBFile: maxConcurrentWrites=%d\n", s_cl5Desc.dbConfig.maxConcurrentWrites ); + } + + if ((*dbFile)->sema == NULL ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5NewDBFile: failed to create semaphore %s; NSPR error - %d\n", + (*dbFile)->semaName ? (*dbFile)->semaName : "(nil)", PR_GetError ()); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + /* compute number of entries in the file */ + /* ONREPL - to improve performance, we keep entry count in memory + and write it down during shutdown. Problem: this will not + work with multiple processes. Do we have to worry about that? + */ + if (s_cl5Desc.dbOpenMode == CL5_OPEN_NORMAL) + { + rc = _cl5GetEntryCount (*dbFile); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5NewDBFile: failed to get entry count\n"); + goto done; + } + } + +done: + if (rc != CL5_SUCCESS) + { + if (dbFile) + _cl5DBCloseFile ((void**)dbFile); + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&name); + + slapi_ch_free ((void**)dbFile); + } + + return rc; +} + +static void _cl5DBCloseFile (void **data) +{ + CL5DBFile *file; + char fullpathname[MAXPATHLEN]; + + PR_ASSERT (data); + + file = *(CL5DBFile**)data; + + /* close the file */ + /* if this is normal close or close after import, update entry count */ + if ((s_cl5Desc.dbOpenMode == CL5_OPEN_NORMAL && s_cl5Desc.dbState == CL5_STATE_CLOSING) || + s_cl5Desc.dbOpenMode == CL5_OPEN_LDIF2CL) + { + _cl5WriteEntryCount (file); + _cl5WriteRUV (file, PR_TRUE); + _cl5WriteRUV (file, PR_FALSE); + } + + /* close file */ + if (file->db) + file->db->close(file->db, 0); + + if (file->flags & DB_FILE_DELETED) + { + PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, file->name); + if (PR_Delete(fullpathname) != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBCloseFile: " + "failed to remove (%s) file; NSPR error - %d\n", file->name, PR_GetError ()); + + } + } + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&file->name); + slapi_ch_free ((void**)&file->replName); + slapi_ch_free ((void**)&file->replGen); + if (file->sema) { + PR_CloseSemaphore (file->sema); + PR_DeleteSemaphore (file->semaName); + file->sema = NULL; + } + slapi_ch_free ((void**)&file->semaName); + + slapi_ch_free (data); +} + +static int _cl5GetDBFile (Object *replica, Object **obj) +{ + char *fileName; + + PR_ASSERT (replica && obj); + + fileName = _cl5Replica2FileName (replica); + + *obj = objset_find(s_cl5Desc.dbFiles, _cl5CompareDBFile, fileName); + slapi_ch_free ((void**)&fileName); + if (*obj) + { + return CL5_SUCCESS; + } + else + { + return CL5_NOTFOUND; + } +} + +static int _cl5GetDBFileByReplicaName (const char *replName, const char *replGen, + Object **obj) +{ + char *fileName; + + PR_ASSERT (replName && replGen && obj); + + fileName = _cl5MakeFileName (replName, replGen); + + *obj = objset_find(s_cl5Desc.dbFiles, _cl5CompareDBFile, fileName); + slapi_ch_free ((void**)&fileName); + if (*obj) + { + return CL5_SUCCESS; + } + else + { + return CL5_NOTFOUND; + } +} + +static void _cl5DBDeleteFile (Object *obj) +{ + CL5DBFile *file; + + PR_ASSERT (obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + file->flags |= DB_FILE_DELETED; + objset_remove_obj(s_cl5Desc.dbFiles, obj); + object_release (obj); +} + +static void _cl5DBFileInitialized (Object *obj) +{ + CL5DBFile *file; + + PR_ASSERT (obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + file->flags |= DB_FILE_INIT; +} + +static int _cl5CompareDBFile (Object *el1, const void *el2) +{ + CL5DBFile *file; + const char *name; + + PR_ASSERT (el1 && el2); + + file = (CL5DBFile*) object_get_data (el1); + name = (const char*) el2; + return ((file->flags & DB_FILE_INIT) ? strcmp (file->name, name) : 1); +} + +static int _cl5CopyDBFiles (const char *srcDir, const char *destDir, Object **replicas) +{ + char srcFile [MAXPATHLEN + 1]; + char destFile[MAXPATHLEN + 1]; + int rc; + Object *obj; + CL5DBFile *file; + + /* ONREPL currently, dbidlist is ignored because db code can't handle discrepancy between + transaction log and present files; this should change before 5.0 ships */ + obj = objset_first_obj (s_cl5Desc.dbFiles); + while (obj) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", srcDir, file->name); + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", destDir, file->name); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + object_release (obj); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CopyDBFiles: failed to copy %s from %s to %s\n", + file, srcDir, destDir); + return CL5_SYSTEM_ERROR; + } + + obj = objset_next_obj (s_cl5Desc.dbFiles, obj); + } + + return CL5_SUCCESS; +} + +/* + * return 1: true (the "filename" ends with "ext") + * return 0: false + */ +static int _cl5FileEndsWith(const char *filename, const char *ext) +{ + char *p = NULL; + int flen = strlen(filename); + int elen = strlen(ext); + if (0 == flen || 0 == elen) + { + return 0; + } + p = strstr(filename, ext); + if (NULL == p) + { + return 0; + } + if (p - filename + elen == flen) + { + return 1; + } + return 0; +} + +static int _cl5ExportFile (PRFileDesc *prFile, Object *obj) +{ + int rc; + void *iterator = NULL; + slapi_operation_parameters op = {0}; + char *buff; + PRInt32 len, wlen; + CL5Entry entry; + CL5DBFile *file; + + PR_ASSERT (prFile && obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + ruv_dump (file->purgeRUV, "clpurgeruv", prFile); + ruv_dump (file->maxRUV, "clmaxruv", prFile); + slapi_write_buffer (prFile, "\n", strlen("\n")); + + entry.op = &op; + rc = _cl5GetFirstEntry (obj, &entry, &iterator, NULL); + while (rc == CL5_SUCCESS) + { + rc = _cl5Operation2LDIF (&op, file->replGen, &buff, &len); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5ExportLDIF: failed to convert operation to ldif\n"); + operation_parameters_done (&op); + break; + } + + wlen = slapi_write_buffer (prFile, buff, len); + slapi_ch_free((void **)&buff); + if (wlen < len) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5ExportLDIF: failed to write to ldif file\n"); + rc = CL5_SYSTEM_ERROR; + operation_parameters_done (&op); + break; + } + + cl5_operation_parameters_done (&op); + + rc = _cl5GetNextEntry (&entry, iterator); + } + + cl5_operation_parameters_done (&op); + + if (iterator) + cl5DestroyIterator (iterator); + + if (rc != CL5_NOTFOUND) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5ExportLDIF: failed to retrieve changelog entry\n"); + } + else + { + rc = CL5_SUCCESS; + } + + return rc; +} + +static PRBool _cl5ReplicaInList (Object *replica, Object **replicas) +{ + int i; + + PR_ASSERT (replica && replicas); + + /* ONREPL I think it should be sufficient to just compare replica pointers */ + for (i=0; replicas[i]; i++) + { + if (replica == replicas[i]) + return PR_TRUE; + } + + return PR_FALSE; +} + +static char* _cl5GetHelperEntryKey (int type, char *csnStr) +{ + CSN *csn= csn_new(); + char *rt; + + csn_set_time(csn, type); + csn_set_replicaid(csn, 0); + + rt = csn_as_string(csn, PR_FALSE, csnStr); + csn_free(&csn); + + return rt; +} + +static Object* _cl5GetReplica (const slapi_operation_parameters *op, const char* replGen) +{ + Slapi_DN *sdn; + Object *replObj; + Replica *replica; + char *newGen; + + PR_ASSERT (op && replGen); + + sdn = slapi_sdn_new_dn_byref(op->target_address.dn); + + replObj = replica_get_replica_from_dn (sdn); + if (replObj) + { + /* check to see if replica generation has not change */ + replica = (Replica*)object_get_data (replObj); + PR_ASSERT (replica); + newGen = replica_get_generation (replica); + PR_ASSERT (newGen); + if (strcmp (replGen, newGen) != 0) + { + object_release (replObj); + replObj = NULL; + } + + slapi_ch_free ((void**)&replGen); + } + + slapi_sdn_free (&sdn); + + return replObj; +} + +int +cl5_is_diskfull() +{ + int rc; + PR_Lock(cl5_diskfull_lock); + rc = cl5_diskfull_flag; + PR_Unlock(cl5_diskfull_lock); + return rc; +} + +static void +cl5_set_diskfull() +{ + PR_Lock(cl5_diskfull_lock); + cl5_diskfull_flag = 1; + PR_Unlock(cl5_diskfull_lock); +} + +static void +cl5_set_no_diskfull() +{ + PR_Lock(cl5_diskfull_lock); + cl5_diskfull_flag = 0; + PR_Unlock(cl5_diskfull_lock); +} + +int +cl5_diskspace_is_available() +{ + int rval = 1; + +#if defined( OS_solaris ) || defined( hpux ) + struct statvfs fsbuf; + if (statvfs(s_cl5Desc.dbDir, &fsbuf) < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: Cannot get file system info\n"); + rval = 0; + } + else + { + unsigned long fsiz = fsbuf.f_bavail * fsbuf.f_frsize; + if (fsiz < NO_DISK_SPACE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: No enough diskspace for changelog: (%u bytes free)\n", fsiz); + rval = 0; + } + else if (fsiz > MIN_DISK_SPACE) + { + /* assume recovered */ + cl5_set_no_diskfull(); + } + } +#endif +#if defined( linux ) + struct statfs fsbuf; + if (statfs(s_cl5Desc.dbDir, &fsbuf) < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: Cannot get file system info\n"); + rval = 0; + } + else + { + unsigned long fsiz = fsbuf.f_bavail * fsbuf.f_bsize; + if (fsiz < NO_DISK_SPACE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: No enough diskspace for changelog: (%u bytes free)\n", fsiz); + rval = 0; + } + else if (fsiz > MIN_DISK_SPACE) + { + /* assume recovered */ + cl5_set_no_diskfull(); + } + } +#endif + return rval; +} diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h new file mode 100644 index 00000000..49296df2 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_api.h @@ -0,0 +1,478 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_api.h - interface to 5.0 changelog */ + +#ifndef CL5_API_H +#define CL5_API_H + +#include "repl5.h" +#include "repl5_prot_private.h" + +#define CL5_TYPE "Changelog5" /* changelog type */ +#define VERSION_SIZE 127 /* size of the buffer to hold changelog version */ +#define CL5_DEFAULT_CONFIG -1 /* value that indicates to changelog to use default */ +#define CL5_STR_IGNORE "-1" /* tels function to ignore this parameter */ +#define CL5_NUM_IGNORE -1 /* tels function to ignore this parameter */ +#define CL5_STR_UNLIMITED "0" /* represent unlimited value (trimming ) */ +#define CL5_NUM_UNLIMITED 0 /* represent unlimited value (trimming ) */ + +#define CL5_OS_ERR_IS_DISKFULL(err) ((err)==ENOSPC || (err)==EFBIG) + +/***** Data Structures *****/ + +/* changelog configuration structure */ +typedef struct cl5dbconfig +{ + size_t cacheSize; /* cache size in bytes */ + PRBool durableTrans; /* flag that tells not to sync log when trans commits */ + PRInt32 checkpointInterval; /* checkpoint interval in seconds */ + PRBool circularLogging; /* flag to archive and trancate log */ + size_t pageSize; /* page size in bytes */ + size_t logfileSize; /* maximum log size in bytes */ + size_t maxTxnSize; /* maximum txn table size in count*/ + PRInt32 fileMode; /* file mode */ + PRBool verbose; /* Get libdb to exhale debugging info */ + PRBool debug; /* Will libdb emit debugging info into our log ? */ + PRInt32 tricklePercentage; /* guaranteed percentage of clean cache pages; 0 - 100 */ + PRInt32 spinCount; /* DB Mutex spin count */ + PRUint32 nb_lock_config; /* Number of locks in the DB lock table. New in 5.1 */ +/* The next 2 parameters are needed for configuring the changelog cache. New in 5.1 */ + PRUint32 maxChCacheEntries; + PRUint32 maxChCacheSize; + PRUint32 maxConcurrentWrites; /* 6.2 max number of concurrent cl writes */ +} CL5DBConfig; + +/* changelog entry format */ +typedef struct cl5entry +{ + slapi_operation_parameters *op; /* operation applied to the server */ + time_t time; /* time added to the cl; used for trimming */ +} CL5Entry; + +/* default values for the changelog configuration structure above */ +/* + * For historical reasons, dbcachesize refers to number of bytes at the DB level, + * whereas cachesize refers to number of entries at the changelog cache level (cachememsize is the + * one refering to number of bytes at the changelog cache level) + */ +#define CL5_DEFAULT_CONFIG_DB_DBCACHESIZE 10485760 /* 10M bytes */ +#define CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS 1 +#define CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL 60 +#define CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING 1 +#define CL5_DEFAULT_CONFIG_DB_PAGE_SIZE 8*1024 +#define CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE 0 +#define CL5_DEFAULT_CONFIG_DB_VERBOSE 0 +#define CL5_DEFAULT_CONFIG_DB_DEBUG 0 +#define CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE 40 +#define CL5_DEFAULT_CONFIG_DB_SPINCOUNT 0 +#define CL5_DEFAULT_CONFIG_DB_TXN_MAX 200 +#define CL5_DEFAULT_CONFIG_CACHESIZE 3000 /* number of entries */ +#define CL5_DEFAULT_CONFIG_CACHEMEMSIZE 1048576 /* 1 M bytes */ +#define CL5_DEFAULT_CONFIG_NB_LOCK 1000 /* Number of locks in the lock table of the DB */ + +/* + * Small number of concurrent writes degradate the throughput. + * Large one increases deadlock. + */ +#ifdef SOLARIS +#define CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES 10 +#else +#define CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES 2 +#endif + + +#define CL5_MIN_DB_DBCACHESIZE 524288 /* min 500K bytes */ +#define CL5_MIN_CACHESIZE 500 /* min number of entries */ +#define CL5_MIN_CACHEMEMSIZE 262144 /* min 250K bytes */ +#define CL5_MIN_NB_LOCK 1000 /* The minimal number of locks in the DB (Same as default) */ + +/* data structure that allows iteration through changelog */ +typedef struct cl5replayiterator CL5ReplayIterator; + +/* changelog state */ +typedef enum +{ + CL5_STATE_NONE, /* changelog has not been initialized */ + CL5_STATE_CLOSING, /* changelog is about to close; all threads must exit */ + CL5_STATE_CLOSED, /* changelog has been initialized, but not opened, or open and then closed */ + CL5_STATE_OPEN /* changelog is opened */ +} CL5State; + +/* error codes */ +enum +{ + CL5_SUCCESS, /* successful operation */ + CL5_BAD_DATA, /* invalid parameter passed to the function */ + CL5_BAD_FORMAT, /* db data has unexpected format */ + CL5_BAD_STATE, /* changelog is in an incorrect state for attempted operation */ + CL5_BAD_DBVERSION, /* changelog has invalid dbversion */ + CL5_DB_ERROR, /* database error */ + CL5_NOTFOUND, /* requested entry or value was not found */ + CL5_MEMORY_ERROR, /* memory allocation failed */ + CL5_SYSTEM_ERROR, /* NSPR error occured, use PR_Error for furhter info */ + CL5_CSN_ERROR, /* CSN API failed */ + CL5_RUV_ERROR, /* RUV API failed */ + CL5_OBJSET_ERROR, /* namedobjset api failed */ + CL5_PURGED_DATA, /* requested data has been purged */ + CL5_MISSING_DATA, /* data should be in the changelog, but is missing */ + CL5_UNKNOWN_ERROR /* unclassified error */ +}; + +/***** Module APIs *****/ + +/* Name: cl5Init + Description: initializes changelog module; must be called by a single thread + before any function of the module. + Parameters: none + Return: CL5_SUCCESS if function is successful; + CL5_BAD_DATA if invalid directory is passed; + CL5_SYSTEM error if NSPR call fails. + */ +int cl5Init (); + +/* Name: cl5Cleanup + Description: performs cleanup of the changelog module. Must be called by a single + thread. It will closed db if it is still open. + Parameters: none + Return: none + */ +void cl5Cleanup (); + +/* Name: cl5Open + Description: opens changelog ; must be called after changelog is + initialized using cl5Init. It is thread safe and the second + call is ignored. + Parameters: dir - changelog dir + config - db configuration parameters; currently not used + openMode - open mode + Return: CL5_SUCCESS if successfull; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_DBVERSION if dbversion file is missing or has unexpected data + CL5_SYSTEM_ERROR if NSPR error occured (during db directory creation); + CL5_MEMORY_ERROR if memory allocation fails; + CL5_DB_ERROR if db initialization or open fails. + */ +int cl5Open (const char *dir, const CL5DBConfig *config); + +/* Name: cl5Close + Description: closes changelog and cleanups changelog module; waits until + all threads are done using changelog + Parameters: none + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if db is not in the open state; + CL5_SYSTEM_ERROR if NSPR call fails + */ +int cl5Close (); + +/* Name: cl5Delete + Description: removes changelog + Parameters: dir - changelog directory + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not in closed state; + CL5_BAD_DATA if invalid directory supplied + CL5_SYSTEM_ERROR if NSPR call fails + */ +int cl5Delete (const char *dir); + +/* Name: cl5OpenDB + Description: opens changelog file for specified file + Parameters: replica - replica whose file we wish to open + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + */ +int cl5OpenDB (Object *replica); + +/* Name: cl5CloseDB + Description: closes changelog file for the specified replica + Parameters: replica - replica whose file we wish to close + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5CloseDB (Object *replica); + +/* Name: cl5DeleteDB + Description: asynchronously removes changelog file for the specified replica. + The file is physically removed when it is no longer in use. + This function is called when a backend is removed or reloaded. + Parameters: replica - replica whose file we wish to delete + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5DeleteDB (Object *replica); + +/* Name: cl5DeleteDBSync + Description: The same as cl5DeleteDB except the function does not return + until the file is removed. +*/ +int cl5DeleteDBSync (Object *replica); + +/* Name: cl5GetUpperBoundRUV + Description: retrieves vector that represent the upper bound of changes + stored in the changelog for the replica. + Parameters: r - replica for which the vector is requested + ruv - contains a copy of the upper bound ruv if function is successful; + unchanged otherwise. It is responsobility pf the caller to free + the ruv when it is no longer is in use + Return: CL5_SUCCESS if function is successfull + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND, if changelog file for replica is not found + */ +int cl5GetUpperBoundRUV (Replica *r, RUV **ruv); + +/* Name: cl5Backup + Description: makes a backup of the changelog including *.db2, + log files, and dbversion. Can be called with the changelog in either open or + closed state. + Parameters: bkDir - directory to which the data is backed up; + created if it does not exist + replicas - optional list of replicas whose changes should be backed up; + if the list is NULL, entire changelog is backed up. + Return: CL5_SUCCESS if function is successful; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_STATE if changelog has not been initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call or file copy failes. + */ +int cl5Backup (const char *bkDir, Object **replicas); + +/* Name: cl5Restore + Description: restores changelog from the backed up copy. Changelog must be ibnitalized and closed. + Parameters: clDir - changelog dir + bkDir - directory that contains the backup + replicas - optional list of replicas whose changes should be recovered; + if the list is NULL, entire changelog is recovered. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call of file copy fails + */ +int cl5Restore (const char *clDir, const char *bkDir, Object **replicas); + +/* Name: cl5ExportLDIF + Description: dumps changelog to an LDIF file; changelog can be open or closed. + Parameters: clDir - changelog dir + ldifFile - full path to ldif file to write + replicas - optional list of replicas whose changes should be exported; + if the list is NULL, entire changelog is exported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is not initialized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ExportLDIF (const char *ldifFile, Object **replicas); + +/* Name: cl5ImportLDIF + Description: imports ldif file into changelog; changelog must be in the closed state + Parameters: clDir - changelog dir + ldifFile - absolute path to the ldif file to import + replicas - optional list of replicas whose data should be imported; + if the list is NULL, all data in the file is imported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not inititalized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ImportLDIF (const char *clDir, const char *ldifFile, Object **replicas); + +/* Name: cl5GetState + Description: returns database state + Parameters: none + Return: changelog state + */ + +int cl5GetState (); + +/* Name: cl5ConfigTrimming + Description: sets changelog trimming parameters + Parameters: maxEntries - maximum number of entries in the log; + maxAge - maximum entry age; + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if changelog has not been open + */ +int cl5ConfigTrimming (int maxEntries, const char *maxAge); + +/* Name: cl5GetOperation + Description: retireves operation specified by its csn and databaseid + Parameters: op - must contain csn and databaseid; the rest of data is + filled if function is successfull + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_NOTFOUND if entry was not found; + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if db data format does not match entry format. + */ +int cl5GetOperation (Object *replica, slapi_operation_parameters *op); + +/* Name: cl5GetFirstOperation + Description: retrieves first operation for a particular database + replica - replica for which the operation should be retrieved. + Parameters: op - buffer to store the operation; + iterator - to be passed to the call to cl5GetNextOperation + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if operation is NULL + CL5_BAD_STATE, if changelog is not open + CL5_DB_ERROR, if db call fails + */ +int cl5GetFirstOperation (Object *replica, slapi_operation_parameters *op, void **iterator); + +/* Name: cl5GetNextOperation + Description: retrieves the next op from the changelog as defined by the iterator + Parameters: replica - replica for which the operation should be retrieved. + op - returned operation, if function is successful + iterator - in: identifies op to retrieve; out: identifies next op + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if invalid parameter is supplied + CL5_BAD_STATE, if changelog is not open + CL5_NOTFOUND, empty changelog + CL5_DB_ERROR, if db call fails + */ +int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator); + +/* Name: cl5DestroyIterator + Description: destroys iterator once iteration through changelog is done + Parameters: iterator - iterator to destroy + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if invalid parameters is supplied + CL5_BAD_STATE, if changelog is not open + CL5_DB_ERROR, if db call fails + */ +void cl5DestroyIterator (void *iterator); + +/* Name: cl5WriteOperation + Description: writes operation to changelog + Parameters: repl_name - name of the replica to which operation applies + repl_gen - replica generation for the operation + !!!Note that we pass name and generation rather than + replica object since generation can change while operation + is in progress (if the data is reloaded). !!! + op - operation to write + local - this is a non-replicated operation + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_MEMORY_ERROR if memory allocation failed; + CL5_DB_ERROR if any other db error occured; + */ +int cl5WriteOperation(const char *repl_name, const char *repl_gen, + const slapi_operation_parameters *op, PRBool local); + +/* Name: cl5CreateReplayIterator + Description: creates an iterator that allows to retireve changes that should + to be sent to the consumer identified by ruv The iteration is peformed by + repeated calls to cl5GetNextOperationToReplay. + Parameters: replica - replica whose data we wish to iterate; + ruv - consumer ruv; + iterator - iterator to be passed to cl5GetNextOperationToReplay call + Return: CL5_SUCCESS, if function is successfull; + CL5_MISSING_DATA, if data that should be in the changelog is missing + CL5_PURGED_DATA, if some data that consumer needs has been purged. + Note that the iterator can be non null if the supplier contains + some data that needs to be sent to the consumer + CL5_NOTFOUND if the consumer is up to data with respect to the supplier + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if db has not been open; + CL5_DB_ERROR if any other db error occured; + CL5_MEMORY_ERROR if memory allocation fails. + */ +int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *ruv, + CL5ReplayIterator **iterator); + +/* Name: cl5GetNextOperationToReplay + Description: retrieves next operation to be sent to the consumer and + that was created on a particular master. Consumer and master info + is encoded in the iterator parameter that must be created by calling + to cl5CreateIterator. + Parameters: iterator - iterator that identifies next entry to retrieve; + op - operation retireved if function is successful + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_NOTFOUND if end of iteration list is reached + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if data in db is of unrecognized format; + CL5_MEMORY_ERROR if memory allocation fails. + */ +int cl5GetNextOperationToReplay (CL5ReplayIterator *iterator, + CL5Entry *entry); + +/* Name: cl5DestroyReplayIterator + Description: destorys iterator + Parameters: iterator - iterator to destory + Return: none + */ +void cl5DestroyReplayIterator (CL5ReplayIterator **iterator); + +/* Name: cl5DeleteOnClose + Description: marks changelog for deletion when it is closed + Parameters: flag; if flag = 1 then delete else don't + Return: none + */ + +void cl5DeleteOnClose (PRBool rm); + +/* Name: cl5GetDir + Description: returns changelog directory; must be freed by the caller; + Parameters: none + Return: copy of the directory; caller needs to free the string + */ + +char *cl5GetDir (); + +/* Name: cl5Exist + Description: checks if a changelog exists in the specified directory + Parameters: clDir - directory to check; + Return: 1 - if changelog exists; 0 - otherwise + */ + +PRBool cl5Exist (const char *clDir); + +/* Name: cl5GetOperationCount + Description: returns number of entries in the changelog. The changelog must be + open for the value to be meaningful. + Parameters: replica - optional parameter that specifies the replica whose operations + we wish to count; if NULL all changelog entries are counted + Return: number of entries in the changelog + */ + +int cl5GetOperationCount (Object *replica); + +/* Name: cl5_operation_parameters_done + Description: frees all parameters that are not freed by operation_parameters_done + function in the server. + + */ + +void cl5_operation_parameters_done (struct slapi_operation_parameters *sop); + +/* Name: cl5CreateDirIfNeeded + Description: Create the directory if it doesn't exist yet + Parameters: dir - Contains the name of the directory to create. Must not be NULL + Return: CL5_SUCCESS if succeeded or existed, + CL5_SYSTEM_ERROR if failed. +*/ + +int cl5CreateDirIfNeeded (const char *dir); +int cl5DBData2Entry (const char *data, PRUint32 len, CL5Entry *entry); + +PRBool cl5HelperEntry (const char *csnstr, CSN *csn); +CSN** cl5BuildCSNList (const RUV *consRuv, const RUV *supRuv); +void cl5DestroyCSNList (CSN*** csns); + +int cl5_is_diskfull(); +int cl5_diskspace_is_available(); + +#endif diff --git a/ldap/servers/plugins/replication/cl5_clcache.c b/ldap/servers/plugins/replication/cl5_clcache.c new file mode 100644 index 00000000..585a7266 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_clcache.c @@ -0,0 +1,910 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2003 Netscape Communications Corporation + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "errno.h" /* ENOMEM, EVAL used by Berkeley DB */ +#include "db.h" /* Berkeley DB */ +#include "cl5.h" /* changelog5Config */ +#include "cl5_clcache.h" + +/* + * Constants for the buffer pool: + * + * DEFAULT_CLC_BUFFER_PAGE_COUNT + * Little performance boost if it is too small. + * + * DEFAULT_CLC_BUFFER_PAGE_SIZE + * Its value is determined based on the DB requirement that + * the buffer size should be the multiple of 1024. + */ +#define DEFAULT_CLC_BUFFER_COUNT_MIN 10 +#define DEFAULT_CLC_BUFFER_COUNT_MAX 0 +#define DEFAULT_CLC_BUFFER_PAGE_COUNT 32 +#define DEFAULT_CLC_BUFFER_PAGE_SIZE 1024 + +static enum { + CLC_STATE_READY = 0, /* ready to iterate */ + CLC_STATE_UP_TO_DATE, /* remote RUV already covers the CSN */ + CLC_STATE_CSN_GT_RUV, /* local RUV doesn't conver the CSN */ + CLC_STATE_NEW_RID, /* unknown RID to local RUVs */ + CLC_STATE_UNSAFE_RUV_CHANGE,/* (RUV1 < maxcsn-in-buffer) && (RUV1 < RUV1') */ + CLC_STATE_DONE, /* no more change */ + CLC_STATE_ABORTING /* abort replication session */ +}; + +typedef struct clc_busy_list CLC_Busy_List; + +struct csn_seq_ctrl_block { + ReplicaId rid; /* RID this block serves */ + CSN *consumer_maxcsn; /* Don't send CSN <= this */ + CSN *local_maxcsn; /* Don't send CSN > this */ + CSN *prev_local_maxcsn; /* */ + int state; /* CLC_STATE_* */ +}; + +/* + * Each cl5replayiterator acquires a buffer from the buffer pool + * at the beginning of a replication session, and returns it back + * at the end. + */ +struct clc_buffer { + char *buf_agmt_name; /* agreement acquired this buffer */ + ReplicaId buf_consumer_rid; /* help checking threshold csn */ + const RUV *buf_consumer_ruv; /* used to skip change */ + const RUV *buf_local_ruv; /* used to refresh local_maxcsn */ + + /* + * fields for retriving data from DB + */ + int buf_state; + CSN *buf_current_csn; + int buf_load_flag; /* db flag DB_MULTIPLE_KEY, DB_SET, DB_NEXT */ + DBC *buf_cursor; + DBT buf_key; /* current csn string */ + DBT buf_data; /* data retrived from db */ + void *buf_record_ptr; /* ptr to the current record in data */ + CSN *buf_missing_csn; /* used to detect persistent missing of CSN */ + + /* fields for control the CSN sequence sent to the consumer */ + struct csn_seq_ctrl_block *buf_cscbs [MAX_NUM_OF_MASTERS]; + int buf_num_cscbs; /* number of csn sequence ctrl blocks */ + + /* fields for debugging stat */ + int buf_load_cnt; /* number of loads for session */ + int buf_record_cnt; /* number of changes for session */ + int buf_record_skipped; /* number of changes skipped */ + + /* + * fields that should be accessed via bl_lock or pl_lock + */ + CLC_Buffer *buf_next; /* next buffer in the same list */ + CLC_Busy_List *buf_busy_list; /* which busy list I'm in */ +}; + +/* + * Each changelog has a busy buffer list + */ +struct clc_busy_list { + PRLock *bl_lock; + DB *bl_db; /* changelog db handle */ + CLC_Buffer *bl_buffers; /* busy buffers of this list */ + CLC_Busy_List *bl_next; /* next busy list in the pool */ +}; + +/* + * Each process has a buffer pool + */ +struct clc_pool { + PRRWLock *pl_lock; /* cl writer and agreements */ + DB_ENV **pl_dbenv; /* pointer to DB_ENV for all the changelog files */ + CLC_Busy_List *pl_busy_lists; /* busy buffer lists, one list per changelog file */ + int pl_buffer_cnt_now; /* total number of buffers */ + int pl_buffer_cnt_min; /* free a newly returned buffer if _now > _min */ + int pl_buffer_cnt_max; /* no use */ + int pl_buffer_default_pages; /* num of pages in a new buffer */ +}; + +/* static variables */ +static struct clc_pool *_pool = NULL; /* process's buffer pool */ + +/* static prototypes */ +static int clcache_adjust_anchorcsn ( CLC_Buffer *buf ); +static void clcache_refresh_consumer_maxcsns ( CLC_Buffer *buf ); +static int clcache_refresh_local_maxcsns ( CLC_Buffer *buf ); +static int clcache_skip_change ( CLC_Buffer *buf ); +static int clcache_load_buffer_bulk ( CLC_Buffer *buf, int flag ); +static int clcache_open_cursor ( DB_TXN *txn, CLC_Buffer *buf, DBC **cursor ); +static int clcache_cursor_get ( DBC *cursor, CLC_Buffer *buf, int flag ); +static struct csn_seq_ctrl_block *clcache_new_cscb (); +static void clcache_free_cscb ( struct csn_seq_ctrl_block ** cscb ); +static CLC_Buffer *clcache_new_buffer ( ReplicaId consumer_rid ); +static void clcache_delete_buffer ( CLC_Buffer **buf ); +static CLC_Busy_List *clcache_new_busy_list (); +static void clcache_delete_busy_list ( CLC_Busy_List **bl ); +static int clcache_enqueue_busy_list( DB *db, CLC_Buffer *buf ); +static void csn_dup_or_init_by_csn ( CSN **csn1, CSN *csn2 ); + +/* + * Initiates the process buffer pool. This should be done + * once and only once when process starts. + */ +int +clcache_init ( DB_ENV **dbenv ) +{ + _pool = (struct clc_pool*) slapi_ch_calloc ( 1, sizeof ( struct clc_pool )); + _pool->pl_dbenv = dbenv; + _pool->pl_buffer_cnt_min = DEFAULT_CLC_BUFFER_COUNT_MIN; + _pool->pl_buffer_cnt_max = DEFAULT_CLC_BUFFER_COUNT_MAX; + _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_COUNT_MAX; + _pool->pl_lock = PR_NewRWLock (PR_RWLOCK_RANK_NONE, "clcache_pl_lock"); + return 0; +} + +/* + * This is part of a callback function when changelog configuration + * is read or updated. + */ +void +clcache_set_config ( CL5DBConfig *config ) +{ + if ( config == NULL ) return; + + PR_RWLock_Wlock ( _pool->pl_lock ); + + _pool->pl_buffer_cnt_max = config->maxChCacheEntries; + + /* + * According to http://www.sleepycat.com/docs/api_c/dbc_get.html, + * data buffer should be a multiple of 1024 bytes in size + * for DB_MULTIPLE_KEY operation. + */ + _pool->pl_buffer_default_pages = config->maxChCacheSize / DEFAULT_CLC_BUFFER_PAGE_SIZE + 1; + _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_PAGE_COUNT; + if ( _pool->pl_buffer_default_pages <= 0 ) { + _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_PAGE_COUNT; + } + + PR_RWLock_Unlock ( _pool->pl_lock ); +} + +/* + * Gets the pointer to a thread dedicated buffer, or allocates + * a new buffer if there is no buffer allocated yet for this thread. + * + * This is called when a cl5replayiterator is created for + * a replication session. + */ +int +clcache_get_buffer ( CLC_Buffer **buf, DB *db, ReplicaId consumer_rid, const RUV *consumer_ruv, const RUV *local_ruv ) +{ + int rc = 0; + + if ( buf == NULL ) return CL5_BAD_DATA; + + *buf = NULL; + + if ( NULL != ( *buf = (CLC_Buffer*) get_thread_private_cache()) ) { + (*buf)->buf_state = CLC_STATE_READY; + (*buf)->buf_load_cnt = 0; + (*buf)->buf_record_cnt = 0; + (*buf)->buf_record_skipped = 0; + (*buf)->buf_cursor = NULL; + (*buf)->buf_num_cscbs = 0; + } + else { + *buf = clcache_new_buffer ( consumer_rid ); + if ( *buf ) { + if ( 0 == clcache_enqueue_busy_list ( db, *buf ) ) { + set_thread_private_cache ( (void*) (*buf) ); + } + else { + clcache_delete_buffer ( buf ); + } + } + } + + if ( NULL != *buf ) { + (*buf)->buf_consumer_ruv = consumer_ruv; + (*buf)->buf_local_ruv = local_ruv; + } + else { + slapi_log_error ( SLAPI_LOG_FATAL, get_thread_private_agmtname(), + "clcache_get_buffer: can't allocate new buffer\n" ); + rc = ENOMEM; + } + + return rc; +} + +/* + * Returns a buffer back to the buffer pool. + */ +void +clcache_return_buffer ( CLC_Buffer **buf ) +{ + int i; + + slapi_log_error ( SLAPI_LOG_REPL, (*buf)->buf_agmt_name, + "session end: state=%d load=%d sent=%d skipped=%d\n", + (*buf)->buf_state, + (*buf)->buf_load_cnt, + (*buf)->buf_record_cnt - (*buf)->buf_record_skipped, + (*buf)->buf_record_skipped ); + + for ( i = 0; i < (*buf)->buf_num_cscbs; i++ ) { + clcache_free_cscb ( &(*buf)->buf_cscbs[i] ); + } + (*buf)->buf_num_cscbs = 0; + + if ( (*buf)->buf_cursor ) { + + (*buf)->buf_cursor->c_close ( (*buf)->buf_cursor ); + (*buf)->buf_cursor = NULL; + } +} + +/* + * Loads a buffer from DB. + * + * anchorcsn - passed in for the first load of a replication session; + * flag - DB_SET to load in the key CSN record. + * DB_NEXT to load in the records greater than key CSN. + * return - DB error code instead of cl5 one because of the + * historic reason. + */ +int +clcache_load_buffer ( CLC_Buffer *buf, CSN *anchorcsn, int flag ) +{ + int rc = 0; + + clcache_refresh_local_maxcsns ( buf ); + + /* Set the loading key */ + if ( anchorcsn ) { + clcache_refresh_consumer_maxcsns ( buf ); + buf->buf_load_flag = DB_MULTIPLE_KEY; + csn_as_string ( anchorcsn, 0, (char*)buf->buf_key.data ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "session start: anchorcsn=%s\n", (char*)buf->buf_key.data ); + } + else if ( csn_get_time(buf->buf_current_csn) == 0 ) { + /* time == 0 means this csn has never been set */ + rc = DB_NOTFOUND; + } + else if ( clcache_adjust_anchorcsn ( buf ) != 0 ) { + rc = DB_NOTFOUND; + } + else { + csn_as_string ( buf->buf_current_csn, 0, (char*)buf->buf_key.data ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "load next: anchorcsn=%s\n", (char*)buf->buf_key.data ); + } + + if ( rc == 0 ) { + + buf->buf_state = CLC_STATE_READY; + rc = clcache_load_buffer_bulk ( buf, flag ); + + /* Reset some flag variables */ + if ( rc == 0 ) { + int i; + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + buf->buf_cscbs[i]->state = CLC_STATE_READY; + } + } + else if ( anchorcsn ) { + /* Report error only when the missing is persistent */ + if ( buf->buf_missing_csn && csn_compare (buf->buf_missing_csn, anchorcsn) == 0 ) { + slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name, + "Can't locate CSN %s in the changelog (DB rc=%d). The consumer may need to be reinitialized.\n", + (char*)buf->buf_key.data, rc ); + } + else { + csn_dup_or_init_by_csn (&buf->buf_missing_csn, anchorcsn); + } + } + } + if ( rc != 0 ) { + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "clcache_load_buffer: rc=%d\n", rc ); + } + + return rc; +} + +static int +clcache_load_buffer_bulk ( CLC_Buffer *buf, int flag ) +{ + DB_TXN *txn = NULL; + DBC *cursor = NULL; + int rc; + + /* txn control seems not improving anything so turn it off */ + /* + if ( *(_pool->pl_dbenv) ) { + txn_begin( *(_pool->pl_dbenv), NULL, &txn, 0 ); + } + */ + + PR_Lock ( buf->buf_busy_list->bl_lock ); + if ( 0 == ( rc = clcache_open_cursor ( txn, buf, &cursor )) ) { + + if ( flag == DB_NEXT ) { + /* For bulk read, position the cursor before read the next block */ + rc = cursor->c_get ( cursor, + & buf->buf_key, + & buf->buf_data, + DB_SET ); + } + + /* + * Continue if the error is no-mem since we don't need to + * load in the key record anyway with DB_SET. + */ + if ( 0 == rc || ENOMEM == rc ) + rc = clcache_cursor_get ( cursor, buf, flag ); + + } + + /* + * Don't keep a cursor open across the whole replication session. + * That had caused noticable DB resource contention. + */ + if ( cursor ) { + cursor->c_close ( cursor ); + } + + if ( txn ) { + txn->commit ( txn, DB_TXN_NOSYNC ); + } + + PR_Unlock ( buf->buf_busy_list->bl_lock ); + + buf->buf_record_ptr = NULL; + if ( 0 == rc ) { + DB_MULTIPLE_INIT ( buf->buf_record_ptr, &buf->buf_data ); + if ( NULL == buf->buf_record_ptr ) + rc = DB_NOTFOUND; + else + buf->buf_load_cnt++; + } + + return rc; +} + +/* + * Gets the next change from the buffer. + * *key : output - key of the next change, or NULL if no more change + * *data: output - data of the next change, or NULL if no more change + */ +int +clcache_get_next_change ( CLC_Buffer *buf, void **key, size_t *keylen, void **data, size_t *datalen, CSN **csn ) +{ + int skip = 1; + int rc = 0; + + do { + *key = *data = NULL; + *keylen = *datalen = 0; + + if ( buf->buf_record_ptr ) { + DB_MULTIPLE_KEY_NEXT ( buf->buf_record_ptr, &buf->buf_data, + *key, *keylen, *data, *datalen ); + } + + /* + * We're done with the current buffer. Now load the next chunk. + */ + if ( NULL == *key && CLC_STATE_READY == buf->buf_state ) { + rc = clcache_load_buffer ( buf, NULL, DB_NEXT ); + if ( 0 == rc && buf->buf_record_ptr ) { + DB_MULTIPLE_KEY_NEXT ( buf->buf_record_ptr, &buf->buf_data, + *key, *keylen, *data, *datalen ); + } + } + + /* Compare the new change to the local and remote RUVs */ + if ( NULL != *key ) { + buf->buf_record_cnt++; + csn_init_by_string ( buf->buf_current_csn, (char*)*key ); + skip = clcache_skip_change ( buf ); + if (skip) buf->buf_record_skipped++; + } + } + while ( rc == 0 && *key && skip ); + + if ( NULL == *key ) { + *key = NULL; + *csn = NULL; + rc = DB_NOTFOUND; + } + else { + *csn = buf->buf_current_csn; + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "load=%d rec=%d csn=%s\n", + buf->buf_load_cnt, buf->buf_record_cnt, (char*)*key ); + } + + return rc; +} + +static void +clcache_refresh_consumer_maxcsns ( CLC_Buffer *buf ) +{ + int i; + + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + ruv_get_largest_csn_for_replica ( + buf->buf_consumer_ruv, + buf->buf_cscbs[i]->rid, + &buf->buf_cscbs[i]->consumer_maxcsn ); + } +} + +static int +clcache_refresh_local_maxcsn ( const ruv_enum_data *rid_data, void *data ) +{ + CLC_Buffer *buf = (CLC_Buffer*) data; + ReplicaId rid; + int rc = 0; + int i; + + rid = csn_get_replicaid ( rid_data->csn ); + + /* + * No need to create cscb for consumer's RID. + * If RID==65535, the CSN is originated from a + * legacy consumer. In this case the supplier + * and the consumer may have the same RID. + */ + if ( rid == buf->buf_consumer_rid && rid != MAX_REPLICA_ID ) + return rc; + + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + if ( buf->buf_cscbs[i]->rid == rid ) + break; + } + if ( i >= buf->buf_num_cscbs ) { + buf->buf_cscbs[i] = clcache_new_cscb (); + if ( buf->buf_cscbs[i] == NULL ) { + return -1; + } + buf->buf_cscbs[i]->rid = rid; + buf->buf_num_cscbs++; + } + + csn_dup_or_init_by_csn ( &buf->buf_cscbs[i]->local_maxcsn, rid_data->csn ); + + if ( buf->buf_cscbs[i]->consumer_maxcsn && + csn_compare (buf->buf_cscbs[i]->consumer_maxcsn, rid_data->csn) >= 0 ) { + /* No change need to be sent for this RID */ + buf->buf_cscbs[i]->state = CLC_STATE_UP_TO_DATE; + } + + return rc; +} + +static int +clcache_refresh_local_maxcsns ( CLC_Buffer *buf ) +{ + int i; + + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + csn_dup_or_init_by_csn ( &buf->buf_cscbs[i]->prev_local_maxcsn, + buf->buf_cscbs[i]->local_maxcsn ); + } + return ruv_enumerate_elements ( buf->buf_local_ruv, clcache_refresh_local_maxcsn, buf ); +} + +/* + * Algorithm: + * + * 1. Snapshot local RUVs; + * 2. Load buffer; + * 3. Send to the consumer only those CSNs that are covered + * by the RUVs snapshot taken in the first step; + * All CSNs that are covered by the RUVs snapshot taken in the + * first step are guaranteed in consecutive order for the respected + * RIDs because of the the CSN pending list control; + * A CSN that is not covered by the RUVs snapshot may be out of order + * since it is possible that a smaller CSN might not have committed + * yet by the time the buffer was loaded. + * 4. Determine anchorcsn for each RID: + * + * Case| Local vs. Buffer | New Local | Next + * | MaxCSN MaxCSN | MaxCSN | Anchor-CSN + * ----+-------------------+-----------+---------------- + * 1 | Cl >= Cb | * | Cb + * 2 | Cl < Cb | Cl | Cb + * 3 | Cl < Cb | Cl2 | Cl + * + * 5. Determine anchorcsn for next load: + * Anchor-CSN = min { all Next-Anchor-CSN, Buffer-MaxCSN } + */ +static int +clcache_adjust_anchorcsn ( CLC_Buffer *buf ) +{ + PRBool hasChange = PR_FALSE; + struct csn_seq_ctrl_block *cscb; + int rc = 0; + int i; + + if ( buf->buf_state == CLC_STATE_READY ) { + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + cscb = buf->buf_cscbs[i]; + + if ( cscb->state == CLC_STATE_UP_TO_DATE ) + continue; + + /* + * Case 3 unsafe ruv change: next buffer load should start + * from where the maxcsn in the old ruv was. Since each + * cscb has remembered the maxcsn sent to the consumer, + * CSNs that may be loaded again could easily be skipped. + */ + if ( cscb->prev_local_maxcsn && + csn_compare (cscb->prev_local_maxcsn, buf->buf_current_csn) < 0 && + csn_compare (cscb->local_maxcsn, cscb->prev_local_maxcsn) != 0 ) { + hasChange = PR_TRUE; + cscb->state = CLC_STATE_READY; + csn_init_by_csn ( buf->buf_current_csn, cscb->prev_local_maxcsn ); + csn_as_string ( cscb->prev_local_maxcsn, 0, (char*)buf->buf_key.data ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "adjust anchor csn upon %s\n", + ( cscb->state == CLC_STATE_CSN_GT_RUV ? "out of sequence csn" : "unsafe ruv change") ); + continue; + } + + /* + * check if there are still changes to send for this RID + * Assume we had compared the local maxcsn and the consumer + * max csn before this function was called and hence the + * cscb->state had been set accordingly. + */ + if ( hasChange == PR_FALSE && + csn_compare (cscb->local_maxcsn, buf->buf_current_csn) > 0 ) { + hasChange = PR_TRUE; + } + } + } + + if ( !hasChange ) { + buf->buf_state = CLC_STATE_DONE; + } + + return buf->buf_state; +} + +static int +clcache_skip_change ( CLC_Buffer *buf ) +{ + struct csn_seq_ctrl_block *cscb = NULL; + ReplicaId rid; + int skip = 1; + int i; + + do { + + rid = csn_get_replicaid ( buf->buf_current_csn ); + + /* + * Skip CSN that is originated from the consumer. + * If RID==65535, the CSN is originated from a + * legacy consumer. In this case the supplier + * and the consumer may have the same RID. + */ + if (rid == buf->buf_consumer_rid && rid != MAX_REPLICA_ID) + break; + + /* Skip helper entry (ENTRY_COUNT, PURGE_RUV and so on) */ + if ( cl5HelperEntry ( NULL, buf->buf_current_csn ) == PR_TRUE ) { + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "Skip helper entry type=%d\n", csn_get_time( buf->buf_current_csn )); + break; + } + + /* Find csn sequence control block for the current rid */ + for (i = 0; i < buf->buf_num_cscbs && buf->buf_cscbs[i]->rid != rid; i++); + + /* Skip CSN whose RID is unknown to the local RUV snapshot */ + if ( i >= buf->buf_num_cscbs ) { + buf->buf_state = CLC_STATE_NEW_RID; + break; + } + + cscb = buf->buf_cscbs[i]; + + /* Skip if the consumer is already up-to-date for the RID */ + if ( cscb->state == CLC_STATE_UP_TO_DATE ) { + break; + } + + /* Skip CSN whose preceedents are not covered by local RUV snapshot */ + if ( cscb->state == CLC_STATE_CSN_GT_RUV ) { + break; + } + + /* Skip CSNs already covered by consumer RUV */ + if ( cscb->consumer_maxcsn && + csn_compare ( buf->buf_current_csn, cscb->consumer_maxcsn ) <= 0 ) { + break; + } + + /* Send CSNs that are covered by the local RUV snapshot */ + if ( csn_compare ( buf->buf_current_csn, cscb->local_maxcsn ) <= 0 ) { + skip = 0; + csn_dup_or_init_by_csn ( &cscb->consumer_maxcsn, buf->buf_current_csn ); + break; + } + + /* + * Promote the local maxcsn to its next neighbor + * to keep the current session going. Skip if we + * are not sure if current_csn is the neighbor. + */ + if ( csn_time_difference(buf->buf_current_csn, cscb->local_maxcsn) == 0 && + (csn_get_seqnum(buf->buf_current_csn) == + csn_get_seqnum(cscb->local_maxcsn) + 1) ) { + csn_init_by_csn ( cscb->local_maxcsn, buf->buf_current_csn ); + csn_init_by_csn ( cscb->consumer_maxcsn, buf->buf_current_csn ); + skip = 0; + break; + } + + /* Skip CSNs not covered by local RUV snapshot */ + cscb->state = CLC_STATE_CSN_GT_RUV; + + } while (0); + +#ifdef DEBUG + if (skip && cscb) { + char consumer[24] = {'\0'}; + char local[24] = {'\0'}; + char current[24] = {'\0'}; + + if ( cscb->consumer_maxcsn ) + csn_as_string ( cscb->consumer_maxcsn, PR_FALSE, consumer ); + if ( cscb->local_maxcsn ) + csn_as_string ( cscb->local_maxcsn, PR_FALSE, local ); + csn_as_string ( buf->buf_current_csn, PR_FALSE, current ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "Skip %s consumer=%s local=%s\n", current, consumer, local ); + } +#endif + + return skip; +} + +static struct csn_seq_ctrl_block * +clcache_new_cscb () +{ + struct csn_seq_ctrl_block *cscb; + + cscb = (struct csn_seq_ctrl_block *) slapi_ch_calloc ( 1, sizeof (struct csn_seq_ctrl_block) ); + if (cscb == NULL) { + slapi_log_error ( SLAPI_LOG_FATAL, NULL, "clcache: malloc failure\n" ); + } + return cscb; +} + +static void +clcache_free_cscb ( struct csn_seq_ctrl_block ** cscb ) +{ + csn_free ( & (*cscb)->consumer_maxcsn ); + csn_free ( & (*cscb)->local_maxcsn ); + csn_free ( & (*cscb)->prev_local_maxcsn ); + slapi_ch_free ( (void **) cscb ); +} + +/* + * Allocate and initialize a new buffer + * It is called when there is a request for a buffer while + * buffer free list is empty. + */ +static CLC_Buffer * +clcache_new_buffer ( ReplicaId consumer_rid ) +{ + CLC_Buffer *buf = NULL; + int page_count = 0; + int welldone = 0; + int rc = 0; + + do { + + buf = (CLC_Buffer*) slapi_ch_calloc (1, sizeof(CLC_Buffer)); + if ( NULL == buf ) + break; + + buf->buf_key.flags = DB_DBT_USERMEM; + buf->buf_key.ulen = CSN_STRSIZE + 1; + buf->buf_key.size = CSN_STRSIZE; + buf->buf_key.data = slapi_ch_calloc( 1, buf->buf_key.ulen ); + if ( NULL == buf->buf_key.data ) + break; + + buf->buf_data.flags = DB_DBT_USERMEM; + buf->buf_data.ulen = _pool->pl_buffer_default_pages * DEFAULT_CLC_BUFFER_PAGE_SIZE; + buf->buf_data.data = slapi_ch_malloc( buf->buf_data.ulen ); + if ( NULL == buf->buf_data.data ) + break; + + if ( NULL == ( buf->buf_current_csn = csn_new()) ) + break; + + buf->buf_state = CLC_STATE_READY; + buf->buf_agmt_name = get_thread_private_agmtname(); + buf->buf_consumer_rid = consumer_rid; + buf->buf_num_cscbs = 0; + + welldone = 1; + + } while (0); + + if ( !welldone ) { + clcache_delete_buffer ( &buf ); + } + + return buf; +} + +/* + * Deallocates a buffer. + * It is called when a buffer is returned to the buffer pool + * and the pool size is over the limit. + */ +static void +clcache_delete_buffer ( CLC_Buffer **buf ) +{ + if ( buf && *buf ) { + slapi_ch_free (&( (*buf)->buf_key.data )); + slapi_ch_free (&( (*buf)->buf_data.data )); + csn_free (&( (*buf)->buf_current_csn )); + csn_free (&( (*buf)->buf_missing_csn )); + slapi_ch_free ( (void **) buf ); + } +} + +static CLC_Busy_List * +clcache_new_busy_list () +{ + CLC_Busy_List *bl; + int welldone = 0; + + do { + if ( NULL == (bl = ( CLC_Busy_List* ) slapi_ch_calloc (1, sizeof(CLC_Busy_List)) )) + break; + + if ( NULL == (bl->bl_lock = PR_NewLock ()) ) + break; + + /* + if ( NULL == (bl->bl_max_csn = csn_new ()) ) + break; + */ + + welldone = 1; + } + while (0); + + if ( !welldone ) { + clcache_delete_busy_list ( &bl ); + } + + return bl; +} + +static void +clcache_delete_busy_list ( CLC_Busy_List **bl ) +{ + if ( bl && *bl ) { + if ( (*bl)->bl_lock ) { + PR_DestroyLock ( (*bl)->bl_lock ); + } + /* csn_free (&( (*bl)->bl_max_csn )); */ + slapi_ch_free ( (void **) bl ); + } +} + +static int +clcache_enqueue_busy_list ( DB *db, CLC_Buffer *buf ) +{ + CLC_Busy_List *bl; + int rc = 0; + + PR_RWLock_Rlock ( _pool->pl_lock ); + for ( bl = _pool->pl_busy_lists; bl && bl->bl_db != db; bl = bl->bl_next ); + PR_RWLock_Unlock ( _pool->pl_lock ); + + if ( NULL == bl ) { + if ( NULL == ( bl = clcache_new_busy_list ()) ) { + rc = ENOMEM; + } + else { + PR_RWLock_Wlock ( _pool->pl_lock ); + bl->bl_db = db; + bl->bl_next = _pool->pl_busy_lists; + _pool->pl_busy_lists = bl; + PR_RWLock_Unlock ( _pool->pl_lock ); + } + } + + if ( NULL != bl ) { + PR_Lock ( bl->bl_lock ); + buf->buf_busy_list = bl; + buf->buf_next = bl->bl_buffers; + bl->bl_buffers = buf; + PR_Unlock ( bl->bl_lock ); + } + + return rc; +} + +static int +clcache_open_cursor ( DB_TXN *txn, CLC_Buffer *buf, DBC **cursor ) +{ + int rc; + + rc = buf->buf_busy_list->bl_db->cursor ( buf->buf_busy_list->bl_db, txn, cursor, 0 ); + if ( rc != 0 ) { + slapi_log_error ( SLAPI_LOG_FATAL, get_thread_private_agmtname(), + "clcache: failed to open cursor; db error - %d %s\n", + rc, db_strerror(rc)); + } + + return rc; +} + +static int +clcache_cursor_get ( DBC *cursor, CLC_Buffer *buf, int flag ) +{ + int rc; + + rc = cursor->c_get ( cursor, + & buf->buf_key, + & buf->buf_data, + buf->buf_load_flag | flag ); + if ( ENOMEM == rc ) { + /* + * The record takes more space than the current size of the + * buffer. Fortunately, buf->buf_data.size has been set by + * c_get() to the actual data size needed. So we can + * reallocate the data buffer and try to read again. + */ + buf->buf_data.ulen = ( buf->buf_data.size / DEFAULT_CLC_BUFFER_PAGE_SIZE + 1 ) * DEFAULT_CLC_BUFFER_PAGE_SIZE; + buf->buf_data.data = slapi_ch_realloc ( buf->buf_data.data, buf->buf_data.ulen ); + if ( buf->buf_data.data != NULL ) { + rc = cursor->c_get ( cursor, + &( buf->buf_key ), + &( buf->buf_data ), + buf->buf_load_flag | flag ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "clcache: (%d | %d) %s reallocated and retry returns %d\n", buf->buf_load_flag, flag, buf->buf_key.data, rc ); + } + } + + switch ( rc ) { + case EINVAL: + slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name, + "clcache_cursor_get: invalid parameter\n" ); + break; + + case ENOMEM: + slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name, + "clcache_cursor_get: cann't allocate %u bytes\n", buf->buf_data.ulen ); + break; + + default: + break; + } + + return rc; +} + +static void +csn_dup_or_init_by_csn ( CSN **csn1, CSN *csn2 ) +{ + if ( *csn1 == NULL ) + *csn1 = csn_new(); + csn_init_by_csn ( *csn1, csn2 ); +} diff --git a/ldap/servers/plugins/replication/cl5_clcache.h b/ldap/servers/plugins/replication/cl5_clcache.h new file mode 100644 index 00000000..93024d1e --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_clcache.h @@ -0,0 +1,22 @@ +#ifndef CL5_CLCACHE_H +#define CL5_CLCACHE_H + +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "db.h" +#include "slapi-private.h" + +typedef struct clc_buffer CLC_Buffer; + +int clcache_init ( DB_ENV **dbenv ); +void clcache_set_config ( CL5DBConfig * config ); +int clcache_get_buffer ( CLC_Buffer **buf, DB *db, ReplicaId consumer_rid, const RUV *consumer_ruv, const RUV *local_ruv ); +int clcache_load_buffer ( CLC_Buffer *buf, CSN *startCSN, int flag ); +void clcache_return_buffer ( CLC_Buffer **buf ); +int clcache_get_next_change ( CLC_Buffer *buf, void **key, size_t *keylen, void **data, size_t *datalen, CSN **csn ); + +#endif diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c new file mode 100644 index 00000000..58c79dc1 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_config.c @@ -0,0 +1,868 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_config.c - functions to process changelog configuration + */ + +#include <string.h> +#include <prio.h> +#include "repl5.h" +#include "cl5.h" +#include "cl5_clcache.h" /* To configure the Changelog Cache */ +#include "intrinsics.h" /* JCMREPL - Is this bad? */ +#ifdef TEST_CL5 +#include "cl5_test.h" +#endif + +#define CONFIG_BASE "cn=changelog5,cn=config" /*"cn=changelog,cn=supplier,cn=replication5.0,cn=replication,cn=config"*/ +#define CONFIG_FILTER "(objectclass=*)" + +static PRRWLock *s_configLock; /* guarantees that only on thread at a time + modifies changelog configuration */ + +/* Forward Declartions */ +static int changelog5_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int changelog5_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg); + +static void changelog5_extract_config(Slapi_Entry* entry, changelog5Config *config); +static changelog5Config * changelog5_dup_config(changelog5Config *config); +static void replace_bslash (char *dir); +static int notify_replica (Replica *r, void *arg); +static int _is_absolutepath (char *dir); + +int changelog5_config_init() +{ + /* The FE DSE *must* be initialised before we get here */ + + /* create the configuration lock */ + s_configLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "config_lock"); + if (s_configLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_init: failed to create configurationlock; " + "NSPR error - %d\n",PR_GetError ()); + return 1; + } + + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_add, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_modify, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, dont_allow_that, NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_delete, NULL); + + return 0; +} + +void changelog5_config_cleanup() +{ + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_add); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_modify); + slapi_config_remove_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, dont_allow_that); + slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_delete); + + if (s_configLock) + { + PR_DestroyRWLock (s_configLock); + s_configLock = NULL; + } +} + +int changelog5_read_config (changelog5Config *config) +{ + int rc = LDAP_SUCCESS; + Slapi_PBlock *pb; + + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, CONFIG_BASE, LDAP_SCOPE_BASE, CONFIG_FILTER, NULL, 0, NULL, + NULL, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + if ( LDAP_SUCCESS == rc ) + { + Slapi_Entry **entries = NULL; + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries ); + if ( NULL != entries && NULL != entries[0]) + { + /* Extract the config info from the changelog entry */ + changelog5_extract_config(entries[0], config); + } + } + else + { + memset (config, 0, sizeof (*config)); + rc = LDAP_SUCCESS; + } + + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return rc; +} + +void changelog5_config_done (changelog5Config *config) +{ + if (config) { + /* slapi_ch_free_string accepts NULL pointer */ + slapi_ch_free_string (&config->maxAge); + slapi_ch_free_string (&config->dir); + } +} + +void changelog5_config_free (changelog5Config **config) +{ + changelog5_config_done(*config); + slapi_ch_free((void **)config); +} + +static int +changelog5_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + int rc; + changelog5Config config; + + *returncode = LDAP_SUCCESS; + + PR_RWLock_Wlock (s_configLock); + + /* we already have a configured changelog - don't need to do anything + since add operation will fail */ + if (cl5GetState () == CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "attempt to add changelog when it already exists"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: changelog already exist; " + "request ignored\n"); + goto done; + } + + changelog5_extract_config(e, &config); + if (config.dir == NULL) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "NULL changelog directory"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: NULL changelog directory\n"); + goto done; + } + + /* start the changelog */ + rc = cl5Open (config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to start changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: failed to start changelog\n"); + goto done; + } + + /* set trimming parameters */ + rc = cl5ConfigTrimming (config.maxEntries, config.maxAge); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to configure changelog trimming; error - %d", rc); + } + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: failed to configure changelog trimming\n"); + goto done; + } + + /* notify all the replicas that the changelog is configured + so that the can log dummy changes if necessary. */ + replica_enumerate_replicas (notify_replica, NULL); + +#ifdef TEST_CL5 + testChangelog (TEST_ITERATION); +#endif + +done:; + PR_RWLock_Unlock (s_configLock); + changelog5_config_done (&config); + if (*returncode == LDAP_SUCCESS) + { + if (returntext) + { + returntext[0] = '\0'; + } + + return SLAPI_DSE_CALLBACK_OK; + } + + return SLAPI_DSE_CALLBACK_ERROR; +} + +static int +changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + int rc= 0; + LDAPMod **mods; + int i; + changelog5Config config; + changelog5Config * originalConfig = NULL; + char *currentDir = NULL; + + *returncode = LDAP_SUCCESS; + + /* changelog must be open before its parameters can be modified */ + if (cl5GetState() != CL5_STATE_OPEN) + { + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: changelog is not configured\n"); + return SLAPI_DSE_CALLBACK_ERROR; + } + + PR_RWLock_Wlock (s_configLock); + + /* changelog must be open before its parameters can be modified */ + if (cl5GetState() != CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: changelog is not configured\n"); + goto done; + } + + /* + * Extract all the original configuration: This is needed to ensure that the configuration + * is trully reloaded. This was not needed before 091401 because the changelog configuration + * was always hardcoded (NULL was being passed to cl5Open). Now we need to ensure we pass to + * cl5Open the proper configuration... + */ + changelog5_extract_config(e, &config); + originalConfig = changelog5_dup_config(&config); + + /* Reset all the attributes that have been potentially modified by the current MODIFY operation */ + slapi_ch_free_string(&config.dir); + config.dir = NULL; + config.maxEntries = CL5_NUM_IGNORE; + slapi_ch_free_string(&config.maxAge); + config.maxAge = slapi_ch_strdup(CL5_STR_IGNORE); + config.dbconfig.maxChCacheEntries = 0; + config.dbconfig.maxChCacheSize = CL5_NUM_IGNORE; + + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + for (i = 0; mods[i] != NULL; i++) + { + if (mods[i]->mod_op & LDAP_MOD_DELETE) + { + /* We don't support deleting changelog attributes */ + } + else + { + int j; + for (j = 0; ((mods[i]->mod_values[j]) && (LDAP_SUCCESS == rc)); j++) + { + char *config_attr, *config_attr_value; + config_attr = (char *) mods[i]->mod_type; + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + +#define ATTR_MODIFIERSNAME "modifiersname" +#define ATTR_MODIFYTIMESTAMP "modifytimestamp" + + if ( strcasecmp ( config_attr, ATTR_MODIFIERSNAME ) == 0 ) { + continue; + } + if ( strcasecmp ( config_attr, ATTR_MODIFYTIMESTAMP ) == 0 ) { + continue; + } + /* replace existing value */ + if ( strcasecmp (config_attr, CONFIG_CHANGELOG_DIR_ATTRIBUTE ) == 0 ) + { + if (config_attr_value && config_attr_value[0] != '\0') + { + slapi_ch_free_string(&config.dir); + config.dir = slapi_ch_strdup(config_attr_value); + replace_bslash (config.dir); + } + else + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "null changelog directory"); + } + goto done; + } + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE ) == 0 ) + { + if (config_attr_value && config_attr_value[0] != '\0') + { + config.maxEntries = atoi (config_attr_value); + } + else + { + config.maxEntries = 0; + } + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE ) == 0 ) + { + slapi_ch_free_string(&config.maxAge); + config.maxAge = slapi_ch_strdup(config_attr_value); + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_CACHESIZE ) == 0 ) + { /* The Changelog Cache Size parameters can be modified online without a need for restart */ + if (config_attr_value && config_attr_value[0] != '\0') + { + config.dbconfig.maxChCacheEntries = atoi (config_attr_value); + } + else + { + config.dbconfig.maxChCacheEntries = 0; + } + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_CACHEMEMSIZE ) == 0 ) + { /* The Changelog Cache Size parameters can be modified online without a need for restart */ + if (config_attr_value && config_attr_value[0] != '\0') + { + config.dbconfig.maxChCacheSize = atoi (config_attr_value); + } + else + { + config.dbconfig.maxChCacheSize = 0; + } + } + else + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + if (returntext) + { + sprintf (returntext, "Unwilling to apply %s mods while the server is running", config_attr); + } + goto done; + } + } + } + } + /* Undo the reset above for all the modifiable attributes that were not modified + * except config.dir */ + if (config.maxEntries == CL5_NUM_IGNORE) + config.maxEntries = originalConfig->maxEntries; + if (strcmp (config.maxAge, CL5_STR_IGNORE) == 0) { + slapi_ch_free_string(&config.maxAge); + if (originalConfig->maxAge) + config.maxAge = slapi_ch_strdup(originalConfig->maxAge); + } + if (config.dbconfig.maxChCacheEntries == 0) + config.dbconfig.maxChCacheEntries = originalConfig->dbconfig.maxChCacheEntries; + if (config.dbconfig.maxChCacheSize == CL5_NUM_IGNORE) + config.dbconfig.maxChCacheSize = originalConfig->dbconfig.maxChCacheSize; + + + /* attempt to change chagelog dir */ + if (config.dir) + { + currentDir = cl5GetDir (); + if (currentDir == NULL) + { + /* something is wrong: we should never be here */ + *returncode = 1; + if (returntext) + { + strcpy (returntext, "internal failure"); + } + + goto done; + } + +#ifdef _WIN32 + if (strcasecmp (currentDir, config.dir) != 0) +#else /* On Unix, path are case sensitive */ + if (strcmp (currentDir, config.dir) != 0) +#endif + { + if (!_is_absolutepath(config.dir) || (CL5_SUCCESS != cl5CreateDirIfNeeded(config.dir))) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "invalid changelog directory or insufficient access"); + } + + goto done; + } + + /* changelog directory changed - need to remove the + previous changelog and create new one */ + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "changelog5_config_modify: changelog directory changed; " + "old dir - %s, new dir - %s; recreating changelog.\n", + currentDir, config.dir); + + /* this call will block until all threads using changelog + release changelog by calling cl5RemoveThread () */ + rc = cl5Close (); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to close changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to close changelog\n"); + goto done; + } + + rc = cl5Delete (currentDir); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to remove changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to remove changelog\n"); + goto done; + } + + rc = cl5Open (config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to restart changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to restart changelog\n"); + /* before finishing, let's try to do some error recovery */ + if (CL5_SUCCESS != cl5Open(currentDir, &config.dbconfig)) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to restore previous changelog\n"); + } + goto done; + } + } + } + + /* one of the changelog parameters is modified */ + if (config.maxEntries != CL5_NUM_IGNORE || + strcmp (config.maxAge, CL5_STR_IGNORE) != 0) + { + rc = cl5ConfigTrimming (config.maxEntries, config.maxAge); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to configure changelog trimming; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to configure changelog trimming\n"); + goto done; + } + } + + if (config.dbconfig.maxChCacheEntries != 0 || config.dbconfig.maxChCacheSize != CL5_NUM_IGNORE) + clcache_set_config(&config.dbconfig); + +done:; + PR_RWLock_Unlock (s_configLock); + + changelog5_config_done (&config); + changelog5_config_free (&originalConfig); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)¤tDir); + + if (*returncode == LDAP_SUCCESS) + { + + if (returntext) + { + returntext[0] = '\0'; + } + + return SLAPI_DSE_CALLBACK_OK; + } + + return SLAPI_DSE_CALLBACK_ERROR; +} + +static int +changelog5_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + int rc; + char *currentDir = NULL; + *returncode = LDAP_SUCCESS; + + /* changelog must be open before it can be deleted */ + if (cl5GetState () != CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: chagelog is not configured\n"); + return SLAPI_DSE_CALLBACK_ERROR; + } + + PR_RWLock_Wlock (s_configLock); + + /* changelog must be open before it can be deleted */ + if (cl5GetState () != CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: chagelog is not configured\n"); + goto done; + } + + currentDir = cl5GetDir (); + + if (currentDir == NULL) + { + /* something is wrong: we should never be here */ + *returncode = 1; + if (returntext) + { + strcpy (returntext, "internal failure"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: NULL directory\n"); + goto done; + } + + /* this call will block until all threads using changelog + release changelog by calling cl5RemoveThread () */ + rc = cl5Close (); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to close changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: failed to close changelog\n"); + goto done; + } + + rc = cl5Delete (currentDir); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to remove changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: failed to remove changelog\n"); + goto done; + } + +done:; + PR_RWLock_Unlock (s_configLock); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)¤tDir); + + if (*returncode == LDAP_SUCCESS) + { + if (returntext) + { + returntext[0] = '\0'; + } + + return SLAPI_DSE_CALLBACK_OK; + } + + return SLAPI_DSE_CALLBACK_ERROR; +} + +static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +static changelog5Config * changelog5_dup_config(changelog5Config *config) +{ + changelog5Config *dup = (changelog5Config *) slapi_ch_calloc(1, sizeof(changelog5Config)); + + if (config->dir) + dup->dir = slapi_ch_strdup(config->dir); + if (config->maxAge) + dup->maxAge = slapi_ch_strdup(config->maxAge); + + dup->maxEntries = config->maxEntries; + + /*memcpy((void *) &dup->dbconfig, (const void *) &config->dbconfig, sizeof(CL5DBConfig));*/ + dup->dbconfig.cacheSize = config->dbconfig.cacheSize; + dup->dbconfig.durableTrans = config->dbconfig.durableTrans; + dup->dbconfig.checkpointInterval = config->dbconfig.checkpointInterval; + dup->dbconfig.circularLogging = config->dbconfig.circularLogging; + dup->dbconfig.pageSize = config->dbconfig.pageSize; + dup->dbconfig.logfileSize = config->dbconfig.logfileSize; + dup->dbconfig.maxTxnSize = config->dbconfig.maxTxnSize; + dup->dbconfig.fileMode = config->dbconfig.fileMode; + dup->dbconfig.verbose = config->dbconfig.verbose; + dup->dbconfig.debug = config->dbconfig.debug; + dup->dbconfig.tricklePercentage = config->dbconfig.tricklePercentage; + dup->dbconfig.spinCount = config->dbconfig.spinCount; + dup->dbconfig.maxChCacheEntries = config->dbconfig.maxChCacheEntries; + dup->dbconfig.maxChCacheSize = config->dbconfig.maxChCacheSize; + dup->dbconfig.nb_lock_config = config->dbconfig.nb_lock_config; + + return dup; +} + + +/* + * Given the changelog configuration entry, extract the configuration directives. + */ +static void changelog5_extract_config(Slapi_Entry* entry, changelog5Config *config) +{ + char *arg; + + memset (config, 0, sizeof (*config)); + config->dir = slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_DIR_ATTRIBUTE); + replace_bslash (config->dir); + + arg= slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE); + if (arg) + { + config->maxEntries = atoi (arg); + slapi_ch_free_string(&arg); + } + + config->maxAge = slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE); + + /* + * Read the Changelog Internal Configuration Parameters for the Changelog DB + * (db cache size, db settings...) + */ + + /* Set configuration default values first... */ + config->dbconfig.cacheSize = CL5_DEFAULT_CONFIG_DB_DBCACHESIZE; + config->dbconfig.durableTrans = CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS; + config->dbconfig.checkpointInterval = CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL; + config->dbconfig.circularLogging = CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING; + config->dbconfig.pageSize = CL5_DEFAULT_CONFIG_DB_PAGE_SIZE; + config->dbconfig.logfileSize = CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE; + config->dbconfig.maxTxnSize = CL5_DEFAULT_CONFIG_DB_TXN_MAX; + config->dbconfig.verbose = CL5_DEFAULT_CONFIG_DB_VERBOSE; + config->dbconfig.debug = CL5_DEFAULT_CONFIG_DB_DEBUG; + config->dbconfig.tricklePercentage = CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE; + config->dbconfig.spinCount = CL5_DEFAULT_CONFIG_DB_SPINCOUNT; + config->dbconfig.nb_lock_config = CL5_DEFAULT_CONFIG_NB_LOCK; + + /* Now read from the entry to override default values if needed */ + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DBCACHESIZE); + if (arg) + { + size_t theSize = atoi (arg); + if (theSize > CL5_MIN_DB_DBCACHESIZE) + config->dbconfig.cacheSize = theSize; + else { + config->dbconfig.cacheSize = CL5_MIN_DB_DBCACHESIZE; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Warning: Changelog dbcache size too small. " + "Increasing the Memory Size to %d bytes\n", + CL5_MIN_DB_DBCACHESIZE); + } + slapi_ch_free_string(&arg); + } + + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DURABLE_TRANSACTIONS); + if (arg) + { + config->dbconfig.durableTrans = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_CHECKPOINT_INTERVAL); + if (arg) + { + config->dbconfig.checkpointInterval = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_CIRCULAR_LOGGING); + if (arg) + { + config->dbconfig.circularLogging = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_PAGE_SIZE); + if (arg) + { + config->dbconfig.pageSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_LOGFILE_SIZE); + if (arg) + { + config->dbconfig.logfileSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_MAXTXN_SIZE); + if (arg) + { + config->dbconfig.maxTxnSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_VERBOSE); + if (arg) + { + config->dbconfig.verbose = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DEBUG); + if (arg) + { + config->dbconfig.debug = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_TRICKLE_PERCENTAGE); + if (arg) + { + config->dbconfig.tricklePercentage = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_SPINCOUNT); + if (arg) + { + config->dbconfig.spinCount = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_MAX_CONCURRENT_WRITES); + if (arg) + { + config->dbconfig.maxConcurrentWrites = atoi (arg); + slapi_ch_free_string(&arg); + } + if ( config->dbconfig.maxConcurrentWrites <= 0 ) + { + config->dbconfig.maxConcurrentWrites = CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES; + } + + /* + * Read the Changelog Internal Configuration Parameters for the Changelog Cache + */ + + /* Set configuration default values first... */ + config->dbconfig.maxChCacheEntries = CL5_DEFAULT_CONFIG_CACHESIZE; + config->dbconfig.maxChCacheSize = CL5_DEFAULT_CONFIG_CACHEMEMSIZE; + + /* Now read from the entry to override default values if needed */ + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_CACHESIZE); + if (arg) + { + config->dbconfig.maxChCacheEntries = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_CACHEMEMSIZE); + if (arg) + { + config->dbconfig.maxChCacheSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg = slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_NB_LOCK); + if (arg) + { + size_t theSize = atoi(arg); + if (theSize < CL5_MIN_NB_LOCK) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Warning: Changelog %s value is too low (%d). Set to minimal value instead (%d)\n", + CONFIG_CHANGELOG_NB_LOCK, theSize, CL5_MIN_NB_LOCK); + config->dbconfig.nb_lock_config = CL5_MIN_NB_LOCK; + } + else + { + config->dbconfig.nb_lock_config = theSize; + } + slapi_ch_free_string(&arg); + } + + clcache_set_config(&config->dbconfig); +} + +static void replace_bslash (char *dir) +{ + char *bslash; + + if (dir == NULL) + return; + + bslash = strchr (dir, '\\'); + while (bslash) + { + *bslash = '/'; + bslash = strchr (bslash, '\\'); + } +} + +static int notify_replica (Replica *r, void *arg) +{ + return replica_log_ruv_elements (r); +} + +static int _is_absolutepath (char * dir) +{ + if (dir[0] == '/') + return 1; +#if defined(_WIN32) + if (dir[2] == '/' && dir[1] == ':') + return 1; +#endif + return 0; +} diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c new file mode 100644 index 00000000..435299c0 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_init.c @@ -0,0 +1,77 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* cl5_init.c - implments initialization/cleanup functions for + 4.0 style changelog + */ + +#include "slapi-plugin.h" +#include "cl5.h" +#include "repl5.h" + +/* initializes changelog*/ +int changelog5_init() +{ + int rc; + changelog5Config config; + + rc = cl5Init (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_init: failed to initialize changelog\n"); + return 1; + } + + /* read changelog configuration */ + changelog5_config_init (); + changelog5_read_config (&config); + + if (config.dir == NULL) + { + /* changelog is not configured - bail out */ + rc = 0; /* OK */ + goto done; + } + + /* start changelog */ + rc = cl5Open (config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_init: failed to start changelog at %s\n", + config.dir); + rc = 1; + goto done; + } + + /* set trimming parameters */ + rc = cl5ConfigTrimming (config.maxEntries, config.maxAge); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_init: failed to configure changelog trimming\n"); + rc = 1; + goto done; + } + + rc = 0; + +done: + changelog5_config_done (&config); + return rc; +} + +/* cleanups changelog data */ +void changelog5_cleanup() +{ + /* close changelog */ + cl5Close (); + cl5Cleanup (); + + /* cleanup config */ + changelog5_config_cleanup (); +} diff --git a/ldap/servers/plugins/replication/cl5_test.c b/ldap/servers/plugins/replication/cl5_test.c new file mode 100644 index 00000000..b64a60f5 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_test.c @@ -0,0 +1,830 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_test.c - changelog test cases */ +#include "cl5_test.h" +#include "slapi-plugin.h" +#include "cl5.h" + +#define REPLICA_ROOT "dc=example,dc=com" /* replica root */ +#define OP_COUNT 4 /* number of ops generated at a time */ +#define MOD_COUNT 5 +#define VALUE_COUNT 5 +#define ENTRY_COUNT 50 +#define CL_DN "cn=changelog5,cn=config" +#define INSTANCE_ATTR "nsslapd-instancedir" +#define REPLICA_OC "nsds5Replica" +#define REPLICA_RDN "cn=replica" + +static void testBasic (); +static void testBackupRestore (); +static void testIteration (); +static void testTrimming (); +static void testPerformance (); +static void testPerformanceMT (); +static void testLDIF (); +static void testAll (); +static int configureChangelog (); +static int configureReplica (); +static int populateChangelogOp (); +static int populateChangelog (int entryCount, CSN ***csnList); +static int processEntries (int entryCount, CSN **csnList); +static void clearCSNList (CSN ***csnList, int count); +static void threadMain (void *data); +static char* getBaseDir (const char *dir); +static LDAPMod **buildMods (); + +void testChangelog (TestType type) +{ + switch (type) + { + case TEST_BASIC: testBasic (); + break; + case TEST_BACKUP_RESTORE: testBackupRestore (); + break; + case TEST_ITERATION: testIteration (); + break; + case TEST_TRIMMING: testTrimming (); + break; + case TEST_PERFORMANCE: testPerformance (); + break; + case TEST_PERFORMANCE_MT: testPerformanceMT (); + break; + case TEST_LDIF: testLDIF (); + break; + case TEST_ALL: testAll (); + break; + default: printf ("Taste case %d is not supported\n", type); + } +} + +/* tests Open/Close, normal recovery, read/write/remove + of an entry */ +static void testBasic () +{ + int rc = 0; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting basic test ...\n"); + + /* ONREPL - we can't run the tests from the startup code because + operations can't be issued until all plugins are started. So, + instead, we do it when changelog is created + rc = configureChangelog (); */ + if (rc == 0) + { + rc = configureReplica (); + if (rc == 0) + { + rc = populateChangelogOp (); + } + } + + if (rc == 0) + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Basic test completed successfully\n"); + else + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Basic test failed\n"); +} + +static void testBackupRestore () +{ + char *dir; + int rc = -1; + char *baseDir; + char bkDir [MAXPATHLEN + 1]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting backup and recovery test ...\n"); + + dir = cl5GetDir (); + + if (dir) + { + baseDir = getBaseDir (dir); + sprintf (bkDir, "%s/clbackup", baseDir); + slapi_ch_free ((void**)&baseDir); + rc = cl5Backup (bkDir, NULL); + + if (rc == CL5_SUCCESS) + { + cl5Close (); + rc = cl5Restore (dir, bkDir, NULL); + if (rc == CL5_SUCCESS) + rc = cl5Open (dir, NULL); + + /* PR_RmDir (bkDir);*/ + } + } + + if (rc == CL5_SUCCESS) + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Backup and Restore test completed successfully\n"); + else + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Backup and Restore test failed\n"); +} + +static void testIteration () +{ + Object *r_obj; + Slapi_DN *r_root; + Replica *r; + char *replGen; + RUV *ruv; + CL5ReplayIterator *it = NULL; + slapi_operation_parameters op; + int rc; + int i; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting iteration test ...\n"); + + /* get replica object */ + r_root = slapi_sdn_new_dn_byval(REPLICA_ROOT); + r_obj = replica_get_replica_from_dn (r_root); + slapi_sdn_free (&r_root); + if (r_obj == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "replica is not configured for (%s)\n", + REPLICA_ROOT); + return; + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting first iteration pass ...\n"); + + /* configure empty consumer ruv */ + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + replGen = replica_get_generation (r); + ruv_init_new (replGen, 0, NULL, &ruv); + + /* create replay iterator */ + rc = cl5CreateReplayIterator (r_obj, ruv, &it); + if (it) + { + i = 0; + while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS) + { + ruv_set_csns (ruv, op.csn, NULL); + operation_parameters_done (&op); + i ++; + } + } + + if (it) + cl5DestroyReplayIterator (&it); + + if (rc == CL5_NOTFOUND) + { + if (i == 0) /* success */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass completed " + "successfully: no changes to replay\n"); + else /* incorrect number of entries traversed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass failed: " + "traversed %d entries; expected none\n", i); + } + else /* general error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass failed\n"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting second iteration pass ...\n"); + + /* add some entries */ + populateChangelogOp (); + + /* create replay iterator */ + rc = cl5CreateReplayIterator (r_obj, ruv, &it); + if (it) + { + i = 0; + while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS) + { + ruv_set_csns (ruv, op.csn, NULL); + operation_parameters_done (&op); + i ++; + } + } + + if (it) + cl5DestroyReplayIterator (&it); + + if (rc == CL5_NOTFOUND) + { + if (i == OP_COUNT) /* success */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass completed " + "successfully: %d entries traversed\n", i); + else /* incorrect number of entries traversed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed: " + "traversed %d entries; expected %d\n", i, OP_COUNT); + } + else /* general error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed\n"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting third iteration pass ...\n"); + /* add more entries */ + populateChangelogOp (); + + /* create replay iterator */ + rc = cl5CreateReplayIterator (r_obj, ruv, &it); + if (it) + { + i = 0; + while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS) + { + ruv_set_csns (ruv, op.csn, NULL); + operation_parameters_done (&op); + i ++; + } + } + + if (it) + cl5DestroyReplayIterator (&it); + + if (rc == CL5_NOTFOUND) + { + if (i == OP_COUNT) /* success */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Third iteration pass completed " + "successfully: %d entries traversed\n", i); + else /* incorrect number of entries traversed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Third iteration pass failed: " + "traversed %d entries; expected %d\n", i, OP_COUNT); + } + else /* general error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed\n"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Iteration test is complete\n"); + + ruv_destroy (&ruv); + object_release (r_obj); + slapi_ch_free ((void**)&replGen); +} + +static void testTrimming () +{ + PRIntervalTime interval; + int count; + int rc; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting trimming test ...\n"); + + rc = populateChangelog (200, NULL); + + if (rc == 0) + { + interval = PR_SecondsToInterval(2); + DS_Sleep (interval); + + rc = populateChangelog (300, NULL); + + if (rc == 0) + rc = cl5ConfigTrimming (300, "1d"); + + interval = PR_SecondsToInterval(300); /* 5 min is default trimming interval */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Trimming test: sleeping for 5 minutes until trimming kicks in\n"); + DS_Sleep (interval); + + count = cl5GetOperationCount (NULL); + } + + if (rc == 0 && count == 300) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Trimming test completed successfully: changelog contains 300 entries\n"); + } + else if (rc == 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Trimming test failed: changelog contains %d entries; expected - 300\n", + count); + } + else /* general failure */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Trimming test failed\n"); +} + +static void testPerformance () +{ + PRTime starttime, endtime, totaltime; + int entryCount = 5000; + CSN **csnList = NULL; + int rc; + + starttime = PR_Now(); + + rc = populateChangelog (entryCount, &csnList); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Write performance:\n" + "entry count - %d, total time - %ldms\n" + "latency = %d msec\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryCount, entryCount * 1000 / totaltime); + + + starttime = endtime; + + rc = processEntries (entryCount, csnList); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Read performance:\n" + "entry count - %d, total time - %ld\n" + "latency = %d msec\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryCount, entryCount * 1000 / totaltime); + + clearCSNList (&csnList, entryCount); +} + +static int threadsLeft; +static void testPerformanceMT () +{ + PRTime starttime, endtime, totaltime; + int entryCount = 200; + int threadCount = 10; + int entryTotal; + int i; + PRIntervalTime interval; + + interval = PR_MillisecondsToInterval(100); + threadsLeft = threadCount * 2; + entryTotal = threadCount * entryCount; + starttime = PR_Now(); + + for (i = 0; i < threadCount; i++) + { + PR_CreateThread(PR_USER_THREAD, threadMain, (void*)&entryCount, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, 0); + } + + while (threadsLeft > 5) + DS_Sleep (interval); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Write performance:\n" + "entry count - %d, total time - %ld\n" + "latency = %d msec per entry\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryTotal, entryTotal * 1000 / totaltime); + + + starttime = endtime; + + while (threadsLeft != 0) + DS_Sleep (interval); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Read performance:\n" + "entry count - %d, total time - %ld\n" + "latency = %d msec per entry\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryTotal, entryTotal * 1000 / totaltime); +} + +static void testLDIF () +{ + char *clDir = cl5GetDir (); + int rc; + char *baseDir; + char ldifFile [MAXPATHLEN + 1]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting LDIF test ...\n"); + + baseDir = getBaseDir (clDir); + sprintf (ldifFile, "%s/cl5.ldif", baseDir); + slapi_ch_free ((void**)&baseDir); + rc = populateChangelog (ENTRY_COUNT, NULL); + + if (rc == CL5_SUCCESS) + { + rc = cl5ExportLDIF (ldifFile, NULL); + if (rc == CL5_SUCCESS) + { + cl5Close(); + rc = cl5ImportLDIF (clDir, ldifFile, NULL); + if (rc == CL5_SUCCESS) + cl5Open(clDir, NULL); + } + } + + PR_Delete (ldifFile); + + if (rc == CL5_SUCCESS) + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "LDIF test completed successfully\n"); + else + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "LDIF test failed\n"); +} + +static void testAll () +{ + testBasic (); + + testIteration (); + + testBackupRestore (); + + testLDIF (); + + /* testTrimming ();*/ + +#if 0 + /* xxxPINAKI */ + /* these tests are not working correctly...the call to db->put() */ + /* just hangs forever */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Starting single threaded performance measurement ...\n"); + testPerformance (); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Starting multi threaded performance measurement ...\n"); + testPerformanceMT (); +#endif + +} + +static int populateChangelog (int entryCount, CSN ***csnList) +{ + CSN *csn; + int i; + slapi_operation_parameters op; + int rc; + char *uniqueid; + + if (csnList) + { + (*csnList) = (CSN**)slapi_ch_calloc (entryCount, sizeof (CSN*)); + } + + /* generate entries */ + for (i = 0; i < entryCount; i++) + { + /* ONREPL need to get replica object + rc = csnGetNewCSNForRepl (&csn); + if (rc != CL5_SUCCESS) */ + return -1; + + if (csnList) + (*csnList) [i] = csn_dup (csn); + memset (&op, 0, sizeof (op)); + op.csn = csn; + slapi_uniqueIDGenerateString(&uniqueid); + op.target_address.uniqueid = uniqueid; + op.target_address.dn = slapi_ch_strdup ("cn=entry,dc=example,dc=com"); + if (i % 5 == 0) + { + op.operation_type = SLAPI_OPERATION_MODRDN; + op.p.p_modrdn.modrdn_deloldrdn = 1; + op.p.p_modrdn.modrdn_newrdn = slapi_ch_strdup("cn=entry2,dc=example,dc=com"); + op.p.p_modrdn.modrdn_newsuperior_address.dn = NULL; + op.p.p_modrdn.modrdn_newsuperior_address.uniqueid = NULL; + op.p.p_modrdn.modrdn_mods = buildMods (); + } + else if (i % 4 == 0) + { + op.operation_type = SLAPI_OPERATION_DELETE; + } + else if (i % 3 == 0) + { + + op.operation_type = SLAPI_OPERATION_ADD; + op.p.p_add.target_entry = slapi_entry_alloc (); + slapi_entry_set_dn (op.p.p_add.target_entry, slapi_ch_strdup(op.target_address.dn)); + slapi_entry_set_uniqueid (op.p.p_add.target_entry, slapi_ch_strdup(op.target_address.uniqueid)); + slapi_entry_attr_set_charptr(op.p.p_add.target_entry, "objectclass", "top"); + slapi_entry_attr_set_charptr(op.p.p_add.target_entry, "cn", "entry"); + } + else + { + op.operation_type = SLAPI_OPERATION_MODIFY; + op.p.p_modify.modify_mods = buildMods (); + } + + /* ONREPL rc = cl5WriteOperation (&op, 1);*/ + operation_parameters_done (&op); + + if (rc != CL5_SUCCESS) + return -1; + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Successfully populated changelog with %d entries\n", entryCount); + return 0; +} + +static int processEntries (int entryCount, CSN **csnList) +{ + int i; + int rc = 0; + slapi_operation_parameters op; + + for (i = 0; i < entryCount; i++) + { + memset (&op, 0, sizeof (op)); + + op.csn = csn_dup (csnList [i]); + + /* rc = cl5GetOperation (&op);*/ + if (rc != CL5_SUCCESS) + return -1; + + operation_parameters_done (&op); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Successfully read %d entries from the changelog\n", entryCount); + return 0; +} + +void clearCSNList (CSN ***csnList, int count) +{ + int i; + + for (i = 0; i < count; i++) + { + csn_free (&((*csnList)[i])); + } + + slapi_ch_free ((void**)csnList); +} + +static void threadMain (void *data) +{ + int entryCount = *(int*)data; + CSN **csnList; + + populateChangelog (entryCount, &csnList); + PR_AtomicDecrement (&threadsLeft); + + processEntries (entryCount, csnList); + PR_AtomicDecrement (&threadsLeft); + + clearCSNList (&csnList, entryCount); +} + +static char* getBaseDir (const char *dir) +{ + char *baseDir = slapi_ch_strdup (dir); + char *ch; + + ch = &(baseDir [strlen (dir) - 2]); + + while (ch >= baseDir && *ch != '\\' && *ch != '/') + ch --; + + if (ch >= baseDir) + { + *ch = '\0'; + } + + return baseDir; +} + +static LDAPMod **buildMods () +{ + Slapi_Mods smods; + Slapi_Mod smod; + LDAPMod **mods; + struct berval bv; + int j, k; + + slapi_mods_init (&smods, MOD_COUNT); + + for (j = 0; j < MOD_COUNT; j++) + { + slapi_mod_init (&smod, VALUE_COUNT); + slapi_mod_set_operation (&smod, LDAP_MOD_ADD | LDAP_MOD_BVALUES); + slapi_mod_set_type (&smod, "attr"); + + for (k = 0; k < VALUE_COUNT; k++) + { + bv.bv_val = "bvalue"; + bv.bv_len = strlen (bv.bv_val) + 1; + slapi_mod_add_value (&smod, &bv); + } + + slapi_mods_add_smod (&smods, &smod); + /* ONREPL slapi_mod_done (&smod); */ + } + + mods = slapi_mods_get_ldapmods_passout (&smods); + slapi_mods_done (&smods); + return mods; +} + +/* Format: + dn: cn=changelog5,cn=config + objectclass: top + objectclass: extensibleObject + cn: changelog5 + nsslapd-changelogDir: d:/netscape/server4/slapd-elf/cl5 */ +static int configureChangelog () +{ + Slapi_PBlock *pb = slapi_pblock_new (); + Slapi_Entry *e = slapi_entry_alloc (); + int rc; + char *attrs[] = {INSTANCE_ATTR, NULL}; + Slapi_Entry **entries; + char cl_dir [256]; + char *str = NULL; + + /* set changelog dn */ + slapi_entry_set_dn (e, slapi_ch_strdup (CL_DN)); + + /* set object classes */ + slapi_entry_add_string(e, "objectclass", "top"); + slapi_entry_add_string(e, "objectclass", "extensibleObject"); + + /* get directory instance dir */ + slapi_search_internal_set_pb (pb, "cn=config", LDAP_SCOPE_BASE, "objectclass=*", + attrs, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to get server instance " + "directory; LDAP error - %d\n", rc); + rc = -1; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + str = slapi_entry_attr_get_charptr(entries[0], INSTANCE_ATTR); + sprintf (cl_dir, "%s/%s", str, "cl5db"); + slapi_ch_free((void **)&str); + slapi_entry_add_string (e, CONFIG_CHANGELOG_DIR_ATTRIBUTE, cl_dir); + + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + + pb = slapi_pblock_new (); + + slapi_add_entry_internal_set_pb (pb, e, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + + slapi_add_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add changelog " + "configuration entry; LDAP error - %d\n", rc); + rc = -1; + } + else + rc = 0; + +done: + slapi_pblock_destroy (pb); + + return rc; +} + +/* Format: + dn: cn=replica,cn="o=NetscapeRoot",cn= mapping tree,cn=config + objectclass: top + objectclass: nsds5Replica + objectclass: extensibleObject + nsds5ReplicaRoot: o=NetscapeRoot + nsds5ReplicaId: 2 + nsds5flags: 1 + cn: replica + */ +static int configureReplica () +{ + Slapi_PBlock *pb = slapi_pblock_new (); + Slapi_Entry *e = slapi_entry_alloc (); + int rc; + char dn [128]; + + /* set changelog dn */ + sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, REPLICA_ROOT, + slapi_get_mapping_tree_config_root ()); + slapi_entry_set_dn (e, slapi_ch_strdup (dn)); + + /* set object classes */ + slapi_entry_add_string(e, "objectclass", "top"); + slapi_entry_add_string(e, "objectclass", REPLICA_OC); + slapi_entry_add_string(e, "objectclass", "extensibleObject"); + + /* set other attributes */ + slapi_entry_add_string (e, attr_replicaRoot, REPLICA_ROOT); + slapi_entry_add_string (e, attr_replicaId, "1"); + slapi_entry_add_string (e, attr_flags, "1"); + + slapi_add_entry_internal_set_pb (pb, e, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + + slapi_add_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add replica for (%s) " + "configuration entry; LDAP error - %d\n", REPLICA_ROOT, rc); + rc = -1; + } + else + rc = 0; + + slapi_pblock_destroy (pb); + + return rc; +} + +/* generates one of each ldap operations */ +static int populateChangelogOp () +{ + Slapi_PBlock *pb = slapi_pblock_new (); + Slapi_Entry *e = slapi_entry_alloc (); + int rc; + char dn [128], newrdn [64]; + LDAPMod *mods[2]; + Slapi_Mod smod; + struct berval bv; + time_t cur_time; + + /* add entry */ + cur_time = time(NULL); + sprintf (dn, "cn=%s,%s", ctime(&cur_time), REPLICA_ROOT); + slapi_entry_set_dn (e, slapi_ch_strdup (dn)); + slapi_entry_add_string(e, "objectclass", "top"); + slapi_entry_add_string(e, "objectclass", "extensibleObject"); + slapi_entry_add_string (e, "mail", "jsmith@netscape.com"); + + slapi_add_entry_internal_set_pb (pb, e, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_add_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + /* modify entry */ + pb = slapi_pblock_new (); + slapi_mod_init (&smod, 1); + slapi_mod_set_type (&smod, "mail"); + slapi_mod_set_operation (&smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + bv.bv_val = "jsmith@aol.com"; + bv.bv_len = strlen (bv.bv_val); + slapi_mod_add_value(&smod, &bv); + mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod); + mods[1] = NULL; + slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_mod_done (&smod); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to modify entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + /* rename entry */ + pb = slapi_pblock_new (); + cur_time = time (NULL); + sprintf (newrdn, "cn=renamed%s", ctime(&cur_time)); + slapi_rename_internal_set_pb (pb, dn, newrdn, NULL, 1, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modrdn_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to rename entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + /* delete the entry */ + pb = slapi_pblock_new (); + sprintf (dn, "%s,%s", newrdn, REPLICA_ROOT); + slapi_delete_internal_set_pb (pb, dn, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_delete_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to delete entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + return 0; +} diff --git a/ldap/servers/plugins/replication/cl5_test.h b/ldap/servers/plugins/replication/cl5_test.h new file mode 100644 index 00000000..57d8435c --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_test.h @@ -0,0 +1,21 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_test.h - changelog test cases */ + +typedef enum +{ + TEST_BASIC, /* open-close-delete, read-write-delete */ + TEST_BACKUP_RESTORE,/* test backup and recovery */ + TEST_ITERATION, /* similar to iteration used by replica upsate protocol */ + TEST_TRIMMING, /* test changelog trimming */ + TEST_PERFORMANCE, /* test read/write performance */ + TEST_PERFORMANCE_MT,/* test multithreaded performance */ + TEST_LDIF, /* test cl2ldif and ldif2cl */ + TEST_ALL /* collective test */ +} TestType; + +void testChangelog (TestType type); + diff --git a/ldap/servers/plugins/replication/csnpl.c b/ldap/servers/plugins/replication/csnpl.c new file mode 100644 index 00000000..7180af67 --- /dev/null +++ b/ldap/servers/plugins/replication/csnpl.c @@ -0,0 +1,328 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "csnpl.h" +#include "llist.h" +#include "repl_shared.h" + +struct csnpl +{ + LList* csnList; /* pending list */ + PRRWLock* csnLock; /* lock to serialize access to PL */ +}; + +typedef struct _csnpldata +{ + PRBool committed; /* True if CSN committed */ + CSN *csn; /* The actual CSN */ +} csnpldata; + +/* forward declarations */ +#ifdef DEBUG +static void _csnplDumpContentNoLock(CSNPL *csnpl, const char *caller); +#endif + +CSNPL* csnplNew () +{ + CSNPL *csnpl; + + csnpl = (CSNPL *)slapi_ch_malloc (sizeof (CSNPL)); + if (csnpl == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplNew: failed to allocate pending list\n"); + return NULL; + } + + csnpl->csnList = llistNew (); + if (csnpl->csnList == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplNew: failed to allocate pending list\n"); + slapi_ch_free ((void**)&csnpl); + return NULL; + } + + /* ONREPL: do locks need different names */ + csnpl->csnLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "pl_lock"); + + if (csnpl->csnLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplNew: failed to create lock; NSPR error - %d\n", + PR_GetError ()); + slapi_ch_free ((void**)&(csnpl->csnList)); + slapi_ch_free ((void**)&csnpl); + return NULL; + } + + return csnpl; +} + + +void +csnpldata_free(void **data) +{ + csnpldata **data_to_free = (csnpldata **)data; + if (NULL != data_to_free) + { + if (NULL != (*data_to_free)->csn) + { + csn_free(&(*data_to_free)->csn); + } + slapi_ch_free((void **)data_to_free); + } +} + +void csnplFree (CSNPL **csnpl) +{ + if ((csnpl == NULL) || (*csnpl == NULL)) + return; + + /* free all remaining nodes */ + llistDestroy (&((*csnpl)->csnList), (FNFree)csnpldata_free); + + if ((*csnpl)->csnLock); + PR_DestroyRWLock ((*csnpl)->csnLock); + + slapi_ch_free ((void**)csnpl); +} + +/* This function isnerts a CSN into the pending list + * Returns: 0 if the csn was successfully inserted + * 1 if the csn has already been seen + * -1 for any other kind of errors + */ +int csnplInsert (CSNPL *csnpl, const CSN *csn) +{ + int rc; + csnpldata *csnplnode; + char csn_str[CSN_STRSIZE]; + + if (csnpl == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplInsert: invalid argument\n"); + return -1; + } + + PR_RWLock_Wlock (csnpl->csnLock); + + /* check to see if this csn is larger than the last csn in the + pending list. It has to be if we have not seen it since + the csns are always added in the accending order. */ + csnplnode = llistGetTail (csnpl->csnList); + if (csnplnode && csn_compare (csnplnode->csn, csn) >= 0) + { + PR_RWLock_Unlock (csnpl->csnLock); + return 1; + } + + csnplnode = (csnpldata *)slapi_ch_malloc(sizeof(csnpldata)); + csnplnode->committed = PR_FALSE; + csnplnode->csn = csn_dup(csn); + csn_as_string(csn, PR_FALSE, csn_str); + rc = llistInsertTail (csnpl->csnList, csn_str, csnplnode); + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplInsert"); +#endif + + PR_RWLock_Unlock (csnpl->csnLock); + if (rc != 0) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "csnplInsert: failed to insert csn (%s) into pending list\n", csn_as_string(csn,PR_FALSE,s)); + return -1; + } + + return 0; +} + +int csnplRemove (CSNPL *csnpl, const CSN *csn) +{ + csnpldata *data; + char csn_str[CSN_STRSIZE]; + + if (csnpl == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplRemove: invalid argument\n"); + return -1; + } + + csn_as_string(csn, PR_FALSE, csn_str); + PR_RWLock_Wlock (csnpl->csnLock); + + data = (csnpldata *)llistRemove (csnpl->csnList, csn_str); + if (data == NULL) + { + PR_RWLock_Unlock (csnpl->csnLock); + return -1; + } + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplRemove"); +#endif + + csn_free(&data->csn); + slapi_ch_free((void **)&data); + + PR_RWLock_Unlock (csnpl->csnLock); + + return 0; +} + +int csnplCommit (CSNPL *csnpl, const CSN *csn) +{ + csnpldata *data; + char csn_str[CSN_STRSIZE]; + + if (csnpl == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplCommit: invalid argument\n"); + return -1; + } + csn_as_string(csn, PR_FALSE, csn_str); + + PR_RWLock_Wlock (csnpl->csnLock); + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplCommit"); +#endif + + data = (csnpldata*)llistGet (csnpl->csnList, csn_str); + if (data == NULL) + { + /* + * In the scenario "4.x master -> 6.x legacy-consumer -> 6.x consumer" + * csn will have rid=65535. Hence 6.x consumer will get here trying + * to commit r->min_csn_pl because its rid matches that in the csn. + * However, r->min_csn_pl is always empty for a dedicated consumer. + * Exclude READ-ONLY replica ID here from error logging. + */ + ReplicaId rid = csn_get_replicaid (csn); + if (rid < MAX_REPLICA_ID) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplCommit: can't find csn %s\n", csn_str); + } + PR_RWLock_Unlock (csnpl->csnLock); + return -1; + } + else + { + data->committed = PR_TRUE; + } + + PR_RWLock_Unlock (csnpl->csnLock); + + return 0; +} + + + +CSN* csnplGetMinCSN (CSNPL *csnpl, PRBool *committed) +{ + csnpldata *data; + CSN *csn = NULL; + PR_RWLock_Rlock (csnpl->csnLock); + if ((data = (csnpldata*)llistGetHead (csnpl->csnList)) != NULL) + { + csn = csn_dup(data->csn); + if (NULL != committed) + { + *committed = data->committed; + } + } + PR_RWLock_Unlock (csnpl->csnLock); + + return csn; +} + + +/* + * Roll up the list of pending CSNs, removing all of the CSNs at the + * head of the the list that are committed and contiguous. Returns + * the largest committed CSN, or NULL if no contiguous block of + * committed CSNs appears at the beginning of the list. The caller + * is responsible for freeing the CSN returned. + */ +CSN * +csnplRollUp(CSNPL *csnpl, CSN **first_commited) +{ + CSN *largest_committed_csn = NULL; + csnpldata *data; + PRBool freeit = PR_TRUE; + + PR_RWLock_Wlock (csnpl->csnLock); + if (first_commited) { + /* Avoid non-initialization issues due to careless callers */ + *first_commited = NULL; + } + data = (csnpldata *)llistGetHead(csnpl->csnList); + while (NULL != data && data->committed) + { + if (NULL != largest_committed_csn && freeit) + { + csn_free(&largest_committed_csn); + } + freeit = PR_TRUE; + largest_committed_csn = data->csn; /* Save it */ + if (first_commited && (*first_commited == NULL)) { + *first_commited = data->csn; + freeit = PR_FALSE; + } + data = (csnpldata*)llistRemoveHead (csnpl->csnList); + slapi_ch_free((void **)&data); + data = (csnpldata *)llistGetHead(csnpl->csnList); + } + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplRollUp"); +#endif + + PR_RWLock_Unlock (csnpl->csnLock); + return largest_committed_csn; +} + +#ifdef DEBUG +/* Dump current content of the list - for debugging */ +void +csnplDumpContent(CSNPL *csnpl, const char *caller) +{ + if (csnpl) + { + PR_RWLock_Rlock (csnpl->csnLock); + _csnplDumpContentNoLock (csnpl, caller); + PR_RWLock_Unlock (csnpl->csnLock); + } +} + +/* helper function */ +static void _csnplDumpContentNoLock(CSNPL *csnpl, const char *caller) +{ + csnpldata *data; + void *iterator; + char csn_str[CSN_STRSIZE]; + + data = (csnpldata *)llistGetFirst(csnpl->csnList, &iterator); + if (data) { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: CSN Pending list content:\n", + caller ? caller : ""); + } + while (data) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s, %s\n", + csn_as_string(data->csn, PR_FALSE, csn_str), + data->committed ? "committed" : "not committed"); + data = (csnpldata *)llistGetNext (csnpl->csnList, &iterator); + } +} +#endif + diff --git a/ldap/servers/plugins/replication/csnpl.h b/ldap/servers/plugins/replication/csnpl.h new file mode 100644 index 00000000..ae1b4c85 --- /dev/null +++ b/ldap/servers/plugins/replication/csnpl.h @@ -0,0 +1,23 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* csnpl.h - interface for csn pending list */ + +#ifndef CSNPL_H +#define CSNPL_H + +#include "slapi-private.h" + +typedef struct csnpl CSNPL; + +CSNPL* csnplNew (); +void csnplFree (CSNPL **csnpl); +int csnplInsert (CSNPL *csnpl, const CSN *csn); +int csnplRemove (CSNPL *csnpl, const CSN *csn); +CSN* csnplGetMinCSN (CSNPL *csnpl, PRBool *committed); +int csnplCommit (CSNPL *csnpl, const CSN *csn); +CSN *csnplRollUp(CSNPL *csnpl, CSN ** first); +void csnplDumpContent(CSNPL *csnpl, const char *caller); +#endif diff --git a/ldap/servers/plugins/replication/dllmain.c b/ldap/servers/plugins/replication/dllmain.c new file mode 100644 index 00000000..3f17b14c --- /dev/null +++ b/ldap/servers/plugins/replication/dllmain.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + /* + * Microsoft Windows specifics for LIBREPLICATION DLL + */ +#include "ldap.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/replication/legacy_consumer.c b/ldap/servers/plugins/replication/legacy_consumer.c new file mode 100644 index 00000000..8bf45ee1 --- /dev/null +++ b/ldap/servers/plugins/replication/legacy_consumer.c @@ -0,0 +1,707 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * repl_legacy_consumer.c - support for legacy replication (consumer-side) + * + * Support for legacy replication involves correctly dealing with + * the addition and removal of attribute types "copiedFrom" and + * "copyingFrom". The behavior is: + * 1) If a copiedFrom appears in an entry, and that entry is the root + * of a replicated area, then put the backend into "refer on update" + * mode and install a referral corresponding to the URL contained + * in the copiedFrom attribute. This referral overrides the mode + * of the replica, e.g. if it was previously an updateable replica, + * it now becomes read-only except for the updatedn. + * 2) If a copiedFrom disappears from an entry, or the entry containing + * the copiedFrom is removed, restore the backend to the state + * determined by the DS 5.0 replica configuration. + * 3) If a "copyingFrom" referral appears in an entry, and that entry + * is the root of a replicated area, then put the backend into + * "refer all operations" mode and install a referral corresponding + * to the URL contained in the copyingFrom attribute. This referral + * overrides the mode of the replica, e.g if it was previously an + * updateable replica, it now becomes read-only and refers all + * operations except for the updatedn. + * 4) If a copyingFrom disappears from an entry, or the entry containing + * the copyingFrom is removed, restore the backend to the state + * determined by the DS 5.0 replica configuration. + */ + + +#include "repl5.h" +#include "repl.h" + +/* Forward Declarations */ +static int legacy_consumer_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int legacy_consumer_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int legacy_consumer_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +static int legacy_consumer_extract_config(Slapi_Entry* entry, char *returntext); +static int legacy_consumer_read_config (); +static void legacy_consumer_encode_pw (Slapi_Entry *e); +static void set_legacy_purl (Slapi_PBlock *pb, const char *purl); +static int get_legacy_referral (Slapi_Entry *e, char **referral, char **state); + +#define LEGACY_CONSUMER_CONFIG_DN "cn=legacy consumer," REPL_CONFIG_TOP +#define LEGACY_CONSUMER_FILTER "(objectclass=*)" + +/* Configuration parameters local to this module */ +static Slapi_DN *legacy_consumer_replicationdn = NULL; +static char *legacy_consumer_replicationpw = NULL; +/* Lock which protects the above config parameters */ +PRRWLock *legacy_consumer_config_lock = NULL; + +static PRBool +target_is_a_replica_root(Slapi_PBlock *pb, const Slapi_DN **root) +{ + char *dn; + Slapi_DN *sdn; + PRBool return_value; + Object *repl_obj; + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + sdn = slapi_sdn_new_dn_byref(dn); + repl_obj = replica_get_replica_from_dn(sdn); + if (NULL != repl_obj) + { + Replica *r = object_get_data(repl_obj); + *root = replica_get_root(r); + return_value = PR_TRUE; + object_release(repl_obj); + } + else + { + *root = NULL; + return_value = PR_FALSE; + } + slapi_sdn_free(&sdn); + return return_value; +} + + + +static int +parse_cfstring(const char *cfstring, char **referral, char **generation, char **lastreplayed) +{ + int return_value = -1; + char *ref, *gen, *lastplayed; + + if (cfstring != NULL) + { + char *tmp; + char *cfcopy = slapi_ch_strdup(cfstring); + ref = cfcopy; + tmp = strchr(cfcopy, ' '); + if (NULL != tmp) + { + *tmp++ = '\0'; + while ('\0' != *tmp && ' ' == *tmp) tmp++; + gen = tmp; + tmp = strchr(gen, ' '); + if (NULL != tmp) + { + *tmp++ = '\0'; + while ('\0' != *tmp && ' ' == *tmp) tmp++; + lastplayed = tmp; + return_value = 0; + } + } + + if (return_value == 0) + { + if (referral) + *referral = slapi_ch_strdup(ref); + if (generation) + *generation = slapi_ch_strdup(gen); + if (lastreplayed) + *lastreplayed = slapi_ch_strdup(lastplayed); + } + slapi_ch_free((void **)&cfcopy); + } + return return_value; +} + + + +/* + * This is called from the consumer post-op plugin point. + * It's called if: + * 1) The operation is an add or modify operation, and a + * copiedfrom/copyingfrom was found in the entry/mods, or + * 2) the operation is a delete operation, or + * 3) the operation is a moddn operation. + */ + +void +process_legacy_cf(Slapi_PBlock *pb) +{ + consumer_operation_extension *opext; + Slapi_Operation *op; + char *referral_array[2] = {0}; + char *referral; + char *state; + int rc; + const Slapi_DN *replica_root_sdn = NULL; + Slapi_Entry *e; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, op); + + if (opext->has_cf) + { + PR_ASSERT (operation_get_type (op) == SLAPI_OPERATION_ADD || + operation_get_type (op) == SLAPI_OPERATION_MODIFY); + + if ((PR_FALSE == target_is_a_replica_root(pb, &replica_root_sdn)) || + (NULL == replica_root_sdn)){ + return; + } + + slapi_pblock_get (pb, SLAPI_ENTRY_POST_OP, &e); + PR_ASSERT (e); + + if (NULL == e) + return; + + rc = get_legacy_referral (e, &referral, &state); + if (rc == 0) + { + referral_array[0] = referral; + referral_array[1] = NULL; + repl_set_mtn_state_and_referrals(replica_root_sdn, state, NULL, NULL, + referral_array); + /* set partial url in the replica_object */ + set_legacy_purl (pb, referral); + + slapi_ch_free((void **)&referral); + } + + } +} + +void legacy_consumer_be_state_change (void *handle, char *be_name, + int old_be_state, int new_be_state) +{ + Object *r_obj; + Replica *r; + + /* we only interested when a backend is coming online */ + if (new_be_state == SLAPI_BE_STATE_ON) + { + r_obj = replica_get_for_backend (be_name); + if (r_obj) + { + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + if (replica_is_legacy_consumer (r)) + legacy_consumer_init_referrals (r); + + object_release (r_obj); + } + } +} + + +static int +dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg) +{ + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +int +legacy_consumer_config_init() +{ + /* The FE DSE *must* be initialised before we get here */ + int rc; + + if ((legacy_consumer_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "legacy_consumer_config_lock")) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Failed to create legacy_consumer config read-write lock\n"); + exit(1); + } + + rc = legacy_consumer_read_config (); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Failed to initialize legacy replication configuration\n"); + return 1; + } + + slapi_config_register_callback(SLAPI_OPERATION_ADD,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_add,NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_modify,NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,dont_allow_that,NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_delete,NULL); + + return 0; +} + +static int +legacy_consumer_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + int rc; + + rc = legacy_consumer_extract_config(e, returntext); + if (rc != LDAP_SUCCESS) + { + *returncode = rc; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Failed to configure legacy replication\n"); + return SLAPI_DSE_CALLBACK_ERROR; + } + /* make sure that the password is encoded */ + legacy_consumer_encode_pw(e); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "legacy_consumer_config_add: " + "successfully configured legacy consumer credentials\n"); + + return SLAPI_DSE_CALLBACK_OK; +} + +#define config_copy_strval( s ) s ? slapi_ch_strdup (s) : NULL; + +static int +legacy_consumer_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg) +{ + int rc= 0; + LDAPMod **mods; + int not_allowed = 0; + int i; + + if (returntext) + { + returntext[0] = '\0'; + } + *returncode = LDAP_SUCCESS; + + + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + PR_RWLock_Wlock (legacy_consumer_config_lock); + + for (i = 0; (mods[i] && (!not_allowed)); i++) + { + if (mods[i]->mod_op & LDAP_MOD_DELETE) + { + /* We don't support deleting an attribute from cn=config */ + } + else + { + int j; + for (j = 0; ((mods[i]->mod_values[j]) && (LDAP_SUCCESS == rc)); j++) + { + char *config_attr, *config_attr_value; + int mod_type; + config_attr = (char *) mods[i]->mod_type; + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + /* replace existing value */ + mod_type = mods[i]->mod_op & ~LDAP_MOD_BVALUES; + if ( strcasecmp (config_attr, CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE ) == 0 ) + { + if (legacy_consumer_replicationdn) + slapi_sdn_free (&legacy_consumer_replicationdn); + + if (mod_type == LDAP_MOD_REPLACE) + { + if (config_attr_value) + legacy_consumer_replicationdn = slapi_sdn_new_dn_byval (config_attr_value); + } + else if (mod_type == LDAP_MOD_DELETE) + { + legacy_consumer_replicationdn = NULL; + } + else if (mod_type == LDAP_MOD_ADD) + { + if (legacy_consumer_replicationdn != NULL) + { + not_allowed = 1; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Multiple replicationdns not permitted." ); + } + else + { + if (config_attr_value) + legacy_consumer_replicationdn = slapi_sdn_new_dn_byval (config_attr_value); + } + } + } + else if ( strcasecmp ( config_attr, CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE ) == 0 ) + { + if (mod_type == LDAP_MOD_REPLACE) + { + legacy_consumer_replicationpw = config_copy_strval(config_attr_value); + } + else if (mod_type == LDAP_MOD_DELETE) + { + legacy_consumer_replicationpw = NULL; + } + else if (mod_type == LDAP_MOD_ADD) + { + if (legacy_consumer_replicationpw != NULL) + { + not_allowed = 1; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Multiple replicationpws not permitted." ); + } + else + { + legacy_consumer_replicationpw = config_copy_strval(config_attr_value); + } + } + } + } + } + } + + PR_RWLock_Unlock (legacy_consumer_config_lock); + + + if (not_allowed) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Failed to modify legacy replication configuration\n" ); + *returncode= LDAP_CONSTRAINT_VIOLATION; + return SLAPI_DSE_CALLBACK_ERROR; + } + + /* make sure that the password is encoded */ + legacy_consumer_encode_pw (e); + + return SLAPI_DSE_CALLBACK_OK; +} + +static int +legacy_consumer_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + + PR_RWLock_Wlock (legacy_consumer_config_lock); + if (legacy_consumer_replicationdn) + slapi_sdn_free (&legacy_consumer_replicationdn); + if (legacy_consumer_replicationpw) + slapi_ch_free ((void**)&legacy_consumer_replicationpw); + + legacy_consumer_replicationdn = NULL; + legacy_consumer_replicationpw = NULL; + PR_RWLock_Unlock (legacy_consumer_config_lock); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +/* + * Given the changelog configuration entry, extract the configuration directives. + */ +static int +legacy_consumer_extract_config(Slapi_Entry* entry, char *returntext) +{ + int rc = LDAP_SUCCESS; /* OK */ + char *arg; + + PR_RWLock_Wlock (legacy_consumer_config_lock); + + arg= slapi_entry_attr_get_charptr(entry,CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE); + if (arg) + legacy_consumer_replicationdn = slapi_sdn_new_dn_passin (arg); + + arg= slapi_entry_attr_get_charptr(entry,CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE); + legacy_consumer_replicationpw = arg; + + PR_RWLock_Unlock (legacy_consumer_config_lock); + + return rc; +} + + + + +static int +legacy_consumer_read_config () +{ + int rc = LDAP_SUCCESS; + int scope= LDAP_SCOPE_BASE; + Slapi_PBlock *pb; + + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, LEGACY_CONSUMER_CONFIG_DN, scope, + "(objectclass=*)", NULL /*attrs*/, 0 /* attrs only */, + NULL /* controls */, NULL /* uniqueid */, + repl_get_plugin_identity(PLUGIN_LEGACY_REPLICATION), 0 /* actions */); + slapi_search_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + if ( LDAP_SUCCESS == rc ) + { + Slapi_Entry **entries = NULL; + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries ); + if ( NULL != entries && NULL != entries[0]) + { + /* Extract the config info from the changelog entry */ + rc = legacy_consumer_extract_config(entries[0], NULL); + } + } + else + { + rc = LDAP_SUCCESS; + } + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return rc; +} + + +int +legacy_consumer_is_replicationdn(char *dn) +{ + int return_value = 0; /* Assume not */ + + if (NULL != dn && '\0' != dn[0]) + { + if (NULL != legacy_consumer_replicationdn) + { + Slapi_DN *sdn = slapi_sdn_new_dn_byref (dn); + + if (slapi_sdn_compare (legacy_consumer_replicationdn, sdn) == 0) { + return_value = 1; + } + + slapi_sdn_free (&sdn); + } + } + return return_value; +} + + +int +legacy_consumer_is_replicationpw(struct berval *pwval) +{ + int return_value = 0; /* Assume not */ + + if (NULL != pwval && NULL != pwval->bv_val) + { + if (NULL != legacy_consumer_replicationpw && + '\0' != legacy_consumer_replicationpw[0]) { + struct berval *pwvals[2]; + struct berval config_pw; + + config_pw.bv_val = legacy_consumer_replicationpw; + config_pw.bv_len = strlen(legacy_consumer_replicationpw); + pwvals[0] = &config_pw; + pwvals[1] = NULL; + + return_value = slapi_pw_find(pwvals, pwval) == 0; + } + } + return return_value; +} + +static void +legacy_consumer_free_config () +{ + if (NULL != legacy_consumer_replicationdn) { + slapi_sdn_free(&legacy_consumer_replicationdn); + } + if (NULL != legacy_consumer_replicationpw) { + slapi_ch_free((void **) &legacy_consumer_replicationpw); + } +} + + + +static void +legacy_consumer_encode_pw (Slapi_Entry *e) +{ + char *updatepw = slapi_entry_attr_get_charptr(e, + CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE); + int is_encoded; + char *encoded_value = NULL; + + if (updatepw != NULL) + { + is_encoded = slapi_is_encoded (updatepw); + + if (!is_encoded) + { + encoded_value = slapi_encode (updatepw, "SHA"); + } + + if (encoded_value) + { + slapi_entry_attr_set_charptr(e, + CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE, encoded_value); + } + } +} + +static void +set_legacy_purl (Slapi_PBlock *pb, const char *purl) +{ + Object *r_obj; + Replica *r; + + r_obj = replica_get_replica_for_op (pb); + PR_ASSERT (r_obj); + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r && replica_is_legacy_consumer(r)); + + replica_set_legacy_purl (r, purl); + + object_release (r_obj); +} + +/* this function get referrals from an entry. + Returns 0 if successful + 1 if no referrals are present + -1 in case of error + */ +static int +get_legacy_referral (Slapi_Entry *e, char **referral, char **state) +{ + char* pat = "ldap://%s"; + const char *val = NULL; + char *hostport; + int rc = 1; + Slapi_Attr *attr; + const Slapi_Value *sval; + + PR_ASSERT (e && referral && state); + + /* Find any copiedFrom/copyingFrom attributes - + copyingFrom has priority */ + if (slapi_entry_attr_find(e, type_copyingFrom, &attr) == 0) + { + slapi_attr_first_value(attr, (Slapi_Value **)&sval); + val = slapi_value_get_string(sval); + *state = STATE_REFERRAL; + } + else if (slapi_entry_attr_find(e, type_copiedFrom, &attr) == 0) + { + slapi_attr_first_value(attr, (Slapi_Value **)&sval); + val = slapi_value_get_string(sval); + *state = STATE_UPDATE_REFERRAL; + } + + if (val) + { + rc = parse_cfstring(val, &hostport, NULL, NULL); + if (rc != 0) + { + const char *target_dn = slapi_entry_get_dn_const(e); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: a copiedFrom " + "or copyingFrom attribute was added to or removed from an " + "entry that is not the root of a replicated area. It is possible " + "that a legacy replication supplier is incorrectly configured " + "to supply updates to the subtree rooted at %s\n", + target_dn == NULL ? "null" : target_dn); + } + else + { + *referral = slapi_ch_malloc (strlen (pat) + strlen (hostport)); + + sprintf (*referral, pat, hostport); + + slapi_ch_free ((void**)&hostport); + } + } + else + { + rc = 1; /* no copiedFrom or copyingFrom int the entry */ + } + + return rc; +} + +/* this function is called during server startup or when replica's data + is reloaded. It sets up referrals in the mapping tree based on the + copiedFrom and copyingFrom attributes. It also sets up partial url in + the replica object used to update RUV. + Returns 0 if successful and -1 otherwise + + */ +int +legacy_consumer_init_referrals (Replica *r) +{ + Slapi_PBlock *pb; + const Slapi_DN *root_sdn; + const char *root_dn; + char *attrs[] = {"copiedFrom", "copyingFrom"}; + int rc; + Slapi_Entry **entries = NULL; + char *referral = NULL; + char *referral_array[2]; + char *state = NULL; + + PR_ASSERT (r); + + pb = slapi_pblock_new (); + PR_ASSERT (pb); + + root_sdn = replica_get_root(r); + PR_ASSERT (root_sdn); + + root_dn = slapi_sdn_get_ndn(root_sdn); + PR_ASSERT (root_dn); + + slapi_search_internal_set_pb (pb, root_dn, LDAP_SCOPE_BASE, "objectclass=*",attrs, + 0 /* attrsonly */, NULL /* controls */, + NULL /* uniqueid */, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), + 0 /* flags */); + + slapi_search_internal_pb (pb); + + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + if (rc == LDAP_REFERRAL) + { + /* We are in referral mode, probably because ORC failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "legacy_consumer_init_referrals " + "data for replica %s is in referral mode due to failed " + "initialization. Replica need to be reinitialized\n", + root_dn); + rc = 0; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "legacy_consumer_init_referrals " + "failed to obtain root entry for replica %s; LDAP error - %d\n", + root_dn, rc); + rc = -1; + } + + goto done; + } + + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + + PR_ASSERT (entries && entries[0]); + + rc = get_legacy_referral (entries[0], &referral, &state); + if (rc == 0) + { + referral_array[0] = referral; + referral_array[1] = NULL; + repl_set_mtn_state_and_referrals(root_sdn, state, NULL, NULL, referral_array); + + /* set purtial url in the replica_object */ + replica_set_legacy_purl (r, referral); + + slapi_ch_free((void **)&referral); + } + else if (rc == 1) /* no referrals - treat as success */ + { + rc = 0; + } + + slapi_free_search_results_internal (pb); + +done: + + slapi_pblock_destroy (pb); + return rc; +} + diff --git a/ldap/servers/plugins/replication/llist.c b/ldap/servers/plugins/replication/llist.c new file mode 100644 index 00000000..175ea48f --- /dev/null +++ b/ldap/servers/plugins/replication/llist.c @@ -0,0 +1,336 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* llist.c - single link list implementation */ + +#include <string.h> +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "llist.h" +#include "repl_shared.h" + +/* data structures */ + +/* link list node */ +typedef struct lnode +{ + char *key; + void *data; + struct lnode *next; +} LNode; + +/* This structure defines a one-way linked list with head and tail pointers. + The list contains a "dummy" head node which makes sure that every node + has a previous node. This allows to remove a node during iteration without + breaking the list */ +struct llist +{ + LNode *head; + LNode *tail; +}; + +/* forward declarations */ +static LNode* _llistNewNode (const char *key, void *data); +static void _llistDestroyNode (LNode **node, FNFree fnFree); + +LList* llistNew () +{ + LList *list = (LList*) slapi_ch_calloc (1, sizeof (LList)); + + /* allocate a special head node - it contains no data but just + fulfills the requirement that every node has a previous one. + This is used during iteration with removal */ + if (list) + { + list->head = (LNode*)slapi_ch_calloc (1, sizeof (LNode)); + if (list->head == NULL) + { + slapi_ch_free ((void**)&list); + } + } + + return list; +} + +void llistDestroy (LList **list, FNFree fnFree) +{ + LNode *node = NULL, *prev_node; + + if (list == NULL || *list == NULL) + return; + + if ((*list)->head) + node = (*list)->head->next; + + while (node) + { + prev_node = node; + node = node->next; + _llistDestroyNode (&prev_node, fnFree); + } + + slapi_ch_free ((void**)&((*list)->head)); + slapi_ch_free ((void**)list); +} + +void* llistGetFirst(LList *list, void **iterator) +{ + if (list == NULL || iterator == NULL || list->head == NULL || list->head->next == NULL) + { + /* empty list or error */ + return NULL; + } + + /* Iterator points to the previous element (so that we can remove current element + and still keep the list in tact. In case of the first element, iterator points + to the dummy head element */ + (*iterator) = list->head; + return list->head->next->data; +} + +void* llistGetNext (LList *list, void **iterator) +{ + LNode *node; + + if (list == NULL || list->head == NULL || iterator == NULL || *iterator == NULL) + { + /* end of the list or error */ + return NULL; + } + + /* Iterator points to the previous element (so that we can + remove current element and still keep list in tact. */ + node = *(LNode **)iterator; + node = node->next; + + (*iterator) = node; + + if (node && node->next) + return node->next->data; + else + return NULL; +} + +void* llistRemoveCurrentAndGetNext (LList *list, void **iterator) +{ + LNode *prevNode, *node; + + /* end of the list is reached or error occured */ + if (list == NULL || iterator == NULL || *iterator == NULL) + return NULL; + + /* Iterator points to the previous element (so that we can + remove current element and still keep list in tact. */ + prevNode = *(LNode **)iterator; + node = prevNode->next; + if (node) + { + prevNode->next = node->next; + _llistDestroyNode (&node, NULL); + node = prevNode->next; + if (node) + return node->data; + else + return NULL; + } + else + return NULL; +} + +void* llistGetHead (LList *list) +{ + if (list == NULL || list->head == NULL || list->head->next == NULL) + { + /* empty list or error */ + return NULL; + } + + return list->head->next->data; +} + +void* llistGetTail (LList *list) +{ + if (list == NULL || list->tail == NULL) + { + /* empty list or error */ + return NULL; + } + + return list->tail->data; +} + +void* llistGet (LList *list, const char* key) +{ + LNode *node; + + /* empty list or invalid input */ + if (list == NULL || list->head == NULL || list->head->next == NULL || key == NULL) + return NULL; + + node = list->head->next; + while (node) + { + if (node->key && strcmp (key, node->key) == 0) + { + return node->data; + } + + node = node->next; + } + + /* node with specified key is not found */ + return NULL; +} + +int llistInsertHead (LList *list, const char *key, void *data) +{ + LNode *node; + if (list == NULL || list->head == NULL || data == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: invalid argument\n"); + return -1; + } + + node = _llistNewNode (key, data); + if (node == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: failed to allocate list node\n"); + return -1; + } + + if (list->head->next == NULL) /* empty list */ + { + list->head->next = node; + list->tail = node; + } + else + { + node->next = list->head->next; + list->head->next = node; + } + + return 0; +} + +int llistInsertTail (LList *list, const char *key, void *data) +{ + LNode *node; + if (list == NULL || list->head == NULL || data == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: invalid argument\n"); + return -1; + } + + node = _llistNewNode (key, data); + if (node == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: failed to allocate list node\n"); + return -1; + } + + if (list->head->next == NULL) /* empty list */ + { + list->head->next = node; + list->tail = node; + } + else + { + list->tail->next = node; + list->tail = node; + } + + return 0; +} + +void* llistRemoveHead (LList *list) +{ + LNode *node; + void *data; + + if (list == NULL || list->head == NULL || list->head->next == NULL) + return NULL; + + node = list->head->next; + data = node->data; + + list->head->next = node->next; + + /* last element removed */ + if (list->head->next == NULL) + list->tail = NULL; + + _llistDestroyNode (&node, NULL); + + return data; +} + +void* llistRemove (LList *list, const char *key) +{ + LNode *node, *prev_node; + void *data; + + if (list == NULL || list->head == NULL || list->head->next == NULL || key == NULL) + return NULL; + + node = list->head->next; + prev_node = list->head; + while (node) + { + if (node->key && strcmp (key, node->key) == 0) + { + prev_node->next = node->next; + /* last element removed */ + if (node->next == NULL) + { + /* no more elements in the list */ + if (list->head->next == NULL) + { + list->tail = NULL; + } + else + { + list->tail = prev_node; + } + } + + data = node->data; + _llistDestroyNode (&node, NULL); + return data; + } + + prev_node = node; + node = node->next; + } + + /* node with specified key is not found */ + return NULL; +} + +static LNode* _llistNewNode (const char *key, void *data) +{ + LNode *node = (LNode*) slapi_ch_malloc (sizeof (LNode)); + if (node == NULL) + return NULL; + + if (key) + node->key = slapi_ch_strdup (key); + else + node->key = NULL; + + node->data = data; + node->next = NULL; + + return node; +} + +static void _llistDestroyNode (LNode **node, FNFree fnFree) +{ + if ((*node)->data && fnFree) + fnFree (&(*node)->data); + if ((*node)->key) + slapi_ch_free ((void**)&((*node)->key)); + + slapi_ch_free ((void**)node); +} diff --git a/ldap/servers/plugins/replication/llist.h b/ldap/servers/plugins/replication/llist.h new file mode 100644 index 00000000..3b196ef8 --- /dev/null +++ b/ldap/servers/plugins/replication/llist.h @@ -0,0 +1,26 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* llist.h - single link list interface */ + +#ifndef LLIST_H +#define LLIST_H +typedef struct llist LList; + +LList* llistNew (); +void llistDestroy (LList **list, FNFree fnFree); +void* llistGetFirst(LList *list, void **iterator); +void* llistGetNext (LList *list, void **iterator); +void* llistRemoveCurrentAndGetNext (LList *list, void **iterator); +void* llistGetHead (LList *list); +void* llistGetTail (LList *list); +void* llistGet (LList *list, const char* key); +int llistInsertHead (LList *list, const char *key, void *data); +int llistInsertTail (LList *list, const char *key, void *data); +void* llistRemoveHead (LList *list); +void* llistRemove (LList *list, const char *key); + +#endif + diff --git a/ldap/servers/plugins/replication/profile.c b/ldap/servers/plugins/replication/profile.c new file mode 100644 index 00000000..0a7de374 --- /dev/null +++ b/ldap/servers/plugins/replication/profile.c @@ -0,0 +1,42 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + + +/* module: provide an interface to the profile file */ + +static FILE *profile_fd=NULL; + +/* JCMREPL - Could build up in an AVL tree and dump out to disk at the end... */ + +void profile_log(char *file,int line) +{ + if (profile_fd==NULL) + slapi_log_error(,"profile_log: profile file not open."); + else + { + /* JCMREPL - Probably need a lock around here */ + fprintf(profile_fd,"%s %d\n",file,line); + } +} + +void profile_open() +{ + char filename[MAX_FILENAME]; + strncpy(filename,CFG_rootpath,MAX_FILENAME); + strcat(filename,CFG_profilefile); + profile_fd= textfile_open(filename,"a"); +} + +void profile_close() +{ + if (profile_fd==NULL) + slapi_log_error(,"profile_close: profile file not open."); + else + textfile_close(profile_fd); +} diff --git a/ldap/servers/plugins/replication/repl.h b/ldap/servers/plugins/replication/repl.h new file mode 100644 index 00000000..8e502816 --- /dev/null +++ b/ldap/servers/plugins/replication/repl.h @@ -0,0 +1,366 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _REPL_H_ +#define _REPL_H_ + +#include <limits.h> +#include <time.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <sys/param.h> +#endif /* _WIN32 */ + +#include "portable.h" /* GGOODREPL - is this cheating? */ +#include "ldaplog.h" +#include "repl_shared.h" +#include "cl4.h" + +typedef struct schedule_item +{ + unsigned long sch_start; /* seconds after midnight */ + unsigned long sch_length; /* sec */ + unsigned int sch_weekdays; /* bit mask; LSB = Sunday */ + struct schedule_item* sch_next; +} schedule_item; + +/* XXXggood - copied from slap.h - bad */ +#if defined( XP_WIN32 ) +#define NO_TIME (time_t)0 /* cannot be -1, NT's localtime( -1 ) returns NULL */ +#else +#define NO_TIME (time_t)-1 /* a value that time() does not return */ +#endif + +/* + * A status message contains a time, the textual message, + * and a count of the number of times the message occured. + */ +typedef struct _status_message { + time_t sm_time; + char *sm_message; + int sm_occurances; +} status_message; + +/* + * A status_message_list is a circular array of status messages. + * Old messages roll off the end and are discarded. + */ +typedef struct _status_message_list { + int sml_size; /* number of slots in array */ + int sml_tail; /* next slot to be written */ + status_message *sml_messages; /* array of messages */ +} sm_list; +#define NUM_REPL_MESSAGES 20 /* max # of messages to save */ + +/* Selective attribute Inclusion states. ORDERING IS SIGNIFICANT */ +#define IMPLICITLY_INCLUDED 1 +#define IMPLICITLY_EXCLUDED 2 +#define EXPLICITLY_EXCLUDED 3 +#define EXPLICITLY_INCLUDED 4 + +#if defined(__JCMREPL_FILTER__) +/* + * Structure used to implement selective attribute filtering. + * sa_filter nodes are arranged in a linked list. + */ +typedef struct _sa_filter { + Slapi_Filter *sa_filter; /* Filter to apply */ + int sa_isexclude; /* non-zero if list is exclude list */ + char **sa_attrlist; /* array - attrs to replicate */ + struct _sa_filter *sa_next; /* Link to next struct */ +} sa_filter; +#endif + +typedef unsigned long changeNumber; +#define a2changeNumber( a ) strtoul(( a ), (char **)NULL, 10 ) + +#define AUTH_SIMPLE 1 +#define AUTH_KERBEROS 2 + +typedef struct modinfo { + char *type; + char *value; + int len; +} modinfo; + +/* + * Representation of one change entry from the replog file. + */ +typedef struct repl { + char *time; /* time of modification */ + changeNumber change; /* number of this change */ + char *dn; /* dn of entry being modified - normalized */ + char *raw_dn; /* dn of entry - not normalized */ + int changetype; /* type of change */ + modinfo *mods; /* modifications to make */ + char *newrdn; /* new rdn for modrdn */ + int deleteoldrdn; /* flag for modrdn */ + +} repl; + +#define BIND_OK 0 +#define BIND_ERR_BADLDP 1 +#define BIND_ERR_OPEN 2 +#define BIND_ERR_BAD_ATYPE 3 +#define BIND_ERR_SIMPLE_FAILED 4 +#define BIND_ERR_KERBEROS_FAILED 5 +#define BIND_ERR_SSL_INIT_FAILED 6 +#define BIND_ERR_RACE 7 + +#define MAX_CHANGENUMBER ULONG_MAX + +#define REPLICATION_SUBSYSTEM "replication" +#define REPL_LDAP_TIMEOUT 30L /* Wait 30 seconds for responses */ + +/* Update the copiedFrom attribute every <n> updates */ +#define UPDATE_COPIEDFROM_INTERVAL 10 +#define REPL_ERROR_REPL_HALTED "REPLICATION HALTED" +#define ATTR_NETSCAPEMDSUFFIX "netscapemdsuffix" + +#define CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE "nsslapd-legacy-updatedn" +#define CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE "nsslapd-legacy-updatepw" + +#define LDAP_CONTROL_REPL_MODRDN_EXTRAMODS "2.16.840.1.113730.3.4.999" + +/* Operation types */ +#define OP_MODIFY 1 +#define OP_ADD 2 +#define OP_DELETE 3 +#define OP_MODDN 4 +#define OP_SEARCH 5 +#define OP_COMPARE 6 + +/* 4.0-style housekeeping interval */ +#define REPLICATION_HOUSEKEEPING_INTERVAL (30 * 1000) /* 30 seconds */ + +/* Top of tree for replication configuration information */ +#define REPL_CONFIG_TOP "cn=replication,cn=config" + +/* Functions */ + +/* repl_rootdse.c */ +int repl_rootdse_init(); + +/* In repl.c */ +Slapi_Entry *get_changerecord(const chglog4Info *cl4, changeNumber cnum, int *err); +changeNumber replog_get_firstchangenum(const chglog4Info *cl4, int *err); +changeNumber replog_get_lastchangenum(const chglog4Info *cl4, int *err); +void changelog_housekeeping(time_t cur_time ); + +/* In repl_config.c */ +int repl_config_init (); + +/* Legacy Plugin Functions */ + +int legacy_preop_bind( Slapi_PBlock *pb ); +int legacy_bepreop_bind( Slapi_PBlock *pb ); +int legacy_postop_bind( Slapi_PBlock *pb ); +int legacy_preop_add( Slapi_PBlock *pb ); +int legacy_bepreop_add( Slapi_PBlock *pb ); +int legacy_postop_add( Slapi_PBlock *pb ); +int legacy_preop_modify( Slapi_PBlock *pb ); +int legacy_bepreop_modify( Slapi_PBlock *pb ); +int legacy_postop_modify( Slapi_PBlock *pb ); +int legacy_preop_modrdn( Slapi_PBlock *pb ); +int legacy_bepreop_modrdn( Slapi_PBlock *pb ); +int legacy_postop_modrdn( Slapi_PBlock *pb ); +int legacy_preop_delete( Slapi_PBlock *pb ); +int legacy_bepreop_delete( Slapi_PBlock *pb ); +int legacy_postop_delete( Slapi_PBlock *pb ); +int legacy_preop_search( Slapi_PBlock *pb ); +int legacy_preop_compare( Slapi_PBlock *pb ); +int legacy_pre_entry( Slapi_PBlock *pb ); +int legacy_bepostop_assignchangenum( Slapi_PBlock *pb ); + +int replication_plugin_start( Slapi_PBlock *pb ); +int replication_plugin_poststart( Slapi_PBlock *pb ); +int replication_plugin_stop( Slapi_PBlock *pb ); + +/* In repl.c */ +void replog( Slapi_PBlock *pb, int optype ); +void init_changelog_trimming( changeNumber max_changes, time_t max_age ); + +/* From repl_globals.c */ + +extern char *attr_changenumber; +extern char *attr_targetdn; +extern char *attr_changetype; +extern char *attr_newrdn; +extern char *attr_deleteoldrdn; +extern char *attr_changes; +extern char *attr_newsuperior; +extern char *attr_changetime; +extern char *attr_dataversion; +extern char *attr_csn; + +extern char *changetype_add; +extern char *changetype_delete; +extern char *changetype_modify; +extern char *changetype_modrdn; +extern char *changetype_moddn; + +extern char *type_copyingFrom; +extern char *type_copiedFrom; +extern char *filter_copyingFrom; +extern char *filter_copiedFrom; +extern char *filter_objectclass; + +extern char *type_cn; +extern char *type_objectclass; + +/* JCMREPL - IFP should be defined centrally */ + +#ifndef _IFP +#define _IFP +typedef int (*IFP)(); +#endif + +/* In cl4.c */ + +changeNumber ldapi_assign_changenumber(chglog4Info *cl4); +changeNumber ldapi_get_last_changenumber(chglog4Info *cl4); +changeNumber ldapi_get_first_changenumber(chglog4Info *cl4); +void ldapi_commit_changenumber(chglog4Info *cl4, changeNumber cnum); +void ldapi_set_first_changenumber(chglog4Info *cl4, changeNumber cnum); +void ldapi_set_last_changenumber(chglog4Info *cl4, changeNumber cnum); +void ldapi_initialize_changenumbers(chglog4Info *cl4, changeNumber first, changeNumber last); + +#define LDBM_TYPE "ldbm" +#define CHANGELOG_LDBM_TYPE "changelog-ldbm" + +#define MAX_RETRY_INTERVAL 3600 /* sec = 1 hour */ + +#define REPL_PROTOCOL_UNKNOWN 0 +#define REPL_PROTOCOL_40 1 +#define REPL_PROTOCOL_50_INCREMENTAL 2 +#define REPL_PROTOCOL_50_TOTALUPDATE 3 + +/* In repl_globals.c */ +int decrement_repl_active_threads(); +int increment_repl_active_threads(); + +/* operation extensions */ + +/* Type of extensions that can be registered */ +typedef enum +{ + REPL_SUP_EXT_OP, /* extension for Operation object, replication supplier */ + REPL_SUP_EXT_CONN, /* extension for Connection object, replication supplier */ + REPL_CON_EXT_OP, /* extension for Operation object, replication consumer */ + REPL_CON_EXT_CONN, /* extension for Connection object, replication consumer */ + REPL_CON_EXT_MTNODE,/* extension for mapping_tree_node object, replication consumer */ + REPL_EXT_ALL +} ext_type; + +/* general extension functions - repl_ext.c */ +void repl_sup_init_ext (); /* initializes registrations - must be called first */ +void repl_con_init_ext (); /* initializes registrations - must be called first */ +int repl_sup_register_ext (ext_type type); /* registers an extension of the specified type */ +int repl_con_register_ext (ext_type type); /* registers an extension of the specified type */ +void* repl_sup_get_ext (ext_type type, void *object); /* retireves the extension from the object */ +void* repl_con_get_ext (ext_type type, void *object); /* retireves the extension from the object */ + +/* Operation extension functions - supplier_operation_extension.c */ + +/* --- supplier operation extension --- */ +typedef struct supplier_operation_extension +{ + int prevent_recursive_call; + struct slapi_operation_parameters *operation_parameters; + char *repl_gen; +} supplier_operation_extension; + +/* extension construct/destructor */ +void* supplier_operation_extension_constructor (void *object, void *parent); +void supplier_operation_extension_destructor (void* ext,void *object, void *parent); + +/* --- consumer operation extension --- */ +typedef struct consumer_operation_extension +{ + int has_cf; /* non-zero if the operation contains a copiedFrom/copyingFrom attr */ + void *search_referrals; +} consumer_operation_extension; + +/* extension construct/destructor */ +void* consumer_operation_extension_constructor (void *object, void *parent); +void consumer_operation_extension_destructor (void* ext,void *object, void *parent); + +/* Connection extension functions - repl_connext.c */ + +/* --- connection extension --- */ +/* ONREPL - some pointers are void* because they represent 5.0 data structures + not known in this header. Fix */ +typedef struct consumer_connection_extension +{ + int is_legacy_replication_dn; + int repl_protocol_version; /* the replication protocol version number the supplier is talking. */ + void *replica_acquired; /* Object* for replica */ + void *supplier_ruv; /* RUV* */ + int isreplicationsession; + Slapi_Connection *connection; +} consumer_connection_extension; + +/* extension construct/destructor */ +void* consumer_connection_extension_constructor (void *object,void *parent); +void consumer_connection_extension_destructor (void* ext,void *object,void *parent); + +/* mapping tree extension - stores replica object */ +typedef struct multimaster_mtnode_extension +{ + Object *replica; +} multimaster_mtnode_extension; +void* multimaster_mtnode_extension_constructor (void *object,void *parent); +void multimaster_mtnode_extension_destructor (void* ext,void *object,void *parent); + +/* In repl_init.c */ + +int get_legacy_stop(); + +/* In repl_entry.c */ +void repl_entry_init(int argc, char** argv); + +/* In repl_ops.c */ +int legacy_preop( Slapi_PBlock *pb, const char* caller, int operation_type); +int legacy_postop( Slapi_PBlock *pb, const char* caller, int operation_type); + +/* In profile.c */ + +#ifdef PROFILE +#define PROFILE_POINT if (CFG_profile) profile_log(__FILE__,__LINE__) /* JCMREPL - Where is the profiling flag stored? */ +#else +#define PROFILE_POINT ((void)0) +#endif + +void profile_log(char *file,int line); +void profile_open(); +void profile_close(); + +/* in repl_controls.c */ +void add_repl_control_mods( Slapi_PBlock *pb, Slapi_Mods *smods ); + +/* ... */ +void create_entity (char* DN, const char* oclass); + +void write_replog_db( int optype, char *dn, void *change, int flag, changeNumber changenum, time_t curtime, LDAPMod **modrdn_mods ); +int entry2reple( Slapi_Entry *e, Slapi_Entry *oe ); +int mods2reple( Slapi_Entry *e, LDAPMod **ldm ); +int modrdn2reple( Slapi_Entry *e, char *newrdn, int deloldrdn, LDAPMod **ldm ); + +/* In legacy_consumer.c */ +void process_legacy_cf(Slapi_PBlock *pb); +int legacy_consumer_is_replicationdn(char *dn); +int legacy_consumer_is_replicationpw(struct berval *creds); +int legacy_consumer_config_init(); + +/* function that gets called when a backend state is changed */ +void legacy_consumer_be_state_change (void *handle, char *be_name, + int old_be_state, int new_be_state); + +#endif /* _REPL_H_ */ + + + diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h new file mode 100644 index 00000000..d936cbea --- /dev/null +++ b/ldap/servers/plugins/replication/repl5.h @@ -0,0 +1,480 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5.h - 5.0 replication header */ + +#ifndef _REPL5_H_ +#define _REPL5_H_ + +#include <limits.h> +#include <time.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <sys/param.h> +#endif /* _WIN32 */ + +#include "portable.h" /* GGOODREPL - is this cheating? */ +#include "repl_shared.h" +#include "llist.h" +#include "repl5_ruv.h" +#include "cl4.h" + +/* DS 5.0 replication protocol OIDs */ +#define REPL_START_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.3" +#define REPL_END_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.5" +#define REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID "2.16.840.1.113730.3.5.6" +#define REPL_NSDS50_REPLICATION_RESPONSE_OID "2.16.840.1.113730.3.5.4" +#define REPL_NSDS50_UPDATE_INFO_CONTROL_OID "2.16.840.1.113730.3.4.13" +#define REPL_NSDS50_INCREMENTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.1" +#define REPL_NSDS50_TOTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.2" + +/* DS 5.0 replication protocol error codes */ +#define NSDS50_REPL_REPLICA_READY 0x00 /* Replica ready, go ahead */ +#define NSDS50_REPL_REPLICA_BUSY 0x01 /* Replica busy, try later */ +#define NSDS50_REPL_EXCESSIVE_CLOCK_SKEW 0x02 /* Supplier clock too far ahead */ +#define NSDS50_REPL_PERMISSION_DENIED 0x03 /* Bind DN not allowed to send updates */ +#define NSDS50_REPL_DECODING_ERROR 0x04 /* Consumer couldn't decode extended operation */ +#define NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL 0x05 /* Consumer doesn't understand suplier's update protocol */ +#define NSDS50_REPL_NO_SUCH_REPLICA 0x06 /* Consumer holds no such replica */ +#define NSDS50_REPL_BELOW_PURGEPOINT 0x07 /* Supplier provided a CSN below the consumer's purge point */ +#define NSDS50_REPL_INTERNAL_ERROR 0x08 /* Something bad happened on consumer */ +#define NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED 0x09 /* Replica released successfully */ +#define NSDS50_REPL_LEGACY_CONSUMER 0x0A /* replica is a legacy consumer */ +#define NSDS50_REPL_REPLICAID_ERROR 0x0B /* replicaID doesn't seem to be unique */ +#define NSDS50_REPL_DISABLED 0x0C /* replica suffix is disabled */ +#define NSDS50_REPL_UPTODATE 0x0D /* replica is uptodate */ +#define NSDS50_REPL_REPLICA_NO_RESPONSE 0xff /* No response received */ + +/* Protocol status */ +#define PROTOCOL_STATUS_UNKNOWN 701 +#define PROTOCOL_STATUS_INCREMENTAL_AWAITING_CHANGES 702 +#define PROTOCOL_STATUS_INCREMENTAL_ACQUIRING_REPLICA 703 +#define PROTOCOL_STATUS_INCREMENTAL_RELEASING_REPLICA 704 +#define PROTOCOL_STATUS_INCREMENTAL_SENDING_UPDATES 705 +#define PROTOCOL_STATUS_INCREMENTAL_BACKING_OFF 706 +#define PROTOCOL_STATUS_INCREMENTAL_NEEDS_TOTAL_UPDATE 707 +#define PROTOCOL_STATUS_INCREMENTAL_FATAL_ERROR 708 +#define PROTOCOL_STATUS_TOTAL_ACQUIRING_REPLICA 709 +#define PROTOCOL_STATUS_TOTAL_RELEASING_REPLICA 710 +#define PROTOCOL_STATUS_TOTAL_SENDING_DATA 711 + +/* To Allow Consumer Initialisation when adding an agreement - */ +#define STATE_PERFORMING_TOTAL_UPDATE 501 +#define STATE_PERFORMING_INCREMENTAL_UPDATE 502 + +#define MAX_NUM_OF_MASTERS 64 +#define REPL_SESSION_ID_SIZE 64 + +/* Attribute names for replication agreement attributes */ +extern const char *type_nsds5ReplicaHost; +extern const char *type_nsds5ReplicaPort; +extern const char *type_nsds5TransportInfo; +extern const char *type_nsds5ReplicaBindDN; +extern const char *type_nsds5ReplicaCredentials; +extern const char *type_nsds5ReplicaBindMethod; +extern const char *type_nsds5ReplicaRoot; +extern const char *type_nsds5ReplicatedAttributeList; +extern const char *type_nsds5ReplicaUpdateSchedule; +extern const char *type_nsds5ReplicaInitialize; +extern const char *type_nsds5ReplicaTimeout; +extern const char *type_nsds5ReplicaBusyWaitTime; +extern const char *type_nsds5ReplicaSessionPauseTime; + +/* To Allow Consumer Initialisation when adding an agreement - */ +extern const char *type_nsds5BeginReplicaRefresh; + +/* replica related attributes */ +extern const char *attr_replicaId; +extern const char *attr_replicaRoot; +extern const char *attr_replicaType; +extern const char *attr_replicaBindDn; +extern const char *attr_state; +extern const char *attr_flags; +extern const char *attr_replicaName; +extern const char *attr_replicaReferral; +extern const char *type_ruvElement; +extern const char *type_replicaPurgeDelay; +extern const char *type_replicaChangeCount; +extern const char *type_replicaTombstonePurgeInterval; +extern const char *type_replicaLegacyConsumer; +extern const char *type_ruvElementUpdatetime; + +/* multimaster plugin points */ +int multimaster_preop_bind (Slapi_PBlock *pb); +int multimaster_preop_add (Slapi_PBlock *pb); +int multimaster_preop_delete (Slapi_PBlock *pb); +int multimaster_preop_modify (Slapi_PBlock *pb); +int multimaster_preop_modrdn (Slapi_PBlock *pb); +int multimaster_preop_search (Slapi_PBlock *pb); +int multimaster_preop_compare (Slapi_PBlock *pb); +int multimaster_bepreop_add (Slapi_PBlock *pb); +int multimaster_bepreop_delete (Slapi_PBlock *pb); +int multimaster_bepreop_modify (Slapi_PBlock *pb); +int multimaster_bepreop_modrdn (Slapi_PBlock *pb); +int multimaster_bepostop_modrdn (Slapi_PBlock *pb); +int multimaster_bepostop_delete (Slapi_PBlock *pb); +int multimaster_postop_bind (Slapi_PBlock *pb); +int multimaster_postop_add (Slapi_PBlock *pb); +int multimaster_postop_delete (Slapi_PBlock *pb); +int multimaster_postop_modify (Slapi_PBlock *pb); +int multimaster_postop_modrdn (Slapi_PBlock *pb); + +/* In repl5_init.c */ +char* get_thread_private_agmtname (); +void set_thread_private_agmtname (const char *agmtname); +void* get_thread_private_cache (); +void set_thread_private_cache (void *buf); +char* get_repl_session_id (Slapi_PBlock *pb, char *id, CSN **opcsn); + +/* In repl_extop.c */ +int multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb); +int multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb); +int extop_noop(Slapi_PBlock *pb); +struct berval *NSDS50StartReplicationRequest_new(const char *protocol_oid, + const char *repl_root, char **extra_referrals, CSN *csn); +struct berval *NSDS50EndReplicationRequest_new(char *repl_root); +int decode_repl_ext_response(struct berval *data, int *response_code, + struct berval ***ruv_bervals); + +/* In repl5_total.c */ +int multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb); + +/* In repl_controls.c */ +int create_NSDS50ReplUpdateInfoControl(const char *uuid, + const char *superior_uuid, const CSN *csn, + LDAPMod **modify_mods, LDAPControl **ctrlp); +void destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp); +int decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp, + char **uuid, char **newsuperior_uuid, CSN **csn, LDAPMod ***modrdn_mods); + +/* In repl5_replsupplier.c */ +typedef struct repl_supplier Repl_Supplier; +Repl_Supplier *replsupplier_init(Slapi_Entry *e); +void replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb); +void replsupplier_start(Repl_Supplier *rs); +void replsupplier_stop(Repl_Supplier *rs); +void replsupplier_destroy(Repl_Supplier **rs); +void replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask); +PRUint32 replsupplier_get_status(Repl_Supplier *rs); + +/* In repl5_plugins.c */ +int multimaster_set_local_purl(); +const char *multimaster_get_local_purl(); +PRBool multimaster_started(); + +/* In repl5_schedule.c */ +typedef struct schedule Schedule; +typedef void (*window_state_change_callback)(void *arg, PRBool opened); +Schedule *schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id); +void schedule_destroy(Schedule *s); +int schedule_set(Schedule *sch, Slapi_Attr *attr); +char **schedule_get(Schedule *sch); +int schedule_in_window_now(Schedule *sch); +PRTime schedule_next(Schedule *sch); +int schedule_notify(Schedule *sch, Slapi_PBlock *pb); +void schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule); +void schedule_set_startup_delay(Schedule *sch, size_t startup_delay); +void schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog); +void schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int flags); +#define REPLICATION_SESSION_SUCCESS 0 + +/* In repl5_bos.c */ +typedef struct repl_bos Repl_Bos; + +/* In repl5_agmt.c */ +typedef struct repl5agmt Repl_Agmt; +#define TRANSPORT_FLAG_SSL 1 +#define TRANSPORT_FLAG_TLS 2 +#define BINDMETHOD_SIMPLE_AUTH 1 +#define BINDMETHOD_SSL_CLIENTAUTH 2 +Repl_Agmt *agmt_new_from_entry(Slapi_Entry *e); +Repl_Agmt *agmt_new_from_pblock(Slapi_PBlock *pb); +void agmt_delete(void **ra); +const Slapi_DN *agmt_get_dn_byref(const Repl_Agmt *ra); +int agmt_get_auto_initialize(const Repl_Agmt *ra); +long agmt_get_timeout(const Repl_Agmt *ra); +long agmt_get_busywaittime(const Repl_Agmt *ra); +long agmt_get_pausetime(const Repl_Agmt *ra); +int agmt_start(Repl_Agmt *ra); +int agmt_stop(Repl_Agmt *ra); +int agmt_replicate_now(Repl_Agmt *ra); +char *agmt_get_hostname(const Repl_Agmt *ra); +int agmt_get_port(const Repl_Agmt *ra); +PRUint32 agmt_get_transport_flags(const Repl_Agmt *ra); +char *agmt_get_binddn(const Repl_Agmt *ra); +struct berval *agmt_get_credentials(const Repl_Agmt *ra); +int agmt_get_bindmethod(const Repl_Agmt *ra); +Slapi_DN *agmt_get_replarea(const Repl_Agmt *ra); +int agmt_is_fractional(const Repl_Agmt *ra); +int agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname); +int agmt_is_50_mm_protocol(const Repl_Agmt *ra); +int agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name); +int agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name); +int agmt_schedule_in_window_now(const Repl_Agmt *ra); +int agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_timeout_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_busywaittime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_pausetime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_credentials_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_binddn_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_bind_method_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_transportinfo_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +const char *agmt_get_long_name(const Repl_Agmt *ra); +int agmt_initialize_replica(const Repl_Agmt *agmt); +void agmt_replica_init_done (const Repl_Agmt *agmt); +void agmt_notify_change(Repl_Agmt *ra, Slapi_PBlock *pb); +Object* agmt_get_consumer_ruv (Repl_Agmt *ra); +ReplicaId agmt_get_consumer_rid ( Repl_Agmt *ra, void *conn ); +int agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv); +void agmt_update_consumer_ruv (Repl_Agmt *ra); +CSN* agmt_get_consumer_schema_csn (Repl_Agmt *ra); +void agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn); +void agmt_set_last_update_in_progress (Repl_Agmt *ra, PRBool in_progress); +void agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time); +void agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time); +void agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg); +void agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress); +void agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time); +void agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time); +void agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg); +void agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped); +void agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize); + +typedef struct replica Replica; + +/* In repl5_agmtlist.c */ +int agmtlist_config_init(); +void agmtlist_shutdown(); +void agmtlist_notify_all(Slapi_PBlock *pb); +Object* agmtlist_get_first_agreement_for_replica (Replica *r); +Object* agmtlist_get_next_agreement_for_replica (Replica *r, Object *prev); + + +/* In repl5_backoff.c */ +typedef struct backoff_timer Backoff_Timer; +#define BACKOFF_FIXED 1 +#define BACKOFF_EXPONENTIAL 2 +#define BACKOFF_RANDOM 3 +Backoff_Timer *backoff_new(int timer_type, int initial_interval, int max_interval); +time_t backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data); +time_t backoff_step(Backoff_Timer *bt); +int backoff_expired(Backoff_Timer *bt, int margin); +void backoff_delete(Backoff_Timer **btp); + +/* In repl5_connection.c */ +typedef struct repl_connection Repl_Connection; +typedef enum +{ + CONN_OPERATION_SUCCESS, + CONN_OPERATION_FAILED, + CONN_NOT_CONNECTED, + CONN_SUPPORTS_DS5_REPL, + CONN_DOES_NOT_SUPPORT_DS5_REPL, + CONN_SCHEMA_UPDATED, + CONN_SCHEMA_NO_UPDATE_NEEDED, + CONN_LOCAL_ERROR, + CONN_BUSY, + CONN_SSL_NOT_ENABLED, + CONN_TIMEOUT +} ConnResult; +Repl_Connection *conn_new(Repl_Agmt *agmt); +ConnResult conn_connect(Repl_Connection *conn); +void conn_disconnect(Repl_Connection *conn); +void conn_delete(Repl_Connection *conn); +void conn_get_error(Repl_Connection *conn, int *operation, int *error); +ConnResult conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_delete(Repl_Connection *conn, const char *dn, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_rename(Repl_Connection *conn, const char *dn, + const char *newrdn, const char *newparent, int deleteoldrdn, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid, + struct berval *payload, char **retoidp, struct berval **retdatap, + LDAPControl *update_control, LDAPControl ***returned_controls); +const char *conn_get_status(Repl_Connection *conn); +void conn_start_linger(Repl_Connection *conn); +void conn_cancel_linger(Repl_Connection *conn); +ConnResult conn_replica_supports_ds5_repl(Repl_Connection *conn); +ConnResult conn_read_entry_attribute(Repl_Connection *conn, const char *dn, char *type, + struct berval ***returned_bvals); +ConnResult conn_push_schema(Repl_Connection *conn, CSN **remotecsn); +void conn_set_timeout(Repl_Connection *conn, long timeout); +void conn_set_agmt_changed(Repl_Connection *conn); + +/* In repl5_protocol.c */ +typedef struct repl_protocol Repl_Protocol; +Repl_Protocol *prot_new(Repl_Agmt *agmt, int protocol_state); +void prot_start(Repl_Protocol *rp); +Repl_Agmt *prot_get_agreement(Repl_Protocol *rp); +/* initiate total protocol */ +void prot_initialize_replica(Repl_Protocol *rp); +/* stop protocol session in progress */ +void prot_stop(Repl_Protocol *rp); +void prot_delete(Repl_Protocol **rpp); +void prot_free(Repl_Protocol **rpp); +PRBool prot_set_active_protocol (Repl_Protocol *rp, PRBool total); +void prot_clear_active_protocol (Repl_Protocol *rp); +Repl_Connection *prot_get_connection(Repl_Protocol *rp); +void prot_resume(Repl_Protocol *rp, int wakeup_action); +void prot_notify_update(Repl_Protocol *rp); +void prot_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name); +void prot_notify_window_opened (Repl_Protocol *rp); +void prot_notify_window_closed (Repl_Protocol *rp); +Object *prot_get_replica_object(Repl_Protocol *rp); +void prot_replicate_now(Repl_Protocol *rp); + +/* In repl5_replica.c */ +typedef enum +{ + REPLICA_TYPE_UNKNOWN, + REPLICA_TYPE_PRIMARY, + REPLICA_TYPE_READONLY, + REPLICA_TYPE_UPDATABLE, + REPLICA_TYPE_END +} ReplicaType; + +#define RUV_STORAGE_ENTRY_UNIQUEID "ffffffff-ffffffff-ffffffff-ffffffff" +#define START_ITERATION_ENTRY_UNIQUEID "00000000-00000000-00000000-00000000" +#define START_ITERATION_ENTRY_DN "cn=start iteration" + +typedef int (*FNEnumReplica) (Replica *r, void *arg); + +/* this function should be called to construct the replica object + from the data already in the DIT */ +Replica *replica_new(const Slapi_DN *root); +/* this function should be called to construct the replica object + during addition of the replica over LDAP */ +Replica *replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation); +void replica_destroy(void **arg); +PRBool replica_get_exclusive_access(Replica *r, PRBool *isInc, int connid, int opid, + const char *locking_purl, + char **current_purl); +void replica_relinquish_exclusive_access(Replica *r, int connid, int opid); +PRBool replica_get_tombstone_reap_active(const Replica *r); +const Slapi_DN *replica_get_root(const Replica *r); +const char *replica_get_name(const Replica *r); +ReplicaId replica_get_rid (const Replica *r); +void replica_set_rid (Replica *r, ReplicaId rid); +PRBool replica_is_initialized (const Replica *r); +Object *replica_get_ruv (const Replica *r); +/* replica now owns the RUV */ +void replica_set_ruv (Replica *r, RUV *ruv); +Object *replica_get_csngen (const Replica *r); +ReplicaType replica_get_type (const Replica *r); +void replica_set_type (Replica *r, ReplicaType type); +PRBool replica_is_legacy_consumer (const Replica *r); +void replica_set_legacy_consumer (Replica *r, PRBool legacy); +char *replica_get_legacy_purl (const Replica *r); +void replica_set_legacy_purl (Replica *r, const char *purl); +PRBool replica_is_updatedn (const Replica *r, const Slapi_DN *sdn); +void replica_set_updatedn (Replica *r, const Slapi_ValueSet *vs, int mod_op); +char *replica_get_generation (const Replica *r); +/* currently supported flags */ +#define REPLICA_LOG_CHANGES 0x1 /* enable change logging */ +PRBool replica_is_flag_set (const Replica *r, PRUint32 flag); +void replica_set_flag (Replica *r, PRUint32 flag, PRBool clear); +void replica_replace_flags (Replica *r, PRUint32 flags); +void replica_dump(Replica *r); +void replica_set_enabled (Replica *r, PRBool enable); +Object *replica_get_replica_from_dn (const Slapi_DN *dn); +void replica_update_ruv(Replica *replica, const CSN *csn, const char *replica_purl); +Object *replica_get_replica_for_op (Slapi_PBlock *pb); +/* the functions below manipulate replica hash */ +int replica_init_name_hash (); +void replica_destroy_name_hash (); +int replica_add_by_name (const char *name, Object *replica); +int replica_delete_by_name (const char *name); +Object* replica_get_by_name (const char *name); +void replica_flush(Replica *r); +void replica_get_referrals(const Replica *r, char ***referrals); +void replica_set_referrals(Replica *r,const Slapi_ValueSet *vs); +int replica_update_csngen_state (Replica *r, const RUV *ruv); +CSN *replica_get_purge_csn(const Replica *r); +int replica_log_ruv_elements (const Replica *r); +void replica_enumerate_replicas (FNEnumReplica fn, void *arg); +int replica_reload_ruv (Replica *r); +int replica_check_for_data_reload (Replica *r, void *arg); +/* the functions below manipulate replica dn hash */ +int replica_init_dn_hash (); +void replica_destroy_dn_hash (); +int replica_add_by_dn (const char *dn); +int replica_delete_by_dn (const char *dn); +int replica_is_being_configured (const char *dn); +const CSN * _get_deletion_csn(Slapi_Entry *e); +int legacy_consumer_init_referrals (Replica *r); +void consumer5_set_mapping_tree_state_for_replica(const Replica *r, RUV *supplierRuv); +Object *replica_get_for_backend (const char *be_name); +void replica_set_purge_delay (Replica *r, PRUint32 purge_delay); +void replica_set_tombstone_reap_interval (Replica *r, long interval); +void replica_update_ruv_consumer (Replica *r, RUV *supplier_ruv); +void replica_set_ruv_dirty (Replica *r); +void replica_write_ruv (Replica *r); +/* The functions below handles the state flag */ +/* Current internal state flags */ +/* The replica can be busy and not other flag, + * it means that the protocol has ended, but the work is not done yet. + * It happens on total protocol, the end protocol has been received, + * and the thread waits for import to finish + */ +#define REPLICA_IN_USE 1 /* The replica is busy */ +#define REPLICA_INCREMENTAL_IN_PROGRESS 2 /* Set only between start and stop inc */ +#define REPLICA_TOTAL_IN_PROGRESS 4 /* Set only between start and stop total */ +#define REPLICA_AGREEMENTS_DISABLED 8 /* Replica is offline */ +PRBool replica_is_state_flag_set(Replica *r, PRInt32 flag); +void replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear); +void replica_enable_replication (Replica *r); +void replica_disable_replication (Replica *r, Object *r_obj); +int replica_start_agreement(Replica *r, Repl_Agmt *ra); + +CSN* replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn ); +int replica_get_attr ( Slapi_PBlock *pb, const char *type, void *value ); + +/* mapping tree extensions manipulation */ +void multimaster_mtnode_extension_init (); +void multimaster_mtnode_extension_destroy (); +void multimaster_mtnode_construct_replicas (); + +void multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state); + +/* In repl5_replica_config.c */ +int replica_config_init(); +void replica_config_destroy (); + +/* replutil.c */ +LDAPControl* create_managedsait_control (); +LDAPControl* create_backend_control(Slapi_DN *sdn); +void repl_set_mtn_state_and_referrals(const Slapi_DN *sdn, const char *mtn_state, + const RUV *ruv, char **ruv_referrals, + char **other_referrals); +void repl_set_repl_plugin_path(const char *path); + +/* repl5_updatedn_list.c */ +typedef void *ReplicaUpdateDNList; +typedef int (*FNEnumDN)(Slapi_DN *dn, void *arg); +ReplicaUpdateDNList replica_updatedn_list_new(const Slapi_Entry *entry); +void replica_updatedn_list_free(ReplicaUpdateDNList list); +void replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs); +void replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs); +void replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs); +PRBool replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn); +char *replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter); +void replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg); + +/* enabling developper traces for MMR to understand the total/inc protocol state machines */ +#ifdef DEV_DEBUG +#define SLAPI_LOG_DEV_DEBUG SLAPI_LOG_FATAL +#define dev_debug(a) slapi_log_error(SLAPI_LOG_DEV_DEBUG, "DEV_DEBUG", "%s\n", a) +#else +#define dev_debug(a) +#endif + +void repl5_set_debug_timeout(const char *val); + +#endif /* _REPL5_H_ */ diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c new file mode 100644 index 00000000..2992fc11 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_agmt.c @@ -0,0 +1,1766 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_agmt.c */ +/* + + Support for 5.0-style replication agreements. + + Directory Server 5.0 replication agreements contain information about + replication consumers that we are supplying. + + This module encapsulates the methods available for adding, deleting, + modifying, and firing replication agreements. + + Methods: + + agmt_new - Create a new replication agreement, in response to a new + replication agreement being added over LDAP. + agmt_delete - Destroy an agreement. It is an error to destroy an + agreement that has not been stopped. + agmt_getstatus - get the status of this replication agreement. + agmt_replicate_now - initiate a replication session asap, even if the + schedule says we shouldn't. + agmt_start - start replicating, according to schedule. Starts a new + thread to handle replication. + agmt_stop - stop replicating asap and end replication thread. + agmt_notify_change - notify the replication agreement about a change that + has been logged. The replication agreement will + decide if it needs to take some action, e.g. start a + replication session. + agmt_initialize_replica - start a complete replica refresh. + agmt_set_schedule_from_entry - (re)set the schedule associated with this + replication agreement based on a RA entry's contents. + agmt_set_credentials_from_entry - (re)set the credentials used to bind + to the remote replica. + agmt_set_binddn_from_entry - (re)set the DN used to bind + to the remote replica. + agmt_set_bind_method_from_entry - (re)set the bind method used to bind + to the remote replica (SIMPLE or SSLCLIENTAUTH). + agmt_set_transportinfo_from_entry - (re)set the transport used to bind + to the remote replica (SSL or not) + +*/ + +#include "repl5.h" +#include "repl5_prot_private.h" +#include "cl5_api.h" + +#define DEFAULT_TIMEOUT 600 /* (seconds) default outbound LDAP connection */ +#define TRANSPORT_FLAG_SSL 1 +#define STATUS_LEN 1024 + +struct changecounter { + ReplicaId rid; + PRUint32 num_replayed; + PRUint32 num_skipped; +}; + +typedef struct repl5agmt { + char *hostname; /* remote hostname */ + int port; /* port of remote server */ + PRUint32 transport_flags; /* SSL, TLS, etc. */ + char *binddn; /* DN to bind as */ + struct berval *creds; /* Password, or certificate */ + int bindmethod; /* Bind method - simple, SSL */ + Slapi_DN *replarea; /* DN of replicated area */ + char **frac_attrs; /* list of fractional attributes to be replicated */ + Schedule *schedule; /* Scheduling information */ + int auto_initialize; /* 1 = automatically re-initialize replica */ + const Slapi_DN *dn; /* DN of replication agreement entry */ + const Slapi_RDN *rdn; /* RDN of replication agreement entry */ + char *long_name; /* Long name (rdn + host, port) of entry, for logging */ + Repl_Protocol *protocol; /* Protocol object - manages protocol */ + struct changecounter *changecounters[MAX_NUM_OF_MASTERS]; /* changes sent/skipped since server start up */ + int num_changecounters; + time_t last_update_start_time; /* Local start time of last update session */ + time_t last_update_end_time; /* Local end time of last update session */ + char last_update_status[STATUS_LEN]; /* Status of last update. Format = numeric code <space> textual description */ + PRBool update_in_progress; + time_t last_init_start_time; /* Local start time of last total init */ + time_t last_init_end_time; /* Local end time of last total init */ + char last_init_status[STATUS_LEN]; /* Status of last total init. Format = numeric code <space> textual description */ + PRLock *lock; + Object *consumerRUV; /* last RUV received from the consumer - used for changelog purging */ + CSN *consumerSchemaCSN; /* last schema CSN received from the consumer */ + ReplicaId consumerRID; /* indicates if the consumer is the originator of a CSN */ + long timeout; /* timeout (in seconds) for outbound LDAP connections to remote server */ + PRBool stop_in_progress; /* set by agmt_stop when shutting down */ + long busywaittime; /* time in seconds to wait after getting a REPLICA BUSY from the consumer - + to allow another supplier to finish sending its updates - + if set to 0, this means to use the default value if we get a busy + signal from the consumer */ + long pausetime; /* time in seconds to pause after sending updates - + to allow another supplier to send its updates - + should be greater than busywaittime - + if set to 0, this means do not pause */ +} repl5agmt; + +/* Forward declarations */ +void agmt_delete(void **rap); +static void update_window_state_change_callback (void *arg, PRBool opened); +static int get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e, + Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); +static int agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); + +/* +Schema for replication agreement: + +cn +nsds5ReplicaHost - hostname +nsds5ReplicaPort - port number +nsds5ReplicaTransportInfo - "SSL", "startTLS", or may be absent; +nsds5ReplicaBindDN +nsds5ReplicaCredentials +nsds5ReplicaBindMethod - "SIMPLE" or "SSLCLIENTAUTH". +nsds5ReplicaRoot - Replicated suffix +nsds5ReplicatedAttributeList - Unused so far (meant for fractional repl). +nsds5ReplicaUpdateSchedule +nsds5ReplicaTimeout - Outbound repl operations timeout +nsds50ruv - consumer's RUV +nsds5ReplicaBusyWaitTime - time to wait after getting a REPLICA BUSY from the consumer +nsds5ReplicaSessionPauseTime - time to pause after sending updates to allow another supplier to send +*/ + + +/* + * Validate an agreement, making sure that it's valid. + * Return 1 if the agreement is valid, 0 otherwise. + */ +static int +agmt_is_valid(Repl_Agmt *ra) +{ + int return_value = 1; /* assume valid, initially */ + PR_ASSERT(NULL != ra); + PR_ASSERT(NULL != ra->dn); + + if (NULL == ra->hostname) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: missing host name.\n", slapi_sdn_get_dn(ra->dn)); + return_value = 0; + } + if (ra->port <= 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid port number %d.\n", slapi_sdn_get_dn(ra->dn), ra->port); + return_value = 0; + } + if (ra->timeout < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid timeout %d.\n", slapi_sdn_get_dn(ra->dn), ra->timeout); + return_value = 0; + } + if (ra->busywaittime < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid busy wait time %d.\n", slapi_sdn_get_dn(ra->dn), ra->busywaittime); + return_value = 0; + } + if (ra->pausetime < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid pausetime %d.\n", slapi_sdn_get_dn(ra->dn), ra->pausetime); + return_value = 0; + } + return return_value; +} + + +Repl_Agmt * +agmt_new_from_entry(Slapi_Entry *e) +{ + Repl_Agmt *ra; + char *tmpstr; + Slapi_Attr *sattr; + + char *auto_initialize = NULL; + char *val_nsds5BeginReplicaRefresh = "start"; + + ra = (Repl_Agmt *)slapi_ch_calloc(1, sizeof(repl5agmt)); + if ((ra->lock = PR_NewLock()) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to create new lock " + "for replication agreement \"%s\" - agreement ignored.\n", + slapi_entry_get_dn_const(e)); + goto loser; + } + + /* Find all the stuff we need for the agreement */ + + /* To Allow Consumer Initialisation when adding an agreement: */ + + /* + Using 'auto_initialize' member of 'repl5agmt' structure to + store the effect of 'nsds5BeginReplicaRefresh' attribute's value + in it. + */ + auto_initialize = slapi_entry_attr_get_charptr(e, type_nsds5BeginReplicaRefresh); + if ((auto_initialize != NULL) && (strcasecmp(auto_initialize, val_nsds5BeginReplicaRefresh) == 0)) + { + ra->auto_initialize = STATE_PERFORMING_TOTAL_UPDATE; + } + else + { + ra->auto_initialize = STATE_PERFORMING_INCREMENTAL_UPDATE; + } + + if (auto_initialize) + { + slapi_ch_free_string (&auto_initialize); + } + + /* Host name of remote replica */ + ra->hostname = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaHost); + /* Port number for remote replica instance */ + ra->port = slapi_entry_attr_get_int(e, type_nsds5ReplicaPort); + /* SSL, TLS, or other transport stuff */ + ra->transport_flags = 0; + agmt_set_transportinfo_no_lock(ra, e); + + /* DN to use when binding. May be empty if cert-based auth is to be used. */ + ra->binddn = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBindDN); + if (NULL == ra->binddn) + { + ra->binddn = slapi_ch_strdup(""); + } + /* Credentials to use when binding. */ + ra->creds = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + ra->creds->bv_val = NULL; + ra->creds->bv_len = 0; + if (slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr) == 0) + { + Slapi_Value *sval; + if (slapi_attr_first_value(sattr, &sval) == 0) + { + const struct berval *bv = slapi_value_get_berval(sval); + if (NULL != bv) + { + ra->creds->bv_val = slapi_ch_malloc(bv->bv_len + 1); + memcpy(ra->creds->bv_val, bv->bv_val, bv->bv_len); + ra->creds->bv_len = bv->bv_len; + ra->creds->bv_val[bv->bv_len] = '\0'; /* be safe */ + } + } + } + /* How to bind */ + (void)agmt_set_bind_method_no_lock(ra, e); + + /* timeout. */ + ra->timeout = DEFAULT_TIMEOUT; + if (slapi_entry_attr_find(e, type_nsds5ReplicaTimeout, &sattr) == 0) + { + Slapi_Value *sval; + if (slapi_attr_first_value(sattr, &sval) == 0) + { + ra->timeout = slapi_value_get_long(sval); + } + } + + /* DN of entry at root of replicated area */ + tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaRoot); + if (NULL != tmpstr) + { + ra->replarea = slapi_sdn_new_dn_passin(tmpstr); + } + /* XXXggood get fractional attribute include/exclude lists here */ + /* Replication schedule */ + ra->schedule = schedule_new(update_window_state_change_callback, ra, agmt_get_long_name(ra)); + if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) == 0) + { + schedule_set(ra->schedule, sattr); + } + + /* busy wait time - time to wait after getting REPLICA BUSY from consumer */ + ra->busywaittime = slapi_entry_attr_get_long(e, type_nsds5ReplicaBusyWaitTime); + + /* pause time - time to pause after a session has ended */ + ra->pausetime = slapi_entry_attr_get_long(e, type_nsds5ReplicaSessionPauseTime); + + /* consumer's RUV */ + if (slapi_entry_attr_find(e, type_ruvElement, &sattr) == 0) + { + RUV *ruv; + + if (ruv_init_from_slapi_attr(sattr, &ruv) == 0) + { + ra->consumerRUV = object_new (ruv, (FNFree)ruv_destroy); + } + } + + ra->consumerRID = 0; + + /* DN and RDN of the replication agreement entry itself */ + ra->dn = slapi_sdn_dup(slapi_entry_get_sdn((Slapi_Entry *)e)); + ra->rdn = slapi_rdn_new_sdn(ra->dn); + + /* Compute long name */ + { + const char *agmtname = slapi_rdn_get_rdn(ra->rdn); + char hostname[128]; + char *dot; + + strncpy(hostname, ra->hostname ? ra->hostname : "(unknown)", sizeof(hostname)); + hostname[sizeof(hostname)-1] = '\0'; + dot = strchr(hostname, '.'); + if (dot) { + *dot = '\0'; + } + ra->long_name = slapi_ch_malloc(strlen(agmtname) + + strlen(hostname) + 25); + sprintf(ra->long_name, "agmt=\"%s\" (%s:%d)", agmtname, hostname, ra->port); + } + + /* Initialize status information */ + ra->last_update_start_time = 0UL; + ra->last_update_end_time = 0UL; + ra->num_changecounters = 0; + ra->last_update_status[0] = '\0'; + ra->update_in_progress = PR_FALSE; + ra->stop_in_progress = PR_FALSE; + ra->last_init_end_time = 0UL; + ra->last_init_start_time = 0UL; + ra->last_init_status[0] = '\0'; + + if (!agmt_is_valid(ra)) + { + goto loser; + } + + /* Now that the agreement is done, just check if changelog is configured */ + if (cl5GetState() != CL5_STATE_OPEN) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "WARNING: " + "Replication agreement added but there is no changelog configured. " + "No change will be replicated until a changelog is configured.\n"); + } + + /* + * Establish a callback for this agreement's entry, so we can + * adorn it with status information when read. + */ + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, slapi_sdn_get_ndn(ra->dn), + LDAP_SCOPE_BASE, "(objectclass=*)", get_agmt_status, ra); + + return ra; +loser: + agmt_delete((void **)&ra); + return NULL; +} + + + +Repl_Agmt * +agmt_new_from_pblock(Slapi_PBlock *pb) +{ + Slapi_Entry *e; + + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + return agmt_new_from_entry(e); +} + + +/* + This should never be called directly - only should be called + as a destructor. XXXggood this is not finished + */ +void +agmt_delete(void **rap) +{ + Repl_Agmt *ra; + PR_ASSERT(NULL != rap); + PR_ASSERT(NULL != *rap); + + ra = (Repl_Agmt *)*rap; + + /* do prot_delete first - we may be doing some processing using this + replication agreement, and prot_delete will make sure the + processing is complete - then it should be safe to clean up the + other fields below + */ + prot_delete(&ra->protocol); + + /* + * Remove the callback for this agreement's entry + */ + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, + slapi_sdn_get_ndn(ra->dn), + LDAP_SCOPE_BASE, "(objectclass=*)", + get_agmt_status); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&(ra->hostname)); + slapi_ch_free((void **)&(ra->binddn)); + + if (NULL != ra->creds) + { + /* XXX free berval */ + } + if (NULL != ra->replarea) + { + slapi_sdn_free(&ra->replarea); + } + + if (NULL != ra->consumerRUV) + { + object_release (ra->consumerRUV); + } + + csn_free (&ra->consumerSchemaCSN); + while ( --(ra->num_changecounters) >= 0 ) + { + slapi_ch_free((void **)&ra->changecounters[ra->num_changecounters]); + } + + schedule_destroy(ra->schedule); + slapi_ch_free((void **)&ra->long_name); + slapi_ch_free((void **)rap); +} + + +/* + * Allow replication for this replica to begin. Replication will + * occur at the next scheduled time. Returns 0 on success, -1 on + * failure. + */ +int +agmt_start(Repl_Agmt *ra) +{ + Repl_Protocol *prot = NULL; + + int protocol_state; + + /* To Allow Consumer Initialisation when adding an agreement: */ + if (ra->auto_initialize == STATE_PERFORMING_TOTAL_UPDATE) + { + protocol_state = STATE_PERFORMING_TOTAL_UPDATE; + } + else + { + protocol_state = STATE_PERFORMING_INCREMENTAL_UPDATE; + } + + /* First, create a new protocol object */ + if ((prot = prot_new(ra, protocol_state)) == NULL) { + return -1; + } + + /* Now it is safe to own the agreement lock */ + PR_Lock(ra->lock); + + /* Check that replication is not already started */ + if (ra->protocol != NULL) { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replication already started for agreement \"%s\"\n", agmt_get_long_name(ra)); + PR_Unlock(ra->lock); + prot_free(&prot); + return 0; + } + + ra->protocol = prot; + + /* Start the protocol thread */ + prot_start(ra->protocol); + + PR_Unlock(ra->lock); + return 0; +} + +/* +Cease replicating to this replica as soon as possible. +*/ +int +agmt_stop(Repl_Agmt *ra) +{ + int return_value = 0; + Repl_Protocol *rp = NULL; + + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + ra->stop_in_progress = PR_TRUE; + rp = ra->protocol; + PR_Unlock(ra->lock); + if (NULL != rp) /* we use this pointer outside the lock - dangerous? */ + { + prot_stop(rp); + } + PR_Lock(ra->lock); + ra->stop_in_progress = PR_FALSE; + /* we do not reuse the protocol object so free it */ + prot_free(&ra->protocol); + PR_Unlock(ra->lock); + return return_value; +} + +/* +Send any pending updates as soon as possible, ignoring any replication +schedules. +*/ +int +agmt_replicate_now(Repl_Agmt *ra) +{ + int return_value = 0; + + return return_value; +} + +/* + * Return a copy of the remote replica's hostname. + */ +char * +agmt_get_hostname(const Repl_Agmt *ra) +{ + char *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = slapi_ch_strdup(ra->hostname); + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return the port number of the remote replica's instance. + */ +int +agmt_get_port(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->port; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return the transport flags for this agreement. + */ +PRUint32 +agmt_get_transport_flags(const Repl_Agmt *ra) +{ + unsigned int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->transport_flags; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return a copy of the bind dn to be used with this + * agreement (may return NULL if no binddn is required, + * e.g. SSL client auth. + */ +char * +agmt_get_binddn(const Repl_Agmt *ra) +{ + char *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->binddn == NULL ? NULL : slapi_ch_strdup(ra->binddn); + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return a copy of the credentials. + */ +struct berval * +agmt_get_credentials(const Repl_Agmt *ra) +{ + struct berval *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + return_value->bv_val = (char *)slapi_ch_malloc(ra->creds->bv_len + 1); + return_value->bv_len = ra->creds->bv_len; + memcpy(return_value->bv_val, ra->creds->bv_val, ra->creds->bv_len); + return_value->bv_val[return_value->bv_len] = '\0'; /* just in case */ + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_get_bindmethod(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->bindmethod; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return a copy of the dn at the top of the replicated area. + */ +Slapi_DN * +agmt_get_replarea(const Repl_Agmt *ra) +{ + Slapi_DN *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = slapi_sdn_new(); + slapi_sdn_copy(ra->replarea, return_value); + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_is_fractional(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->frac_attrs != NULL; + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = 1; /* XXXggood finish this */ + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_get_auto_initialize(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->auto_initialize; + PR_Unlock(ra->lock); + return return_value; +} + +long +agmt_get_timeout(const Repl_Agmt *ra) +{ + long return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->timeout; + PR_Unlock(ra->lock); + return return_value; +} + +long +agmt_get_busywaittime(const Repl_Agmt *ra) +{ + long return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->busywaittime; + PR_Unlock(ra->lock); + return return_value; +} +long +agmt_get_pausetime(const Repl_Agmt *ra) +{ + long return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->pausetime; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Warning - reference to the long name of the agreement is returned. + * The long name of an agreement is the DN of the agreement entry, + * followed by the host/port for the replica. + */ +const char * +agmt_get_long_name(const Repl_Agmt *ra) +{ + char *return_value = NULL; + + return_value = ra ? ra->long_name : ""; + return return_value; +} + +/* + * Warning - reference to dn is returned. However, since the dn of + * the replication agreement is its name, it won't change during the + * lifetime of the replication agreement object. + */ +const Slapi_DN * +agmt_get_dn_byref(const Repl_Agmt *ra) +{ + const Slapi_DN *return_value = NULL; + + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + return_value = ra->dn; + } + return return_value; +} + +/* Return 1 if name matches the replication Dn, 0 otherwise */ +int +agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name) +{ + int return_value = 0; + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + PR_Lock(ra->lock); + if (slapi_sdn_compare(name, ra->dn) == 0) + { + return_value = 1; + } + PR_Unlock(ra->lock); + } + return return_value; +} + +/* Return 1 if name matches the replication area, 0 otherwise */ +int +agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name) +{ + int return_value = 0; + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + PR_Lock(ra->lock); + if (slapi_sdn_compare(name, ra->replarea) == 0) + { + return_value = 1; + } + PR_Unlock(ra->lock); + } + return return_value; +} + + +int +agmt_schedule_in_window_now(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (NULL != ra->schedule && schedule_in_window_now(ra->schedule)) + { + return_value = 1; + } + else + { + return_value = 0; + } + PR_Unlock(ra->lock); + return return_value; +} + + +/* + * Set or reset the credentials used to bind to the remote replica. + * + * Returns 0 if credentials set, or -1 if an error occurred. + */ +int +agmt_set_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr); + PR_Lock(ra->lock); + slapi_ch_free((void **)&ra->creds->bv_val); + ra->creds->bv_len = 0; + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + const struct berval *bv = slapi_value_get_berval(sval); + ra->creds->bv_val = slapi_ch_calloc(1, bv->bv_len + 1); + memcpy(ra->creds->bv_val, bv->bv_val, bv->bv_len); + ra->creds->bv_len = bv->bv_len; + } + } + /* If no credentials set, set to zero-length string */ + ra->creds->bv_val = NULL == ra->creds->bv_val ? slapi_ch_strdup("") : ra->creds->bv_val; + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + return return_value; +} + +/* + * Set or reset the DN used to bind to the remote replica. + * + * Returns 0 if DN set, or -1 if an error occurred. + */ +int +agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + slapi_entry_attr_find(e, type_nsds5ReplicaBindDN, &sattr); + PR_Lock(ra->lock); + slapi_ch_free((void **)&ra->binddn); + ra->binddn = NULL; + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + const char *val = slapi_value_get_string(sval); + ra->binddn = strdup(val); + } + } + /* If no BindDN set, set to zero-length string */ + if (ra->binddn == NULL) { + ra->binddn = strdup(""); + } + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + return return_value; +} + +/* + * Set or reset the bind method used to bind to the remote replica. + * + * Returns 0 if bind method set, or -1 if an error occurred. + */ +static int +agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) +{ + char *tmpstr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBindMethod); + + if (NULL == tmpstr || strcasecmp(tmpstr, "SIMPLE") == 0) + { + ra->bindmethod = BINDMETHOD_SIMPLE_AUTH; + } + else if (strcasecmp(tmpstr, "SSLCLIENTAUTH") == 0) + { + ra->bindmethod = BINDMETHOD_SSL_CLIENTAUTH; + } + else + { + ra->bindmethod = BINDMETHOD_SIMPLE_AUTH; + } + slapi_ch_free((void **)&tmpstr); + return return_value; +} + +int +agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + char *tmpstr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + return_value = agmt_set_bind_method_no_lock(ra, e); + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + return return_value; +} + +/* + * Set or reset the transport used to bind to the remote replica. + * + * Returns 0 if transport set, or -1 if an error occurred. + */ +static int +agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) +{ + char *tmpstr; + int rc = 0; + + tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5TransportInfo); + if (NULL != tmpstr && strcasecmp(tmpstr, "SSL") == 0) + { + ra->transport_flags |= TRANSPORT_FLAG_SSL; + } else { + ra->transport_flags &= ~TRANSPORT_FLAG_SSL; + } + + slapi_ch_free((void **)&tmpstr); + return (rc); +} + +int +agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + int return_value = 0; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + return_value = agmt_set_transportinfo_no_lock(ra, e); + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + + return return_value; +} + + +/* + * Set or reset the replication schedule. Notify the protocol handler + * that a change has been made. + * + * Returns 0 if schedule was set or -1 if an error occurred. + */ +int +agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ) +{ + Slapi_Attr *sattr; + int return_value = 0; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + PR_Unlock(ra->lock); + + if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) != 0) + { + sattr = NULL; /* no schedule ==> delete any existing one */ + } + + /* make it so */ + return_value = schedule_set(ra->schedule, sattr); + + if ( 0 == return_value ) { + /* schedule set OK -- spread the news */ + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + + return return_value; +} + +/* + * Set or reset the timeout used to bind to the remote replica. + * + * Returns 0 if timeout set, or -1 if an error occurred. + */ +int +agmt_set_timeout_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = -1; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + + slapi_entry_attr_find(e, type_nsds5ReplicaTimeout, &sattr); + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + long tmpval = slapi_value_get_long(sval); + if (tmpval >= 0) { + ra->timeout = tmpval; + return_value = 0; /* success! */ + } + } + } + PR_Unlock(ra->lock); + if (return_value == 0) + { + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + return return_value; +} + +/* + * Set or reset the busywaittime + * + * Returns 0 if busywaittime set, or -1 if an error occurred. + */ +int +agmt_set_busywaittime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = -1; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + + slapi_entry_attr_find(e, type_nsds5ReplicaBusyWaitTime, &sattr); + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + long tmpval = slapi_value_get_long(sval); + if (tmpval >= 0) { + ra->busywaittime = tmpval; + return_value = 0; /* success! */ + } + } + } + PR_Unlock(ra->lock); + if (return_value == 0) + { + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + return return_value; +} + +/* + * Set or reset the pausetime + * + * Returns 0 if pausetime set, or -1 if an error occurred. + */ +int +agmt_set_pausetime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = -1; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + + slapi_entry_attr_find(e, type_nsds5ReplicaSessionPauseTime, &sattr); + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + long tmpval = slapi_value_get_long(sval); + if (tmpval >= 0) { + ra->pausetime = tmpval; + return_value = 0; /* success! */ + } + } + } + PR_Unlock(ra->lock); + if (return_value == 0) + { + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + return return_value; +} + +/* XXXggood - also make this pass an arg that tells if there was + * an update to a priority attribute */ +void +agmt_notify_change(Repl_Agmt *agmt, Slapi_PBlock *pb) +{ + if (NULL != pb) + { + /* Is the entry within our replicated area? */ + char *target_dn; + Slapi_DN *target_sdn; + int change_is_relevant = 0; + + PR_ASSERT(NULL != agmt); + PR_Lock(agmt->lock); + if (agmt->stop_in_progress) + { + PR_Unlock(agmt->lock); + return; + } + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &target_dn); + target_sdn = slapi_sdn_new_dn_byref(target_dn); /* XXX see if you can avoid allocating this */ + + if (slapi_sdn_issuffix(target_sdn, agmt->replarea)) + { + /* + * Yep, it's in our replicated area. Is this a fractional + * replication agreement? + */ + if (NULL != agmt->frac_attrs) + { + /* + * Yep, it's fractional. See if the change should be + * tossed because it doesn't affect any of the replicated + * attributes. + */ + int optype; + int affects_fractional_attribute = 0; + + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype); + if (SLAPI_OPERATION_MODIFY == optype) + { + LDAPMod **mods; + int i, j; + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (i = 0; !affects_fractional_attribute && NULL != agmt->frac_attrs[i]; i++) + { + for (j = 0; !affects_fractional_attribute && NULL != mods[j]; j++) + { + if (slapi_attr_types_equivalent(agmt->frac_attrs[i], + mods[i]->mod_type)) + { + affects_fractional_attribute = 1; + } + } + } + } + else + { + /* + * Add, delete, and modrdn always cause some sort of + * operation replay, even if agreement is fractional. + */ + affects_fractional_attribute = 1; + } + if (affects_fractional_attribute) + { + change_is_relevant = 1; + } + } + else + { + /* Not a fractional agreement */ + change_is_relevant = 1; + } + } + PR_Unlock(agmt->lock); + slapi_sdn_free(&target_sdn); + if (change_is_relevant) + { + /* Notify the protocol that a change has occurred */ + prot_notify_update(agmt->protocol); + } + } +} + + + +int +agmt_is_50_mm_protocol(const Repl_Agmt *agmt) +{ + return 1; /* XXXggood could support > 1 protocol */ +} + + + +int +agmt_initialize_replica(const Repl_Agmt *agmt) +{ + PR_ASSERT(NULL != agmt); + PR_Lock(agmt->lock); + if (agmt->stop_in_progress) + { + PR_Unlock(agmt->lock); + return 0; + } + PR_Unlock(agmt->lock); + /* Call prot_initialize_replica only if the suffix is enabled (agmt->protocol != NULL) */ + if (NULL != agmt->protocol) { + prot_initialize_replica(agmt->protocol); + } + else { + /* agmt->protocol == NULL --> Suffix is disabled */ + return -1; + } + return 0; +} + +/* delete nsds5BeginReplicaRefresh attribute to indicate to the clients + that replica initialization have completed */ +void +agmt_replica_init_done (const Repl_Agmt *agmt) +{ + int rc; + Slapi_PBlock *pb = slapi_pblock_new (); + LDAPMod *mods [2]; + LDAPMod mod; + + mods[0] = &mod; + mods[1] = NULL; + mod.mod_op = LDAP_MOD_DELETE | LDAP_MOD_BVALUES; + mod.mod_type = (char*)type_nsds5ReplicaInitialize; + mod.mod_bvalues = NULL; + + slapi_modify_internal_set_pb(pb, slapi_sdn_get_dn (agmt->dn), mods, NULL/* controls */, + NULL/* uniqueid */, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0/* flags */); + slapi_modify_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_replica_init_done: " + "failed to remove (%s) attribute from (%s) entry; LDAP error - %d\n", + type_nsds5ReplicaInitialize, slapi_sdn_get_ndn (agmt->dn), rc); + } + + slapi_pblock_destroy (pb); +} + +/* Agreement object is acquired on behalf of the caller. + The caller is responsible for releasing the object + when it is no longer used */ + +Object* +agmt_get_consumer_ruv (Repl_Agmt *ra) +{ + Object *rt = NULL; + + PR_ASSERT(NULL != ra); + + PR_Lock(ra->lock); + if (ra->consumerRUV) + { + object_acquire (ra->consumerRUV); + rt = ra->consumerRUV; + } + + PR_Unlock(ra->lock); + + return rt; +} + +int +agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv) +{ + if (ra == NULL || ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmt_set_consumer_ruv: invalid argument" + " agmt - %p, ruv - %p\n", ra, ruv); + return -1; + } + + PR_Lock(ra->lock); + + if (ra->consumerRUV) + { + object_release (ra->consumerRUV); + } + + ra->consumerRUV = object_new (ruv_dup (ruv), (FNFree)ruv_destroy); + + PR_Unlock(ra->lock); + + return 0; +} + +void +agmt_update_consumer_ruv (Repl_Agmt *ra) +{ + int rc; + RUV *ruv; + Slapi_Mod smod; + Slapi_Mod smod_last_modified; + Slapi_PBlock *pb; + LDAPMod *mods[3]; + + PR_ASSERT (ra); + PR_Lock(ra->lock); + + if (ra->consumerRUV) + { + ruv = (RUV*) object_get_data (ra->consumerRUV); + PR_ASSERT (ruv); + + ruv_to_smod(ruv, &smod); + ruv_last_modified_to_smod(ruv, &smod_last_modified); + + /* it is ok to release the lock here because we are done with the agreement data. + we have to do it before issuing the modify operation because it causes + agmtlist_notify_all to be called which uses the same lock - hence the deadlock */ + PR_Unlock(ra->lock); + + pb = slapi_pblock_new (); + mods[0] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod); + mods[1] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_last_modified); + mods[2] = NULL; + + slapi_modify_internal_set_pb (pb, (char*)slapi_sdn_get_dn(ra->dn), mods, NULL, NULL, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modify_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: agmt_update_consumer_ruv: " + "failed to update consumer's RUV; LDAP error - %d\n", + ra->long_name, rc); + } + + slapi_mod_done (&smod); + slapi_mod_done (&smod_last_modified); + slapi_pblock_destroy (pb); + } + else + PR_Unlock(ra->lock); +} + +CSN* +agmt_get_consumer_schema_csn (Repl_Agmt *ra) +{ + CSN *rt; + + PR_ASSERT(NULL != ra); + + PR_Lock(ra->lock); + rt = ra->consumerSchemaCSN; + PR_Unlock(ra->lock); + + return rt; +} + +void +agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn) +{ + PR_ASSERT(NULL != ra); + + PR_Lock(ra->lock); + csn_free(&ra->consumerSchemaCSN); + ra->consumerSchemaCSN = csn; + PR_Unlock(ra->lock); +} + +void +agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_update_start_time = start_time; + ra->last_update_end_time = 0UL; + } +} + + +void +agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_update_end_time = end_time; + } +} + +void +agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_init_start_time = start_time; + ra->last_init_end_time = 0UL; + } +} + + +void +agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_init_end_time = end_time; + } +} + +void +agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *message) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + if (replrc == NSDS50_REPL_UPTODATE) + { + /* no session started, no status update */ + } + else if (ldaprc != LDAP_SUCCESS) + { + char *replmsg = NULL; + if ( replrc ) { + replmsg = protocol_response2string(replrc); + /* Do not mix the unknown replication error with the known ldap one */ + if ( strcasecmp(replmsg, "unknown error") == 0 ) { + replmsg = NULL; + } + } + if (ldaprc > 0) { + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d %s%sLDAP error: %s%s%s", + ldaprc, + message?message:"",message?"":" - ", + ldap_err2string(ldaprc), + replmsg ? " - " : "", replmsg ? replmsg : ""); + } else { /* ldaprc is < 0 */ + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d %s%sSystem error%s%s", + ldaprc,message?message:"",message?"":" - ", + replmsg ? " - " : "", replmsg ? replmsg : ""); + } + } + else if (replrc != 0) + { + if (replrc == NSDS50_REPL_REPLICA_READY) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", + ldaprc, "Replica acquired successfully"); + } + else if (replrc == NSDS50_REPL_REPLICA_BUSY) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d Can't acquire busy replica", replrc ); + } + else if (replrc == NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", + ldaprc, "Replication session successful"); + } + else if (replrc == NSDS50_REPL_DISABLED) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d Total update aborted: " + "Replication agreement for %s\n can not be updated while the replica is disabled.\n" + "(If the suffix is disabled you must enable it then restart the server for replication to take place).", + replrc, ra->long_name ? ra->long_name : "a replica"); + /* Log into the errors log, as "ra->long_name" is not accessible from the caller */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Total update aborted: Replication agreement for \"%s\" " + "can not be updated while the replica is disabled\n", ra->long_name ? ra->long_name : "a replica"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "(If the suffix is disabled you must enable it then restart the server for replication to take place).\n"); + } + else + { + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d Replication error acquiring replica: %s%s%s", + replrc, protocol_response2string(replrc), + message?" - ":"",message?message:""); + } + } + else if (message != NULL) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", ldaprc, message); + } + else { /* agmt_set_last_update_status(0,0,NULL) to reset agmt */ + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d", ldaprc); + } + } +} + +void +agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *message) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + if (ldaprc != LDAP_SUCCESS) + { + char *replmsg = NULL; + if ( replrc ) { + replmsg = protocol_response2string(replrc); + /* Do not mix the unknown replication error with the known ldap one */ + if ( strcasecmp(replmsg, "unknown error") == 0 ) { + replmsg = NULL; + } + } + if (ldaprc > 0) { + PR_snprintf(ra->last_init_status, STATUS_LEN, + "%d %s%sLDAP error: %s%s%s", + ldaprc, + message?message:"",message?"":" - ", + ldap_err2string(ldaprc), + replmsg ? " - " : "", replmsg ? replmsg : ""); + } else { /* ldaprc is < 0 */ + PR_snprintf(ra->last_init_status, STATUS_LEN, + "%d %s%sSystem error%s%s", + ldaprc,message?message:"",message?"":" - ", + replmsg ? " - " : "", replmsg ? replmsg : ""); + } + } + else if (replrc != 0) + { + if (replrc == NSDS50_REPL_REPLICA_READY) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", + ldaprc, "Replica acquired successfully"); + } + else if (replrc == NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", + ldaprc, "Replication session successful"); + } + else if (replrc == NSDS50_REPL_DISABLED) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d Total update aborted: " + "Replication agreement for %s\n can not be updated while the replica is disabled.\n" + "(If the suffix is disabled you must enable it then restart the server for replication to take place).", + replrc, ra->long_name ? ra->long_name : "a replica"); + /* Log into the errors log, as "ra->long_name" is not accessible from the caller */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Total update aborted: Replication agreement for \"%s\" " + "can not be updated while the replica is disabled\n", ra->long_name ? ra->long_name : "a replica"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "(If the suffix is disabled you must enable it then restart the server for replication to take place).\n"); + } + else + { + PR_snprintf(ra->last_init_status, STATUS_LEN, + "%d Replication error acquiring replica: %s%s%s", + replrc, protocol_response2string(replrc), + message?" - ":"",message?message:""); + } + } + else if (message != NULL) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", ldaprc, message); + } + else { /* agmt_set_last_init_status(0,0,NULL) to reset agmt */ + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d", ldaprc); + } + } +} + + +void +agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->update_in_progress = in_progress; + } +} + +void +agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + int i; + + for ( i = 0; i < ra->num_changecounters; i++ ) + { + if ( ra->changecounters[i]->rid == rid ) + break; + } + + if ( i < ra->num_changecounters ) + { + if ( skipped ) + ra->changecounters[i]->num_skipped ++; + else + ra->changecounters[i]->num_replayed ++; + } + else + { + ra->num_changecounters ++; + ra->changecounters[i] = (struct changecounter*) slapi_ch_calloc(1, sizeof(struct changecounter)); + ra->changecounters[i]->rid = rid; + if ( skipped ) + ra->changecounters[i]->num_skipped = 1; + else + ra->changecounters[i]->num_replayed = 1; + } + } +} + +void +agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize) +{ + char tmp_buf[32]; /* 5 digit RID, 10 digit each replayed and skipped */ + int i; + int buflen = 0; + + *buf = '\0'; + if (NULL != ra) + { + for ( i = 0; i < ra->num_changecounters; i++ ) + { + PR_snprintf (tmp_buf, sizeof(tmp_buf), "%u:%u/%u ", + ra->changecounters[i]->rid, + ra->changecounters[i]->num_replayed, + ra->changecounters[i]->num_skipped); + PR_snprintf (buf+buflen, bufsize-buflen, "%s", tmp_buf); + buflen += strlen (tmp_buf); + } + } +} + +static int +get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + char *time_tmp = NULL; + char changecount_string[BUFSIZ]; + Repl_Agmt *ra = (Repl_Agmt *)arg; + + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + PRBool reapActive = PR_FALSE; + Slapi_DN *replarea_sdn = NULL; + Object *repl_obj = NULL; + + replarea_sdn = agmt_get_replarea(ra); + repl_obj = replica_get_replica_from_dn(replarea_sdn); + slapi_sdn_free(&replarea_sdn); + if (repl_obj) { + Replica *replica = (Replica*)object_get_data (repl_obj); + reapActive = replica_get_tombstone_reap_active(replica); + object_release(repl_obj); + } + slapi_entry_attr_set_int(e, "nsds5replicaReapActive", (int)reapActive); + + /* these values persist in the dse.ldif file, so we delete them + here to avoid multi valued attributes */ + slapi_entry_attr_delete(e, "nsds5replicaLastUpdateStart"); + slapi_entry_attr_delete(e, "nsds5replicaLastUpdateEnd"); + slapi_entry_attr_delete(e, "nsds5replicaChangesSentSinceStartup"); + slapi_entry_attr_delete(e, "nsds5replicaLastUpdateStatus"); + slapi_entry_attr_delete(e, "nsds5replicaUpdateInProgress"); + slapi_entry_attr_delete(e, "nsds5replicaLastInitStart"); + slapi_entry_attr_delete(e, "nsds5replicaLastInitStatus"); + slapi_entry_attr_delete(e, "nsds5replicaLastInitEnd"); + + /* now, add the real values (singly) */ + if (ra->last_update_start_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateStart", "0"); + } + else + { + time_tmp = format_genTime(ra->last_update_start_time); + slapi_entry_add_string(e, "nsds5replicaLastUpdateStart", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + if (ra->last_update_end_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateEnd", "0"); + } + else + { + time_tmp = format_genTime(ra->last_update_end_time); + slapi_entry_add_string(e, "nsds5replicaLastUpdateEnd", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + agmt_get_changecount_string (ra, changecount_string, sizeof (changecount_string) ); + slapi_entry_add_string(e, "nsds5replicaChangesSentSinceStartup", changecount_string); + if (ra->last_update_status[0] == '\0') + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateStatus", "0 No replication sessions started since server startup"); + } + else + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateStatus", ra->last_update_status); + } + slapi_entry_add_string(e, "nsds5replicaUpdateInProgress", ra->update_in_progress ? "TRUE" : "FALSE"); + if (ra->last_init_start_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastInitStart", "0"); + } + else + { + time_tmp = format_genTime(ra->last_init_start_time); + slapi_entry_add_string(e, "nsds5replicaLastInitStart", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + if (ra->last_init_end_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastInitEnd", "0"); + } + else + { + time_tmp = format_genTime(ra->last_init_end_time); + slapi_entry_add_string(e, "nsds5replicaLastInitEnd", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + if (ra->last_init_status[0] != '\0') + { + slapi_entry_add_string(e, "nsds5replicaLastInitStatus", ra->last_init_status); + } + } + return SLAPI_DSE_CALLBACK_OK; +} + +static void +update_window_state_change_callback (void *arg, PRBool opened) +{ + Repl_Agmt *agmt = (Repl_Agmt*)arg; + + PR_ASSERT (agmt); + + if (opened) + { + prot_notify_window_opened (agmt->protocol); + } + else + { + prot_notify_window_closed (agmt->protocol); + } +} + +ReplicaId +agmt_get_consumer_rid ( Repl_Agmt *agmt, void *conn ) +{ + if ( agmt->consumerRID <= 0 ) { + + char mapping_tree_node[512]; + struct berval **bvals = NULL; + + PR_snprintf ( mapping_tree_node, + sizeof (mapping_tree_node), + "cn=replica,cn=\"%s\",cn=mapping tree,cn=config", + slapi_sdn_get_dn (agmt->replarea) ); + conn_read_entry_attribute ( conn, mapping_tree_node, "nsDS5ReplicaID", &bvals ); + if ( NULL != bvals && NULL != bvals[0] ) { + char *ridstr = slapi_ch_malloc( bvals[0]->bv_len + 1 ); + memcpy ( ridstr, bvals[0]->bv_val, bvals[0]->bv_len ); + ridstr[bvals[0]->bv_len] = '\0'; + agmt->consumerRID = atoi (ridstr); + slapi_ch_free ( (void**) &ridstr ); + ber_bvecfree ( bvals ); + } + } + + return agmt->consumerRID; +} diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c new file mode 100644 index 00000000..5c7213a6 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_agmtlist.c @@ -0,0 +1,618 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_agmtlist.c */ +/* + + Replication agreements are held in object set (objset.c). + +*/ + +#include "repl5.h" + +#define AGMT_CONFIG_BASE "cn=mapping tree, cn=config" +#define CONFIG_FILTER "(objectclass=nsds5replicationagreement)" + +PRCallOnceType once = {0}; +static Objset *agmt_set = NULL; /* The set of replication agreements */ + +typedef struct agmt_wrapper { + Repl_Agmt *agmt; + void *handle; +} agmt_wrapper; + + + +/* + * Find the replication agreement whose entry DN matches the given DN. + * Object is returned referenced, so be sure to release it when + * finished. + */ +Repl_Agmt * +agmtlist_get_by_agmt_name(const Slapi_DN *agmt_name) +{ + Repl_Agmt *ra; + Object *ro; + + for (ro = objset_first_obj(agmt_set); NULL != ro; + ro = objset_next_obj(agmt_set, ro)) + { + ra = (Repl_Agmt *)object_get_data(ro); + if (agmt_matches_name(ra, agmt_name)) + { + break; + } + } + return ra; +} + + +static int +agmt_ptr_cmp(Object *ro, const void *arg) +{ + Repl_Agmt *ra; + Repl_Agmt *provided_ra = (Repl_Agmt *)arg; + + ra = object_get_data(ro); + + if (ra == provided_ra) + return 0; + else + return 1; +} + + + +static int +agmt_dn_cmp(Object *ro, const void *arg) +{ + Repl_Agmt *ra; + Slapi_DN *sdn = (Slapi_DN *)arg; + + ra = object_get_data(ro); + return(slapi_sdn_compare(sdn, agmt_get_dn_byref(ra))); +} + +void +agmtlist_release_agmt(Repl_Agmt *ra) +{ + Object *ro; + + PR_ASSERT(NULL != agmt_set); + PR_ASSERT(NULL != ra); + + ro = objset_find(agmt_set, agmt_ptr_cmp, (const void *)ra); + if (NULL != ro) + { + /* + * Release twice - once for the reference we got when finding + * it, and once for the reference we got when we called + * agmtlist_get_*(). + */ + object_release(ro); + object_release(ro); + } +} + + +/* + * Note: when we add the new object, we have a reference to it. We hold + * on to this reference until the agreement is deleted (or until the + * server is shut down). + */ +static int +add_new_agreement(Slapi_Entry *e) +{ + int rc = 0; + Repl_Agmt *ra = agmt_new_from_entry(e); + Slapi_DN *replarea_sdn = NULL; + Replica *replica = NULL; + Object *repl_obj = NULL; + Object *ro = NULL; + + if (ra == NULL) return 0; + + ro = object_new((void *)ra, agmt_delete); + objset_add_obj(agmt_set, ro); + object_release(ro); /* Object now owned by objset */ + + /* get the replica for this agreement */ + replarea_sdn = agmt_get_replarea(ra); + repl_obj = replica_get_replica_from_dn(replarea_sdn); + slapi_sdn_free(&replarea_sdn); + if (repl_obj) { + replica = (Replica*)object_get_data (repl_obj); + } + + rc = replica_start_agreement(replica, ra); + + if (repl_obj) object_release(repl_obj); + + return rc; +} + +static int +agmtlist_add_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, + int *returncode, char *returntext, void *arg) +{ + int rc; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmt_add: begin\n"); + + rc = add_new_agreement(e); + if (0 != rc) { + char *dn; + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_add_callback: " + "Can't start agreement \"%s\"\n", dn); + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; + } + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +static int +agmtlist_modify_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e, + int *returncode, char *returntext, void *arg) +{ + int i; + char *dn; + Slapi_DN *sdn = NULL; + int start_initialize = 0, stop_initialize = 0, cancel_initialize = 0; + int update_the_schedule = 0; /* do we need to update the repl sched? */ + Repl_Agmt *agmt = NULL; + LDAPMod **mods; + char buff [BUFSIZ]; + char *errortext = returntext ? returntext : buff; + int rc = SLAPI_DSE_CALLBACK_OK; + Slapi_Operation *op; + void *identity; + + *returncode = LDAP_SUCCESS; + + /* just let internal operations originated from replication plugin to go through */ + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + + if (operation_is_flag_set(op, OP_FLAG_INTERNAL) && + (identity == repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION))) + { + goto done; + } + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + sdn= slapi_sdn_new_dn_byref(dn); + agmt = agmtlist_get_by_agmt_name(sdn); + if (NULL == agmt) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_modify_callback: received " + "a modification for unknown replication agreement \"%s\"\n", dn); + goto done; + } + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (i = 0; NULL != mods && NULL != mods[i]; i++) + { + if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaInitialize)) + { + /* we don't allow delete attribute operations unless it was issued by + the replication plugin - handled above */ + if (mods[i]->mod_op & LDAP_MOD_DELETE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "deletion of %s attribute is not allowed\n", type_nsds5ReplicaInitialize); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + else + { + char *val; + + if (mods[i]->mod_bvalues && mods[i]->mod_bvalues[0]) + val = slapi_berval_get_string_copy (mods[i]->mod_bvalues[0]); + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "no value provided for %s attribute\n", type_nsds5ReplicaInitialize); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + + /* Start replica initialization */ + if (val == NULL) + { + sprintf (errortext, "No value supplied for attr (%s)", mods[i]->mod_type); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: %s\n", + errortext); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + + if (strcasecmp (val, "start") == 0) + { + start_initialize = 1; + } + else if (strcasecmp (val, "stop") == 0) + { + stop_initialize = 1; + } + else if (strcasecmp (val, "cancel") == 0) + { + cancel_initialize = 1; + } + else + { + sprintf (errortext, "Invalid value (%s) value supplied for attr (%s)", + val, mods[i]->mod_type); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: %s\n", + errortext); + } + + slapi_ch_free ((void**)&val); + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaUpdateSchedule)) + { + /* + * Request to update the replication schedule. Set a flag so + * we know to update the schedule later. + */ + update_the_schedule = 1; + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaCredentials)) + { + /* New replica credentials */ + if (agmt_set_credentials_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update credentials for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaTimeout)) + { + /* New replica timeout */ + if (agmt_set_timeout_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update timeout for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBusyWaitTime)) + { + /* New replica busywaittime */ + if (agmt_set_busywaittime_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update busy wait time for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaSessionPauseTime)) + { + /* New replica pausetime */ + if (agmt_set_pausetime_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update session pause time for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBindDN)) + { + /* New replica Bind DN */ + if (agmt_set_binddn_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update bind DN for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5TransportInfo)) + { + /* New Transport info */ + if (agmt_set_transportinfo_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update transport info for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBindMethod)) + { + /* New replica bind method */ + if (agmt_set_bind_method_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update bind method for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + "nsds5debugreplicatimeout")) + { + char *val = slapi_entry_attr_get_charptr(e, "nsds5debugreplicatimeout"); + repl5_set_debug_timeout(val); + slapi_ch_free_string(&val); + } + else if (strcasecmp (mods[i]->mod_type, "modifytimestamp") == 0 || + strcasecmp (mods[i]->mod_type, "modifiersname") == 0 || + strcasecmp (mods[i]->mod_type, "description") == 0) + { + /* ignore modifier's name and timestamp attributes and the description. */ + continue; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "modification of %s attribute is not allowed\n", mods[i]->mod_type); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + } + + if (stop_initialize) + { + agmt_stop (agmt); + } + else if (start_initialize) + { + if (agmt_initialize_replica(agmt) != 0) { + /* The suffix is disabled */ + agmt_set_last_init_status(agmt, 0, NSDS50_REPL_DISABLED, NULL); + } + } + else if (cancel_initialize) + { + agmt_replica_init_done(agmt); + } + + if (update_the_schedule) + { + if (agmt_set_schedule_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update replication schedule for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + +done: + if (NULL != agmt) + { + agmtlist_release_agmt(agmt); + } + + if (sdn) + slapi_sdn_free(&sdn); + return rc; +} + +static int +agmtlist_delete_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, + int *returncode, char *returntext, void *arg) +{ + Repl_Agmt *ra; + Object *ro; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_delete: begin\n"); + ro = objset_find(agmt_set, agmt_dn_cmp, (const void *)slapi_entry_get_sdn_const(e)); + ra = (NULL == ro) ? NULL : (Repl_Agmt *)object_get_data(ro); + if (NULL == ra) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_delete: " + "Tried to delete replication agreement \"%s\", but no such " + "agreement was configured.\n", slapi_sdn_get_dn(slapi_entry_get_sdn_const(e))); + } + else + { + agmt_stop(ra); + object_release(ro); /* Release ref acquired in objset_find */ + objset_remove_obj(agmt_set, ro); /* Releases a reference (should be final reference */ + } + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +static int +agmtlist_rename_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e, + int *returncode, char *returntext, void *arg) +{ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_rename: begin\n"); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + + +static int +handle_agmt_search(Slapi_Entry *e, void *callback_data) +{ + int *agmtcount = (int *)callback_data; + int rc; + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Found replication agreement named \"%s\".\n", + slapi_sdn_get_dn(slapi_entry_get_sdn(e))); + rc = add_new_agreement(e); + if (0 == rc) + { + (*agmtcount)++; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "The replication " + "agreement named \"%s\" could not be correctly parsed. No " + "replication will occur with this replica.\n", + slapi_sdn_get_dn(slapi_entry_get_sdn(e))); + } + + return rc; +} + + +static void +agmtlist_objset_destructor(void **o) +{ + /* XXXggood Nothing to do, I think. */ +} + + +int +agmtlist_config_init() +{ + Slapi_PBlock *pb; + int agmtcount = 0; + + agmt_set = objset_new(agmtlist_objset_destructor); + + /* Register callbacks so we're informed about updates */ + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_add_callback, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_modify_callback, NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_delete_callback, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_rename_callback, NULL); + + /* Search the DIT and find all the replication agreements */ + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, AGMT_CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, NULL /* attrs */, 0 /* attrsonly */, + NULL, /* controls */ NULL /* uniqueid */, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* actions */); + slapi_search_internal_callback_pb(pb, + (void *)&agmtcount /* callback data */, + NULL /* result_callback */, + handle_agmt_search /* search entry cb */, + NULL /* referral callback */); + slapi_pblock_destroy(pb); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_config_init: found %d replication agreements in DIT\n", agmtcount); + + return 0; +} + + + +void +agmtlist_shutdown() +{ + Repl_Agmt *ra; + Object *ro; + Object *next_ro; + + ro = objset_first_obj(agmt_set); + while (NULL != ro) + { + next_ro = objset_next_obj(agmt_set, ro); + ra = (Repl_Agmt *)object_get_data(ro); + agmt_stop(ra); + agmt_update_consumer_ruv (ra); + objset_remove_obj(agmt_set, ro); + ro = next_ro; + } + objset_delete(&agmt_set); + agmt_set = NULL; +} + + + +/* + * Notify each replication agreement about an update. + */ +void +agmtlist_notify_all(Slapi_PBlock *pb) +{ + Repl_Agmt *ra; + Object *ro; + + if (NULL != agmt_set) + { + ro = objset_first_obj(agmt_set); + while (NULL != ro) + { + ra = (Repl_Agmt *)object_get_data(ro); + agmt_notify_change(ra, pb); + ro = objset_next_obj(agmt_set, ro); + } + } +} + +Object* agmtlist_get_first_agreement_for_replica (Replica *r) +{ + return agmtlist_get_next_agreement_for_replica (r, NULL) ; +} + +Object* agmtlist_get_next_agreement_for_replica (Replica *r, Object *prev) +{ + const Slapi_DN *replica_root; + Slapi_DN *agmt_root; + Object *obj; + Repl_Agmt *agmt; + + if (r == NULL) + { + /* ONREPL - log error */ + return NULL; + } + + replica_root = replica_get_root(r); + + if (prev) + obj = objset_next_obj(agmt_set, prev); + else + obj = objset_first_obj(agmt_set); + + while (obj) + { + agmt = (Repl_Agmt*)object_get_data (obj); + PR_ASSERT (agmt); + + agmt_root = agmt_get_replarea(agmt); + PR_ASSERT (agmt_root); + + if (slapi_sdn_compare (replica_root, agmt_root) == 0) + { + slapi_sdn_free (&agmt_root); + return obj; + } + + slapi_sdn_free (&agmt_root); + obj = objset_next_obj(agmt_set, obj); + } + + return NULL; +} diff --git a/ldap/servers/plugins/replication/repl5_backoff.c b/ldap/servers/plugins/replication/repl5_backoff.c new file mode 100644 index 00000000..d0a90878 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_backoff.c @@ -0,0 +1,232 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_backoff.c */ +/* + + The backoff object implements a backoff timer. The timer can operate + with a fixed interval, an expontially increasing interval, or a + random interval. + + The caller creates a new backoff timer, specifying the backoff behavior + desired (fixed, exponential, or random), the initial backoff value, + and the maximum backoff interval. This does not start the timer - the + backoff_reset() function must be used to actually start the timer. + + The backoff_reset() function takes an optional function that + will be called when the backoff time has expired, and a void * + that can be used to pass arguments into the callback function. + + When the time expires, the callback function will be called. If no + callback function has been provided, the timer simply expires. + A timer does not recompute the next interval and begin timing until + the backoff_step() function is called. Therefore, callers that + do not install a callback function may use the timer by polling. + When a callback function is provided, the timer is typically reset + inside the callback function. + +*/ + +#include "repl5.h" + + +typedef struct backoff_timer { + int type; + int running; + slapi_eq_fn_t callback; + void *callback_arg; + time_t initial_interval; + time_t next_interval; + time_t max_interval; + time_t last_fire_time; + Slapi_Eq_Context pending_event; + PRLock *lock; + +} backoff_timer; + +/* Forward declarations */ +static PRIntervalTime random_interval_in_range(time_t lower_bound, time_t upper_bound); + + +/* + Create a new backoff timer. The timer is initialized, but is not + started. + */ +Backoff_Timer * +backoff_new(int timer_type, int initial_interval, int max_interval) +{ + Backoff_Timer *bt; + + bt = (Backoff_Timer *)slapi_ch_calloc(1, sizeof(struct backoff_timer)); + bt->type = timer_type; + bt->initial_interval = initial_interval; + bt->next_interval = bt->initial_interval; + bt->max_interval = max_interval; + bt->running = 0; + if ((bt->lock = PR_NewLock()) == NULL) + { + slapi_ch_free((void **)&bt); + } + return bt; +} + + +/* + * Reset and start the timer. Returns the time (as a time_t) when the + * time will next expire. + */ +time_t +backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data) +{ + time_t return_value = 0UL; + + PR_ASSERT(NULL != bt); + PR_ASSERT(NULL != callback); + + PR_Lock(bt->lock); + bt->running = 1; + bt->callback = callback; + bt->callback_arg = callback_data; + /* Cancel any pending events in the event queue */ + if (NULL != bt->pending_event) + { + slapi_eq_cancel(bt->pending_event); + bt->pending_event = NULL; + } + /* Compute the first fire time */ + if (BACKOFF_RANDOM == bt->type) + { + bt->next_interval = random_interval_in_range(bt->initial_interval, + bt->max_interval); + } + else + { + bt->next_interval = bt->initial_interval; + } + /* Schedule the callback */ + time(&bt->last_fire_time); + return_value = bt->last_fire_time + bt->next_interval; + bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg, + return_value); + PR_Unlock(bt->lock); + return return_value; +} + + +/* + Step the timer - compute the new backoff interval and start + counting. Note that the next expiration time is based on the + last timer expiration time, *not* the current time. + + Returns the time (as a time_t) when the timer will next expire. + */ +time_t +backoff_step(Backoff_Timer *bt) +{ + time_t return_value = 0UL; + + PR_ASSERT(NULL != bt); + + /* If the timer has never been reset, then return 0 */ + PR_Lock(bt->lock); + if (bt->running) + { + time_t previous_interval = bt->next_interval; + switch (bt->type) { + case BACKOFF_FIXED: + /* Interval stays the same */ + break; + case BACKOFF_EXPONENTIAL: + /* Interval doubles, up to a maximum */ + if (bt->next_interval < bt->max_interval) + { + bt->next_interval *= 2; + if (bt->next_interval > bt->max_interval) + { + bt->next_interval = bt->max_interval; + } + } + break; + case BACKOFF_RANDOM: + /* Compute the new random interval time */ + bt->next_interval = random_interval_in_range(bt->initial_interval, + bt->max_interval); + break; + } + /* Schedule the callback, if any */ + bt->last_fire_time += previous_interval; + return_value = bt->last_fire_time + bt->next_interval; + bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg, + return_value); + } + PR_Unlock(bt->lock); + return return_value; +} + + +/* + * Return 1 if the backoff timer has expired, 0 otherwise. + */ +int +backoff_expired(Backoff_Timer *bt, int margin) +{ + int return_value = 0; + + PR_ASSERT(NULL != bt); + PR_Lock(bt->lock); + return_value = (current_time() >= (bt->last_fire_time + bt->next_interval + margin)); + PR_Unlock(bt->lock); + return return_value; +} + + +/* + Destroy and deallocate a timer object + */ +void +backoff_delete(Backoff_Timer **btp) +{ + Backoff_Timer *bt; + + PR_ASSERT(NULL != btp && NULL != *btp); + bt = *btp; + PR_Lock(bt->lock); + /* Cancel any pending events in the event queue */ + if (NULL != bt->pending_event) + { + slapi_eq_cancel(bt->pending_event); + } + PR_Unlock(bt->lock); + PR_DestroyLock(bt->lock); + slapi_ch_free((void **)btp); +} + + +/* + * Return the next fire time for the timer. + */ +time_t +backoff_get_next_fire_time(Backoff_Timer *bt) +{ + time_t return_value; + + PR_ASSERT(NULL != bt); + PR_Lock(bt->lock); + return_value = bt->last_fire_time + bt->next_interval; + PR_Unlock(bt->lock); + return return_value; +} + +static PRIntervalTime +random_interval_in_range(time_t lower_bound, time_t upper_bound) +{ + /* + * slapi_rand() provides some entropy from two or three system timer + * calls (depending on the platform) down in NSS. If more entropy is + * required, slapi_rand_r(unsigned int *seed) can be called instead. + */ + return(lower_bound + (slapi_rand() % (upper_bound - lower_bound))); +} + diff --git a/ldap/servers/plugins/replication/repl5_connection.c b/ldap/servers/plugins/replication/repl5_connection.c new file mode 100644 index 00000000..a50c163a --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_connection.c @@ -0,0 +1,1493 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_connection.c */ +/* + + The connection object manages a connection to a single replication + consumer. + +XXXggood what to do on timeout? If we close connection, then we won't leave a +replica locked. Seems like right thing to do. +*/ + +#include "repl5.h" +#include "ldappr.h" + +typedef struct repl_connection +{ + char *hostname; + int port; + char *binddn; + int bindmethod; + int state; + int last_operation; + int last_ldap_error; + const char *status; + char *last_ldap_errmsg; + PRUint32 transport_flags; + LDAP *ld; + int supports_ldapv3; /* 1 if does, 0 if doesn't, -1 if not determined */ + int supports_ds50_repl; /* 1 if does, 0 if doesn't, -1 if not determined */ + int supports_ds40_repl; /* 1 if does, 0 if doesn't, -1 if not determined */ + int linger_time; /* time in seconds to leave an idle connection open */ + PRBool linger_active; + Slapi_Eq_Context *linger_event; + PRBool delete_after_linger; + int refcnt; + const Repl_Agmt *agmt; + PRLock *lock; + struct timeval timeout; + int flag_agmt_changed; + char *plain; +} repl_connection; + +/* #define DEFAULT_LINGER_TIME (5 * 60) */ /* 5 minutes */ +#define DEFAULT_LINGER_TIME (60) + +/* Controls we add on every outbound operation */ + +static LDAPControl manageDSAITControl = {LDAP_CONTROL_MANAGEDSAIT, {0, ""}, '\0'}; +static int attribute_string_value_present(LDAP *ld, LDAPMessage *entry, + const char *type, const char *value); +static int bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password); +static int do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password); + +static int s_debug_timeout = 0; +static int s_debug_level = 0; +static Slapi_Eq_Context repl5_start_debug_timeout(int *setlevel); +static void repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel); +static void repl5_debug_timeout_callback(time_t when, void *arg); +#ifndef DSE_RETURNTEXT_SIZE +#define SLAPI_DSE_RETURNTEXT_SIZE 512 +#endif + +#define STATE_CONNECTED 600 +#define STATE_DISCONNECTED 601 + +#define STATUS_DISCONNECTED "disconnected" +#define STATUS_CONNECTED "connected" +#define STATUS_PROCESSING_ADD "processing add operation" +#define STATUS_PROCESSING_DELETE "processing delete operation" +#define STATUS_PROCESSING_MODIFY "processing modify operation" +#define STATUS_PROCESSING_RENAME "processing rename operation" +#define STATUS_PROCESSING_EXTENDED_OPERATION "processing extended operation" +#define STATUS_LINGERING "lingering" +#define STATUS_SHUTTING_DOWN "shutting down" +#define STATUS_BINDING "connecting and binding" +#define STATUS_SEARCHING "processing search operation" + +#define CONN_NO_OPERATION 0 +#define CONN_ADD 1 +#define CONN_DELETE 2 +#define CONN_MODIFY 3 +#define CONN_RENAME 4 +#define CONN_EXTENDED_OPERATION 5 +#define CONN_BIND 6 +#define CONN_INIT 7 + +/* These are errors returned from ldap operations which should cause us to disconnect and + retry the connection later */ +#define IS_DISCONNECT_ERROR(rc) (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR || rc == LDAP_INVALID_CREDENTIALS || rc == LDAP_INAPPROPRIATE_AUTH || rc == LDAP_LOCAL_ERROR) + +/* Forward declarations */ +static void close_connection_internal(Repl_Connection *conn); + +/* + * Create a new conenction object. Returns a pointer to the object, or + * NULL if an error occurs. + */ +Repl_Connection * +conn_new(Repl_Agmt *agmt) +{ + Repl_Connection *rpc; + + rpc = (Repl_Connection *)slapi_ch_malloc(sizeof(repl_connection)); + if ((rpc->lock = PR_NewLock()) == NULL) + { + goto loser; + } + rpc->hostname = agmt_get_hostname(agmt); + rpc->port = agmt_get_port(agmt); + rpc->binddn = agmt_get_binddn(agmt); + rpc->bindmethod = agmt_get_bindmethod(agmt); + rpc->transport_flags = agmt_get_transport_flags(agmt); + rpc->ld = NULL; + rpc->state = STATE_DISCONNECTED; + rpc->last_operation = CONN_NO_OPERATION; + rpc->last_ldap_error = LDAP_SUCCESS; + rpc->last_ldap_errmsg = NULL; + rpc->supports_ldapv3 = -1; + rpc->supports_ds40_repl = -1; + rpc->supports_ds50_repl = -1; + rpc->linger_active = PR_FALSE; + rpc->delete_after_linger = PR_FALSE; + rpc->linger_event = NULL; + rpc->linger_time = DEFAULT_LINGER_TIME; + rpc->status = STATUS_DISCONNECTED; + rpc->agmt = agmt; + rpc->refcnt = 1; + rpc->timeout.tv_sec = agmt_get_timeout(agmt); + rpc->timeout.tv_usec = 0; + rpc->flag_agmt_changed = 0; + rpc->plain = NULL; + return rpc; +loser: + conn_delete(rpc); + return NULL; +} + + +/* + * Return PR_TRUE if the connection is in the connected state + */ +static PRBool +conn_connected(Repl_Connection *conn) +{ + PRBool return_value; + PR_Lock(conn->lock); + return_value = STATE_CONNECTED == conn->state; + PR_Unlock(conn->lock); + return return_value; +} + + +/* + * Destroy a connection object. + */ +static void +conn_delete_internal(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + close_connection_internal(conn); + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&conn->hostname); + slapi_ch_free((void **)&conn->binddn); + slapi_ch_free((void **)&conn->plain); +} + +/* + * Destroy a connection. It is an error to use the connection object + * after conn_delete() has been called. + */ +void +conn_delete(Repl_Connection *conn) +{ + PRBool destroy_it = PR_FALSE; + + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + if (conn->linger_active) + { + if (slapi_eq_cancel(conn->linger_event) == 1) + { + /* Event was found and cancelled. Destroy the connection object. */ + PR_Unlock(conn->lock); + destroy_it = PR_TRUE; + } + else + { + /* + * The event wasn't found, but we think it's still active. + * That means an event is in the process of being fired + * off, so arrange for the event to destroy the object . + */ + conn->delete_after_linger = PR_TRUE; + PR_Unlock(conn->lock); + } + } + if (destroy_it) + { + conn_delete_internal(conn); + } +} + + +/* + * Return the last operation type processed by the connection + * object, and the LDAP error encountered. + */ +void +conn_get_error(Repl_Connection *conn, int *operation, int *error) +{ + PR_Lock(conn->lock); + *operation = conn->last_operation; + *error = conn->last_ldap_error; + PR_Unlock(conn->lock); +} + + +/* + * Common code to send an LDAPv3 operation and collect the result. + * Return values: + * CONN_OPERATION_SUCCESS - the operation succeeded + * CONN_OPERATION_FAILED - the operation was sent to the consumer + * and failed. Use conn_get_error() to determine the LDAP error + * code. + * CONN_NOT_CONNECTED - no connection is active. The caller should + * use conn_connect() to connect to the replica and bind, then should + * reacquire the replica (if needed). + * CONN_BUSY - the server is busy with previous requests, must wait for a while + * before retrying + */ +static ConnResult +perform_operation(Repl_Connection *conn, int optype, const char *dn, + LDAPMod **attrs, const char *newrdn, const char *newparent, + int deleteoldrdn, LDAPControl *update_control, + const char *extop_oid, struct berval *extop_payload, char **retoidp, + struct berval **retdatap, LDAPControl ***returned_controls) +{ + int rc; + ConnResult return_value; + LDAPControl *server_controls[3]; + LDAPControl **loc_returned_controls; + const char *op_string = NULL; + const char *extra_op_string = NULL; + + server_controls[0] = &manageDSAITControl; + server_controls[1] = update_control; + server_controls[2] = NULL; + + if (conn_connected(conn)) + { + int msgid; + + conn->last_operation = optype; + switch (optype) + { + case CONN_ADD: + conn->status = STATUS_PROCESSING_ADD; + op_string = "add"; + rc = ldap_add_ext(conn->ld, dn, attrs, server_controls, + NULL /* clientctls */, &msgid); + break; + case CONN_MODIFY: + conn->status = STATUS_PROCESSING_MODIFY; + op_string = "modify"; + rc = ldap_modify_ext(conn->ld, dn, attrs, server_controls, + NULL /* clientctls */, &msgid); + break; + case CONN_DELETE: + conn->status = STATUS_PROCESSING_DELETE; + op_string = "delete"; + rc = ldap_delete_ext(conn->ld, dn, server_controls, + NULL /* clientctls */, &msgid); + break; + case CONN_RENAME: + conn->status = STATUS_PROCESSING_RENAME; + op_string = "rename"; + rc = ldap_rename(conn->ld, dn, newrdn, newparent, deleteoldrdn, + server_controls, NULL /* clientctls */, &msgid); + break; + case CONN_EXTENDED_OPERATION: + conn->status = STATUS_PROCESSING_EXTENDED_OPERATION; + op_string = "extended"; + extra_op_string = extop_oid; + rc = ldap_extended_operation(conn->ld, extop_oid, extop_payload, + server_controls, NULL /* clientctls */, &msgid); + } + if (LDAP_SUCCESS == rc) + { + LDAPMessage *res = NULL; + int setlevel = 0; + Slapi_Eq_Context eqctx = repl5_start_debug_timeout(&setlevel); + + rc = ldap_result(conn->ld, msgid, 1, &conn->timeout, &res); + repl5_stop_debug_timeout(eqctx, &setlevel); + if (0 == rc) + { + /* Timeout */ + rc = ldap_get_lderrno(conn->ld, NULL, NULL); + conn->last_ldap_error = LDAP_TIMEOUT; + return_value = CONN_TIMEOUT; + } + else if (-1 == rc) + { + /* Error */ + char *s = NULL; + + rc = ldap_get_lderrno(conn->ld, NULL, &s); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Received error %d: %s for %s operation\n", + agmt_get_long_name(conn->agmt), + rc, s ? s : "NULL", + op_string ? op_string : "NULL"); + conn->last_ldap_error = rc; + /* some errors will require a disconnect and retry the connection + later */ + if (IS_DISCONNECT_ERROR(rc)) + { + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + conn->status = STATUS_CONNECTED; + return_value = CONN_OPERATION_FAILED; + } + } + else + { + int err; + char *errmsg = NULL; + char **referrals = NULL; + char *matched = NULL; + + rc = ldap_parse_result(conn->ld, res, &err, &matched, + &errmsg, &referrals, &loc_returned_controls, + 0 /* Don't free the result */); + if (IS_DISCONNECT_ERROR(rc)) + { + conn->last_ldap_error = rc; + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else if (IS_DISCONNECT_ERROR(err)) + { + conn->last_ldap_error = err; + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + /* Got a result */ + else if (CONN_EXTENDED_OPERATION == optype) + { + if ((rc == LDAP_SUCCESS) && (err == LDAP_BUSY)) + return_value = CONN_BUSY; + else { + if (rc == LDAP_SUCCESS) { + rc = ldap_parse_extended_result(conn->ld, res, retoidp, + retdatap, 0 /* Don't Free it */); + } + conn->last_ldap_error = rc; + return_value = (LDAP_SUCCESS == conn->last_ldap_error ? + CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED); + } + } + else /* regular operation, result returned */ + { + if (NULL != returned_controls) + { + *returned_controls = loc_returned_controls; + } + if (LDAP_SUCCESS != rc) + { + conn->last_ldap_error = rc; + } + else + { + conn->last_ldap_error = err; + } + return_value = LDAP_SUCCESS == conn->last_ldap_error ? CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED; + } + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Received result code %d for %s operation %s%s\n", + agmt_get_long_name(conn->agmt), + conn->last_ldap_error, + op_string == NULL ? "" : op_string, + extra_op_string == NULL ? "" : extra_op_string, + extra_op_string == NULL ? "" : " "); + /* + * XXXggood do I need to free matched, referrals, + * anything else? Or can I pass NULL for the args + * I'm not interested in? + */ + /* Good question! Meanwhile, as RTM aproaches, let's free them... */ + slapi_ch_free((void **) &errmsg); + slapi_ch_free((void **) &matched); + charray_free(referrals); + conn->status = STATUS_CONNECTED; + } + if (res) ldap_msgfree(res); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Failed to send %s operation: LDAP error %d (%s)\n", + agmt_get_long_name(conn->agmt), + op_string ? op_string : "NULL", rc, ldap_err2string(rc)); + conn->last_ldap_error = rc; + if (IS_DISCONNECT_ERROR(rc)) + { + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + conn->status = STATUS_CONNECTED; + return_value = CONN_OPERATION_FAILED; + } + } + } + else + { + /* conn->last_ldap_error has been set to a more specific value + * in conn_connected() + * conn->last_ldap_error = LDAP_SERVER_DOWN; + */ + return_value = CONN_NOT_CONNECTED; + } + return return_value; +} + +/* + * Send an LDAP add operation. + */ +ConnResult +conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_ADD, dn, attrs, NULL /* newrdn */, + NULL /* newparent */, 0 /* deleteoldrdn */, update_control, + NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */, + NULL /* retdatap */, returned_controls); +} + + +/* + * Send an LDAP delete operation. + */ +ConnResult +conn_send_delete(Repl_Connection *conn, const char *dn, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_DELETE, dn, NULL /* attrs */, + NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */, + update_control, NULL /* extop OID */, NULL /* extop payload */, + NULL /* retoidp */, NULL /* retdatap */, returned_controls); +} + + +/* + * Send an LDAP modify operation. + */ +ConnResult +conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_MODIFY, dn, mods, NULL /* newrdn */, + NULL /* newparent */, 0 /* deleteoldrdn */, update_control, + NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */, + NULL /* retdatap */, returned_controls); +} + +/* + * Send an LDAP moddn operation. + */ +ConnResult +conn_send_rename(Repl_Connection *conn, const char *dn, + const char *newrdn, const char *newparent, int deleteoldrdn, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_RENAME, dn, NULL /* attrs */, + newrdn, newparent, deleteoldrdn, update_control, + NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */, + NULL /* retdatap */, returned_controls); +} + + +/* + * Send an LDAP extended operation. + */ +ConnResult +conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid, + struct berval *payload, char **retoidp, struct berval **retdatap, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_EXTENDED_OPERATION, NULL /* dn */, NULL /* attrs */, + NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */, + update_control, extop_oid, payload, retoidp, retdatap, + returned_controls); +} + + +/* + * Synchronously read an entry and return a specific attribute's values. + * Returns CONN_OPERATION_SUCCESS if successful. Returns + * CONN_OPERATION_FAILED if the operation was sent but an LDAP error + * occurred (conn->last_ldap_error is set in this case), and + * CONN_NOT_CONNECTED if no connection was active. + * + * The caller must free the returned_bvals. + */ +ConnResult +conn_read_entry_attribute(Repl_Connection *conn, const char *dn, + char *type, struct berval ***returned_bvals) +{ + ConnResult return_value; + int ldap_rc; + LDAPControl *server_controls[2]; + LDAPMessage *res = NULL; + char *attrs[2]; + + PR_ASSERT(NULL != type); + if (conn_connected(conn)) + { + server_controls[0] = &manageDSAITControl; + server_controls[1] = NULL; + attrs[0] = type; + attrs[1] = NULL; + ldap_rc = ldap_search_ext_s(conn->ld, dn, LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, 0 /* attrsonly */, + server_controls, NULL /* client controls */, + &conn->timeout, 0 /* sizelimit */, &res); + if (LDAP_SUCCESS == ldap_rc) + { + LDAPMessage *entry = ldap_first_entry(conn->ld, res); + if (NULL != entry) + { + *returned_bvals = ldap_get_values_len(conn->ld, entry, type); + } + return_value = CONN_OPERATION_SUCCESS; + } + else if (IS_DISCONNECT_ERROR(ldap_rc)) + { + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + return_value = CONN_OPERATION_FAILED; + } + conn->last_ldap_error = ldap_rc; + if (NULL != res) + { + ldap_msgfree(res); + res = NULL; + } + } + else + { + return_value = CONN_NOT_CONNECTED; + } + return return_value; +} + + +/* + * Return an pointer to a string describing the connection's status. +*/ + +const char * +conn_get_status(Repl_Connection *conn) +{ + return conn->status; +} + + + +/* + * Cancel any outstanding linger timer. Should be called when + * a replication session is beginning. + */ +void +conn_cancel_linger(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + if (conn->linger_active) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Cancelling linger on the connection\n", + agmt_get_long_name(conn->agmt)); + conn->linger_active = PR_FALSE; + if (slapi_eq_cancel(conn->linger_event) == 1) + { + conn->refcnt--; + } + conn->linger_event = NULL; + conn->status = STATUS_CONNECTED; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No linger to cancel on the connection\n", + agmt_get_long_name(conn->agmt)); + } + PR_Unlock(conn->lock); +} + + +/* + * Called when our linger timeout timer expires. This means + * we should check to see if perhaps the connection's become + * active again, in which case we do nothing. Otherwise, + * we close the connection. + */ +static void +linger_timeout(time_t event_time, void *arg) +{ + PRBool delete_now; + Repl_Connection *conn = (Repl_Connection *)arg; + + PR_ASSERT(NULL != conn); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Linger timeout has expired on the connection\n", + agmt_get_long_name(conn->agmt)); + PR_Lock(conn->lock); + if (conn->linger_active) + { + conn->linger_active = PR_FALSE; + conn->linger_event = NULL; + close_connection_internal(conn); + } + delete_now = conn->delete_after_linger; + PR_Unlock(conn->lock); + if (delete_now) + { + conn_delete_internal(conn); + } +} + + +/* + * Indicate that a session is ending. The linger timer starts when + * this function is called. + */ +void +conn_start_linger(Repl_Connection *conn) +{ + time_t now; + + PR_ASSERT(NULL != conn); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Beginning linger on the connection\n", + agmt_get_long_name(conn->agmt)); + if (!conn_connected(conn)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No linger on the closed conn\n", + agmt_get_long_name(conn->agmt)); + return; + } + time(&now); + PR_Lock(conn->lock); + if (conn->linger_active) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Linger already active on the connection\n", + agmt_get_long_name(conn->agmt)); + } + else + { + conn->linger_active = PR_TRUE; + conn->linger_event = slapi_eq_once(linger_timeout, conn, now + conn->linger_time); + conn->status = STATUS_LINGERING; + } + PR_Unlock(conn->lock); +} + + + +/* + * If no connection is currently active, opens a connection and binds to + * the remote server. If a connection is open (e.g. lingering) then + * this is a no-op. + * + * Returns CONN_OPERATION_SUCCESS on success, or CONN_OPERATION_FAILED + * on failure. Sets conn->last_ldap_error and conn->last_operation; + */ +ConnResult +conn_connect(Repl_Connection *conn) +{ + int ldap_rc; + int optdata; + int secure = 0; + char* binddn = NULL; + struct berval *creds; + ConnResult return_value = CONN_OPERATION_SUCCESS; + int pw_ret = 1; + + /** Connection already open just return SUCCESS **/ + if(conn->state == STATE_CONNECTED) return return_value; + + PR_Lock(conn->lock); + if (conn->flag_agmt_changed) { + /* So far we cannot change Hostname and Port */ + /* slapi_ch_free((void **)&conn->hostname); */ + /* conn->hostname = agmt_get_hostname(conn->agmt); */ + /* conn->port = agmt_get_port(conn->agmt); */ + slapi_ch_free((void **)&conn->binddn); + conn->binddn = agmt_get_binddn(conn->agmt); + conn->bindmethod = agmt_get_bindmethod(conn->agmt); + conn->transport_flags = agmt_get_transport_flags(conn->agmt); + conn->timeout.tv_sec = agmt_get_timeout(conn->agmt); + conn->flag_agmt_changed = 0; + slapi_ch_free((void **)&conn->plain); + } + PR_Unlock(conn->lock); + + creds = agmt_get_credentials(conn->agmt); + + if (conn->plain == NULL) { + + char *plain = NULL; + + /* kexcoff: for reversible encryption */ + /* We need to test the return code of pw_rever_decode in order to decide + * if a free for plain will be needed (pw_ret == 0) or not (pw_ret != 0) */ + pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaCredentials); + /* Pb occured in decryption: stop now, binding will fail */ + if ( pw_ret == -1 ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Decoding of the credentials failed.\n", + agmt_get_long_name(conn->agmt)); + + return_value = CONN_OPERATION_FAILED; + conn->last_ldap_error = LDAP_INVALID_CREDENTIALS; + conn->state = STATE_DISCONNECTED; + return (return_value); + } /* Else, does not mean that the plain is correct, only means the we had no internal + decoding pb */ + conn->plain = slapi_ch_strdup (plain); + if (!pw_ret) slapi_ch_free((void**)&plain); + } + + + /* ugaston: if SSL has been selected in the replication agreement, SSL client + * initialisation should be done before ever trying to open any connection at all. + */ + if (conn->transport_flags == TRANSPORT_FLAG_TLS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication secured by StartTLS not currently supported\n", + agmt_get_long_name(conn->agmt)); + + return_value = CONN_OPERATION_FAILED; + conn->last_ldap_error = LDAP_STRONG_AUTH_NOT_SUPPORTED; + conn->state = STATE_DISCONNECTED; + } else if(conn->transport_flags == TRANSPORT_FLAG_SSL) + { + + /** Make sure the SSL Library has been initialized before anything else **/ + if(slapd_security_library_is_initialized() != 1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: SSL Not Initialized, Replication over SSL FAILED\n", + agmt_get_long_name(conn->agmt)); + conn->last_ldap_error = LDAP_INAPPROPRIATE_AUTH; + conn->last_operation = CONN_INIT; + ber_bvfree(creds); + creds = NULL; + return CONN_SSL_NOT_ENABLED; + } else + { + secure = 1; + } + } + + if (return_value == CONN_OPERATION_SUCCESS) { + int io_timeout_ms; + /* Now we initialize the LDAP Structure and set options */ + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Trying %s slapi_ldap_init\n", + agmt_get_long_name(conn->agmt), + secure ? "secure" : "non-secure"); + + conn->ld = slapi_ldap_init(conn->hostname, conn->port, secure, 0); + if (NULL == conn->ld) + { + return_value = CONN_OPERATION_FAILED; + conn->state = STATE_DISCONNECTED; + conn->last_operation = CONN_INIT; + conn->last_ldap_error = LDAP_LOCAL_ERROR; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Failed to establish %sconnection to the consumer\n", + agmt_get_long_name(conn->agmt), + secure ? "secure " : ""); + ber_bvfree(creds); + creds = NULL; + return return_value; + } + + /* slapi_ch_strdup is OK with NULL strings */ + binddn = slapi_ch_strdup(conn->binddn); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: binddn = %s, passwd = %s\n", + agmt_get_long_name(conn->agmt), + binddn?binddn:"NULL", creds->bv_val?creds->bv_val:"NULL"); + + /* Set some options for the connection. */ + optdata = LDAP_DEREF_NEVER; /* Don't dereference aliases */ + ldap_set_option(conn->ld, LDAP_OPT_DEREF, &optdata); + + optdata = LDAP_VERSION3; /* We need LDAP version 3 */ + ldap_set_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, &optdata); + + /* Don't chase any referrals (although we shouldn't get any) */ + ldap_set_option(conn->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); + + /* override the default timeout with the specified timeout */ + io_timeout_ms = conn->timeout.tv_sec * 1000 + conn->timeout.tv_usec / 1000; + prldap_set_session_option(conn->ld, NULL, PRLDAP_OPT_IO_MAX_TIMEOUT, + io_timeout_ms); + + /* We've got an ld. Now bind to the server. */ + conn->last_operation = CONN_BIND; + + } + + if ( bind_and_check_pwp(conn, binddn, conn->plain) == CONN_OPERATION_FAILED ) + { + conn->last_ldap_error = ldap_get_lderrno (conn->ld, NULL, NULL); + conn->state = STATE_DISCONNECTED; + return_value = CONN_OPERATION_FAILED; + } + else + { + conn->last_ldap_error = ldap_rc = LDAP_SUCCESS; + conn->state = STATE_CONNECTED; + return_value = CONN_OPERATION_SUCCESS; + } + + + ber_bvfree(creds); + creds = NULL; + + slapi_ch_free((void**)&binddn); + + if(return_value == CONN_OPERATION_FAILED) + { + close_connection_internal(conn); + } else + { + conn->last_ldap_error = ldap_rc = LDAP_SUCCESS; + conn->state = STATE_CONNECTED; + } + + return return_value; +} + + +static void +close_connection_internal(Repl_Connection *conn) +{ + if (NULL != conn->ld) + { + /* Since we call slapi_ldap_init, + we must call slapi_ldap_unbind */ + slapi_ldap_unbind(conn->ld); + } + conn->ld = NULL; + conn->state = STATE_DISCONNECTED; + conn->status = STATUS_DISCONNECTED; + conn->supports_ds50_repl = -1; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Disconnected from the consumer\n", agmt_get_long_name(conn->agmt)); +} + +void +conn_disconnect(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + close_connection_internal(conn); + PR_Unlock(conn->lock); +} + + +/* + * Determine if the remote replica supports DS 5.0 replication. + * Return codes: + * CONN_SUPPORTS_DS5_REPL - the remote replica suport DS5 replication + * CONN_DOES_NOT_SUPPORT_DS5_REPL - the remote replica does not + * support DS5 replication. + * CONN_OPERATION_FAILED - it could not be determined if the remote + * replica supports DS5 replication. + * CONN_NOT_CONNECTED - no connection was active. + */ +ConnResult +conn_replica_supports_ds5_repl(Repl_Connection *conn) +{ + ConnResult return_value; + int ldap_rc; + + if (conn_connected(conn)) + { + if (conn->supports_ds50_repl == -1) { + LDAPMessage *res = NULL; + LDAPMessage *entry = NULL; + char *attrs[] = {"supportedcontrol", "supportedextension", NULL}; + + conn->status = STATUS_SEARCHING; + ldap_rc = ldap_search_ext_s(conn->ld, "", LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, 0 /* attrsonly */, + NULL /* server controls */, NULL /* client controls */, + &conn->timeout, LDAP_NO_LIMIT, &res); + if (LDAP_SUCCESS == ldap_rc) + { + conn->supports_ds50_repl = 0; + entry = ldap_first_entry(conn->ld, res); + if (!attribute_string_value_present(conn->ld, entry, "supportedcontrol", REPL_NSDS50_UPDATE_INFO_CONTROL_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_START_NSDS50_REPLICATION_REQUEST_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_END_NSDS50_REPLICATION_REQUEST_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_RESPONSE_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else + { + conn->supports_ds50_repl = 1; + return_value = CONN_SUPPORTS_DS5_REPL; + } + } + else + { + if (IS_DISCONNECT_ERROR(ldap_rc)) + { + conn->last_ldap_error = ldap_rc; /* specific reason */ + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + return_value = CONN_OPERATION_FAILED; + } + } + if (NULL != res) + ldap_msgfree(res); + } + else { + return_value = conn->supports_ds50_repl ? CONN_SUPPORTS_DS5_REPL : CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + } + else + { + /* Not connected */ + return_value = CONN_NOT_CONNECTED; + } + return return_value; +} + + + + + +/* + * Return 1 if "value" is a value of attribute type "type" in entry "entry". + * Otherwise, return 0. + */ +static int +attribute_string_value_present(LDAP *ld, LDAPMessage *entry, const char *type, + const char *value) +{ + int return_value = 0; + + if (NULL != entry) + { + char *atype = NULL; + BerElement *ber = NULL; + + atype = ldap_first_attribute(ld, entry, &ber); + while (NULL != atype && 0 == return_value) + { + if (strcasecmp(atype, type) == 0) + { + char **strvals = ldap_get_values(ld, entry, atype); + int i; + for (i = 0; return_value == 0 && NULL != strvals && NULL != strvals[i]; i++) + { + if (strcmp(strvals[i], value) == 0) + { + return_value = 1; + } + } + if (NULL != strvals) + { + ldap_value_free(strvals); + } + } + ldap_memfree(atype); + atype = ldap_next_attribute(ld, entry, ber); + } + if (NULL != ber) + ldap_ber_free(ber, 0); + /* The last atype has not been freed yet */ + if (NULL != atype) + ldap_memfree(atype); + } + return return_value; +} + + + + +/* + * Read the remote server's schema entry, then read the local schema entry, + * and compare the nsschemacsn attribute. If the local csn is newer, or + * the remote csn is absent, push the schema down to the consumer. + * Return codes: + * CONN_SCHEMA_UPDATED if the schema was pushed successfully + * CONN_SCHEMA_NO_UPDATE_NEEDED if the schema was as new or newer than + * the local server's schema + * CONN_OPERATION_FAILED if an error occurred + * CONN_NOT_CONNECTED if no connection was active + * NOTE: Should only be called when a replication session has been + * established by sending a startReplication extended operation. + */ +ConnResult +conn_push_schema(Repl_Connection *conn, CSN **remotecsn) +{ + ConnResult return_value = CONN_OPERATION_SUCCESS; + char *nsschemacsn = "nsschemacsn"; + Slapi_Entry **entries = NULL; + Slapi_Entry *schema_entry = NULL; + int push_schema = 1; /* Assume we need to push for now */ + int local_error = 0; /* No local error encountered yet */ + int remote_error = 0; /* No remote error encountered yet */ + CSN *localcsn = NULL; + Slapi_PBlock *spb = NULL; + char localcsnstr[CSN_STRSIZE + 1] = {0}; + + if (!conn_connected(conn)) + { + return_value = CONN_NOT_CONNECTED; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Schema replication update failed: not connected to consumer\n", + agmt_get_long_name(conn->agmt)); + } + else + { + localcsn = dup_global_schema_csn(); + if (NULL == localcsn) + { + /* Local server has epoch CSN, so don't push schema */ + return_value = CONN_SCHEMA_NO_UPDATE_NEEDED; + } + else if ( remotecsn && *remotecsn && csn_compare(localcsn, *remotecsn) <= 0 ) + { + /* Local server schema is not newer than the remote one */ + return_value = CONN_SCHEMA_NO_UPDATE_NEEDED; + } + else + { + struct berval **remote_schema_csn_bervals = NULL; + /* Get remote server's schema */ + return_value = conn_read_entry_attribute(conn, "cn=schema", nsschemacsn, + &remote_schema_csn_bervals); + if (CONN_OPERATION_SUCCESS == return_value) + { + if (NULL != remote_schema_csn_bervals && NULL != remote_schema_csn_bervals[0]) + { + char remotecsnstr[CSN_STRSIZE + 1] = {0}; + memcpy(remotecsnstr, remote_schema_csn_bervals[0]->bv_val, + remote_schema_csn_bervals[0]->bv_len); + remotecsnstr[remote_schema_csn_bervals[0]->bv_len] = '\0'; + *remotecsn = csn_new_by_string(remotecsnstr); + if (NULL != remotecsn && (csn_compare(localcsn, *remotecsn) <= 0)) + { + return_value = CONN_SCHEMA_NO_UPDATE_NEEDED; + } + /* Need to free the remote_schema_csn_bervals */ + ber_bvecfree(remote_schema_csn_bervals); + } + } + } + } + if (CONN_OPERATION_SUCCESS == return_value) + { + /* We know we need to push the schema out. */ + LDAPMod ocmod = {0}; + LDAPMod atmod = {0}; + LDAPMod csnmod = {0}; + LDAPMod *attrs[4] = {0}; + int numvalues = 0; + Slapi_Attr *attr = NULL; + char *csnvalues[2]; + + ocmod.mod_type = "objectclasses"; + ocmod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES; + ocmod.mod_bvalues = NULL; + atmod.mod_type = "attributetypes"; + atmod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES; + atmod.mod_bvalues = NULL; + csnmod.mod_type = nsschemacsn; + csnmod.mod_op = LDAP_MOD_REPLACE; + csn_as_string (localcsn, PR_FALSE, localcsnstr); + csnvalues[0] = localcsnstr; + csnvalues[1] = NULL; + csnmod.mod_values = csnvalues; + attrs[0] = &ocmod; + attrs[1] = &atmod; + attrs[2] = &csnmod; + attrs[3] = NULL; + + return_value = CONN_OPERATION_FAILED; /* assume failure */ + + /* Get local schema */ + spb = slapi_search_internal("cn=schema", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL /* controls */, NULL /* schema_csn_attrs */, 0 /* attrsonly */); + slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || NULL == entries[0]) + { + /* Whoops - couldn't read our own schema! */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Error: unable to read local schema definitions.\n", + agmt_get_long_name(conn->agmt)); + return_value = CONN_OPERATION_FAILED; + } + else + { + schema_entry = entries[0]; + if (slapi_entry_attr_find(schema_entry, "objectclasses", &attr) != -1) + { + int i, ind; + Slapi_Value *value; + slapi_attr_get_numvalues(attr, &numvalues); + ocmod.mod_bvalues = (struct berval **)slapi_ch_malloc((numvalues + 1) * + sizeof(struct berval *)); + for (i = 0, ind = slapi_attr_first_value(attr, &value); + ind != -1; ind = slapi_attr_next_value(attr, ind, &value), i++) + { + /* XXXggood had to cast away const below */ + ocmod.mod_bvalues[i] = (struct berval *)slapi_value_get_berval(value); + } + ocmod.mod_bvalues[numvalues] = NULL; + if (slapi_entry_attr_find(schema_entry, "attributetypes", &attr) != -1) + { + ConnResult result; + slapi_attr_get_numvalues(attr, &numvalues); + atmod.mod_bvalues = (struct berval **)slapi_ch_malloc((numvalues + 1) * + sizeof(struct berval *)); + for (i = 0, ind = slapi_attr_first_value(attr, &value); + ind != -1; ind = slapi_attr_next_value(attr, ind, &value), i++) + { + /* XXXggood had to cast away const below */ + atmod.mod_bvalues[i] = (struct berval *)slapi_value_get_berval(value); + } + atmod.mod_bvalues[numvalues] = NULL; + + result = conn_send_modify(conn, "cn=schema", attrs, NULL, NULL); + switch (result) + { + case CONN_OPERATION_FAILED: + { + int ldaperr = -1, optype = -1; + conn_get_error(conn, &optype, &ldaperr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Schema replication update failed: %s\n", + agmt_get_long_name(conn->agmt), + ldaperr == -1 ? "Unknown Error" : ldap_err2string(ldaperr)); + } + case CONN_NOT_CONNECTED: + return_value = CONN_NOT_CONNECTED; + break; + case CONN_OPERATION_SUCCESS: + return_value = CONN_SCHEMA_UPDATED; + break; + } + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Schema replication update failed: " + "unable to prepare schema entry for transmission.\n", + agmt_get_long_name(conn->agmt)); + } + } + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&ocmod.mod_bvalues); + slapi_ch_free((void **)&atmod.mod_bvalues); + } + if (NULL != spb) + { + slapi_free_search_results_internal(spb); + slapi_pblock_destroy(spb); + spb = NULL; + } + if (NULL != localcsn) + { + csn_free(&localcsn); + } + return return_value; +} + +void +conn_set_timeout(Repl_Connection *conn, long timeout) +{ + PR_ASSERT(NULL != conn); + PR_ASSERT(timeout >= 0); + PR_Lock(conn->lock); + conn->timeout.tv_sec = timeout; + PR_Unlock(conn->lock); +} + +void conn_set_agmt_changed(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + if (NULL != conn->agmt) + conn->flag_agmt_changed = 1; + PR_Unlock(conn->lock); +} + +/* + * Check the result of an ldap_simple_bind operation to see we it + * contains the expiration controls + * return: -1 error, not bound + * 0, OK bind has succeeded + */ +static int +bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password) +{ + + LDAPControl **ctrls = NULL; + LDAPMessage *res = NULL; + char *errmsg = NULL; + LDAP *ld = conn->ld; + int msgid; + int *msgidAdr = &msgid; + int rc; + + char * optype; /* ldap_simple_bind or slapd_SSL_client_bind */ + + if ( conn->transport_flags == TRANSPORT_FLAG_SSL ) + { + char *auth; + optype = "ldap_sasl_bind"; + + if ( conn->bindmethod == BINDMETHOD_SSL_CLIENTAUTH ) + { + rc = slapd_sasl_ext_client_bind(conn->ld, &msgidAdr); + auth = "SSL client authentication"; + + if ( rc == LDAP_SUCCESS ) + { + if (conn->last_ldap_error != rc) + { + conn->last_ldap_error = rc; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication bind with %s resumed\n", + agmt_get_long_name(conn->agmt), auth); + } + } + else + { + /* Do not report the same error over and over again */ + if (conn->last_ldap_error != rc) + { + conn->last_ldap_error = rc; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication bind with %s failed: LDAP error %d (%s)\n", + agmt_get_long_name(conn->agmt), auth, rc, + ldap_err2string(rc)); + } + + return (CONN_OPERATION_FAILED); + } + } + else + { + if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 ) + { + return (CONN_OPERATION_FAILED); + } + } + } + else + { + optype = "ldap_simple_bind"; + if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 ) + { + return (CONN_OPERATION_FAILED); + } + } + + /* Wait for the result */ + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &res ) == -1 ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Received error from consumer for %s operation\n", + agmt_get_long_name(conn->agmt), optype); + + return (CONN_OPERATION_FAILED); + } + /* Don't check ldap_result against 0 because, no timeout is specified */ + + /* Free res as we won't use it any longer */ + if ( ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, &ctrls, 1 /* Free res */) + != LDAP_SUCCESS ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Received error from consumer for %s operation\n", + agmt_get_long_name(conn->agmt), optype); + + return (CONN_OPERATION_FAILED); + } + + if ( rc == LDAP_SUCCESS ) + { + if ( ctrls ) + { + int i; + for( i = 0; ctrls[ i ] != NULL; ++i ) + { + if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRED)) ) + { + /* Bind is successfull but password has expired */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Succesfully bound %s to consumer, " + "but password has expired on consumer.\n", + agmt_get_long_name(conn->agmt), binddn); + } + else if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRING)) ) + { + /* The password is expiring in n seconds */ + if ( (ctrls[ i ]->ldctl_value.bv_val != NULL) && + (ctrls[ i ]->ldctl_value.bv_len > 0) ) + { + int password_expiring = atoi( ctrls[ i ]->ldctl_value.bv_val ); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Succesfully bound %s to consumer, " + "but password is expiring on consumer in %d seconds.\n", + agmt_get_long_name(conn->agmt), binddn, password_expiring); + } + } + } + ldap_controls_free( ctrls ); + } + + return (CONN_OPERATION_SUCCESS); + } + else + { + /* errmsg is a pointer directly into the ld structure - do not free */ + rc = ldap_get_lderrno( ld, NULL, &errmsg ); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication bind to %s on consumer failed: %d (%s)\n", + agmt_get_long_name(conn->agmt), binddn, rc, errmsg); + + conn->last_ldap_error = rc; /* specific error */ + return (CONN_OPERATION_FAILED); + } +} + +static int +do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password) +{ + int msgid; + + if( ( msgid = ldap_simple_bind( ld, binddn, password ) ) == -1 ) + { + char *ldaperrtext = NULL; + int ldaperr; + int prerr = PR_GetError(); + + ldaperr = ldap_get_lderrno( ld, NULL, &ldaperrtext ); + /* Do not report the same error over and over again */ + if (conn->last_ldap_error != ldaperr) + { + conn->last_ldap_error = ldaperr; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Simple bind failed, " + SLAPI_COMPONENT_NAME_LDAPSDK " error %d (%s), " + SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n", + agmt_get_long_name(conn->agmt), + ldaperr, ldaperrtext ? ldaperrtext : ldap_err2string(ldaperr), + prerr, slapd_pr_strerror(prerr)); + } + } + else if (conn->last_ldap_error != LDAP_SUCCESS) + { + conn->last_ldap_error = LDAP_SUCCESS; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Simple bind resumed\n", + agmt_get_long_name(conn->agmt)); + } + return msgid; +} + +void +repl5_set_debug_timeout(const char *val) +{ + /* val looks like this: seconds[:debuglevel] */ + /* seconds is the number of seconds to wait until turning on the debug level */ + /* this should be less than the ldap connection timeout (default 10 minutes) */ + /* the optional debug level is the error log debugging level to use (default repl) */ + if (val) { + const char *p = strchr(val, ':'); + s_debug_timeout = atoi(val); + if (p) { + s_debug_level = atoi(p+1); + } else { + s_debug_level = 8192; + } + } +} + +static time_t +PRTime2time_t (PRTime tm) +{ + PRInt64 rt; + + PR_ASSERT (tm); + + LL_DIV(rt, tm, PR_USEC_PER_SEC); + + return (time_t)rt; +} + +static Slapi_Eq_Context +repl5_start_debug_timeout(int *setlevel) +{ + Slapi_Eq_Context eqctx = 0; + if (s_debug_timeout && s_debug_level) { + time_t now = time(NULL); + eqctx = slapi_eq_once(repl5_debug_timeout_callback, setlevel, + s_debug_timeout + now); + } + return eqctx; +} + +static void +repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel) +{ + char buf[20]; + char msg[SLAPI_DSE_RETURNTEXT_SIZE]; + + if (eqctx && !*setlevel) { + int found = slapi_eq_cancel(eqctx); + } + + if (s_debug_timeout && s_debug_level && *setlevel) { + void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply); + sprintf(buf, "%d", 0); + config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1); + } +} + +static void +repl5_debug_timeout_callback(time_t when, void *arg) +{ + int *setlevel = (int *)arg; + void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply); + char buf[20]; + char msg[SLAPI_DSE_RETURNTEXT_SIZE]; + + *setlevel = 1; + sprintf(buf, "%d", s_debug_level); + config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "repl5_debug_timeout_callback: set debug level to %d at %d\n", + s_debug_level, when); +} diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c new file mode 100644 index 00000000..a9905a34 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c @@ -0,0 +1,1759 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_inc_protocol.c */ +/* + + The Prot_Incremental object implements the DS 5.0 multi-master incremental + replication protocol. + + +Stuff to do: + +- Need to figure out how asynchronous events end up in here. They are: + - entry updated in replicated area. + - backoff timeout + - enter/leave. + +Perhaps these events should be properties of the main protocol. +*/ + +#include "repl.h" +#include "repl5.h" +#include "repl5_ruv.h" +#include "repl5_prot_private.h" +#include "cl5_api.h" + +extern int slapi_log_urp; + +/*** from proto-slap.h ***/ +void ava_done(struct ava *ava); + +typedef struct repl5_inc_private +{ + char *ruv; /* RUV on remote replica (use diff type for this? - ggood */ + Backoff_Timer *backoff; + Repl_Protocol *rp; + PRLock *lock; + PRUint32 eventbits; +} repl5_inc_private; + + +/* Various states the incremental protocol can pass through */ +#define STATE_START 0 /* ONREPL - should we rename this - we don't use it just to start up? */ +#define STATE_WAIT_WINDOW_OPEN 1 +#define STATE_WAIT_CHANGES 2 +#define STATE_READY_TO_ACQUIRE 3 +#define STATE_BACKOFF_START 4 /* ONREPL - can we combine BACKOFF_START and BACKOFF states? */ +#define STATE_BACKOFF 5 +#define STATE_SENDING_UPDATES 6 +#define STATE_STOP_FATAL_ERROR 7 +#define STATE_STOP_FATAL_ERROR_PART2 8 +#define STATE_STOP_NORMAL_TERMINATION 9 + +/* Events (synchronous and asynchronous; these are bits) */ +#define EVENT_WINDOW_OPENED 1 +#define EVENT_WINDOW_CLOSED 2 +#define EVENT_TRIGGERING_CRITERIA_MET 4 /* ONREPL - should we rename this to EVENT_CHANGE_AVAILABLE */ +#define EVENT_BACKOFF_EXPIRED 8 +#define EVENT_REPLICATE_NOW 16 +#define EVENT_PROTOCOL_SHUTDOWN 32 +#define EVENT_AGMT_CHANGED 64 + +#define UPDATE_NO_MORE_UPDATES 201 +#define UPDATE_TRANSIENT_ERROR 202 +#define UPDATE_FATAL_ERROR 203 +#define UPDATE_SCHEDULE_WINDOW_CLOSED 204 +#define UPDATE_CONNECTION_LOST 205 +#define UPDATE_TIMEOUT 206 +#define UPDATE_YIELD 207 + +/* Return codes from examine_update_vector */ +#define EXAMINE_RUV_PRISTINE_REPLICA 401 +#define EXAMINE_RUV_GENERATION_MISMATCH 402 +#define EXAMINE_RUV_REPLICA_TOO_OLD 403 +#define EXAMINE_RUV_OK 404 +#define EXAMINE_RUV_PARAM_ERROR 405 + +#define MAX_CHANGES_PER_SESSION 10000 +/* + * Maximum time to wait between replication sessions. If we + * don't see any updates for a period equal to this interval, + * we go ahead and start a replication session, just to be safe + */ +#define MAX_WAIT_BETWEEN_SESSIONS PR_SecondsToInterval(60 * 5) /* 5 minutes */ + +/* + * tests if the protocol has been shutdown and we need to quit + * event_occurred resets the bits in the bit flag, so whoever tests for shutdown + * resets the flags, so the next one who tests for shutdown won't get it, so we + * also look at the terminate flag + */ +#define PROTOCOL_IS_SHUTDOWN(prp) (event_occurred(prp, EVENT_PROTOCOL_SHUTDOWN) || prp->terminate) + +/* Forward declarations */ +static PRUint32 event_occurred(Private_Repl_Protocol *prp, PRUint32 event); +static void reset_events (Private_Repl_Protocol *prp); +static void protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration); +static int send_updates(Private_Repl_Protocol *prp, RUV *ruv, PRUint32 *num_changes_sent); +static void repl5_inc_backoff_expired(time_t timer_fire_time, void *arg); +static int examine_update_vector(Private_Repl_Protocol *prp, RUV *ruv); +static PRBool ignore_error_and_keep_going(int error); +static const char* state2name (int state); +static const char* event2name (int event); +static const char* op2string (int op); + +/* + * It's specifically ok to delete a protocol instance that + * is currently running. The instance will be shut down, and + * then resources will be freed. Since a graceful shutdown is + * attempted, this function may take some time to complete. + */ +static void +repl5_inc_delete(Private_Repl_Protocol **prpp) +{ + /* First, stop the protocol if it isn't already stopped */ + /* Then, delete all resources used by the protocol */ +} + +/* helper function */ +void +set_pause_and_busy_time(long *pausetime, long *busywaittime) +{ + /* If neither are set, set busy time to its default */ + if (!*pausetime && !*busywaittime) + { + *busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM; + } + /* pause time must be at least 1 more than the busy backoff time */ + if (*pausetime && !*busywaittime) + { + /* + * user specified a pause time but no busy wait time - must + * set busy wait time to 1 less than pause time - if pause + * time is 1, we must set it to 2 + */ + if (*pausetime < 2) + { + *pausetime = 2; + } + *busywaittime = *pausetime - 1; + } + else if (!*pausetime && *busywaittime) + { + /* + * user specified a busy wait time but no pause time - must + * set pause time to 1 more than busy wait time + */ + *pausetime = *busywaittime + 1; + } + else if (*pausetime && *busywaittime && *pausetime <= *busywaittime) + { + /* + * user specified both pause and busy wait times, but the pause + * time was <= busy wait time - pause time must be at least + * 1 more than the busy wait time + */ + *pausetime = *busywaittime + 1; + } +} + +/* + * Do the incremental protocol. + * + * What's going on here? This thing is a state machine. It has the + * following states: + * + * State transition table: + * + * Curr State Condition/Event Next State + * ---------- ------------ ----------- + * START schedule window is open ACQUIRE_REPLICA + * schedule window is closed WAIT_WINDOW_OPEN + * WAIT_WINDOW_OPEN schedule change START + * replicate now ACQUIRE_REPLICA + * schedule window opens ACQUIRE_REPLICA + * ACQUIRE_REPLICA acquired replica SEND_CHANGES + * failed to acquire - transient error START_BACKOFF + * failed to acquire - fatal error STOP_FATAL_ERROR + * SEND_CHANGES can't update CONSUMER_NEEDS_REINIT + * no changes to send WAIT_CHANGES + * can't send - thransient error START_BACKOF + * can't send - window closed WAIT_WINDOW_OPEN + * can'r send - fatal error STOP_FATAL_ERROR + * START_BACKOF replicate now ACQUIRE_REPLICA + * schedule changes START + * schedule window closes WAIT_WINDOW_OPEN + * backoff expires & can acquire SEND_CHANGES + * backoff expires & can't acquire-trans BACKOFF + * backoff expires & can't acquire-fatal STOP_FATAL_ERROR + * BACKOF replicate now ACQUIRE_REPLICA + * schedule changes START + * schedule window closes WAIT_WINDOW_OPEN + * backoff expires & can acquire SEND_CHANGES + * backoff expires & can't acquire-trans BACKOFF + * backoff expires & can't acquire-fatal STOP_FATAL_ERROR + * WAIT_CHANGES schedule window closes WAIT_WINDOW_OPEN + * replicate_now ACQUIRE_REPLICA + * change available ACQUIRE_REPLICA + * schedule_change START + */ + +/* + * Main state machine for the incremental protocol. This routine will, + * under normal circumstances, not return until the protocol is shut + * down. + */ +static void +repl5_inc_run(Private_Repl_Protocol *prp) +{ + int current_state = STATE_START; + int next_state = STATE_START; + repl5_inc_private *prp_priv = (repl5_inc_private *)prp->private; + int done; + int e1; + RUV *ruv = NULL; + CSN *cons_schema_csn; + Replica *replica; + int wait_change_timer_set = 0; + time_t last_start_time; + PRUint32 num_changes_sent; + char *hostname = NULL; + int portnum = 0; + /* use a different backoff timer strategy for ACQUIRE_REPLICA_BUSY errors */ + PRBool use_busy_backoff_timer = PR_FALSE; + long pausetime = 0; + long busywaittime = 0; + + prp->stopped = 0; + prp->terminate = 0; + hostname = agmt_get_hostname(prp->agmt); + portnum = agmt_get_port(prp->agmt); + + /* establish_protocol_callbacks(prp); */ + done = 0; + do { + int rc; + + /* Take action, based on current state, and compute new state. */ + switch (current_state) + { + case STATE_START: + + dev_debug("repl5_inc_run(STATE_START)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + + /* + * Our initial state. See if we're in a schedule window. If + * so, then we're ready to acquire the replica and see if it + * needs any updates from us. If not, then wait for the window + * to open. + */ + if (agmt_schedule_in_window_now(prp->agmt)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else + { + next_state = STATE_WAIT_WINDOW_OPEN; + } + + /* we can get here from other states because some events happened and were + not cleared. For instance when we wake up in STATE_WAIT_CHANGES state. + Since this is a fresh start state, we should clear all events */ + /* ONREPL - this does not feel right - we should take another look + at this state machine */ + reset_events (prp); + + /* Cancel any linger timer that might be in effect... */ + conn_cancel_linger(prp->conn); + /* ... and disconnect, if currently connected */ + conn_disconnect(prp->conn); + /* get the new pause time, if any */ + pausetime = agmt_get_pausetime(prp->agmt); + /* get the new busy wait time, if any */ + busywaittime = agmt_get_busywaittime(prp->agmt); + if (pausetime || busywaittime) + { + /* helper function to make sure they are set correctly */ + set_pause_and_busy_time(&pausetime, &busywaittime); + } + break; + case STATE_WAIT_WINDOW_OPEN: + /* + * We're waiting for a schedule window to open. If one did, + * or we receive a "replicate now" event, then start a protocol + * session immediately. If the replication schedule changed, go + * back to start. Otherwise, go back to sleep. + */ + dev_debug("repl5_inc_run(STATE_WAIT_WINDOW_OPEN)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + else if (event_occurred(prp, EVENT_WINDOW_OPENED)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + next_state = STATE_START; + conn_set_agmt_changed(prp->conn); + } + else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) /* change available */ + { + /* just ignore it and go to sleep */ + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + else if (e1 = event_occurred(prp, EVENT_WINDOW_CLOSED) || + event_occurred(prp, EVENT_BACKOFF_EXPIRED)) + { + /* this events - should not occur - log a warning and go to sleep */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental protocol: " + "event %s should not occur in state %s; going to sleep\n", + agmt_get_long_name(prp->agmt), + e1 ? event2name(EVENT_WINDOW_CLOSED) : event2name(EVENT_BACKOFF_EXPIRED), + state2name(current_state)); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + else + { + /* wait until window opens or an event occurs */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Incremental protocol: " + "waiting for update window to open\n", agmt_get_long_name(prp->agmt)); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + break; + case STATE_WAIT_CHANGES: + /* + * We're in a replication window, but we're waiting for more + * changes to accumulate before we actually hook up and send + * them. + */ + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): PROTOCOL_IS_SHUTING_DOWN -> end repl5_inc_run\n"); + done = 1; + break; + } + else if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_REPLICATE_NOW received -> STATE_READY_TO_ACQUIRE\n"); + next_state = STATE_READY_TO_ACQUIRE; + wait_change_timer_set = 0; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_AGMT_CHANGED received -> STATE_START\n"); + next_state = STATE_START; + conn_set_agmt_changed(prp->conn); + wait_change_timer_set = 0; + } + else if (event_occurred(prp, EVENT_WINDOW_CLOSED)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_WINDOW_CLOSED received -> STATE_WAIT_WINDOW_OPEN\n"); + next_state = STATE_WAIT_WINDOW_OPEN; + wait_change_timer_set = 0; + } + else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_TRIGGERING_CRITERIA_MET received -> STATE_READY_TO_ACQUIRE\n"); + next_state = STATE_READY_TO_ACQUIRE; + wait_change_timer_set = 0; + } + else if (e1 = event_occurred(prp, EVENT_WINDOW_OPENED) || + event_occurred(prp, EVENT_BACKOFF_EXPIRED)) + { + /* this events - should not occur - log a warning and clear the event */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: Incremental protocol: " + "event %s should not occur in state %s\n", + agmt_get_long_name(prp->agmt), + e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED), + state2name(current_state)); + wait_change_timer_set = 0; + } + else + { + if (wait_change_timer_set) + { + /* We are here because our timer expired */ + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): wait_change_timer_set expired -> STATE_START\n"); + next_state = STATE_START; + wait_change_timer_set = 0; + } + else + { + /* We are here because the last replication session + * finished or aborted. + */ + wait_change_timer_set = 1; + protocol_sleep(prp, MAX_WAIT_BETWEEN_SESSIONS); + } + } + break; + case STATE_READY_TO_ACQUIRE: + + dev_debug("repl5_inc_run(STATE_READY_TO_ACQUIRE)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + + /* ONREPL - at this state we unconditionally acquire the replica + ignoring all events. Not sure if this is good */ + object_acquire(prp->replica_object); + replica = object_get_data(prp->replica_object); + + rc = acquire_replica(prp, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID, &ruv); + use_busy_backoff_timer = PR_FALSE; /* default */ + if (rc == ACQUIRE_SUCCESS) + { + next_state = STATE_SENDING_UPDATES; + } + else if (rc == ACQUIRE_REPLICA_BUSY) + { + next_state = STATE_BACKOFF_START; + use_busy_backoff_timer = PR_TRUE; + } + else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE) + { + next_state = STATE_WAIT_CHANGES; + } + else if (rc == ACQUIRE_TRANSIENT_ERROR) + { + next_state = STATE_BACKOFF_START; + } + else if (rc == ACQUIRE_FATAL_ERROR) + { + next_state = STATE_STOP_FATAL_ERROR; + } + if (rc != ACQUIRE_SUCCESS) + { + int optype, ldaprc; + conn_get_error(prp->conn, &optype, &ldaprc); + agmt_set_last_update_status(prp->agmt, ldaprc, + prp->last_acquire_response_code, NULL); + } + + object_release(prp->replica_object); replica = NULL; + break; + case STATE_BACKOFF_START: + dev_debug("repl5_inc_run(STATE_BACKOFF_START)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + next_state = STATE_START; + conn_set_agmt_changed(prp->conn); + } + else if (event_occurred (prp, EVENT_WINDOW_CLOSED)) + { + next_state = STATE_WAIT_WINDOW_OPEN; + } + else if (event_occurred (prp, EVENT_TRIGGERING_CRITERIA_MET)) + { + /* consume and ignore */ + } + else if (e1 = event_occurred (prp, EVENT_WINDOW_OPENED) || + event_occurred (prp, EVENT_BACKOFF_EXPIRED)) + { + /* This should never happen */ + /* this events - should not occur - log a warning and go to sleep */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental protocol: event %s should not occur in state %s\n", + agmt_get_long_name(prp->agmt), + e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED), + state2name(current_state)); + } + else + { + /* Set up the backoff timer to wake us up at the appropriate time */ + if (use_busy_backoff_timer) + { + /* we received a busy signal from the consumer, wait for a while */ + if (!busywaittime) + { + busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM; + } + prp_priv->backoff = backoff_new(BACKOFF_FIXED, busywaittime, + busywaittime); + } + else + { + prp_priv->backoff = backoff_new(BACKOFF_EXPONENTIAL, PROTOCOL_BACKOFF_MINIMUM, + PROTOCOL_BACKOFF_MAXIMUM); + } + next_state = STATE_BACKOFF; + backoff_reset(prp_priv->backoff, repl5_inc_backoff_expired, (void *)prp); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + use_busy_backoff_timer = PR_FALSE; + } + break; + case STATE_BACKOFF: + /* + * We're in a backoff state. + */ + dev_debug("repl5_inc_run(STATE_BACKOFF)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + if (prp_priv->backoff) + backoff_delete(&prp_priv->backoff); + done = 1; + break; + } + else if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + next_state = STATE_START; + + conn_set_agmt_changed(prp->conn); + /* Destroy the backoff timer, since we won't need it anymore */ + if (prp_priv->backoff) + backoff_delete(&prp_priv->backoff); + } + else if (event_occurred(prp, EVENT_WINDOW_CLOSED)) + { + next_state = STATE_WAIT_WINDOW_OPEN; + /* Destroy the backoff timer, since we won't need it anymore */ + if (prp_priv->backoff) + backoff_delete(&prp_priv->backoff); + } + else if (event_occurred(prp, EVENT_BACKOFF_EXPIRED)) + { + rc = acquire_replica(prp, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID, &ruv); + use_busy_backoff_timer = PR_FALSE; + if (rc == ACQUIRE_SUCCESS) + { + next_state = STATE_SENDING_UPDATES; + } + else if (rc == ACQUIRE_REPLICA_BUSY) + { + next_state = STATE_BACKOFF; + use_busy_backoff_timer = PR_TRUE; + } + else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE) + { + next_state = STATE_WAIT_CHANGES; + } + else if (rc == ACQUIRE_TRANSIENT_ERROR) + { + next_state = STATE_BACKOFF; + } + else if (rc == ACQUIRE_FATAL_ERROR) + { + next_state = STATE_STOP_FATAL_ERROR; + } + if (rc != ACQUIRE_SUCCESS) + { + int optype, ldaprc; + conn_get_error(prp->conn, &optype, &ldaprc); + agmt_set_last_update_status(prp->agmt, ldaprc, + prp->last_acquire_response_code, NULL); + } + /* + * We either need to step the backoff timer, or + * destroy it if we don't need it anymore. + */ + if (STATE_BACKOFF == next_state) + { + time_t next_fire_time; + time_t now; + /* Step the backoff timer */ + time(&now); + next_fire_time = backoff_step(prp_priv->backoff); + /* And go back to sleep */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Replication session backing off for %d seconds\n", + agmt_get_long_name(prp->agmt), + next_fire_time - now); + + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + else + { + /* Destroy the backoff timer, since we won't need it anymore */ + backoff_delete(&prp_priv->backoff); + } + use_busy_backoff_timer = PR_FALSE; + } + else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) + { + /* changes are available */ + if ( prp_priv->backoff == NULL || backoff_expired (prp_priv->backoff, 60) ) + { + /* + * Have seen cases that the agmt stuck here forever since + * somehow the backoff timer was not in event queue anymore. + * If the backoff timer has expired more than 60 seconds, + * destroy it. + */ + if ( prp_priv->backoff ) + backoff_delete(&prp_priv->backoff); + next_state = STATE_READY_TO_ACQUIRE; + } + else + { + /* ignore changes and go to sleep */ + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + } + else if (event_occurred(prp, EVENT_WINDOW_OPENED)) + { + /* this should never happen - log an error and go to sleep */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: Incremental protocol: " + "event %s should not occur in state %s; going to sleep\n", + agmt_get_long_name(prp->agmt), + event2name(EVENT_WINDOW_OPENED), state2name(current_state)); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + break; + case STATE_SENDING_UPDATES: + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES)"); + agmt_set_update_in_progress(prp->agmt, PR_TRUE); + num_changes_sent = 0; + last_start_time = current_time(); + agmt_set_last_update_start(prp->agmt, last_start_time); + /* + * We've acquired the replica, and are ready to send any + * needed updates. + */ + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + release_replica (prp); + done = 1; + agmt_set_update_in_progress(prp->agmt, PR_FALSE); + agmt_set_last_update_end(prp->agmt, current_time()); + /* MAB: I don't find the following status correct. How do we know it has + been stopped by an admin and not by a total update request, for instance? + In any case, how is this protocol shutdown situation different from all the + other ones that are present in this state machine? */ + /* richm: We at least need to let monitors know that the protocol has been + shutdown - maybe they can figure out why */ + agmt_set_last_update_status(prp->agmt, 0, 0, "Protocol stopped"); + break; + } + + agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update started"); + + /* ONREPL - in this state we send changes no matter what other events occur. + This is because we can get because of the REPLICATE_NOW event which + has high priority. Is this ok? */ + /* First, push new schema to the consumer if needed */ + /* ONREPL - should we push schema after we examine the RUV? */ + /* + * GGOOREPL - I don't see why we should wait until we've + * examined the RUV. The schema entry has its own CSN that is + * used to decide if the remote schema needs to be updated. + */ + cons_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt ); + rc = conn_push_schema(prp->conn, &cons_schema_csn); + if ( cons_schema_csn != agmt_get_consumer_schema_csn ( prp->agmt )) + { + agmt_set_consumer_schema_csn ( prp->agmt, cons_schema_csn ); + } + if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: unable to replicate schema: rc=%d\n", + agmt_get_long_name(prp->agmt), rc); + /* But keep going */ + } + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> examine_update_vector"); + rc = examine_update_vector(prp, ruv); + /* + * Decide what to do next - proceed with incremental, + * backoff, or total update + */ + switch (rc) + { + case EXAMINE_RUV_PARAM_ERROR: + /* this is really bad - we have NULL prp! */ + next_state = STATE_STOP_FATAL_ERROR; + break; + case EXAMINE_RUV_PRISTINE_REPLICA: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replica has no update vector. It has never been initialized.\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_BACKOFF_START; + break; + case EXAMINE_RUV_GENERATION_MISMATCH: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replica has a different generation ID than the local data.\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_BACKOFF_START; + break; + case EXAMINE_RUV_REPLICA_TOO_OLD: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replica update vector is too out of date to bring " + "into sync using the incremental protocol. The replica " + "must be reinitialized.\n", agmt_get_long_name(prp->agmt)); + next_state = STATE_BACKOFF_START; + break; + case EXAMINE_RUV_OK: + /* update our csn generator state with the consumer's ruv data */ + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> examine_update_vector OK"); + object_acquire(prp->replica_object); + replica = object_get_data(prp->replica_object); + rc = replica_update_csngen_state (replica, ruv); + object_release (prp->replica_object); + replica = NULL; + if (rc != 0) /* too much skew */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental protocol: fatal error - too much time skew between replicas!\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_STOP_FATAL_ERROR; + } + else + { + rc = send_updates(prp, ruv, &num_changes_sent); + if (rc == UPDATE_NO_MORE_UPDATES) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_NO_MORE_UPDATES -> STATE_WAIT_CHANGES"); + agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded"); + next_state = STATE_WAIT_CHANGES; + } + else if (rc == UPDATE_YIELD) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_YIELD -> STATE_BACKOFF_START"); + agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded and yielded"); + next_state = STATE_BACKOFF_START; + } + else if (rc == UPDATE_TRANSIENT_ERROR) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TRANSIENT_ERROR -> STATE_BACKOFF_START"); + next_state = STATE_BACKOFF_START; + } + else if (rc == UPDATE_FATAL_ERROR) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_FATAL_ERROR -> STATE_STOP_FATAL_ERROR"); + next_state = STATE_STOP_FATAL_ERROR; + } + else if (rc == UPDATE_SCHEDULE_WINDOW_CLOSED) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_SCHEDULE_WINDOW_CLOSED -> STATE_WAIT_WINDOW_OPEN"); + /* ONREPL - I don't think we should check this. We might be + here because of replicate_now event - so we don't care + about the schedule */ + next_state = STATE_WAIT_WINDOW_OPEN; + /* ONREPL - do we need to release the replica here ? */ + conn_disconnect (prp->conn); + } + else if (rc == UPDATE_CONNECTION_LOST) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_CONNECTION_LOST -> STATE_BACKOFF_START"); + next_state = STATE_BACKOFF_START; + } + else if (rc == UPDATE_TIMEOUT) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TIMEOUT -> STATE_BACKOFF_START"); + next_state = STATE_BACKOFF_START; + } + } + last_start_time = 0UL; + break; + } + if (NULL != ruv) + { + ruv_destroy(&ruv); ruv = NULL; + } + agmt_set_last_update_end(prp->agmt, current_time()); + agmt_set_update_in_progress(prp->agmt, PR_FALSE); + /* If timed out, close the connection after released the replica */ + release_replica(prp); + if (rc == UPDATE_TIMEOUT) { + conn_disconnect(prp->conn); + } + if (rc == UPDATE_NO_MORE_UPDATES && num_changes_sent > 0) + { + if (pausetime > 0) + { + /* richm - 20020219 - If we have acquired the consumer, and another master has gone + into backoff waiting for us to release it, we may acquire the replica sooner + than the other master has a chance to, and the other master may not be able + to acquire the consumer for a long time (hours, days?) if this server is + under a heavy load (see reliab06 et. al. system tests) + So, this sleep gives the other master(s) a chance to acquire the consumer + replica */ + long loops = pausetime; + /* the while loop is so that we don't just sleep and sleep if an + event comes in that we should handle immediately (like shutdown) */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Pausing updates for %ld seconds to allow other suppliers to update consumer\n", + agmt_get_long_name(prp->agmt), pausetime); + while (loops-- && !(PROTOCOL_IS_SHUTDOWN(prp))) + { + DS_Sleep(PR_SecondsToInterval(1)); + } + } + else if (num_changes_sent > 10) + { + /* wait for consumer to write its ruv if the replication was busy */ + /* When asked, consumer sends its ruv in cache to the supplier. */ + /* DS_Sleep ( PR_SecondsToInterval(1) ); */ + } + } + break; + case STATE_STOP_FATAL_ERROR: + /* + * We encountered some sort of a fatal error. Suspend. + */ + /* XXXggood update state in replica */ + agmt_set_last_update_status(prp->agmt, -1, 0, "Incremental update has failed and requires administrator action"); + dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR)"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental update failed and requires administrator action\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_STOP_FATAL_ERROR_PART2; + break; + case STATE_STOP_FATAL_ERROR_PART2: + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + + /* MAB: This state is the FATAL state where we are supposed to get + as a result of a FATAL error on send_updates. But, as bug + states, send_updates was always returning TRANSIENT errors and never + FATAL... In other words, this code has never been tested before... + + As of 01/16/01, this piece of code was in a very dangerous state. In particular, + 1) it does not catch any events + 2) it is a terminal state (once reached it never transitions to a different state) + + Both things combined make this state to become a consuming infinite loop + that is useless after all (we are in a fatal place requiring manual admin jobs */ + + /* MAB: The following lines fix problem number 1 above... When the code gets + into this state, it should only get a chance to get out of it by an + EVENT_AGMT_CHANGED event... All other events should be ignored */ + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR): EVENT_AGMT_CHANGED received\n"); + /* Chance to recover for the EVENT_AGMT_CHANGED event. + This is not mandatory, but fixes problem 2 above */ + next_state = STATE_STOP_NORMAL_TERMINATION; + } + else + { + dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR): Event received. Clearing it\n"); + reset_events (prp); + } + + protocol_sleep (prp, PR_INTERVAL_NO_TIMEOUT); + break; + + case STATE_STOP_NORMAL_TERMINATION: + /* + * We encountered some sort of a fatal error. Return. + */ + /* XXXggood update state in replica */ + dev_debug("repl5_inc_run(STATE_STOP_NORMAL_TERMINATION)"); + done = 1; + break; + } + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: State: %s -> %s\n", + agmt_get_long_name(prp->agmt), + state2name(current_state), state2name(next_state)); + + current_state = next_state; + } while (!done); + slapi_ch_free((void**)&hostname); + /* remove_protocol_callbacks(prp); */ + prp->stopped = 1; + /* Cancel any linger timer that might be in effect... */ + conn_cancel_linger(prp->conn); + /* ... and disconnect, if currently connected */ + conn_disconnect(prp->conn); +} + + + +/* + * Go to sleep until awakened. + */ +static void +protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration) +{ + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + /* we should not go to sleep if there are events available to be processed. + Otherwise, we can miss the event that suppose to wake us up */ + if (prp->eventbits == 0) + PR_WaitCondVar(prp->cvar, duration); + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Incremental protocol: can't go to sleep: event bits - %x\n", + agmt_get_long_name(prp->agmt), prp->eventbits); + } + PR_Unlock(prp->lock); +} + + +/* + * Notify the protocol about some event. Signal the condition + * variable in case the protocol is sleeping. Multiple occurences + * of a single event type are not remembered (e.g. no stack + * of events is maintained). + */ +static void +event_notify(Private_Repl_Protocol *prp, PRUint32 event) +{ + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + prp->eventbits |= event; + PR_NotifyCondVar(prp->cvar); + PR_Unlock(prp->lock); +} + + +/* + * Test to see if an event occurred. The event is cleared when + * read. + */ +static PRUint32 +event_occurred(Private_Repl_Protocol *prp, PRUint32 event) +{ + PRUint32 return_value; + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + return_value = (prp->eventbits & event); + prp->eventbits &= ~event; /* Clear event */ + PR_Unlock(prp->lock); + return return_value; +} + +static void +reset_events (Private_Repl_Protocol *prp) +{ + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + prp->eventbits = 0; + PR_Unlock(prp->lock); +} + + +/* + * Replay the actual update to the consumer. Construct an appropriate LDAP + * operation, attach the baggage LDAPv3 control that contains the CSN, etc., + * and send the operation to the consumer. + */ +ConnResult +replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op) +{ + ConnResult return_value; + LDAPControl *update_control; + char *parentuniqueid; + LDAPMod **modrdn_mods = NULL; + char csn_str[CSN_STRSIZE]; /* For logging only */ + + csn_as_string(op->csn, PR_FALSE, csn_str); + + /* Construct the replication info control that accompanies the operation */ + if (SLAPI_OPERATION_ADD == op->operation_type) + { + parentuniqueid = op->p.p_add.parentuniqueid; + } + else if (SLAPI_OPERATION_MODRDN == op->operation_type) + { + /* + * For modrdn operations, we need to send along modified attributes, e.g. + * modifytimestamp. + * And the superior_uniqueid ! + */ + modrdn_mods = op->p.p_modrdn.modrdn_mods; + parentuniqueid = op->p.p_modrdn.modrdn_newsuperior_address.uniqueid; + } + else + { + parentuniqueid = NULL; + } + if (create_NSDS50ReplUpdateInfoControl(op->target_address.uniqueid, + parentuniqueid, op->csn, modrdn_mods, &update_control) != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: replay_update: Unable to create NSDS50ReplUpdateInfoControl " + "for operation with csn %s. Skipping update.\n", + agmt_get_long_name(prp->agmt), csn_str); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: replay_update: Sending %s operation (dn=\"%s\" csn=%s)\n", + agmt_get_long_name(prp->agmt), + op2string(op->operation_type), op->target_address.dn, csn_str); + /* What type of operation is it? */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: + { + LDAPMod **entryattrs; + /* Convert entry to mods */ + (void)slapi_entry2mods (op->p.p_add.target_entry, + NULL /* &entrydn : We don't need it */, + &entryattrs); + if (NULL == entryattrs) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: replay_update: Cannot convert entry to LDAPMods.\n", + agmt_get_long_name(prp->agmt)); + return_value = CONN_LOCAL_ERROR; + } + else + { + return_value = conn_send_add(prp->conn, op->target_address.dn, + entryattrs, update_control, NULL /* returned controls */); + ldap_mods_free(entryattrs, 1); + } + break; + } + case SLAPI_OPERATION_MODIFY: + return_value = conn_send_modify(prp->conn, op->target_address.dn, + op->p.p_modify.modify_mods, update_control, + NULL /* returned controls */); + break; + case SLAPI_OPERATION_DELETE: + return_value = conn_send_delete(prp->conn, op->target_address.dn, + update_control, NULL /* returned controls */); + break; + case SLAPI_OPERATION_MODRDN: + /* XXXggood need to pass modrdn mods in update control! */ + return_value = conn_send_rename(prp->conn, op->target_address.dn, + op->p.p_modrdn.modrdn_newrdn, + op->p.p_modrdn.modrdn_newsuperior_address.dn, + op->p.p_modrdn.modrdn_deloldrdn, + update_control, NULL /* returned controls */); + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: replay_update: Unknown " + "operation type %d found in changelog - skipping change.\n", + agmt_get_long_name(prp->agmt), op->operation_type); + } + + destroy_NSDS50ReplUpdateInfoControl(&update_control); + } + + if (CONN_OPERATION_SUCCESS == return_value) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: replay_update: Consumer successfully replayed operation with csn %s\n", + agmt_get_long_name(prp->agmt), csn_str); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: replay_update: Consumer could not replay operation with csn %s\n", + agmt_get_long_name(prp->agmt), csn_str); + } + return return_value; +} + +static PRBool +is_dummy_operation (const slapi_operation_parameters *op) +{ + return (strcmp (op->target_address.uniqueid, START_ITERATION_ENTRY_UNIQUEID) == 0); +} + + + +void +cl5_operation_parameters_done (struct slapi_operation_parameters *sop) +{ + if(sop!=NULL) { + switch(sop->operation_type) + { + case SLAPI_OPERATION_BIND: + slapi_ch_free((void **)&(sop->p.p_bind.bind_saslmechanism)); + if (sop->p.p_bind.bind_creds) + ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_creds)); + if (sop->p.p_bind.bind_ret_saslcreds) + ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_ret_saslcreds)); + sop->p.p_bind.bind_creds = NULL; + sop->p.p_bind.bind_ret_saslcreds = NULL; + break; + case SLAPI_OPERATION_COMPARE: + ava_done((struct ava *)&(sop->p.p_compare.compare_ava)); + break; + case SLAPI_OPERATION_SEARCH: + slapi_ch_free((void **)&(sop->p.p_search.search_strfilter)); + charray_free(sop->p.p_search.search_attrs); + slapi_filter_free(sop->p.p_search.search_filter,1); + break; + case SLAPI_OPERATION_MODRDN: + sop->p.p_modrdn.modrdn_deloldrdn = 0; + break; + case SLAPI_OPERATION_EXTENDED: + slapi_ch_free((void **)&(sop->p.p_extended.exop_oid)); + if (sop->p.p_extended.exop_value) + ber_bvecfree((struct berval**)&(sop->p.p_extended.exop_value)); + sop->p.p_extended.exop_value = NULL; + break; + default: + break; + } + } + operation_parameters_done(sop); + +} + + + +/* + * Send a set of updates to the replica. Assumes that (1) the replica + * has already been acquired, (2) that the consumer's update vector has + * been checked and (3) that it's ok to send incremental updates. + * Returns: + * UPDATE_NO_MORE_UPDATES - all updates were sent succussfully + * UPDATE_TRANSIENT_ERROR - some non-permanent error occurred. Try again later. + * UPDATE_FATAL_ERROR - some bad, permanent error occurred. + * UPDATE_SCHEDULE_WINDOW_CLOSED - the schedule window closed on us. + */ +static int +send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *num_changes_sent) +{ + CL5Entry entry; + slapi_operation_parameters op; + int return_value; + int rc; + CL5ReplayIterator *changelog_iterator; + + *num_changes_sent = 0; + /* + * Iterate over the changelog. Retrieve each update, + * construct an appropriate LDAP operation, + * attaching the CSN, and send the change. + */ + + rc = cl5CreateReplayIterator(prp, remote_update_vector, &changelog_iterator); + if (CL5_SUCCESS != rc) + { + switch (rc) + { + case CL5_BAD_DATA: /* invalid parameter passed to the function */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Invalid parameter passed to cl5CreateReplayIterator\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_BAD_FORMAT: /* db data has unexpected format */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unexpected format encountered in changelog database\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_BAD_STATE: /* changelog is in an incorrect state for attempted operation */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Changelog database was in an incorrect state\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_BAD_DBVERSION: /* changelog has invalid dbversion */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incorrect dbversion found in changelog database\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_DB_ERROR: /* database error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A changelog database error was encountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_NOTFOUND: /* we have no changes to send */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No changes to send\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_NO_MORE_UPDATES; + break; + case CL5_MEMORY_ERROR: /* memory allocation failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Memory allocation error occurred\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_SYSTEM_ERROR: /* NSPR error occurred: use PR_GetError for furhter info */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An NSPR error (%d) occurred\n", + agmt_get_long_name(prp->agmt), PR_GetError()); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_CSN_ERROR: /* CSN API failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A CSN API failure was encountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_RUV_ERROR: /* RUV API failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An RUV API failure occurred\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_OBJSET_ERROR: /* namedobjset api failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A namedobject API failure occurred\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_PURGED_DATA: /* requested data has been purged */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Data required to update replica has been purged. " + "The replica must be reinitialized.\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_MISSING_DATA: /* data should be in the changelog, but is missing */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Missing data encountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_UNKNOWN_ERROR: /* unclassified error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An unknown error was ecountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An unknown error (%d) occurred " + "(cl5CreateReplayIterator)\n", + agmt_get_long_name(prp->agmt), rc); + return_value = UPDATE_TRANSIENT_ERROR; + } + } + else + { + int finished = 0; + ConnResult replay_crc; + char csn_str[CSN_STRSIZE]; + + memset ( (void*)&op, 0, sizeof (op) ); + entry.op = &op; + do { + cl5_operation_parameters_done ( entry.op ); + memset ( (void*)entry.op, 0, sizeof (op) ); + rc = cl5GetNextOperationToReplay(changelog_iterator, &entry); + switch (rc) + { + case CL5_SUCCESS: + /* check that we don't return dummy entries */ + if (is_dummy_operation (entry.op)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: changelog iteration code returned a dummy entry with csn %s, " + "skipping ...\n", + agmt_get_long_name(prp->agmt), csn_as_string(entry.op->csn, PR_FALSE, csn_str)); + continue; + } + replay_crc = replay_update(prp, entry.op); + if (CONN_OPERATION_SUCCESS != replay_crc) + { + int operation, error; + conn_get_error(prp->conn, &operation, &error); + csn_as_string(entry.op->csn, PR_FALSE, csn_str); + /* Figure out what to do next */ + if (CONN_OPERATION_FAILED == replay_crc) + { + /* Map ldap error code to return value */ + if (!ignore_error_and_keep_going(error)) + { + return_value = UPDATE_TRANSIENT_ERROR; + finished = 1; + } + else + { + agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 1 /*skipped*/); + } + slapi_log_error(finished ? SLAPI_LOG_FATAL : slapi_log_urp, repl_plugin_name, + "%s: Consumer failed to replay change (uniqueid %s, CSN %s): %s. %s.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str, + ldap_err2string(error), + finished ? "Will retry later" : "Skipping"); + } + else if (CONN_NOT_CONNECTED == replay_crc) + { + /* We lost the connection - enter backoff state */ + + return_value = UPDATE_TRANSIENT_ERROR; + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Consumer failed to replay change (uniqueid %s, CSN %s): " + "%s. Will retry later.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str, + error ? ldap_err2string(error) : "Connection lost"); + } + else if (CONN_TIMEOUT == replay_crc) + { + return_value = UPDATE_TIMEOUT; + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Consumer timed out to replay change (uniqueid %s, CSN %s): " + "%s.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str, + error ? ldap_err2string(error) : "Timeout"); + } + else if (CONN_LOCAL_ERROR == replay_crc) + { + /* + * Something bad happened on the local server - enter + * backoff state. + */ + return_value = UPDATE_TRANSIENT_ERROR; + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Failed to replay change (uniqueid %s, CSN %s): " + "Local error. Will retry later.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str); + } + + } + else + { + /* Positive response received */ + (*num_changes_sent)++; + agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 0 /*replayed*/); + } + break; + case CL5_BAD_DATA: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Invalid parameter passed to cl5GetNextOperationToReplay\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + finished = 1; + break; + case CL5_NOTFOUND: + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No more updates to send (cl5GetNextOperationToReplay)\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_NO_MORE_UPDATES; + finished = 1; + break; + case CL5_DB_ERROR: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A database error occurred (cl5GetNextOperationToReplay)\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + finished = 1; + break; + case CL5_BAD_FORMAT: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A malformed changelog entry was encountered (cl5GetNextOperationToReplay)\n", + agmt_get_long_name(prp->agmt)); + break; + case CL5_MEMORY_ERROR: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A memory allocation error occurred (cl5GetNextOperationToRepla)\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unknown error code (%d) returned from cl5GetNextOperationToReplay\n", + agmt_get_long_name(prp->agmt), rc); + return_value = UPDATE_TRANSIENT_ERROR; + break; + } + /* Check for protocol shutdown */ + if (prp->terminate) + { + return_value = UPDATE_NO_MORE_UPDATES; + finished = 1; + } + if (*num_changes_sent >= MAX_CHANGES_PER_SESSION) + { + return_value = UPDATE_YIELD; + finished = 1; + } + } while (!finished); + cl5_operation_parameters_done ( entry.op ); + cl5DestroyReplayIterator(&changelog_iterator); + } + return return_value; +} + + + +/* + * XXXggood this should probably be in the superclass, since the full update + * protocol is going to need it too. + */ +static int +repl5_inc_stop(Private_Repl_Protocol *prp) +{ + int return_value; + PRIntervalTime start, maxwait, now; + int seconds = 1200; + + maxwait = PR_SecondsToInterval(seconds); + prp->terminate = 1; + event_notify(prp, EVENT_PROTOCOL_SHUTDOWN); + start = PR_IntervalNow(); + now = start; + while (!prp->stopped && ((now - start) < maxwait)) + { + DS_Sleep(PR_SecondsToInterval(1)); + now = PR_IntervalNow(); + } + if (!prp->stopped) + { + /* Isn't listening. Do something drastic. */ + return_value = -1; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: repl5_inc_stop: protocol does not stop after %d seconds\n", + agmt_get_long_name(prp->agmt), seconds); + } + else + { + return_value = 0; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: repl5_inc_stop: protocol stopped after %d seconds\n", + agmt_get_long_name(prp->agmt), + PR_IntervalToSeconds(now-start)); + } + return return_value; +} + + + +static int +repl5_inc_status(Private_Repl_Protocol *prp) +{ + int return_value = 0; + + return return_value; +} + + + +static void +repl5_inc_notify_update(Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_TRIGGERING_CRITERIA_MET); +} + + +static void +repl5_inc_update_now(Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_REPLICATE_NOW); +} + + +static void +repl5_inc_notify_agmt_changed(Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_AGMT_CHANGED); +} + +static void +repl5_inc_notify_window_opened (Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_WINDOW_OPENED); +} + +static void +repl5_inc_notify_window_closed (Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_WINDOW_CLOSED); +} + +Private_Repl_Protocol * +Repl_5_Inc_Protocol_new(Repl_Protocol *rp) +{ + repl5_inc_private *rip = NULL; + Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol)); + prp->delete = repl5_inc_delete; + prp->run = repl5_inc_run; + prp->stop = repl5_inc_stop; + prp->status = repl5_inc_status; + prp->notify_update = repl5_inc_notify_update; + prp->notify_agmt_changed = repl5_inc_notify_agmt_changed; + prp->notify_window_opened = repl5_inc_notify_window_opened; + prp->notify_window_closed = repl5_inc_notify_window_closed; + prp->update_now = repl5_inc_update_now; + prp->replica_object = prot_get_replica_object(rp); + if ((prp->lock = PR_NewLock()) == NULL) + { + goto loser; + } + if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL) + { + goto loser; + } + prp->stopped = 0; + prp->terminate = 0; + prp->eventbits = 0; + prp->conn = prot_get_connection(rp); + prp->agmt = prot_get_agreement(rp); + prp->last_acquire_response_code = NSDS50_REPL_REPLICA_READY; + rip = (void *)slapi_ch_malloc(sizeof(repl5_inc_private)); + rip->ruv = NULL; + rip->backoff = NULL; + rip->rp = rp; + prp->private = (void *)rip; + prp->replica_acquired = PR_FALSE; + return prp; +loser: + repl5_inc_delete(&prp); + return NULL; +} + + + + +static void +repl5_inc_backoff_expired(time_t timer_fire_time, void *arg) +{ + Private_Repl_Protocol *prp = (Private_Repl_Protocol *)arg; + PR_ASSERT(NULL != prp); + event_notify(prp, EVENT_BACKOFF_EXPIRED); +} + + + +/* + * Examine the update vector and determine our course of action. + * There are 3 different possibilities, plus a catch-all error: + * 1 - no update vector (ruv is NULL). The consumer's replica is + * pristine, so it needs to be initialized. Return + * EXAMINE_RUV_PRISTINE_REPLICA. + * 2 - ruv is present, but its database generation ID doesn't + * match the local generation ID. This means that either + * the local replica must be reinitialized from the remote + * replica or vice-versa. Return + * EXAMINE_RUV_GENERATION_MISMATCH. + * 3 - ruv is present, and we have all updates needed to bring + * the replica up to date using the incremental protocol. + * return EXAMINE_RUV_OK. + * 4 - parameter error. Return EXAMINE_RUV_PARAM_ERROR + */ +static int +examine_update_vector(Private_Repl_Protocol *prp, RUV *remote_ruv) +{ + int return_value; + + PR_ASSERT(NULL != prp); + if (NULL == prp) + { + return_value = EXAMINE_RUV_PARAM_ERROR; + } + else if (NULL == remote_ruv) + { + return_value = EXAMINE_RUV_PRISTINE_REPLICA; + } + else + { + char *local_gen = NULL; + char *remote_gen = ruv_get_replica_generation(remote_ruv); + Object *local_ruv_obj; + RUV *local_ruv; + Replica *replica; + + PR_ASSERT(NULL != prp->replica_object); + replica = object_get_data(prp->replica_object); + PR_ASSERT(NULL != replica); + local_ruv_obj = replica_get_ruv (replica); + if (NULL != local_ruv_obj) + { + local_ruv = (RUV*)object_get_data (local_ruv_obj); + PR_ASSERT (local_ruv); + local_gen = ruv_get_replica_generation(local_ruv); + object_release (local_ruv_obj); + } + if (NULL == remote_gen || NULL == local_gen || strcmp(remote_gen, local_gen) != 0) + { + return_value = EXAMINE_RUV_GENERATION_MISMATCH; + } + else + { + return_value = EXAMINE_RUV_OK; + } + slapi_ch_free((void**)&remote_gen); + slapi_ch_free((void**)&local_gen); + } + return return_value; +} + + +/* + * When we get an error from an LDAP operation, we call this + * function to decide if we should just keep replaying + * updates, or if we should stop, back off, and try again + * later. + * Returns PR_TRUE if we shoould keep going, PR_FALSE if + * we should back off and try again later. + * + * In general, we keep going if the return code is consistent + * with some sort of bug in URP that causes the consumer to + * emit an error code that it shouldn't have, e.g. LDAP_ALREADY_EXISTS. + * + * We stop if there's some indication that the server just completely + * failed to process the operation, e.g. LDAP_OPERATIONS_ERROR. + */ +static PRBool +ignore_error_and_keep_going(int error) +{ + int return_value; + + switch (error) + { + /* Cases where we keep going */ + case LDAP_SUCCESS: + case LDAP_NO_SUCH_ATTRIBUTE: + case LDAP_UNDEFINED_TYPE: + case LDAP_CONSTRAINT_VIOLATION: + case LDAP_TYPE_OR_VALUE_EXISTS: + case LDAP_INVALID_SYNTAX: + case LDAP_NO_SUCH_OBJECT: + case LDAP_INVALID_DN_SYNTAX: + case LDAP_IS_LEAF: + case LDAP_INSUFFICIENT_ACCESS: + case LDAP_NAMING_VIOLATION: + case LDAP_OBJECT_CLASS_VIOLATION: + case LDAP_NOT_ALLOWED_ON_NONLEAF: + case LDAP_NOT_ALLOWED_ON_RDN: + case LDAP_ALREADY_EXISTS: + case LDAP_NO_OBJECT_CLASS_MODS: + return_value = PR_TRUE; + break; + + /* Cases where we stop and retry */ + case LDAP_OPERATIONS_ERROR: + case LDAP_PROTOCOL_ERROR: + case LDAP_TIMELIMIT_EXCEEDED: + case LDAP_SIZELIMIT_EXCEEDED: + case LDAP_STRONG_AUTH_NOT_SUPPORTED: + case LDAP_STRONG_AUTH_REQUIRED: + case LDAP_PARTIAL_RESULTS: + case LDAP_REFERRAL: + case LDAP_ADMINLIMIT_EXCEEDED: + case LDAP_UNAVAILABLE_CRITICAL_EXTENSION: + case LDAP_CONFIDENTIALITY_REQUIRED: + case LDAP_SASL_BIND_IN_PROGRESS: + case LDAP_INAPPROPRIATE_MATCHING: + case LDAP_ALIAS_PROBLEM: + case LDAP_ALIAS_DEREF_PROBLEM: + case LDAP_INAPPROPRIATE_AUTH: + case LDAP_INVALID_CREDENTIALS: + case LDAP_BUSY: + case LDAP_UNAVAILABLE: + case LDAP_UNWILLING_TO_PERFORM: + case LDAP_LOOP_DETECT: + case LDAP_SORT_CONTROL_MISSING: + case LDAP_INDEX_RANGE_ERROR: + case LDAP_RESULTS_TOO_LARGE: + case LDAP_AFFECTS_MULTIPLE_DSAS: + case LDAP_OTHER: + case LDAP_SERVER_DOWN: + case LDAP_LOCAL_ERROR: + case LDAP_ENCODING_ERROR: + case LDAP_DECODING_ERROR: + case LDAP_TIMEOUT: + case LDAP_AUTH_UNKNOWN: + case LDAP_FILTER_ERROR: + case LDAP_USER_CANCELLED: + case LDAP_PARAM_ERROR: + case LDAP_NO_MEMORY: + case LDAP_CONNECT_ERROR: + case LDAP_NOT_SUPPORTED: + case LDAP_CONTROL_NOT_FOUND: + case LDAP_NO_RESULTS_RETURNED: + case LDAP_MORE_RESULTS_TO_RETURN: + case LDAP_CLIENT_LOOP: + case LDAP_REFERRAL_LIMIT_EXCEEDED: + return_value = PR_FALSE; + break; + } + return return_value; +} + +/* this function converts a state to its name - for debug output */ +static const char* +state2name (int state) +{ + switch (state) + { + case STATE_START: return "start"; + case STATE_WAIT_WINDOW_OPEN: return "wait_for_window_to_open"; + case STATE_WAIT_CHANGES: return "wait_for_changes"; + case STATE_READY_TO_ACQUIRE: return "ready_to_acquire_replica"; + case STATE_BACKOFF_START: return "start_backoff"; + case STATE_BACKOFF: return "backoff"; + case STATE_SENDING_UPDATES: return "sending_updates"; + case STATE_STOP_FATAL_ERROR: return "stop_fatal_error"; + case STATE_STOP_FATAL_ERROR_PART2: return "stop_fatal_error"; + case STATE_STOP_NORMAL_TERMINATION: return "stop_normal_termination"; + default: return "invalid_state"; + } +} + +/* this function convert s an event to its name - for debug output */ +static const char* +event2name (int event) +{ + switch (event) + { + case EVENT_WINDOW_OPENED: return "update_window_opened"; + case EVENT_WINDOW_CLOSED: return "update_window_closed"; + case EVENT_TRIGGERING_CRITERIA_MET: return "data_modified"; + case EVENT_BACKOFF_EXPIRED: return "backoff_timer_expired"; + case EVENT_REPLICATE_NOW: return "replicate_now"; + case EVENT_PROTOCOL_SHUTDOWN: return "protocol_shutdown"; + case EVENT_AGMT_CHANGED: return "agreement_changed"; + default: return "invalid_event"; + } +} + +static const char* +op2string(int op) +{ + switch (op) { + case SLAPI_OPERATION_ADD: + return "add"; + case SLAPI_OPERATION_MODIFY: + return "modify"; + case SLAPI_OPERATION_DELETE: + return "delete"; + case SLAPI_OPERATION_MODRDN: + return "rename"; + case SLAPI_OPERATION_EXTENDED: + return "extended"; + } + + return "unknown"; +} diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c new file mode 100644 index 00000000..eae3b238 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_init.c @@ -0,0 +1,572 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + repl5_init.c - plugin initialization functions +*/ + +/* + * Add an entry like the following to dse.ldif to enable this plugin: + +dn: cn=Multi-Master Replication Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: Legacy Replication Plugin +nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/replication-plugin.so +nsslapd-plugininitfunc: replication_multimaster_plugin_init +nsslapd-plugintype: object +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database +nsslapd-plugin-depends-on-named: Class of Service +nsslapd-pluginid: replication-multimaster +nsslapd-pluginversion: 5.0b1 +nsslapd-pluginvendor: Netscape Communications +nsslapd-plugindescription: Multi-Master Replication Plugin + +*/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "cl5.h" /* changelog interface */ +#include "dirver.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +/* #ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif*/ + +#define NSDS_REPL_NAME_PREFIX "Netscape Replication" + +static char *start_oid_list[] = { + REPL_START_NSDS50_REPLICATION_REQUEST_OID, + NULL +}; +static char *start_name_list[] = { + NSDS_REPL_NAME_PREFIX " Start Session", + NULL +}; +static char *end_oid_list[] = { + REPL_END_NSDS50_REPLICATION_REQUEST_OID, + NULL +}; +static char *end_name_list[] = { + NSDS_REPL_NAME_PREFIX " End Session", + NULL +}; +static char *total_oid_list[] = { + REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID, + NULL +}; +static char *total_name_list[] = { + NSDS_REPL_NAME_PREFIX " Total Update Entry", + NULL +}; +static char *response_oid_list[] = { + REPL_NSDS50_REPLICATION_RESPONSE_OID, + NULL +}; +static char *response_name_list[] = { + NSDS_REPL_NAME_PREFIX " Response", + NULL +}; + +/* List of plugin identities for every plugin registered. Plugin identity + is passed by the server in the plugin init function and must be supplied + by the plugin to all internal operations it initiates + */ + +/* ----------------------------- Multi-Master Replication Plugin */ + +static Slapi_PluginDesc multimasterdesc = {"replication-multimaster", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master Replication Plugin"}; +static Slapi_PluginDesc multimasterpreopdesc = {"replication-multimaster-preop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication pre-operation plugin"}; +static Slapi_PluginDesc multimasterpostopdesc = {"replication-multimaster-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication post-operation plugin"}; +static Slapi_PluginDesc multimasterinternalpreopdesc = {"replication-multimaster-internalpreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication internal pre-operation plugin"}; +static Slapi_PluginDesc multimasterinternalpostopdesc = {"replication-multimaster-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication internal post-operation plugin"}; +static Slapi_PluginDesc multimasterbepreopdesc = {"replication-multimaster-bepreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication bepre-operation plugin"}; +static Slapi_PluginDesc multimasterbepostopdesc = {"replication-multimaster-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication bepost-operation plugin"}; +static Slapi_PluginDesc multimasterextopdesc = { "replication-multimaster-extop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication extended-operation plugin" }; + +static int multimaster_stopped_flag; /* A flag which is set when all the plugin threads are to stop */ +static int multimaster_started_flag = 0; + +/* Thread private data and interface */ +static PRUintn thread_private_agmtname; /* thread private index for logging*/ +static PRUintn thread_private_cache; + +char* +get_thread_private_agmtname() +{ + char *agmtname = NULL; + if (thread_private_agmtname) + agmtname = PR_GetThreadPrivate(thread_private_agmtname); + return (agmtname ? agmtname : ""); +} + +void +set_thread_private_agmtname(const char *agmtname) +{ + if (thread_private_agmtname) + PR_SetThreadPrivate(thread_private_agmtname, (void *)agmtname); +} + +void* +get_thread_private_cache () +{ + void *buf = NULL; + if ( thread_private_cache ) + buf = PR_GetThreadPrivate ( thread_private_cache ); + return buf; +} + +void +set_thread_private_cache ( void *buf ) +{ + if ( thread_private_cache ) + PR_SetThreadPrivate ( thread_private_cache, buf ); +} + +char* +get_repl_session_id (Slapi_PBlock *pb, char *idstr, CSN **csn) +{ + int connid=-1, opid=-1; + CSN *opcsn; + char opcsnstr[CSN_STRSIZE]; + + *idstr = '\0'; + opcsn = NULL; + opcsnstr[0] = '\0'; + + if (pb) { + Slapi_Operation *op; + slapi_pblock_get (pb, SLAPI_OPERATION_ID, &opid); + /* Avoid "Connection is NULL and hence cannot access SLAPI_CONN_ID" */ + if (opid) { + slapi_pblock_get (pb, SLAPI_CONN_ID, &connid); + sprintf (idstr, "conn=%d op=%d", connid, opid); + } + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + opcsn = operation_get_csn (op); + if (opcsn) { + csn_as_string (opcsn, PR_FALSE, opcsnstr); + strcat (idstr, " csn="); + strcat (idstr, opcsnstr); + } + } + if (csn) { + *csn = opcsn; + } + return idstr; +} + + +int +multimaster_preop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterpreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) multimaster_preop_bind ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) multimaster_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void *) multimaster_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) multimaster_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void *) multimaster_preop_modrdn ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) multimaster_preop_search ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void *) multimaster_preop_compare ) != 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_preop_init failed\n" ); + rc= -1; + } + return rc; +} + +int +multimaster_postop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_BIND_FN, (void *) multimaster_postop_bind ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) multimaster_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) multimaster_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) multimaster_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) multimaster_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_postop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_internalpreop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterinternalpreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) multimaster_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, (void *) multimaster_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) multimaster_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, (void *) multimaster_preop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_internalpreop_init failed\n" ); + rc= -1; + } + return rc; +} + +int +multimaster_internalpostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterinternalpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) multimaster_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) multimaster_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) multimaster_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) multimaster_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_internalpostop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_bepreop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbepreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_ADD_FN, (void *) multimaster_bepreop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_DELETE_FN, (void *) multimaster_bepreop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_MODIFY_FN, (void *) multimaster_bepreop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_MODRDN_FN, (void *) multimaster_bepreop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_bepreop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_bepostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbepostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_POST_MODRDN_FN, (void *) multimaster_bepostop_modrdn ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_POST_DELETE_FN, (void *) multimaster_bepostop_delete ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_bepostop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_start_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)start_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)start_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_StartNSDS50ReplicationRequest )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (StartNSDS50ReplicationRequest) failed\n" ); + rc= -1; + } + + + return rc; +} + + +int +multimaster_end_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)end_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)end_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_EndNSDS50ReplicationRequest )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_end_extop_init (EndNSDS50ReplicationRequest) failed\n" ); + rc= -1; + } + + return rc; +} + + +int +multimaster_total_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + void *identity = NULL; + + /* get plugin identity and store it to pass to internal operations */ + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)total_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)total_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_NSDS50ReplicationEntry )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (NSDS50ReplicationEntry failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_response_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + void *identity = NULL; + + /* get plugin identity and store it to pass to internal operations */ + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)response_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)response_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)extop_noop )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (NSDS50ReplicationResponse failed\n" ); + rc= -1; + } + + return rc; +} + + +static PRBool +check_for_ldif_dump(Slapi_PBlock *pb) +{ + int i; + int argc; + char **argv; + PRBool return_value = PR_FALSE; + + slapi_pblock_get( pb, SLAPI_ARGC, &argc); + slapi_pblock_get( pb, SLAPI_ARGV, &argv); + + for (i = 1; i < argc && !return_value; i++) + { + if (strcmp(argv[i], "db2ldif") == 0) + { + return_value = PR_TRUE; + } + } + return return_value; +} + + +static PRBool is_ldif_dump = PR_FALSE; + +int +multimaster_start( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if (!multimaster_started_flag) + { + /* Initialize thread private data for logging. Ignore if fails */ + PR_NewThreadPrivateIndex (&thread_private_agmtname, NULL); + PR_NewThreadPrivateIndex (&thread_private_cache, NULL); + + /* Decode the command line args to see if we're dumping to LDIF */ + is_ldif_dump = check_for_ldif_dump(pb); + + /* allow online replica configuration */ + rc = replica_config_init (); + if (rc != 0) + goto out; + + slapi_register_supported_control(REPL_NSDS50_UPDATE_INFO_CONTROL_OID, + SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE | + SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN); + + /* Stash away our partial URL, used in RUVs */ + rc = multimaster_set_local_purl(); + if (rc != 0) + goto out; + + /* Initialise support for cn=monitor */ + rc = repl_monitor_init(); + if (rc != 0) + goto out; + + /* initialize name hash */ + rc = replica_init_name_hash (); + if (rc != 0) + goto out; + + /* initialize dn hash */ + rc = replica_init_dn_hash (); + if (rc != 0) + goto out; + + /* create replicas */ + multimaster_mtnode_construct_replicas (); + + /* Initialise the 5.0 Changelog */ + rc = changelog5_init(); + if (rc != 0) + goto out; + + /* Initialize the replication agreements, unless we're dumping LDIF */ + if (!is_ldif_dump) + { + rc = agmtlist_config_init(); + if (rc != 0) + goto out; + } + + /* check if the replica's data was reloaded offline and we need + to reinitialize replica's changelog. This should be done + after the changelog is initialized */ + + replica_enumerate_replicas (replica_check_for_data_reload, NULL); + + /* register to be notified when backend state changes */ + slapi_register_backend_state_change((void *)multimaster_be_state_change, + multimaster_be_state_change); + + multimaster_started_flag = 1; + multimaster_stopped_flag = 0; + } +out: + return rc; +} + +int +multimaster_stop( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if (!multimaster_stopped_flag) + { + if (!is_ldif_dump) + { + agmtlist_shutdown(); /* Shut down replication agreements */ + } + + /* unregister backend state change notification */ + slapi_unregister_backend_state_change((void *)multimaster_be_state_change); + + changelog5_cleanup(); /* Shut down the changelog */ + multimaster_mtnode_extension_destroy(); /* Destroy mapping tree node exts */ + replica_destroy_name_hash(); /* destroy the hash and its remaining content */ + replica_config_destroy (); /* Destroy replica config info */ + multimaster_stopped_flag = 1; + /* JCMREPL - Wait for all our threads to stop */ + /* JCMREPL - Shut down the replication plugin */ + /* JCMREPL - Mark all the replication plugin interfaces at not enabled. */ + } + return rc; +} + + +PRBool +multimaster_started() +{ + return(multimaster_started_flag != 0); +} + + +/* + * Initialize the multimaster replication plugin. + */ +int replication_multimaster_plugin_init(Slapi_PBlock *pb) +{ + static int multimaster_initialised= 0; + int rc= 0; /* OK */ + void *identity = NULL; + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + repl_set_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION, identity); + + /* need the repl plugin path for the chain on update function */ +/* slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &entry); + PR_ASSERT(entry); + path = slapi_entry_attr_get_charptr(entry, "nsslapd-pluginpath"); + repl_set_repl_plugin_path(path); + slapi_ch_free_string(&path); +*/ + multimaster_mtnode_extension_init (); + + if(config_is_slapd_lite()) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "replication plugin not approved for restricted" + " mode Directory Server.\n" ); + rc= -1; + } + if(rc==0 && !multimaster_initialised) + { + /* initialize replica hash - has to be done before mapping tree is + initialized so we can't do it in the start function */ + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replication_multimaster_plugin_init: failed to initialize replica hash\n"); + return -1; + } + + /* Initialize extensions */ + repl_con_init_ext(); + repl_sup_init_ext(); + + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterdesc ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) multimaster_start ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) multimaster_stop ); + + /* Register the plugin interfaces we implement */ + rc= slapi_register_plugin("preoperation", 1 /* Enabled */, "multimaster_preop_init", multimaster_preop_init, "Multimaster replication preoperation plugin", NULL, identity); + rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "multimaster_postop_init", multimaster_postop_init, "Multimaster replication postoperation plugin", NULL, identity); + rc= slapi_register_plugin("bepreoperation", 1 /* Enabled */, "multimaster_bepreop_init", multimaster_bepreop_init, "Multimaster replication bepreoperation plugin", NULL, identity); + rc= slapi_register_plugin("bepostoperation", 1 /* Enabled */, "multimaster_bepostop_init", multimaster_bepostop_init, "Multimaster replication bepostoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "multimaster_internalpreop_init", multimaster_internalpreop_init, "Multimaster replication internal preoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "multimaster_internalpostop_init", multimaster_internalpostop_init, "Multimaster replication internal postoperation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_start_extop_init", multimaster_start_extop_init, "Multimaster replication start extended operation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_end_extop_init", multimaster_end_extop_init, "Multimaster replication end extended operation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_total_extop_init", multimaster_total_extop_init, "Multimaster replication total update extended operation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_response_extop_init", multimaster_response_extop_init, "Multimaster replication extended response plugin", NULL, identity); + } + return rc; +} diff --git a/ldap/servers/plugins/replication/repl5_mtnode_ext.c b/ldap/servers/plugins/replication/repl5_mtnode_ext.c new file mode 100644 index 00000000..e677927c --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_mtnode_ext.c @@ -0,0 +1,194 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_replica.c */ + +#include "repl.h" /* ONREPL - this is bad */ +#include "repl5.h" +#include "cl5_api.h" + +/* global data */ +static DataList *root_list; + +/* + * Mapping tree node extension management. Node stores replica object + */ + +void +multimaster_mtnode_extension_init () +{ + /* Initialize list that store node roots. It is used during + plugin startup to create replica objects */ + root_list = dl_new (); + dl_init (root_list, 0); +} + +void +multimaster_mtnode_extension_destroy () +{ + dl_cleanup (root_list, (FREEFN)slapi_sdn_free); + dl_free (&root_list); +} + +/* This function loops over the list of node roots, constructing replica objects + where exist */ +void +multimaster_mtnode_construct_replicas () +{ + Slapi_DN *root; + int cookie; + Replica *r; + mapping_tree_node *mtnode; + multimaster_mtnode_extension *ext; + + for (root = dl_get_first (root_list, &cookie); root; + root = dl_get_next (root_list, &cookie)) + { + r = replica_new(root); + if (r) + { + + mtnode = slapi_get_mapping_tree_node_by_dn(root); + if (mtnode == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "multimaster_mtnode_construct_replicas: " + "failed to locate mapping tree node for %s\n", + slapi_sdn_get_dn (root)); + continue; + } + + ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode); + if (ext == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "multimaster_mtnode_construct_replicas: " + "failed to locate replication extension of mapping tree node for %s\n", + slapi_sdn_get_dn (root)); + continue; + } + + ext->replica = object_new(r, replica_destroy); + if (replica_add_by_name (replica_get_name (r), ext->replica) != 0) + { + object_release (ext->replica); + ext->replica = NULL; + } + } + } +} + +void * +multimaster_mtnode_extension_constructor (void *object, void *parent) +{ + mapping_tree_node *node; + const Slapi_DN *root; + multimaster_mtnode_extension *ext; + + ext = (multimaster_mtnode_extension *)slapi_ch_calloc (1, sizeof (multimaster_mtnode_extension)); + + node = (mapping_tree_node *)object; + + /* replica can be attached only to local public data */ + if (slapi_mapping_tree_node_is_set (node, SLAPI_MTN_LOCAL) && + !slapi_mapping_tree_node_is_set (node, SLAPI_MTN_PRIVATE)) + { + root = slapi_get_mapping_tree_node_root (node); + /* ONREPL - we don't create replica object here because + we can't fully initialize replica here since backends + are not yet started. Instead, replica objects are created + during replication plugin startup */ + if (root) + { + /* for now just store node root in the root list */ + dl_add (root_list, slapi_sdn_dup (root)); + } + } + + return ext; +} + +void +multimaster_mtnode_extension_destructor (void* ext, void *object, void *parent) +{ + if (ext) + { + multimaster_mtnode_extension *mtnode_ext = (multimaster_mtnode_extension *)ext; + if (mtnode_ext->replica) + { + object_release (mtnode_ext->replica); + mtnode_ext->replica = NULL; + } + } +} + +Object * +replica_get_replica_from_dn (const Slapi_DN *dn) +{ + mapping_tree_node *mtnode; + multimaster_mtnode_extension *ext; + + if (dn == NULL) + return NULL; + + mtnode = slapi_get_mapping_tree_node_by_dn(dn); + if (mtnode == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_get_replica_from_dn: " + "failed to locate mapping tree node for %s\n", + slapi_sdn_get_dn (dn)); + return NULL; + } + + ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode); + if (ext == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_get_replica_from_dn: " + "failed to locate replication extension of mapping tree node for %s\n", + slapi_sdn_get_dn (dn)); + return NULL; + } + + if (ext->replica) + object_acquire (ext->replica); + + return ext->replica; +} + +Object *replica_get_replica_for_op (Slapi_PBlock *pb) +{ + char *dn; + Slapi_DN *sdn; + Object *repl_obj = NULL; + + if (pb) + { + /* get replica generation for this operation */ + slapi_pblock_get (pb, SLAPI_TARGET_DN, &dn); + sdn = slapi_sdn_new_dn_byref(dn); + repl_obj = replica_get_replica_from_dn (sdn); + + slapi_sdn_free (&sdn); + } + + return repl_obj; +} + +Object *replica_get_for_backend (const char *be_name) +{ + Slapi_Backend *be; + const Slapi_DN *suffix; + Object *r_obj; + + be = slapi_be_select_by_instance_name(be_name); + if (NULL == be) + return NULL; + + suffix = slapi_be_getsuffix(be, 0); + + r_obj = replica_get_replica_from_dn (suffix); + + return r_obj; +} diff --git a/ldap/servers/plugins/replication/repl5_plugins.c b/ldap/servers/plugins/replication/repl5_plugins.c new file mode 100644 index 00000000..afee321a --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_plugins.c @@ -0,0 +1,1416 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * repl5_consumer.c - consumer plugin functions + */ + +/* + * LDAP Data Model Constraints... + * + * 1) Parent entry must exist. + * 2) RDN must be unique. + * 3) RDN components must be attribute values. + * 4) Must conform to the schema constraints - Single Valued Attributes. + * 5) Must conform to the schema constraints - Required Attributes. + * 6) Must conform to the schema constraints - No Extra Attributes. + * 7) Duplicate attribute values are not permited. + * 8) Cycle in the ancestry graph not permitted. + * + * Update Resolution Procedures... + * 1) ... + * 2) Add the UniqueID to the RDN. + * 3) Remove the not present RDN component. + * Use the UniqueID if the RDN becomes empty. + * 4) Keep the most recent value. + * 5) Ignore. + * 6) Ignore. + * 7) Keep the largest CSN for the duplicate value. + * 8) Don't check for this. + */ + +#include "repl5.h" +#include "repl.h" +#include "cl5_api.h" +#include "urp.h" + +static char *local_purl = NULL; +static char *purl_attrs[] = {"nsslapd-localhost", "nsslapd-port", "nsslapd-secureport", NULL}; + +/* Forward declarations */ +static void copy_operation_parameters(Slapi_PBlock *pb); +static int write_changelog_and_ruv(Slapi_PBlock *pb); +static int process_postop (Slapi_PBlock *pb); +static int cancel_opcsn (Slapi_PBlock *pb); +static int ruv_tombstone_op (Slapi_PBlock *pb); +static PRBool process_operation (Slapi_PBlock *pb, const CSN *csn); +static PRBool is_mmr_replica (Slapi_PBlock *pb); +static const char *replica_get_purl_for_op (const Replica *r, Slapi_PBlock *pb, const CSN *opcsn); +static void strip_legacy_info (slapi_operation_parameters *op_params); +static void close_changelog_for_replica (Object *r_obj); +static void process_new_ruv_for_replica (Replica *r); + +/* + * XXXggood - what to do if both ssl and non-ssl ports available? How + * do you know which to use? Offer a choice in replication config? + */ +int +multimaster_set_local_purl() +{ + int rc = 0; + Slapi_Entry **entries; + Slapi_PBlock *pb = NULL; + + pb = slapi_pblock_new (); + + slapi_search_internal_set_pb (pb, "cn=config", LDAP_SCOPE_BASE, + "objectclass=*", purl_attrs, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_set_local_purl: " + "unable to read server configuration: error %d\n", rc); + } + else + { + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || NULL == entries[0]) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_set_local_purl: " + "server configuration missing\n"); + rc = -1; + } + else + { + char *host = slapi_entry_attr_get_charptr(entries[0], "nsslapd-localhost"); + char *port = slapi_entry_attr_get_charptr(entries[0], "nsslapd-port"); + char *sslport = slapi_entry_attr_get_charptr(entries[0], "nsslapd-secureport"); + if (host == NULL || ((port == NULL && sslport == NULL))) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "multimaster_set_local_purl: invalid server " + "configuration\n"); + } + else + { + int len = 0; + char *patt = "ldap://%s:%s"; + len += strlen(host); + len += strlen(port); + len += strlen(patt); + len++; /* for \0 */ + local_purl = slapi_ch_malloc(len); + sprintf(local_purl, patt, host, port); + } + + /* slapi_ch_free acceptS NULL pointer */ + slapi_ch_free ((void**)&host); + slapi_ch_free ((void**)&port); + slapi_ch_free ((void**)&sslport); + } + } + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + + return rc; +} + + +const char * +multimaster_get_local_purl() +{ + return local_purl; +} + + +/* ================= Multimaster Pre-Op Plugin Points ================== */ + + +int +multimaster_preop_bind (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_preop_add (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + char *superior_uuid= NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, &superior_uuid, &csn, NULL /* modrdn_mods */); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control - Add\n", sessionid); + } + else if (1 == drc) + { + /* + * For add operations, we just set the operation csn. The entry's + * uniqueid should already be an attribute of the replicated entry. + */ + struct slapi_operation_parameters *op_params; + + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + slapi_ch_free ((void**)&superior_uuid); + + return -1; + } + + operation_set_csn(op, csn); + slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, target_uuid); + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + /* JCMREPL - Complain if there's no superior uuid */ + op_params->p.p_add.parentuniqueid= superior_uuid; /* JCMREPL - Not very elegant */ + /* JCMREPL - When do these things get free'd? */ + if(target_uuid!=NULL) + { + /* + * Make sure that the Unique Identifier in the Control is the + * same as the one in the entry. + */ + Slapi_Entry *addentry; + char *entry_uuid; + slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &addentry ); + entry_uuid= slapi_entry_attr_get_charptr( addentry, SLAPI_ATTR_UNIQUEID); + if(entry_uuid==NULL) + { + /* Odd that the entry doesn't have a Unique Identifier. But, we can fix it. */ + slapi_entry_set_uniqueid(addentry, slapi_ch_strdup(target_uuid)); /* JCMREPL - strdup EVIL! There should be a uuid dup function. */ + } + else + { + if(strcasecmp(entry_uuid,target_uuid)!=0) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s Replicated Add received with Control_UUID=%s and Entry_UUID=%s.\n", + sessionid, target_uuid,entry_uuid); + } + + slapi_ch_free ((void**)&entry_uuid); + } + } + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + + return 0; +} + +int +multimaster_preop_delete (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_replica_attr_handler ( op, (void*)replica_get_attr ); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, NULL, &csn, NULL /* modrdn_mods */); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control - Delete\n", sessionid); + } + else if (1 == drc) + { + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, + "%s replication operation not processed, replica unavailable " + "or csn ignored\n", sessionid); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + + return -1; + } + + /* + * For delete operations, we pass the uniqueid of the deleted entry + * to the backend and let it sort out which entry to really delete. + * We also set the operation csn. + */ + operation_set_csn(op, csn); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid); + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + slapi_operation_set_replica_attr_handler ( op, (void*)replica_get_attr ); + + return 0; +} + +int +multimaster_preop_modify (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, NULL, &csn, NULL /* modrdn_mods */); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control- Modify\n", sessionid); + } + else if (1 == drc) + { + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, + "%s replication operation not processed, replica unavailable " + "or csn ignored\n", sessionid); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + + return -1; + } + + /* + * For modify operations, we pass the uniqueid of the modified entry + * to the backend and let it sort out which entry to really modify. + * We also set the operation csn. + */ + operation_set_csn(op, csn); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid); + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + return 0; +} + +int +multimaster_preop_modrdn (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + char *newsuperior_uuid = NULL; + LDAPMod **modrdn_mods = NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, &newsuperior_uuid, + &csn, &modrdn_mods); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control - ModRDN\n", sessionid); + } + else if (1 == drc) + { + /* + * For modrdn operations, we pass the uniqueid of the entry being + * renamed to the backend and let it sort out which entry to really + * rename. We also set the operation csn, and if the newsuperior_uuid + * was sent, we decode that as well. + */ + struct slapi_operation_parameters *op_params; + + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + slapi_ch_free ((void**)&newsuperior_uuid); + ldap_mods_free (modrdn_mods, 1); + + return -1; + } + + operation_set_csn(op, csn); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid); + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid= newsuperior_uuid; /* JCMREPL - Not very elegant */ + } + + /* + * The replicated modrdn operation may also contain a sequence + * that contains side effects of the modrdn operation, e.g. the + * modifiersname and modifytimestamp are updated. + */ + if (NULL != modrdn_mods) + { + LDAPMod **mods; + Slapi_Mods smods; + int i; + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + slapi_mods_init_passin(&smods, mods); + for (i = 0; NULL != modrdn_mods[i]; i++) + { + slapi_mods_add_ldapmod(&smods, modrdn_mods[i]); /* Does not copy mod */ + } + mods = slapi_mods_get_ldapmods_passout(&smods); + slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods); + slapi_mods_done(&smods); + slapi_ch_free((void **)&modrdn_mods); /* Free container only - contents are referred to by array "mods" */ + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + + return 0; +} + +int +multimaster_preop_search (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_preop_compare (Slapi_PBlock *pb) +{ + return 0; +} + +static void +purge_entry_state_information (Slapi_PBlock *pb) +{ + CSN *purge_csn; + Object *repl_obj; + Replica *replica; + + /* we don't want to trim RUV tombstone because we will + deadlock with ourself */ + if (ruv_tombstone_op (pb)) + return; + + repl_obj = replica_get_replica_for_op(pb); + if (NULL != repl_obj) + { + replica = object_get_data(repl_obj); + if (NULL != replica) + { + purge_csn = replica_get_purge_csn(replica); + } + if (NULL != purge_csn) + { + Slapi_Entry *e; + int optype; + + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype); + switch (optype) + { + case SLAPI_OPERATION_MODIFY: + slapi_pblock_get(pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); + break; + case SLAPI_OPERATION_MODRDN: + /* XXXggood - the following always gives a NULL entry - why? */ + slapi_pblock_get(pb, SLAPI_MODRDN_EXISTING_ENTRY, &e); + break; + case SLAPI_OPERATION_DELETE: + slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &e); + break; + default: + e = NULL; /* Don't purge on ADD - not needed */ + break; + } + if (NULL != e) + { + char csn_str[CSN_STRSIZE]; + entry_purge_state_information(e, purge_csn); + /* conntion is always null */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Purged state information from entry %s up to " + "CSN %s\n", slapi_entry_get_dn(e), + csn_as_string(purge_csn, PR_FALSE, csn_str)); + } + csn_free(&purge_csn); + } + object_release(repl_obj); + } +} + +int +multimaster_bepreop_add (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if (is_replicated_operation && !is_fixup_operation) + { + rc = urp_add_operation(pb); + } + + return rc; +} + +int +multimaster_bepreop_delete (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if(is_replicated_operation && !is_fixup_operation) + { + rc = urp_delete_operation(pb); + } + + return rc; +} + +int +multimaster_bepreop_modify (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if(is_replicated_operation && !is_fixup_operation) + { + rc = urp_modify_operation(pb); + } + + /* Clean up old state information */ + purge_entry_state_information(pb); + + return rc; +} + +int +multimaster_bepreop_modrdn (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if(is_replicated_operation && !is_fixup_operation) + { + rc = urp_modrdn_operation(pb); + } + + /* Clean up old state information */ + purge_entry_state_information(pb); + + return rc; +} + +int +multimaster_bepostop_modrdn (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_bepostop_delete (Slapi_PBlock *pb) +{ + return 0; +} + +/* postop - write to changelog */ +int +multimaster_postop_bind (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_postop_add (Slapi_PBlock *pb) +{ + return process_postop(pb); +} + +int +multimaster_postop_delete (Slapi_PBlock *pb) +{ + int rc; + Slapi_Operation *op; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if ( ! operation_is_flag_set (op, OP_FLAG_REPL_FIXUP) ) + { + urp_post_delete_operation (pb); + } + rc = process_postop(pb); + return rc; +} + +int +multimaster_postop_modify (Slapi_PBlock *pb) +{ + return process_postop(pb); +} + +int +multimaster_postop_modrdn (Slapi_PBlock *pb) +{ + int rc; + Slapi_Operation *op; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if ( ! operation_is_flag_set (op, OP_FLAG_REPL_FIXUP) ) + { + urp_post_modrdn_operation (pb); + } + rc = process_postop(pb); + return rc; +} + + +/* Helper functions */ + +/* + * This function makes a copy of the operation parameters + * and stashes them in the consumer operation extension. + * This is where the 5.0 Change Log will get the operation + * details from. + */ +static void +copy_operation_parameters(Slapi_PBlock *pb) +{ + Slapi_Operation *op = NULL; + struct slapi_operation_parameters *op_params; + supplier_operation_extension *opext; + Object *repl_obj; + Replica *replica; + + repl_obj = replica_get_replica_for_op (pb); + + /* we are only interested in the updates to replicas */ + if (repl_obj) + { + /* we only save the original operation parameters for replicated operations + since client operations don't go through urp engine and pblock data can be logged */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + PR_ASSERT (op); + + replica = (Replica*)object_get_data (repl_obj); + PR_ASSERT (replica); + + opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op); + if (operation_is_flag_set(op,OP_FLAG_REPLICATED) && + !operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + slapi_pblock_get (pb, SLAPI_OPERATION_PARAMETERS, &op_params); + opext->operation_parameters= operation_parameters_dup(op_params); + } + + /* this condition is needed to avoid re-entering lock when + ruv state is updated */ + if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + /* save replica generation in case it changes */ + opext->repl_gen = replica_get_generation (replica); + } + + object_release (repl_obj); + } +} + +/* + * Helper function: update the RUV so that it reflects the + * locally-processed update. This is called for both replicated + * and non-replicated operations. + */ +static void +update_ruv_component(Replica *replica, CSN *opcsn, Slapi_PBlock *pb) +{ + PRBool legacy; + char *purl; + + if (!replica || !opcsn) + return; + + /* Replica configured, so update its ruv */ + legacy = replica_is_legacy_consumer (replica); + if (legacy) + purl = replica_get_legacy_purl (replica); + else + purl = (char*)replica_get_purl_for_op (replica, pb, opcsn); + + replica_update_ruv(replica, opcsn, purl); + + if (legacy) + { + slapi_ch_free ((void**)&purl); + } +} + +/* + * Write the changelog. Note: it is an error to call write_changelog_and_ruv() for fixup + * operations. The caller should avoid calling this function if the operation is + * a fixup op. + * Also update the ruv - we need to do this while we have the replica lock acquired + * so that the csn is written to the changelog and the ruv is updated with the csn + * in one atomic operation - if there is no changelog, we still need to update + * the ruv (e.g. for consumers) + */ +static int +write_changelog_and_ruv (Slapi_PBlock *pb) +{ + int rc; + slapi_operation_parameters *op_params = NULL; + Object *repl_obj; + int return_value = 0; + Replica *r; + + /* we only log changes for operations applied to a replica */ + repl_obj = replica_get_replica_for_op (pb); + if (repl_obj == NULL) + return 0; + + r = (Replica*)object_get_data (repl_obj); + PR_ASSERT (r); + + if (replica_is_flag_set (r, REPLICA_LOG_CHANGES) && + (cl5GetState () == CL5_STATE_OPEN)) + { + supplier_operation_extension *opext = NULL; + const char *repl_name; + char *repl_gen; + Slapi_Operation *op; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op); + PR_ASSERT (opext); + + /* get replica generation and replica name to pass to the write function */ + repl_name = replica_get_name (r); + repl_gen = opext->repl_gen; + PR_ASSERT (repl_name && repl_gen); + + /* for replicated operations, we log the original, non-urp data which is + saved in the operation extension */ + if (operation_is_flag_set(op,OP_FLAG_REPLICATED)) + { + PR_ASSERT (opext->operation_parameters); + op_params = opext->operation_parameters; + } + else /* since client operations don't go through urp, we log the operation data in pblock */ + { + Slapi_Entry *e = NULL; + const char *uniqueid = NULL; + + slapi_pblock_get (pb, SLAPI_OPERATION_PARAMETERS, &op_params); + PR_ASSERT (op_params); + + /* need to set uniqueid operation parameter */ + /* we try to use the appropriate entry (pre op or post op) + depending on the type of operation (add, delete, modify) + However, in some cases, the backend operation may fail in + a non fatal way (e.g. attempt to remove an attribute value + that does not exist) but we still need to log the change. + So, the POST_OP entry may not have been set in the FE modify + code. In that case, we use the PRE_OP entry. + */ + slapi_pblock_get (pb, SLAPI_ENTRY_POST_OP, &e); + if ((e == NULL) || + (op_params->operation_type == SLAPI_OPERATION_DELETE)) + { + slapi_pblock_get (pb, SLAPI_ENTRY_PRE_OP, &e); + } + + PR_ASSERT (e); + + uniqueid = slapi_entry_get_uniqueid (e); + PR_ASSERT (uniqueid); + + op_params->target_address.uniqueid = slapi_ch_strdup (uniqueid); + } + + /* we might have stripped all the mods - in that case we do not + log the operation */ + if (op_params->operation_type != SLAPI_OPERATION_MODIFY || + op_params->p.p_modify.modify_mods != NULL) + { + if (cl5_is_diskfull() && !cl5_diskspace_is_available()) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "write_changelog_and_ruv: Skipped due to DISKFULL\n"); + return 0; + } + rc = cl5WriteOperation(repl_name, repl_gen, op_params, + !operation_is_flag_set(op, OP_FLAG_REPLICATED)); + if (rc != CL5_SUCCESS) + { + char csn_str[CSN_STRSIZE]; + /* ONREPL - log error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "write_changelog_and_ruv: can't add a change for " + "%s (uniqid: %s, optype: %u) to changelog csn %s\n", + op_params->target_address.dn, + op_params->target_address.uniqueid, + op_params->operation_type, + csn_as_string(op_params->csn, PR_FALSE, csn_str)); + return_value = 1; + } + } + + if (!operation_is_flag_set(op,OP_FLAG_REPLICATED)) + { + slapi_ch_free((void**)&op_params->target_address.uniqueid); + } + } + + /* + This was moved because we need to write the changelog and update + the ruv in one atomic operation - I have seen cases where the inc + protocol thread interrupts this thread between the time the changelog + is written and the ruv is updated - this causes confusion in several + places, especially in _cl5SkipReplayEntry since it cannot find the csn it + just read from the changelog in either the supplier or consumer ruv + */ + if (0 == return_value) { + Slapi_Operation *op; + CSN *opcsn; + + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + opcsn = operation_get_csn(op); + update_ruv_component(r, opcsn, pb); + } + + object_release (repl_obj); + return return_value; +} + +/* + * Postop processing - write the changelog if the operation resulted in + * an LDAP_SUCCESS result code, update the RUV, and notify the replication + * agreements about the change. + * If the result code is not LDAP_SUCCESS, then cancel the operation CSN. + */ +static int +process_postop (Slapi_PBlock *pb) +{ + int rc = LDAP_SUCCESS; + Slapi_Operation *op; + Slapi_Backend *be; + int is_replicated_operation = 0; + CSN *opcsn = NULL; + char sessionid[REPL_SESSION_ID_SIZE]; + + /* we just let fixup operations through */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + if ((operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) || + (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY))) + { + return 0; + } + + /* ignore operations intended for chaining backends - they will be + replicated back to us or should be ignored anyway + replicated operations should be processed normally, as they should + be going to a local backend */ + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + slapi_pblock_get(pb, SLAPI_BACKEND, &be); + if (!is_replicated_operation && + slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)) + { + return 0; + } + + get_repl_session_id (pb, sessionid, &opcsn); + + slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc); + /* + * Don't abandon writing changelog since we'd do everything + * possible to keep the changelog in sync with the backend + * db which was committed before this function was called. + * + * if (rc == LDAP_SUCCESS && !slapi_op_abandoned(pb)) + */ + if (rc == LDAP_SUCCESS) + { + rc = write_changelog_and_ruv(pb); + if (rc == 0) + { + agmtlist_notify_all(pb); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s process postop: error writing changelog and ruv\n", sessionid); + } + } + else if (opcsn) + { + rc = cancel_opcsn (pb); + + /* Don't try to get session id since conn is always null */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s process postop: canceling operation csn\n", sessionid); + } + + /* the target unique id is set in the modify_preop above, so + we need to free it */ + /* The following bunch of frees code does not belong to this place + * but rather to operation_free who should be responsible for calling + * operation_parameters_free and it doesn't. I guess this is like this + * because several crashes happened in the past regarding some opparams + * that were getting individually freed before they should. Whatever + * the case, this is not the place and we should make sure that this + * code gets removed for 5.next and substituted by the strategy (operation_free). + * For 5.0, such change is too risky, so this will be done here */ + if (is_replicated_operation) + { + slapi_operation_parameters *op_params = NULL; + int optype = 0; + /* target uid and csn are set for all repl operations. Free them */ + char *target_uuid = NULL; + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype); + slapi_pblock_get(pb, SLAPI_TARGET_UNIQUEID, &target_uuid); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, NULL); + slapi_ch_free((void**)&target_uuid); + if (optype == SLAPI_OPERATION_ADD) { + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + slapi_ch_free((void **) &op_params->p.p_add.parentuniqueid); + } + if (optype == SLAPI_OPERATION_MODRDN) { + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + slapi_ch_free((void **) &op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid); + } + } + if (NULL == opcsn) + opcsn = operation_get_csn(op); + if (opcsn) + csn_free(&opcsn); + + return rc; +} + + +/* + * Cancel an operation CSN. This removes it from any CSN pending lists. + * This function is called when a previously-generated CSN will not + * be needed, e.g. if the update operation produced an error. + */ +static int +cancel_opcsn (Slapi_PBlock *pb) +{ + Object *repl_obj; + Slapi_Operation *op = NULL; + + PR_ASSERT (pb); + + repl_obj = replica_get_replica_for_op (pb); + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + PR_ASSERT (op); + if (repl_obj) + { + Replica *r; + Object *gen_obj; + CSNGen *gen; + CSN *opcsn; + + r = (Replica*)object_get_data (repl_obj); + PR_ASSERT (r); + opcsn = operation_get_csn(op); + + if (!operation_is_flag_set(op,OP_FLAG_REPLICATED)) + { + /* get csn generator for the replica */ + gen_obj = replica_get_csngen (r); + PR_ASSERT (gen_obj); + gen = (CSNGen*)object_get_data (gen_obj); + + if (NULL != opcsn) + { + csngen_abort_csn (gen, operation_get_csn(op)); + } + + object_release (gen_obj); + } + else if (!operation_is_flag_set(op,OP_FLAG_REPL_FIXUP)) + { + Object *ruv_obj; + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + ruv_cancel_csn_inprogress ((RUV*)object_get_data (ruv_obj), opcsn); + object_release (ruv_obj); + } + + object_release (repl_obj); + } + + return 0; +} + + + +/* + * Return non-zero if the target entry DN is the DN of the RUV tombstone + * entry. + * The entry has rdn of nsuniqueid = ffffffff-ffffffff-ffffffff-ffffffff + */ +static int +ruv_tombstone_op (Slapi_PBlock *pb) +{ + char *uniqueid; + int rc; + + slapi_pblock_get (pb, SLAPI_TARGET_UNIQUEID, &uniqueid); + + rc = uniqueid && strcasecmp (uniqueid, RUV_STORAGE_ENTRY_UNIQUEID) == 0; + + return rc; +} + +/* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ +static PRBool +process_operation (Slapi_PBlock *pb, const CSN *csn) +{ + Object *r_obj; + Replica *r; + Object *ruv_obj; + RUV *ruv; + int rc; + + r_obj = replica_get_replica_for_op(pb); + if (r_obj == NULL) + { + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s process_operation: " + "can't locate replica for the replicated operation\n", + sessionid ); + return PR_FALSE; + } + + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + + ruv = (RUV*)object_get_data (ruv_obj); + PR_ASSERT (ruv); + + rc = ruv_add_csn_inprogress (ruv, csn); + + object_release (ruv_obj); + object_release (r_obj); + + return (rc == RUV_SUCCESS); +} + +static PRBool +is_mmr_replica (Slapi_PBlock *pb) +{ + Object *r_obj; + Replica *r; + PRBool mmr; + + r_obj = replica_get_replica_for_op(pb); + if (r_obj == NULL) + { + return PR_FALSE; + } + + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + mmr = !replica_is_legacy_consumer (r); + + object_release (r_obj); + + return mmr; +} + +static const char *replica_get_purl_for_op (const Replica *r, Slapi_PBlock *pb, const CSN *opcsn) +{ + int is_replicated_op; + const char *purl; + + slapi_pblock_get(pb, SLAPI_IS_MMR_REPLICATED_OPERATION, &is_replicated_op); + + if (!is_replicated_op) + { + purl = multimaster_get_local_purl(); + } + else + { + /* Get the appropriate partial URL from the supplier RUV */ + Slapi_Connection *conn; + consumer_connection_extension *connext; + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + connext = (consumer_connection_extension *)repl_con_get_ext( + REPL_CON_EXT_CONN, conn); + if (NULL == connext || NULL == connext->supplier_ruv) + { + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s replica_get_purl_for_op: " + "cannot obtain consumer connection extension or supplier_ruv.\n", + sessionid); + } + else + { + purl = ruv_get_purl_for_replica(connext->supplier_ruv, + csn_get_replicaid(opcsn)); + } + } + + return purl; +} + +/* ONREPL at the moment, I decided not to trim copiedFrom and copyingFrom + attributes when sending operation to replicas. This is because, each + operation results in a state information stored in the database and + if we don't replay all operations we will endup with state inconsistency. + + Keeping the function just in case + */ +static void strip_legacy_info (slapi_operation_parameters *op_params) +{ + switch (op_params->operation_type) + { + case SLAPI_OPERATION_ADD: + slapi_entry_delete_values_sv(op_params->p.p_add.target_entry, + type_copiedFrom, NULL); + slapi_entry_delete_values_sv(op_params->p.p_add.target_entry, + type_copyingFrom, NULL); + break; + case SLAPI_OPERATION_MODIFY: + { + Slapi_Mods smods; + LDAPMod *mod; + + slapi_mods_init_byref(&smods, op_params->p.p_modify.modify_mods); + mod = slapi_mods_get_first_mod(&smods); + while (mod) + { + /* modify just to update copiedFrom or copyingFrom attribute + does not contain modifiersname or modifytime - so we don't + have to strip them */ + if (strcasecmp (mod->mod_type, type_copiedFrom) == 0 || + strcasecmp (mod->mod_type, type_copyingFrom) == 0) + slapi_mods_remove(&smods); + mod = slapi_mods_get_next_mod(&smods); + } + + op_params->p.p_modify.modify_mods = slapi_mods_get_ldapmods_passout (&smods); + slapi_mods_done (&smods); + + break; + } + + default: break; + } +} + +/* this function is called when state of a backend changes */ +void +multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state) +{ + Object *r_obj; + Replica *r; + + /* check if we have replica associated with the backend */ + r_obj = replica_get_for_backend (be_name); + if (r_obj == NULL) + return; + + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + if (new_be_state == SLAPI_BE_STATE_ON) + { + /* backend came back online - restart replication */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: " + "replica %s is coming online; enabling replication\n", + slapi_sdn_get_ndn (replica_get_root (r))); + replica_enable_replication (r); + } + else if (new_be_state == SLAPI_BE_STATE_OFFLINE) + { + /* backend is about to be taken down - disable replication */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: " + "replica %s is going offline; disabling replication\n", + slapi_sdn_get_ndn (replica_get_root (r))); + replica_disable_replication (r, r_obj); + } + else if (new_be_state == SLAPI_BE_STATE_DELETE) + { + /* backend is about to be removed - disable replication */ + if (old_be_state == SLAPI_BE_STATE_ON) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: " + "replica %s is about to be deleted; disabling replication\n", + slapi_sdn_get_ndn (replica_get_root (r))); + replica_disable_replication (r, r_obj); + } + } + + object_release (r_obj); +} + +static void +close_changelog_for_replica (Object *r_obj) +{ + if (cl5GetState () == CL5_STATE_OPEN) + cl5CloseDB (r_obj); +} diff --git a/ldap/servers/plugins/replication/repl5_prot_private.h b/ldap/servers/plugins/replication/repl5_prot_private.h new file mode 100644 index 00000000..fbaf96e4 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_prot_private.h @@ -0,0 +1,66 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _REPL5_PROT_PRIVATE_H_ +#define _REPL5_PROT_PRIVATE_H_ + +#define ACQUIRE_SUCCESS 101 +#define ACQUIRE_REPLICA_BUSY 102 +#define ACQUIRE_FATAL_ERROR 103 +#define ACQUIRE_CONSUMER_WAS_UPTODATE 104 +#define ACQUIRE_TRANSIENT_ERROR 105 + +typedef struct private_repl_protocol +{ + void (*delete)(struct private_repl_protocol **); + void (*run)(struct private_repl_protocol *); + int (*stop)(struct private_repl_protocol *); + int (*status)(struct private_repl_protocol *); + void (*notify_update)(struct private_repl_protocol *); + void (*notify_agmt_changed)(struct private_repl_protocol *); + void (*notify_window_opened)(struct private_repl_protocol *); + void (*notify_window_closed)(struct private_repl_protocol *); + void (*update_now)(struct private_repl_protocol *); + PRLock *lock; + PRCondVar *cvar; + int stopped; + int terminate; + PRUint32 eventbits; + Repl_Connection *conn; + int last_acquire_response_code; + Repl_Agmt *agmt; + Object *replica_object; + void *private; + PRBool replica_acquired; +} Private_Repl_Protocol; + +extern Private_Repl_Protocol *Repl_5_Inc_Protocol_new(); +extern Private_Repl_Protocol *Repl_5_Tot_Protocol_new(); + +#define PROTOCOL_TERMINATION_NORMAL 301 +#define PROTOCOL_TERMINATION_ABNORMAL 302 +#define PROTOCOL_TERMINATION_NEEDS_TOTAL_UPDATE 303 + +#define RESUME_DO_TOTAL_UPDATE 401 +#define RESUME_DO_INCREMENTAL_UPDATE 402 +#define RESUME_TERMINATE 403 +#define RESUME_SUSPEND 404 + +/* Backoff timer settings for reconnect */ +#define PROTOCOL_BACKOFF_MINIMUM 3 /* 3 seconds */ +#define PROTOCOL_BACKOFF_MAXIMUM (60 * 5) /* 5 minutes */ +/* Backoff timer settings for replica busy reconnect */ +#define PROTOCOL_BUSY_BACKOFF_MINIMUM PROTOCOL_BACKOFF_MINIMUM +#define PROTOCOL_BUSY_BACKOFF_MAXIMUM PROTOCOL_BUSY_BACKOFF_MINIMUM + +/* protocol related functions */ +void release_replica(Private_Repl_Protocol *prp); +int acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv); +BerElement *entry2bere(const Slapi_Entry *e); +CSN *get_current_csn(Slapi_DN *replarea_sdn); +char* protocol_response2string (int response); + +#endif /* _REPL5_PROT_PRIVATE_H_ */ diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c new file mode 100644 index 00000000..725bf3f2 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_protocol.c @@ -0,0 +1,502 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_protocol.c */ +/* + + The replication protocol object manages the replication protocol for + a given replica. It determines which protocol(s) are appropriate to + use when updating a given replica. It also knows how to arbitrate + incremental and total update protocols for a given replica. + +*/ + +#include "repl5.h" +#include "repl5_prot_private.h" + +#define PROTOCOL_5_INCREMENTAL 1 +#define PROTOCOL_5_TOTAL 2 +#define PROTOCOL_4_INCREMENTAL 3 +#define PROTOCOL_4_TOTAL 4 + +typedef struct repl_protocol +{ + Private_Repl_Protocol *prp_incremental; /* inc protocol to use */ + Private_Repl_Protocol *prp_total; /* total protocol to use */ + Private_Repl_Protocol *prp_active_protocol; /* Pointer to active protocol */ + Repl_Agmt *agmt; /* The replication agreement we're servicing */ + Repl_Connection *conn; /* Connection to remote server */ + Object *replica_object; /* Local replica. If non-NULL, replica object is acquired */ + int state; + int next_state; + PRLock *lock; +} repl_protocol; + + +/* States */ +#define STATE_FINISHED 503 +#define STATE_BAD_STATE_SHOULD_NEVER_HAPPEN 599 + +/* Forward declarations */ +static Private_Repl_Protocol *private_protocol_factory(Repl_Protocol *rp, int type); + + + + +/* + * Create a new protocol instance. + */ +Repl_Protocol * +prot_new(Repl_Agmt *agmt, int protocol_state) +{ + Slapi_DN *replarea_sdn = NULL; + Repl_Protocol *rp = (Repl_Protocol *)slapi_ch_malloc(sizeof(Repl_Protocol)); + + rp->prp_incremental = rp->prp_total = rp->prp_active_protocol = NULL; + if (protocol_state == STATE_PERFORMING_TOTAL_UPDATE) + { + rp->state = STATE_PERFORMING_TOTAL_UPDATE; + } + else + { + rp->state = STATE_PERFORMING_INCREMENTAL_UPDATE; + } + rp->next_state = STATE_PERFORMING_INCREMENTAL_UPDATE; + if ((rp->lock = PR_NewLock()) == NULL) + { + goto loser; + } + rp->agmt = agmt; + if ((rp->conn = conn_new(agmt)) == NULL) + { + goto loser; + } + /* Acquire the local replica object */ + replarea_sdn = agmt_get_replarea(agmt); + rp->replica_object = replica_get_replica_from_dn(replarea_sdn); + if (NULL == rp->replica_object) + { + /* Whoa, no local replica!?!? */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to locate replica object for local replica %s\n", + agmt_get_long_name(agmt), + slapi_sdn_get_dn(replarea_sdn)); + goto loser; + } + rp->prp_incremental = private_protocol_factory(rp, PROTOCOL_5_INCREMENTAL); + rp->prp_total = private_protocol_factory(rp, PROTOCOL_5_TOTAL); + /* XXXggood register callback handlers for entries updated, and + schedule window enter/leave. */ + slapi_sdn_free(&replarea_sdn); + + return rp; +loser: + prot_delete(&rp); + return NULL; +} + + + + + +Object * +prot_get_replica_object(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + return rp->replica_object; +} + + + + + +Repl_Agmt * +prot_get_agreement(Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return NULL; + return rp->agmt; +} + + + + +void +prot_free(Repl_Protocol **rpp) +{ + Repl_Protocol *rp = NULL; + + if (rpp == NULL || *rpp == NULL) return; + + rp = *rpp; + + PR_Lock(rp->lock); + if (NULL != rp->prp_incremental) + { + rp->prp_incremental->delete(&rp->prp_incremental); + } + if (NULL != rp->prp_total) + { + rp->prp_total->delete(&rp->prp_total); + } + if (NULL != rp->replica_object) + { + object_release(rp->replica_object); + } + if (NULL != rp->conn) + { + conn_delete(rp->conn); + } + rp->prp_active_protocol = NULL; + PR_Unlock(rp->lock); + slapi_ch_free((void **)rpp); +} + +/* + * Destroy a protocol instance XXXggood not complete + */ +void +prot_delete(Repl_Protocol **rpp) +{ + Repl_Protocol *rp; + + PR_ASSERT(NULL != rpp); + rp = *rpp; + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL != rp) + { + prot_stop(rp); + prot_free(rpp); + } +} + + + + + +/* + * Get the connection object. + */ +Repl_Connection * +prot_get_connection(Repl_Protocol *rp) +{ + Repl_Connection *return_value; + + PR_ASSERT(NULL != rp); + PR_Lock(rp->lock); + return_value = rp->conn; + PR_Unlock(rp->lock); + return return_value; +} + + + + +/* + * This function causes the total protocol to start. + * This is accomplished by registering a state transition + * to a new state, and then signaling the incremental + * protocol to stop. + */ +void +prot_initialize_replica(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + + PR_Lock(rp->lock); + /* check that total protocol is not running */ + rp->next_state = STATE_PERFORMING_TOTAL_UPDATE; + /* Stop the incremental protocol, if running */ + rp->prp_incremental->stop(rp->prp_incremental); + if (rp->prp_total) agmt_set_last_init_status(rp->prp_total->agmt, 0, 0, NULL); + PR_Unlock(rp->lock); +} + + + + + +/* + * Main thread for protocol manager. + +This is a simple state machine. State transition table: + +Initial state: incremental update + +STATE EVENT NEXT STATE +----- ----- ---------- +incremental update shutdown finished +incremental update total update requested total update +total update shutdown finished +total update update complete incremental update +finished (any) finished + +*/ + +static void +prot_thread_main(void *arg) +{ + Repl_Protocol *rp = (Repl_Protocol *)arg; + int done; + + PR_ASSERT(NULL != rp); + + if (rp->agmt) { + set_thread_private_agmtname (agmt_get_long_name(rp->agmt)); + } + + done = 0; + + while (!done) + { + switch (rp->state) + { + case STATE_PERFORMING_INCREMENTAL_UPDATE: + /* Run the incremental update protocol */ + PR_Lock(rp->lock); + dev_debug("prot_thread_main(STATE_PERFORMING_INCREMENTAL_UPDATE): begin"); + rp->prp_active_protocol = rp->prp_incremental; + PR_Unlock(rp->lock); + rp->prp_incremental->run(rp->prp_incremental); + dev_debug("prot_thread_main(STATE_PERFORMING_INCREMENTAL_UPDATE): end"); + break; + case STATE_PERFORMING_TOTAL_UPDATE: + PR_Lock(rp->lock); + + /* stop incremental protocol if running */ + rp->prp_active_protocol = rp->prp_total; + /* After total protocol finished, return to incremental */ + rp->next_state = STATE_PERFORMING_INCREMENTAL_UPDATE; + PR_Unlock(rp->lock); + /* Run the total update protocol */ + dev_debug("prot_thread_main(STATE_PERFORMING_TOTAL_UPDATE): begin"); + rp->prp_total->run(rp->prp_total); + dev_debug("prot_thread_main(STATE_PERFORMING_TOTAL_UPDATE): end"); + /* update the agreement entry to notify clients that + replica initialization is completed. */ + agmt_replica_init_done (rp->agmt); + + break; + case STATE_FINISHED: + dev_debug("prot_thread_main(STATE_FINISHED): exiting prot_thread_main"); + done = 1; + break; + } + rp->state = rp->next_state; + } +} + + +/* + * Start a thread to handle the replication protocol. + */ +void +prot_start(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + if (NULL != rp) + { + if (PR_CreateThread(PR_USER_THREAD, prot_thread_main, (void *)rp, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL) + { + PRErrorCode prerr = PR_GetError(); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to create protocol thread; NSPR error - %d, %s\n", + agmt_get_long_name(rp->agmt), + prerr, slapd_pr_strerror(prerr)); + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to start " + "protocol object - NULL protocol object passed to prot_start.\n"); + } +} + + + + + +/* + * Stop a protocol instance. + */ +void +prot_stop(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + if (NULL != rp) + { + PR_Lock(rp->lock); + rp->next_state = STATE_FINISHED; + if (NULL != rp->prp_incremental) + { + if (rp->prp_incremental->stop(rp->prp_incremental) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Warning: incremental protocol for replica \"%s\" " + "did not shut down properly.\n", + agmt_get_long_name(rp->agmt)); + } + } + if (NULL != rp->prp_total) + { + if (rp->prp_total->stop(rp->prp_total) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Warning: total protocol for replica \"%s\" " + "did not shut down properly.\n", + agmt_get_long_name(rp->agmt)); + } + } + PR_Unlock(rp->lock); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: prot_stop() " + " called on NULL protocol instance.\n"); + } +} + + + + + +/* + * Call the notify_update method of the incremental or total update + * protocol, is either is active. + */ +void +prot_notify_update(Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return; + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_update(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +/* + * Call the notify_agmt_changed method of the incremental or total update + * protocol, is either is active. + */ +void +prot_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Replication agreement for %s could not be updated. " + "For replication to take place, please enable the suffix " + "and restart the server\n", agmt_name); + return; + } + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_agmt_changed(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +void +prot_notify_window_opened (Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return; + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_window_opened(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +void +prot_notify_window_closed (Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return; + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_window_closed(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +int +prot_status(Repl_Protocol *rp) +{ + int return_status = PROTOCOL_STATUS_UNKNOWN; + + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL != rp) + { + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + return_status = rp->prp_active_protocol->status(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); + } + return return_status; +} + + +/* + * Start an incremental protocol session, even if we're not + * currently in a schedule window. + * If the total protocol is active, do nothing. + * Otherwise, notify the incremental protocol that it should + * run once. + */ +void +prot_replicate_now(Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + + if (NULL != rp) + { + PR_Lock(rp->lock); + if (rp->prp_incremental == rp->prp_active_protocol) + { + rp->prp_active_protocol->update_now(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); + } +} + +/* + * A little factory function to create a protocol + * instance of the correct type. + */ +static Private_Repl_Protocol * +private_protocol_factory(Repl_Protocol *rp, int type) +{ + Private_Repl_Protocol *prp; + switch (type) + { + case PROTOCOL_5_INCREMENTAL: + prp = Repl_5_Inc_Protocol_new(rp); + break; + case PROTOCOL_5_TOTAL: + prp = Repl_5_Tot_Protocol_new(rp); + break; + } + return prp; +} diff --git a/ldap/servers/plugins/replication/repl5_protocol_util.c b/ldap/servers/plugins/replication/repl5_protocol_util.c new file mode 100644 index 00000000..16f65485 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_protocol_util.c @@ -0,0 +1,468 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_protocol_util.c */ +/* + +Code common to both incremental and total protocols. + +*/ + +#include "repl5.h" +#include "repl5_prot_private.h" + + +/* + * Obtain a current CSN (e.g. one that would have been + * generated for an operation occurring at this time) + * for a given replica. + */ +CSN * +get_current_csn(Slapi_DN *replarea_sdn) +{ + Object *replica_obj; + Replica *replica; + Object *gen_obj; + CSNGen *gen; + CSN *current_csn = NULL; + + if (NULL != replarea_sdn) + { + replica_obj = replica_get_replica_from_dn(replarea_sdn); + if (NULL != replica_obj) + { + replica = object_get_data(replica_obj); + if (NULL != replica) + { + gen_obj = replica_get_csngen(replica); + if (NULL != gen_obj) + { + gen = (CSNGen *)object_get_data(gen_obj); + if (NULL != gen) + { + if (csngen_new_csn(gen, ¤t_csn, + PR_FALSE /* notify */) != CSN_SUCCESS) + { + current_csn = NULL; + + } + object_release(gen_obj); + } + } + } + } + } + return current_csn; +} + + +/* + * Acquire exclusive access to a replica. Send a start replication extended + * operation to the replica. The response will contain a success code, and + * optionally the replica's update vector if acquisition is successful. + * This function returns one of the following: + * ACQUIRE_SUCCESS - the replica was acquired, and we have exclusive update access + * ACQUIRE_REPLICA_BUSY - another master was updating the replica + * ACQUIRE_FATAL_ERROR - something bad happened, and it's not likely to improve + * if we wait. + * ACQUIRE_TRANSIENT_ERROR - something bad happened, but it's probably worth + * another try after waiting a while. + * If ACQUIRE_SUCCESS is returned, then ruv will point to the replica's update + * vector. It's possible that the replica does something goofy and doesn't + * return us an update vector, so be prepared for ruv to be NULL (but this is + * an error). + */ +int +acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv) +{ + int return_value; + ConnResult crc; + Repl_Connection *conn; + + PR_ASSERT(prp && prot_oid); + + if (prp->replica_acquired) /* we already acquire replica */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Remote replica already acquired\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + return ACQUIRE_SUCCESS; + } + + if (NULL != ruv) + { + ruv_destroy ( ruv ); + } + + if (strcmp(prot_oid, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID) == 0) + { + Replica *replica; + Object *supl_ruv_obj, *cons_ruv_obj; + PRBool is_newer = PR_FALSE; + + object_acquire(prp->replica_object); + replica = object_get_data(prp->replica_object); + supl_ruv_obj = replica_get_ruv ( replica ); + cons_ruv_obj = agmt_get_consumer_ruv ( prp->agmt ); + is_newer = ruv_is_newer ( supl_ruv_obj, cons_ruv_obj ); + if ( supl_ruv_obj ) object_release ( supl_ruv_obj ); + if ( cons_ruv_obj ) object_release ( cons_ruv_obj ); + object_release (prp->replica_object); + replica = NULL; + + if (is_newer == PR_FALSE) { + prp->last_acquire_response_code = NSDS50_REPL_UPTODATE; + return ACQUIRE_CONSUMER_WAS_UPTODATE; + } + } + + prp->last_acquire_response_code = NSDS50_REPL_REPLICA_NO_RESPONSE; + + /* Get the connection */ + conn = prp->conn; + + crc = conn_connect(conn); + if (CONN_OPERATION_FAILED == crc) + { + return_value = ACQUIRE_TRANSIENT_ERROR; + } + else if (CONN_SSL_NOT_ENABLED == crc) + { + return_value = ACQUIRE_FATAL_ERROR; + } + else + { + /* we don't want the timer to go off in the middle of an operation */ + conn_cancel_linger(conn); + /* Does the remote replica support the 5.0 protocol? */ + crc = conn_replica_supports_ds5_repl(conn); + if (CONN_DOES_NOT_SUPPORT_DS5_REPL == crc) + { + return_value = ACQUIRE_FATAL_ERROR; + } + else if (CONN_NOT_CONNECTED == crc || CONN_OPERATION_FAILED == crc) + { + /* We don't know anything about the remote replica. Try again later. */ + return_value = ACQUIRE_TRANSIENT_ERROR; + } + else + { + /* Good to go. Start the protocol. */ + CSN *current_csn = NULL; + struct berval *retdata = NULL; + char *retoid = NULL; + Slapi_DN *replarea_sdn; + + /* Obtain a current CSN */ + replarea_sdn = agmt_get_replarea(prp->agmt); + current_csn = get_current_csn(replarea_sdn); + if (NULL != current_csn) + { + struct berval *payload = NSDS50StartReplicationRequest_new( + prot_oid, slapi_sdn_get_ndn(replarea_sdn), + NULL /* XXXggood need to provide referral(s) */, current_csn); + /* JCMREPL - Need to extract the referrals from the RUV */ + csn_free(¤t_csn); + current_csn = NULL; + crc = conn_send_extended_operation(conn, + REPL_START_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid, + &retdata, NULL /* update control */, NULL /* returned controls */); + ber_bvfree(payload); + payload = NULL; + /* Look at the response we got. */ + if (CONN_OPERATION_SUCCESS == crc) + { + /* + * Extop was processed. Look at extop response to see if we're + * permitted to go ahead. + */ + struct berval **ruv_bervals = NULL; + int extop_result; + int extop_rc = decode_repl_ext_response(retdata, &extop_result, + &ruv_bervals); + if (0 == extop_rc) + { + prp->last_acquire_response_code = extop_result; + switch (extop_result) + { + /* XXXggood handle other error codes here */ + case NSDS50_REPL_INTERNAL_ERROR: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: " + "an internal error occurred on the remote replica. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_PERMISSION_DENIED: + /* Not allowed to send updates */ + { + char *repl_binddn = agmt_get_binddn(prp->agmt); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: permission denied. " + "The bind dn \"%s\" does not have permission to " + "supply replication updates to the replica. " + "Will retry later.\n", + agmt_get_long_name(prp->agmt), repl_binddn); + slapi_ch_free((void **)&repl_binddn); + return_value = ACQUIRE_TRANSIENT_ERROR; + break; + } + case NSDS50_REPL_NO_SUCH_REPLICA: + /* There is no such replica on the consumer */ + { + Slapi_DN *repl_root = agmt_get_replarea(prp->agmt); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: there is no " + "replicated area \"%s\" on the consumer server. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt), + slapi_sdn_get_dn(repl_root)); + slapi_sdn_free(&repl_root); + return_value = ACQUIRE_FATAL_ERROR; + break; + } + case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW: + /* Large clock skew between the consumer and the supplier */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: " + "Excessive clock skew between the supplier and " + "the consumer. Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_DECODING_ERROR: + /* We sent something the replica couldn't understand. */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: " + "the consumer was unable to decode the " + "startReplicationRequest extended operation sent by the " + "supplier. Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_REPLICA_BUSY: + /* Someone else is updating the replica. Try later. */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Unable to acquire replica: " + "the replica is currently being updated" + "by another supplier. Will try later\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_REPLICA_BUSY; + break; + case NSDS50_REPL_LEGACY_CONSUMER: + /* remote replica is a legacy consumer */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: the replica " + "is supplied by a legacy supplier. " + "Replication is aborting.\n", agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_REPLICAID_ERROR: + /* remote replica detected a duplicate ReplicaID */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to aquire replica: the replica " + "has the same Replica ID as this one. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_REPLICA_READY: + /* We've acquired the replica. */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Replica was successfully acquired.\n", + agmt_get_long_name(prp->agmt)); + /* Parse the update vector */ + if (NULL != ruv_bervals && NULL != ruv) + { + if (ruv_init_from_bervals(ruv_bervals, ruv) != RUV_SUCCESS) + { + /* Couldn't parse the update vector */ + *ruv = NULL; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: acquired replica, " + "but could not parse update vector. " + "The replica must be reinitialized.\n", + agmt_get_long_name(prp->agmt)); + } + } + + /* Save consumer's RUV in the replication agreement. + It is used by the changelog trimming code */ + if (ruv && *ruv) + agmt_set_consumer_ruv (prp->agmt, *ruv); + + return_value = ACQUIRE_SUCCESS; + break; + default: + return_value = ACQUIRE_FATAL_ERROR; + } + } + else + { + /* Couldn't parse the response */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to parse the response to the " + "startReplication extended operation. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + prp->last_acquire_response_code = NSDS50_REPL_INTERNAL_ERROR; + return_value = ACQUIRE_FATAL_ERROR; + } + if (NULL != ruv_bervals) + ber_bvecfree(ruv_bervals); + } + else + { + int operation, error; + conn_get_error(conn, &operation, &error); + + /* Couldn't send the extended operation */ + return_value = ACQUIRE_TRANSIENT_ERROR; /* XXX right return value? */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to send a startReplication " + "extended operation to consumer (%s). Will retry later.\n", + agmt_get_long_name(prp->agmt), + error ? ldap_err2string(error) : "unknown error"); + } + } + else + { + /* Couldn't get a current CSN */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to obtain current CSN. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + } + slapi_sdn_free(&replarea_sdn); + if (NULL != retoid) + ldap_memfree(retoid); + if (NULL != retdata) + ber_bvfree(retdata); + } + } + + if (ACQUIRE_SUCCESS != return_value) + { + /* could not acquire the replica, so reinstate the linger timer, since this + means we won't call release_replica, which also reinstates the timer */ + conn_start_linger(conn); + } + else + { + /* replica successfully acquired */ + prp->replica_acquired = PR_TRUE; + } + + return return_value; +} + + +/* + * Release a replica by sending an "end replication" extended request. + */ +void +release_replica(Private_Repl_Protocol *prp) +{ + int rc; + struct berval *retdata = NULL; + char *retoid = NULL; + struct berval *payload = NULL; + Slapi_DN *replarea_sdn = NULL; + + PR_ASSERT(NULL != prp); + PR_ASSERT(NULL != prp->conn); + + if (!prp->replica_acquired) + return; + + replarea_sdn = agmt_get_replarea(prp->agmt); + payload = NSDS50EndReplicationRequest_new((char *)slapi_sdn_get_dn(replarea_sdn)); /* XXXggood had to cast away const */ + slapi_sdn_free(&replarea_sdn); + rc = conn_send_extended_operation(prp->conn, + REPL_END_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid, + &retdata, NULL /* update control */, NULL /* returned controls */); + if (0 != rc) + { + int operation, error; + conn_get_error(prp->conn, &operation, &error); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: unable to send endReplication extended operation (%s)\n", + agmt_get_long_name(prp->agmt), + error ? ldap_err2string(error) : "unknown error"); + } + else + { + struct berval **ruv_bervals = NULL; /* Shouldn't actually be returned */ + int extop_result; + int extop_rc = decode_repl_ext_response(retdata, &extop_result, + (struct berval ***)&ruv_bervals); + if (0 == extop_rc) + { + if (NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED == extop_result) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Successfully released consumer\n", agmt_get_long_name(prp->agmt)); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to release consumer: response code %d\n", + agmt_get_long_name(prp->agmt), extop_result); + /* disconnect from the consumer so that it does not stay locked */ + conn_disconnect (prp->conn); + } + } + else + { + /* Couldn't parse the response */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: Unable to parse the response " + " to the endReplication extended operation.\n", + agmt_get_long_name(prp->agmt)); + } + if (NULL != ruv_bervals) + ber_bvecfree(ruv_bervals); + /* XXXggood free ruv_bervals if we got them for some reason */ + } + if (NULL != payload) + ber_bvfree(payload); + if (NULL != retoid) + ldap_memfree(retoid); + if (NULL != retdata) + ber_bvfree(retdata); + + /* replica is released, start the linger timer on the connection, which + was stopped in acquire_replica */ + conn_start_linger(prp->conn); + + prp->replica_acquired = PR_FALSE; +} + +/* converts consumer's response to a string */ +char * +protocol_response2string (int response) +{ + switch (response) + { + case NSDS50_REPL_REPLICA_READY: return "replica acquired"; + case NSDS50_REPL_REPLICA_BUSY: return "replica busy"; + case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW: return "excessive clock skew"; + case NSDS50_REPL_PERMISSION_DENIED: return "permission denied"; + case NSDS50_REPL_DECODING_ERROR: return "decoding error"; + case NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL: return "unknown update protocol"; + case NSDS50_REPL_NO_SUCH_REPLICA: return "no such replica"; + case NSDS50_REPL_BELOW_PURGEPOINT: return "csn below purge point"; + case NSDS50_REPL_INTERNAL_ERROR: return "internal error"; + case NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED: return "replica released"; + case NSDS50_REPL_LEGACY_CONSUMER: return "replica is a legacy consumer"; + case NSDS50_REPL_REPLICAID_ERROR: return "duplicate replica ID detected"; + case NSDS50_REPL_UPTODATE: return "no change to send"; + default: return "unknown error"; + } +} diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c new file mode 100644 index 00000000..5bc3e8ee --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica.c @@ -0,0 +1,3387 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_replica.c */ + +#include "slapi-plugin.h" +#include "repl.h" /* ONREPL - this is bad */ +#include "repl5.h" +#include "repl_shared.h" +#include "csnpl.h" +#include "cl5_api.h" + +/* from proto-slap.h */ +int g_get_shutdown(); + +#define RUV_SAVE_INTERVAL (30 * 1000) /* 30 seconds */ +#define START_UPDATE_DELAY 2 /* 2 second */ +#define START_REAP_DELAY 3600 /* 1 hour */ + +#define REPLICA_RDN "cn=replica" +#define CHANGELOG_RDN "cn=legacy changelog" + +/* + * A replica is a locally-held copy of a portion of the DIT. + */ +struct replica { + Slapi_DN *repl_root; /* top of the replicated area */ + char *repl_name; /* unique replica name */ + PRBool new_name; /* new name was generated - need to be saved */ + ReplicaUpdateDNList updatedn_list; /* list of dns with which a supplier should bind + to update this replica */ + ReplicaType repl_type; /* is this replica read-only ? */ + PRBool legacy_consumer; /* if true, this replica is supplied by 4.0 consumer */ + char* legacy_purl; /* partial url of the legacy supplier */ + ReplicaId repl_rid; /* replicaID */ + Object *repl_ruv; /* replica update vector */ + PRBool repl_ruv_dirty; /* Dirty flag for ruv */ + CSNPL *min_csn_pl; /* Pending list for minimal CSN */ + void *csn_pl_reg_id; /* registration assignment for csn callbacks */ + unsigned long repl_state_flags; /* state flags */ + PRUint32 repl_flags; /* persistent, externally visible flags */ + PRLock *repl_lock; /* protects entire structure */ + Slapi_Eq_Context repl_eqcxt_rs; /* context to cancel event that saves ruv */ + Slapi_Eq_Context repl_eqcxt_tr; /* context to cancel event that reaps tombstones */ + Object *repl_csngen; /* CSN generator for this replica */ + PRBool repl_csn_assigned; /* Flag set when new csn is assigned. */ + PRUint32 repl_purge_delay; /* When purgeable, CSNs are held on to for this many extra seconds */ + PRBool tombstone_reap_stop; /* TRUE when the tombstone reaper should stop */ + PRBool tombstone_reap_active; /* TRUE when the tombstone reaper is running */ + long tombstone_reap_interval; /* Time in seconds between tombstone reaping */ + Slapi_ValueSet *repl_referral; /* A list of administrator provided referral URLs */ + PRBool state_update_inprogress; /* replica state is being updated */ + PRLock *agmt_lock; /* protects agreement creation, start and stop */ + char *locking_purl; /* supplier who has exclusive access */ +}; + + +typedef struct reap_callback_data +{ + int rc; + unsigned long num_entries; + unsigned long num_purged_entries; + CSN *purge_csn; + PRBool *tombstone_reap_stop; +} reap_callback_data; + + +/* Forward declarations of helper functions*/ +static Slapi_Entry* _replica_get_config_entry (const Slapi_DN *root); +static int _replica_check_validity (const Replica *r); +static int _replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext); +static int _replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext); +static int _replica_configure_ruv (Replica *r, PRBool isLocked); +static void _replica_update_state (time_t when, void *arg); +static char * _replica_get_config_dn (const Slapi_DN *root); +static char * _replica_type_as_string (const Replica *r); +static int replica_create_ruv_tombstone(Replica *r); +static void assign_csn_callback(const CSN *csn, void *data); +static void abort_csn_callback(const CSN *csn, void *data); +static void eq_cb_reap_tombstones(time_t when, void *arg); +static CSN *_replica_get_purge_csn_nolock (const Replica *r); +static void replica_get_referrals_nolock (const Replica *r, char ***referrals); +static void replica_clear_legacy_referrals (const Slapi_DN *repl_root_sdn, char **referrals, const char *state); +static void replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr); +static int replica_log_ruv_elements_nolock (const Replica *r); +static void replica_replace_ruv_tombstone(Replica *r); +static void start_agreements_for_replica (Replica *r, PRBool start); + +/* Allocates new replica and reads its state and state of its component from + * various parts of the DIT. + */ +Replica * +replica_new(const Slapi_DN *root) +{ + Replica *r = NULL; + Slapi_Entry *e = NULL; + char errorbuf[BUFSIZ]; + char ebuf[BUFSIZ]; + + PR_ASSERT (root); + + /* check if there is a replica associated with the tree */ + e = _replica_get_config_entry (root); + if (e) + { + errorbuf[0] = '\0'; + r = replica_new_from_entry(e, errorbuf, + PR_FALSE /* not a newly added entry */); + + if (NULL == r) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to " + "configure replica %s: %s\n", + escape_string(slapi_sdn_get_dn(root), ebuf), + errorbuf); + } + + slapi_entry_free (e); + } + + return r; +} + +/* constructs the replica object from the newly added entry */ +Replica * +replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation) +{ + int rc = 0; + Replica *r; + RUV *ruv; + char *repl_name = NULL; + + if (e == NULL) + { + if (NULL != errortext) + { + sprintf (errortext, "NULL entry"); + } + return NULL; + } + + r = (Replica *)slapi_ch_calloc(1, sizeof(Replica)); + + if ((r->repl_lock = PR_NewLock()) == NULL) + { + if (NULL != errortext) + { + sprintf (errortext, "failed to create replica lock"); + } + rc = -1; + goto done; + } + + if ((r->agmt_lock = PR_NewLock()) == NULL) + { + if (NULL != errortext) + { + sprintf (errortext, "failed to create replica lock"); + } + rc = -1; + goto done; + } + + /* read parameters from the replica config entry */ + rc = _replica_init_from_config (r, e, errortext); + if (rc != 0) + { + goto done; + } + + /* configure ruv */ + rc = _replica_configure_ruv (r, PR_FALSE); + if (rc != 0) + { + goto done; + } + + /* If smallest csn exists in RUV for our local replica, it's ok to begin iteration */ + ruv = (RUV*) object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + + if (is_add_operation) + { + /* + * This is called by an ldap add operation. + * Update the entry to contain information generated + * during replica initialization + */ + rc = _replica_update_entry (r, e, errortext); + } + else + { + /* + * Entry is already in dse.ldif - update it on the disk + * (done by the update state event scheduled below) + */ + } + if (rc != 0) + goto done; + + /* ONREPL - the state update can occur before the entry is added to the DIT. + In that case the updated would fail but nothing bad would happen. The next + scheduled update would save the state */ + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_rs = slapi_eq_repeat(_replica_update_state, repl_name, + current_time () + START_UPDATE_DELAY, RUV_SAVE_INTERVAL); + + if (r->tombstone_reap_interval > 0) + { + /* + * Reap Tombstone should be started some time after the plugin started. + * This will allow the server to fully start before consuming resources. + */ + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_tr = slapi_eq_repeat(eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval); + } + + if (r->legacy_consumer) + { + char ebuf[BUFSIZ]; + + legacy_consumer_init_referrals (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_new_from_entry: " + "replica for %s was configured as legacy consumer\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + +done: + if (rc != 0 && r) + { + replica_destroy ((void**)&r); + } + + return r; +} + + +void +replica_flush(Replica *r) +{ + PR_ASSERT(NULL != r); + if (NULL != r) + { + PR_Lock(r->repl_lock); + /* Make sure we dump the CSNGen state */ + r->repl_csn_assigned = PR_TRUE; + PR_Unlock(r->repl_lock); + /* This function take the Lock Inside */ + /* And also write the RUV */ + _replica_update_state((time_t)0, r->repl_name); + } +} + + +/* + * Deallocate a replica. arg should point to the address of a + * pointer that points to a replica structure. + */ +void +replica_destroy(void **arg) +{ + Replica *r; + void *repl_name; + + if (arg == NULL) + return; + + r = *((Replica **)arg); + + PR_ASSERT(r); + + slapi_log_error (SLAPI_LOG_REPL, NULL, "replica_destroy\n"); + + /* + * The function will not be called unless the refcnt of its + * wrapper object is 0. Hopefully this refcnt could sync up + * this destruction and the events such as tombstone reap + * and ruv updates. + */ + + if (r->repl_eqcxt_rs) + { + repl_name = slapi_eq_get_arg (r->repl_eqcxt_rs); + slapi_ch_free (&repl_name); + slapi_eq_cancel(r->repl_eqcxt_rs); + r->repl_eqcxt_rs = NULL; + } + + if (r->repl_eqcxt_tr) + { + repl_name = slapi_eq_get_arg (r->repl_eqcxt_tr); + slapi_ch_free (&repl_name); + slapi_eq_cancel(r->repl_eqcxt_tr); + r->repl_eqcxt_tr = NULL; + } + + if (r->repl_root) + { + slapi_sdn_free(&r->repl_root); + } + + slapi_ch_free_string(&r->locking_purl); + + if (r->updatedn_list) + { + replica_updatedn_list_free(r->updatedn_list); + r->updatedn_list = NULL; + } + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&r->repl_name); + slapi_ch_free ((void**)&r->legacy_purl); + + if (r->repl_lock) + { + PR_DestroyLock(r->repl_lock); + r->repl_lock = NULL; + } + + if (r->agmt_lock) + { + PR_DestroyLock(r->agmt_lock); + r->agmt_lock = NULL; + } + + if(NULL != r->repl_ruv) + { + object_release(r->repl_ruv); + } + + if(NULL != r->repl_csngen) + { + if (r->csn_pl_reg_id) + { + csngen_unregister_callbacks((CSNGen *)object_get_data (r->repl_csngen), r->csn_pl_reg_id); + } + object_release(r->repl_csngen); + } + + if (NULL != r->repl_referral) + { + slapi_valueset_free(r->repl_referral); + } + + if (NULL != r->min_csn_pl) + { + csnplFree(&r->min_csn_pl);; + } + + slapi_ch_free((void **)arg); +} + +/* + * Attempt to obtain exclusive access to replica (advisory only) + * + * Returns PR_TRUE if exclusive access was granted, + * PR_FALSE otherwise + * The parameter isInc tells whether or not the replica is being + * locked for an incremental update session - if the replica is + * successfully locked, this value is used - if the replica is already + * in use, this value will be set to TRUE or FALSE, depending on what + * type of update session has the replica in use currently + * locking_purl is the supplier who is attempting to acquire access + * current_purl is the supplier who already has access, if any + */ +PRBool +replica_get_exclusive_access(Replica *r, PRBool *isInc, int connid, int opid, + const char *locking_purl, + char **current_purl) +{ + char ebuf[BUFSIZ]; + PRBool rval = PR_TRUE; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + if (r->repl_state_flags & REPLICA_IN_USE) + { + if (isInc) + *isInc = (r->repl_state_flags & REPLICA_INCREMENTAL_IN_PROGRESS); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Replica in use locking_purl=%s\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), + r->locking_purl ? r->locking_purl : "unknown"); + rval = PR_FALSE; + if (current_purl) + { + *current_purl = slapi_ch_strdup(r->locking_purl); + } + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": Acquired replica\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + r->repl_state_flags |= REPLICA_IN_USE; + if (isInc && *isInc) + { + r->repl_state_flags |= REPLICA_INCREMENTAL_IN_PROGRESS; + } + else + { + /* if connid or opid != 0, it's a total update */ + /* Both set to 0 means we're disabling replication */ + if (connid || opid) + { + r->repl_state_flags |= REPLICA_TOTAL_IN_PROGRESS; + } + } + slapi_ch_free_string(&r->locking_purl); + r->locking_purl = slapi_ch_strdup(locking_purl); + } + PR_Unlock(r->repl_lock); + return rval; +} + +/* + * Relinquish exclusive access to the replica + */ +void +replica_relinquish_exclusive_access(Replica *r, int connid, int opid) +{ + char ebuf[BUFSIZ]; + PRBool isInc; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + isInc = (r->repl_state_flags & REPLICA_INCREMENTAL_IN_PROGRESS); + /* check to see if the replica is in use and log a warning if not */ + if (!(r->repl_state_flags & REPLICA_IN_USE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Replica not in use\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } else { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Released replica\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + slapi_ch_free_string(&r->locking_purl); + r->repl_state_flags &= ~(REPLICA_IN_USE); + if (isInc) + r->repl_state_flags &= ~(REPLICA_INCREMENTAL_IN_PROGRESS); + else + r->repl_state_flags &= ~(REPLICA_TOTAL_IN_PROGRESS); + } + PR_Unlock(r->repl_lock); +} + +/* + * Returns root of the replicated area + */ +PRBool +replica_get_tombstone_reap_active(const Replica *r) +{ + PR_ASSERT(r); + + return(r->tombstone_reap_active); +} + +/* + * Returns root of the replicated area + */ +const Slapi_DN * +replica_get_root(const Replica *r) /* ONREPL - should we return copy instead? */ +{ + PR_ASSERT(r); + + /* replica root never changes so we don't have to lock */ + return(r->repl_root); +} + +/* + * Returns normalized dn of the root of the replicated area + */ +const char * +replica_get_name(const Replica *r) /* ONREPL - should we return copy instead? */ +{ + PR_ASSERT(r); + + /* replica name never changes so we don't have to lock */ + return(r->repl_name); +} + +/* + * Returns replicaid of this replica + */ +ReplicaId +replica_get_rid (const Replica *r) +{ + ReplicaId rid; + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + rid = r->repl_rid; + PR_Unlock(r->repl_lock); + return rid; +} + +/* + * Sets replicaid of this replica - should only be used when also changing the type + */ +void +replica_set_rid (Replica *r, ReplicaId rid) +{ + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + r->repl_rid = rid; + PR_Unlock(r->repl_lock); +} + +/* Returns true if replica was initialized through ORC or import; + * otherwise, false. An uninitialized replica should return + * LDAP_UNWILLING_TO_PERFORM to all client requests + */ +PRBool +replica_is_initialized (const Replica *r) +{ + PR_ASSERT(r); + return (r->repl_ruv != NULL); +} + +/* + * Returns refcounted object that contains RUV. The caller should release the + * object once it is no longer used. To release, call object_release + */ +Object * +replica_get_ruv (const Replica *r) +{ + Object *ruv = NULL; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + PR_ASSERT (r->repl_ruv); + + object_acquire (r->repl_ruv); + + ruv = r->repl_ruv; + + PR_Unlock(r->repl_lock); + + return ruv; +} + +/* + * Sets RUV vector. This function should be called during replica + * (re)initialization. During normal operation, the RUV is read from + * the root of the replicated in the replica_new call + */ +void +replica_set_ruv (Replica *r, RUV *ruv) +{ + PR_ASSERT(r && ruv); + + PR_Lock(r->repl_lock); + + if(NULL != r->repl_ruv) + { + object_release(r->repl_ruv); + } + + /* if the local replica is not in the RUV and it is writable - add it + and reinitialize min_csn pending list */ + if (r->repl_type == REPLICA_TYPE_UPDATABLE) + { + CSN *csn = NULL; + if (r->min_csn_pl) + csnplFree (&r->min_csn_pl); + + if (ruv_contains_replica (ruv, r->repl_rid)) + { + ruv_get_smallest_csn_for_replica(ruv, r->repl_rid, &csn); + if (csn) + csn_free (&csn); + else + r->min_csn_pl = csnplNew (); + /* We need to make sure the local ruv element is the 1st. */ + ruv_move_local_supplier_to_first(ruv, r->repl_rid); + } + else + { + r->min_csn_pl = csnplNew (); + /* To be sure that the local is in first */ + ruv_add_index_replica(ruv, r->repl_rid, multimaster_get_local_purl(), 1); + } + } + + r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy); + r->repl_ruv_dirty = PR_TRUE; + + PR_Unlock(r->repl_lock); +} + +/* + * Update one particular CSN in an RUV. This is meant to be called + * whenever (a) the server has processed a client operation and + * needs to update its CSN, or (b) the server is completing an + * inbound replication session operation, and needs to update its + * local RUV. + */ +void +replica_update_ruv(Replica *r, const CSN *updated_csn, const char *replica_purl) +{ + char csn_str[CSN_STRSIZE]; + char ebuf[BUFSIZ]; + + PR_ASSERT(NULL != r); + PR_ASSERT(NULL != updated_csn); +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "replica_update_ruv: csn %s\n", + csn_as_string(updated_csn, PR_FALSE, csn_str)); /* XXXggood remove debugging */ +#endif + if (NULL == r) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: replica " + "is NULL\n"); + } + else if (NULL == updated_csn) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: csn " + "is NULL when updating replica %s\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + else + { + RUV *ruv; + PR_Lock(r->repl_lock); + + if (r->repl_ruv != NULL) + { + ruv = object_get_data(r->repl_ruv); + if (NULL != ruv) + { + ReplicaId rid = csn_get_replicaid(updated_csn); + if (rid == r->repl_rid) + { + if (NULL != r->min_csn_pl) + { + CSN *min_csn; + PRBool committed; + (void)csnplCommit(r->min_csn_pl, updated_csn); + min_csn = csnplGetMinCSN(r->min_csn_pl, &committed); + if (NULL != min_csn) + { + if (committed) + { + ruv_set_min_csn(ruv, min_csn, replica_purl); + csnplFree(&r->min_csn_pl); + } + csn_free(&min_csn); + } + } + } + /* Update max csn for local and remote replicas */ + if (ruv_update_ruv (ruv, updated_csn, replica_purl, rid == r->repl_rid) + != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, + repl_plugin_name, "replica_update_ruv: unable " + "to update RUV for replica %s, csn = %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), + csn_as_string(updated_csn, PR_FALSE, csn_str)); + } + + r->repl_ruv_dirty = PR_TRUE; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_update_ruv: unable to get RUV object for replica " + "%s\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: " + "unable to initialize RUV for replica %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + PR_Unlock(r->repl_lock); + } +} + +/* + * Returns refcounted object that contains csn generator. The caller should release the + * object once it is no longer used. To release, call object_release + */ +Object * +replica_get_csngen (const Replica *r) +{ + Object *csngen; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + object_acquire (r->repl_csngen); + csngen = r->repl_csngen; + + PR_Unlock(r->repl_lock); + + return csngen; +} + +/* + * Returns the replica type. + */ +ReplicaType +replica_get_type (const Replica *r) +{ + PR_ASSERT(r); + return r->repl_type; +} + +/* + * Sets the replica type. + */ +void +replica_set_type (Replica *r, ReplicaType type) +{ + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + r->repl_type = type; + PR_Unlock(r->repl_lock); +} + +/* + * Returns PR_TRUE if this replica is a consumer of 4.0 server + * and PR_FALSE otherwise + */ +PRBool +replica_is_legacy_consumer (const Replica *r) +{ + PR_ASSERT(r); + return r->legacy_consumer; +} + +/* + * Sets the replica type. + */ +void +replica_set_legacy_consumer (Replica *r, PRBool legacy_consumer) +{ + int legacy2mmr; + Slapi_DN *repl_root_sdn = NULL; + char **referrals = NULL; + char *replstate = NULL; + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + legacy2mmr = r->legacy_consumer && !legacy_consumer; + + /* making the server a regular 5.0 replica */ + if (legacy2mmr) + { + slapi_ch_free ((void**)&r->legacy_purl); + /* Remove copiedFrom/copyingFrom attributes from the root entry */ + /* set the right state in the mapping tree */ + if (r->repl_type == REPLICA_TYPE_READONLY) + { + replica_get_referrals_nolock (r, &referrals); + replstate = STATE_UPDATE_REFERRAL; + } + else /* updateable */ + { + replstate = STATE_BACKEND; + } + } + + r->legacy_consumer = legacy_consumer; + repl_root_sdn = slapi_sdn_dup(r->repl_root); + PR_Unlock(r->repl_lock); + + if (legacy2mmr) + { + replica_clear_legacy_referrals(repl_root_sdn, referrals, replstate); + /* Also change state of the mapping tree node and/or referrals */ + replica_remove_legacy_attr (repl_root_sdn, type_copiedFrom); + replica_remove_legacy_attr (repl_root_sdn, type_copyingFrom); + } + charray_free(referrals); + slapi_sdn_free(&repl_root_sdn); +} + +/* Gets partial url of the legacy supplier - applicable for legacy consumer only */ +char * +replica_get_legacy_purl (const Replica *r) +{ + char *purl; + + PR_Lock (r->repl_lock); + + PR_ASSERT (r->legacy_consumer); + + purl = slapi_ch_strdup (r->legacy_purl); + + PR_Unlock (r->repl_lock); + + return purl; +} + +void +replica_set_legacy_purl (Replica *r, const char *purl) +{ + PR_Lock (r->repl_lock); + + PR_ASSERT (r->legacy_consumer); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&r->legacy_purl); + + r->legacy_purl = slapi_ch_strdup (purl); + + PR_Unlock (r->repl_lock); +} + +/* + * Returns true if sdn is the same as updatedn and false otherwise + */ +PRBool +replica_is_updatedn (const Replica *r, const Slapi_DN *sdn) +{ + PRBool result; + + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + if (sdn == NULL) + { + result = (r->updatedn_list == NULL); + } + else if (r->updatedn_list == NULL) + { + result = PR_FALSE; + } + else + { + result = replica_updatedn_list_ismember(r->updatedn_list, sdn); + } + + PR_Unlock(r->repl_lock); + + return result; +} + +/* + * Sets updatedn list for this replica + */ +void +replica_set_updatedn (Replica *r, const Slapi_ValueSet *vs, int mod_op) +{ + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + if (!r->updatedn_list) + r->updatedn_list = replica_updatedn_list_new(NULL); + + if (mod_op & LDAP_MOD_DELETE || vs == NULL || + (0 == slapi_valueset_count(vs))) /* null value also causes list deletion */ + replica_updatedn_list_delete(r->updatedn_list, vs); + else if (mod_op & LDAP_MOD_REPLACE) + replica_updatedn_list_replace(r->updatedn_list, vs); + else if (mod_op & LDAP_MOD_ADD) + replica_updatedn_list_add(r->updatedn_list, vs); + + PR_Unlock(r->repl_lock); +} + +/* gets current replica generation for this replica */ +char *replica_get_generation (const Replica *r) +{ + int rc = 0; + char *gen = NULL; + + if (r) + { + PR_Lock(r->repl_lock); + + PR_ASSERT (r->repl_ruv); + + if (rc == 0) + gen = ruv_get_replica_generation ((RUV*)object_get_data (r->repl_ruv)); + + PR_Unlock(r->repl_lock); + } + + return gen; +} + +PRBool replica_is_flag_set (const Replica *r, PRUint32 flag) +{ + if (r) + return (r->repl_flags & flag); + else + return PR_FALSE; +} + +void replica_set_flag (Replica *r, PRUint32 flag, PRBool clear) +{ + if (r == NULL) + return; + + PR_Lock(r->repl_lock); + + if (clear) + { + r->repl_flags &= ~flag; + } + else + { + r->repl_flags |= flag; + } + + PR_Unlock(r->repl_lock); +} + +void replica_replace_flags (Replica *r, PRUint32 flags) +{ + if (r) + { + PR_Lock(r->repl_lock); + r->repl_flags = flags; + PR_Unlock(r->repl_lock); + } +} + +void +replica_get_referrals(const Replica *r, char ***referrals) +{ + PR_Lock(r->repl_lock); + replica_get_referrals_nolock (r, referrals); + PR_Unlock(r->repl_lock); +} + +void +replica_set_referrals(Replica *r,const Slapi_ValueSet *vs) +{ + int ii = 0; + Slapi_Value *vv = NULL; + if (r->repl_referral == NULL) + { + r->repl_referral = slapi_valueset_new(); + } + else + { + slapi_valueset_done(r->repl_referral); + } + slapi_valueset_set_valueset(r->repl_referral, vs); + /* make sure the DN is included in the referral LDAP URL */ + if (r->repl_referral) + { + Slapi_ValueSet *newvs = slapi_valueset_new(); + const char *repl_root = slapi_sdn_get_dn(r->repl_root); + int rootlen = strlen(repl_root); + ii = slapi_valueset_first_value(r->repl_referral, &vv); + while (vv) + { + const char *ref = slapi_value_get_string(vv); + struct ldap_url_desc *lud = NULL; + int myrc = ldap_url_parse(ref, &lud); + /* see if the dn is already in the referral URL */ + if (myrc == LDAP_URL_ERR_NODN || !lud || !lud->lud_dn) { + /* add the dn */ + Slapi_Value *newval = NULL; + int len = strlen(ref); + char *tmpref = NULL; + int need_slash = 0; + if (ref[len-1] != '/') { + len++; /* add another one for the slash */ + need_slash = 1; + } + len += rootlen + 2; + tmpref = slapi_ch_malloc(len); + sprintf(tmpref, "%s%s%s", ref, (need_slash ? "/" : ""), + repl_root); + newval = slapi_value_new_string(tmpref); + slapi_ch_free_string(&tmpref); /* sv_new_string makes a copy */ + slapi_valueset_add_value(newvs, newval); + slapi_value_free(&newval); /* s_vs_add_value makes a copy */ + } + if (lud) + ldap_free_urldesc(lud); + ii = slapi_valueset_next_value(r->repl_referral, ii, &vv); + } + if (slapi_valueset_count(newvs) > 0) { + slapi_valueset_done(r->repl_referral); + slapi_valueset_set_valueset(r->repl_referral, newvs); + } + slapi_valueset_free(newvs); /* s_vs_set_vs makes a copy */ + } +} + +int +replica_update_csngen_state (Replica *r, const RUV *ruv) +{ + int rc = 0; + CSNGen *gen; + CSN *csn = NULL; + + PR_ASSERT (r && ruv); + + rc = ruv_get_max_csn(ruv, &csn); + if (rc != RUV_SUCCESS) + { + return -1; + } + + if (csn == NULL) /* ruv contains no csn - we are done */ + { + return 0; + } + + PR_Lock(r->repl_lock); + + gen = (CSNGen *)object_get_data (r->repl_csngen); + PR_ASSERT (gen); + + rc = csngen_adjust_time (gen, csn); + if (rc != CSN_SUCCESS) + { + rc = -1; + goto done; + } + + rc = 0; + +done: + + PR_Unlock(r->repl_lock); + if (csn) + csn_free (&csn); + + return rc; +} + +/* + * dumps replica state for debugging purpose + */ +void +replica_dump(Replica *r) +{ + char *updatedn_list = NULL; + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "Replica state:\n"); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica root: %s\n", + slapi_sdn_get_ndn (r->repl_root)); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica type: %s\n", + _replica_type_as_string (r)); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica id: %d\n", r->repl_rid); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tflags: %d\n", r->repl_flags); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tstate flags: %d\n", r->repl_state_flags); + if (r->updatedn_list) + updatedn_list = replica_updatedn_list_to_string(r->updatedn_list, "\n\t\t"); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tupdate dn: %s\n", + updatedn_list? updatedn_list : "not configured"); + slapi_ch_free_string(&updatedn_list); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\truv: %s configured and is %sdirty\n", + r->repl_ruv ? "" : "not", r->repl_ruv_dirty ? "" : "not "); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tCSN generator: %s configured\n", + r->repl_csngen ? "" : "not"); + /* JCMREPL - Dump Referrals */ + + PR_Unlock(r->repl_lock); +} + + +/* + * Return the CSN of the purge point. Any CSNs smaller than the + * purge point can be safely removed from entries within this + * this replica. Returns an allocated CSN that must be freed by + * the caller, or NULL if purging is disabled. + */ + +CSN * +replica_get_purge_csn(const Replica *r) +{ + CSN *csn; + + PR_Lock(r->repl_lock); + + csn= _replica_get_purge_csn_nolock(r); + + PR_Unlock(r->repl_lock); + + return csn; +} + + +/* + * This function logs a dummy entry for the smallest csn in the RUV. + * This is necessary because, to get the next change, we need to position + * changelog on the previous change. So this function insures that we always have one. + */ + +/* ONREPL we will need to change this function to log all the + * ruv elements not just the smallest when changelog iteration + * algoritm changes to iterate replica by replica +*/ +int +replica_log_ruv_elements (const Replica *r) +{ + int rc = 0; + + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + rc = replica_log_ruv_elements_nolock (r); + + PR_Unlock(r->repl_lock); + + return rc; +} + +void +consumer5_set_mapping_tree_state_for_replica(const Replica *r, RUV *supplierRuv) +{ + const Slapi_DN *repl_root_sdn= replica_get_root(r); + char **ruv_referrals= NULL; + char **replica_referrals= NULL; + RUV *ruv; + int state_backend = -1; + const char *mtn_state = NULL; + + PR_Lock (r->repl_lock); + + if ( supplierRuv == NULL ) + { + ruv = (RUV*)object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + + ruv_referrals= ruv_get_referrals(ruv); /* ruv_referrals has to be free'd */ + } + else + { + ruv_referrals = ruv_get_referrals(supplierRuv); + } + + replica_get_referrals_nolock (r, &replica_referrals); /* replica_referrals has to be free'd */ + + /* JCMREPL - What if there's a Total update in progress? */ + if( (r->repl_type==REPLICA_TYPE_READONLY) || (r->legacy_consumer) ) + { + state_backend = 0; + } + else if (r->repl_type==REPLICA_TYPE_UPDATABLE) + { + state_backend = 1; + } + /* Unlock to avoid changing MTN state under repl lock */ + PR_Unlock (r->repl_lock); + + if(state_backend == 0 ) + { + /* Read-Only - The mapping tree should be refering all update operations. */ + mtn_state = STATE_UPDATE_REFERRAL; + } + else if (state_backend == 1) + { + /* Updatable - The mapping tree should be accepting all update operations. */ + mtn_state = STATE_BACKEND; + } + + /* JCMREPL - Check the return code. */ + repl_set_mtn_state_and_referrals(repl_root_sdn, mtn_state, NULL, + ruv_referrals, replica_referrals); + charray_free(ruv_referrals); + charray_free(replica_referrals); +} + +void +replica_set_enabled (Replica *r, PRBool enable) +{ + char *repl_name = NULL; + + PR_ASSERT (r); + + PR_Lock (r->repl_lock); + + if (enable) + { + if (r->repl_eqcxt_rs == NULL) /* event is not already registered */ + { + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_rs = slapi_eq_repeat(_replica_update_state, repl_name, + current_time() + START_UPDATE_DELAY, RUV_SAVE_INTERVAL); + } + } + else /* disable */ + { + if (r->repl_eqcxt_rs) /* event is still registerd */ + { + repl_name = slapi_eq_get_arg (r->repl_eqcxt_rs); + slapi_ch_free ((void**)&repl_name); + slapi_eq_cancel(r->repl_eqcxt_rs); + r->repl_eqcxt_rs = NULL; + } + } + + PR_Unlock (r->repl_lock); +} + +/* This function is generally called when replica's data store + is reloaded. It retrieves new RUV from the datastore. If new + RUV does not exist or if it is not as up to date as the purge RUV + of the corresponding changelog file, we need to remove */ + +/* the function minimizes the use of replica lock where ever possible. + Locking replica lock while calling changelog functions + causes a deadlock because changelog calls replica functions that + that lock the same lock */ + +int +replica_reload_ruv (Replica *r) +{ + int rc = 0; + Object *old_ruv_obj = NULL, *new_ruv_obj = NULL; + RUV *upper_bound_ruv = NULL; + RUV *new_ruv = NULL; + Object *r_obj; + + PR_ASSERT (r); + + PR_Lock (r->repl_lock); + + old_ruv_obj = r->repl_ruv; + + r->repl_ruv = NULL; + + rc = _replica_configure_ruv (r, PR_TRUE); + + PR_Unlock (r->repl_lock); + + if (rc != 0) + { + return rc; + } + + /* check if there is a changelog and whether this replica logs changes */ + if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES) + { + + /* Compare new ruv to the changelog's upper bound ruv. We could only keep + the existing changelog if its upper bound is the same as replica's RUV. + This is because if changelog has changes not in RUV, they will be + eventually sent to the consumer's which will cause a state mismatch + (because the supplier does not actually contain the changes in its data store. + If, on the other hand, the changelog is not as up to date as the supplier, + it is not really useful since out of sync consumer's can't be brought + up to date using this changelog and hence will need to be reinitialized */ + + /* replace ruv to make sure we work with the correct changelog file */ + PR_Lock (r->repl_lock); + + new_ruv_obj = r->repl_ruv; + r->repl_ruv = old_ruv_obj; + + PR_Unlock (r->repl_lock); + + rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv); + if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) + { + return -1; + } + + if (upper_bound_ruv) + { + new_ruv = object_get_data (new_ruv_obj); + PR_ASSERT (new_ruv); + + /* ONREPL - there are more efficient ways to establish RUV equality. + However, because this is not in the critical path and we at most + have 2 elements in the RUV, this will not effect performance */ + + if (!ruv_covers_ruv (new_ruv, upper_bound_ruv) || + !ruv_covers_ruv (upper_bound_ruv, new_ruv)) + { + char ebuf[BUFSIZ]; + + /* create a temporary replica object to conform to the interface */ + r_obj = object_new (r, NULL); + + /* We can't use existing changelog - remove existing file */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_reload_ruv: " + "Warning: new data for replica %s does not match the data in the changelog.\n" + " Recreating the changelog file. This could affect replication with replica's " + " consumers in which case the consumers should be reinitialized.\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + rc = cl5DeleteDBSync (r_obj); + + /* reinstate new ruv */ + PR_Lock (r->repl_lock); + + r->repl_ruv = new_ruv_obj; + + object_release (r_obj); + + if (rc == CL5_SUCCESS) + { + /* log changes to mark starting point for replication */ + rc = replica_log_ruv_elements_nolock (r); + } + + PR_Unlock (r->repl_lock); + } + else + { + /* we just need to reinstate new ruv */ + PR_Lock (r->repl_lock); + + r->repl_ruv = new_ruv_obj; + + PR_Unlock (r->repl_lock); + } + } + else /* upper bound vector is not there - we have no changes logged */ + { + /* reinstate new ruv */ + PR_Lock (r->repl_lock); + + r->repl_ruv = new_ruv_obj; + + /* just log elements of the current RUV. This is to have + a starting point for iteration through the changes */ + rc = replica_log_ruv_elements_nolock (r); + + PR_Unlock (r->repl_lock); + } + } + + if (rc == 0) + { + consumer5_set_mapping_tree_state_for_replica(r, NULL); + /* reset mapping tree referrals based on new local RUV */ + } + + if (old_ruv_obj) + object_release (old_ruv_obj); + + if (upper_bound_ruv) + ruv_destroy (&upper_bound_ruv); + + return rc; +} + +/* this function is called during server startup for each replica + to check whether the replica's data was reloaded offline and + whether replica's changelog needs to be reinitialized */ + +/* the function does not use replica lock but all functions it calls are + thread safe. Locking replica lock while calling changelog functions + causes a deadlock because changelog calls replica functions that + that lock the same lock */ +int replica_check_for_data_reload (Replica *r, void *arg) +{ + int rc = 0; + RUV *upper_bound_ruv = NULL; + RUV *r_ruv = NULL; + Object *r_obj, *ruv_obj; + int cl_cover_be, be_cover_cl; + + PR_ASSERT (r); + + /* check that we have a changelog and if this replica logs changes */ + if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES) + { + /* Compare new ruv to the purge ruv. If the new contains csns which + are smaller than those in purge ruv, we need to remove old and + create new changelog file for this replica. This is because we + will not have sufficient changes to incrementally update a consumer + to the current state of the supplier. */ + + rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv); + if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) + { + return -1; + } + + if (upper_bound_ruv) + { + ruv_obj = replica_get_ruv (r); + r_ruv = object_get_data (ruv_obj); + PR_ASSERT (r_ruv); + + /* Compare new ruv to the changelog's upper bound ruv. We could only keep + the existing changelog if its upper bound is the same as replica's RUV. + This is because if changelog has changes not in RUV, they will be + eventually sent to the consumer's which will cause a state mismatch + (because the supplier does not actually contain the changes in its data store. + If, on the other hand, the changelog is not as up to date as the supplier, + it is not really useful since out of sync consumer's can't be brought + up to date using this changelog and hence will need to be reinitialized */ + + /* + * Actually we can ignore the scenario that the changelog's upper + * bound ruv covers data store's ruv for two reasons: (1) a change + * is always written to the changelog after it is committed to the + * data store; (2) a change will be ignored if the server has seen + * it before - this happens frequently at the beginning of replication + * sessions. + */ + + be_cover_cl = ruv_covers_ruv (r_ruv, upper_bound_ruv); + cl_cover_be = ruv_covers_ruv (upper_bound_ruv, r_ruv); + if (!cl_cover_be) + { + /* the data was reloaded and we can no longer use existing changelog */ + char ebuf[BUFSIZ]; + + /* create a temporary replica object to conform to the interface */ + r_obj = object_new (r, NULL); + + /* We can't use existing changelog - remove existing file */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_check_for_data_reload: " + "Warning: data for replica %s was reloaded and it no longer matches the data " + "in the changelog (replica data %s changelog). Recreating the changelog file. This could affect replication " + "with replica's consumers in which case the consumers should be reinitialized.\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), + ((!be_cover_cl && !cl_cover_be) ? "<>" : (!be_cover_cl ? "<" : ">")) ); + + rc = cl5DeleteDBSync (r_obj); + + object_release (r_obj); + + if (rc == CL5_SUCCESS) + { + /* log changes to mark starting point for replication */ + rc = replica_log_ruv_elements (r); + } + } + + object_release (ruv_obj); + } + else /* we have no changes currently logged for this replica */ + { + /* log changes to mark starting point for replication */ + rc = replica_log_ruv_elements (r); + } + } + + if (rc == 0) + { + /* reset mapping tree referrals based on new local RUV */ + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + + if (upper_bound_ruv) + ruv_destroy (&upper_bound_ruv); + + return rc; +} + +/* Helper functions */ +/* reads replica configuration entry. The entry is the child of the + mapping tree node for the replica's backend */ + +static Slapi_Entry* +_replica_get_config_entry (const Slapi_DN *root) +{ + int rc = 0; + char *dn = NULL; + Slapi_Entry **entries; + Slapi_Entry *e = NULL; + Slapi_PBlock *pb = NULL; + + dn = _replica_get_config_dn (root); + pb = slapi_pblock_new (); + + slapi_search_internal_set_pb (pb, dn, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL, + NULL, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc == 0) + { + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + e = slapi_entry_dup (entries [0]); + } + + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + slapi_ch_free_string(&dn); + + return e; +} + +static int +_replica_check_validity (const Replica *r) +{ + PR_ASSERT (r); + + if (r->repl_root == NULL || r->repl_type == 0 || r->repl_rid == 0 || + r->repl_rid > MAX_REPLICA_ID || r->repl_csngen == NULL || r->repl_name == NULL) + { + return -1; + } + else + { + return 0; + } +} + +/* replica configuration entry has the following format: + dn: cn=replica,<mapping tree node dn> + objectclass: top + objectclass: nsds5Replica + objectclass: extensibleObject + nsds5ReplicaRoot: <root of the replica> + nsds5ReplicaId: <replica id> + nsds5ReplicaType: <type of the replica: primary, read-write or read-only> + nsState: <state of the csn generator> missing the first time replica is started + nsds5ReplicaBindDN: <supplier update dn> consumers only + nsds5ReplicaReferral: <referral URL to updatable replica> consumers only + nsds5ReplicaPurgeDelay: <time, in seconds, to keep purgeable CSNs, 0 == keep forever> + nsds5ReplicaTombstonePurgeInterval: <time, in seconds, between tombstone purge runs, 0 == don't reap> + nsds5ReplicaLegacyConsumer: <TRUE | FALSE> + + richm: changed slapi entry from const to editable - if the replica id is supplied for a read + only replica, we ignore it and replace the value with the READ_ONLY_REPLICA_ID + */ +static int +_replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext) +{ + int rc; + Slapi_Attr *attr; + char *val; + CSNGen *gen; + char buf [BUFSIZ]; + char *errormsg = errortext? errortext : buf; + Slapi_Attr *a = NULL; + char dnescape[BUFSIZ]; /* for escape_string */ + + PR_ASSERT (r && e); + + /* get replica root */ + val = slapi_entry_attr_get_charptr (e, attr_replicaRoot); + if (val == NULL) + { + sprintf (errormsg, "failed to retrieve %s attribute from (%s)\n", + attr_replicaRoot, + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_init_from_config: %s\n", + errormsg); + + return -1; + } + + r->repl_root = slapi_sdn_new_dn_passin (val); + + /* get replica type */ + val = slapi_entry_attr_get_charptr (e, attr_replicaType); + if (val) + { + r->repl_type = atoi(val); + slapi_ch_free ((void**)&val); + } + else + { + r->repl_type = REPLICA_TYPE_READONLY; + } + + /* get legacy consumer flag */ + val = slapi_entry_attr_get_charptr (e, type_replicaLegacyConsumer); + if (val) + { + if (strcasecmp (val, "on") == 0 || strcasecmp (val, "yes") == 0 || + strcasecmp (val, "true") == 0 || strcasecmp (val, "1") == 0) + { + r->legacy_consumer = PR_TRUE; + } + else + { + r->legacy_consumer = PR_FALSE; + } + + slapi_ch_free ((void**)&val); + } + else + { + r->legacy_consumer = PR_FALSE; + } + + /* get replica flags */ + r->repl_flags = slapi_entry_attr_get_ulong(e, attr_flags); + + /* get replicaid */ + /* the replica id is ignored for read only replicas and is set to the + special value READ_ONLY_REPLICA_ID */ + if (r->repl_type == REPLICA_TYPE_READONLY) + { + r->repl_rid = READ_ONLY_REPLICA_ID; + slapi_entry_attr_set_uint(e, attr_replicaId, (unsigned int)READ_ONLY_REPLICA_ID); + } + /* a replica id is required for updatable and primary replicas */ + else if (r->repl_type == REPLICA_TYPE_UPDATABLE || + r->repl_type == REPLICA_TYPE_PRIMARY) + { + if ((val = slapi_entry_attr_get_charptr (e, attr_replicaId))) + { + int temprid = atoi (val); + slapi_ch_free ((void**)&val); + if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID) + { + sprintf (errormsg, + "attribute %s must have a value greater than 0 " + "and less than %d: entry %s", + attr_replicaId, READ_ONLY_REPLICA_ID, + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), + dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + else + { + r->repl_rid = (ReplicaId)temprid; + } + } + else + { + sprintf (errormsg, "failed to retrieve required %s attribute from %s", + attr_replicaId, + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), + dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + } + + attr = NULL; + rc = slapi_entry_attr_find(e, attr_state, &attr); + gen = csngen_new (r->repl_rid, attr); + if (gen == NULL) + { + sprintf (errormsg, "failed to create csn generator for replica (%s)", + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), + dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + r->repl_csngen = object_new((void*)gen, (FNFree)csngen_free); + + /* Hook generator so we can maintain min/max CSN info */ + r->csn_pl_reg_id = csngen_register_callbacks(gen, assign_csn_callback, r, abort_csn_callback, r); + + /* get replication bind dn */ + r->updatedn_list = replica_updatedn_list_new(e); + + /* get replica name */ + val = slapi_entry_attr_get_charptr (e, attr_replicaName); + if (val) { + r->repl_name = val; + } + else + { + rc = slapi_uniqueIDGenerateString (&r->repl_name); + if (rc != UID_SUCCESS) + { + sprintf (errormsg, "failed to assign replica name for replica (%s); " + "uuid generator error - %d ", + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape), + rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + else + r->new_name = PR_TRUE; + } + + /* get the list of referrals */ + slapi_entry_attr_find( e, attr_replicaReferral, &attr ); + if(attr!=NULL) + { + slapi_attr_get_valueset(attr, &r->repl_referral); + } + + /* + * Set the purge offset (default 7 days). This is the extra + * time we allow purgeable CSNs to stick around, in case a + * replica regresses. Could also be useful when LCUP happens, + * since we don't know about LCUP replicas, and they can just + * turn up whenever they want to. + */ + if (slapi_entry_attr_find(e, type_replicaPurgeDelay, &a) == -1) + { + /* No purge delay provided, so use default */ + r->repl_purge_delay = 60 * 60 * 24 * 7; /* One week, in seconds */ + } + else + { + r->repl_purge_delay = slapi_entry_attr_get_uint(e, type_replicaPurgeDelay); + } + + if (slapi_entry_attr_find(e, type_replicaTombstonePurgeInterval, &a) == -1) + { + /* No reap interval provided, so use default */ + r->tombstone_reap_interval = 3600 * 24; /* One day */ + } + else + { + r->tombstone_reap_interval = slapi_entry_attr_get_int(e, type_replicaTombstonePurgeInterval); + } + + r->tombstone_reap_stop = r->tombstone_reap_active = PR_FALSE; + + return (_replica_check_validity (r)); +} + +/* This function updates the entry to contain information generated + during replica initialization. + Returns 0 if successful and -1 otherwise */ +static int +_replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext) +{ + int rc; + Slapi_Mod smod; + Slapi_Value *val; + + PR_ASSERT (r); + + /* add attribute that stores state of csn generator */ + rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod); + if (rc != CSN_SUCCESS) + { + sprintf (errortext, "failed to get csn generator's state; csn error - %d", rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_update_entry: %s\n", errortext); + return -1; + } + + val = slapi_value_new_berval(slapi_mod_get_first_value(&smod)); + + rc = slapi_entry_add_value (e, slapi_mod_get_type (&smod), val); + + slapi_value_free(&val); + slapi_mod_done (&smod); + + if (rc != 0) + { + sprintf (errortext, "failed to update replica entry"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_update_entry: %s\n", errortext); + return -1; + } + + /* add attribute that stores replica name */ + rc = slapi_entry_add_string (e, attr_replicaName, r->repl_name); + if (rc != 0) + { + sprintf (errortext, "failed to update replica entry"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_update_entry: %s\n", errortext); + return -1; + } + else + r->new_name = PR_FALSE; + + return 0; +} + +/* DN format: cn=replica,cn=\"<root>\",cn=mapping tree,cn=config */ +static char* +_replica_get_config_dn (const Slapi_DN *root) +{ + char *dn; + const char *mp_base = slapi_get_mapping_tree_config_root (); + int len; + + PR_ASSERT (root); + + len = strlen (REPLICA_RDN) + strlen (slapi_sdn_get_dn (root)) + + strlen (mp_base) + 8; /* 8 = , + cn= + \" + \" + , + \0 */ + + dn = (char*)slapi_ch_malloc (len); + sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, slapi_sdn_get_dn (root), mp_base); + + return dn; +} + +/* This function retrieves RUV from the root of the replicated tree. + * The attribute can be missing if + * (1) this replica is the first supplier and replica generation has not been assigned + * or + * (2) this is a consumer that has not been yet initialized + * In either case, replica_set_ruv should be used to further initialize the replica. + * Returns 0 on success, -1 on failure. If 0 is returned, the RUV is present in the replica. + */ +static int +_replica_configure_ruv (Replica *r, PRBool isLocked) +{ + Slapi_PBlock *pb = NULL; + char *attrs[2]; + int rc; + int return_value = -1; + Slapi_Entry **entries = NULL; + Slapi_Attr *attr; + RUV *ruv = NULL; + CSN *csn = NULL; + ReplicaId rid = 0; + char ebuf[BUFSIZ]; + + /* read ruv state from the ruv tombstone entry */ + pb = slapi_pblock_new(); + attrs[0] = (char*)type_ruvElement; + attrs[1] = NULL; + slapi_search_internal_set_pb( + pb, + slapi_sdn_get_dn(r->repl_root), + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, /* attrsonly */ + NULL, /* controls */ + RUV_STORAGE_ENTRY_UNIQUEID, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED); /* flags */ + slapi_search_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc == LDAP_SUCCESS) + { + /* get RUV attributes and construct the RUV */ + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || NULL == entries[0]) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replica ruv tombstone entry for " + "replica %s not found\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + + rc = slapi_entry_attr_find(entries[0], type_ruvElement, &attr); + if (rc != 0) /* ruv attribute is missing - this not allowed */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replica ruv tombstone entry for " + "replica %s does not contain %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), type_ruvElement); + goto done; + } + + /* Check in the tombstone we have retrieved if the local purl is + already present: + rid == 0: the local purl is not present + rid != 0: the local purl is present ==> nothing to do + */ + ruv_init_from_slapi_attr_and_check_purl (attr, &ruv, &rid); + if (ruv) + { + char *generation = NULL; + generation = ruv_get_replica_generation(ruv); + if (NULL != generation) + { + r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy); + + /* Is the local purl in the ruv? (the port or the host could have + changed) + */ + /* A consumer only doesn't have its purl in its ruv */ + if (r->repl_type == REPLICA_TYPE_UPDATABLE) + { + int need_update = 0; + if (rid == 0) + { + /* We can not have more than 1 ruv with the same rid + so we replace it */ + const char *purl = NULL; + + purl = multimaster_get_local_purl(); + ruv_delete_replica(ruv, r->repl_rid); + ruv_add_index_replica(ruv, r->repl_rid, purl, 1); + need_update = 1; /* ruv changed, so write tombstone */ + } + else /* bug 540844: make sure the local supplier rid is first in the ruv */ + { + /* make sure local supplier is first in list */ + ReplicaId first_rid = 0; + char *first_purl = NULL; + ruv_get_first_id_and_purl(ruv, &first_rid, &first_purl); + /* if the local supplier is not first in the list . . . */ + if (rid != first_rid) + { + /* . . . move the local supplier to the beginning of the list */ + ruv_move_local_supplier_to_first(ruv, rid); + need_update = 1; /* must update tombstone also */ + } + } + + /* Update also the directory entry */ + if (need_update) { + /* richm 20010821 bug 556498 + replica_replace_ruv_tombstone acquires the repl_lock, so release + the lock then reacquire it if locked */ + if (isLocked) PR_Unlock(r->repl_lock); + replica_replace_ruv_tombstone(r); + if (isLocked) PR_Lock(r->repl_lock); + } + } + + slapi_ch_free((void **)&generation); + return_value = 0; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "RUV for replica %s is missing replica generation\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Unable to convert %s attribute in entry %s to a replica update vector.\n", + type_ruvElement, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + + } + else /* search failed */ + { + if (LDAP_NO_SUCH_OBJECT == rc) + { + /* The entry doesn't exist: create it */ + rc = replica_create_ruv_tombstone(r); + if (LDAP_SUCCESS != rc) + { + /* + * XXXggood - the following error appears on startup if we try + * to initialize replica RUVs before the backend instance is up. + * It's alarming to see this error, and we should suppress it + * (or avoid trying to configure it) if the backend instance is + * not yet online. + */ + /* + * XXXrichm - you can also get this error when the backend is in + * read only mode c.f. bug 539782 + */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: failed to create replica ruv tombstone " + "entry (%s); LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + goto done; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_configure_ruv: No ruv tombstone found for replica %s. " + "Created a new one\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + return_value = 0; + } + } + else + { + /* see if the suffix is disabled */ + char *state = slapi_mtn_get_state(r->repl_root); + if (state && !strcasecmp(state, "disabled")) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replication disabled for " + "entry (%s); LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + slapi_ch_free_string(&state); + goto done; + } + else if (!r->repl_ruv) /* other error */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replication broken for " + "entry (%s); LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + slapi_ch_free_string(&state); + goto done; + } + else /* some error but continue anyway? */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_configure_ruv: Error %d reading tombstone for replica %s.\n", + rc, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + return_value = 0; + } + slapi_ch_free_string(&state); + } + } + + if (NULL != r->min_csn_pl) + { + csnplFree (&r->min_csn_pl); + } + + /* create pending list for min csn if necessary */ + if (ruv_get_smallest_csn_for_replica ((RUV*)object_get_data (r->repl_ruv), + r->repl_rid, &csn) == RUV_SUCCESS) + { + csn_free (&csn); + r->min_csn_pl = NULL; + } + else + { + /* + * The local replica has not generated any of its own CSNs yet. + * We need to watch CSNs being generated and note the first + * locally-generated CSN that's committed. Once that event occurs, + * the RUV is suitable for iteration over locally generated + * changes. + */ + r->min_csn_pl = csnplNew(); + } + +done: + if (NULL != pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + } + if (return_value != 0) + { + if (ruv) + ruv_destroy (&ruv); + } + + return return_value; +} + +/* NOTE - this is the only non-api function that performs locking because + it is called by the event queue */ +static void +_replica_update_state (time_t when, void *arg) +{ + int rc; + const char *replica_name = (const char *)arg; + Object *replica_object = NULL; + Replica *r; + Slapi_Mod smod; + LDAPMod *mods[3]; + Slapi_PBlock *pb = NULL; + char *dn = NULL; + + if (NULL == replica_name) + return; + + /* + * replica_get_by_name() will acquire the replica object + * and that could prevent the replica from being destroyed + * until the object_release is called. + */ + replica_object = replica_get_by_name(replica_name); + if (NULL == replica_object) + { + return; + } + + /* We have a reference, so replica won't vanish on us. */ + r = (Replica *)object_get_data(replica_object); + if (NULL == r) + { + goto done; + } + + PR_Lock(r->repl_lock); + + /* replica state is currently being updated + or no CSN was assigned - bail out */ + if (r->state_update_inprogress) + { + PR_Unlock(r->repl_lock); + goto done; + } + + /* This might be a consumer */ + if (!r->repl_csn_assigned) + { + /* EY: the consumer needs to flush ruv to disk. */ + PR_Unlock(r->repl_lock); + replica_write_ruv(r); + goto done; + } + + /* ONREPL update csn generator state of an updatable replica only */ + /* ONREPL state always changes because we update time every second and + we write state to the disk less frequently */ + rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod); + if (rc != 0) + { + PR_Unlock(r->repl_lock); + goto done; + } + + r->state_update_inprogress = PR_TRUE; + r->repl_csn_assigned = PR_FALSE; + + dn = _replica_get_config_dn (r->repl_root); + pb = slapi_pblock_new(); + mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod); + + /* we don't want to held lock during operations since it causes lock contention + and sometimes deadlock. So releasing lock here */ + + PR_Unlock(r->repl_lock); + + /* replica repl_name and new_name attributes do not get changed once + the replica is configured - so it is ok that they are outside replica lock */ + + /* write replica name if it has not been written before */ + if (r->new_name) + { + struct berval *vals[2]; + struct berval val; + LDAPMod mod; + + mods[1] = &mod; + + mod.mod_op = LDAP_MOD_REPLACE; + mod.mod_type = (char*)attr_replicaName; + mod.mod_bvalues = vals; + vals [0] = &val; + vals [1] = NULL; + val.bv_val = r->repl_name; + val.bv_len = strlen (val.bv_val); + mods[2] = NULL; + } + else + { + mods[1] = NULL; + } + + slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + char ebuf[BUFSIZ]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_update_state: " + "failed to update state of csn generator for replica %s: LDAP " + "error - %d\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + } + else + { + r->new_name = PR_FALSE; + } + + /* update RUV - performs its own locking */ + replica_write_ruv (r); + + /* since this is the only place this value is changed and we are + guaranteed that only one thread enters the function, its ok + to change it outside replica lock */ + r->state_update_inprogress = PR_FALSE; + + slapi_ch_free ((void**)&dn); + slapi_pblock_destroy (pb); + slapi_mod_done (&smod); + +done: + if (replica_object) + object_release (replica_object); +} + +void +replica_write_ruv (Replica *r) +{ + int rc; + Slapi_Mod smod; + Slapi_Mod smod_last_modified; + LDAPMod *mods [3]; + Slapi_PBlock *pb; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + if (!r->repl_ruv_dirty) + { + PR_Unlock(r->repl_lock); + return; + } + + PR_ASSERT (r->repl_ruv); + + ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod); + ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified); + + PR_Unlock (r->repl_lock); + + mods [0] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod); + mods [1] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_last_modified); + mods [2] = NULL; + pb = slapi_pblock_new(); + + /* replica name never changes so it is ok to reference it outside the lock */ + slapi_modify_internal_set_pb( + pb, + slapi_sdn_get_dn(r->repl_root), /* only used to select be */ + mods, + NULL, /* controls */ + RUV_STORAGE_ENTRY_UNIQUEID, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + /* Add OP_FLAG_TOMBSTONE_ENTRY so that this doesn't get logged in the Retro ChangeLog */ + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | OP_FLAG_TOMBSTONE_ENTRY); + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + + /* ruv does not exist - create one */ + PR_Lock(r->repl_lock); + + if (rc == LDAP_SUCCESS) + { + r->repl_ruv_dirty = PR_FALSE; + } + else if (rc == LDAP_NO_SUCH_OBJECT) + { + /* this includes an internal operation - but since this only happens + during server startup - its ok that we have lock around it */ + rc = _replica_configure_ruv (r, PR_TRUE); + if (rc == 0) + r->repl_ruv_dirty = PR_FALSE; + } + else /* error */ + { + char ebuf[BUFSIZ]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_write_ruv: failed to update RUV tombstone for %s; " + "LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + PR_ASSERT (0); + } + + PR_Unlock(r->repl_lock); + + slapi_mod_done (&smod); + slapi_mod_done (&smod_last_modified); + slapi_pblock_destroy (pb); +} + + +const CSN * +_get_deletion_csn(Slapi_Entry *e) +{ + const CSN *deletion_csn = NULL; + + PR_ASSERT(NULL != e); + if (NULL != e) + { + Slapi_Attr *oc_attr = NULL; + if (entry_attr_find_wsi(e, SLAPI_ATTR_OBJECTCLASS, &oc_attr) == ATTRIBUTE_PRESENT) + { + Slapi_Value *tombstone_value = NULL; + struct berval v; + v.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE; + v.bv_len = strlen(SLAPI_ATTR_VALUE_TOMBSTONE); + if (attr_value_find_wsi(oc_attr, &v, &tombstone_value) == VALUE_PRESENT) + { + deletion_csn = value_get_csn(tombstone_value, CSN_TYPE_VALUE_UPDATED); + } + } + } + return deletion_csn; +} + + +static void +_delete_tombstone(const char *tombstone_dn, const char *uniqueid) +{ + + PR_ASSERT(NULL != tombstone_dn && NULL != uniqueid); + if (NULL == tombstone_dn || NULL == uniqueid) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_delete_tombstone: " + "NULL tombstone_dn or uniqueid provided.\n"); + } + else + { + int ldaprc; + Slapi_PBlock *pb = slapi_pblock_new(); + slapi_delete_internal_set_pb(pb, tombstone_dn, NULL, /* controls */ + uniqueid, repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_TOMBSTONE_ENTRY); + slapi_delete_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ldaprc); + if (LDAP_SUCCESS != ldaprc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_delete_tombstone: unable to delete tombstone %s, " + "uniqueid %s: %s.\n", tombstone_dn, uniqueid, + ldap_err2string(ldaprc)); + } + slapi_pblock_destroy(pb); + } +} + +static +void get_reap_result (int rc, void *cb_data) +{ + PR_ASSERT (cb_data); + + ((reap_callback_data*)cb_data)->rc = rc; +} + +static +int process_reap_entry (Slapi_Entry *entry, void *cb_data) +{ + char ebuf[BUFSIZ]; + char deletion_csn_str[CSN_STRSIZE]; + char purge_csn_str[CSN_STRSIZE]; + unsigned long *num_entriesp = &((reap_callback_data *)cb_data)->num_entries; + unsigned long *num_purged_entriesp = &((reap_callback_data *)cb_data)->num_purged_entries; + CSN *purge_csn = ((reap_callback_data *)cb_data)->purge_csn; + PRBool *tombstone_reap_stop = ((reap_callback_data *)cb_data)->tombstone_reap_stop; + /* we only ask for the objectclass in the search - the deletion csn is in the + objectclass attribute values - if we need more attributes returned by the + search in the future, see _replica_reap_tombstones below and add more to the + attrs array */ + const CSN *deletion_csn = _get_deletion_csn(entry); + + if ((NULL == deletion_csn || csn_compare(deletion_csn, purge_csn) < 0) && + (!is_ruv_tombstone_entry(entry))) { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_reap_tombstones: removing tombstone %s " + "because its deletion csn (%s) is less than the " + "purge csn (%s).\n", + escape_string(slapi_entry_get_dn(entry), ebuf), + csn_as_string(deletion_csn, PR_FALSE, deletion_csn_str), + csn_as_string(purge_csn, PR_FALSE, purge_csn_str)); + _delete_tombstone(slapi_entry_get_dn(entry), + slapi_entry_get_uniqueid(entry)); + (*num_purged_entriesp)++; + } + else { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_reap_tombstones: NOT removing tombstone " + "%s\n", escape_string(slapi_entry_get_dn(entry),ebuf)); + } + (*num_entriesp)++; + if (*tombstone_reap_stop || g_get_shutdown()) { + return -1; + } + + return 0; +} + + + + +/* This does the actual work of searching for tombstones and deleting them. + This must be called in a separate thread because it may take a long time. +*/ +static void +_replica_reap_tombstones(void *arg) +{ + const char *replica_name = (const char *)arg; + Slapi_PBlock *pb = NULL; + Object *replica_object = NULL; + Replica *replica = NULL; + CSN *purge_csn = NULL; + char ebuf[BUFSIZ]; + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: Beginning tombstone reap for replica %s.\n", + replica_name ? replica_name : "(null)"); + + if (NULL == replica_name) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Warning: Replica name is null in tombstone reap\n"); + goto done; + } + + /* + * replica_get_by_name() will acquire the replica object + * and that could prevent the replica from being destroyed + * until the object_release is called. + */ + replica_object = replica_get_by_name(replica_name); + if (NULL == replica_object) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Warning: Replica object %s is null in tombstone reap\n", replica_name); + goto done; + } + + /* We have a reference, so replica won't vanish on us. */ + replica = (Replica *)object_get_data(replica_object); + if (NULL == replica) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Warning: Replica %s is null in tombstone reap\n", replica_name); + goto done; + } + + if (replica->tombstone_reap_stop) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: Replica %s reap stop flag is set for tombstone reap\n", replica_name); + goto done; + } + + purge_csn = replica_get_purge_csn(replica); + if (NULL != purge_csn) + { + LDAPControl **ctrls; + int oprc; + reap_callback_data cb_data; + char **attrs = NULL; + + /* we just need the objectclass - for the deletion csn + and the dn and nsuniqueid - for possible deletion + saves time to return only 2 attrs + */ + charray_add(&attrs, slapi_ch_strdup("objectclass")); + charray_add(&attrs, slapi_ch_strdup("nsuniqueid")); + + ctrls = (LDAPControl **)slapi_ch_calloc (3, sizeof (LDAPControl *)); + ctrls[0] = create_managedsait_control(); + ctrls[1] = create_backend_control(replica->repl_root); + ctrls[2] = NULL; + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, slapi_sdn_get_dn(replica->repl_root), + LDAP_SCOPE_SUBTREE, "(&(objectclass=nstombstone)(nscpentrydn=*))", + attrs, 0, ctrls, NULL, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0); + + cb_data.rc = 0; + cb_data.num_entries = 0UL; + cb_data.num_purged_entries = 0UL; + cb_data.purge_csn = purge_csn; + cb_data.tombstone_reap_stop = &(replica->tombstone_reap_stop); + + slapi_search_internal_callback_pb (pb, &cb_data /* callback data */, + get_reap_result /* result callback */, + process_reap_entry /* entry callback */, + NULL /* referral callback*/); + + charray_free(attrs); + + oprc = cb_data.rc; + + if (LDAP_SUCCESS != oprc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_reap_tombstones: failed when searching for " + "tombstones in replica %s: %s. Will try again in %d " + "seconds.\n", escape_string(slapi_sdn_get_dn(replica->repl_root),ebuf), + ldap_err2string(oprc), replica->tombstone_reap_interval); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_reap_tombstones: purged %d of %d tombstones " + "in replica %s. Will try again in %d " + "seconds.\n", cb_data.num_purged_entries, cb_data.num_entries, + escape_string(slapi_sdn_get_dn(replica->repl_root),ebuf), + replica->tombstone_reap_interval); + } + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: No purge CSN for tombstone reap for replica %s.\n", + replica_name ? replica_name : "(null)"); + } + + PR_Lock(replica->repl_lock); + replica->tombstone_reap_active = PR_FALSE; + PR_Unlock(replica->repl_lock); + +done: + if (NULL != purge_csn) + { + csn_free(&purge_csn); + } + if (NULL != pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + } + if (NULL != replica_object) + { + object_release(replica_object); + replica_object = NULL; + replica = NULL; + } + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: Finished tombstone reap for replica %s.\n", + replica_name ? replica_name : "(null)"); + +} + +/* + We don't want to run the reaper function directly from the event + queue since it may hog the event queue, starving other events. + See bug 604441 + The function eq_cb_reap_tombstones will fire off the actual thread + that does the real work. +*/ +static void +eq_cb_reap_tombstones(time_t when, void *arg) +{ + const char *replica_name = (const char *)arg; + Object *replica_object = NULL; + Replica *replica = NULL; + + if (NULL != replica_name) + { + /* + * replica_get_by_name() will acquire the replica object + * and that could prevent the replica from being destroyed + * until the object_release is called. + */ + replica_object = replica_get_by_name(replica_name); + if (NULL != replica_object) + { + /* We have a reference, so replica won't vanish on us. */ + replica = (Replica *)object_get_data(replica_object); + if (replica) + { + + PR_Lock(replica->repl_lock); + + /* No action if purge is disabled or the previous purge is not done yet */ + if (replica->tombstone_reap_interval != 0 && + replica->tombstone_reap_active == PR_FALSE) + { + /* set the flag here to minimize race conditions */ + replica->tombstone_reap_active = PR_TRUE; + if (PR_CreateThread(PR_USER_THREAD, + _replica_reap_tombstones, (void *)replica_name, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, + SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL) + { + replica->tombstone_reap_active = PR_FALSE; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Error: unable to create the tombstone reap thread for replica %s. " + "Possible system resources problem\n", + replica_name); + } + } + /* reap thread will wait until this lock is released */ + PR_Unlock(replica->repl_lock); + } + object_release(replica_object); + replica_object = NULL; + replica = NULL; + } + } +} + +static char * +_replica_type_as_string (const Replica *r) +{ + switch (r->repl_type) + { + case REPLICA_TYPE_PRIMARY: return "primary"; + case REPLICA_TYPE_READONLY: return "read-only"; + case REPLICA_TYPE_UPDATABLE: return "updatable"; + default: return "unknown"; + } +} + + +static const char *root_glue = + "dn: %s\n" + "objectclass: top\n" + "objectclass: nsTombstone\n" + "objectclass: extensibleobject\n" + "nsuniqueid: %s\n"; + +static int +replica_create_ruv_tombstone(Replica *r) +{ + int return_value = LDAP_LOCAL_ERROR; + char *root_entry_str; + Slapi_Entry *e; + const char *purl = NULL; + RUV *ruv; + struct berval **bvals = NULL; + Slapi_PBlock *pb = NULL; + int rc; + char ebuf[BUFSIZ]; + + PR_ASSERT(NULL != r && NULL != r->repl_root); + root_entry_str = slapi_ch_malloc(strlen(root_glue) + + slapi_sdn_get_ndn_len(r->repl_root) + + strlen(RUV_STORAGE_ENTRY_UNIQUEID) + 1); + sprintf(root_entry_str, root_glue, slapi_sdn_get_ndn(r->repl_root), + RUV_STORAGE_ENTRY_UNIQUEID); + + e = slapi_str2entry(root_entry_str, SLAPI_STR2ENTRY_TOMBSTONE_CHECK); + if (e == NULL) + goto done; + + /* Add ruv */ + if (r->repl_ruv == NULL) + { + CSNGen *gen; + CSN *csn; + char csnstr [CSN_STRSIZE]; + + /* first attempt to write RUV tombstone - need to create RUV */ + gen = (CSNGen *)object_get_data(r->repl_csngen); + PR_ASSERT (gen); + + if (csngen_new_csn(gen, &csn, PR_FALSE /* notify */) == CSN_SUCCESS) + { + (void)csn_as_string(csn, PR_FALSE, csnstr); + csn_free(&csn); + + /* if this is an updateable replica - add its own + element to the RUV so that referrals work correctly */ + if (r->repl_type == REPLICA_TYPE_UPDATABLE) + purl = multimaster_get_local_purl(); + + if (ruv_init_new(csnstr, r->repl_rid, purl, &ruv) == RUV_SUCCESS) + { + r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy); + r->repl_ruv_dirty = PR_TRUE; + return_value = LDAP_SUCCESS; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Cannot create new replica update vector for %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Cannot obtain CSN for new replica update vector for %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + } + else /* failed to write the entry because DB was not initialized - retry */ + { + ruv = (RUV*) object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + } + + PR_ASSERT (r->repl_ruv); + + rc = ruv_to_bervals(ruv, &bvals); + if (rc != RUV_SUCCESS) + { + goto done; + } + + /* ONREPL this is depricated function but there is currently no better API to use */ + rc = slapi_entry_add_values(e, type_ruvElement, bvals); + if (rc != 0) + { + goto done; + } + + + pb = slapi_pblock_new(); + slapi_add_entry_internal_set_pb( + pb, + e, + NULL /* controls */, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_TOMBSTONE_ENTRY | OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP); + slapi_add_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value); + if (return_value == LDAP_SUCCESS) + r->repl_ruv_dirty = PR_FALSE; + +done: + if (return_value != LDAP_SUCCESS) + { + slapi_entry_free (e); + } + + if (bvals) + ber_bvecfree(bvals); + + if (pb) + slapi_pblock_destroy(pb); + + slapi_ch_free((void **) &root_entry_str); + + return return_value; +} + + +static void +assign_csn_callback(const CSN *csn, void *data) +{ + Replica *r = (Replica *)data; + Object *ruv_obj; + RUV *ruv; + + PR_ASSERT(NULL != csn); + PR_ASSERT(NULL != r); + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + ruv = (RUV*)object_get_data (ruv_obj); + PR_ASSERT (ruv); + + PR_Lock(r->repl_lock); + + r->repl_csn_assigned = PR_TRUE; + + if (NULL != r->min_csn_pl) + { + if (csnplInsert(r->min_csn_pl, csn) != 0) + { + char ebuf[BUFSIZ]; + char csn_str[CSN_STRSIZE]; /* For logging only */ + /* Ack, we can't keep track of min csn. Punt. */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "assign_csn_callback: " + "failed to insert csn %s for replica %s\n", + csn_as_string(csn, PR_FALSE, csn_str), + escape_string(slapi_sdn_get_dn(r->repl_root), ebuf)); + csnplFree(&r->min_csn_pl); + } + } + + ruv_add_csn_inprogress (ruv, csn); + + PR_Unlock(r->repl_lock); + + object_release (ruv_obj); +} + + +static void +abort_csn_callback(const CSN *csn, void *data) +{ + Replica *r = (Replica *)data; + Object *ruv_obj; + RUV *ruv; + int rc; + + PR_ASSERT(NULL != csn); + PR_ASSERT(NULL != data); + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + ruv = (RUV*)object_get_data (ruv_obj); + PR_ASSERT (ruv); + + PR_Lock(r->repl_lock); + + if (NULL != r->min_csn_pl) + { + rc = csnplRemove(r->min_csn_pl, csn); + PR_ASSERT(rc == 0); + } + + ruv_cancel_csn_inprogress (ruv, csn); + PR_Unlock(r->repl_lock); + + object_release (ruv_obj); +} + +static CSN * +_replica_get_purge_csn_nolock(const Replica *r) +{ + static unsigned long a_week = 3600*24*7; + CSN *purge_csn = NULL; + CSN **csns = NULL; + RUV *ruv; + time_t cutoff_time; + time_t max_time_in_csn_list; + int i; + + if (r->repl_purge_delay > 0) + { + /* + * Don't let inactive or obsolete masters in the ruv hold back + * the purge forever: + * - set a graceful period of at least 7 days; + * - set cutoff_time = max(maxcsns) - gracefule_period; + * - the first maxcsn that was generated at or after the cutoff + * time would be the purge csn. + */ + + /* get a sorted list of all maxcsns in ruv in ascend order */ + object_acquire(r->repl_ruv); + ruv = object_get_data(r->repl_ruv); + csns = cl5BuildCSNList (ruv, NULL); + object_release(r->repl_ruv); + + if (csns == NULL) + return NULL; + + /* locate the max csn in the csn list */ + for (i = 0; csns[i]; i++); + max_time_in_csn_list = csn_get_time (csns[i-1]); + + if ( r->repl_purge_delay > a_week ) + { + cutoff_time = max_time_in_csn_list - r->repl_purge_delay; + } + else + { + cutoff_time = max_time_in_csn_list - a_week; + } + for (i = 0; csns[i]; i++) + { + if ( csn_get_time (csns[i]) >= cutoff_time ) + { + purge_csn = csn_dup (csns[i]); + break; + } + } + + /* Subtract purge delay */ + if (purge_csn) + { + csn_set_time(purge_csn, csn_get_time(purge_csn) - r->repl_purge_delay); + } + } + + if (csns) + cl5DestroyCSNList (&csns); + + return purge_csn; +} + +static void +replica_get_referrals_nolock (const Replica *r, char ***referrals) +{ + if(referrals!=NULL) + { + + int hint; + int i= 0; + Slapi_Value *v= NULL; + + if (NULL == r->repl_referral) + { + *referrals = NULL; + } + else + { + /* richm: +1 for trailing NULL */ + *referrals= (char**)slapi_ch_calloc(sizeof(char*),1+slapi_valueset_count(r->repl_referral)); + hint= slapi_valueset_first_value( r->repl_referral, &v ); + while(v!=NULL) + { + const char *s= slapi_value_get_string(v); + if(s!=NULL && s[0]!='\0') + { + (*referrals)[i]= slapi_ch_strdup(s); + i++; + } + hint= slapi_valueset_next_value( r->repl_referral, hint, &v); + } + (*referrals)[i] = NULL; + } + + } +} + +static void +replica_clear_legacy_referrals(const Slapi_DN *repl_root_sdn, + char **referrals, const char *state) +{ + repl_set_mtn_state_and_referrals(repl_root_sdn, state, NULL, NULL, referrals); +} + +static void +replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr) +{ + Slapi_PBlock *pb; + Slapi_Mods smods; + LDAPControl **ctrls; + int rc; + + pb = slapi_pblock_new (); + + slapi_mods_init(&smods, 1); + slapi_mods_add(&smods, LDAP_MOD_DELETE, attr, 0, NULL); + + + ctrls = (LDAPControl**)slapi_ch_malloc (2 * sizeof (LDAPControl*)); + ctrls[0] = create_managedsait_control (); + ctrls[1] = NULL; + + /* remove copiedFrom/copyingFrom first */ + slapi_modify_internal_set_pb (pb, slapi_sdn_get_dn (repl_root_sdn), + slapi_mods_get_ldapmods_passout (&smods), ctrls, + NULL /*uniqueid */, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION) , + 0 /* operation_flags */); + + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + char ebuf[BUFSIZ]; + + /* this is not a fatal error because the attribute may not be there */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_remove_legacy_attr: " + "failed to remove legacy attribute %s for replica %s; LDAP error - %d\n", + attr, escape_string(slapi_sdn_get_dn(repl_root_sdn),ebuf), rc); + } + + slapi_mods_done (&smods); + slapi_pblock_destroy (pb); +} + +static int +replica_log_ruv_elements_nolock (const Replica *r) +{ + int rc = 0; + slapi_operation_parameters op_params; + RUV *ruv; + char *repl_gen; + CSN *csn = NULL; + + ruv = (RUV*) object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + + if ((ruv_get_min_csn(ruv, &csn) == RUV_SUCCESS) && csn) + { + /* we log it as a delete operation to have the least number of fields + to set. the entry can be identified by a special target uniqueid and + special target dn */ + memset (&op_params, 0, sizeof (op_params)); + op_params.operation_type = SLAPI_OPERATION_DELETE; + op_params.target_address.dn = START_ITERATION_ENTRY_DN; + op_params.target_address.uniqueid = START_ITERATION_ENTRY_UNIQUEID; + op_params.csn = csn; + repl_gen = ruv_get_replica_generation (ruv); + + rc = cl5WriteOperation(r->repl_name, repl_gen, &op_params, PR_FALSE); + if (rc == CL5_SUCCESS) + rc = 0; + else + rc = -1; + + slapi_ch_free ((void**)&repl_gen); + csn_free (&csn); + } + + return rc; +} + +void +replica_set_purge_delay(Replica *r, PRUint32 purge_delay) +{ + PR_ASSERT(r); + PR_Lock(r->repl_lock); + r->repl_purge_delay = purge_delay; + PR_Unlock(r->repl_lock); +} + +void +replica_set_tombstone_reap_interval (Replica *r, long interval) +{ + char *repl_name; + + PR_Lock(r->repl_lock); + + /* + * Leave the event there to purge the existing tombstones + * if we are about to turn off tombstone creation + */ + if (interval > 0 && r->repl_eqcxt_tr && r->tombstone_reap_interval != interval) + { + int found; + + repl_name = slapi_eq_get_arg (r->repl_eqcxt_tr); + slapi_ch_free ((void**)&repl_name); + found = slapi_eq_cancel (r->repl_eqcxt_tr); + slapi_log_error (SLAPI_LOG_REPL, NULL, + "tombstone_reap event (interval=%d) was %s\n", + r->tombstone_reap_interval, (found ? "cancelled" : "not found")); + r->repl_eqcxt_tr = NULL; + } + r->tombstone_reap_interval = interval; + if ( interval > 0 && r->repl_eqcxt_tr == NULL ) + { + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_tr = slapi_eq_repeat (eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval); + slapi_log_error (SLAPI_LOG_REPL, NULL, + "tombstone_reap event (interval=%d) was %s\n", + r->tombstone_reap_interval, (r->repl_eqcxt_tr ? "scheduled" : "not scheduled successfully")); + } + PR_Unlock(r->repl_lock); +} + +/* Update the tombstone entry to reflect the content of the ruv */ +static void +replica_replace_ruv_tombstone(Replica *r) +{ + Slapi_PBlock *pb = NULL; + char *dn; + int rc; + + Slapi_Mod smod; + Slapi_Mod smod_last_modified; + LDAPMod *mods [3]; + + PR_ASSERT(NULL != r && NULL != r->repl_root); + + PR_Lock(r->repl_lock); + + PR_ASSERT (r->repl_ruv); + ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod); + ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified); + + dn = _replica_get_config_dn (r->repl_root); + mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod); + mods[1] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod_last_modified); + + PR_Unlock (r->repl_lock); + + mods [2] = NULL; + pb = slapi_pblock_new(); + + slapi_modify_internal_set_pb( + pb, + (char*)slapi_sdn_get_dn (r->repl_root), /* only used to select be */ + mods, + NULL, /* controls */ + RUV_STORAGE_ENTRY_UNIQUEID, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP); + + slapi_modify_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + + if (rc != LDAP_SUCCESS) + { + if ((rc != LDAP_NO_SUCH_OBJECT) || !replica_is_state_flag_set(r, REPLICA_IN_USE)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_replace_ruv_tombstone: " + "failed to update replication update vector for replica %s: LDAP " + "error - %d\n", (char*)slapi_sdn_get_dn (r->repl_root), rc); + } + } + + slapi_ch_free ((void**)&dn); + slapi_pblock_destroy (pb); + slapi_mod_done (&smod); + slapi_mod_done (&smod_last_modified); +} + +void +replica_update_ruv_consumer(Replica *r, RUV *supplier_ruv) +{ + ReplicaId supplier_id = 0; + char *supplier_purl = NULL; + + if ( ruv_get_first_id_and_purl(supplier_ruv, &supplier_id, &supplier_purl) == RUV_SUCCESS ) + { + RUV *local_ruv = NULL; + + PR_Lock(r->repl_lock); + + local_ruv = (RUV*)object_get_data (r->repl_ruv); + PR_ASSERT (local_ruv); + + if ( ruv_local_contains_supplier(local_ruv, supplier_id) == 0 ) + { + if ( r->repl_type == REPLICA_TYPE_UPDATABLE ) + { + /* Add the new ruv right after the consumer own purl */ + ruv_add_index_replica(local_ruv, supplier_id, supplier_purl, 2); + } + else + { + /* This is a consumer only, add it first */ + ruv_add_index_replica(local_ruv, supplier_id, supplier_purl, 1); + } + } + else + { + /* Replace it */ + ruv_replace_replica_purl(local_ruv, supplier_id, supplier_purl); + } + PR_Unlock(r->repl_lock); + + /* Update also the directory entry */ + replica_replace_ruv_tombstone(r); + } +} + +void +replica_set_ruv_dirty(Replica *r) +{ + PR_ASSERT(r); + PR_Lock(r->repl_lock); + r->repl_ruv_dirty = PR_TRUE; + PR_Unlock(r->repl_lock); +} + +PRBool +replica_is_state_flag_set(Replica *r, PRInt32 flag) +{ + PR_ASSERT(r); + if (r) + return (r->repl_state_flags & flag); + else + return PR_FALSE; +} + +void +replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear) +{ + if (r == NULL) + return; + + PR_Lock(r->repl_lock); + + if (clear) + { + r->repl_state_flags &= ~flag; + } + else + { + r->repl_state_flags |= flag; + } + + PR_Unlock(r->repl_lock); +} + +/* replica just came back online, probably after data was reloaded */ +void +replica_enable_replication (Replica *r) +{ + int rc; + + PR_ASSERT(r); + + /* prevent creation of new agreements until the replica is enabled */ + PR_Lock(r->agmt_lock); + + /* retrieve new ruv */ + rc = replica_reload_ruv (r); + if (rc) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_enable_replication: " + "reloading ruv failed\n"); + /* What to do ? */ + } + + /* Replica came back online, Check if the total update was terminated. + If flag is still set, it was not terminated, therefore the data is + very likely to be incorrect, and we should not restart Replication threads... + */ + if (!replica_is_state_flag_set(r, REPLICA_TOTAL_IN_PROGRESS)){ + /* restart outbound replication */ + start_agreements_for_replica (r, PR_TRUE); + + /* enable ruv state update */ + replica_set_enabled (r, PR_TRUE); + } + + /* mark the replica as being available for updates */ + replica_relinquish_exclusive_access(r, 0, 0); + + replica_set_state_flag(r, REPLICA_AGREEMENTS_DISABLED, PR_TRUE /* clear */); + PR_Unlock(r->agmt_lock); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_enable_replication: " + "replica %s is relinquished\n", + slapi_sdn_get_ndn (replica_get_root (r))); +} + +/* replica is about to be taken offline */ +void +replica_disable_replication (Replica *r, Object *r_obj) +{ + char *current_purl = NULL; + char *p_locking_purl = NULL; + char *locking_purl = NULL; + int junkrc; + ReplicaId junkrid; + PRBool isInc = PR_FALSE; /* get exclusive access, but not for inc update */ + RUV *repl_ruv = NULL; + + /* prevent creation of new agreements until the replica is disabled */ + PR_Lock(r->agmt_lock); + + /* stop ruv update */ + replica_set_enabled (r, PR_FALSE); + + /* disable outbound replication */ + start_agreements_for_replica (r, PR_FALSE); + + /* close the corresponding changelog file */ + /* close_changelog_for_replica (r_obj); */ + + /* mark the replica as being unavailable for updates */ + /* If an incremental update is in progress, we want to wait until it is + finished until we get exclusive access to the replica, because we have + to make sure no operations are in progress - it messes up replication + when a restore is in progress but we are still adding replicated entries + from a supplier + */ + repl_ruv = (RUV*) object_get_data (r->repl_ruv); + junkrc = ruv_get_first_id_and_purl(repl_ruv, &junkrid, &p_locking_purl); + locking_purl = slapi_ch_strdup(p_locking_purl); + p_locking_purl = NULL; + repl_ruv = NULL; + while (!replica_get_exclusive_access(r, &isInc, 0, 0, "replica_disable_replication", + ¤t_purl)) { + if (!isInc) /* already locked, but not by inc update - break */ + break; + isInc = PR_FALSE; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "replica_disable_replication: " + "replica %s is already locked by (%s) for incoming " + "incremental update; sleeping 100ms\n", + slapi_sdn_get_ndn (replica_get_root (r)), + current_purl ? current_purl : "unknown"); + slapi_ch_free_string(¤t_purl); + DS_Sleep(PR_MillisecondsToInterval(100)); + } + + slapi_ch_free_string(¤t_purl); + slapi_ch_free_string(&locking_purl); + replica_set_state_flag(r, REPLICA_AGREEMENTS_DISABLED, PR_FALSE); + PR_Unlock(r->agmt_lock); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_disable_replication: " + "replica %s is acquired\n", + slapi_sdn_get_ndn (replica_get_root (r))); +} + +static void +start_agreements_for_replica (Replica *r, PRBool start) +{ + Object *agmt_obj; + Repl_Agmt *agmt; + + agmt_obj = agmtlist_get_first_agreement_for_replica (r); + while (agmt_obj) + { + agmt = (Repl_Agmt*)object_get_data (agmt_obj); + PR_ASSERT (agmt); + + if (start) + agmt_start (agmt); + else /* stop */ + agmt_stop (agmt); + + agmt_obj = agmtlist_get_next_agreement_for_replica (r, agmt_obj); + } +} + +int replica_start_agreement(Replica *r, Repl_Agmt *ra) +{ + int ret = 0; + + if (r == NULL) return -1; + + PR_Lock(r->agmt_lock); + + if (!replica_is_state_flag_set(r, REPLICA_AGREEMENTS_DISABLED)) { + ret = agmt_start(ra); /* Start the replication agreement */ + } + + PR_Unlock(r->agmt_lock); + return ret; +} + +/* + * A callback function registed as op->o_csngen_handler and + * called by backend ops to generate opcsn. + */ +CSN * +replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn ) +{ + CSN *opcsn = NULL; + Object *replica_obj; + + replica_obj = replica_get_replica_for_op (pb); + if (NULL != replica_obj) + { + Replica *replica = (Replica*) object_get_data (replica_obj); + if ( NULL != replica ) + { + Slapi_Operation *op; + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + if ( replica->repl_type != REPLICA_TYPE_READONLY || + operation_is_flag_set (op, OP_FLAG_LEGACY_REPLICATION_DN )) + { + Object *gen_obj = replica_get_csngen (replica); + if (NULL != gen_obj) + { + CSNGen *gen = (CSNGen*) object_get_data (gen_obj); + if (NULL != gen) + { + /* The new CSN should be greater than the base CSN */ + csngen_new_csn (gen, &opcsn, PR_FALSE /* don't notify */); + if (csn_compare (opcsn, basecsn) <= 0) + { + char opcsnstr[CSN_STRSIZE], basecsnstr[CSN_STRSIZE]; + char opcsn2str[CSN_STRSIZE]; + + csn_as_string (opcsn, PR_FALSE, opcsnstr); + csn_as_string (basecsn, PR_FALSE, basecsnstr); + csn_free ( &opcsn ); + csngen_adjust_time (gen, basecsn); + csngen_new_csn (gen, &opcsn, PR_FALSE /* don't notify */); + csn_as_string (opcsn, PR_FALSE, opcsn2str); + slapi_log_error (SLAPI_LOG_FATAL, NULL, + "replica_generate_next_csn: " + "opcsn=%s <= basecsn=%s, adjusted opcsn=%s\n", + opcsnstr, basecsnstr, opcsn2str); + } + /* + * Insert opcsn into the csn pending list. + * This is the notify effect in csngen_new_csn(). + */ + assign_csn_callback (opcsn, (void *)replica); + } + object_release (gen_obj); + } + } + } + object_release (replica_obj); + } + + return opcsn; +} + +/* + * A callback function registed as op->o_replica_attr_handler and + * called by backend ops to get replica attributes. + */ +int +replica_get_attr ( Slapi_PBlock *pb, const char* type, void *value ) +{ + int rc = -1; + + Object *replica_obj; + replica_obj = replica_get_replica_for_op (pb); + if (NULL != replica_obj) + { + Replica *replica = (Replica*) object_get_data (replica_obj); + if ( NULL != replica ) + { + if (strcasecmp (type, type_replicaTombstonePurgeInterval) == 0) + { + *((int*)value) = replica->tombstone_reap_interval; + rc = 0; + } + else if (strcasecmp (type, type_replicaPurgeDelay) == 0) + { + *((int*)value) = replica->repl_purge_delay; + rc = 0; + } + } + object_release (replica_obj); + } + + return rc; +} diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c new file mode 100644 index 00000000..df2573a0 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica_config.c @@ -0,0 +1,750 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_replica_config.c - replica configuration over ldap */ +#include <ctype.h> /* for isdigit() */ +#include "repl.h" /* ONREPL - this is bad */ +#include "repl5.h" +#include "cl5_api.h" + +#define CONFIG_BASE "cn=mapping tree,cn=config" +#define CONFIG_FILTER "(objectclass=nsDS5Replica)" +#define TASK_ATTR "nsds5Task" +#define CL2LDIF_TASK "CL2LDIF" +#define CLEANRUV "CLEANRUV" +#define CLEANRUVLEN 8 + +int slapi_log_urp = SLAPI_LOG_REPL; + +/* Forward Declartions */ +static int replica_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int replica_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int replica_config_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +static int replica_config_change_type_and_id (Replica *r, const char *new_type, const char *new_id, char *returntext, int apply_mods); +static int replica_config_change_updatedn (Replica *r, const LDAPMod *mod, char *returntext, int apply_mods); +static int replica_config_change_flags (Replica *r, const char *new_flags, char *returntext, int apply_mods); +static int replica_execute_task (Object *r, const char *task_name, char *returntext, int apply_mods); +static int replica_execute_cl2ldif_task (Object *r, char *returntext); +static int replica_execute_cleanruv_task (Object *r, ReplicaId rid, char *returntext); + +static multimaster_mtnode_extension * _replica_config_get_mtnode_ext (const Slapi_Entry *e); + +static PRLock *s_configLock; + +static int +dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg) +{ + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +int +replica_config_init() +{ + s_configLock = PR_NewLock (); + if (s_configLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_init: " + "failed to cretate configuration lock; NSPR error - %d\n", + PR_GetError ()); + return -1; + } + + /* config DSE must be initialized before we get here */ + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_add, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_modify,NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, dont_allow_that, NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_delete,NULL); + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_search,NULL); + return 0; +} + +void +replica_config_destroy () +{ + if (s_configLock) + { + PR_DestroyLock (s_configLock); + s_configLock = NULL; + } + + /* config DSE must be initialized before we get here */ + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_add); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_modify); + slapi_config_remove_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, dont_allow_that); + slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_delete); + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_search); +} + +static int +replica_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *errorbuf, void *arg) +{ + Replica *r = NULL; + multimaster_mtnode_extension *mtnode_ext; + char *replica_root = (char*)slapi_entry_attr_get_charptr (e, attr_replicaRoot); + char buf [BUFSIZ]; + char *errortext = errorbuf ? errorbuf : buf; + + if (errorbuf) + { + errorbuf[0] = '\0'; + } + + *returncode = LDAP_SUCCESS; + + PR_Lock (s_configLock); + + /* add the dn to the dn hash so we can tell this replica is being configured */ + replica_add_by_dn(replica_root); + + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + { + sprintf (errortext, "replica already configured for %s", replica_root); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: %s\n", errortext); + *returncode = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + /* create replica object */ + r = replica_new_from_entry (e, errortext, PR_TRUE /* is a newly added entry */); + if (r == NULL) + { + *returncode = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* Set the mapping tree node state, and the referrals from the RUV */ + /* if this server is a 4.0 consumer the referrals are set by legacy plugin */ + if (!replica_is_legacy_consumer (r)) + consumer5_set_mapping_tree_state_for_replica(r, NULL); + + /* ONREPL if replica is added as writable we need to execute protocol that + introduces new writable replica to the topology */ + + mtnode_ext->replica = object_new (r, replica_destroy); /* Refcnt is 1 */ + + /* add replica object to the hash */ + *returncode = replica_add_by_name (replica_get_name (r), mtnode_ext->replica); /* Increments object refcnt */ + /* delete the dn from the dn hash - done with configuration */ + replica_delete_by_dn(replica_root); + +done: + + PR_Unlock (s_configLock); + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&replica_root); + + if (*returncode != LDAP_SUCCESS) + { + if (mtnode_ext->replica) + object_release (mtnode_ext->replica); + return SLAPI_DSE_CALLBACK_ERROR; + } + else + return SLAPI_DSE_CALLBACK_OK; +} + +static int +replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + int rc= 0; + LDAPMod **mods; + int i, apply_mods; + multimaster_mtnode_extension *mtnode_ext; + Replica *r = NULL; + char *replica_root = NULL; + char buf [BUFSIZ]; + char *errortext = returntext ? returntext : buf; + char *config_attr, *config_attr_value; + Slapi_Operation *op; + void *identity; + + if (returntext) + { + returntext[0] = '\0'; + } + *returncode = LDAP_SUCCESS; + + /* just let internal operations originated from replication plugin to go through */ + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + + if (operation_is_flag_set(op, OP_FLAG_INTERNAL) && + (identity == repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION))) + { + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; + } + + replica_root = (char*)slapi_entry_attr_get_charptr (e, attr_replicaRoot); + + PR_Lock (s_configLock); + + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + object_acquire (mtnode_ext->replica); + + if (mtnode_ext->replica == NULL) + { + sprintf (errortext, "replica does not exist for %s", replica_root); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + *returncode = LDAP_OPERATIONS_ERROR; + goto done; + } + + r = object_get_data (mtnode_ext->replica); + PR_ASSERT (r); + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (apply_mods = 0; apply_mods <= 1; apply_mods++) + { + /* we only allow the replica ID and type to be modified together e.g. + if converting a read only replica to a master or vice versa - + we will need to change both the replica ID and the type at the same + time - we must disallow changing the replica ID if the type is not + being changed and vice versa + */ + char *new_repl_id = NULL; + char *new_repl_type = NULL; + + if (*returncode != LDAP_SUCCESS) + break; + + for (i = 0; (mods[i] && (LDAP_SUCCESS == rc)); i++) + { + if (*returncode != LDAP_SUCCESS) + break; + + config_attr = (char *) mods[i]->mod_type; + PR_ASSERT (config_attr); + + /* disallow modifications or removal of replica root, + replica name and replica state attributes */ + if (strcasecmp (config_attr, attr_replicaRoot) == 0 || + strcasecmp (config_attr, attr_replicaName) == 0 || + strcasecmp (config_attr, attr_state) == 0) + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + sprintf (errortext, "modification of %s attribute is not allowed", + config_attr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + } + /* this is a request to delete an attribute */ + else if (mods[i]->mod_op & LDAP_MOD_DELETE || mods[i]->mod_bvalues == NULL + || mods[i]->mod_bvalues[0]->bv_val == NULL) + { + /* currently, you can only remove referral, + legacy consumer or bind dn attribute */ + if (strcasecmp (config_attr, attr_replicaBindDn) == 0) + { + *returncode = replica_config_change_updatedn (r, mods[i], errortext, apply_mods); + } + else if (strcasecmp (config_attr, attr_replicaReferral) == 0) + { + if (apply_mods) { + replica_set_referrals(r, NULL); + if (!replica_is_legacy_consumer (r)) { + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + } + } + else if (strcasecmp (config_attr, type_replicaLegacyConsumer) == 0) + { + if (apply_mods) + replica_set_legacy_consumer (r, PR_FALSE); + } + else + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + sprintf (errortext, "deletion of %s attribute is not allowed", config_attr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + } + } + else /* modify an attribute */ + { + config_attr_value = (char *) mods[i]->mod_bvalues[0]->bv_val; + + if (strcasecmp (config_attr, attr_replicaBindDn) == 0) + { + *returncode = replica_config_change_updatedn (r, mods[i], + errortext, apply_mods); + } + else if (strcasecmp (config_attr, attr_replicaType) == 0) + { + new_repl_type = slapi_ch_strdup(config_attr_value); + } + else if (strcasecmp (config_attr, attr_replicaId) == 0) + { + new_repl_id = slapi_ch_strdup(config_attr_value); + } + else if (strcasecmp (config_attr, attr_flags) == 0) + { + *returncode = replica_config_change_flags (r, config_attr_value, + errortext, apply_mods); + } + else if (strcasecmp (config_attr, TASK_ATTR) == 0) + { + *returncode = replica_execute_task (mtnode_ext->replica, config_attr_value, + errortext, apply_mods); + } + else if (strcasecmp (config_attr, attr_replicaReferral) == 0) + { + if (apply_mods) + { + Slapi_Mod smod; + Slapi_ValueSet *vs= slapi_valueset_new(); + slapi_mod_init_byref(&smod,mods[i]); + slapi_valueset_set_from_smod(vs, &smod); + replica_set_referrals (r, vs); + slapi_mod_done(&smod); + slapi_valueset_free(vs); + if (!replica_is_legacy_consumer (r)) { + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + } + } + else if (strcasecmp (config_attr, type_replicaPurgeDelay) == 0) + { + if (apply_mods && config_attr_value && config_attr_value[0]) + { + PRUint32 delay; + if (isdigit (config_attr_value[0])) + { + delay = (unsigned int)atoi(config_attr_value); + replica_set_purge_delay(r, delay); + } + else + *returncode = LDAP_OPERATIONS_ERROR; + } + } + else if (strcasecmp (config_attr, type_replicaTombstonePurgeInterval) == 0) + { + if (apply_mods && config_attr_value && config_attr_value[0]) + { + long interval; + interval = atol (config_attr_value); + replica_set_tombstone_reap_interval (r, interval); + } + } + else if (strcasecmp (config_attr, type_replicaLegacyConsumer) == 0) + { + if (apply_mods) + { + PRBool legacy = (strcasecmp (config_attr_value, "on") == 0) || + (strcasecmp (config_attr_value, "true") == 0) || + (strcasecmp (config_attr_value, "yes") == 0) || + (strcasecmp (config_attr_value, "1") == 0); + + replica_set_legacy_consumer (r, legacy); + } + } + /* ignore modifiers attributes added by the server */ + else if (strcasecmp (config_attr, "modifytimestamp") == 0 || + strcasecmp (config_attr, "modifiersname") == 0) + { + *returncode = LDAP_SUCCESS; + } + else + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + sprintf (errortext, "modification of attribute %s is not allowed in replica entry", config_attr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + } + } + } + + if (new_repl_id || new_repl_type) + { + *returncode = replica_config_change_type_and_id(r, new_repl_type, + new_repl_id, errortext, + apply_mods); + slapi_ch_free_string(&new_repl_id); + slapi_ch_free_string(&new_repl_type); + } + } + +done: + if (mtnode_ext->replica) + object_release (mtnode_ext->replica); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&replica_root); + + PR_Unlock (s_configLock); + + if (*returncode != LDAP_SUCCESS) + { + return SLAPI_DSE_CALLBACK_ERROR; + } + else + { + return SLAPI_DSE_CALLBACK_OK; + } +} + +static int +replica_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + multimaster_mtnode_extension *mtnode_ext; + Replica *r; + + PR_Lock (s_configLock); + + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + { + /* remove object from the hash */ + r = (Replica*)object_get_data (mtnode_ext->replica); + PR_ASSERT (r); + replica_delete_by_name (replica_get_name (r)); + object_release (mtnode_ext->replica); + mtnode_ext->replica = NULL; + } + + PR_Unlock (s_configLock); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +static int +replica_config_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, + char *returntext, void *arg) +{ + multimaster_mtnode_extension *mtnode_ext; + int changeCount = 0; + char val [64]; + + /* add attribute that contains number of entries in the changelog for this replica */ + + PR_Lock (s_configLock); + + /* if we have no changelog - we have no changes */ + if (cl5GetState () == CL5_STATE_OPEN) + { + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + { + object_acquire (mtnode_ext->replica); + changeCount = cl5GetOperationCount (mtnode_ext->replica); + object_release (mtnode_ext->replica); + } + } + + sprintf (val, "%d", changeCount); + slapi_entry_add_string (e, type_replicaChangeCount, val); + + PR_Unlock (s_configLock); + + return SLAPI_DSE_CALLBACK_OK; +} + +static int +replica_config_change_type_and_id (Replica *r, const char *new_type, + const char *new_id, char *returntext, + int apply_mods) +{ + int type; + ReplicaType oldtype; + ReplicaId rid; + ReplicaId oldrid; + + PR_ASSERT (r); + + oldtype = replica_get_type(r); + oldrid = replica_get_rid(r); + if (new_type == NULL) /* by default - replica is read-only */ + { + type = REPLICA_TYPE_READONLY; + } + else + { + type = atoi (new_type); + if (type <= REPLICA_TYPE_UNKNOWN || type >= REPLICA_TYPE_END) + { + sprintf (returntext, "invalid replica type %d", type); + return LDAP_OPERATIONS_ERROR; + } + } + + /* disallow changing type to itself just to permit a replica ID change */ + if (oldtype == type) + { + sprintf (returntext, "replica type is already %d - not changing", type); + return LDAP_OPERATIONS_ERROR; + } + + if (type == REPLICA_TYPE_READONLY) + { + rid = READ_ONLY_REPLICA_ID; /* default rid for read only */ + } + else if (!new_id) + { + sprintf(returntext, "a replica ID is required when changing replica type to read-write"); + return LDAP_UNWILLING_TO_PERFORM; + } + else + { + int temprid = atoi (new_id); + if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID) + { + sprintf(returntext, + "attribute %s must have a value greater than 0 " + "and less than %d", + attr_replicaId, READ_ONLY_REPLICA_ID); + return LDAP_UNWILLING_TO_PERFORM; + } + else + { + rid = (ReplicaId)temprid; + } + } + + /* error if old rid == new rid */ + if (oldrid == rid) + { + sprintf (returntext, "replica ID is already %d - not changing", rid); + return LDAP_OPERATIONS_ERROR; + } + + if (apply_mods) + { + replica_set_type (r, type); + replica_set_rid(r, rid); + + /* Set the mapping tree node, and the list of referrals */ + /* if this server is a 4.0 consumer the referrals are set by legacy plugin */ + if (!replica_is_legacy_consumer(r)) + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + + return LDAP_SUCCESS; +} + +static int +replica_config_change_updatedn (Replica *r, const LDAPMod *mod, char *returntext, + int apply_mods) +{ + PR_ASSERT (r); + + if (apply_mods) + { + Slapi_Mod smod; + Slapi_ValueSet *vs= slapi_valueset_new(); + slapi_mod_init_byref(&smod, (LDAPMod *)mod); /* cast away const */ + slapi_valueset_set_from_smod(vs, &smod); + replica_set_updatedn(r, vs, mod->mod_op); + slapi_mod_done(&smod); + slapi_valueset_free(vs); + } + + return LDAP_SUCCESS; +} + +static int replica_config_change_flags (Replica *r, const char *new_flags, + char *returntext, int apply_mods) +{ + PR_ASSERT (r); + + if (apply_mods) + { + PRUint32 flags; + + flags = atol (new_flags); + + replica_replace_flags (r, flags); + } + + return LDAP_SUCCESS; +} + +static int replica_execute_task (Object *r, const char *task_name, char *returntext, + int apply_mods) +{ + + if (strcasecmp (task_name, CL2LDIF_TASK) == 0) + { + if (apply_mods) + { + return replica_execute_cl2ldif_task (r, returntext); + } + else + return LDAP_SUCCESS; + } + else if (strncasecmp (task_name, CLEANRUV, CLEANRUVLEN) == 0) + { + int temprid = atoi(&(task_name[CLEANRUVLEN])); + if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID){ + sprintf(returntext, "Invalid replica id for task - %s", task_name); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + if (apply_mods) + { + return replica_execute_cleanruv_task (r, (ReplicaId)temprid, returntext); + } + else + return LDAP_SUCCESS; + } + else + { + sprintf (returntext, "unsupported replica task - %s", task_name); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + +} + +static int replica_execute_cl2ldif_task (Object *r, char *returntext) +{ + int rc; + Object *rlist [2]; + Replica *replica; + char fName [MAXPATHLEN]; + char *clDir; + + if (cl5GetState () != CL5_STATE_OPEN) + { + sprintf (returntext, "changelog is not open"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_cl2ldif_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + + rlist[0] = r; + rlist[1] = NULL; + + /* file is stored in the changelog directory and is named + <replica name>.ldif */ + clDir = cl5GetDir (); + PR_ASSERT (clDir); + + replica = (Replica*)object_get_data (r); + PR_ASSERT (replica); + + sprintf (fName, "%s/%s.ldif", clDir, replica_get_name (replica)); + slapi_ch_free ((void**)&clDir); + + rc = cl5ExportLDIF (fName, rlist); + if (rc != CL5_SUCCESS) + { + sprintf (returntext, "failed to export changelog data to file %s; " + "changelog error - %d", fName, rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_cl2ldif_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + + return LDAP_SUCCESS; +} + +static multimaster_mtnode_extension * +_replica_config_get_mtnode_ext (const Slapi_Entry *e) +{ + const char *replica_root; + Slapi_DN *sdn = NULL; + mapping_tree_node *mtnode; + multimaster_mtnode_extension *ext = NULL; + char ebuf[BUFSIZ]; + + /* retirve root of the tree for which replica is configured */ + replica_root = slapi_entry_attr_get_charptr (e, attr_replicaRoot); + if (replica_root == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: " + "configuration entry %s missing %s attribute\n", + escape_string(slapi_entry_get_dn((Slapi_Entry *)e), ebuf), + attr_replicaRoot); + return NULL; + } + + sdn = slapi_sdn_new_dn_passin (replica_root); + + /* locate mapping tree node for the specified subtree */ + mtnode = slapi_get_mapping_tree_node_by_dn (sdn); + if (mtnode == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: " + "failed to locate mapping tree node for dn %s\n", + escape_string(slapi_sdn_get_dn(sdn), ebuf)); + } + else + { + /* check if replica object already exists for the specified subtree */ + ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode); + } + + slapi_sdn_free (&sdn); + + return ext; +} + +static int +replica_execute_cleanruv_task (Object *r, ReplicaId rid, char *returntext) +{ + int rc = 0; + Object *RUVObj; + RUV *local_ruv = NULL; + Replica *replica = (Replica*)object_get_data (r); + + PR_ASSERT (replica); + + RUVObj = replica_get_ruv(replica); + PR_ASSERT(RUVObj); + local_ruv = (RUV*)object_get_data (RUVObj); + /* Need to check that : + * - rid is not the local one + * - rid is not the last one + */ + if ((replica_get_rid(replica) == rid) || + (ruv_replica_count(local_ruv) <= 1)) { + return LDAP_UNWILLING_TO_PERFORM; + } + rc = ruv_delete_replica(local_ruv, rid); + replica_set_ruv_dirty(replica); + replica_write_ruv(replica); + object_release(RUVObj); + + /* Update Mapping Tree to reflect RUV changes */ + consumer5_set_mapping_tree_state_for_replica(replica, NULL); + + if (rc != RUV_SUCCESS){ + return LDAP_OPERATIONS_ERROR; + } + return LDAP_SUCCESS; +} + + diff --git a/ldap/servers/plugins/replication/repl5_replica_dnhash.c b/ldap/servers/plugins/replication/repl5_replica_dnhash.c new file mode 100644 index 00000000..4b9f42b9 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica_dnhash.c @@ -0,0 +1,189 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_replica_dnhash.c */ + +#include "repl5.h" +#include "plhash.h" + +/* global data */ +static PLHashTable *s_hash; +static PRRWLock *s_lock; + +/* Forward declarations */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg); + +int replica_init_dn_hash () +{ + /* allocate table */ + s_hash = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, + PL_CompareValues, NULL, NULL); + if (s_hash == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_dn_hash: " + "failed to allocate hash table; NSPR error - %d\n", + PR_GetError ()); + return -1; + } + + /* create lock */ + s_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "replica_dnhash_lock"); + if (s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_dn_hash: " + "failed to create lock; NSPR error - %d\n", + PR_GetError ()); + replica_destroy_dn_hash (); + return -1; + } + + return 0; +} + +void replica_destroy_dn_hash () +{ + /* destroy the content */ + PL_HashTableEnumerateEntries(s_hash, replica_destroy_hash_entry, NULL); + + if (s_hash) + PL_HashTableDestroy(s_hash); + + if (s_lock) + PR_DestroyRWLock (s_lock); +} + +int replica_add_by_dn (const char *dn) +{ + char *dn_copy = NULL; + + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* make sure that the dn is unique */ + if (PL_HashTableLookup(s_hash, dn) != NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: " + "replica with dn (%s) already in the hash\n", dn); + PR_RWLock_Unlock (s_lock); + return -1 ; + } + + /* add dn */ + dn_copy = slapi_ch_strdup(dn); + if (PL_HashTableAdd(s_hash, dn_copy, dn_copy) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: " + "failed to add dn (%s); NSPR error - %d\n", + dn_copy, PR_GetError ()); + slapi_ch_free((void **)&dn_copy); + PR_RWLock_Unlock (s_lock); + return -1; + } + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_add_by_dn: " + "added dn (%s)\n", + dn_copy); + PR_RWLock_Unlock (s_lock); + return 0; +} + +int replica_delete_by_dn (const char *dn) +{ + char *dn_copy = NULL; + + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: " + "NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* locate object */ + if (NULL == (dn_copy = (char *)PL_HashTableLookup(s_hash, dn))) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: " + "dn (%s) is not in the hash.\n", dn); + PR_RWLock_Unlock (s_lock); + return -1; + } + + /* remove from hash */ + PL_HashTableRemove(s_hash, dn); + slapi_ch_free((void **)&dn_copy); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_delete_by_dn: " + "removed dn (%s)\n", + dn); + PR_RWLock_Unlock (s_lock); + + return 0; +} + +int replica_is_being_configured (const char *dn) +{ + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_is_dn_in_hash: " + "NULL argument\n"); + return 0; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_is_dn_in_hash: " + "dn hash is not initialized\n"); + return 0; + } + + PR_RWLock_Wlock (s_lock); + + /* locate object */ + if (NULL == PL_HashTableLookup(s_hash, dn)) + { + PR_RWLock_Unlock (s_lock); + return 0; + } + + PR_RWLock_Unlock (s_lock); + + return 1; +} + +/* Helper functions */ + +/* this function called for each hash node during hash destruction */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg) +{ + char *dn_copy; + + if (he == NULL) + return HT_ENUMERATE_NEXT; + + dn_copy = (char*)he->value; + slapi_ch_free((void **)&dn_copy); + + return HT_ENUMERATE_REMOVE; +} diff --git a/ldap/servers/plugins/replication/repl5_replica_hash.c b/ldap/servers/plugins/replication/repl5_replica_hash.c new file mode 100644 index 00000000..92ea87f4 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica_hash.c @@ -0,0 +1,243 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_replica_hash.c */ + +#include "repl5.h" +#include "plhash.h" + +/* global data */ +static PLHashTable *s_hash; +static PRRWLock *s_lock; + +struct repl_enum_data +{ + FNEnumReplica fn; + void *arg; +}; + +/* Forward declarations */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg); +static PRIntn replica_enumerate (PLHashEntry *he, PRIntn index, void *hash_data); + + +int replica_init_name_hash () +{ + /* allocate table */ + s_hash = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, + PL_CompareValues, NULL, NULL); + if (s_hash == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_name_hash: " + "failed to allocate hash table; NSPR error - %d\n", + PR_GetError ()); + return -1; + } + + /* create lock */ + s_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "replica_hash_lock"); + if (s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_name_hash: " + "failed to create lock; NSPR error - %d\n", + PR_GetError ()); + replica_destroy_name_hash (); + return -1; + } + + return 0; +} + +void replica_destroy_name_hash () +{ + /* destroy the content */ + PL_HashTableEnumerateEntries(s_hash, replica_destroy_hash_entry, NULL); + + if (s_hash) + PL_HashTableDestroy(s_hash); + + if (s_lock) + PR_DestroyRWLock (s_lock); +} + +int replica_add_by_name (const char *name, Object *replica) +{ + if (name == NULL || replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* make sure that the name is unique */ + if (PL_HashTableLookup(s_hash, name) != NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: " + "replica with name (%s) already in the hash\n", name); + PR_RWLock_Unlock (s_lock); + return -1 ; + } + + /* acquire replica object */ + object_acquire (replica); + + /* add replica */ + if (PL_HashTableAdd(s_hash, name, replica) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: " + "failed to add replica with name (%s); NSPR error - %d\n", + name, PR_GetError ()); + object_release (replica); + PR_RWLock_Unlock (s_lock); + return -1; + } + + PR_RWLock_Unlock (s_lock); + return 0; +} + +int replica_delete_by_name (const char *name) +{ + Object *replica; + + if (name == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: " + "NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* locate object */ + replica = (Object*)PL_HashTableLookup(s_hash, name); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: " + "replica with name (%s) is not in the hash.\n", name); + PR_RWLock_Unlock (s_lock); + return -1; + } + + /* remove from hash */ + PL_HashTableRemove(s_hash, name); + + /* release replica */ + object_release (replica); + + PR_RWLock_Unlock (s_lock); + + return 0; +} + +Object* replica_get_by_name (const char *name) +{ + Object *replica; + + if (name == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_get_by_name: " + "NULL argument\n"); + return NULL; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_get_by_name: " + "replica hash is not initialized\n"); + return NULL; + } + + PR_RWLock_Rlock (s_lock); + + /* locate object */ + replica = (Object*)PL_HashTableLookup(s_hash, name); + if (replica == NULL) + { + PR_RWLock_Unlock (s_lock); + return NULL; + } + + object_acquire (replica); + + PR_RWLock_Unlock (s_lock); + + return replica; +} + +void replica_enumerate_replicas (FNEnumReplica fn, void *arg) +{ + struct repl_enum_data data; + + PR_ASSERT (fn); + + data.fn = fn; + data.arg = arg; + + PR_RWLock_Wlock (s_lock); + PL_HashTableEnumerateEntries(s_hash, replica_enumerate, &data); + PR_RWLock_Unlock (s_lock); +} + +/* Helper functions */ + +/* this function called for each hash node during hash destruction */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg) +{ + Object *r_obj; + Replica *r; + + if (he == NULL) + return HT_ENUMERATE_NEXT; + + r_obj = (Object*)he->value; + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + /* flash replica state to the disk */ + replica_flush (r); + + /* release replica object */ + object_release (r_obj); + + return HT_ENUMERATE_REMOVE; +} + +static PRIntn replica_enumerate (PLHashEntry *he, PRIntn index, void *hash_data) +{ + Object *r_obj; + Replica *r; + struct repl_enum_data *data = hash_data; + + r_obj = (Object*)he->value; + PR_ASSERT (r_obj); + + object_acquire (r_obj); + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + data->fn (r, data->arg); + + object_release (r_obj); + + return HT_ENUMERATE_NEXT; +} + diff --git a/ldap/servers/plugins/replication/repl5_replsupplier.c b/ldap/servers/plugins/replication/repl5_replsupplier.c new file mode 100644 index 00000000..e98d0e58 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replsupplier.c @@ -0,0 +1,166 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_replsupplier.c */ +/* + +A replsupplier is an object that knows how to manage outbound replication +for one consumer. + +Methods: +init() +configure() +start() +stop() +destroy() +status() +notify() + +*/ + +#include "slapi-plugin.h" +#include "repl5.h" + +typedef struct repl_supplier { + PRUint32 client_change_count; /* # of client-supplied changes */ + PRUint32 repl_change_count; /* # of replication updates */ + + PRLock *lock; + +} repl_supplier; + + +static void repl_supplier_free(Repl_Supplier **rsp); + +/* + * Create and initialize this replsupplier object. + */ +Repl_Supplier * +replsupplier_init(Slapi_Entry *e) +{ + Repl_Supplier *rs; + + if ((rs = (Repl_Supplier *)slapi_ch_malloc(sizeof(Repl_Supplier))) == NULL) + { + goto loser; + } + if ((rs->lock = PR_NewLock()) == NULL) + { + goto loser; + } + return rs; + +loser: + repl_supplier_free(&rs); + return NULL; +} + + + +static void +repl_supplier_free(Repl_Supplier **rsp) +{ + if (NULL != rsp) + { + Repl_Supplier *rs = *rsp; + if (NULL != rs) + { + if (NULL != rs->lock) + { + PR_DestroyLock(rs->lock); + rs->lock = NULL; + } + slapi_ch_free((void **)rsp); + } + } +} + + + +/* + * Configure a repl_supplier object. + */ +void +replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb) +{ + PR_ASSERT(NULL != rs); + +} + + + +/* + * Start a repl_supplier object. This means that it's ok for + * the repl_supplier to begin normal replication duties. It does + * not necessarily mean that a replication session will occur + * immediately. + */ +void +replsupplier_start(Repl_Supplier *rs) +{ + PR_ASSERT(NULL != rs); +} + + + + +/* + * Stop a repl_supplier object. This causes any active replication + * sessions to be stopped ASAP, and puts the repl_supplier into a + * stopped state. No additional replication activity will occur + * until the replsupplier_start() function is called. + */ +void +replsupplier_stop(Repl_Supplier *rs) +{ + PR_ASSERT(NULL != rs); +} + + + + +/* + * Destroy a repl_supplier object. The object will be stopped, if it + * is not already stopped. + */ +void +replsupplier_destroy(Repl_Supplier **rsp) +{ + Repl_Supplier *rs; + + PR_ASSERT(NULL != rsp && NULL != *rsp); + + rs = *rsp; + + slapi_ch_free((void **)rsp); +} + + + +/* + * This method should be called by the repl_bos whenever it determines + * that a change to the replicated area serviced by this repl_supplier + * has occurred. This gives the repl_supplier a chance to implement a + * scheduling policy. + */ +void +replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask) +{ + PR_ASSERT(NULL != rs); +} + + + +/* + * This method is used to obtain the status of this particular + * repl_supplier object. Eventually it will return some object containing + * status information. For now, it's just a placeholder function. + */ +PRUint32 +replsupplier_get_status(Repl_Supplier *rs) +{ + PR_ASSERT(NULL != rs); + return 0; +} diff --git a/ldap/servers/plugins/replication/repl5_ruv.c b/ldap/servers/plugins/replication/repl5_ruv.c new file mode 100644 index 00000000..b8cb79c9 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_ruv.c @@ -0,0 +1,2022 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_ruv.c - implementation of replica update vector */ +/* + * The replica update vector is stored in the nsds50ruv attribute. The LDIF + * representation of the ruv is: + * nsds50ruv: {replicageneration} <gen-id-for-this-replica> + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>] + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>] + * ... + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>] + * + * nsruvReplicaLastModified: {replica <rid>[ <url>]} lastModifiedTime + * nsruvReplicaLastModified: {replica <rid>[ <url>]} lastModifiedTime + * ... + * + * For readability, ruv_dump appends nsruvReplicaLastModified to nsds50ruv: + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn> [<lastModifiedTime>]] + */ + +#include <string.h> +#include <ctype.h> /* For isdigit() */ +#include "csnpl.h" +#include "repl5_ruv.h" +#include "repl_shared.h" +#include "repl5.h" + +#define RIDSTR_SIZE 16 /* string large enough to hold replicaid*/ +#define RUVSTR_SIZE 256 /* string large enough to hold ruv and lastmodifiedtime */ + +/* Data Structures */ + +/* replica */ +typedef struct ruvElement +{ + ReplicaId rid; /* replica id for this element */ + CSN *csn; /* largest csn that we know about that originated at the master */ + CSN *min_csn; /* smallest csn that originated at the master */ + char *replica_purl; /* Partial URL for replica */ + CSNPL *csnpl; /* list of operations in progress */ + time_t last_modified; /* timestamp the modification of csn */ +} RUVElement; + +/* replica update vector */ +struct _ruv +{ + char *replGen; /* replicated area generation: identifies replica + in space and in time */ + DataList *elements; /* replicas */ + PRRWLock *lock; /* concurrency control */ +}; + +/* forward declarations */ +static int ruvInit (RUV **ruv, int initCount); +static void ruvFreeReplica (void **data); +static RUVElement* ruvGetReplica (const RUV *ruv, ReplicaId rid); +static RUVElement* ruvAddReplica (RUV *ruv, const CSN *csn, const char *replica_purl); +static RUVElement* ruvAddReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl); +static RUVElement* ruvAddIndexReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl, int index); +static int ruvReplicaCompare (const void *el1, const void *el2); +static RUVElement *get_ruvelement_from_berval(const struct berval *bval); +static char *get_replgen_from_berval(const struct berval *bval); + +static const char * const prefix_replicageneration = "{replicageneration}"; +static const char * const prefix_ruvcsn = "{replica "; /* intentionally missing '}' */ + + +/* API implementation */ + + +/* + * Allocate a new ruv and set its replica generation to the given generation. + */ +int +ruv_init_new(const char *replGen, ReplicaId rid, const char *purl, RUV **ruv) +{ + int rc; + RUVElement *replica; + + if (ruv == NULL || replGen == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_init_new: NULL argument\n"); + return RUV_BAD_DATA; + } + + rc = ruvInit (ruv, 0); + if (rc != RUV_SUCCESS) + return rc; + + (*ruv)->replGen = slapi_ch_strdup (replGen); + + /* we want to add the local writable replica to the RUV before any csns are created */ + /* this is so that it can be referred to even before it accepted any changes */ + if (purl) + { + replica = ruvAddReplicaNoCSN (*ruv, rid, purl); + + if (replica == NULL) + return RUV_MEMORY_ERROR; + } + + return RUV_SUCCESS; +} + + +/* + * Create a new RUV and initialize its contents from the provided Slapi_Attr. + * Returns: + * RUV_BAD_DATA if the values in the attribute were malformed. + * RUV_SUCCESS if all went well + */ +int +ruv_init_from_slapi_attr(Slapi_Attr *attr, RUV **ruv) +{ + ReplicaId dummy = 0; + + return (ruv_init_from_slapi_attr_and_check_purl(attr, ruv, &dummy)); +} + +/* + * Create a new RUV and initialize its contents from the provided Slapi_Attr. + * Returns: + * RUV_BAD_DATA if the values in the attribute were malformed. + * RUV_SUCCESS if all went well + * contain_purl is 0 if the ruv doesn't contain the local purl + * contain_purl is != 0 if the ruv contains the local purl (contains the rid) + */ +int +ruv_init_from_slapi_attr_and_check_purl(Slapi_Attr *attr, RUV **ruv, ReplicaId *contain_purl) +{ + int return_value; + + PR_ASSERT(NULL != attr && NULL != ruv); + + if (NULL == ruv || NULL == attr) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_attr: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + int rc; + int numvalues; + slapi_attr_get_numvalues(attr, &numvalues); + if ((rc = ruvInit(ruv, numvalues)) != RUV_SUCCESS) + { + return_value = rc; + } + else + { + int hint; + Slapi_Value *value; + const struct berval *bval; + const char *purl = NULL; + + return_value = RUV_SUCCESS; + + purl = multimaster_get_local_purl(); + *contain_purl = 0; + + for (hint = slapi_attr_first_value(attr, &value); + hint != -1; hint = slapi_attr_next_value(attr, hint, &value)) + { + bval = slapi_value_get_berval(value); + if (NULL != bval && NULL != bval->bv_val) + { + if (strncmp(bval->bv_val, prefix_replicageneration, strlen(prefix_replicageneration)) == 0) { + if (NULL == (*ruv)->replGen) + { + (*ruv)->replGen = get_replgen_from_berval(bval); + } else { + /* Twice replicageneration is wrong, just log and ignore */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_attr: %s is present more than once\n", + prefix_replicageneration); + } + } + else + { + RUVElement *ruve = get_ruvelement_from_berval(bval); + if (NULL != ruve) + { + /* Is the local purl already in the ruv ? */ + if ( (*contain_purl==0) && (strncmp(ruve->replica_purl, purl, strlen(purl))==0) ) + { + *contain_purl = ruve->rid; + } + dl_add ((*ruv)->elements, ruve); + } + } + } + } + } + } + return return_value; +} + + + +/* + * Same as ruv_init_from_slapi_attr, but takes an array of pointers to bervals. + * I wish this wasn't a cut-n-paste of the above function, but I don't see a + * clean way to define one API in terms of the other. + */ +int +ruv_init_from_bervals(struct berval **vals, RUV **ruv) +{ + int return_value; + + PR_ASSERT(NULL != vals && NULL != ruv); + + if (NULL == ruv || NULL == vals) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_value: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + int i, rc; + i = 0; + while (vals[i] != NULL) + { + i++; + } + if ((rc = ruvInit (ruv, i)) != RUV_SUCCESS) + { + return_value = rc; + } + else + { + return_value = RUV_SUCCESS; + for (i = 0; NULL != vals[i]; i++) + { + if (NULL != vals[i]->bv_val) + { + if (strncmp(vals[i]->bv_val, prefix_replicageneration, strlen(prefix_replicageneration)) == 0) { + if (NULL == (*ruv)->replGen) + { + (*ruv)->replGen = get_replgen_from_berval(vals[i]); + } else { + /* Twice replicageneration is wrong, just log and ignore */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_value: %s is present more than once\n", + prefix_replicageneration); + } + } + else + { + RUVElement *ruve = get_ruvelement_from_berval(vals[i]); + if (NULL != ruve) + { + dl_add ((*ruv)->elements, ruve); + } + } + } + } + } + } + return return_value; +} + + + +RUV* +ruv_dup (const RUV *ruv) +{ + int rc; + RUVElement *replica, *dupReplica; + int cookie; + RUV *dupRUV = NULL; + + if (ruv == NULL) + return NULL; + + PR_RWLock_Rlock (ruv->lock); + + rc = ruvInit (&dupRUV, dl_get_count (ruv->elements)); + if (rc != RUV_SUCCESS || dupRUV == NULL) + goto done; + + dupRUV->replGen = slapi_ch_strdup (ruv->replGen); + + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + dupReplica = (RUVElement *)slapi_ch_calloc (1, sizeof (*dupReplica)); + dupReplica->rid = replica->rid; + if (replica->csn) + dupReplica->csn = csn_dup (replica->csn); + if (replica->min_csn) + dupReplica->min_csn = csn_dup (replica->min_csn); + if (replica->replica_purl) + dupReplica->replica_purl = slapi_ch_strdup (replica->replica_purl); + dupReplica->last_modified = replica->last_modified; + + /* ONREPL - we don't make copy of the pernding list. For now + we don't need it. */ + + dl_add (dupRUV->elements, dupReplica); + } + +done: + PR_RWLock_Unlock (ruv->lock); + + return dupRUV; +} + +void +ruv_destroy (RUV **ruv) +{ + if (ruv != NULL && *ruv != NULL) + { + if ((*ruv)->elements) + { + dl_cleanup ((*ruv)->elements, ruvFreeReplica); + dl_free (&(*ruv)->elements); + } + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void **)&((*ruv)->replGen)); + + if ((*ruv)->lock) + { + PR_DestroyRWLock ((*ruv)->lock); + } + + slapi_ch_free ((void**)ruv); + } +} + +/* + * [610948] + * copy elements in srcruv to destruv + * destruv is the live wrapper, which could be referred by other threads. + * srcruv is cleaned up after copied. + */ +void +ruv_copy_and_destroy (RUV **srcruv, RUV **destruv) +{ + DataList *elemp = NULL; + char *replgp = NULL; + + if (NULL == srcruv || NULL == *srcruv || NULL == destruv) + { + return; + } + + if (NULL == *destruv) + { + *destruv = *srcruv; + *srcruv = NULL; + } + else + { + PR_RWLock_Wlock((*destruv)->lock); + elemp = (*destruv)->elements; + (*destruv)->elements = (*srcruv)->elements; + if (elemp) + { + dl_cleanup (elemp, ruvFreeReplica); + dl_free (&elemp); + } + + /* slapi_ch_free accepts NULL pointer */ + replgp = (*destruv)->replGen; + (*destruv)->replGen = (*srcruv)->replGen; + slapi_ch_free ((void **)&replgp); + + if ((*srcruv)->lock) + { + PR_DestroyRWLock ((*srcruv)->lock); + } + slapi_ch_free ((void**)srcruv); + + PR_RWLock_Unlock((*destruv)->lock); + } + PR_ASSERT (*destruv != NULL && *srcruv == NULL); +} + +int +ruv_delete_replica (RUV *ruv, ReplicaId rid) +{ + int return_value; + if (ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_delete_replica: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + /* check for duplicates */ + PR_RWLock_Wlock (ruv->lock); + dl_delete (ruv->elements, (const void*)&rid, ruvReplicaCompare, ruvFreeReplica); + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_add_replica (RUV *ruv, ReplicaId rid, const char *replica_purl) +{ + RUVElement* replica; + + PR_ASSERT (ruv && replica_purl); + + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) + { + replica = ruvAddReplicaNoCSN (ruv, rid, replica_purl); + } + + PR_RWLock_Unlock (ruv->lock); + + if (replica) + return RUV_SUCCESS; + else + return RUV_MEMORY_ERROR; +} + +int +ruv_replace_replica_purl (RUV *ruv, ReplicaId rid, const char *replica_purl) +{ + RUVElement* replica; + int rc = RUV_NOTFOUND; + + PR_ASSERT (ruv && replica_purl); + + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + if (replica != NULL) + { + slapi_ch_free((void **)&(replica->replica_purl)); + replica->replica_purl = slapi_ch_strdup(replica_purl); + rc = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int +ruv_add_index_replica (RUV *ruv, ReplicaId rid, const char *replica_purl, int index) +{ + RUVElement* replica; + + PR_ASSERT (ruv && replica_purl); + + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) + { + replica = ruvAddIndexReplicaNoCSN (ruv, rid, replica_purl, index); + } + + PR_RWLock_Unlock (ruv->lock); + + if (replica) + return RUV_SUCCESS; + else + return RUV_MEMORY_ERROR; +} + + +PRBool +ruv_contains_replica (const RUV *ruv, ReplicaId rid) +{ + RUVElement *replica; + + if (ruv == NULL) + return PR_FALSE; + + PR_RWLock_Rlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + PR_RWLock_Unlock (ruv->lock); + + return replica != NULL; +} + + + + +#define GET_LARGEST_CSN 231 +#define GET_SMALLEST_CSN 232 +static int +get_csn_internal(const RUV *ruv, ReplicaId rid, CSN **csn, int whichone) +{ + RUVElement *replica; + int return_value = RUV_SUCCESS; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_get_largest_csn_for_replica: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + *csn = NULL; + /* prevent element from being destroyed while we get its data */ + PR_RWLock_Rlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + /* replica without min csn is treated as a non-existent replica */ + if (replica == NULL || replica->min_csn == NULL) + { + return_value = RUV_NOTFOUND; + } + else + { + switch (whichone) + { + case GET_LARGEST_CSN: + *csn = replica->csn ? csn_dup (replica->csn) : NULL; + break; + case GET_SMALLEST_CSN: + *csn = replica->min_csn ? csn_dup (replica->min_csn) : NULL; + break; + default: + *csn = NULL; + } + } + PR_RWLock_Unlock (ruv->lock); + } + return return_value; +} + + +int +ruv_get_largest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn) +{ + return get_csn_internal(ruv, rid, csn, GET_LARGEST_CSN); +} + +int +ruv_get_smallest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn) +{ + return get_csn_internal(ruv, rid, csn, GET_SMALLEST_CSN); +} + +const char * +ruv_get_purl_for_replica(const RUV *ruv, ReplicaId rid) +{ + RUVElement *replica; + const char *return_value = NULL; + + PR_RWLock_Rlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + if (replica != NULL) + { + return_value = replica->replica_purl; + } + + PR_RWLock_Unlock (ruv->lock); + + return return_value; +} + + +static int +set_min_csn_nolock(RUV *ruv, const CSN *min_csn, const char *replica_purl) +{ + int return_value; + ReplicaId rid = csn_get_replicaid (min_csn); + RUVElement *replica = ruvGetReplica (ruv, rid); + if (NULL == replica) + { + replica = ruvAddReplica (ruv, min_csn, replica_purl); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (replica->min_csn == NULL || csn_compare (min_csn, replica->min_csn) < 0) + { + csn_free(&replica->min_csn); + replica->min_csn = csn_dup(min_csn); + } + + return_value = RUV_SUCCESS; + } + + return return_value; +} + +static int +set_max_csn_nolock(RUV *ruv, const CSN *max_csn, const char *replica_purl) +{ + int return_value; + ReplicaId rid = csn_get_replicaid (max_csn); + RUVElement *replica = ruvGetReplica (ruv, rid); + if (NULL == replica) + { + replica = ruvAddReplica (ruv, max_csn, replica_purl); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (replica_purl && replica->replica_purl == NULL) + replica->replica_purl = slapi_ch_strdup (replica_purl); + csn_free(&replica->csn); + replica->csn = csn_dup(max_csn); + replica->last_modified = current_time(); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_set_min_csn(RUV *ruv, const CSN *min_csn, const char *replica_purl) +{ + int return_value; + PR_RWLock_Wlock (ruv->lock); + return_value = set_min_csn_nolock(ruv, min_csn, replica_purl); + PR_RWLock_Unlock (ruv->lock); + return return_value; +} + + +int +ruv_set_max_csn(RUV *ruv, const CSN *max_csn, const char *replica_purl) +{ + int return_value; + PR_RWLock_Wlock (ruv->lock); + return_value = set_max_csn_nolock(ruv, max_csn, replica_purl); + PR_RWLock_Unlock (ruv->lock); + return return_value; +} + +int +ruv_set_csns(RUV *ruv, const CSN *csn, const char *replica_purl) +{ + RUVElement *replica; + ReplicaId rid; + int return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_set_csns: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + rid = csn_get_replicaid (csn); + + /* prevent element from being destroyed while we get its data */ + PR_RWLock_Wlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) /* add new replica */ + { + replica = ruvAddReplica (ruv, csn, replica_purl); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (csn_compare (csn, replica->csn) > 0) + { + if (replica->csn != NULL) + { + csn_init_by_csn ( replica->csn, csn ); + } + else + { + replica->csn = csn_dup(csn); + } + replica->last_modified = current_time(); + if (replica_purl && (NULL == replica->replica_purl || + strcmp(replica->replica_purl, replica_purl) != 0)) + { + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&replica->replica_purl); + + replica->replica_purl = slapi_ch_strdup(replica_purl); + } + } + /* XXXggood only need to worry about this if real min csn not committed to changelog yet */ + if (csn_compare (csn, replica->min_csn) < 0) + { + csn_free(&replica->min_csn); + replica->min_csn = csn_dup(csn); + } + return_value = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + } + return return_value; +} + +/* This function, for each replica keeps the smallest CSN its seen so far. + Used for initial setup of changelog purge vector */ + +int +ruv_set_csns_keep_smallest(RUV *ruv, const CSN *csn) +{ + RUVElement *replica; + ReplicaId rid; + int return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_set_csns_keep_smallest: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + rid = csn_get_replicaid (csn); + + /* prevent element from being destroyed while we get its data */ + PR_RWLock_Wlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) /* add new replica */ + { + replica = ruvAddReplica (ruv, csn, NULL); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (csn_compare (csn, replica->csn) < 0) + { + csn_free(&replica->csn); + replica->csn = csn_dup(csn); + replica->last_modified = current_time(); + } + + return_value = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + } + return return_value; +} + + +void +ruv_set_replica_generation(RUV *ruv, const char *csnstr) +{ + if (NULL != csnstr && NULL != ruv) + { + PR_RWLock_Wlock (ruv->lock); + + if (NULL != ruv->replGen) + { + slapi_ch_free((void **)&ruv->replGen); + } + ruv->replGen = slapi_ch_strdup(csnstr); + + PR_RWLock_Unlock (ruv->lock); + } +} + + +char * +ruv_get_replica_generation(const RUV *ruv) +{ + char *return_str = NULL; + + PR_RWLock_Rlock (ruv->lock); + + if (ruv != NULL && ruv->replGen != NULL) + { + return_str = slapi_ch_strdup(ruv->replGen); + } + + PR_RWLock_Unlock (ruv->lock); + + return return_str; +} + +static PRBool +ruv_covers_csn_internal(const RUV *ruv, const CSN *csn, PRBool strict) +{ + RUVElement *replica; + ReplicaId rid; + PRBool return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_covers_csn: NULL argument\n"); + return_value = PR_FALSE; + } + else + { + rid = csn_get_replicaid(csn); + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_covers_csn: replica for id %d not found\n", rid); + return_value = PR_FALSE; + } + else + { + if (strict) + { + return_value = (csn_compare (csn, replica->csn) < 0); + } + else + { + return_value = (csn_compare (csn, replica->csn) <= 0); + } + } + } + return return_value; +} + +PRBool +ruv_covers_csn(const RUV *ruv, const CSN *csn) +{ + PRBool rc; + + PR_RWLock_Rlock (ruv->lock); + rc = ruv_covers_csn_internal(ruv, csn, PR_FALSE); + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + +PRBool +ruv_covers_csn_strict(const RUV *ruv, const CSN *csn) +{ + PRBool rc; + + PR_RWLock_Rlock (ruv->lock); + rc = ruv_covers_csn_internal(ruv, csn, PR_TRUE); + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + + +/* + * The function gets min{maxcsns of all ruv elements} if get_the_max=0, + * or max{maxcsns of all ruv elements} if get_the_max != 0. + */ +static int +ruv_get_min_or_max_csn(const RUV *ruv, CSN **csn, int get_the_max) +{ + int return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_get_min_or_max_csn: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + CSN *found = NULL; + RUVElement *replica; + int cookie; + PR_RWLock_Rlock (ruv->lock); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + /* + * Skip replica whose maxcsn is NULL otherwise + * the code will return different min_csn if + * the sequence of the replicas is altered. + * + * don't use READ_ONLY replicas for computing the value of + * "found", as they seem to have NULL csn and min_csn + */ + if (replica->csn == NULL || replica->rid == READ_ONLY_REPLICA_ID) + { + continue; + } + + if (found == NULL || + (!get_the_max && csn_compare(found, replica->csn)>0) || + ( get_the_max && csn_compare(found, replica->csn)<0)) + { + found = replica->csn; + } + } + if (found == NULL) + { + *csn = NULL; + } + else + { + *csn = csn_dup (found); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_get_max_csn(const RUV *ruv, CSN **csn) +{ + return ruv_get_min_or_max_csn(ruv, csn, 1 /* get the max */); +} + +int +ruv_get_min_csn(const RUV *ruv, CSN **csn) +{ + return ruv_get_min_or_max_csn(ruv, csn, 0 /* get the min */); +} + +int +ruv_enumerate_elements (const RUV *ruv, FNEnumRUV fn, void *arg) +{ + int cookie; + RUVElement *elem; + int rc = 0; + ruv_enum_data enum_data = {0}; + + if (ruv == NULL || fn == NULL) + { + /* ONREPL - log error */ + return -1; + } + + PR_RWLock_Rlock (ruv->lock); + for (elem = (RUVElement*)dl_get_first (ruv->elements, &cookie); elem; + elem = (RUVElement*)dl_get_next (ruv->elements, &cookie)) + { + /* we only return elements that contains both minimal and maximal CSNs */ + if (elem->csn && elem->min_csn) + { + enum_data.csn = elem->csn; + enum_data.min_csn = elem->min_csn; + rc = fn (&enum_data, arg); + if (rc != 0) + break; + } + } + + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + +/* + * Convert a replica update vector to a NULL-terminated array + * of bervals. The caller is responsible for freeing the bervals. + */ +int +ruv_to_bervals(const RUV *ruv, struct berval ***bvals) +{ + struct berval **returned_bervals = NULL; + int return_value; + if (ruv == NULL || bvals == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_to_bervals: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + int count; + int i; + RUVElement *replica; + char csnStr1 [CSN_STRSIZE]; + char csnStr2 [CSN_STRSIZE]; + int cookie; + PR_RWLock_Rlock (ruv->lock); + count = dl_get_count (ruv->elements) + 2; + returned_bervals = (struct berval **)slapi_ch_malloc(sizeof(struct berval *) * count); + returned_bervals[count - 1] = NULL; + returned_bervals[0] = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + returned_bervals[0]->bv_val = slapi_ch_malloc(strlen(prefix_replicageneration) + strlen(ruv->replGen) + 2); + sprintf(returned_bervals[0]->bv_val, "%s %s", + prefix_replicageneration, ruv->replGen); + returned_bervals[0]->bv_len = strlen(returned_bervals[0]->bv_val); + for (i = 1, replica = dl_get_first (ruv->elements, &cookie); replica; + i++, replica = dl_get_next (ruv->elements, &cookie)) + { + returned_bervals[i] = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + returned_bervals[i]->bv_val = slapi_ch_malloc(strlen(prefix_ruvcsn) + RIDSTR_SIZE + + ((replica->replica_purl == NULL) ? 0 : strlen(replica->replica_purl)) + + ((replica->min_csn == NULL) ? 0 : CSN_STRSIZE) + + ((replica->csn == NULL) ? 0 : CSN_STRSIZE) + 5); + sprintf(returned_bervals[i]->bv_val, "%s%d%s%s}%s%s%s%s", + prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + replica->min_csn == NULL ? "" : " ", + replica->min_csn == NULL ? "" : csn_as_string (replica->min_csn, PR_FALSE, csnStr1), + replica->csn == NULL ? "" : " ", + replica->csn == NULL ? "" : csn_as_string (replica->csn, PR_FALSE, csnStr2)); + returned_bervals[i]->bv_len = strlen(returned_bervals[i]->bv_val); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + *bvals = returned_bervals; + } + return return_value; +} + +int +ruv_to_smod(const RUV *ruv, Slapi_Mod *smod) +{ + int return_value; + + if (ruv == NULL || smod == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_to_smod: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + struct berval val; + RUVElement *replica; + int cookie; + char csnStr1 [CSN_STRSIZE]; + char csnStr2 [CSN_STRSIZE]; +#define B_SIZ 1024 + char buf[B_SIZ]; + PR_RWLock_Rlock (ruv->lock); + slapi_mod_init (smod, dl_get_count (ruv->elements) + 1); + slapi_mod_set_type (smod, type_ruvElement); + slapi_mod_set_operation (smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + PR_snprintf(buf, B_SIZ, "%s %s", prefix_replicageneration, ruv->replGen); + val.bv_val = buf; + val.bv_len = strlen(buf); + slapi_mod_add_value(smod, &val); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + + PR_snprintf(buf, B_SIZ, "%s%d%s%s}%s%s%s%s", prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + replica->min_csn == NULL ? "" : " ", + replica->min_csn == NULL ? "" : csn_as_string (replica->min_csn, PR_FALSE, csnStr1), + replica->csn == NULL ? "" : " ", + replica->csn == NULL ? "" : csn_as_string (replica->csn, PR_FALSE, csnStr2)); + val.bv_len = strlen(buf); + slapi_mod_add_value(smod, &val); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_last_modified_to_smod(const RUV *ruv, Slapi_Mod *smod) +{ + int return_value; + + if (ruv == NULL || smod == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_last_modified_to_smod: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + struct berval val; + RUVElement *replica; + int cookie; + char buf[B_SIZ]; + PR_RWLock_Rlock (ruv->lock); + slapi_mod_init (smod, dl_get_count (ruv->elements)); + slapi_mod_set_type (smod, type_ruvElementUpdatetime); + slapi_mod_set_operation (smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + val.bv_val = buf; + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + PR_snprintf(buf, B_SIZ, "%s%d%s%s} %08lx", prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + replica->last_modified); + val.bv_len = strlen(buf); + slapi_mod_add_value(smod, &val); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +/* + * XXXggood do we need "ruv_covers_ruv_strict" ???? */ +PRBool +ruv_covers_ruv(const RUV *covering_ruv, const RUV *covered_ruv) +{ + PRBool return_value = PR_TRUE; + RUVElement *replica; + int cookie; + + /* compare replica generations first */ + if (covering_ruv->replGen == NULL) + { + if (covered_ruv->replGen) + return PR_FALSE; + } + else + { + if (covered_ruv->replGen == NULL) + return PR_FALSE; + } + + if (strcasecmp (covered_ruv->replGen, covering_ruv->replGen)) + return PR_FALSE; + + /* replica generation is the same, now compare element by element */ + for (replica = dl_get_first (covered_ruv->elements, &cookie); + NULL != replica; + replica = dl_get_next (covered_ruv->elements, &cookie)) + { + if (replica->csn && + (ruv_covers_csn(covering_ruv, replica->csn) == PR_FALSE)) + { + return_value = PR_FALSE; + /* Don't break here - may leave something referenced? */ + } + } + return return_value; +} + +PRInt32 +ruv_replica_count (const RUV *ruv) +{ + if (ruv == NULL) + return 0; + else + { + int count; + + PR_RWLock_Rlock (ruv->lock); + count = dl_get_count (ruv->elements); + PR_RWLock_Unlock (ruv->lock); + + return count; + } +} + +/* + * Extract all the referral URL's from the RUV (but self URL), + * returning them in an array of strings, that + * the caller must free. + */ +char ** +ruv_get_referrals(const RUV *ruv) +{ + char **r= NULL; + int n; + const char *mypurl = multimaster_get_local_purl(); + + PR_RWLock_Rlock (ruv->lock); + + n = ruv_replica_count(ruv); + if(n>0) + { + RUVElement *replica; + int cookie; + int i= 0; + r= (char**)slapi_ch_calloc(sizeof(char*),n+1); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + /* Add URL into referrals if doesn't match self URL */ + if((replica->replica_purl!=NULL) && + (slapi_utf8casecmp((unsigned char *)replica->replica_purl, + (unsigned char *)mypurl) != 0)) + { + r[i]= slapi_ch_strdup(replica->replica_purl); + i++; + } + } + } + + PR_RWLock_Unlock (ruv->lock); + + return r; /* Caller must free this */ +} + +void +ruv_dump(const RUV *ruv, char *ruv_name, PRFileDesc *prFile) +{ + RUVElement *replica; + int cookie; + char csnstr1[CSN_STRSIZE]; + char csnstr2[CSN_STRSIZE]; + char buff[RUVSTR_SIZE]; + int len = sizeof (buff); + + PR_ASSERT(NULL != ruv); + + PR_RWLock_Rlock (ruv->lock); + + PR_snprintf (buff, len, "%s: {replicageneration} %s\n", + ruv_name ? ruv_name : type_ruvElement, + ruv->replGen == NULL ? "" : ruv->replGen); + + if (prFile) + { + slapi_write_buffer (prFile, buff, strlen(buff)); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, buff); + } + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + /* prefix_ruvcsn = "{replica " */ + PR_snprintf (buff, len, "%s: %s%d%s%s} %s %s\n", + ruv_name ? ruv_name : type_ruvElement, + prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + csn_as_string(replica->min_csn, PR_FALSE, csnstr1), + csn_as_string(replica->csn, PR_FALSE, csnstr2)); + if (strlen (csnstr1) > 0) { + PR_snprintf (buff + strlen(buff) - 1, len - strlen(buff), " %08lx\n", + replica->last_modified); + } + if (prFile) + { + slapi_write_buffer (prFile, buff, strlen(buff)); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, buff); + } + } + + PR_RWLock_Unlock (ruv->lock); +} + +/* this function notifies the ruv that there are operations in progress so that + they can be added to the pending list for the appropriate client. */ +int ruv_add_csn_inprogress (RUV *ruv, const CSN *csn) +{ + RUVElement* replica; + char csn_str[CSN_STRSIZE]; + int rc = RUV_SUCCESS; + + PR_ASSERT (ruv && csn); + + /* locate ruvElement */ + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, csn_get_replicaid (csn)); + if (replica == NULL) + { + replica = ruvAddReplicaNoCSN (ruv, csn_get_replicaid (csn), NULL/*purl*/); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: failed to add replica" + " that created csn %s\n", csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_MEMORY_ERROR; + goto done; + } + } + + /* check first that this csn is not already covered by this RUV */ + if (ruv_covers_csn_internal(ruv, csn, PR_FALSE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: " + "the csn %s has already be seen - ignoring\n", + csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_COVERS_CSN; + goto done; + } + + rc = csnplInsert (replica->csnpl, csn); + if (rc == 1) /* we already seen this csn */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: " + "the csn %s has already be seen - ignoring\n", + csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_COVERS_CSN; + } + else if(rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: failed to insert csn %s" + " into pending list\n", csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_UNKNOWN_ERROR; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: successfully inserted csn %s" + " into pending list\n", csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_SUCCESS; + } + +done: + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int ruv_cancel_csn_inprogress (RUV *ruv, const CSN *csn) +{ + RUVElement* replica; + int rc = RUV_SUCCESS; + + PR_ASSERT (ruv && csn); + + /* locate ruvElement */ + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, csn_get_replicaid (csn)); + if (replica == NULL) + { + /* ONREPL - log error */ + rc = RUV_NOTFOUND; + goto done; + } + + rc = csnplRemove (replica->csnpl, csn); + if (rc != 0) + rc = RUV_NOTFOUND; + else + rc = RUV_SUCCESS; + +done: + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int ruv_update_ruv (RUV *ruv, const CSN *csn, const char *replica_purl, PRBool isLocal) +{ + int rc=RUV_SUCCESS; + char csn_str[CSN_STRSIZE]; + CSN *max_csn; + CSN *first_csn = NULL; + RUVElement *replica; + + PR_ASSERT (ruv && csn); + + PR_RWLock_Wlock (ruv->lock); + + replica = ruvGetReplica (ruv, csn_get_replicaid (csn)); + if (replica == NULL) + { + /* we should have a ruv element at this point because it would have + been added by ruv_add_inprogress function */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: " + "can't locate RUV element for replica %d\n", csn_get_replicaid (csn)); + goto done; + } + + if (csnplCommit(replica->csnpl, csn) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "ruv_update_ruv: cannot commit csn %s\n", + csn_as_string(csn, PR_FALSE, csn_str)); + rc = RUV_CSNPL_ERROR; + goto done; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: " + "successfully committed csn %s\n", csn_as_string(csn, PR_FALSE, csn_str)); + } + + if ((max_csn = csnplRollUp(replica->csnpl, &first_csn)) != NULL) + { +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: rolled up to csn %s\n", + csn_as_string(max_csn, PR_FALSE, csn_str)); /* XXXggood remove debugging */ +#endif + /* replica object sets min csn for local replica */ + if (!isLocal && replica->min_csn == NULL) { + /* bug 559223 - it seems that, under huge stress, a server might pass + * through this code when more than 1 change has already been sent and commited into + * the pending lists... Therefore, as we are trying to set the min_csn ever + * generated by this replica, we need to set the first_csn as the min csn in the + * ruv */ + set_min_csn_nolock(ruv, first_csn, replica_purl); + } + set_max_csn_nolock(ruv, max_csn, replica_purl); + /* It is possible that first_csn points to max_csn. + We need to free it once */ + if (max_csn != first_csn) { + csn_free(&first_csn); + } + csn_free(&max_csn); + } + +done: + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + +/* Helper functions */ + +static int +ruvInit (RUV **ruv, int initCount) +{ + PR_ASSERT (ruv); + + /* allocate new RUV */ + *ruv = (RUV *)slapi_ch_calloc (1, sizeof (RUV)); + if (ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvInit: memory allocation failed\n"); + return RUV_MEMORY_ERROR; + } + + /* allocate elements */ + (*ruv)->elements = dl_new (); + if ((*ruv)->elements == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvInit: memory allocation failed\n"); + return RUV_MEMORY_ERROR; + } + + dl_init ((*ruv)->elements, initCount); + + /* create lock */ + (*ruv)->lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "ruv_lock"); + if ((*ruv)->lock == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvInit: failed to create lock\n"); + return RUV_NSPR_ERROR; + } + + return RUV_SUCCESS; +} + +static void +ruvFreeReplica (void **data) +{ + RUVElement *element = *(RUVElement**)data; + + if (NULL != element) + { + if (NULL != element->csn) + { + csn_free (&element->csn); + } + if (NULL != element->min_csn) + { + csn_free (&element->min_csn); + } + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&element->replica_purl); + + if (element->csnpl) + { + csnplFree (&(element->csnpl)); + } + slapi_ch_free ((void **)&element); + } +} + +static RUVElement* +ruvAddReplica (RUV *ruv, const CSN *csn, const char *replica_purl) +{ + RUVElement *replica; + + PR_ASSERT (NULL != ruv); + PR_ASSERT (NULL != csn); + + replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement)); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvAddReplica: memory allocation failed\n"); + return NULL; + } + + replica->rid = csn_get_replicaid (csn); +/* PR_ASSERT(replica->rid != READ_ONLY_REPLICA_ID); */ + + replica->csn = csn_dup (csn); + replica->last_modified = current_time(); + replica->min_csn = csn_dup (csn); + + replica->replica_purl = slapi_ch_strdup(replica_purl); + replica->csnpl = csnplNew (); + + dl_add (ruv->elements, replica); + + return replica; +} + +static RUVElement* +ruvAddReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl) +{ + RUVElement *replica; + + PR_ASSERT (NULL != ruv); +/* PR_ASSERT(rid != READ_ONLY_REPLICA_ID); */ + + replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement)); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvAddReplicaNoCSN: memory allocation failed\n"); + return NULL; + } + + replica->rid = rid; + replica->replica_purl = slapi_ch_strdup(replica_purl); + replica->csnpl = csnplNew (); + + dl_add (ruv->elements, replica); + + return replica; +} + +static RUVElement* +ruvAddIndexReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl, int index) +{ + RUVElement *replica; + + PR_ASSERT (NULL != ruv); +/* PR_ASSERT(rid != READ_ONLY_REPLICA_ID); */ + + replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement)); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvAddIndexReplicaNoCSN: memory allocation failed\n"); + return NULL; + } + + replica->rid = rid; + replica->replica_purl = slapi_ch_strdup(replica_purl); + replica->csnpl = csnplNew (); + + dl_add_index (ruv->elements, replica, index); + + return replica; +} + +static RUVElement * +ruvGetReplica (const RUV *ruv, ReplicaId rid) +{ + PR_ASSERT (ruv /* && rid >= 0 -- rid can't be negative */); + + return (RUVElement *)dl_get (ruv->elements, (const void*)&rid, ruvReplicaCompare); +} + +static int +ruvReplicaCompare (const void *el1, const void *el2) +{ + RUVElement *replica = (RUVElement*)el1; + ReplicaId *rid1 = (ReplicaId*) el2; + + if (replica == NULL || rid1 == NULL) + return -1; + + if (*rid1 == replica->rid) + return 0; + + if (*rid1 < replica->rid) + return -1; + else + return 1; +} + + + +/* + * Given a berval that points to a string of the form: + * "{dbgen} generation-id", return a copy of the + * "generation-id" part in a null-terminated string. + * Returns NULL if the berval is malformed. + */ +static char * +get_replgen_from_berval(const struct berval *bval) +{ + char *ret_string = NULL; + + if (NULL != bval && NULL != bval->bv_val && + (bval->bv_len > strlen(prefix_replicageneration)) && + strncasecmp(bval->bv_val, prefix_replicageneration, + strlen(prefix_replicageneration)) == 0) + { + unsigned int index = strlen(prefix_replicageneration); + /* Skip any whitespace */ + while (index++ < bval->bv_len && bval->bv_val[index] == ' '); + if (index < bval->bv_len) + { + unsigned int ret_len = bval->bv_len - index; + ret_string = slapi_ch_malloc(ret_len + 1); + memcpy(ret_string, &bval->bv_val[index], ret_len); + ret_string[ret_len] = '\0'; + } + } + return ret_string; +} + + + +/* + * Given a berval that points to a string of the form: + * "{replica ldap[s]//host:port} <min_csn> <csn>", parse out the + * partial URL and the CSNs into an RUVElement, and return + * a pointer to the copy. Returns NULL if the berval is + * malformed. + */ +static RUVElement * +get_ruvelement_from_berval(const struct berval *bval) +{ + RUVElement *ret_ruve = NULL; + char *purl = NULL; + ReplicaId rid = 0; + char ridbuff [RIDSTR_SIZE]; + int i; + + if (NULL != bval && NULL != bval->bv_val && + bval->bv_len > strlen(prefix_ruvcsn) && + strncasecmp(bval->bv_val, prefix_ruvcsn, strlen(prefix_ruvcsn)) == 0) + { + unsigned int urlbegin = strlen(prefix_ruvcsn); + unsigned int urlend; + unsigned int mincsnbegin; + + /* replica id must be here */ + i = 0; + while (isdigit (bval->bv_val[urlbegin])) + { + ridbuff [i] = bval->bv_val[urlbegin]; + i++; + urlbegin ++; + } + + if (i == 0) /* replicaid is missing */ + goto loser; + + ridbuff[i] = '\0'; + rid = atoi (ridbuff); + + if (bval->bv_val[urlbegin] == '}') + { + /* No purl in this value */ + purl = NULL; + mincsnbegin = urlbegin + 1; + } + else + { + while (urlbegin++ < bval->bv_len && bval->bv_val[urlbegin] == ' '); + urlend = urlbegin; + while (urlend++ < bval->bv_len && bval->bv_val[urlend] != '}'); + purl = slapi_ch_malloc(urlend - urlbegin + 1); + memcpy(purl, &bval->bv_val[urlbegin], urlend - urlbegin); + purl[urlend - urlbegin] = '\0'; + mincsnbegin = urlend; + } + /* Skip any whitespace before the first (minimum) CSN */ + while (mincsnbegin++ < (bval->bv_len-1) && bval->bv_val[mincsnbegin] == ' '); + /* Now, mincsnbegin should contain the index of the beginning of the first csn */ + if (mincsnbegin >= bval->bv_len) + { + /* Missing the entire content*/ + if (purl == NULL) + goto loser; + else /* we have just purl - no changes from the replica has been seen */ + { + ret_ruve = (RUVElement *)slapi_ch_calloc(1, sizeof(RUVElement)); + ret_ruve->rid = rid; + ret_ruve->replica_purl = purl; + } + } + else + { + if (bval->bv_len - mincsnbegin != (_CSN_VALIDCSN_STRLEN * 2) + 1) + { + /* Malformed - incorrect length for 2 CSNs + space */ + goto loser; + } + else + { + char mincsnstr[CSN_STRSIZE]; + char maxcsnstr[CSN_STRSIZE]; + + memset(mincsnstr, '\0', CSN_STRSIZE); + memset(maxcsnstr, '\0', CSN_STRSIZE); + memcpy(mincsnstr, &bval->bv_val[mincsnbegin], _CSN_VALIDCSN_STRLEN); + memcpy(maxcsnstr, &bval->bv_val[mincsnbegin + _CSN_VALIDCSN_STRLEN + 1], _CSN_VALIDCSN_STRLEN); + ret_ruve = (RUVElement *)slapi_ch_calloc(1, sizeof(RUVElement)); + ret_ruve->min_csn = csn_new_by_string(mincsnstr); + ret_ruve->csn = csn_new_by_string(maxcsnstr); + ret_ruve->rid = rid; + ret_ruve->replica_purl = purl; + if (NULL == ret_ruve->min_csn || NULL == ret_ruve->csn) + { + goto loser; + } + } + } + } + + /* initialize csn pending list */ + ret_ruve->csnpl = csnplNew (); + if (ret_ruve->csnpl == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "get_ruvelement_from_berval: failed to create csn pending list\n"); + goto loser; + } + + return ret_ruve; + +loser: + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&purl); + if (NULL != ret_ruve) + { + if (NULL != ret_ruve->min_csn) + { + csn_free(&ret_ruve->min_csn); + } + if (NULL != ret_ruve->csn) + { + csn_free(&ret_ruve->csn); + } + slapi_ch_free((void **)&ret_ruve); + } + return NULL; +} + +int +ruv_move_local_supplier_to_first(RUV *ruv, ReplicaId aRid) +{ + RUVElement * elem = NULL; + int rc = RUV_NOTFOUND; + + PR_ASSERT(ruv); + + PR_RWLock_Wlock (ruv->lock); + + elem = (RUVElement *)dl_delete(ruv->elements,(const void*)&aRid, ruvReplicaCompare, 0); + if (elem) { + dl_add_index(ruv->elements, elem, 1); + rc = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + + +int +ruv_get_first_id_and_purl(RUV *ruv, ReplicaId *rid, char **replica_purl ) +{ + RUVElement * first = NULL; + int cookie; + int rc; + + PR_ASSERT(ruv); + + PR_RWLock_Rlock (ruv->lock); + first = dl_get_first(ruv->elements, &cookie); + if ( first == NULL ) + { + rc = RUV_MEMORY_ERROR; + } + else + { + *rid = first->rid; + *replica_purl = first->replica_purl; + rc = RUV_SUCCESS; + } + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int ruv_local_contains_supplier(RUV *ruv, ReplicaId rid) +{ + int cookie; + RUVElement *elem = NULL; + + PR_ASSERT(ruv); + + PR_RWLock_Rlock (ruv->lock); + for (elem = dl_get_first (ruv->elements, &cookie); + elem; + elem = dl_get_next (ruv->elements, &cookie)) + { + if (elem->rid == rid){ + PR_RWLock_Unlock (ruv->lock); + return 1; + } + } + PR_RWLock_Unlock (ruv->lock); + return 0; +} + +PRBool ruv_has_csns(const RUV *ruv) +{ + PRBool retval = PR_TRUE; + CSN *mincsn = NULL; + CSN *maxcsn = NULL; + + ruv_get_min_csn(ruv, &mincsn); + ruv_get_max_csn(ruv, &maxcsn); + if (mincsn) { + csn_free(&mincsn); + csn_free(&maxcsn); + } else if (maxcsn) { + csn_free(&maxcsn); + } else { + retval = PR_FALSE; /* both min and max are false */ + } + + return retval; +} + +/* Check if the first ruv is newer than the second one */ +PRBool +ruv_is_newer (Object *sruvobj, Object *cruvobj) +{ + RUV *sruv, *cruv; + RUVElement *sreplica, *creplica; + int scookie, ccookie; + int is_newer = PR_FALSE; + + if ( sruvobj == NULL ) { + return 0; + } + if ( cruvobj == NULL ) { + return 1; + } + sruv = (RUV *) object_get_data ( sruvobj ); + cruv = (RUV *) object_get_data ( cruvobj ); + + for (sreplica = dl_get_first (sruv->elements, &scookie); sreplica; + sreplica = dl_get_next (sruv->elements, &scookie)) + { + /* A hub may have a dummy ruv with rid 65535 */ + if ( sreplica->csn == NULL ) continue; + + for (creplica = dl_get_first (cruv->elements, &ccookie); creplica; + creplica = dl_get_next (cruv->elements, &ccookie)) + { + if ( sreplica->rid == creplica->rid ) { + if ( csn_compare ( sreplica->csn, creplica->csn ) > 0 ) { + is_newer = PR_TRUE; + } + break; + } + } + if ( creplica == NULL || is_newer ) { + is_newer = PR_TRUE; + break; + } + } + + return is_newer; +} + +#ifdef TESTING /* Some unit tests for code in this file */ + +static void +ruv_dump_internal(RUV *ruv) +{ + RUVElement *replica; + int cookie; + char csnstr1[CSN_STRSIZE]; + char csnstr2[CSN_STRSIZE]; + + PR_ASSERT(NULL != ruv); + printf("{replicageneration} %s\n", ruv->replGen == NULL ? "NULL" : ruv->replGen); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + printf("{replica%s%s} %s %s\n", + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + csn_as_string(replica->min_csn, PR_FALSE, csnstr1), + csn_as_string(replica->csn, PR_FALSE, csnstr2)); + } +} + +void +ruv_test() +{ + const struct berval *vals[5]; + struct berval val0, val1, val2, val3; + RUV *ruv; + Slapi_Attr *attr; + Slapi_Value *sv0, *sv1, *sv2, *sv3; + int rc; + char csnstr[CSN_STRSIZE]; + char *gen; + CSN *newcsn; + ReplicaId *ids; + int nids; + Slapi_Mod smods; + PRBool covers; + + vals[0] = &val0; + vals[1] = &val1; + vals[2] = &val2; + vals[3] = &val3; + vals[4] = NULL; + + val0.bv_val = "{replicageneration} 0440FDC0A33F"; + val0.bv_len = strlen(val0.bv_val); + + val1.bv_val = "{replica ldap://ggood.mcom.com:389} 12345670000000FE0000 12345671000000FE0000"; + val1.bv_len = strlen(val1.bv_val); + + val2.bv_val = "{replica ldaps://an-impossibly-long-host-name-that-drags-on-forever-and-forever.mcom.com:389} 11112110000000FF0000 11112111000000FF0000"; + val2.bv_len = strlen(val2.bv_val); + + val3.bv_val = "{replica} 12345672000000FD0000 12345673000000FD0000"; + val3.bv_len = strlen(val3.bv_val); + + rc = ruv_init_from_bervals(vals, &ruv); + ruv_dump_internal(ruv); + + attr = slapi_attr_new(); + attr = slapi_attr_init(attr, "ruvelement"); + sv0 = slapi_value_new(); + sv1 = slapi_value_new(); + sv2 = slapi_value_new(); + sv3 = slapi_value_new(); + slapi_value_init_berval(sv0, &val0); + slapi_value_init_berval(sv1, &val1); + slapi_value_init_berval(sv2, &val2); + slapi_value_init_berval(sv3, &val3); + slapi_attr_add_value(attr, sv0); + slapi_attr_add_value(attr, sv1); + slapi_attr_add_value(attr, sv2); + slapi_attr_add_value(attr, sv3); + rc = ruv_init_from_slapi_attr(attr, &ruv); + ruv_dump_internal(ruv); + + rc = ruv_delete_replica(ruv, 0xFF); + /* Should delete one replica */ + ruv_dump_internal(ruv); + + rc = ruv_delete_replica(ruv, 0xAA); + /* No such replica - should not do anything */ + ruv_dump_internal(ruv); + + rc = ruv_get_largest_csn_for_replica(ruv, 0xFE, &newcsn); + if (NULL != newcsn) + { + csn_as_string(newcsn, PR_FALSE, csnstr); + printf("Replica 0x%X has largest csn \"%s\"\n", 0xFE, csnstr); + } + else + { + printf("BAD - can't get largest CSN for replica 0x%X\n", 0xFE); + } + + rc = ruv_get_smallest_csn_for_replica(ruv, 0xFE, &newcsn); + if (NULL != newcsn) + { + csn_as_string(newcsn, PR_FALSE, csnstr); + printf("Replica 0x%X has smallest csn \"%s\"\n", 0xFE, csnstr); + } + else + { + printf("BAD - can't get smallest CSN for replica 0x%X\n", 0xFE); + } + rc = ruv_get_largest_csn_for_replica(ruv, 0xAA, &newcsn); + printf("ruv_get_largest_csn_for_replica on non-existent replica ID returns %d\n", rc); + + rc = ruv_get_smallest_csn_for_replica(ruv, 0xAA, &newcsn); + printf("ruv_get_smallest_csn_for_replica on non-existent replica ID returns %d\n", rc); + + newcsn = csn_new_by_string("12345674000000FE0000"); /* Old replica 0xFE */ + rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com"); + /* Should update replica FE's CSN */ + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("12345675000000FB0000"); /* New replica 0xFB */ + rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com"); + /* Should get a new replica in the list with min == max csn */ + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("12345676000000FD0000"); /* Old replica 0xFD */ + rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com"); + /* Should update replica 0xFD so new CSN is newer than min CSN */ + ruv_dump_internal(ruv); + + gen = ruv_get_replica_generation(ruv); + printf("replica generation is \"%s\"\n", gen); + + newcsn = csn_new_by_string("12345673000000FE0000"); /* Old replica 0xFE */ + covers = ruv_covers_csn(ruv, newcsn); /* should say "true" */ + + newcsn = csn_new_by_string("12345675000000FE0000"); /* Old replica 0xFE */ + covers = ruv_covers_csn(ruv, newcsn); /* Should say "false" */ + + newcsn = csn_new_by_string("123456700000000A0000"); /* New replica 0A */ + rc = ruv_set_min_csn(ruv, newcsn, "ldap://repl0a.mcom.com"); + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("123456710000000A0000"); /* New replica 0A */ + rc = ruv_set_max_csn(ruv, newcsn, "ldap://repl0a.mcom.com"); + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("123456700000000B0000"); /* New replica 0B */ + rc = ruv_set_max_csn(ruv, newcsn, "ldap://repl0b.mcom.com"); + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("123456710000000B0000"); /* New replica 0B */ + rc = ruv_set_min_csn(ruv, newcsn, "ldap://repl0b.mcom.com"); + ruv_dump_internal(ruv); + + /* ONREPL test ruv enumeration */ + + rc = ruv_to_smod(ruv, &smods); + + ruv_destroy(&ruv); +} +#endif /* TESTING */ diff --git a/ldap/servers/plugins/replication/repl5_ruv.h b/ldap/servers/plugins/replication/repl5_ruv.h new file mode 100644 index 00000000..8484e74b --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_ruv.h @@ -0,0 +1,88 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_ruv.h - interface for replica update vector */ + +#ifndef REPL5_RUV +#define REPL5_RUV + +#include "slapi-private.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _ruv RUV; + +enum +{ + RUV_SUCCESS=0, + RUV_BAD_DATA, + RUV_NOTFOUND, + RUV_MEMORY_ERROR, + RUV_NSPR_ERROR, + RUV_BAD_FORMAT, + RUV_UNKNOWN_ERROR, + RUV_ALREADY_EXIST, + RUV_CSNPL_ERROR, + RUV_COVERS_CSN +}; + +typedef struct ruv_enum_data +{ + CSN *csn; + CSN *min_csn; +} ruv_enum_data; + +typedef int (*FNEnumRUV) (const ruv_enum_data *element, void *arg); +int ruv_init_new (const char *replGen, ReplicaId rid, const char *purl, RUV **ruv); +int ruv_init_from_bervals(struct berval** vals, RUV **ruv); +int ruv_init_from_slapi_attr(Slapi_Attr *attr, RUV **ruv); +int ruv_init_from_slapi_attr_and_check_purl(Slapi_Attr *attr, RUV **ruv, ReplicaId *rid); +RUV* ruv_dup (const RUV *ruv); +void ruv_destroy (RUV **ruv); +void ruv_copy_and_destroy (RUV **srcruv, RUV **destruv); +int ruv_replace_replica_purl (RUV *ruv, ReplicaId rid, const char *replica_purl); +int ruv_delete_replica (RUV *ruv, ReplicaId rid); +int ruv_add_replica (RUV *ruv, ReplicaId rid, const char *replica_purl); +int ruv_add_index_replica (RUV *ruv, ReplicaId rid, const char *replica_purl, int index); +PRBool ruv_contains_replica (const RUV *ruv, ReplicaId rid); +int ruv_get_largest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn); +int ruv_get_smallest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn); +int ruv_set_csns(RUV *ruv, const CSN *csn, const char *replica_purl); +int ruv_set_csns_keep_smallest(RUV *ruv, const CSN *csn); +int ruv_set_max_csn(RUV *ruv, const CSN *max_csn, const char *replica_purl); +int ruv_set_min_csn(RUV *ruv, const CSN *min_csn, const char *replica_purl); +const char *ruv_get_purl_for_replica(const RUV *ruv, ReplicaId rid); +char *ruv_get_replica_generation (const RUV *ruv); +void ruv_set_replica_generation (RUV *ruv, const char *generation); +PRBool ruv_covers_ruv(const RUV *covering_ruv, const RUV *covered_ruv); +PRBool ruv_covers_csn(const RUV *ruv, const CSN *csn); +PRBool ruv_covers_csn_strict(const RUV *ruv, const CSN *csn); +int ruv_get_min_csn(const RUV *ruv, CSN **csn); +int ruv_get_max_csn(const RUV *ruv, CSN **csn); +int ruv_enumerate_elements (const RUV *ruv, FNEnumRUV fn, void *arg); +int ruv_to_smod(const RUV *ruv, Slapi_Mod *smod); +int ruv_last_modified_to_smod(const RUV *ruv, Slapi_Mod *smod); +int ruv_to_bervals(const RUV *ruv, struct berval ***bvals); +PRInt32 ruv_replica_count (const RUV *ruv); +char **ruv_get_referrals(const RUV *ruv); +void ruv_dump(const RUV *ruv, char *ruv_name, PRFileDesc *prFile); +int ruv_add_csn_inprogress (RUV *ruv, const CSN *csn); +int ruv_cancel_csn_inprogress (RUV *ruv, const CSN *csn); +int ruv_update_ruv (RUV *ruv, const CSN *csn, const char *replica_purl, PRBool isLocal); +int ruv_move_local_supplier_to_first(RUV *ruv, ReplicaId rid); +int ruv_get_first_id_and_purl(RUV *ruv, ReplicaId *rid, char **replica_purl ); +int ruv_local_contains_supplier(RUV *ruv, ReplicaId rid); +/* returns true if the ruv has any csns, false otherwise - used for testing + whether or not an RUV is empty */ +PRBool ruv_has_csns(const RUV *ruv); +PRBool ruv_is_newer (Object *sruv, Object *cruv); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ldap/servers/plugins/replication/repl5_schedule.c b/ldap/servers/plugins/replication/repl5_schedule.c new file mode 100644 index 00000000..427e79a9 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_schedule.c @@ -0,0 +1,742 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_schedule.c */ +/* + +The schedule object implements the scheduling policy for a DS 5.0 replication +supplier. + +Methods: +schedule_set() - sets the schedule +schedule_get() - gets the schedule +schedule_in_window_now() - returns TRUE if a replication session + should commence. +schedule_next() - returns the next time that replication is + scheduled to occur. +schedule_notify() - called to inform the scheduler when entries + have been updated. +schedule_set_priority_attributes() - sets the attributes that are + considered "high priority". A modification to one of these attributes + will cause replication to commence asap, overriding the startup + delay and maximum backlog. Also includes an additional parameter + that controls whether priority attributes are propagated regardless + of the scheduling window, e.g. it's possible to configure things + so that password changes get propagated even if we're not in a + replication window. +schedule_set_startup_delay() - sets the time that replication should + wait before commencing replication sessions. +schedule_set_maximum_backlog() - sets the maximum number of updates + which can occur before replication will commence. If the backlog + threshhold is exceeded, then replication will commence ASAP, + overriding the startup delay. + +*/ + +/* ONREPL - I made a simplifying assumption that a schedule item does not + cross day boundaries. Implementing this is hard because we search + for the items for a particular day only based on the item's staring time. + For instance if the current time is tuesday morning, we would not consider + the item that started on monday and continued through tuesday. + To simulate an item that crosses day boundaries, you can create 2 items - + one for the time in the first day and one for the time in the second. + We could do this internally by allowing items do span 2 days and + splitting them ourselves. This, however, is not currently implemented */ + +#include "slapi-plugin.h" +#include "repl5.h" + +#include <ctype.h> /* For isdigit() */ + +/* from proto-slap.h */ +char *get_timestring(time_t *t); +void free_timestring(char *timestr); + +typedef struct schedule_item { + struct schedule_item *next; + PRUint32 start; /* Start time, given as seconds after midnight */ + PRUint32 end; /* End time */ + unsigned char dow; /* Days of week, LSB = Sunday */ +} schedule_item; + +typedef struct schedule { + const char *session_id; + size_t max_backlog; + size_t startup_delay; + schedule_item *schedule_list; /* Linked list of schedule windows */ + char **prio_attrs; /* Priority attributes - start replication now */ + int prio_attrs_override_schedule; + PRTime last_session_end; + int last_session_status; + PRTime last_successful_session_end; + window_state_change_callback callback_fn; /* function to call when window opens/closes */ + void *callback_arg; /* argument to pass to the window state change callback */ + Slapi_Eq_Context pending_event; /* event scheduled with the event queue */ + PRLock *lock; +} schedule; + +/* Forward declarations */ +static schedule_item *parse_schedule_value(const Slapi_Value *v); +static void schedule_window_state_change_event (Schedule *sch); +static void unschedule_window_state_change_event (Schedule *sch); +static void window_state_changed (time_t when, void *arg); +static int schedule_in_window_now_nolock(Schedule *sch); +static schedule_item* get_current_schedule_item (Schedule *sch); +static time_t PRTime2time_t (PRTime tm); +static PRTime schedule_next_nolock (Schedule *sch, PRBool start); +static void free_schedule_list(schedule_item **schedule_list); + +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE) +#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR) +#define DAYS_PER_WEEK 7 +#define ALL_DAYS 0x7F /* Bit mask */ + + + +/* + * Create a new schedule object and return a pointer to it. + */ +Schedule* +schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id) +{ + Schedule *sch = NULL; + sch = (Schedule *)slapi_ch_calloc(1, sizeof(struct schedule)); + + sch->session_id = session_id ? session_id : ""; + sch->callback_fn = callback_fn; + sch->callback_arg = callback_arg; + + if ((sch->lock = PR_NewLock()) == NULL) + { + slapi_ch_free((void **)&sch); + } + + return sch; +} + + +void +schedule_destroy(Schedule *s) +{ + int i; + + /* unschedule update window event if exists */ + unschedule_window_state_change_event (s); + + if (s->schedule_list) + { + free_schedule_list (&s->schedule_list); + } + + if (NULL != s->prio_attrs) + { + for (i = 0; NULL != s->prio_attrs[i]; i++) + { + slapi_ch_free((void **)&(s->prio_attrs[i])); + } + slapi_ch_free((void **)&(s->prio_attrs)); + } + PR_DestroyLock(s->lock); + s->lock = NULL; + slapi_ch_free((void **)&s); +} + +static void +free_schedule_list(schedule_item **schedule_list) +{ + schedule_item *si = *schedule_list; + schedule_item *tmp_si; + while (NULL != si) + { + tmp_si = si->next; + slapi_ch_free((void **)&si); + si = tmp_si; + } + *schedule_list = NULL; +} + + + +/* + * Sets the schedule. Returns 0 if all of the schedule lines were + * correctly parsed and the new schedule has been put into effect. + * Returns -1 if one or more of the schedule items could not be + * parsed. If -1 is returned, then no changes have been made to the + * current schedule. + */ +int +schedule_set(Schedule *sch, Slapi_Attr *attr) +{ + int return_value; + schedule_item *si = NULL; + schedule_item *new_schedule_list = NULL; + int valid = 1; + + if (NULL != attr) + { + int ind; + Slapi_Value *sval; + ind = slapi_attr_first_value(attr, &sval); + while (ind >= 0) + { + si = parse_schedule_value(sval); + if (NULL == si) + { + valid = 0; + break; + } + /* Put at head of linked list */ + si->next = new_schedule_list; + new_schedule_list = si; + ind = slapi_attr_next_value(attr, ind, &sval); + } + } + + if (!valid) + { + /* deallocate any new schedule items */ + free_schedule_list(&new_schedule_list); + return_value = -1; + } + else + { + PR_Lock(sch->lock); + + /* if there is an update window event scheduled - unschedule it */ + unschedule_window_state_change_event (sch); + + free_schedule_list(&sch->schedule_list); + sch->schedule_list = new_schedule_list; + + /* schedule an event to notify the caller about openning/closing of the update window */ + schedule_window_state_change_event (sch); + + PR_Unlock(sch->lock); + return_value = 0; + } + return return_value; +} + + + +/* + * Returns the schedule. + */ +char ** +schedule_get(Schedule *sch) +{ + char **return_value = NULL; + + return return_value; +} + + + +/* + * Return an integer corresponding to the day of the week for + * "when". + */ +static PRInt32 +day_of_week(PRTime when) +{ + + PRExplodedTime exp; + + PR_ExplodeTime(when, PR_LocalTimeParameters, &exp); + return(exp.tm_wday); +} + + +/* + * Return the number of seconds between "when" and the + * most recent midnight. + */ +static PRUint32 +seconds_since_midnight(PRTime when) +{ + PRExplodedTime exp; + + PR_ExplodeTime(when, PR_LocalTimeParameters, &exp); + return(exp.tm_hour * 3600 + exp.tm_min * 60 + exp.tm_sec); +} + + +/* + * Return 1 if "now" is within the schedule window + * specified by "si", 0 otherwise. + */ +static int +time_in_window(PRTime now, schedule_item *si) +{ + unsigned char dow = 1 << day_of_week(now); + int return_value = 0; + + if (dow & si->dow) + { + PRUint32 nowsec = seconds_since_midnight(now); + + return_value = (nowsec >= si->start) && (nowsec <= si->end); + } + + return return_value; +} + + + +/* + * Returns a non-zero value if the current time is within a + * replication window and if scheduling constraints are all met. + * Otherwise, returns zero. + */ + +int +schedule_in_window_now (Schedule *sch) +{ + int rc; + + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + + rc = schedule_in_window_now_nolock(sch); + + PR_Unlock(sch->lock); + + return rc; +} + +/* Must be called under sch->lock */ +static int +schedule_in_window_now_nolock(Schedule *sch) +{ + int return_value = 0; + + if (NULL == sch->schedule_list) + { + /* Absence of a schedule is the same as 0000-2359 0123456 */ + return_value = 1; + } + else + { + schedule_item *si = sch->schedule_list; + PRTime now; + now = PR_Now(); + while (NULL != si) + { + if (time_in_window(now, si)) + { + /* XXX check backoff timers??? */ + return_value = 1; + break; + } + si = si->next; + } + } + + return return_value; +} + + + +/* + * Calculate the next time (expressed as a PRTime) when this + * schedule item will change state (from open to close or vice versa). + */ +static PRTime +next_change_time(schedule_item *si, PRTime now, PRBool start) +{ + PRUint32 nowsec = seconds_since_midnight(now); + PRUint32 sec_til_change; + PRUint32 change_time; + PRExplodedTime exp; + PRInt32 dow = day_of_week(now); + unsigned char dow_bit = 1 << dow; + unsigned char next_dow; + + if (start) /* we are looking for the next window opening */ + { + change_time = si->start; + } + else /* we are looking for the next window closing */ + { + /* open range is inclusive - so we need to add a minute if we are looking for close time */ + change_time = si->end + SECONDS_PER_MINUTE; + } + + /* we are replicating today and next change is also today */ + if ((dow_bit & si->dow) && (nowsec < change_time)) + { + sec_til_change = change_time - nowsec; + } + else /* not replicating today or the change already occured today */ + { + int i; + + /* find next day when we replicate */ + for (i = 1; i <= DAYS_PER_WEEK; i++) + { + next_dow = 1 << ((dow + i) % DAYS_PER_WEEK); + if (next_dow & si->dow) + break; + } + + sec_til_change = change_time + i * SECONDS_PER_DAY - nowsec; + } + + PR_ExplodeTime(now, PR_LocalTimeParameters, &exp); + exp.tm_sec += sec_til_change; + + + PR_NormalizeTime(&exp, PR_LocalTimeParameters); + return PR_ImplodeTime(&exp); +} + + + +/* + * Returns the next time that replication is scheduled to occur. + * Returns 0 if there is no time in the future that replication + * will begin (e.g. there's no schedule at all). + */ +PRTime +schedule_next(Schedule *sch) +{ + PRTime tm; + + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + + tm = schedule_next_nolock (sch, PR_TRUE); + + PR_Unlock(sch->lock); + + return tm; +} + +/* Must be called under sch->lock */ +static PRTime +schedule_next_nolock (Schedule *sch, PRBool start) +{ + + PRTime closest_time = LL_Zero(); + + if (NULL != sch->schedule_list) + { + schedule_item *si = sch->schedule_list; + PRTime now = PR_Now(); + unsigned char dow = 1 << day_of_week(now); + + while (NULL != si) + { + PRTime tmp_time; + + /* Check if this item's change time is sooner than the others */ + tmp_time = next_change_time(si, now, start); + if (LL_IS_ZERO(closest_time)) + { + LL_ADD(closest_time, tmp_time, LL_Zero()); /* Really just an asignment */ + } + else if (LL_CMP(tmp_time, <, closest_time)) + { + LL_ADD(closest_time, tmp_time, LL_Zero()); /* Really just an asignment */ + } + + si = si->next; + } + } + + return closest_time; +} + + + + +/* + * Called by the enclosing object (replsupplier) when a change within the + * replicated area has occurred. This allows the scheduler to update its + * internal counters, timers, etc. Returns a non-zero value if replication + * should commence, zero if it should not. + */ +int +schedule_notify(Schedule *sch, Slapi_PBlock *pb) +{ + int return_value = 0; + + return return_value; +} + + + + +/* + * Provide a list of attributes which, if changed, + * will cause replication to commence as soon as possible. There + * is also a flag that tells the scheduler if the update of a + * priority attribute should cause the schedule to be overridden, + * e.g. if the administrator wants password changes to propagate + * even if not in a replication window. + * + * This function consumes "prio_attrs" and assumes management + * of the memory. + */ +void +schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + if (NULL != sch->prio_attrs) + { + int i; + for (i = 0; NULL != prio_attrs[i]; i++) { + slapi_ch_free((void **)&sch->prio_attrs[i]); + } + slapi_ch_free((void **)&sch->prio_attrs); + } + sch->prio_attrs = prio_attrs; + sch->prio_attrs_override_schedule = override_schedule; + + PR_Unlock(sch->lock); +} + + + + + +/* + * Set the time, in seconds, that replication will wait after a change is + * available before propagating it. This capability will allow multiple + * updates to be coalesced into a single replication session. + */ +void +schedule_set_startup_delay(Schedule *sch, size_t startup_delay) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + sch->startup_delay = startup_delay; + PR_Unlock(sch->lock); +} + + + + + +/* + * Set the maximum number of pending changes allowed to accumulate + * before a replication session is begun. + */ +void +schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + sch->max_backlog = max_backlog; + PR_Unlock(sch->lock); +} + + + + + +/* + * Notify the scheduler that a replication session completed at a certain + * time. There is also a status argument that says more about the session's + * termination (normal, abnormal), which the scheduler uses in determining + * the backoff strategy. + */ +void +schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int status) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + sch->last_session_end = session_end_time; + sch->last_session_status = status; + if (REPLICATION_SESSION_SUCCESS == status) + { + sch->last_successful_session_end = session_end_time; + } + PR_Unlock(sch->lock); +} + +/* schedule an event that will fire the next time the update window state + changes from open to closed or vice versa */ +static void +schedule_window_state_change_event (Schedule *sch) +{ + time_t wakeup_time; + PRTime tm; + int window_opened; + char *timestr = NULL; + + /* if we have a schedule and a callback function is registerd - + register an event with the event queue */ + if (sch->schedule_list && sch->callback_fn) + { + /* ONREPL what if the window is really small and by the time we are done + with the computation - we cross window boundary. + I think we should put some constrains on schedule to avoid that */ + + window_opened = schedule_in_window_now_nolock(sch); + + tm = schedule_next_nolock(sch, !window_opened); + + wakeup_time = PRTime2time_t (tm); + + /* schedule the event */ + sch->pending_event = slapi_eq_once(window_state_changed, sch, wakeup_time); + + timestr = get_timestring(&wakeup_time); + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "%s: Update window will %s at %s\n", + sch->session_id, + window_opened ? "close" : "open", timestr); + free_timestring(timestr); + timestr = NULL; + } +} + +/* this function is called by the even queue the next time + the window is opened or closed */ +static void +window_state_changed (time_t when, void *arg) +{ + Schedule *sch = (Schedule*)arg; + int open; + + PR_ASSERT (sch); + + PR_Lock(sch->lock); + + open = schedule_in_window_now_nolock(sch); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: Update window is now %s\n", + sch->session_id, + open ? "open" : "closed"); + + /* schedule next event */ + schedule_window_state_change_event (sch); + + /* notify the agreement */ + sch->callback_fn (sch->callback_arg, open); + + PR_Unlock(sch->lock); +} + +/* cancel the event registered with the event queue */ +static void +unschedule_window_state_change_event (Schedule *sch) +{ + if (sch->pending_event) + { + slapi_eq_cancel(sch->pending_event); + sch->pending_event = NULL; + } +} + +static time_t +PRTime2time_t (PRTime tm) +{ + PRInt64 rt; + + PR_ASSERT (tm); + + LL_DIV(rt, tm, PR_USEC_PER_SEC); + + return (time_t)rt; +} + +/* + * Parse a schedule line. + * The format is: + * <start>-<end> <day_of_week> + * <start> and <end> are in 24-hour time + * <day_of_week> is like cron(5): 0 = Sunday, 1 = Monday, etc. + * + * The schedule item "*" is equivalen to 0000-2359 0123456 + * + * Returns a pointer to a schedule item on success, NULL if the + * schedule item cannot be parsed. + */ +static schedule_item * +parse_schedule_value(const Slapi_Value *v) +{ +#define RANGE_VALID(p, limit) \ + ((p + 9) < limit && \ + isdigit(p[0]) && \ + isdigit(p[1]) && \ + isdigit(p[2]) && \ + isdigit(p[3]) && \ + ('-' == p[4]) && \ + isdigit(p[5]) && \ + isdigit(p[6]) && \ + isdigit(p[7]) && \ + isdigit(p[8])) + + schedule_item *si = NULL; + int valid = 0; + const struct berval *sch_bval; + + if (NULL != v && (sch_bval = slapi_value_get_berval(v)) != NULL && + NULL != sch_bval && sch_bval->bv_len > 0 && NULL != sch_bval->bv_val ) + { + char *p = sch_bval->bv_val; + char *limit = p + sch_bval->bv_len; + + si = (schedule_item *)slapi_ch_malloc(sizeof(schedule_item)); + si->next = NULL; + si->start = 0UL; + si->end = SECONDS_PER_DAY; + si->dow = ALL_DAYS; + + if (*p == '*') + { + valid = 1; + goto done; + } + else + { + if (RANGE_VALID(p, limit)) + { + si->start = ((strntoul(p, 2, 10) * 60) + + strntoul(p + 2, 2, 10)) * 60; + p += 5; + si->end = ((strntoul(p, 2, 10) * 60) + + strntoul(p + 2, 2, 10)) * 60; + p += 4; + + /* ONREPL - for now wi don't allow items that span multiple days. + See note in the beginning of the file for more details. */ + /* ONREPL - we should also decide on the minimum of the item size */ + if (si->start > si->end) + { + valid = 0; + goto done; + } + + if (p < limit && ' ' == *p) + { + /* Specific days of week */ + si->dow = 0; + while (++p < limit) + { + if (!isdigit(*p)) + { + valid = 0; + goto done; + } + si->dow |= (1 << strntoul(p, 1, 10)); + + } + } + valid = 1; + } + } + } + +done: + if (!valid) + { + slapi_ch_free((void **)&si); + } + return si; +} diff --git a/ldap/servers/plugins/replication/repl5_tot_protocol.c b/ldap/servers/plugins/replication/repl5_tot_protocol.c new file mode 100644 index 00000000..45e91f3b --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_tot_protocol.c @@ -0,0 +1,372 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_tot_protocol.c */ +/* + + The tot_protocol object implements the DS 5.0 multi-master total update + replication protocol, used to (re)populate a replica. + +*/ + +#include "repl.h" +#include "repl5.h" +#include "repl5_prot_private.h" + +/* Private data structures */ +typedef struct repl5_tot_private +{ + Repl_Protocol *rp; + Repl_Agmt *ra; + PRLock *lock; + PRUint32 eventbits; +} repl5_tot_private; + +typedef struct callback_data +{ + Private_Repl_Protocol *prp; + int rc; + unsigned long num_entries; + time_t sleep_on_busy; + time_t last_busy; +} callback_data; + +/* + * Number of window seconds to wait until we programmatically decide + * that the replica has got out of BUSY state + */ +#define SLEEP_ON_BUSY_WINDOW (10) + +/* Helper functions */ +static void get_result (int rc, void *cb_data); +static int send_entry (Slapi_Entry *e, void *callback_data); +static void repl5_tot_delete(Private_Repl_Protocol **prp); + +/* + * Completely refresh a replica. The basic protocol interaction goes + * like this: + * - Acquire Replica by sending a StartReplicationRequest extop, with the + * total update protocol OID and supplier's ruv. + * - Send a series of extended operations containing entries. + * - send an EndReplicationRequest extended operation + */ +static void +repl5_tot_run(Private_Repl_Protocol *prp) +{ + int rc; + callback_data cb_data; + Slapi_PBlock *pb; + LDAPControl **ctrls; + PRBool replica_acquired = PR_FALSE; + char *hostname = NULL; + int portnum = 0; + Slapi_DN *area_sdn = NULL; + CSN *remote_schema_csn = NULL; + + PR_ASSERT(NULL != prp); + + prp->stopped = 0; + if (prp->terminate) + { + prp->stopped = 1; + goto done; + } + + conn_set_timeout(prp->conn, agmt_get_timeout(prp->agmt)); + + /* acquire remote replica */ + agmt_set_last_init_start(prp->agmt, current_time()); + rc = acquire_replica (prp, REPL_NSDS50_TOTAL_PROTOCOL_OID, NULL /* ruv */); + /* We never retry total protocol, even in case a transient error. + This is because if somebody already updated the replica we don't + want to do it again */ + if (rc != ACQUIRE_SUCCESS) + { + int optype, ldaprc; + conn_get_error(prp->conn, &optype, &ldaprc); + agmt_set_last_init_status(prp->agmt, ldaprc, + prp->last_acquire_response_code, NULL); + goto done; + } + else if (prp->terminate) + { + conn_disconnect(prp->conn); + prp->stopped = 1; + goto done; + } + + hostname = agmt_get_hostname(prp->agmt); + portnum = agmt_get_port(prp->agmt); + + agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update in progress"); + remote_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt ); + rc = conn_push_schema(prp->conn, &remote_schema_csn); + if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable to " + "replicate schema to host %s, port %d. Continuing with " + "total update session.\n", + hostname, portnum); + /* But keep going */ + agmt_set_last_init_status(prp->agmt, 0, rc, "Total schema update failed"); + } + else + { + agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update succeeded"); + } + + /* ONREPL - big assumption here is that entries a returned in the id order + and that the order implies that perent entry is always ahead of the + child entry in the list. Otherwise, the consumer would not be + properly updated because bulk import at the moment skips orphand entries. */ + /* XXXggood above assumption may not be valid if orphaned entry moved???? */ + + agmt_set_last_init_status(prp->agmt, 0, 0, "Total update in progress"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Beginning total update of replica " + "\"%s\".\n", agmt_get_long_name(prp->agmt)); + pb = slapi_pblock_new (); + + /* RMREPL - need to send schema here */ + + area_sdn = agmt_get_replarea(prp->agmt); + /* we need to provide managedsait control so that referral entries can + be replicated */ + ctrls = (LDAPControl **)slapi_ch_calloc (3, sizeof (LDAPControl *)); + ctrls[0] = create_managedsait_control (); + ctrls[1] = create_backend_control(area_sdn); + + slapi_search_internal_set_pb (pb, slapi_sdn_get_dn (area_sdn), + LDAP_SCOPE_SUBTREE, "(|(objectclass=ldapsubentry)(objectclass=nstombstone)(nsuniqueid=*))", NULL, 0, ctrls, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + cb_data.prp = prp; + cb_data.rc = 0; + cb_data.num_entries = 0UL; + cb_data.sleep_on_busy = 0UL; + cb_data.last_busy = current_time (); + + /* this search get all the entries from the replicated area including tombstones + and referrals */ + slapi_search_internal_callback_pb (pb, &cb_data /* callback data */, + get_result /* result callback */, + send_entry /* entry callback */, + NULL /* referral callback*/); + slapi_pblock_destroy (pb); + agmt_set_last_init_end(prp->agmt, current_time()); + rc = cb_data.rc; + release_replica(prp); + slapi_sdn_free(&area_sdn); + + if (rc != LDAP_SUCCESS) + { + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "%s: repl5_tot_run: " + "failed to obtain data to send to the consumer; LDAP error - %d\n", + agmt_get_long_name(prp->agmt), rc); + agmt_set_last_init_status(prp->agmt, rc, 0, "Total update aborted"); + } else { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Finished total update of replica " + "\"%s\". Sent %d entries.\n", agmt_get_long_name(prp->agmt), cb_data.num_entries); + agmt_set_last_init_status(prp->agmt, 0, 0, "Total update succeeded"); + } + +done: + slapi_ch_free_string(&hostname); + prp->stopped = 1; +} + +static int +repl5_tot_stop(Private_Repl_Protocol *prp) +{ + int return_value; + int seconds = 600; + PRIntervalTime start, maxwait, now; + + prp->terminate = 1; + maxwait = PR_SecondsToInterval(seconds); + start = PR_IntervalNow(); + now = start; + while (!prp->stopped && ((now - start) < maxwait)) + { + DS_Sleep(PR_SecondsToInterval(1)); + now = PR_IntervalNow(); + } + if (!prp->stopped) + { + /* Isn't listening. Disconnect from the replica. */ + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "repl5_tot_run: " + "protocol not stopped after waiting for %d seconds " + "for agreement %s\n", PR_IntervalToSeconds(now-start), + agmt_get_long_name(prp->agmt)); + conn_disconnect(prp->conn); + return_value = -1; + } + else + { + return_value = 0; + } + + return return_value; +} + + + +static int +repl5_tot_status(Private_Repl_Protocol *prp) +{ + int return_value = 0; + return return_value; +} + + + +static void +repl5_tot_noop(Private_Repl_Protocol *prp) +{ + /* noop */ +} + + +Private_Repl_Protocol * +Repl_5_Tot_Protocol_new(Repl_Protocol *rp) +{ + repl5_tot_private *rip = NULL; + Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol)); + prp->delete = repl5_tot_delete; + prp->run = repl5_tot_run; + prp->stop = repl5_tot_stop; + prp->status = repl5_tot_status; + prp->notify_update = repl5_tot_noop; + prp->notify_agmt_changed = repl5_tot_noop; + prp->notify_window_opened = repl5_tot_noop; + prp->notify_window_closed = repl5_tot_noop; + prp->update_now = repl5_tot_noop; + if ((prp->lock = PR_NewLock()) == NULL) + { + goto loser; + } + if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL) + { + goto loser; + } + prp->stopped = 1; + prp->terminate = 0; + prp->eventbits = 0; + prp->conn = prot_get_connection(rp); + prp->agmt = prot_get_agreement(rp); + rip = (void *)slapi_ch_malloc(sizeof(repl5_tot_private)); + rip->rp = rp; + prp->private = (void *)rip; + prp->replica_acquired = PR_FALSE; + return prp; +loser: + repl5_tot_delete(&prp); + return NULL; +} + +static void +repl5_tot_delete(Private_Repl_Protocol **prp) +{ +} + +static +void get_result (int rc, void *cb_data) +{ + PR_ASSERT (cb_data); + ((callback_data*)cb_data)->rc = rc; +} + +static +int send_entry (Slapi_Entry *e, void *cb_data) +{ + int rc; + Private_Repl_Protocol *prp; + BerElement *bere; + struct berval *bv; + unsigned long *num_entriesp; + time_t *sleep_on_busyp; + time_t *last_busyp; + + PR_ASSERT (cb_data); + + prp = ((callback_data*)cb_data)->prp; + num_entriesp = &((callback_data *)cb_data)->num_entries; + sleep_on_busyp = &((callback_data *)cb_data)->sleep_on_busy; + last_busyp = &((callback_data *)cb_data)->last_busy; + PR_ASSERT (prp); + + if (prp->terminate) + { + conn_disconnect(prp->conn); + prp->stopped = 1; + ((callback_data*)cb_data)->rc = -1; + return -1; + } + + /* skip ruv tombstone - need to do this because it might be + more up to date then the data we are sending to the client. + RUV is sent separately via the protocol */ + if (is_ruv_tombstone_entry (e)) + return 0; + + /* ONREPL we would purge copiedFrom and copyingFrom here but I decided against it. + Instead, it will get removed when this replica stops being 4.0 consumer and + then propagated to all its consumer */ + + /* convert the entry to the on the wire format */ + bere = entry2bere(e); + if (bere == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: send_entry: Encoding Error\n", + agmt_get_long_name(prp->agmt)); + ((callback_data*)cb_data)->rc = -1; + return -1; + } + + rc = ber_flatten(bere, &bv); + ber_free (bere, 1); + if (rc != 0) + { + ((callback_data*)cb_data)->rc = -1; + return -1; + } + + do { + /* push the entry to the consumer */ + rc = conn_send_extended_operation(prp->conn, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID, + bv /* payload */, NULL /* retoidp */, + NULL /* retdatap */, NULL /* update_control */, + NULL /* returned_controls */); + + if (rc == CONN_BUSY) { + time_t now = current_time (); + if ((now - *last_busyp) < (*sleep_on_busyp + 10)) { + *sleep_on_busyp +=5; + } + else { + *sleep_on_busyp = 5; + } + *last_busyp = now; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Replica \"%s\" is busy. Waiting %ds while" + " it finishes processing its current import queue\n", + agmt_get_long_name(prp->agmt), *sleep_on_busyp); + DS_Sleep(PR_SecondsToInterval(*sleep_on_busyp)); + } + } + while (rc == CONN_BUSY); + + ber_bvfree(bv); + (*num_entriesp)++; + + if (CONN_OPERATION_SUCCESS == rc) { + return 0; + } else { + ((callback_data*)cb_data)->rc = rc; + return -1; + } +} + diff --git a/ldap/servers/plugins/replication/repl5_total.c b/ldap/servers/plugins/replication/repl5_total.c new file mode 100644 index 00000000..66dcc353 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_total.c @@ -0,0 +1,869 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + +/* + repl5_total.c - code that implements a total replica update. + + The requestValue of the NSDS50ReplicationEntry looks like this: + + requestValue ::= SEQUENCE { + uniqueid OCTET STRING, + dn LDAPDN, + annotatedAttributes AnnotatedAttributeList + } + + AnnotatedAttributeList ::= SET OF SEQUENCE { + attributeType AttributeDescription, + attributeDeletionCSN OCTET STRING OPTIONAL, + attributeDeleted BOOLEAN DEFAULT FALSE, + annotatedValues SET OF AnnotatedValue + } + + AnnotatedValue ::= SEQUENCE { + value AttributeValue, + valueDeleted BOOLEAN DEFAULT FALSE, + valueCSNSet SEQUENCE OF ValueCSN, + } + + ValueCSN ::= SEQUENCE { + CSNType ENUMERATED { + valuePresenceCSN (1), + valueDeletionCSN (2), + valueDistinguishedCSN (3) + } + CSN OCTET STRING, + } +*/ + +#include "repl5.h" + +#define CSN_TYPE_VALUE_UPDATED_ON_WIRE 1 +#define CSN_TYPE_VALUE_DELETED_ON_WIRE 2 +#define CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE 3 + +/* #define GORDONS_PATENTED_BER_DEBUG 1 */ +#ifdef GORDONS_PATENTED_BER_DEBUG +#define BER_DEBUG(a) printf(a) +#else +#define BER_DEBUG(a) +#endif + +/* Forward declarations */ +static int my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t); +static int my_ber_printf_value(BerElement *ber, const char *type, + const Slapi_Value *value, PRBool deleted); +static int my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted); +static int my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted); +static int my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted); + +/* + * Get a Slapi_Entry ready to send over the wire as part of + * a total update protocol stream. Convert the entry and all + * of its state information to a BerElement which will be the + * payload of an extended LDAP operation. + * + * Entries consist of: + * - An entry DN + * - A uniqueID + * - A set of present attributes, each of which consists of: + * - A set of present values, each of which consists of: + * - A value + * - A set of CSNs + * - A set of deleted values, each of which consists of: + * - A value + * - A set of CSNs + * - A set of deleted attibutes, each of which consists of: + * - An attribute type + * - A set of CSNs. Note that this list of CSNs will always contain exactly one CSN. + * + * This all gets mashed into one BerElement, ready to be blasted over the wire to + * a replica. + * + */ +BerElement * +entry2bere(const Slapi_Entry *e) +{ + BerElement *ber = NULL; + const char *str = NULL; + const char *dnstr = NULL; + char *type; + Slapi_DN *sdn = NULL; + Slapi_Attr *attr = NULL, *prev_attr; + int rc; + + PR_ASSERT(NULL != e); + + if ((ber = ber_alloc()) == NULL) + { + goto loser; + } + BER_DEBUG("{"); + if (ber_printf(ber, "{") == -1) /* Begin outer sequence */ + { + goto loser; + } + + /* Get the entry's uniqueid */ + if ((str = slapi_entry_get_uniqueid(e)) == NULL) + { + goto loser; + } + BER_DEBUG("s(uniqueid)"); + if (ber_printf(ber, "s", str) == -1) + { + goto loser; + } + + /* Get the entry's DN */ + if ((sdn = slapi_entry_get_sdn((Slapi_Entry *)e)) == NULL) /* XXXggood had to cast away const */ + { + goto loser; + } + if ((dnstr = slapi_sdn_get_dn(sdn)) == NULL) + { + goto loser; + } + BER_DEBUG("s(dn)"); + if (ber_printf(ber, "s", dnstr) == -1) + { + goto loser; + } + + /* Next comes the annoted list of the entry's attributes */ + BER_DEBUG("["); + if (ber_printf(ber, "[") == -1) /* Begin set of attributes */ + { + goto loser; + } + /* + * We iterate over all of the non-deleted attributes first. + */ + slapi_entry_first_attr(e, &attr); + while (NULL != attr) + { + /* ONREPL - skip uniqueid attribute since we already sent uniqueid + This is a hack; need to figure a better way of storing uniqueid + in an entry */ + slapi_attr_get_type (attr, &type); + if (strcasecmp (type, SLAPI_ATTR_UNIQUEID) != 0) + { + /* Process this attribute */ + rc = my_ber_printf_attr (ber, attr, PR_FALSE); + if (rc != 0) + { + goto loser; + } + } + + prev_attr = attr; + slapi_entry_next_attr(e, prev_attr, &attr); + } + + /* + * Now iterate over the deleted attributes. + */ + entry_first_deleted_attribute(e, &attr); + while (attr != NULL) + { + /* Process this attribute */ + rc = my_ber_printf_attr (ber, attr, PR_TRUE); + if (rc != 0) + { + goto loser; + } + entry_next_deleted_attribute(e, &attr); + } + BER_DEBUG("]"); + if (ber_printf(ber, "]") == -1) /* End set for attributes */ + { + goto loser; + } + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End sequence for this entry */ + { + goto loser; + } + + /* If we get here, everything went ok */ + BER_DEBUG("\n"); + goto free_and_return; +loser: + if (NULL != ber) + { + ber_free(ber, 1); + ber = NULL; + } + +free_and_return: + return ber; +} + + +/* + * Helper function - convert a CSN to a string and ber_printf() it. + */ +static int +my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t) +{ + char csn_str[CSN_STRSIZE]; + unsigned long len; + int rc = -1; + int csn_type_as_ber = -1; + + switch (t) + { + case CSN_TYPE_VALUE_UPDATED: + csn_type_as_ber = CSN_TYPE_VALUE_UPDATED_ON_WIRE; + break; + case CSN_TYPE_VALUE_DELETED: + csn_type_as_ber = CSN_TYPE_VALUE_DELETED_ON_WIRE; + break; + case CSN_TYPE_VALUE_DISTINGUISHED: + csn_type_as_ber = CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE; + break; + case CSN_TYPE_ATTRIBUTE_DELETED: + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_printf_csn: unknown " + "csn type %d encountered.\n", (int)t); + return -1; + } + + csn_as_string(csn,PR_FALSE,csn_str); + + /* we don't send type for attr csn since there is only one */ + if (t == CSN_TYPE_ATTRIBUTE_DELETED) + { + rc = ber_printf(ber, "s", csn_str); + BER_DEBUG("s(csn_str)"); + } + else + { + len = CSN_STRSIZE; + rc = ber_printf(ber, "{es}", csn_type_as_ber, csn_str); + BER_DEBUG("{e(csn type)s(csn)}"); + } + + return rc; +} + + +/* + * Send a single annotated attribute value. + */ +static int +my_ber_printf_value(BerElement *ber, const char *type, const Slapi_Value *value, PRBool deleted) +{ + const struct berval *bval = NULL; + int rc = -1; + const CSNSet *csnset; + void *cookie; + CSN *csn; + CSNType t; + + bval = slapi_value_get_berval(value); + BER_DEBUG("{o(value)"); + if (ber_printf(ber, "{o", bval->bv_val, bval->bv_len) == -1) /* Start sequence */ + { + goto done; + } + +/* if (ber_printf(ber, "o", bval->bv_val, bval->bv_len) == -1) + { + goto done; + } */ + + if (deleted) + { + BER_DEBUG("b(deleted flag)"); + if (ber_printf (ber, "b", PR_TRUE) == -1) + { + goto done; + } + } + /* Send value CSN list */ + BER_DEBUG("{"); + if (ber_printf(ber, "{") == -1) /* Start set */ + { + goto done; + } + + /* Iterate over the sequence of CSNs. */ + csnset = value_get_csnset (value); + if (csnset) + { + for (cookie = csnset_get_first_csn (csnset, &csn, &t); NULL != cookie; + cookie = csnset_get_next_csn (csnset, cookie, &csn, &t)) + { + /* Don't send any adcsns, since that was already sent */ + if (t != CSN_TYPE_ATTRIBUTE_DELETED) + { + if (my_ber_printf_csn(ber, csn, t) == -1) + { + goto done; + } + } + } + } + + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End CSN sequence */ + { + goto done; + } + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End sequence */ + { + goto done; + } + + /* Everything's ok */ + rc = 0; + +done: + return rc; + +} + +/* send a single attribute */ +static int +my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted) +{ + Slapi_Value *value; + char *type; + int i; + const CSN *csn; + + /* First, send the type */ + slapi_attr_get_type(attr, &type); + BER_DEBUG("{s(type "); + BER_DEBUG(type); + BER_DEBUG(")"); + if (ber_printf(ber, "{s", type) == -1) /* Begin sequence for this type */ + { + goto loser; + } + + /* Send the attribute deletion CSN if present */ + csn = attr_get_deletion_csn(attr); + if (csn) + { + if (my_ber_printf_csn(ber, csn, CSN_TYPE_ATTRIBUTE_DELETED) == -1) + { + goto loser; + } + } + + /* only send "is deleted" flag for deleted attributes since it defaults to false */ + if (deleted) + { + BER_DEBUG("b(del flag)"); + if (ber_printf (ber, "b", PR_TRUE) == -1) + { + goto loser; + } + } + + /* + * Iterate through all the values. + */ + BER_DEBUG("["); + if (ber_printf(ber, "[") == -1) /* Begin set */ + { + goto loser; + } + + /* + * Process the non-deleted values first. + */ + i = slapi_attr_first_value(attr, &value); + while (i != -1) + { + if (my_ber_printf_value(ber, type, value, PR_FALSE) == -1) + { + goto loser; + } + i= slapi_attr_next_value(attr, i, &value); + } + + /* + * Now iterate over all of the deleted values. + */ + i= attr_first_deleted_value(attr, &value); + while (i != -1) + { + if (my_ber_printf_value(ber, type, value, PR_TRUE) == -1) + { + goto loser; + } + i= attr_next_deleted_value(attr, i, &value); + } + BER_DEBUG("]"); + if (ber_printf(ber, "]") == -1) /* End set */ + { + goto loser; + } + + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End sequence for this type */ + { + goto loser; + } + + return 0; +loser: + return -1; +} + +/* + * Get an annotated value from the BerElement. Returns 0 on + * success, -1 on failure. + */ +static int +my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted) +{ + struct berval *attrval = NULL; + unsigned long len; + unsigned long tag; + CSN *csn = NULL; + char csnstring[CSN_STRSIZE + 1]; + CSNType csntype; + char *lasti; + + PR_ASSERT(ber && value && deleted); + + *value = NULL; + + if (NULL == ber && NULL == value) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 1\n"); + goto loser; + } + + /* Each value is a sequence */ + if (ber_scanf(ber, "{O", &attrval) == -1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 2\n"); + goto loser; + } + /* Allocate and fill in the attribute value */ + if ((*value = slapi_value_new_berval(attrval)) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 3\n"); + goto loser; + } + + /* check if this is a deleted value */ + if (ber_peek_tag(ber, &len) == LBER_BOOLEAN) + { + if (ber_scanf(ber, "b", deleted) == -1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 4\n"); + goto loser; + } + } + + else /* default is present value */ + { + *deleted = PR_FALSE; + } + + /* Read the sequence of CSNs */ + for (tag = ber_first_element(ber, &len, &lasti); + tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET; + tag = ber_next_element(ber, &len, lasti)) + { + long csntype_tmp; + /* Each CSN is in a sequence that includes a csntype and CSN */ + len = CSN_STRSIZE; + if (ber_scanf(ber, "{es}", &csntype_tmp, csnstring, &len) == -1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 7 - bval is %s\n", attrval->bv_val); + goto loser; + } + switch (csntype_tmp) + { + case CSN_TYPE_VALUE_UPDATED_ON_WIRE: + csntype = CSN_TYPE_VALUE_UPDATED; + break; + case CSN_TYPE_VALUE_DELETED_ON_WIRE: + csntype = CSN_TYPE_VALUE_DELETED; + break; + case CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE: + csntype = CSN_TYPE_VALUE_DISTINGUISHED; + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: preposterous CSN type " + "%d received during total update.\n", csntype_tmp); + goto loser; + } + csn = csn_new_by_string(csnstring); + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 8\n"); + goto loser; + } + value_add_csn(*value, csntype, csn); + csn_free (&csn); + } + + if (ber_scanf(ber, "}") == -1) /* End of annotated attribute value seq */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 10\n"); + goto loser; + } + + if (attrval) + ber_bvfree(attrval); + return 0; + +loser: + /* Free any stuff we allocated */ + if (csn) + csn_free (&csn); + if (attrval) + ber_bvfree(attrval); + if (value) + { + slapi_value_free (value); + } + + return -1; +} + +static int +my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted) +{ + char *attrtype = NULL; + CSN *attr_deletion_csn = NULL; + PRBool val_deleted; + char *lasti; + unsigned long len; + unsigned long tag; + char *str; + int rc; + Slapi_Value *value; + + PR_ASSERT (ber && attr && deleted); + + /* allocate the attribute */ + *attr = slapi_attr_new (); + if (attr == NULL) + { + goto loser; + } + + if (ber_scanf(ber, "{a", &attrtype) == -1) /* Begin sequence for this attr */ + { + goto loser; + } + + + slapi_attr_init(*attr, attrtype); + slapi_ch_free ((void **)&attrtype); + + /* The attribute deletion CSN is next and is optional? */ + if (ber_peek_tag(ber, &len) == LBER_OCTETSTRING) + { + if (ber_scanf(ber, "a", &str) == -1) + { + goto loser; + } + attr_deletion_csn = csn_new_by_string(str); + slapi_ch_free((void **)&str); + } + + if (attr_deletion_csn) + { + rc = attr_set_deletion_csn(*attr, attr_deletion_csn); + csn_free (&attr_deletion_csn); + if (rc != 0) + { + goto loser; + } + } + + /* The "attribute deleted" flag is next, and is optional */ + if (ber_peek_tag(ber, &len) == LBER_BOOLEAN) + { + if (ber_scanf(ber, "b", deleted) == -1) + { + goto loser; + } + } + else /* default is present */ + { + *deleted = PR_FALSE; + } + + /* loop over the list of attribute values */ + for (tag = ber_first_element(ber, &len, &lasti); + tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET; + tag = ber_next_element(ber, &len, lasti)) + { + + value = NULL; + if (my_ber_scanf_value(ber, &value, &val_deleted) == -1) + { + goto loser; + } + + if (val_deleted) + { + /* Add the value to the attribute */ + if (attr_add_deleted_value(*attr, value) == -1) /* attr has ownership of value */ + { + goto loser; + } + } + else + { + /* Add the value to the attribute */ + if (slapi_attr_add_value(*attr, value) == -1) /* attr has ownership of value */ + { + goto loser; + } + } + if (value) + slapi_value_free(&value); + } + + if (ber_scanf(ber, "}") == -1) /* End sequence for this attribute */ + { + goto loser; + } + + return 0; +loser: + if (*attr) + slapi_attr_free (attr); + if (value) + slapi_value_free (&value); + + return -1; +} + +/* + * Extract the payload from a total update extended operation, + * decode it, and produce a Slapi_Entry structure representing a new + * entry to be added to the local database. + */ +static int +decode_total_update_extop(Slapi_PBlock *pb, Slapi_Entry **ep) +{ + BerElement *tmp_bere = NULL; + Slapi_Entry *e = NULL; + Slapi_Attr *attr = NULL; + char *str = NULL; + CSN *dn_csn = NULL; + struct berval *extop_value = NULL; + char *extop_oid = NULL; + unsigned long len; + char *lasto; + unsigned long tag; + int rc; + PRBool deleted; + + PR_ASSERT(NULL != pb); + PR_ASSERT(NULL != ep); + + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid); + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if (NULL == extop_oid || + strcmp(extop_oid, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID) != 0 || + NULL == extop_value) + { + /* Bogus */ + goto loser; + } + + if ((tmp_bere = ber_init(extop_value)) == NULL) + { + goto loser; + } + + if ((e = slapi_entry_alloc()) == NULL) + { + goto loser; + } + + if (ber_scanf(tmp_bere, "{") == -1) /* Begin outer sequence */ + { + goto loser; + } + + /* The entry's uniqueid is first */ + if (ber_scanf(tmp_bere, "a", &str) == -1) + { + goto loser; + } + slapi_entry_set_uniqueid(e, str); + str = NULL; /* Slapi_Entry now owns the uniqueid */ + + /* The entry's DN is next */ + if (ber_scanf(tmp_bere, "a", &str) == -1) + { + goto loser; + } + slapi_entry_set_dn(e, str); + str = NULL; /* Slapi_Entry now owns the dn */ + + /* Get the attributes */ + for ( tag = ber_first_element( tmp_bere, &len, &lasto ); + tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET; + tag = ber_next_element( tmp_bere, &len, lasto ) ) + { + + if (my_ber_scanf_attr (tmp_bere, &attr, &deleted) != 0) + { + goto loser; + } + + /* Add the attribute to the entry */ + if (deleted) + entry_add_deleted_attribute_wsi(e, attr); /* entry now owns attr */ + else + entry_add_present_attribute_wsi(e, attr); /* entry now owns attr */ + attr = NULL; + } + + if (ber_scanf(tmp_bere, "}") == -1) /* End sequence for this entry */ + { + goto loser; + } + + /* Check for ldapsubentries and tombstone entries to set flags properly */ + slapi_entry_attr_find(e, "objectclass", &attr); + if (attr != NULL) { + struct berval bv; + bv.bv_val = "ldapsubentry"; + bv.bv_len = strlen(bv.bv_val); + if (slapi_attr_value_find(attr, &bv) == 0) { + slapi_entry_set_flag(e, SLAPI_ENTRY_LDAPSUBENTRY); + } + bv.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE; + bv.bv_len = strlen(bv.bv_val); + if (slapi_attr_value_find(attr, &bv) == 0) { + slapi_entry_set_flag(e, SLAPI_ENTRY_FLAG_TOMBSTONE); + } + } + + /* If we get here, the entry is properly constructed. Return it. */ + + rc = 0; + *ep = e; + goto free_and_return; + +loser: + rc = -1; + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&str); + + if (NULL != dn_csn) + { + csn_free(&dn_csn); + } + if (attr != NULL) + { + slapi_attr_free (&attr); + } + + if (NULL != e) + { + slapi_entry_free (e); + } + *ep = NULL; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: could not decode extended " + "operation containing entry for total update.\n"); + +free_and_return: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + return rc; +} + +/* + * This plugin entry point is called whenever an NSDS50ReplicationEntry + * extended operation is received. + */ +int +multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb) +{ + int rc; + Slapi_Entry *e = NULL; + Slapi_Connection *conn = NULL; + int connid, opid; + + connid = 0; + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + opid = 0; + slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid); + + /* Decode the extended operation */ + rc = decode_total_update_extop(pb, &e); + + if (0 == rc) + { +#ifdef notdef + /* + * Just spew LDIF so we're sure we got it right. Later we'll firehose + * this into the database import code + */ + int len; + char *str = slapi_entry2str_with_options(e, &len,SLAPI_DUMP_UNIQUEID); + puts(str); + free(str); +#endif + + rc = slapi_import_entry (pb, e); + /* slapi_import_entry return an LDAP error in case of problem + * LDAP_BUSY is used to indicate that the import queue is full + * and that flow control must happen to stop the supplier + * from sending entries + */ + if ((rc != LDAP_SUCCESS) && (rc != LDAP_BUSY)) + { + const char *dn = slapi_entry_get_dn_const(e); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Error %d: could not import entry dn %s " + "for total update operation conn=%d op=%d\n", + rc, dn, connid, opid); + rc = -1; + } + + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Error %d: could not decode the total update extop " + "for total update operation conn=%d op=%d\n", + rc, connid, opid); + } + + if ((rc != 0) && (rc != LDAP_BUSY)) + { + /* just disconnect from the supplier. bulk import is stopped when + connection object is destroyed */ + slapi_pblock_get (pb, SLAPI_CONNECTION, &conn); + if (conn) + { + slapi_disconnect_server(conn); + } + + /* cleanup */ + if (e) + { + slapi_entry_free (e); + } + } + + return rc; +} diff --git a/ldap/servers/plugins/replication/repl5_updatedn_list.c b/ldap/servers/plugins/replication/repl5_updatedn_list.c new file mode 100644 index 00000000..02304d19 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_updatedn_list.c @@ -0,0 +1,243 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_updatedn_list.c */ + +/* + This is the internal representation for the list of update DNs in the replica. + The list is implemented as a hash table - the key is the normalized DN, and the + value is the Slapi_DN representation of the DN +*/ + +#include "repl5.h" +#include "plhash.h" + +/* global data */ + +/* typedef ReplicaUpdateDNList PLHashTable; */ + +struct repl_enum_data +{ + FNEnumDN fn; + void *arg; +}; + +/* Forward declarations */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg); +static PRIntn updatedn_list_enumerate (PLHashEntry *he, PRIntn index, void *hash_data); + +static int +updatedn_compare_dns(const void *d1, const void *d2) +{ + return (0 == slapi_sdn_compare((const Slapi_DN *)d1, (const Slapi_DN *)d2)); +} + +/* create a new updatedn list - if the entry is given, initialize the list from + the replicabinddn values given in the entry */ +ReplicaUpdateDNList +replica_updatedn_list_new(const Slapi_Entry *entry) +{ + /* allocate table */ + PLHashTable *hash = PL_NewHashTable(4, PL_HashString, PL_CompareStrings, + updatedn_compare_dns, NULL, NULL); + if (hash == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_new_updatedn_list: " + "failed to allocate hash table; NSPR error - %d\n", + PR_GetError ()); + return NULL; + } + + if (entry) { + Slapi_Attr *attr = NULL; + if (!slapi_entry_attr_find(entry, attr_replicaBindDn, &attr)) { + Slapi_ValueSet *vs = NULL; + slapi_attr_get_valueset(attr, &vs); + replica_updatedn_list_replace(hash, vs); + slapi_valueset_free(vs); + } + } + + return (ReplicaUpdateDNList)hash; +} + +void +replica_updatedn_list_free(ReplicaUpdateDNList list) +{ + /* destroy the content */ + PLHashTable *hash = list; + PL_HashTableEnumerateEntries(hash, replica_destroy_hash_entry, NULL); + + if (hash) + PL_HashTableDestroy(hash); +} + +void +replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs) +{ + replica_updatedn_list_delete(list, NULL); /* delete all values */ + replica_updatedn_list_add(list, vs); +} + +/* if vs is given, delete only those values - otherwise, delete all values */ +void +replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs) +{ + PLHashTable *hash = list; + if (!vs || slapi_valueset_count(vs) == 0) { /* just delete everything */ + PL_HashTableEnumerateEntries(hash, replica_destroy_hash_entry, NULL); + } else { + Slapi_ValueSet *vs_nc = (Slapi_ValueSet *)vs; /* cast away const */ + Slapi_Value *val = NULL; + int index = 0; + for (index = slapi_valueset_first_value(vs_nc, &val); val; + index = slapi_valueset_next_value(vs_nc, index, &val)) { + Slapi_DN *dn = slapi_sdn_new_dn_byval(slapi_value_get_string(val)); + /* locate object */ + Slapi_DN *deldn = (Slapi_DN *)PL_HashTableLookup(hash, slapi_sdn_get_ndn(dn)); + if (deldn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_updatedn_list_delete: " + "update DN with value (%s) is not in the update DN list.\n", + slapi_sdn_get_ndn(dn)); + } else { + /* remove from hash */ + PL_HashTableRemove(hash, slapi_sdn_get_ndn(dn)); + /* free the pointer */ + slapi_sdn_free(&deldn); + } + /* free the temp dn */ + slapi_sdn_free(&dn); + } + } + + return; +} + +void +replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs) +{ + PLHashTable *hash = list; + Slapi_ValueSet *vs_nc = (Slapi_ValueSet *)vs; /* cast away const */ + Slapi_Value *val = NULL; + int index = 0; + + PR_ASSERT(list && vs); + + for (index = slapi_valueset_first_value(vs_nc, &val); val; + index = slapi_valueset_next_value(vs_nc, index, &val)) { + Slapi_DN *dn = slapi_sdn_new_dn_byval(slapi_value_get_string(val)); + const char *ndn = slapi_sdn_get_ndn(dn); + + /* make sure that the name is unique */ + if (PL_HashTableLookup(hash, ndn) != NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_updatedn_list_add: " + "update DN with value (%s) already in the update DN list\n", + ndn); + slapi_sdn_free(&dn); + } else { + PL_HashTableAdd(hash, ndn, dn); + } + } + + return; +} + +PRBool +replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn) +{ + PLHashTable *hash = list; + PRBool ret = PR_FALSE; + + const char *ndn = slapi_sdn_get_ndn(dn); + + /* Bug 605169 - null ndn would cause core dump */ + if ( ndn ) { + ret = (PRBool)PL_HashTableLookupConst(hash, ndn); + } + + return ret; +} + +struct list_to_string_data { + char *string; + const char *delimiter; +}; + +static int +convert_to_string(Slapi_DN *dn, void *arg) +{ + struct list_to_string_data *data = (struct list_to_string_data *)arg; + int newlen = strlen(slapi_sdn_get_dn(dn)) + strlen(data->delimiter) + 1; + if (data->string) { + newlen += strlen(data->string); + data->string = slapi_ch_realloc(data->string, newlen); + } else { + data->string = slapi_ch_calloc(1, newlen); + } + strcat(data->string, slapi_sdn_get_dn(dn)); + strcat(data->string, data->delimiter); + + return 1; +} + +/* caller must slapi_ch_free_string the returned string */ +char * +replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter) +{ + struct list_to_string_data data; + data.string = NULL; + data.delimiter = delimiter; + replica_updatedn_list_enumerate(list, convert_to_string, (void *)&data); + return data.string; +} + +void +replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg) +{ + PLHashTable *hash = list; + struct repl_enum_data data; + + PR_ASSERT (fn); + + data.fn = fn; + data.arg = arg; + + PL_HashTableEnumerateEntries(hash, updatedn_list_enumerate, &data); +} + +/* Helper functions */ + +/* this function called for each hash node during hash destruction */ +static PRIntn +replica_destroy_hash_entry(PLHashEntry *he, PRIntn index, void *arg) +{ + Slapi_DN *dn = NULL; + + if (he == NULL) + return HT_ENUMERATE_NEXT; + + dn = (Slapi_DN *)he->value; + PR_ASSERT (dn); + + slapi_sdn_free(&dn); + + return HT_ENUMERATE_REMOVE; +} + +static PRIntn +updatedn_list_enumerate(PLHashEntry *he, PRIntn index, void *hash_data) +{ + Slapi_DN *dn = NULL; + struct repl_enum_data *data = hash_data; + + dn = (Slapi_DN*)he->value; + PR_ASSERT (dn); + + data->fn(dn, data->arg); + + return HT_ENUMERATE_NEXT; +} diff --git a/ldap/servers/plugins/replication/repl_add.c b/ldap/servers/plugins/replication/repl_add.c new file mode 100644 index 00000000..3d47c1db --- /dev/null +++ b/ldap/servers/plugins/replication/repl_add.c @@ -0,0 +1,30 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + + +/* Add Operation Plugin Functions for legacy replication plugin */ + +int +legacy_preop_add( Slapi_PBlock *pb ) +{ + return legacy_preop( pb, "legacy_preop_add", OP_ADD ); +} + +int +legacy_bepreop_add( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + return rc; +} + +int +legacy_postop_add( Slapi_PBlock *pb ) +{ + return legacy_postop( pb, "legacy_postop_add", OP_ADD ); +} diff --git a/ldap/servers/plugins/replication/repl_bind.c b/ldap/servers/plugins/replication/repl_bind.c new file mode 100644 index 00000000..e317e311 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_bind.c @@ -0,0 +1,48 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" + + +int +legacy_preop_bind( Slapi_PBlock *pb ) +{ + int return_value = 0; + char *dn = NULL; + struct berval *cred = NULL; + int method; + int one = 1; + + slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); + slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn); + slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &cred); + + if (LDAP_AUTH_SIMPLE == method) + { + if (legacy_consumer_is_replicationdn(dn) && legacy_consumer_is_replicationpw(cred)) + { + /* Successful bind as replicationdn */ + void *conn = NULL; + consumer_connection_extension *connext = NULL; +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, "legacy_preop_bind: begin\n"); +#endif + slapi_pblock_get( pb, SLAPI_CONNECTION, &conn ); + connext = (consumer_connection_extension*) repl_con_get_ext (REPL_CON_EXT_CONN, conn); + if (NULL != connext) + { + connext->is_legacy_replication_dn = 1; + } + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + return_value = 1; /* Prevent further processing in front end */ + } + } + return return_value; + +} diff --git a/ldap/servers/plugins/replication/repl_compare.c b/ldap/servers/plugins/replication/repl_compare.c new file mode 100644 index 00000000..34f2b944 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_compare.c @@ -0,0 +1,34 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +int +legacy_preop_compare( Slapi_PBlock *pb ) +{ + int is_replicated_operation = 0; + char *compare_base = NULL; + struct berval **referral = NULL; + int return_code = 0; + Slapi_DN *basesdn; + + slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation); + slapi_pblock_get(pb, SLAPI_COMPARE_TARGET, &compare_base); + basesdn= slapi_sdn_new_dn_byref(compare_base); + referral = get_data_source(pb, basesdn, 1, NULL); + slapi_sdn_free(&basesdn); + if (NULL != referral && !is_replicated_operation) + { + /* + * There is a copyingFrom in this entry or an ancestor. + * Return a referral to the supplier, and we're all done. + */ + slapi_send_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, referral); + return_code = 1; /* return 1 to prevent further search processing */ + } + return return_code; +} diff --git a/ldap/servers/plugins/replication/repl_connext.c b/ldap/servers/plugins/replication/repl_connext.c new file mode 100644 index 00000000..8b0c0551 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_connext.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl_connext.c - replication extension to the Connection object + */ + + +#include "repl.h" +#include "repl5.h" + + +/* ***** Supplier side ***** */ + +/* NOT NEEDED YET */ + +/* ***** Consumer side ***** */ + +/* consumer connection extension constructor */ +void* consumer_connection_extension_constructor (void *object, void *parent) +{ + consumer_connection_extension *ext = (consumer_connection_extension*) slapi_ch_malloc (sizeof (consumer_connection_extension)); + if (ext == NULL) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication consumer connection extension - out of memory\n" ); + } + else + { + ext->is_legacy_replication_dn= 0; + ext->repl_protocol_version = REPL_PROTOCOL_UNKNOWN; + ext->replica_acquired = NULL; + ext->isreplicationsession= 0; + ext->supplier_ruv = NULL; + ext->connection = NULL; + } + + return ext; +} + +/* consumer connection extension destructor */ +void consumer_connection_extension_destructor (void *ext, void *object, void *parent) +{ + int connid = 0; + if (ext) + { + /* Check to see if this replication session has acquired + * a replica. If so, release it here. + */ + consumer_connection_extension *connext = (consumer_connection_extension *)ext; + if (NULL != connext->replica_acquired) + { + Replica *r = object_get_data ((Object*)connext->replica_acquired); + /* If a total update was in progress, abort it */ + if (REPL_PROTOCOL_50_TOTALUPDATE == connext->repl_protocol_version) + { + Slapi_PBlock *pb = slapi_pblock_new(); + const Slapi_DN *repl_root_sdn = replica_get_root(r); + PR_ASSERT(NULL != repl_root_sdn); + if (NULL != repl_root_sdn) + { + slapi_pblock_set(pb, SLAPI_CONNECTION, connext->connection); + slapi_pblock_set(pb, SLAPI_TARGET_DN, (void*)slapi_sdn_get_dn(repl_root_sdn)); + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Aborting total update in progress for replicated " + "area %s connid=%d\n", slapi_sdn_get_dn(repl_root_sdn), + connid); + slapi_stop_bulk_import(pb); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "consumer_connection_extension_destructor: can't determine root " + "of replicated area.\n"); + } + slapi_pblock_destroy(pb); + } + replica_relinquish_exclusive_access(r, connid, -1); + object_release ((Object*)connext->replica_acquired); + connext->replica_acquired = NULL; + } + + if (connext->supplier_ruv) + { + ruv_destroy ((RUV **)&connext->supplier_ruv); + } + connext->connection = NULL; + slapi_ch_free((void **)&ext); + } +} diff --git a/ldap/servers/plugins/replication/repl_controls.c b/ldap/servers/plugins/replication/repl_controls.c new file mode 100644 index 00000000..ae1cb119 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_controls.c @@ -0,0 +1,337 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl5.h" +#include "repl.h" /* For LDAP_CONTROL_REPL_MODRDN_EXTRAMODS */ + +/* + * repl_controls.c - convenience functions for creating and + * decoding controls that implement 5.0-style replication + * protocol operations. + * + * TODO: Send modrdn mods with modrdn operation + * Fix ber_printf() and ber_scanf() format strings - some are + * the wrong types. + */ + +/* + * Return a pointer to a NSDS50ReplUpdateInfoControl. + * The control looks like this: + * + * NSDS50ReplUpdateInfoControl ::= SEQUENCE { + * uuid OCTET STRING, + * csn OCTET STRING, + * OPTIONAL [new]superior-uuid OCTET STRING + * OPTIONAL modrdn_mods XXXggood WHAT TYPE??? + * } + */ +int +create_NSDS50ReplUpdateInfoControl(const char *uuid, + const char *superior_uuid, const CSN *csn, + LDAPMod **modrdn_mods, LDAPControl **ctrlp) +{ + int retval; + BerElement *tmp_bere = NULL; + struct berval tmpval = {0}; + char csn_str[CSN_STRSIZE]; + + if (NULL == ctrlp) + { + retval = LDAP_PARAM_ERROR; + goto loser; + } + else + { + if ((tmp_bere = ber_alloc()) == NULL) + { + retval = LDAP_NO_MEMORY; + goto loser; + } + else + { + /* Stuff uuid and csn into BerElement */ + if (ber_printf(tmp_bere, "{") == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + /* Stuff uuid of this entry into BerElement */ + if (ber_printf(tmp_bere, "s", uuid) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + /* Stuff csn of this change into BerElement */ + csn_as_string(csn, PR_FALSE, csn_str); + if (ber_printf(tmp_bere, "s", csn_str) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + /* If present, stuff uuid of parent entry into BerElement */ + if (NULL != superior_uuid) + { + if (ber_printf(tmp_bere, "s", superior_uuid) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + } + + /* If present, add the modrdn mods */ + if (NULL != modrdn_mods) + { + int i; + if (ber_printf(tmp_bere, "{" ) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + /* for each modification to be performed... */ + for (i = 0; NULL != modrdn_mods[i]; i++) + { + if (ber_printf(tmp_bere, "{e{s[V]}}", + modrdn_mods[i]->mod_op & ~LDAP_MOD_BVALUES, + modrdn_mods[i]->mod_type, modrdn_mods[i]->mod_bvalues ) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + } + if (ber_printf(tmp_bere, "}") == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + } + + /* Close the sequence */ + if (ber_printf(tmp_bere, "}") == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + retval = slapi_build_control( REPL_NSDS50_UPDATE_INFO_CONTROL_OID, + tmp_bere, 1 /* is critical */, ctrlp); + } + } +loser: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + return retval; +} + + +/* + * Destroy a ReplUpdateInfoControl and set the pointer to NULL. + */ +void +destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp) +{ + if (NULL != ctrlp && NULL != *ctrlp) + { + ldap_control_free(*ctrlp); + *ctrlp = NULL; + } +} + + + + +/* + * Look through the array of controls. If an NSDS50ReplUpdateInfoControl + * is present, decode it and return pointers to the broken-out + * components. The caller is responsible for freeing pointers to + * the returned objects. The caller may indicate that it is not + * interested in any of the output parameters by passing NULL + * for that parameter. + * + * Returns 0 if the control is not present, 1 if it is present, and + * -1 if an error occurs. + */ +int +decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp, + char **uuid, char **superior_uuid, + CSN **csn, LDAPMod ***modrdn_mods) +{ + struct berval *ctl_value = NULL; + int iscritical = 0; + int rc = -1; + struct berval uuid_val = {0}; + struct berval superior_uuid_val = {0}; + struct berval csn_val = {0}; + BerElement *tmp_bere = NULL; + Slapi_Mods modrdn_smods; + PRBool got_modrdn_mods = PR_FALSE; + unsigned long len; + + slapi_mods_init(&modrdn_smods, 4); + if (slapi_control_present(controlsp, REPL_NSDS50_UPDATE_INFO_CONTROL_OID, + &ctl_value, &iscritical)) + { + if ((tmp_bere = ber_init(ctl_value)) == NULL) + { + rc = -1; + goto loser; + } + if (ber_scanf(tmp_bere, "{oo", &uuid_val, &csn_val) == -1) + { + rc = -1; + goto loser; + } + if (ber_peek_tag(tmp_bere, &len) == LBER_OCTETSTRING) + { + /* The optional superior_uuid is present */ + if (ber_scanf(tmp_bere, "o", &superior_uuid_val) == -1) + { + rc = -1; + goto loser; + } + } + if (ber_peek_tag(tmp_bere, &len) == LBER_SEQUENCE) + { + unsigned long emtag, emlen; + char *emlast; + + for ( emtag = ber_first_element( tmp_bere, &emlen, &emlast ); + emtag != LBER_ERROR && emtag != LBER_END_OF_SEQORSET; + emtag = ber_next_element( tmp_bere, &emlen, emlast )) + { + struct berval **embvals; + long op; + char *type; + if ( ber_scanf( tmp_bere, "{i{a[V]}}", &op, &type, &embvals ) == LBER_ERROR ) + { + rc = -1; + goto loser; + } + slapi_mods_add_modbvps(&modrdn_smods, op, type, embvals); + free( type ); + ber_bvecfree( embvals ); + } + got_modrdn_mods = PR_TRUE; + } + if (ber_scanf(tmp_bere, "}") == -1) + { + rc = -1; + goto loser; + } + + if (NULL != uuid) + { + *uuid = slapi_ch_malloc(uuid_val.bv_len + 1); + strncpy(*uuid, uuid_val.bv_val, uuid_val.bv_len); + (*uuid)[uuid_val.bv_len] = '\0'; + } + + if (NULL != csn) + { + char *csnstr = slapi_ch_malloc(csn_val.bv_len + 1); + strncpy(csnstr, csn_val.bv_val, csn_val.bv_len); + csnstr[csn_val.bv_len] = '\0'; + *csn = csn_new_by_string(csnstr); + slapi_ch_free((void **)&csnstr); + } + + if (NULL != superior_uuid && NULL != superior_uuid_val.bv_val) + { + *superior_uuid = slapi_ch_malloc(superior_uuid_val.bv_len + 1); + strncpy(*superior_uuid, superior_uuid_val.bv_val, + superior_uuid_val.bv_len); + (*superior_uuid)[superior_uuid_val.bv_len] = '\0'; + } + + if (NULL != modrdn_mods && got_modrdn_mods) + { + *modrdn_mods = slapi_mods_get_ldapmods_passout(&modrdn_smods); + } + slapi_mods_done(&modrdn_smods); + + rc = 1; + } + else + { + rc = 0; + } +loser: + /* XXXggood free CSN here if allocated */ + + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + if (NULL != uuid_val.bv_val) + { + ldap_memfree(uuid_val.bv_val); + uuid_val.bv_val = NULL; + } + if (NULL != superior_uuid_val.bv_val) + { + ldap_memfree(superior_uuid_val.bv_val); + superior_uuid_val.bv_val = NULL; + } + if (NULL != csn_val.bv_val) + { + ldap_memfree(csn_val.bv_val); + csn_val.bv_val = NULL; + } + return rc; +} + + + +void +add_repl_control_mods( Slapi_PBlock *pb, Slapi_Mods *smods ) +{ + struct berval *embvp; + LDAPControl **controls = NULL; + + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls); + if ( slapi_control_present( controls, + LDAP_CONTROL_REPL_MODRDN_EXTRAMODS, + &embvp, NULL )) + { + if ( embvp != NULL && embvp->bv_len > 0 && embvp->bv_val != NULL ) + { + /* Parse the extramods stuff */ + long op; + char *type; + unsigned long emlen; + unsigned long emtag; + char *emlast; + BerElement *ember = ber_init( embvp ); + if ( ember != NULL ) + { + for ( emtag = ber_first_element( ember, &emlen, &emlast ); + emtag != LBER_ERROR && emtag != LBER_END_OF_SEQORSET; + emtag = ber_next_element( ember, &emlen, emlast )) + { + struct berval **embvals; + if ( ber_scanf( ember, "{i{a[V]}}", &op, &type, &embvals ) == LBER_ERROR ) + { + continue; + /* GGOODREPL I suspect this will cause two sets of lastmods attr values + to end up in the entry. We need to remove the old ones. + */ + } + slapi_mods_add_modbvps( smods, op, type, embvals); + free( type ); + ber_bvecfree( embvals ); + } + } + ber_free( ember, 1 ); + } + } +} diff --git a/ldap/servers/plugins/replication/repl_delete.c b/ldap/servers/plugins/replication/repl_delete.c new file mode 100644 index 00000000..6f8e07df --- /dev/null +++ b/ldap/servers/plugins/replication/repl_delete.c @@ -0,0 +1,26 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +int +legacy_preop_delete( Slapi_PBlock *pb ) +{ + return legacy_preop(pb, "legacy_preop_delete", OP_DELETE); +} + +int +legacy_bepreop_delete( Slapi_PBlock *pb ) +{ + return 0; /* OK */ +} + +int +legacy_postop_delete( Slapi_PBlock *pb ) +{ + return legacy_postop(pb, "legacy_preop_delete", OP_DELETE); +} diff --git a/ldap/servers/plugins/replication/repl_entry.c b/ldap/servers/plugins/replication/repl_entry.c new file mode 100644 index 00000000..83bf2857 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_entry.c @@ -0,0 +1,38 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +static int dumping_to_ldif= 0; +static int doing_replica_init= 0; +static char **include_suffix= NULL; + +/* + * This is passed the slapd command line arguments. + */ +void +repl_entry_init(int argc, char** argv) +{ + int i; + for(i=1;i<argc;i++) + { + if(strcmp(argv[i],"db2ldif")==0) + { + dumping_to_ldif= 1; + } + if(strcmp(argv[i],"-r")==0) + { + doing_replica_init= 1; + } + if(strcmp(argv[i],"-s")==0) + { + char *s= slapi_dn_normalize ( slapi_ch_strdup(argv[i+1]) ); + charray_add(&include_suffix,s); + i++; + } + } +} diff --git a/ldap/servers/plugins/replication/repl_ext.c b/ldap/servers/plugins/replication/repl_ext.c new file mode 100644 index 00000000..4ad28726 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_ext.c @@ -0,0 +1,113 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl_ext.c - manages operation extensions created by the + * replication system + */ + + +#include "repl.h" + +/* structure with information for each extension */ +typedef struct repl_ext +{ + char *object_name; /* name of the object extended */ + int object_type; /* handle to the extended object */ + int handle; /* extension handle */ +} repl_ext; + +/* ----------------------------- Supplier ----------------------------- */ + +static repl_ext repl_sup_ext_list [REPL_EXT_ALL]; + +/* initializes replication extensions */ +void repl_sup_init_ext () +{ + int rc; + + /* populate the extension list */ + repl_sup_ext_list[REPL_SUP_EXT_OP].object_name = SLAPI_EXT_OPERATION; + + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_OPERATION, + supplier_operation_extension_constructor, + supplier_operation_extension_destructor, + &repl_sup_ext_list[REPL_SUP_EXT_OP].object_type, + &repl_sup_ext_list[REPL_SUP_EXT_OP].handle); + + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } +} + +void* repl_sup_get_ext (ext_type type, void *object) +{ + /* find the requested extension */ + repl_ext ext = repl_sup_ext_list [type]; + + void* data = slapi_get_object_extension(ext.object_type, object, ext.handle); + + return data; +} + +/* ----------------------------- Consumer ----------------------------- */ + +static repl_ext repl_con_ext_list [REPL_EXT_ALL]; + +/* initializes replication extensions */ +void repl_con_init_ext () +{ + int rc; + + /* populate the extension list */ + repl_con_ext_list[REPL_CON_EXT_OP].object_name = SLAPI_EXT_OPERATION; + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_OPERATION, + consumer_operation_extension_constructor, + consumer_operation_extension_destructor, + &repl_con_ext_list[REPL_CON_EXT_OP].object_type, + &repl_con_ext_list[REPL_CON_EXT_OP].handle); + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } + + repl_con_ext_list[REPL_CON_EXT_CONN].object_name = SLAPI_EXT_CONNECTION; + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_CONNECTION, + consumer_connection_extension_constructor, + consumer_connection_extension_destructor, + &repl_con_ext_list[REPL_CON_EXT_CONN].object_type, + &repl_con_ext_list[REPL_CON_EXT_CONN].handle); + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } + + repl_con_ext_list[REPL_CON_EXT_MTNODE].object_name = SLAPI_EXT_MTNODE; + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_MTNODE, + multimaster_mtnode_extension_constructor, + multimaster_mtnode_extension_destructor, + &repl_con_ext_list[REPL_CON_EXT_MTNODE].object_type, + &repl_con_ext_list[REPL_CON_EXT_MTNODE].handle); + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } +} + +void* repl_con_get_ext (ext_type type, void *object) +{ + /* find the requested extension */ + repl_ext ext = repl_con_ext_list [type]; + + void* data = slapi_get_object_extension(ext.object_type, object, ext.handle); + + return data; +} + + diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c new file mode 100644 index 00000000..b13ad6ac --- /dev/null +++ b/ldap/servers/plugins/replication/repl_extop.c @@ -0,0 +1,1134 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "repl5_prot_private.h" +#include "cl5_api.h" + + +/* + * repl_extop.c - there are two types of functions in this file: + * - Code that implements an extended operation plugin. + * The replication DLL arranges for this code to + * be called when a StartNSDS50ReplicationRequest + * or an EndNSDS50ReplicationRequest extended operation + * is received. + * - Code that sends extended operations on an already- + * established client connection. + * + * The requestValue portion of the StartNSDS50ReplicationRequest + * looks like this: + * + * requestValue ::= SEQUENCE { + * replProtocolOID LDAPOID, + * replicatedTree LDAPDN, + supplierRUV OCTET STRING + * referralURLs SET of LDAPURL OPTIONAL + * csn OCTET STRING OPTIONAL + * } + * + */ +static int check_replica_id_uniqueness(Replica *replica, RUV *supplier_ruv); + +static int +encode_ruv (BerElement *ber, const RUV *ruv) +{ + int rc = LDAP_SUCCESS; + struct berval **bvals = NULL; + + PR_ASSERT (ber); + PR_ASSERT (ruv); + + if (ruv_to_bervals(ruv, &bvals) != 0) + { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (ber_printf(ber, "[V]", bvals) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto done; + } + + rc = LDAP_SUCCESS; + +done: + if (bvals) + ber_bvecfree (bvals); + + return rc; +} + +static struct berval * +create_NSDS50ReplicationExtopPayload(const char *protocol_oid, + const char *repl_root, char **extra_referrals, CSN *csn, + int send_end) +{ + struct berval *req_data = NULL; + BerElement *tmp_bere = NULL; + int rc = 0; + const char *csnstr = NULL; + Object *repl_obj, *ruv_obj = NULL; + Replica *repl; + RUV *ruv; + Slapi_DN *sdn; + + PR_ASSERT(protocol_oid != NULL || send_end); + PR_ASSERT(repl_root != NULL); + + /* Create the request data */ + + if ((tmp_bere = der_alloc()) == NULL) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + if (!send_end) + { + if (ber_printf(tmp_bere, "{ss", protocol_oid, repl_root) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + } + else + { + if (ber_printf(tmp_bere, "{s", repl_root) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + } + + sdn = slapi_sdn_new_dn_byref(repl_root); + repl_obj = replica_get_replica_from_dn (sdn); + if (repl_obj == NULL) + { + rc = LDAP_OPERATIONS_ERROR; + goto loser; + } + + repl = (Replica*)object_get_data (repl_obj); + PR_ASSERT (repl); + ruv_obj = replica_get_ruv (repl); + if (ruv_obj == NULL) + { + rc = LDAP_OPERATIONS_ERROR; + goto loser; + } + ruv = object_get_data(ruv_obj); + PR_ASSERT(ruv); + + /* send supplier's ruv so that consumer can build its own referrals. + In case of total protocol, it is also used as consumer's ruv once + protocol successfully completes */ + /* We need to encode and send each time the local ruv in case we have changed it */ + rc = encode_ruv (tmp_bere, ruv); + if (rc != 0) + { + goto loser; + } + + if (!send_end) + { + char s[CSN_STRSIZE]; + ReplicaId rid; + char *local_replica_referral[2] = {0}; + char **referrals_to_send = NULL; + /* Add the referral URL(s), if present */ + rid = replica_get_rid(repl); + if (!ruv_contains_replica(ruv, rid)) + { + /* + * In the event that there is no RUV component for this replica (e.g. + * if the database was just loaded from LDIF and no local CSNs have been + * generated), then we need to explicitly add this server to the list + * of referrals, since it wouldn't have been sent with the RUV. + */ + local_replica_referral[0] = (char *)multimaster_get_local_purl(); /* XXXggood had to cast away const */ + } + charray_merge(&referrals_to_send, extra_referrals, 0); + charray_merge(&referrals_to_send, local_replica_referral, 0); + if (NULL != referrals_to_send) + { + if (ber_printf(tmp_bere, "[v]", referrals_to_send) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + slapi_ch_free((void **)&referrals_to_send); + } + /* Add the CSN */ + PR_ASSERT(NULL != csn); + if (ber_printf(tmp_bere, "s", csnstr = csn_as_string(csn,PR_FALSE,s)) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + } + + if (ber_printf(tmp_bere, "}") == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + + if (ber_flatten(tmp_bere, &req_data) == -1) + { + rc = LDAP_LOCAL_ERROR; + goto loser; + } + /* Success */ + goto done; + +loser: + /* Free stuff we allocated */ + if (NULL != req_data) + { + ber_bvfree(req_data); req_data = NULL; + } + +done: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); tmp_bere = NULL; + } + if (NULL != sdn) + { + slapi_sdn_free (&sdn); /* Put on stack instead of allocating? */ + } + if (NULL != repl_obj) + { + object_release (repl_obj); + } + if (NULL != ruv_obj) + { + object_release (ruv_obj); + } + return req_data; +} + + +struct berval * +NSDS50StartReplicationRequest_new(const char *protocol_oid, + const char *repl_root, char **extra_referrals, CSN *csn) +{ + return(create_NSDS50ReplicationExtopPayload(protocol_oid, + repl_root, extra_referrals, csn, 0)); +} + +struct berval * +NSDS50EndReplicationRequest_new(char *repl_root) +{ + return(create_NSDS50ReplicationExtopPayload(NULL, repl_root, NULL, NULL, 1)); +} + +static int +decode_ruv (BerElement *ber, RUV **ruv) +{ + int rc = -1; + struct berval **bvals = NULL; + + PR_ASSERT (ber && ruv); + + if (ber_scanf(ber, "[V]", &bvals) == -1) + { + goto done; + } + + if (ruv_init_from_bervals(bvals, ruv) != 0) + { + goto done; + } + + rc = 0; +done: + if (bvals) + ber_bvecfree (bvals); + + return rc; +} + +/* + * Decode an NSDS50 Start Replication Request extended + * operation. Returns 0 on success, -1 on decoding error. + * The caller is responsible for freeing protocol_oid, + * repl_root, referrals, and csn. + */ +static int +decode_startrepl_extop(Slapi_PBlock *pb, char **protocol_oid, char **repl_root, + RUV **supplier_ruv, char ***extra_referrals, char **csnstr) +{ + char *extop_oid = NULL; + struct berval *extop_value = NULL; + BerElement *tmp_bere = NULL; + unsigned long len; + int rc = 0; + + PR_ASSERT (pb && protocol_oid && repl_root && supplier_ruv && extra_referrals && csnstr); + + *protocol_oid = NULL; + *repl_root = NULL; + *supplier_ruv = NULL; + *extra_referrals = NULL; + *csnstr = NULL; + + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid); + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if (NULL == extop_oid || + strcmp(extop_oid, REPL_START_NSDS50_REPLICATION_REQUEST_OID) != 0 || + NULL == extop_value) + { + /* bogus */ + rc = -1; + goto free_and_return; + } + + if ((tmp_bere = ber_init(extop_value)) == NULL) + { + rc = -1; + goto free_and_return; + } + if (ber_scanf(tmp_bere, "{") == -1) + { + rc = -1; + goto free_and_return; + } + /* Get the required protocol OID and root of replicated subtree */ + if (ber_get_stringa(tmp_bere, protocol_oid) == -1) + { + rc = -1; + goto free_and_return; + } + if (ber_get_stringa(tmp_bere, repl_root) == -1) + { + rc = -1; + goto free_and_return; + } + + /* get supplier's ruv */ + if (decode_ruv (tmp_bere, supplier_ruv) == -1) + { + rc = -1; + goto free_and_return; + } + + /* Get the optional set of referral URLs */ + if (ber_peek_tag(tmp_bere, &len) == LBER_SET) + { + if (ber_scanf(tmp_bere, "[v]", extra_referrals) == -1) + { + rc = -1; + goto free_and_return; + } + } + /* Get the optional CSN */ + if (ber_peek_tag(tmp_bere, &len) == LBER_OCTETSTRING) + { + if (ber_get_stringa(tmp_bere, csnstr) == -1) + { + rc = -1; + goto free_and_return; + } + } + if (ber_scanf(tmp_bere, "}") == -1) + { + rc = -1; + goto free_and_return; + } + +free_and_return: + if (-1 == rc) + { + /* Free everything when error encountered */ + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)protocol_oid); + slapi_ch_free ((void**)repl_root); + slapi_ch_free ((void **)extra_referrals); + slapi_ch_free ((void**)csnstr); + + if (*supplier_ruv) + { + ruv_destroy (supplier_ruv); + } + + } + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + + return rc; +} + + +/* + * Decode an NSDS50 End Replication Request extended + * operation. Returns 0 on success, -1 on decoding error. + * The caller is responsible for freeing repl_root. + */ +static int +decode_endrepl_extop(Slapi_PBlock *pb, char **repl_root) +{ + char *extop_oid = NULL; + struct berval *extop_value = NULL; + BerElement *tmp_bere = NULL; + int rc = 0; + + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid); + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if (NULL == extop_oid || + strcmp(extop_oid, REPL_END_NSDS50_REPLICATION_REQUEST_OID) != 0 || + NULL == extop_value) + { + /* bogus */ + rc = -1; + goto free_and_return; + } + + if ((tmp_bere = ber_init(extop_value)) == NULL) + { + rc = -1; + goto free_and_return; + } + if (ber_scanf(tmp_bere, "{") == -1) + { + rc = -1; + goto free_and_return; + } + /* Get the required root of replicated subtree */ + if (ber_get_stringa(tmp_bere, repl_root) == -1) + { + rc = -1; + goto free_and_return; + } + if (ber_scanf(tmp_bere, "}") == -1) + { + rc = -1; + goto free_and_return; + } + +free_and_return: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + + return rc; +} + + + + +/* + * Decode an NSDS50ReplicationResponse extended response. + * The extended response just contains a sequence that contains: + * 1) An integer response code + * 2) An optional array of bervals representing the consumer + * replica's update vector + * Returns 0 on success, or -1 if the response could not be parsed. + */ +int +decode_repl_ext_response(struct berval *data, int *response_code, + struct berval ***ruv_bervals) +{ + BerElement *tmp_bere = NULL; + int return_value = 0; + + PR_ASSERT(NULL != response_code); + PR_ASSERT(NULL != ruv_bervals); + + if (NULL == data || NULL == response_code || NULL == ruv_bervals) + { + return_value = -1; + } + else + { + unsigned long len, tag = 0; + long temp_response_code = 0; + *ruv_bervals = NULL; + if ((tmp_bere = ber_init(data)) == NULL) + { + return_value = -1; + } + else if (ber_scanf(tmp_bere, "{e", &temp_response_code) == -1) + { + return_value = -1; + } + else if ((tag = ber_peek_tag(tmp_bere, &len)) == LBER_SEQUENCE) + { + if (ber_scanf(tmp_bere, "{V}}", ruv_bervals) == -1) + { + return_value = -1; + } + } else if (ber_scanf(tmp_bere, "}") == -1) + { + return_value = -1; + } + *response_code = (int)temp_response_code; + } + if (0 != return_value) + { + if (NULL != *ruv_bervals) + { + ber_bvecfree(*ruv_bervals); + } + } + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); tmp_bere = NULL; + } + return return_value; +} + + +/* + * This plugin entry point is called whenever a + * StartNSDS50ReplicationRequest is received. + */ +int +multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb) +{ + int return_value = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; + int response = 0; + int rc = 0; + BerElement *resp_bere = NULL; + struct berval *resp_bval = NULL; + char *protocol_oid = NULL; + char *repl_root = NULL; + Slapi_DN *repl_root_sdn = NULL; + char **referrals = NULL; + Object *replica_object = NULL; + Replica *replica = NULL; + void *conn; + consumer_connection_extension *connext = NULL; + CSN *mycsn = NULL; + char *replicacsnstr = NULL; + CSN *replicacsn = NULL; + int zero = 0; + int one = 1; + RUV *ruv = NULL; + struct berval **ruv_bervals = NULL; + CSNGen *gen = NULL; + Object *gen_obj = NULL; + Slapi_DN *bind_sdn = NULL; + char *bind_dn = NULL; + Object *ruv_object = NULL; + RUV *supplier_ruv = NULL; + int connid, opid; + PRBool isInc = PR_FALSE; /* true if incremental update */ + char *locking_purl = NULL; /* the supplier contacting us */ + char *current_purl = NULL; /* the supplier which already has exclusive access */ + char locking_session[24]; + + /* Decode the extended operation */ + if (decode_startrepl_extop(pb, &protocol_oid, &repl_root, &supplier_ruv, + &referrals, &replicacsnstr) == -1) + { + response = NSDS50_REPL_DECODING_ERROR; + goto send_response; + } + if (NULL == protocol_oid || NULL == repl_root || NULL == replicacsnstr) + { + response = NSDS50_REPL_DECODING_ERROR; + goto send_response; + } + + connid = 0; + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + opid = 0; + slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid); + + /* + * Get a hold of the connection extension object and + * make sure it's there. + */ + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + connext = (consumer_connection_extension *)repl_con_get_ext( + REPL_CON_EXT_CONN, conn); + if (NULL == connext) + { + /* Something bad happened. Don't go any further */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + /* Verify that we know about this replication protocol OID */ + if (strcmp(protocol_oid, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID) == 0) + { + /* Stash info that this is an incremental update session */ + connext->repl_protocol_version = REPL_PROTOCOL_50_INCREMENTAL; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": Begin incremental protocol\n", + connid, opid, repl_root); + isInc = PR_TRUE; + } + else if (strcmp(protocol_oid, REPL_NSDS50_TOTAL_PROTOCOL_OID) == 0) + { + /* Stash info that this is a total update session */ + if (NULL != connext) + { + connext->repl_protocol_version = REPL_PROTOCOL_50_TOTALUPDATE; + } + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": Begin total protocol\n", + connid, opid, repl_root); + isInc = PR_FALSE; + } + else + { + /* Unknown replication protocol */ + response = NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL; + goto send_response; + } + + /* Verify that repl_root names a valid replicated area */ + if ((repl_root_sdn = slapi_sdn_new_dn_byval(repl_root)) == NULL) + { + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + /* see if this replica is being configured and wait for it */ + if (replica_is_being_configured(repl_root)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d replica=\"%s\": " + "Replica is being configured: try again later\n", + connid, opid, repl_root); + response = NSDS50_REPL_REPLICA_BUSY; + goto send_response; + } + + replica_object = replica_get_replica_from_dn(repl_root_sdn); + if (NULL != replica_object) + { + replica = object_get_data(replica_object); + } + if (NULL == replica) + { + response = NSDS50_REPL_NO_SUCH_REPLICA; + goto send_response; + } + + /* check that this replica is not a 4.0 consumer */ + if (replica_is_legacy_consumer (replica)) + { + response = NSDS50_REPL_LEGACY_CONSUMER; + goto send_response; + } + + /* Check that bind dn is authorized to supply replication updates */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn); /* bind_dn is allocated */ + bind_sdn = slapi_sdn_new_dn_passin(bind_dn); + if (replica_is_updatedn(replica, bind_sdn) == PR_FALSE) + { + response = NSDS50_REPL_PERMISSION_DENIED; + goto send_response; + } + + /* Check received CSN for clock skew */ + gen_obj = replica_get_csngen(replica); + if (NULL != gen_obj) + { + gen = object_get_data(gen_obj); + if (NULL != gen) + { + if (csngen_new_csn(gen, &mycsn, PR_FALSE /* notify */) == CSN_SUCCESS) + { + replicacsn = csn_new_by_string(replicacsnstr); + if (NULL != replicacsn) + { + /* ONREPL - we used to manage clock skew here. However, csn generator + code already does it. The csngen also manages local skew caused by + system clock reset, so to keep it consistent, I removed code from here */ + time_t diff = 0L; + diff = csn_time_difference(mycsn, replicacsn); + if (diff > 0) + { + /* update the state of the csn generator */ + rc = csngen_adjust_time (gen, replicacsn); + if (rc == CSN_LIMIT_EXCEEDED) /* too much skew */ + { + response = NSDS50_REPL_EXCESSIVE_CLOCK_SKEW; + goto send_response; + } + } + else if (diff <= 0) + { + /* Supplier's clock is behind ours */ + /* XXXggood check if CSN smaller than purge point */ + /* response = NSDS50_REPL_BELOW_PURGEPOINT; */ + /* goto send_response; */ + } + } + else + { + /* Oops, csnstr couldn't be converted */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + } + else + { + /* Oops, csn generator failed */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + /* update csn generator's state from the supplier's ruv */ + rc = replica_update_csngen_state (replica, supplier_ruv); /* too much skew */ + if (rc != 0) + { + response = NSDS50_REPL_EXCESSIVE_CLOCK_SKEW; + goto send_response; + } + } + else + { + /* Oops, no csn generator */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + } + else + { + /* Oops, no csn generator object */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + if (check_replica_id_uniqueness(replica, supplier_ruv) != 0){ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Replica has same replicaID %d as supplier\n", + connid, opid, repl_root, replica_get_rid(replica)); + response = NSDS50_REPL_REPLICAID_ERROR; + goto send_response; + } + + /* Attempt to acquire exclusive access to the replicated area */ + /* Since partial URL is always the master, this locking_purl does not + * help us to know the true locker when it is a hub. Change to use + * the session's conn id and op id to identify the the supplier. + */ + /* junkrc = ruv_get_first_id_and_purl(supplier_ruv, &junkrid, &locking_purl); */ + sprintf(locking_session, "conn=%d id=%d", connid, opid); + locking_purl = &locking_session[0]; + if (replica_get_exclusive_access(replica, &isInc, connid, opid, + locking_purl, + ¤t_purl) == PR_FALSE) + { + locking_purl = NULL; /* no dangling pointers */ + response = NSDS50_REPL_REPLICA_BUSY; + goto send_response; + } + else + { + locking_purl = NULL; /* no dangling pointers */ + /* Stick the replica object pointer in the connection extension */ + connext->replica_acquired = (void *)replica_object; + replica_object = NULL; + } + + /* If this is incremental protocol get replica's ruv to return to the supplier */ + if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL) + { + ruv_object = replica_get_ruv(replica); + if (NULL != ruv_object) + { + ruv = object_get_data(ruv_object); + (void)ruv_to_bervals(ruv, &ruv_bervals); + object_release(ruv_object); + } + } + + /* + * Save the supplier ruv in the connection extension so it can + * either (a) be installed upon successful initialization (if this + * is a total update session) or used to update referral information + * for new replicas that show up in the supplier's RUV. + */ + /* + * the supplier_ruv may have been set before, so free it here + * (in ruv_copy_and_destroy) + */ + ruv_copy_and_destroy(&supplier_ruv, (RUV **)&connext->supplier_ruv); + + if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL) + { + /* The supplier ruv may have changed, so let's update the referrals */ + consumer5_set_mapping_tree_state_for_replica(replica, connext->supplier_ruv); + } + else /* full protocol */ + { + char *mtnstate = slapi_mtn_get_state(repl_root_sdn); + char **mtnreferral = slapi_mtn_get_referral(repl_root_sdn); + + /* richm 20010831 - set the mapping tree to the referral state *before* + we invoke slapi_start_bulk_import - see bug 556992 - + slapi_start_bulk_import sets the database offline, if an operation comes + in while the database is offline but the mapping tree is not referring yet, + the server gets confused + */ + /* During a total update we refer *all* operations */ + repl_set_mtn_state_and_referrals(repl_root_sdn, STATE_REFERRAL, + connext->supplier_ruv, NULL, referrals); + /* LPREPL - check the return code. + * But what do we do if mapping tree could not be updated ? */ + + /* start the bulk import */ + slapi_pblock_set (pb, SLAPI_TARGET_DN, repl_root); + rc = slapi_start_bulk_import (pb); + if (rc != LDAP_SUCCESS) + { + response = NSDS50_REPL_INTERNAL_ERROR; + /* reset the mapping tree state to what it was before + we tried to do the bulk import */ + repl_set_mtn_state_and_referrals(repl_root_sdn, mtnstate, + NULL, NULL, mtnreferral); + slapi_ch_free_string(&mtnstate); + charray_free(mtnreferral); + mtnreferral = NULL; + + goto send_response; + } + slapi_ch_free_string(&mtnstate); + charray_free(mtnreferral); + mtnreferral = NULL; + } + + response = NSDS50_REPL_REPLICA_READY; + /* Set the "is replication session" flag in the connection extension */ + slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &one ); + connext->isreplicationsession = 1; + /* Save away the connection */ + slapi_pblock_get(pb, SLAPI_CONNECTION, &connext->connection); + +send_response: + if (response != NSDS50_REPL_REPLICA_READY) + { + int resp_log_level = SLAPI_LOG_FATAL; + char purlstr[1024] = {0}; + if (current_purl) + sprintf(purlstr, " locked by %s for %s update", current_purl, + isInc ? "incremental" : "total"); + + /* Don't log replica busy as errors - these are almost always not + errors - use the replication monitoring tools to determine if + a replica is not converging, then look for pathological replica + busy errors by turning on the replication log level */ + if (response == NSDS50_REPL_REPLICA_BUSY) { + resp_log_level = SLAPI_LOG_REPL; + } + + slapi_log_error (resp_log_level, repl_plugin_name, + "conn=%d op=%d replica=\"%s\": " + "Unable to acquire replica: error: %s%s\n", + connid, opid, + (replica ? slapi_sdn_get_dn(replica_get_root(replica)) : "unknown"), + protocol_response2string (response), purlstr); + } + /* Send the response */ + if ((resp_bere = der_alloc()) == NULL) + { + /* ONREPL - not sure what we suppose to do here */ + } + ber_printf(resp_bere, "{e", response); + if (NULL != ruv_bervals) + { + ber_printf(resp_bere, "{V}", ruv_bervals); + } + ber_printf(resp_bere, "}"); + ber_flatten(resp_bere, &resp_bval); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "StartNSDS50ReplicationRequest: response=%d rc=%d\n", + connid, opid, repl_root, + response, rc); + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + + return_value = SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + + slapi_ch_free_string(¤t_purl); + + /* protocol_oid */ + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&protocol_oid); + + /* repl_root */ + slapi_ch_free((void **)&repl_root); + + /* supplier's ruv */ + if (supplier_ruv) + { + ruv_destroy (&supplier_ruv); + } + /* referrals */ + slapi_ch_free((void **)&referrals); + + /* replicacsnstr */ + slapi_ch_free((void **)&replicacsnstr); + + /* repl_root_sdn */ + if (NULL != repl_root_sdn) + { + slapi_sdn_free(&repl_root_sdn); + } + if (NSDS50_REPL_REPLICA_READY != response) + { + /* + * Something went wrong, and we never told the other end that the + * replica had been acquired, so we'd better release it. + */ + if (NULL != connext && NULL != connext->replica_acquired) + { + Object *r_obj = (Object*)connext->replica_acquired; + replica_relinquish_exclusive_access((Replica*)object_get_data (r_obj), + connid, opid); + } + /* Remove any flags that would indicate repl session in progress */ + if (NULL != connext) + { + connext->repl_protocol_version = REPL_PROTOCOL_UNKNOWN; + connext->isreplicationsession = 0; + } + slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &zero ); + } + /* Release reference to replica_object */ + if (NULL != replica_object) + { + object_release(replica_object); + } + /* bind_sdn */ + if (NULL != bind_sdn) + { + slapi_sdn_free(&bind_sdn); + } + /* Release reference to gen_obj */ + if (NULL != gen_obj) + { + object_release(gen_obj); + } + /* mycsn */ + if (NULL != mycsn) + { + csn_free(&mycsn); + } + /* replicacsn */ + if (NULL != replicacsn) + { + csn_free(&replicacsn); + } + /* resp_bere */ + if (NULL != resp_bere) + { + ber_free(resp_bere, 1); + } + /* resp_bval */ + if (NULL != resp_bval) + { + ber_bvfree(resp_bval); + } + /* ruv_bervals */ + if (NULL != ruv_bervals) + { + ber_bvecfree(ruv_bervals); + } + + return return_value; +} + +/* + * This plugin entry point is called whenever an + * EndNSDS50ReplicationRequest is received. + * XXXggood this code is not finished. + */ +int +multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb) +{ + int return_value = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; + char *repl_root = NULL; + BerElement *resp_bere = NULL; + struct berval *resp_bval = NULL; + int response; + void *conn; + consumer_connection_extension *connext = NULL; + int rc; + int connid=-1, opid=-1; + + /* Decode the extended operation */ + if (decode_endrepl_extop(pb, &repl_root) == -1) + { + response = NSDS50_REPL_DECODING_ERROR; + } + else + { + + /* First, verify that the current connection is a replication session */ + /* XXXggood - do we need to wait around for any pending updates to complete? + I suppose it's possible that the end request may arrive asynchronously, before + we're really done processing all the updates. + */ + /* Get a hold of the connection extension object */ + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + connext = (consumer_connection_extension *)repl_con_get_ext( + REPL_CON_EXT_CONN, conn); + if (NULL != connext && NULL != connext->replica_acquired) + { + int zero= 0; + Replica *r = (Replica*)object_get_data ((Object*)connext->replica_acquired); + + /* if this is total protocol we need to install suppliers ruv for the replica */ + if (connext->repl_protocol_version == REPL_PROTOCOL_50_TOTALUPDATE) + { + /* We no longer need to refer all operations... + * and update the referrals on the mapping tree node + */ + consumer5_set_mapping_tree_state_for_replica(r, NULL); + + /* LPREPL - First we clear the total in progress flag + Like this we know it's a normal termination of import. This is required by + the replication function that responds to backend state change. + If the flag is not clear, the callback knows that replication should not be + enabled again */ + replica_set_state_flag(r, REPLICA_TOTAL_IN_PROGRESS, PR_TRUE /* clear flag */); + + slapi_pblock_set (pb, SLAPI_TARGET_DN, repl_root); + slapi_stop_bulk_import (pb); + + /* ONREPL - this is a bit of a hack. Once bulk import is finished, + the replication function that responds to backend state change + will be called. That function normally do all ruv and changelog + processing. However, in the case of replica initalization, it + will not do the right thing because supplier does not send its + ruv tombstone to the consumer. So that's why we need to do the + second processing here. + The supplier does not send its RUV entry because it could be + more up to date then the data send to the consumer. + The best solution I think, would be to "fake" on the supplier + an entry that corresponds to the ruv sent to the consumer and then + send it as part of the data */ + + if (cl5GetState () == CL5_STATE_OPEN) + { + rc = cl5DeleteDBSync (connext->replica_acquired); + } + + replica_set_ruv (r, connext->supplier_ruv); + connext->supplier_ruv = NULL; + + /* if changelog is enabled, we need to log a dummy change for the + smallest csn in the new ruv, so that this replica ca supply + other servers. + */ + if (cl5GetState () == CL5_STATE_OPEN) + { + replica_log_ruv_elements (r); + } + + /* ONREPL code that dealt with new RUV, etc was moved into the code + that enables replication when a backend comes back online. This + code is called once the bulk import is finished */ + } + else if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL) + { + /* The ruv from the supplier may have changed. Report the change on the + consumer side */ + + replica_update_ruv_consumer(r, connext->supplier_ruv); + } + + /* Relinquish control of the replica */ + slapi_pblock_get (pb, SLAPI_OPERATION_ID, &opid); + if (opid) slapi_pblock_get (pb, SLAPI_CONN_ID, &connid); + replica_relinquish_exclusive_access(r, connid, opid); + object_release ((Object*)connext->replica_acquired); + connext->replica_acquired = NULL; + connext->isreplicationsession= 0; + slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &zero ); + response = NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED; + /* Outbound replication agreements need to all be restarted now */ + /* XXXGGOOD RESTART REEPL AGREEMENTS */ + } + } + + /* Send the response code */ + if ((resp_bere = der_alloc()) == NULL) + { + rc = LDAP_ENCODING_ERROR; + goto free_and_return; + } + ber_printf(resp_bere, "{e}", response); + ber_flatten(resp_bere, &resp_bval); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval); + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + + return_value = SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + +free_and_return: + /* repl_root */ + slapi_ch_free((void **)&repl_root); + + /* BerElement */ + if (NULL != resp_bere) + { + ber_free(resp_bere, 1); + } + /* response */ + if (NULL != resp_bval) + { + ber_bvfree(resp_bval); + } + + return return_value; +} + +/* + * This plugin entry point is a noop entry + * point. It's used when registering extops that + * are only used as responses. We'll never receive + * one of those, unsolicited, but we still want to + * register them so they appear in the + * supportedextension attribute in the root DSE. + */ +int +extop_noop(Slapi_PBlock *pb) +{ + return SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; +} + + +static int +check_replica_id_uniqueness(Replica *replica, RUV *supplier_ruv) +{ + ReplicaId local_rid = replica_get_rid(replica); + ReplicaId sup_rid = 0; + char *sup_purl = NULL; + + if (ruv_get_first_id_and_purl(supplier_ruv, &sup_rid, &sup_purl) == RUV_SUCCESS) { + /* ReplicaID Uniqueness is checked only on Masters */ + if ((replica_get_type(replica) == REPLICA_TYPE_UPDATABLE) && + (sup_rid == local_rid)) { + return 1; + } + } + return 0; +} + + + diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c new file mode 100644 index 00000000..bee5dc4a --- /dev/null +++ b/ldap/servers/plugins/replication/repl_globals.c @@ -0,0 +1,108 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "nspr.h" +#include "repl.h" + +char *repl_plugin_name = REPL_PLUGIN_NAME; +char *repl_plugin_name_cl = REPL_PLUGIN_NAME " - changelog program"; + +/* String constants (no need to change these for I18N) */ + +#define CHANGETYPE_ADD "add" +#define CHANGETYPE_DELETE "delete" +#define CHANGETYPE_MODIFY "modify" +#define CHANGETYPE_MODRDN "modrdn" +#define CHANGETYPE_MODDN "moddn" +#define ATTR_CHANGENUMBER "changenumber" +#define ATTR_TARGETDN "targetdn" +#define ATTR_CHANGETYPE "changetype" +#define ATTR_NEWRDN "newrdn" +#define ATTR_DELETEOLDRDN "deleteoldrdn" +#define ATTR_CHANGES "changes" +#define ATTR_NEWSUPERIOR "newsuperior" +#define ATTR_CHANGETIME "changetime" +#define ATTR_DATAVERSION "dataVersion" +#define ATTR_CSN "csn" +#define TYPE_COPYINGFROM "copyingFrom" +#define TYPE_COPIEDFROM "copiedFrom" +#define FILTER_COPYINGFROM "copyingFrom=*" +#define FILTER_COPIEDFROM "copiedFrom=*" +#define FILTER_OBJECTCLASS "objectclass=*" + + +char *changetype_add = CHANGETYPE_ADD; +char *changetype_delete = CHANGETYPE_DELETE; +char *changetype_modify = CHANGETYPE_MODIFY; +char *changetype_modrdn = CHANGETYPE_MODRDN; +char *changetype_moddn = CHANGETYPE_MODDN; +char *attr_changenumber = ATTR_CHANGENUMBER; +char *attr_targetdn = ATTR_TARGETDN; +char *attr_changetype = ATTR_CHANGETYPE; +char *attr_newrdn = ATTR_NEWRDN; +char *attr_deleteoldrdn = ATTR_DELETEOLDRDN; +char *attr_changes = ATTR_CHANGES; +char *attr_newsuperior = ATTR_NEWSUPERIOR; +char *attr_changetime = ATTR_CHANGETIME; +char *attr_dataversion = ATTR_DATAVERSION; +char *attr_csn = ATTR_CSN; +char *type_copyingFrom = TYPE_COPYINGFROM; +char *type_copiedFrom = TYPE_COPIEDFROM; +char *filter_copyingFrom = FILTER_COPYINGFROM; +char *filter_copiedFrom = FILTER_COPIEDFROM; +char *filter_objectclass = FILTER_OBJECTCLASS; +char *type_cn = "cn"; +char *type_objectclass = "objectclass"; + +/* Names for replica attributes */ +const char *attr_replicaId = "nsDS5ReplicaId"; +const char *attr_replicaRoot = "nsDS5ReplicaRoot"; +const char *attr_replicaType = "nsDS5ReplicaType"; +const char *attr_replicaBindDn = "nsDS5ReplicaBindDn"; +const char *attr_state = "nsState"; +const char *attr_flags = "nsds5Flags"; +const char *attr_replicaName = "nsds5ReplicaName"; +const char *attr_replicaReferral = "nsds5ReplicaReferral"; +const char *type_ruvElement = "nsds50ruv"; +const char *type_replicaPurgeDelay = "nsds5ReplicaPurgeDelay"; +const char *type_replicaChangeCount = "nsds5ReplicaChangeCount"; +const char *type_replicaTombstonePurgeInterval = "nsds5ReplicaTombstonePurgeInterval"; +const char *type_replicaLegacyConsumer = "nsds5ReplicaLegacyConsumer"; +const char *type_ruvElementUpdatetime = "nsruvReplicaLastModified"; + +/* Attribute names for replication agreement attributes */ +const char *type_nsds5ReplicaHost = "nsds5ReplicaHost"; +const char *type_nsds5ReplicaPort = "nsds5ReplicaPort"; +const char *type_nsds5TransportInfo = "nsds5ReplicaTransportInfo"; +const char *type_nsds5ReplicaBindDN = "nsds5ReplicaBindDN"; +const char *type_nsds5ReplicaCredentials = "nsds5ReplicaCredentials"; +const char *type_nsds5ReplicaBindMethod = "nsds5ReplicaBindMethod"; +const char *type_nsds5ReplicaRoot = "nsds5ReplicaRoot"; +const char *type_nsds5ReplicatedAttributeList = "nsds5ReplicatedAttributeList"; +const char *type_nsds5ReplicaUpdateSchedule = "nsds5ReplicaUpdateSchedule"; +const char *type_nsds5ReplicaInitialize = "nsds5BeginReplicaRefresh"; +const char *type_nsds5ReplicaTimeout = "nsds5ReplicaTimeout"; +const char *type_nsds5ReplicaBusyWaitTime = "nsds5ReplicaBusyWaitTime"; +const char *type_nsds5ReplicaSessionPauseTime = "nsds5ReplicaSessionPauseTime"; + +/* To Allow Consumer Initialisation when adding an agreement - */ +const char *type_nsds5BeginReplicaRefresh = "nsds5BeginReplicaRefresh"; + +static int repl_active_threads; + +int +decrement_repl_active_threads() +{ + PR_AtomicIncrement(&repl_active_threads); + return repl_active_threads; +} + +int +increment_repl_active_threads() +{ + PR_AtomicDecrement(&repl_active_threads); + return repl_active_threads; +} diff --git a/ldap/servers/plugins/replication/repl_helper.c b/ldap/servers/plugins/replication/repl_helper.c new file mode 100644 index 00000000..05616cf1 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_helper.c @@ -0,0 +1,85 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "repl_helper.h" + +ReplGenericList * +ReplGenericListNew(void) +{ + ReplGenericList *list=NULL; + if(NULL == (list = (ReplGenericList *) + slapi_ch_calloc(1,sizeof(ReplGenericList)))) { + return(NULL); + } + list->object = NULL; + list->next = NULL; + list->prev = NULL; + return(list); +} + +void +ReplGenericListAddObject(ReplGenericList *list, + void *newObject) +{ + if(list) { + ReplGenericList *new_struct = (ReplGenericList *) + slapi_ch_calloc(1, sizeof(ReplGenericList)); + + if (!new_struct) + return; + /* set back pointer of old first element */ + if(list->next) { + list->next->prev = new_struct; + } + + /* we might have a next but since we are the first we WONT have + a previous */ + new_struct->object = newObject; + new_struct->next = list->next; + new_struct->prev = NULL; + + /* the new element is the first one */ + list->next = new_struct; + + /* if this is the only element it is the end too */ + if(NULL == list->prev) + list->prev = new_struct; + + } + return; +} + +ReplGenericList * +ReplGenericListFindObject(ReplGenericList *list, + void *object) +{ + if(!list) + return(NULL); + list = list->next; /* the first list item never has data */ + + while (list) { + if(list->object == object) + return(list); + list = list->next; + } + return(NULL); +} + +void +ReplGenericListDestroy(ReplGenericList *list, + ReplGenericListObjectDestroyFn destroyFn) +{ + ReplGenericList *list_ptr; + + while (list) { + list_ptr = list; + list = list->next; + if(destroyFn && list_ptr->object) { + (destroyFn)(list_ptr->object); + } + slapi_ch_free((void **)(&list_ptr)); + } + return; +} diff --git a/ldap/servers/plugins/replication/repl_helper.h b/ldap/servers/plugins/replication/repl_helper.h new file mode 100644 index 00000000..076710ce --- /dev/null +++ b/ldap/servers/plugins/replication/repl_helper.h @@ -0,0 +1,69 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * repl_helper.h - Helper functions (should actually be repl_utils.h) + * + * + * + */ + +#ifndef _REPL_HELPER_H +#define _REPL_HELPER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nspr.h" +#include "slapi-plugin.h" + +/* + * shamelessly stolen from the xp library + * + */ + +/* + Linked list manipulation routines + + this is a very standard linked list structure + used by many many programmers all over the world + + The lists have been modified to be doubly linked. The + first element in a list is always the header. The 'next' + pointer of the header is the first element in the list. + The 'prev' pointer of the header is the last element in + the list. + + The 'prev' pointer of the first real element in the list + is NULL as is the 'next' pointer of the last real element + in the list + + */ + + +typedef struct _repl_genericList { + void *object; + struct _repl_genericList *next; + struct _repl_genericList *prev; +} ReplGenericList; + +typedef void *(ReplGenericListObjectDestroyFn)(void *obj); + +ReplGenericList *ReplGenericListNew(void); +void ReplGenericListDestroy(ReplGenericList *list, ReplGenericListObjectDestroyFn destroyFn); + +void ReplGenericListAddObject(ReplGenericList *list, + void *newObject); +ReplGenericList *ReplGenericListFindObject(ReplGenericList *list, + void *obj); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ldap/servers/plugins/replication/repl_init.c b/ldap/servers/plugins/replication/repl_init.c new file mode 100644 index 00000000..42da2074 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_init.c @@ -0,0 +1,312 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * Add an entry like the following to dse.ldif to enable this plugin: + +dn: cn=Legacy Replication Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: Legacy Replication Plugin +nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/replication-plugin.so +nsslapd-plugininitfunc: replication_legacy_plugin_init +nsslapd-plugintype: object +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database +nsslapd-plugin-depends-on-named: Class of Service +nsslapd-plugin-depends-on-named: Multi-Master Replication Plugin +nsslapd-pluginid: replication-legacy +nsslapd-pluginversion: 5.0b1 +nsslapd-pluginvendor: Netscape Communications +nsslapd-plugindescription: Legacy Replication Plugin + +NOTE: This plugin depends on the Multi-Master Replication Plugin + +*/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "repl_shared.h" +#include "cl4.h" /* changelog interface */ +#include "dirver.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/* ----------------------------- Legacy Replication Plugin */ + +static Slapi_PluginDesc legacydesc = { "replication-legacy", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy Replication Plugin" }; +static Slapi_PluginDesc legacypreopdesc = { "replication-legacy-preop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication pre-operation plugin" }; +static Slapi_PluginDesc legacypostopdesc = { "replication-legacy-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication post-operation plugin" }; +static Slapi_PluginDesc legacyinternalpreopdesc = { "replication-legacy-internalpreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication internal pre-operation plugin" }; +static Slapi_PluginDesc legacyinternalpostopdesc = { "replication-legacy-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication internal post-operation plugin" }; +static Slapi_PluginDesc legacybepostopdesc = { "replication-legacy-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication bepost-operation plugin" }; +static Slapi_PluginDesc legacyentrydesc = { "replication-legacy-entry", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication entry plugin" }; + +static int legacy_stopped; /* A flag which is set when all the plugin threads are to stop */ + + +/* Initialize preoperation plugin points */ +int +legacy_preop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacypreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) legacy_preop_bind ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) legacy_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void *) legacy_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) legacy_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void *) legacy_preop_modrdn ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) legacy_preop_search ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void *) legacy_preop_compare ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ENTRY_FN, (void *) legacy_pre_entry )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_preop_init failed\n" ); + rc= -1; + } + return rc; +} + + + +/* Initialize postoperation plugin points */ +static int +legacy_postop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacypostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) legacy_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) legacy_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) legacy_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) legacy_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_postop_init failed\n" ); + rc= -1; + } + + return rc; +} + + + +/* Initialize internal preoperation plugin points (called for internal operations) */ +static int +legacy_internalpreop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyinternalpreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) legacy_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, (void *) legacy_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) legacy_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, (void *) legacy_preop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_internalpreop_init failed\n" ); + rc= -1; + } + return rc; +} + + + +/* Initialize internal postoperation plugin points (called for internal operations) */ +static int +legacy_internalpostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyinternalpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) legacy_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) legacy_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) legacy_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) legacy_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_internalpostop_init failed\n" ); + rc= -1; + } + + return rc; +} + + + +/* Initialize the entry plugin point for the legacy replication plugin */ +static int +legacy_entry_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + /* Set up the fn pointers for the preop and postop operations we're interested in */ + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyentrydesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_entry_init failed\n" ); + rc= -1; + } + return rc; +} + + + + +/* + * Create the entry at the top of the replication configuration subtree. + */ +static int +create_config_top() +{ + const char *dn = REPL_CONFIG_TOP; + char *entry_string = slapi_ch_strdup("dn: cn=replication,cn=config\nobjectclass: top\nobjectclass: extensibleobject\ncn: replication\n"); + Slapi_PBlock *pb = slapi_pblock_new(); + Slapi_Entry *e = slapi_str2entry(entry_string, 0); + int return_value; + + slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* flags */); + slapi_add_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value); + slapi_pblock_destroy(pb); + slapi_ch_free((void **)&entry_string); + return return_value; +} + + +/* Start the legacy replication plugin */ +static int +legacy_start( Slapi_PBlock *pb ) +{ + static int legacy_started = 0; + int rc= 0; /* OK */ + + if (!legacy_started) + { + int ctrc; + + /* Initialise support for cn=monitor */ + repl_monitor_init(); + + /* Initialise support for "" (the rootdse) */ + /* repl_rootdse_init(); */ + + /* Decode the command line args to see if we're dumping to LDIF */ + { + int argc; + char **argv; + slapi_pblock_get( pb, SLAPI_ARGC, &argc); + slapi_pblock_get( pb, SLAPI_ARGV, &argv); + repl_entry_init(argc,argv); + } + + /* Create the entry at the top of the config area, if it doesn't exist */ + /* XXXggood this should be in the 5.0 plugin! */ + ctrc = create_config_top(); + if (ctrc != LDAP_SUCCESS && ctrc != LDAP_ALREADY_EXISTS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable to " + "create configuration entry %s: %s\n", REPL_CONFIG_TOP, + ldap_err2string(ctrc)); + } + (void)legacy_consumer_config_init(); + + /* register to be notified when backend state changes */ + slapi_register_backend_state_change((void *)legacy_consumer_be_state_change, + legacy_consumer_be_state_change); + + legacy_started = 1; + legacy_stopped = 0; + } + return rc; +} + + +/* Post-start function for the legacy replication plugin */ +static int +legacy_poststart( Slapi_PBlock *pb ) +{ + int rc = 0; /* OK */ + return rc; +} + + +/* Stop the legacy replication plugin */ +static int +legacy_stop( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if (!legacy_stopped) + { + /*csnShutdown();*/ + legacy_stopped = 1; + } + + /* unregister backend state change notification */ + slapi_unregister_backend_state_change((void *)legacy_consumer_be_state_change); + + return rc; +} + + +/* Initialize the legacy replication plugin */ +int +replication_legacy_plugin_init(Slapi_PBlock *pb) +{ + static int legacy_initialised= 0; + int rc= 0; /* OK */ + void *identity = NULL; + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + repl_set_plugin_identity (PLUGIN_LEGACY_REPLICATION, identity); + + if(config_is_slapd_lite()) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "replication plugin not approved for restricted" + " mode Directory Server.\n" ); + rc= -1; + } + if(rc==0 && !legacy_initialised) + { + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacydesc ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) legacy_start ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) legacy_stop ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_POSTSTART_FN, (void *) legacy_poststart ); + + /* Register the plugin interfaces we implement */ + rc= slapi_register_plugin("preoperation", 1 /* Enabled */, "legacy_preop_init", legacy_preop_init, "Legacy replication preoperation plugin", NULL, identity); + rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "legacy_postop_init", legacy_postop_init, "Legacy replication postoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "legacy_internalpreop_init", legacy_internalpreop_init, "Legacy replication internal preoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "legacy_internalpostop_init", legacy_internalpostop_init, "Legacy replication internal postoperation plugin", NULL, identity); + rc= slapi_register_plugin("entry", 1 /* Enabled */, "legacy_entry_init", legacy_entry_init, "Legacy replication entry plugin", NULL, identity); + + legacy_initialised= 1; + } + return rc; +} + + +int +get_legacy_stop() +{ + return legacy_stopped; +} diff --git a/ldap/servers/plugins/replication/repl_modify.c b/ldap/servers/plugins/replication/repl_modify.c new file mode 100644 index 00000000..26753cc0 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_modify.c @@ -0,0 +1,29 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +/* The modify operation plugin functions for the legacy replication plugin */ + +int +legacy_preop_modify( Slapi_PBlock *pb ) +{ + return legacy_preop( pb, "legacy_preop_modify", OP_MODIFY ); +} + +int +legacy_bepreop_modify( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + return rc; +} + +int +legacy_postop_modify( Slapi_PBlock *pb ) +{ + return legacy_postop( pb, "legacy_postop_modify", OP_MODIFY ); +} diff --git a/ldap/servers/plugins/replication/repl_modrdn.c b/ldap/servers/plugins/replication/repl_modrdn.c new file mode 100644 index 00000000..653aff0a --- /dev/null +++ b/ldap/servers/plugins/replication/repl_modrdn.c @@ -0,0 +1,28 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +/* The modrdn plugin points for the legacy replication plugin */ + +int +legacy_preop_modrdn( Slapi_PBlock *pb ) +{ + return legacy_preop(pb, "legacy_preop_modrdn", OP_MODDN); +} + +int +legacy_bepreop_modrdn( Slapi_PBlock *pb ) +{ + return 0; /* OK */ +} + +int +legacy_postop_modrdn( Slapi_PBlock *pb ) +{ + return legacy_postop(pb, "legacy_postop_modrdn", OP_MODDN); +} diff --git a/ldap/servers/plugins/replication/repl_monitor.c b/ldap/servers/plugins/replication/repl_monitor.c new file mode 100644 index 00000000..ec52d611 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_monitor.c @@ -0,0 +1,58 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <string.h> + +#include "repl.h" +#include "slapi-plugin.h" + +/* Forward Declartions */ +static int repl_monitor_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +int +repl_monitor_init() +{ + /* The FE DSE *must* be initialised before we get here */ + int return_value= LDAP_SUCCESS; + static int initialized = 0; + + if (!initialized) + { + /* ONREPL - this is commented until we implement 4.0 style changelog + slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"cn=monitor",LDAP_SCOPE_BASE,"(objectclass=*)",repl_monitor_search,NULL); */ + initialized = 1; + } + + return return_value; +} + +static int +repl_monitor_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + const char *sdv = get_server_dataversion(); + if ( sdv != NULL ) + { + int port; + char buf[BUFSIZ]; + struct berval val; + struct berval *vals[2]; + vals[0] = &val; + vals[1] = NULL; + port= config_get_port(); + if(port==0) + { + port= config_get_secureport(); + } + /* ONREPL - how do we publish changenumbers now with multiple changelogs? + sprintf( buf, "%s:%lu %s% lu", get_localhost_DNS(), port, sdv, ldapi_get_last_changenumber()); + */ + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, attr_dataversion, vals ); + } + return SLAPI_DSE_CALLBACK_OK; +} + diff --git a/ldap/servers/plugins/replication/repl_objset.c b/ldap/servers/plugins/replication/repl_objset.c new file mode 100644 index 00000000..f0a68097 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_objset.c @@ -0,0 +1,524 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl_objset.c */ +/* + * Support for lifetime management of sets of objects. + * Objects are refcounted. NOTE: this API is deprecated. + * Use the object/objset API provided by libslapd. + */ + +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "repl_objset.h" +#include <prlock.h> + +#define REPL_OBJSET_OBJ_FLAG_DELETED 0x1 + + +typedef struct repl_objset_object +{ + void *data; /* pointer to actual node data */ + char *key; /* key for this object. null-terminated string */ + int refcnt; /* reference count for this object */ + unsigned long flags; /* state of this object */ +} Repl_Objset_object; + +typedef struct repl_objset +{ + LList *objects; + FNFree destructor; /* destructor for objects - provided by caller */ + PRLock *lock; +} repl_objset; + + +/* Forward declarations */ +static void removeObjectNolock(Repl_Objset *o, Repl_Objset_object *co); +static Repl_Objset_object *removeCurrentObjectAndGetNextNolock (Repl_Objset *o, + Repl_Objset_object *co, void *iterator); + +/* + * Create a new set. + * + * Arguments: + * destructor: a function to be called when an object is to be destroyed + * + * Returns: + * A pointer to the object set, or NULL if an error occured. + */ +Repl_Objset * +repl_objset_new(FNFree destructor) +{ + Repl_Objset *p; + + p = (Repl_Objset *)slapi_ch_malloc(sizeof(Repl_Objset)); + p->lock = PR_NewLock(); + if (NULL == p->lock) + { + free(p); p = NULL; + } + p->objects = llistNew(); + p->destructor = destructor; + return p; +} + + +/* + * Destroy a Repl_Objset. + * Arguments: + * o: the object set to be destroyed + * maxwait: the maximum time to wait for all object refcnts to + * go to zero. + * panic_fn: a function to be called if, after waiting "maxwait" + * seconds, not all object refcnts are zero. + * The caller must ensure that no one else holds references to the + * set or any objects it contains. + */ +void +repl_objset_destroy(Repl_Objset **o, time_t maxwait, FNFree panic_fn) +{ + Repl_Objset_object *co = NULL; + time_t now, stop_time; + int really_gone; + int loopcount; + void *cookie; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != *o); + + time(&now); + stop_time = now + maxwait; + + /* + * Loop over the objects until they all are actually gone, + * or until maxwait seconds have passed. + */ + really_gone = 0; + loopcount = 0; + + while (now < stop_time) + { + void *cookie; + + PR_Lock((*o)->lock); + + if ((co = llistGetFirst((*o)->objects, &cookie)) == NULL) + { + really_gone = 1; + PR_Unlock((*o)->lock); + break; + } + while (NULL != co) + { + /* Set the deleted flag so the object isn't returned by iterator */ + co->flags |= REPL_OBJSET_OBJ_FLAG_DELETED; + if (0 == co->refcnt) + { + /* Remove the object */ + co = removeCurrentObjectAndGetNextNolock ((*o), co, cookie); + + } + else + co = llistGetNext((*o)->objects, &cookie); + } + PR_Unlock((*o)->lock); + time(&now); + if (loopcount > 0) + { + DS_Sleep(PR_TicksPerSecond()); + } + loopcount++; + } + + if (!really_gone) + { + if (NULL != panic_fn) + { + /* + * Call the "aargh, this thing won't go away" panic + * function for each remaining object. + */ + PR_Lock((*o)->lock); + if ((co = llistGetFirst((*o)->objects, &cookie)) == NULL) + { + panic_fn(co->data); + while (NULL != co) + { + panic_fn(co->data); + co = llistGetNext((*o)->objects, &cookie); + } + } + PR_Unlock((*o)->lock); + } + } + else + { + /* Free the linked list */ + llistDestroy(&(*o)->objects, (*o)->destructor); + PR_DestroyLock((*o)->lock); + free(*o); *o = NULL; + } +} + + + +/* + * Add an object to an object set. + * + * Arguments: + * o: The object set to which the object is to be added. + * name: a null-terminated string that names the object. Must + * be unique. + * obj: pointer to the object to be added. + * + * Return codes: + * REPL_OBJSET_SUCCESS: the item was added to the object set + * REPL_OBJSET_DUPLICATE_KEY: an item with the same key is already + * in the object set. + * REPL_OBJSET_INTERNAL_ERROR: something bad happened. + */ +int +repl_objset_add(Repl_Objset *o, const char *name, void *obj) +{ + Repl_Objset_object *co = NULL; + Repl_Objset_object *tmp = NULL; + int rc = REPL_OBJSET_SUCCESS; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != name); + PR_ASSERT(NULL != obj); + + PR_Lock(o->lock); + tmp = llistGet(o->objects, name); + if (NULL != tmp) + { + rc = REPL_OBJSET_DUPLICATE_KEY; + goto loser; + } + co = (Repl_Objset_object *)slapi_ch_malloc(sizeof(Repl_Objset_object)); + co->data = obj; + co->key = slapi_ch_strdup(name); + co->refcnt = 0; + co->flags = 0UL; + if (llistInsertHead(o->objects, name, co) != 0) + { + rc = REPL_OBJSET_INTERNAL_ERROR; + goto loser; + } + PR_Unlock(o->lock); + return rc; + +loser: + PR_Unlock(o->lock); + if (NULL != co) + { + if (NULL != co->key) + { + slapi_ch_free((void **)&co->key); + } + slapi_ch_free((void **)&co); + } + return rc; +} + + +/* Must be called with the repl_objset locked */ +static void +removeObjectNolock(Repl_Objset *o, Repl_Objset_object *co) +{ + /* Remove from list */ + llistRemove(o->objects, co->key); + /* Destroy the object */ + o->destructor(&(co->data)); + free(co->key); + /* Deallocate the container */ + free(co); +} + +static Repl_Objset_object * +removeCurrentObjectAndGetNextNolock (Repl_Objset *o, Repl_Objset_object *co, void *iterator) +{ + Repl_Objset_object *ro; + + PR_ASSERT (o); + PR_ASSERT (co); + PR_ASSERT (iterator); + + ro = llistRemoveCurrentAndGetNext (o->objects, &iterator); + + o->destructor(&(co->data)); + free(co->key); + /* Deallocate the container */ + free(co); + + return ro; +} + +/* Must be called with the repl_objset locked */ +static void +acquireNoLock(Repl_Objset_object *co) +{ + co->refcnt++; +} + + +/* Must be called with the repl_objset locked */ +static void +releaseNoLock(Repl_Objset *o, Repl_Objset_object *co) +{ + PR_ASSERT(co->refcnt >= 1); + if (--co->refcnt == 0) + { + if (co->flags & REPL_OBJSET_OBJ_FLAG_DELETED) + { + /* Remove the object */ + removeObjectNolock(o, co); + } + } +} + +/* + * Retrieve an object from the object set. If an object with + * the given key is found, its reference count is incremented, + * a pointer to the object is returned, and a handle to use + * to refer to the object is returned. + * + * Arguments: + * o: The object set to be searched. + * key: key of the object to be retrieved + * obj: pointer to void * that will be set to point to the + * object, if found. + * handle: pointer to void * that will be set to point to a + * handle, used to refer to the object, if found. + * + * Returns: + * REPL_OBJSET_SUCCESS: an item was found. + * REPL_OBJSET_KEY_NOT_FOUND: no item with the given key was found. + */ +int +repl_objset_acquire(Repl_Objset *o, const char *key, void **obj, void **handle) +{ + Repl_Objset_object *co = NULL; + int rc = REPL_OBJSET_KEY_NOT_FOUND; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != key); + PR_ASSERT(NULL != obj); + PR_ASSERT(NULL != handle); + + PR_Lock(o->lock); + co = llistGet(o->objects, key); + if (NULL != co && !(co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)) + { + acquireNoLock(co); + *obj = (void *)co->data; + *handle = (void *)co; + rc = REPL_OBJSET_SUCCESS; + } + PR_Unlock(o->lock); + return rc; +} + + +/* + * Return an object to the object set. + * + * Arguments: + * o: The object set containing the objct + * handle: reference to the object. + * + */ +void +repl_objset_release(Repl_Objset *o, void *handle) +{ + Repl_Objset_object *co; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != handle); + + co = (Repl_Objset_object *)handle; + PR_Lock(o->lock); + releaseNoLock(o, co); + PR_Unlock(o->lock); +} + + + +/* + * Delete an object from the object set + * + * Arguments: + * o: The object set containing the object. + * handle: reference to the object. + */ +void +repl_objset_delete(Repl_Objset *o, void *handle) +{ + Repl_Objset_object *co = (Repl_Objset_object *)handle; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != co); + + PR_Lock(o->lock); + if (co->refcnt == 0) + { + removeObjectNolock(o, co); + } + else + { + /* Set deleted flag, clean up later */ + co->flags |= REPL_OBJSET_OBJ_FLAG_DELETED; + } + PR_Unlock(o->lock); +} + + +typedef struct _iterator +{ + Repl_Objset *o; /* set for which iterator was created */ + void *cookie; /* for linked list */ + Repl_Objset_object *co; /* our wrapper */ +} iterator; + +/* + * Get the first object in an object set. + * Used when enumerating all of the objects in a set. + * Arguments: + * o: The object set being enumerated + * itcontext: an iteration context, to be passed back to subsequent calls + * to repl_objset_next_object. + * handle: a pointer to pointer to void. This will be filled in with + * a reference to the object's enclosing object. + * Returns: + * A pointer to the next object in the set, or NULL if there are no + * objects in the set. + * + */ +void * +repl_objset_first_object(Repl_Objset *o, void **itcontext, void **handle) +{ + Repl_Objset_object *co = NULL; + void *cookie; + void *retptr = NULL; + iterator *it; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != itcontext); + + *itcontext = NULL; + + if (NULL == o->objects) { + return(NULL); + } + + /* Find the first non-deleted object */ + PR_Lock(o->lock); + co = llistGetFirst(o->objects, &cookie); + while (NULL != co && (co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)) + { + co = llistGetNext(o->objects, &cookie); + } + + if (NULL != co) + { + /* Increment refcnt until item given back to us */ + acquireNoLock(co); + + /* Save away context */ + it = (iterator *)slapi_ch_malloc(sizeof(iterator)); + *itcontext = it; + it->o = o; + it->cookie = cookie; + it->co = co; + retptr = co->data; + } + + PR_Unlock(o->lock); + if (NULL != handle) + { + *handle = co; + } + + return retptr; +} + + + +/* + * Get the next object in the set. + * Arguments: + * o: The object set being enumerated + * itcontext: an iteration context, to be passed back to subsequent calls + * to repl_objset_next_object. + * handle: a pointer to pointer to void. This will be filled in with + * a reference to the object's enclosing object. + * + * Returns: + * A pointer to the next object in the set, or NULL if there are no more + * objects. + */ +void * +repl_objset_next_object(Repl_Objset *o, void *itcontext, void **handle) +{ + Repl_Objset_object *co = NULL; + Repl_Objset_object *tmp_co; + void *retptr = NULL; + iterator *it = (iterator *)itcontext; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != it); + PR_ASSERT(NULL != it->co); + + PR_Lock(o->lock); + tmp_co = it->co; + + /* Find the next non-deleted object */ + while ((co = llistGetNext(o->objects, &it->cookie)) != NULL && + !(co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)); + + if (NULL != co) + { + acquireNoLock(co); + it->co = co; + retptr = co->data; + } + else + { + /* + * No more non-deleted objects - erase context (freeing + * it is responsibility of caller. + */ + it->cookie = NULL; + it->co = NULL; + } + releaseNoLock(o, tmp_co); + PR_Unlock(o->lock); + if (NULL != handle) + { + *handle = co; + } + return retptr; +} + + + +/* + * Destroy an itcontext iterator + */ +void +repl_objset_iterator_destroy(void **itcontext) +{ + if (NULL != itcontext && NULL != *itcontext) + { + /* check if we did not iterate through the entire list + and need to release last accessed element */ + iterator *it = *(iterator**)itcontext; + if (it->co) + repl_objset_release (it->o, it->co); + + slapi_ch_free((void **)itcontext); + } +} diff --git a/ldap/servers/plugins/replication/repl_objset.h b/ldap/servers/plugins/replication/repl_objset.h new file mode 100644 index 00000000..72af5109 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_objset.h @@ -0,0 +1,37 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + */ + +/* repl_objset.h */ + /* + * Support for lifetime management of sets of objects. + * Objects are refcounted. NOTE: This API should go away + * in favor of the objset API provided by libslapd. + */ +#ifndef _REPL_OBJSET_H +#define __REPL_OBJSET_H + +#include "llist.h" + +#define REPL_OBJSET_SUCCESS 0 +#define REPL_OBJSET_DUPLICATE_KEY 1 +#define REPL_OBJSET_INTERNAL_ERROR 2 +#define REPL_OBJSET_KEY_NOT_FOUND 3 + +typedef struct repl_objset Repl_Objset; + +Repl_Objset *repl_objset_new(FNFree destructor); +void repl_objset_destroy(Repl_Objset **o, time_t maxwait, FNFree panic_fn); +int repl_objset_add(Repl_Objset *o, const char *name, void *obj); +int repl_objset_acquire(Repl_Objset *o, const char *key, void **obj, void **handle); +void repl_objset_release(Repl_Objset *o, void *handle); +void repl_objset_delete(Repl_Objset *o, void *handle); +void *repl_objset_next_object(Repl_Objset *o, void *cookie, void **handle); +void *repl_objset_first_object(Repl_Objset *o, void **cookie, void **handle); +void repl_objset_iterator_destroy(void **itcontext); + +#endif /* _REPL_OBJSET_H */ diff --git a/ldap/servers/plugins/replication/repl_opext.c b/ldap/servers/plugins/replication/repl_opext.c new file mode 100644 index 00000000..d9c8d1ed --- /dev/null +++ b/ldap/servers/plugins/replication/repl_opext.c @@ -0,0 +1,97 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* supplier_operation_extension.c - replication extension to the Operation object + */ + + +#include "repl.h" +#include "repl5.h" + +/* ***** Supplier side ***** */ + +/* JCMREPL -> PINAKIxxx The interface to the referral stuff is not correct */ +void ref_array_dup_free(void *the_copy); /* JCMREPL - should be #included */ + +/* supplier operation extension constructor */ +void* supplier_operation_extension_constructor (void *object, void *parent) +{ + supplier_operation_extension *ext = (supplier_operation_extension*) slapi_ch_calloc (1, sizeof (supplier_operation_extension)); + if (ext == NULL) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication supplier operation extension - out of memory\n" ); + } + else + { + ext->prevent_recursive_call= 0; + } + return ext; +} + +/* supplier operation extension destructor */ +void supplier_operation_extension_destructor (void *ext,void *object, void *parent) +{ + if (ext) + { + supplier_operation_extension *supext = (supplier_operation_extension *)ext; + if (supext->operation_parameters) + operation_parameters_free (&(supext->operation_parameters)); + if (supext->repl_gen) + slapi_ch_free ((void**)&supext->repl_gen); + slapi_ch_free((void **)&ext); + } +} + +/* ***** Consumer side ***** */ + +/* consumer operation extension constructor */ +void* consumer_operation_extension_constructor (void *object, void *parent) +{ + consumer_operation_extension *ext = (consumer_operation_extension*) slapi_ch_calloc (1, sizeof (consumer_operation_extension)); + if (ext == NULL) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication consumer operation extension - out of memory\n" ); + } + if(object!=NULL && parent!=NULL) + { + consumer_connection_extension *connext; + connext = (consumer_connection_extension *)repl_con_get_ext(REPL_CON_EXT_CONN, parent); + if(NULL != connext) + { + /* We copy the Connection Replicated Session flag to the Replicated Operation flag */ + if (connext->isreplicationsession) + { + operation_set_flag((Slapi_Operation *)object,OP_FLAG_REPLICATED); + } + /* We set the Replication DN flag if session bound as replication dn */ + if (connext->is_legacy_replication_dn) + { + operation_set_flag((Slapi_Operation *)object, OP_FLAG_LEGACY_REPLICATION_DN); + } + } + } + else + { + /* (parent==NULL) for internal operations */ + PR_ASSERT(object!=NULL); + } + + return ext; +} + +/* consumer operation extension destructor */ +void consumer_operation_extension_destructor (void *ext,void *object, void *parent) +{ + if (NULL != ext) + { + consumer_operation_extension *opext = (consumer_operation_extension *)ext; + if (NULL != opext->search_referrals) + { + ref_array_dup_free(opext->search_referrals); /* JCMREPL - undefined */ + opext->search_referrals = NULL; + } + slapi_ch_free((void **)&ext); + } +} diff --git a/ldap/servers/plugins/replication/repl_ops.c b/ldap/servers/plugins/replication/repl_ops.c new file mode 100644 index 00000000..e1e51355 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_ops.c @@ -0,0 +1,180 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" + +int +legacy_postop( Slapi_PBlock *pb, const char *caller, int operation_type) +{ + int rc = 0; + Object *r_obj; + Replica *r; + + r_obj = replica_get_replica_for_op (pb); + if (r_obj == NULL) /* there is no replica configured for this operations */ + return 0; + else + { + /* check if this replica is 4.0 consumer */ + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + /* this replica is not a 4.0 consumer - so we don't need to do any processing */ + if (!replica_is_legacy_consumer (r)) + { + object_release (r_obj); + return 0; + } + + object_release (r_obj); + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &rc); + if (0 == rc) + { + if (OP_ADD == operation_type || OP_MODIFY == operation_type) + { + void *op; + consumer_operation_extension *opext = NULL; + + /* Optimise out traversal of mods/entry if no cop{ied|ying}From present */ + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, op); + if (NULL != opext && opext->has_cf) + { + process_legacy_cf( pb ); + } + } + } + + return 0; +} + + + +static char *not_replicationdn_errmsg = + "An operation was submitted that contained copiedFrom or " + "copyingFrom attributes, but the connection was not bound " + "as the replicationdn."; + +int +legacy_preop(Slapi_PBlock *pb, const char *caller, int operation_type) +{ + int rc = 0; + Slapi_Operation *operation = NULL; + consumer_operation_extension *opext = NULL; + int has_cf = 0; + Object *r_obj; + Replica *r; + int is_legacy_op = 0; + + slapi_pblock_get( pb, SLAPI_OPERATION, &operation ); + is_legacy_op = operation_is_flag_set(operation,OP_FLAG_LEGACY_REPLICATION_DN); + r_obj = replica_get_replica_for_op (pb); + + if (r_obj == NULL) { /* there is no replica configured for this operations */ + if (is_legacy_op){ + /* This is a legacy replication operation but there are NO replica defined + Just refuse it */ + slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, + "Replication operation refused because the consumer is not defined as a replica", 0, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Incoming replication operation was refused because " + "there's no replica defined for this operation\n"); + return -1; + } + else { + return 0; + } + } + else + { + /* check if this replica is 4.0 consumer */ + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + if (!replica_is_legacy_consumer (r)) + { + object_release (r_obj); + if (is_legacy_op) { + /* This is a legacy replication operation + but the replica is doesn't accept from legacy + Just refuse it */ + slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, + "Replication operation refused because " + "the consumer is not defined as a legacy replica", 0, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Incoming replication operation was refused because " + "there's no legacy replica defined for this operation\n"); + return -1; + } else { + return 0; + } + } + + object_release (r_obj); + } + + opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, operation); + + switch (operation_type) { + case OP_ADD: + { + Slapi_Entry *e = NULL; + Slapi_Attr *attr; + /* + * Check if the entry being added has copiedFrom/copyingFrom + * attributes. + */ + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + if (NULL != e) + { + if (slapi_entry_attr_find(e, type_copiedFrom, &attr) == 0) + { + has_cf = 1; + } + else + if (slapi_entry_attr_find(e, type_copyingFrom, &attr) == 0) + { + has_cf = 1; + } + } + /* JCMREPL - If this is a replicated operation then the baggage control also contains the Unique Identifier of the superior entry. */ + } + break; + case OP_MODIFY: + { + LDAPMod **mods = NULL; + int i; + + /* + * Check if the modification contains copiedFrom/copyingFrom + * attributes. + */ + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (i = 0; NULL != mods && NULL != mods[i]; i++) + { + if ((strcasecmp(mods[i]->mod_type, type_copiedFrom) == 0) || + (strcasecmp(mods[i]->mod_type, type_copyingFrom) == 0)) + { + has_cf = 1; + } + } + } + break; + case OP_DELETE: + break; + case OP_MODDN: + break; + } + + /* Squirrel away an optimization hint for the postop plugin */ + opext->has_cf = has_cf; + + return rc; +} diff --git a/ldap/servers/plugins/replication/repl_rootdse.c b/ldap/servers/plugins/replication/repl_rootdse.c new file mode 100644 index 00000000..2bd1d8e8 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_rootdse.c @@ -0,0 +1,79 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <string.h> + +#include "repl.h" +#include "cl4.h" +#include "slapi-plugin.h" + +/* Forward Declartions */ +static int repl_rootdse_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +int +repl_rootdse_init() +{ + /* The FE DSE *must* be initialised before we get here */ + int return_value= LDAP_SUCCESS; + + slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"",LDAP_SCOPE_BASE,"(objectclass=*)",repl_rootdse_search,NULL); + + return return_value; +} + +static int +repl_rootdse_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + +#if 0 + struct berval val; + struct berval *vals[2]; + vals[0] = &val; + vals[1] = NULL; + + /* machine data suffix */ + val.bv_val = REPL_CONFIG_TOP; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, ATTR_NETSCAPEMDSUFFIX, vals ); + + /* Changelog information */ +/* ONREPL because we now support multiple 4.0 changelogs we no longer publish + info in the rootdse */ + if ( get_repl_backend() != NULL ) + { + char buf[BUFSIZ]; + changeNumber cnum; + + /* Changelog suffix */ + val.bv_val = changelog4_get_suffix (); + if ( val.bv_val != NULL ) + { + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "changelog", vals ); + } + slapi_ch_free ((void **)&val.bv_val); + + /* First change number contained in log */ + cnum = ldapi_get_first_changenumber(); + sprintf( buf, "%lu", cnum ); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "firstchangenumber", vals ); + + /* Last change number contained in log */ + cnum = ldapi_get_last_changenumber(); + sprintf( buf, "%lu", cnum ); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "lastchangenumber", vals ); + } +#endif + + return SLAPI_DSE_CALLBACK_OK; +} + + + diff --git a/ldap/servers/plugins/replication/repl_search.c b/ldap/servers/plugins/replication/repl_search.c new file mode 100644 index 00000000..cfa21222 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_search.c @@ -0,0 +1,25 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +/* XXXggood I think we no longer need this - the mapping tree should do it for us */ +int +legacy_preop_search( Slapi_PBlock *pb ) +{ + int return_code = 0; + return return_code; +} + + +/* XXXggood I think we no longer need this - the mapping tree should do it for us */ +int +legacy_pre_entry( Slapi_PBlock *pb ) +{ + int return_code = 0; + return return_code; +} diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h new file mode 100644 index 00000000..0c7454ab --- /dev/null +++ b/ldap/servers/plugins/replication/repl_shared.h @@ -0,0 +1,132 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl_shared.h - definitions shared between 4.0 and 5.0 replication + modules + */ + +#ifndef REPL_SHARED_H +#define REPL_SHARED_H + +#include "slapi-private.h" +#include "slapi-plugin.h" +#include "ldif.h" /* GGOODREPL - is this cheating? */ + +#ifdef _WIN32 +#define FILE_PATHSEP '\\' +#else +#define FILE_PATHSEP '/' +#endif + +#define CHANGELOGDB_TRIM_INTERVAL 300 /* 5 minutes */ + +#define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir" +#define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries" +#define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage" +/* Changelog Internal Configuration Parameters -> DB related */ +#define CONFIG_CHANGELOG_DB_DBCACHESIZE "nsslapd-dbcachesize" +#define CONFIG_CHANGELOG_DB_DURABLE_TRANSACTIONS "nsslapd-db-durable-transaction" +#define CONFIG_CHANGELOG_DB_CHECKPOINT_INTERVAL "nsslapd-db-checkpoint-interval" +#define CONFIG_CHANGELOG_DB_CIRCULAR_LOGGING "nsslapd-db-circular-logging" +#define CONFIG_CHANGELOG_DB_PAGE_SIZE "nsslapd-db-page-size" +#define CONFIG_CHANGELOG_DB_LOGFILE_SIZE "nsslapd-db-logfile-size" +#define CONFIG_CHANGELOG_DB_MAXTXN_SIZE "nsslapd-db-max-txn" +#define CONFIG_CHANGELOG_DB_VERBOSE "nsslapd-db-verbose" +#define CONFIG_CHANGELOG_DB_DEBUG "nsslapd-db-debug" +#define CONFIG_CHANGELOG_DB_TRICKLE_PERCENTAGE "nsslapd-db-trickle-percentage" +#define CONFIG_CHANGELOG_DB_SPINCOUNT "nsslapd-db-spin-count" +/* Changelog Internal Configuration Parameters -> Changelog Cache related */ +#define CONFIG_CHANGELOG_CACHESIZE "nsslapd-cachesize" +#define CONFIG_CHANGELOG_CACHEMEMSIZE "nsslapd-cachememsize" +#define CONFIG_CHANGELOG_NB_LOCK "nsslapd-db-locks" +#define CONFIG_CHANGELOG_MAX_CONCURRENT_WRITES "nsslapd-changelogmaxconcurrentwrites" + +#define T_CHANGETYPESTR "changetype" +#define T_CHANGETYPE 1 +#define T_TIMESTR "time" +#define T_TIME 2 +#define T_DNSTR "dn" +#define T_DN 3 +#define T_CHANGESTR "change" +#define T_CHANGE 4 + +#define T_ADDCTSTR "add" +#define T_ADDCT 4 +#define T_MODIFYCTSTR "modify" +#define T_MODIFYCT 5 +#define T_DELETECTSTR "delete" +#define T_DELETECT 6 +#define T_MODRDNCTSTR "modrdn" +#define T_MODRDNCT 7 +#define T_MODDNCTSTR "moddn" +#define T_MODDNCT 8 + +#define T_MODOPADDSTR "add" +#define T_MODOPADD 9 +#define T_MODOPREPLACESTR "replace" +#define T_MODOPREPLACE 10 +#define T_MODOPDELETESTR "delete" +#define T_MODOPDELETE 11 +#define T_MODSEPSTR "-" +#define T_MODSEP 12 + +#define T_NEWRDNSTR "newrdn" +#define T_NEWSUPERIORSTR ATTR_NEWSUPERIOR +#define T_DRDNFLAGSTR "deleteoldrdn" + +#define T_ERR -1 +#define AWAITING_OP -1 + +#define STATE_REFERRAL "referral" +#define STATE_UPDATE_REFERRAL "referral on update" +#define STATE_BACKEND "backend" + +#define REPL_PLUGIN_NAME "NSMMReplicationPlugin" +/* + * Changed version from 1.0 to 2.0 when we switched from libdb32 to libdb33 + * richm 20020708 + * also changed name from REPL_PLUGIN_VERSION to CHANGELOG_DB_VERSION since we use + * a different version for the plugin itself and this particular version is only + * used for the changelog database +*/ +/* + * Changed version from 2.0 to 3.0 when we switched from libdb33 to libdb41 + * noriko 20021203 + */ +#define CHANGELOG_DB_VERSION_PREV "3.0" +#define CHANGELOG_DB_VERSION "4.0" +extern char *repl_plugin_name; +extern char *repl_plugin_name_cl; + +/* repl_monitor.c */ +int repl_monitor_init(); + +/* In replutil.c */ +char ** get_cleattrs(); +unsigned long strntoul( char *from, size_t len, int base ); +void freepmods( LDAPMod **pmods ); +char *copy_berval (struct berval* from); +void entry_print(Slapi_Entry *e); +int copyfile(char* source, char *destination, int overwrite, int mode); +time_t age_str2time (const char *age); +const char* changeType2Str (int type); +int str2ChangeType (const char *str); +lenstr *make_changes_string(LDAPMod **ldm, char **includeattrs); +Slapi_Mods* parse_changes_string(char *str); +PRBool IsValidOperation (const slapi_operation_parameters *op); +const char *map_repl_root_to_dbid(Slapi_DN *repl_root); +PRBool is_ruv_tombstone_entry (Slapi_Entry *e); + +/* replication plugins */ +enum { + PLUGIN_LEGACY_REPLICATION, + PLUGIN_MULTIMASTER_REPLICATION, + PLUGIN_MAX +}; + +void* repl_get_plugin_identity (int pluginID); +void repl_set_plugin_identity (int pluginID, void *identity); + +#endif diff --git a/ldap/servers/plugins/replication/replication.def b/ldap/servers/plugins/replication/replication.def new file mode 100644 index 00000000..e71be4f6 --- /dev/null +++ b/ldap/servers/plugins/replication/replication.def @@ -0,0 +1,16 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7.0 Replication Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + plugin_init_debug_level @1 + replication_legacy_plugin_init @2 + replication_multimaster_plugin_init @3 + repl_chain_on_update @4 diff --git a/ldap/servers/plugins/replication/replutil.c b/ldap/servers/plugins/replication/replutil.c new file mode 100644 index 00000000..f8a23f93 --- /dev/null +++ b/ldap/servers/plugins/replication/replutil.c @@ -0,0 +1,1073 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + /* + * replutil.c - various utility functions common to all replication methods. + */ + +#include <nspr.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <errno.h> +#ifndef _WIN32 +#include <sys/file.h> +#include <sys/socket.h> +#include <unistd.h> +#include <fcntl.h> +#endif +#ifdef OS_solaris +#include <dlfcn.h> /* needed for dlopen and dlsym */ +#endif /* solaris: dlopen */ +#include <time.h> +#ifdef LINUX +#include <errno.h> /* weird use of errno */ +#endif + +#include "slapi-plugin.h" +#include "repl5.h" +#include "repl.h" + +typedef int (*open_fn)(const char *path, int flags, ...); + +/* this is set during replication plugin initialization from the plugin entry */ +static char *replpluginpath = NULL; +static PRBool is_chain_on_update_setup(const Slapi_DN *replroot); + +/* + * All standard changeLogEntry attributes (initialized in get_cleattrs) + */ +static char *cleattrs[ 10 ] = { NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL }; + +/* + * Function: get_cleattrs + * + * Returns: an array of pointers to attribute names. + * + * Arguments: None. + * + * Description: Initializes, if necessary, and returns an array of char *s + * with attribute names used for retrieving changeLogEntry + * entries from the directory. + */ +char ** +get_cleattrs() +{ + if ( cleattrs[ 0 ] == NULL ) { + cleattrs[ 0 ] = type_objectclass; + cleattrs[ 1 ] = attr_changenumber; + cleattrs[ 2 ] = attr_targetdn; + cleattrs[ 3 ] = attr_changetype; + cleattrs[ 4 ] = attr_newrdn; + cleattrs[ 5 ] = attr_deleteoldrdn; + cleattrs[ 6 ] = attr_changes; + cleattrs[ 7 ] = attr_newsuperior; + cleattrs[ 8 ] = attr_changetime; + cleattrs[ 9 ] = NULL; + } + return cleattrs; +} + +/* + * Function: add_bval2mods + * + * Description: same as add_val2mods, but sticks in a bval instead. + * val can be null. + */ +void +add_bval2mods(LDAPMod **mod, char *type, char *val, int mod_op) +{ + *mod = (LDAPMod *) slapi_ch_calloc(1, sizeof (LDAPMod)); + memset (*mod, 0, sizeof(LDAPMod)); + (*mod)->mod_op = mod_op | LDAP_MOD_BVALUES; + (*mod)->mod_type = slapi_ch_strdup(type); + + if (val != NULL){ + (*mod)->mod_bvalues = (struct berval **) slapi_ch_calloc(2, sizeof(struct berval *)); + (*mod)->mod_bvalues[0] = (struct berval *) slapi_ch_malloc (sizeof(struct berval)); + (*mod)->mod_bvalues[1] = NULL; + (*mod)->mod_bvalues[0]->bv_len = strlen(val); + (*mod)->mod_bvalues[0]->bv_val = slapi_ch_strdup(val); + } else { + (*mod)->mod_bvalues = NULL; + } +} + + +char* +copy_berval (struct berval* from) +{ + char* s = slapi_ch_malloc (from->bv_len + 1); + memcpy (s, from->bv_val, from->bv_len); + s [from->bv_len] = '\0'; + return s; +} + + +/* + * Function: entry_print + * Arguments: e - entry to print + * Returns: nothing + * Description: Prints the contents of an Slapi_Entry struct. Used for debugging. + */ +void +entry_print( Slapi_Entry *e ) +{ + int sz; + char *p; + + printf( "Slapi_Entry dump:\n" ); + + if ( e == NULL ) { + printf( "Slapi_Entry is NULL\n" ); + return; + } + + if (( p = slapi_entry2str( e, &sz )) == NULL ) { + printf( "slapi_entry2str returned NULL\n" ); + return; + } + puts( p ); + fflush( stdout ); + free( p ); + return; +} + +/* NSPR supports large file, but, according to dboreham, it does not work. + The backed has its own functions to deal with large files. I thought + about making them slapi function, but I don't think it makes sense because + server should only export function which have to do with its operation + and copying files is not one of them. So, instead, I made a copy of it in the + replication module. I will switch it to NSPR once that stuff works. +*/ + +int copyfile(char* source, char * destination, int overwrite, int mode) +{ +#if defined _WIN32 + return (0 == CopyFile(source,destination,overwrite ? FALSE : TRUE)); +#else +#ifdef DB_USE_64LFS +#define OPEN_FUNCTION dblayer_open_large +#else +#define OPEN_FUNCTION open +#endif + int source_fd = -1; + int dest_fd = -1; + char *buffer = NULL; + int return_value = -1; + int bytes_to_write = 0; + + /* malloc the buffer */ + buffer = (char*) malloc(64*1024); + if (NULL == buffer) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "copy file: memory allocation failed\n"); + goto error; + } + /* Open source file */ + source_fd = OPEN_FUNCTION(source,O_RDONLY,0); + if (-1 == source_fd) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "copyfile: failed to open source file %s\n", source); + goto error; + } + /* Open destination file */ + dest_fd = OPEN_FUNCTION(destination,O_CREAT | O_WRONLY, mode); + if (-1 == dest_fd) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "copyfile: failed to open destination file %s\n", destination); + goto error; + } + /* Loop round reading data and writing it */ + while (1) + { + return_value = read(source_fd,buffer,64*1024); + if (return_value <= 0) + { + /* means error or EOF */ + break; + } + bytes_to_write = return_value; + return_value = write(dest_fd,buffer,bytes_to_write); + if (return_value != bytes_to_write) + { + /* means error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "copyfile: failed to write to destination file %s\n"); + return_value = -1; + break; + } + } +error: + if (source_fd != -1) + { + close(source_fd); + } + if (dest_fd != -1) + { + close(dest_fd); + } + if (NULL != buffer) + { + free(buffer); + } + return return_value; +#endif +} + +/* convert time from string like 1h (1 hour) to corresponding time in seconds */ +time_t +age_str2time (const char *age) +{ + char *maxage; + char unit; + time_t ageval; + + if (age == NULL || age[0] == '\0' || strcmp (age, "0") == 0) + { + return 0; + } + + maxage = slapi_ch_strdup ( age ); + unit = maxage[ strlen( maxage ) - 1 ]; + maxage[ strlen( maxage ) - 1 ] = '\0'; + ageval = strntoul( maxage, strlen( maxage ), 10 ); + if ( maxage) + { + slapi_ch_free ( (void **) &maxage ); + } + switch ( unit ) + { + case 's': + break; + case 'm': + ageval *= 60; + break; + case 'h': + ageval *= ( 60 * 60 ); + break; + case 'd': + ageval *= ( 24 * 60 * 60 ); + break; + case 'w': + ageval *= ( 7 * 24 * 60 * 60 ); + break; + default: + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, + "age_str2time: unknown unit \"%c\" " + "for maxiumum changelog age\n", unit ); + ageval = -1; + } + + return ageval; +} + +const char* +changeType2Str (int type) +{ + switch (type) + { + case T_ADDCT: return T_ADDCTSTR; + case T_MODIFYCT: return T_MODIFYCTSTR; + case T_MODRDNCT: return T_MODRDNCTSTR; + case T_DELETECT: return T_DELETECTSTR; + default: return NULL; + } +} + +int +str2ChangeType (const char *str) +{ + if (strcasecmp (str, T_ADDCTSTR) == 0) + return T_ADDCT; + + if (strcasecmp (str, T_MODIFYCTSTR) == 0) + return T_MODIFYCT; + + if (strcasecmp (str, T_MODRDNCTSTR) == 0) + return T_MODRDNCT; + + if (strcasecmp (str, T_DELETECTSTR) == 0) + return T_DELETECT; + + return -1; +} + +lenstr * +make_changes_string(LDAPMod **ldm, char **includeattrs) +{ + lenstr *l; + int i, j, len; + int skip; + + /* loop through the LDAPMod struct and construct the changes attribute */ + l = lenstr_new(); + + for ( i = 0; ldm[ i ] != NULL; i++ ) { + /* If a list of explicit attributes was given, only add those */ + if ( NULL != includeattrs ) { + skip = 1; + for ( j = 0; includeattrs[ j ] != NULL; j++ ) { + if ( strcasecmp( includeattrs[ j ], ldm[ i ]->mod_type ) == 0 ) { + skip = 0; + break; + } + } + if ( skip ) { + continue; + } + } + switch ( ldm[ i ]->mod_op & ~LDAP_MOD_BVALUES ) { + case LDAP_MOD_ADD: + addlenstr( l, "add: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + case LDAP_MOD_DELETE: + addlenstr( l, "delete: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + case LDAP_MOD_REPLACE: + addlenstr( l, "replace: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + } + for ( j = 0; ldm[ i ]->mod_bvalues != NULL && + ldm[ i ]->mod_bvalues[ j ] != NULL; j++ ) { + char *buf = NULL; + char *bufp = NULL; + + len = strlen( ldm[ i ]->mod_type ); + len = LDIF_SIZE_NEEDED( len, + ldm[ i ]->mod_bvalues[ j ]->bv_len ) + 1; + buf = slapi_ch_malloc( len ); + bufp = buf; + ldif_put_type_and_value( &bufp, ldm[ i ]->mod_type, + ldm[ i ]->mod_bvalues[ j ]->bv_val, + ldm[ i ]->mod_bvalues[ j ]->bv_len ); + *bufp = '\0'; + + addlenstr( l, buf ); + + free( buf ); + } + addlenstr( l, "-\n" ); + } + return l; +} + +/* note that the string get modified by ldif_parse*** functions */ +Slapi_Mods * +parse_changes_string(char *str) +{ + int rc; + Slapi_Mods *mods; + Slapi_Mod mod; + char *line, *next; + char *type, *value; + int vlen; + struct berval bv; + + /* allocate mods array */ + mods = slapi_mods_new (); + if (mods == NULL) + return NULL; + + slapi_mods_init (mods, 16); /* JCMREPL - ONREPL : 16 bigger than needed? */ + + /* parse mods */ + next = str; + line = ldif_getline (&next); + while (line) + { + slapi_mod_init (&mod, 0); + while (line) + { + char * errmsg = NULL; + + if (strcasecmp (line, "-") == 0) + { + if (slapi_mod_isvalid (&mod)) + { + slapi_mods_add_smod (mods, &mod); + /* JCMREPL - ONREPL - slapi_mod_done(&mod) ??? */ + } + else + { + /* ONREPL - need to cleanup */ + } + + line = ldif_getline (&next); + break; + } + + rc = ldif_parse_line(line, &type, &value, &vlen, &errmsg); + if (rc != 0) + { + /* ONREPL - log warning */ + if ( errmsg != NULL ) { + slapi_log_error( SLAPI_LOG_PARSE, repl_plugin_name, "%s", errmsg ); + slapi_ch_free( (void**)&errmsg ); + } + slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name, + "Failed to parse the ldif line.\n"); + continue; + } + + if (strcasecmp (type, "add") == 0) + { + slapi_mod_set_operation (&mod, LDAP_MOD_ADD | LDAP_MOD_BVALUES); + } + else if (strcasecmp (type, "delete") == 0) + { + slapi_mod_set_operation (&mod, LDAP_MOD_DELETE | LDAP_MOD_BVALUES); + } + else if (strcasecmp (type, "replace") == 0) + { + slapi_mod_set_operation (&mod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + } + else /* attr: value pair */ + { + /* adding first value */ + if (slapi_mod_get_type (&mod) == NULL) + { + slapi_mod_set_type (&mod, type); + } + + bv.bv_val = value; + bv.bv_len = vlen; + + slapi_mod_add_value (&mod, &bv); + } + + line = ldif_getline (&next); + } + } + + return mods; +} + +static void* g_plg_identity [PLUGIN_MAX]; + +void* +repl_get_plugin_identity (int pluginID) +{ + PR_ASSERT (pluginID < PLUGIN_MAX); + return g_plg_identity [pluginID]; +} + +void +repl_set_plugin_identity (int pluginID, void *identity) +{ + PR_ASSERT (pluginID < PLUGIN_MAX); + g_plg_identity [pluginID] = identity; +} + +/* this function validates operation parameters */ +PRBool +IsValidOperation (const slapi_operation_parameters *op) +{ + if (op == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL operation\n"); + return PR_FALSE; + } + + if (op->csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL operation CSN\n"); + return PR_FALSE; + } + + if (op->target_address.uniqueid == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL entry uniqueid\n"); + return PR_FALSE; + } + + if (op->target_address.dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL entry DN\n"); + return PR_FALSE; + } + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.target_entry == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL entry for add operation\n"); + return PR_FALSE; + } + else + break; + + case SLAPI_OPERATION_MODIFY: if (op->p.p_modify.modify_mods == NULL || + op->p.p_modify.modify_mods[0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL mods for modify operation\n"); + return PR_FALSE; + } + else + break; + + case SLAPI_OPERATION_MODRDN: if (op->p.p_modrdn.modrdn_mods == NULL || + op->p.p_modrdn.modrdn_mods[0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL mods for modrdn operation\n"); + return PR_FALSE; + } + if (op->p.p_modrdn.modrdn_newrdn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL new rdn for modrdn operation\n"); + return PR_FALSE; + } + else + break; + + case SLAPI_OPERATION_DELETE: break; + + default: return PR_FALSE; + } + + return PR_TRUE; +} + + + +const char * +map_repl_root_to_dbid(Slapi_DN *repl_root) +{ + const char *return_ptr; + + PR_ASSERT(NULL != repl_root); + if (NULL != repl_root) + { + /* XXXggood get per-database ID here, when code available */ + } + return_ptr = get_server_dataversion(); /* XXXggood temporary hack until we have per-database instance dbids */ + return return_ptr; +} + + + +PRBool +is_ruv_tombstone_entry (Slapi_Entry *e) +{ + char *dn; + char *match; + PR_ASSERT (e); + + dn = slapi_entry_get_dn (e); + PR_ASSERT (dn); + + /* tombstone has rdn: nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff */ + match = strstr (dn, RUV_STORAGE_ENTRY_UNIQUEID); + + return (match != NULL); +} + +LDAPControl* create_managedsait_control () +{ + LDAPControl *control; + + control = (LDAPControl*)slapi_ch_malloc (sizeof (LDAPControl)); + + control->ldctl_oid = slapi_ch_strdup (LDAP_CONTROL_MANAGEDSAIT); + control->ldctl_value.bv_val = NULL; + control->ldctl_value.bv_len = 0; + control->ldctl_iscritical = '\0'; + + return control; +} + +LDAPControl* create_backend_control (Slapi_DN *sdn) +{ + LDAPControl *control = NULL; + const char *be_name = slapi_mtn_get_backend_name(sdn); + if (NULL != be_name) { + control = (LDAPControl*)slapi_ch_malloc (sizeof (LDAPControl)); + + control->ldctl_oid = slapi_ch_strdup ("2.16.840.1.113730.3.4.14"); + control->ldctl_value.bv_val = strdup(be_name); + control->ldctl_value.bv_len = strlen (be_name); + control->ldctl_iscritical = 1; + } + + return control; +} + +/* + * HREF_CHAR_ACCEPTABLE was copied from slapd/referral.c + * which was copied from libldap/tmplout.c. + */ +/* Note: an identical function is in ../../slapd/referral.c */ +#define HREF_CHAR_ACCEPTABLE( c ) (( c >= '-' && c <= '9' ) || \ + ( c >= '@' && c <= 'Z' ) || \ + ( c == '_' ) || \ + ( c >= 'a' && c <= 'z' )) + +/* + * Function: strcat_escaped + * + * Returns: nothing + * + * Description: Appends string s2 to s1, URL-escaping (%HH) unsafe + * characters in s2 as appropriate. This function was + * copied from slapd/referral.c. + * which was copied from libldap/tmplout.c. + * added const qualifier + * + * Author: MCS + */ +/* + * append s2 to s1, URL-escaping (%HH) unsafe characters + */ +/* Note: an identical function is in ../../slapd/referral.c */ +static void +strcat_escaped( char *s1, const char *s2 ) +{ + char *p, *q; + char *hexdig = "0123456789ABCDEF"; + + p = s1 + strlen( s1 ); + for ( q = (char*)s2; *q != '\0'; ++q ) { + if ( HREF_CHAR_ACCEPTABLE( *q )) { + *p++ = *q; + } else { + *p++ = '%'; + *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ]; + *p++ = hexdig[ 0x0F & *q ]; + } + } + + *p = '\0'; +} + +/* + This function appends the replication root to the purl referrals found + in the given ruv and the other given referrals, merges the lists, and sets the + referrals in the mapping tree node corresponding to the given sdn, which is the + repl_root + This function also sets the mapping tree state (e.g. disabled, backend, referral, + referral on update) - the mapping tree has very specific rules about how states + can be set in the presence of referrals - specifically: + 1) the nsslapd-referral attribute must be set before changing the state to referral + or referral on update + 2) the state must be set to backend or disabled before removing referrals +*/ +void +repl_set_mtn_state_and_referrals( + const Slapi_DN *repl_root_sdn, + const char *mtn_state, + const RUV *ruv, + char **ruv_referrals, + char **other_referrals +) +{ + int rc = 0; + int ii = 0; + char **referrals_to_set = NULL; + PRBool chain_on_update = is_chain_on_update_setup(repl_root_sdn); + + /* Fix for blackflag bug 601440: We want the new behaviour of DS, + ** going forward, to now be that if the nsds5replicareferral attrib + ** has values, it should be the only values in nsslapd-referral (as + ** opposed to older behaviour of concatenating with RUV-based + ** referrals). -jay@netscape.com + */ + if (other_referrals) { + /* use the referrals passed in, instead of RUV-based referrals */ + charray_merge(&referrals_to_set, other_referrals, 1); + /* Do copies. referrals is freed at the end */ + } + else + { + /* use the referrals from the RUV */ + ruv_referrals= (ruv ? ruv_get_referrals(ruv) : ruv_referrals); + if (ruv_referrals) { + charray_merge(&referrals_to_set, ruv_referrals, 1); + if (ruv) /* free referrals from ruv_get_referrals() */ + charray_free(ruv_referrals); + } + } + + /* next, add the repl root dn to each referral if not present */ + for (ii = 0; referrals_to_set && referrals_to_set[ii]; ++ii) { + struct ldap_url_desc *lud = NULL; + int myrc = ldap_url_parse(referrals_to_set[ii], &lud); + /* see if the dn is already in the referral URL */ + if (myrc == LDAP_URL_ERR_NODN || !lud || !lud->lud_dn) { + /* add the dn */ + int len = strlen(referrals_to_set[ii]); + const char *cdn = slapi_sdn_get_dn(repl_root_sdn); + char *tmpref = NULL; + int need_slash = 0; + if (referrals_to_set[ii][len-1] != '/') { + len++; /* add another one for the slash */ + need_slash = 1; + } + len += (strlen(cdn) * 3) + 2; /* 3 for %HH possible per char */ + tmpref = slapi_ch_malloc(len); + sprintf(tmpref, "%s%s", referrals_to_set[ii], (need_slash ? "/" : "")); + strcat_escaped(tmpref, cdn); + slapi_ch_free((void **)&referrals_to_set[ii]); + referrals_to_set[ii] = tmpref; + } + if (lud) + ldap_free_urldesc(lud); + } + + if (!referrals_to_set) { /* deleting referrals */ + /* Set state before */ + if (!chain_on_update) { + slapi_mtn_set_state(repl_root_sdn, (char *)mtn_state); + } + /* We should delete referral only if we want to set the + replica database in backend state mode */ + /* if chain on update mode, go ahead and set the referrals anyway */ + if (strcasecmp(mtn_state, STATE_BACKEND) == 0 || chain_on_update) { + rc = slapi_mtn_set_referral(repl_root_sdn, referrals_to_set); + if (rc == LDAP_NO_SUCH_ATTRIBUTE) { + /* we will get no such attribute (16) if we try to set the referrals to NULL if + there are no referrals - not an error */ + rc = LDAP_SUCCESS; + } + } + } else { /* Replacing */ + rc = slapi_mtn_set_referral(repl_root_sdn, referrals_to_set); + if (rc == LDAP_SUCCESS && !chain_on_update){ + slapi_mtn_set_state(repl_root_sdn, (char *)mtn_state); + } + } + + if (rc != LDAP_SUCCESS) { + char ebuf[BUFSIZ]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "repl_set_mtn_referrals: could " + "not set referrals for replica %s: %d\n", + escape_string(slapi_sdn_get_dn(repl_root_sdn), ebuf), rc); + } + + charray_free(referrals_to_set); + return; +} + +/* + * This function allows to use a local backend in conjunction with + * a chaining backend + * The local ldbm backend is the replication consumer database + * (e.g. on a hub or consumer) - it is read-only except for supplier updates + * The chaining backend points to the supplier(s) + * This distribution logic forwards the update request to the chaining + * backend, and sends the search request to the local ldbm database + * + * To be able to use it one must define one ldbm backend and one chaining + * backend in the mapping tree node - the ldbm backend will usually + * already be there + * + */ +int +repl_chain_on_update(Slapi_PBlock *pb, Slapi_DN * target_dn, + char **mtn_be_names, int be_count, + Slapi_DN * node_dn, int *mtn_be_states) +{ + char * requestor_dn; + unsigned long op_type; + Slapi_Operation *op; + int repl_op = 0; + int local_backend = -1; /* index of local backend */ + int chaining_backend = -1; /* index of chain backend */ + PRBool local_online = PR_FALSE; /* true if the local db is online */ + PRBool chain_online = PR_FALSE; /* true if the chain db is online */ + int ii; + int opid, connid; + + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid); + /* first, we have to decide which backend is the local backend + * and which is the chaining one + * also find out if any are not online (e.g. during import) + */ + for (ii = 0; ii < be_count; ++ii) + { + Slapi_Backend *be = slapi_be_select_by_instance_name(mtn_be_names[ii]); + if (slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)) + { + chaining_backend = ii; + if (mtn_be_states[ii] == SLAPI_BE_STATE_ON) + { + chain_online = PR_TRUE; + } + } + else + { + local_backend = ii; + if (mtn_be_states[ii] == SLAPI_BE_STATE_ON) + { + local_online = PR_TRUE; + } + } +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d be " + "%s is the %s backend and is %s\n", + connid, opid, + mtn_be_names[ii], (chaining_backend == ii) ? "chaining" : "local", + (mtn_be_states[ii] == SLAPI_BE_STATE_ON) ? "online" : "offline"); +*/ + } + + /* if no chaining backends are defined, just use the local one */ + if (chaining_backend == -1) { + return local_backend; + } + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* All internal operations go to the local backend */ + if (operation_is_flag_set(op, OP_FLAG_INTERNAL)) { + return local_backend; + } + + /* Check the operation type + * read-only operation will go to the local backend if online + */ + op_type = slapi_op_get_type(op); + if (local_online && + ((op_type == SLAPI_OPERATION_SEARCH) || + (op_type == SLAPI_OPERATION_BIND) || + (op_type == SLAPI_OPERATION_UNBIND) || + (op_type == SLAPI_OPERATION_COMPARE))) { +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d op is " + "%d: using local backend\n", + connid, opid, op_type); +*/ + return local_backend; + } + + /* if the operation is done by directory manager + * use local database even for updates because it is an administrative + * operation + * remarks : one could also use an update DN in the same way + * to let update operation go to the local backend when they are done + * by specific administrator user but let all the other user + * go to the read-write replica + * also - I don't think it is possible to chain directory manager + */ + slapi_pblock_get(pb, SLAPI_REQUESTOR_DN, &requestor_dn); + if (slapi_dn_isroot(requestor_dn)) { +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d requestor " + "is root: using local backend\n", connid, opid); +*/ + return local_backend; + } + + /* if the operation is a replicated operation + * use local database even for updates to avoid infinite loops + */ + slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op); + if (repl_op) { +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d op is " + "replicated: using local backend\n", connid, opid); +*/ + return local_backend; + } + + /* all other case (update while not directory manager) : + * or any normal non replicated client operation while local is disabled (import) : + * use the chaining backend + */ +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d using " + "chaining backend\n", connid, opid); +*/ + return chaining_backend; +} + +int +repl_enable_chain_on_update(Slapi_DN *suffix) +{ + /* Submit a Modify operation to add the distribution function to the mapping tree + node for the given suffix */ + slapi_mods smods; + Slapi_Operation *op = NULL; + int operation_result; + Slapi_PBlock *pb= slapi_pblock_new(); + char *mtnnodedn; + + slapi_mods_init(&smods,2); + + /* need path and file name of the replication plugin here */ + slapi_mods_add_string(&smods, LDAP_MOD_ADD, "nsslapd-distribution-plugin", replpluginpath); + slapi_mods_add_string(&smods, LDAP_MOD_ADD, "nsslapd-distribution-funct", "repl_chain_on_update"); + + /* need DN of mapping tree node here */ + mtnnodedn = slapi_get_mapping_tree_node_configdn(suffix); + slapi_modify_internal_set_pb( + pb, + mtnnodedn, + slapi_mods_get_ldapmods_byref(&smods), /* JCM cast */ + NULL, /*Controls*/ + NULL, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + + slapi_modify_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result); + slapi_ch_free_string(&mtnnodedn); + slapi_pblock_destroy(pb); + switch(operation_result) + { + case LDAP_SUCCESS: + /* OK, everything is fine. */ + break; + default: + PR_ASSERT(0); /* JCMREPL FOR DEBUGGING */ + } + slapi_mods_done(&smods); + + return operation_result; +} + +int +repl_disable_chain_on_update(Slapi_DN *suffix) +{ + /* Submit a Modify operation to remove the distribution function from the mapping tree + node for the given suffix */ + slapi_mods smods; + Slapi_Operation *op = NULL; + int operation_result; + Slapi_PBlock *pb= slapi_pblock_new(); + char *mtnnodedn; + + slapi_mods_init(&smods,2); + + slapi_mods_add_modbvps(&smods, LDAP_MOD_DELETE, "nsslapd-distribution-plugin", NULL); + slapi_mods_add_modbvps(&smods, LDAP_MOD_DELETE, "nsslapd-distribution-funct", NULL); + + /* need DN of mapping tree node here */ + mtnnodedn = slapi_get_mapping_tree_node_configdn(suffix); + slapi_modify_internal_set_pb( + pb, + mtnnodedn, + slapi_mods_get_ldapmods_byref(&smods), /* JCM cast */ + NULL, /*Controls*/ + NULL, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + + slapi_modify_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result); + slapi_ch_free_string(&mtnnodedn); + slapi_pblock_destroy(pb); + switch(operation_result) + { + case LDAP_SUCCESS: + /* OK, everything is fine. */ + break; + default: + PR_ASSERT(0); /* JCMREPL FOR DEBUGGING */ + } + slapi_mods_done(&smods); + + return operation_result; +} + +static PRBool +is_chain_on_update_setup(const Slapi_DN *replroot) +{ + /* Do an internal search of the mapping tree node to see if chain on update is setup + for this replica + - has two backends + - has a distribution function + - has a distribution plugin + - one of the backends is a ldbm database + - one of the backends is a chaining database + */ + static char* attrs[] = { "nsslapd-backend", + "nsslapd-distribution-plugin", "nsslapd-distribution-funct", + NULL }; + int operation_result; + Slapi_PBlock *pb= slapi_pblock_new(); + char *mtnnodedn = slapi_get_mapping_tree_node_configdn(replroot); + PRBool retval = PR_FALSE; + + slapi_search_internal_set_pb( + pb, + mtnnodedn, + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, /*attrs*/ + 0, /*attrsonly*/ + NULL, /*Controls*/ + NULL, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + slapi_search_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result); + switch(operation_result) + { + case LDAP_SUCCESS: + { + Slapi_Entry **entries= NULL; + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if(entries!=NULL && entries[0]!=NULL) + { + Slapi_Entry *e = entries[0]; + + char **backends = slapi_entry_attr_get_charray(e, "nsslapd-backend"); + char *plg = slapi_entry_attr_get_charptr(e, "nsslapd-distribution-plugin"); + char *func = slapi_entry_attr_get_charptr(e, "nsslapd-distribution-funct"); + + if (backends && backends[0] && backends[1] && plg && func) + { + /* all the necessary attrs are present - check to see if we + have one chaining backend */ + Slapi_Backend *be0 = slapi_be_select_by_instance_name(backends[0]); + Slapi_Backend *be1 = slapi_be_select_by_instance_name(backends[1]); + PRBool foundchain0 = slapi_be_is_flag_set(be0,SLAPI_BE_FLAG_REMOTE_DATA); + PRBool foundchain1 = slapi_be_is_flag_set(be1,SLAPI_BE_FLAG_REMOTE_DATA); + retval = (foundchain0 || foundchain1) && + !(foundchain0 && foundchain1); /* 1 (but not both) backend is chaining */ + } + slapi_ch_array_free(backends); + slapi_ch_free_string(&plg); + slapi_ch_free_string(&func); + } + else /* could not find mapping tree entry - assume not set up */ + { + } + } + break; + default: /* search error - assume not set up */ + break; + } + slapi_ch_free_string(&mtnnodedn); + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return retval; +} + +void +repl_set_repl_plugin_path(const char *path) +{ + replpluginpath = slapi_ch_strdup(path); +} diff --git a/ldap/servers/plugins/replication/tests/dnp_sim.c b/ldap/servers/plugins/replication/tests/dnp_sim.c new file mode 100644 index 00000000..ff524988 --- /dev/null +++ b/ldap/servers/plugins/replication/tests/dnp_sim.c @@ -0,0 +1,1033 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dnp_simulation.c - this file varifies the correctness of dnp algorithm + by generating random sequences of operations, applying + the algorithm and outputing the result + + usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>] + -h - print usage information. + -n <number of simulations> - how many simulations to perform; default - 1. + -v - verbose mode (prints full entry state after each operation execution) + -f <output file> - file where results are stored; by default results are + printed to the screen. + -o <op file> - file that contains operation sequence to execute; by default, + random sequence is generated. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <memory.h> +#include <string.h> +#include <time.h> + +#define MAX_OPS 12 /* maximum number of operations in a simulation */ +#define MAX_VALS 10 /* maximum number of values is entry or dn */ +#define NOT_PRESENT -1 + +/* data types */ +typedef struct value_state +{ + int value_index; /* value */ + int presense_csn; /* last time at which we know the value was present */ + int distinguished_csn; /* last time at which we know the value was distinguished */ + int delete_csn; /* last attempt to delete this value */ + int non_distinguished_csns [MAX_OPS];/* list of times at which value became non-distinguished */ + int present; /* flag that tells whether the value iscurrently present */ +} Value_State; + +typedef struct entry_state +{ + int dn_index; + int dn_csn; + Value_State values[MAX_VALS]; /* values of the attribute */ + int attr_delete_csn; /* last attempt to delete the entire attribute */ +} Entry_State; + +typedef enum +{ + OP_ADD_VALUE, + OP_RENAME_ENTRY, + OP_DELETE_VALUE, + OP_DELETE_ATTR, + OP_END +} Operation_Type; + +typedef struct operation +{ + Operation_Type type; /* operation type */ + int csn; /* operation type */ + int value_index; /* value to add, remove or rename from */ + int old_dn_index; /* new dn - rename only */ +}Operation; + +typedef struct simulator_params +{ + int runs; /* number of runs */ + FILE *fout; /* output file */ + int value_count; /* number of values */ + int verbose; /* verbose mode */ + Operation *ops; /* operation sequence to apply */ + int op_count; +}Simulator_Params; + + +/* gloabl data */ +Simulator_Params sim; +char *g_values[] = +{ + "v", + "u", + "w", + NULL +}; + +/* forward declarations */ + +/* initialization */ +void process_cmd (int argc, char **argv); +void set_default_sim_params (); +int count_values (); +void parse_operations_file (char *name); +void parse_operation (char *line, int pos); +int value2index (char *value); +void print_usage (); + +/* simulation run */ +void run_simulation (); +void generate_operations (Operation **ops, int *op_count); +void generate_operation (Operation *op, int csn, int *last_dn_index); +int* generate_operation_order (int op_count, int seq_num); +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); +void init_entry_state (Entry_State *entry); +void init_value_state (Value_State *val, int seq_num); +void apply_operation (Entry_State *entry, Operation *op); +void free_operations (Operation **ops); +int ** new_perm_table (int op_count); +void free_perm_table (int ***perm_table, int op_count); +int get_perm_count (int op_count); +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table); +void apply_add_operation (Entry_State *entry, Operation *op); +void apply_value_delete_operation (Entry_State *entry, Operation *op); +void apply_attr_delete_operation (Entry_State *entry, Operation *op); +void apply_rename_operation (Entry_State *entry, Operation *op); +void make_value_distinguished (int op_csn, Entry_State *entry, int value_index); +void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index); +void purge_value_state (Value_State *value); +void purge_non_distinguished_csns (Value_State *value); +void resolve_value_state (Entry_State *entry, int value_index); +int value_distinguished_at_delete (Value_State *value, int attr_delete_csn); +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); + +/* data tracing */ +void dump_operations (Operation *ops, int op_count, int *order); +void dump_operation (Operation *op); +void dump_perm_table (int **perm_table, int op_count); +void dump_entry_state (Entry_State *entry); +void dump_value_state (Value_State *value); +void dump_list (int *list); + +/* misc functions */ +int max_val (int a, int b); +int is_list_empty (int *list); +int min_list_val (int *list); +int list_equal (int *list1, int *list2); + +int main (int argc, char **argv) +{ + int i; + + process_cmd (argc, argv); + + for (i = 0; i < sim.runs; i++) + { + fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); + run_simulation (); + fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); + } + + if (sim.fout != stdout) + fclose (sim.fout); + + return 0; +} + +void process_cmd (int argc, char **argv) +{ + int i; + + set_default_sim_params (); + + if (argc == 1) + { + return; + } + + if (strcmp (argv[1], "-h") == 0) /* print help */ + { + print_usage (); + exit (0); + } + + i = 1; + while (i < argc) + { + if (strcmp (argv[i], "-v") == 0) /* verbose mode */ + { + sim.verbose = 1; + i ++; + } + else if (strcmp (argv[i], "-n") == 0) + { + if (i < argc - 1) + { + int runs = atoi (argv[i + 1]); + if (runs > 0) + sim.runs = runs; + i+=2; + } + else + { + /* ONREPL print warning */ + i++; + } + } + else if (strcmp (argv[i], "-f") == 0) /* output file */ + { + if (i < argc - 1) + { + FILE *f = fopen (argv[i + 1], "w"); + if (f == 0) + { + printf ("failed to open output file; error - %s, using stdout\n", + strerror(errno)); + } + else + { + /* ONREPL print warning */ + sim.fout = f; + } + + i += 2; + } + else + i++; + } + else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ + { + if (i < argc - 1) + { + parse_operations_file (argv[i+1]); + i += 2; + } + else + { + /* ONREPL print warning */ + i ++; + } + } + else /* unknown option */ + { + printf ("unknown option - %s; ignored\n", argv[i]); + i ++; + } + + } +} + +void set_default_sim_params () +{ + memset (&sim, 0, sizeof (sim)); + sim.runs = 1; + sim.fout = stdout; + sim.value_count = count_values (); +} + +/* file format: <op count> + add <value> + delete <value> + delete attribute + rename <value> to <value> + */ +void parse_operations_file (char *name) +{ + FILE *file = fopen (name, "r"); + char line [256]; + int i; + + if (file == NULL) + { + printf ("failed to open operations file %s: error = %d\n", name, errno); + print_usage (); + exit (1); + } + + i = 0; + while (fgets (line, sizeof (line), file)) + { + if (i == 0) + { + /* read operation count */ + sim.op_count = atoi (line); + if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) + { + printf ("invalid operation count - %d; value must be between 1 and %d\n", + sim.op_count, MAX_OPS/2); + print_usage (); + exit (1); + } + else + { + sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); + } + } + else + { + if (strlen (line) == 0) /* skip empty lines */ + continue; + parse_operation (line, i); + } + + i ++; + } + +} + +void parse_operation (char *line, int i) +{ + sim.ops [i - 1].csn = i; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + if (strncmp (line, "add", 3) == 0) + { + sim.ops [i - 1].type = OP_ADD_VALUE; + sim.ops [i - 1].value_index = value2index (&line[4]); + } + else if (strncmp (line, "delete attribute", 16) == 0) + { + sim.ops [i - 1].type = OP_DELETE_ATTR; + } + else if (strncmp (line, "delete", 6) == 0) + { + sim.ops [i - 1].type = OP_DELETE_VALUE; + sim.ops [i - 1].value_index = value2index (&line[7]); + } + else if (strncmp (line, "rename", 6) == 0) + { + char *tok; + sim.ops [i - 1].type = OP_RENAME_ENTRY; + /* strtok() is not MT safe, but it is okay to call here because this is a program test */ + tok = strtok (&line[7], " "); + sim.ops [i - 1].old_dn_index = value2index (tok); + /* skip to */ + tok = strtok (NULL, " "); + tok = strtok (NULL, " "); + sim.ops [i - 1].value_index = value2index (tok); + } + else + { + /* invalid line */ + printf ("invalid operation: %s\n", line); + exit (1); + } +} + +int value2index (char *value) +{ + int i; + + for (i = 0; i < sim.value_count; i++) + { + if (strcmp (g_values[i], value) == 0) + return i; + } + + return -1; +} + +void print_usage () +{ + printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n" + "\t-h - print usage information\n" + "\t-n <number of simulations>; default - 1\n" + "\t-v - verbose mode\n" + "\t-f <output file> - by default, results are printed to the screen\n" + "\t-o <op file> - file that contains operation sequence to execute;\n" + "\tby default, random sequence is generated.\n"); +} + +int count_values () +{ + int i; + + for (i = 0; g_values[i]; i++); + + return i; +} + +void run_simulation () +{ + int *order; + int i; + int perm_count; + Entry_State entry_first, entry_current; + int error = 0; + + init_entry_state (&entry_first); + fprintf (sim.fout, "initial entry state :\n"); + dump_entry_state (&entry_first); + + if (sim.ops == NULL) + { + generate_operations (&sim.ops, &sim.op_count); + } + fprintf (sim.fout, "initial operation set:\n"); + dump_operations (sim.ops, sim.op_count, NULL/* order */); + + perm_count = get_perm_count (sim.op_count); + for (i = 0; i < perm_count; i++) + { + fprintf (sim.fout, "--------------------------------\n"); + fprintf (sim.fout, "simulation run %d\n", i + 1); + fprintf (sim.fout, "--------------------------------\n"); + order = generate_operation_order (sim.op_count, i); + if (i == 0) + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); + else + { + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); + error |= compare_entry_state (&entry_first, &entry_current, i + 1); + } + } + + switch (error) + { + case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); + break; + case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " + "the exact state does not match\n"); + break; + case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); + break; + } + + free_operations (&sim.ops); +} + +void generate_operations (Operation **ops, int *op_count) +{ + int i; + int last_dn_index = 0; + + /* generate number operations in the sequence */ + *op_count = slapi_rand () % (MAX_OPS / 2) + 1; + *ops = (Operation *)malloc (*op_count * sizeof (Operation)); + + for (i = 0; i < *op_count; i ++) + { + generate_operation (&((*ops)[i]), i + 1, &last_dn_index); + } +} + +void generate_operation (Operation *op, int csn, int *last_dn_index) +{ + /* generate operation type */ + op->type = slapi_rand () % OP_END; + + /* generate value to which operation applies */ + op->value_index = slapi_rand () % sim.value_count; + + op->csn = csn; + + /* generate new distinguished value */ + if (op->type == OP_RENAME_ENTRY) + { + op->old_dn_index = *last_dn_index; + while (op->value_index == op->old_dn_index) + op->value_index = slapi_rand () % sim.value_count; + *last_dn_index = op->value_index; + } +} + +int* generate_operation_order (int op_count, int seq_num) +{ + static int **perm_table = NULL; + + /* first request - generate pemutation table */ + if (seq_num == 0) + { + int elements [MAX_OPS]; + int i; + + if (perm_table) + free_perm_table (&perm_table, op_count); + perm_table = new_perm_table (op_count); + + for (i = 0; i < op_count; i++) + elements [i] = i; + + generate_perm_table (elements, op_count, 0 /* static part */, + perm_table); + /* dump_perm_table (perm_table, op_count);*/ + } + + return perm_table [seq_num]; +} + +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) +{ + int i; + + init_entry_state (entry); + + if (!sim.verbose) + { + if (!sim.verbose) + { + fprintf (sim.fout, "operation_sequence for this run:\n"); + dump_operations (ops, op_count, order); + } + } + + for (i = 0; i < op_count; i++) + { + apply_operation (entry, &(ops [order[i]])); + } + + if (!sim.verbose) + { + fprintf (sim.fout, "final entry state :\n"); + dump_entry_state (entry); + } + +} + +void init_entry_state (Entry_State *entry) +{ + int i; + + memset (entry, 0, sizeof (*entry)); + entry->attr_delete_csn = NOT_PRESENT; + + for (i = 0; i < sim.value_count; i++) + { + init_value_state (&(entry->values[i]), i); + } +} + +void init_value_state (Value_State *val, int seq_num) +{ + memset (val, 0, sizeof (*val)); + val->value_index = seq_num; + val->present = 1; + val->delete_csn = NOT_PRESENT; + if (seq_num > 0) /* only first value is distinguished */ + val->distinguished_csn = -1; +} + +void apply_operation (Entry_State *entry, Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: apply_add_operation (entry, op); + break; + + case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); + break; + + case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); + break; + + case OP_RENAME_ENTRY: apply_rename_operation (entry, op); + break; + } + + if (sim.verbose) + { + fprintf (sim.fout, "operation: "); + dump_operation (op); + fprintf (sim.fout, "\n"); + dump_entry_state (entry); + } +} + +void free_operations (Operation **ops) +{ + free (*ops); + *ops = NULL; +} + +int **new_perm_table (int op_count) +{ + int i; + int **perm_table; + int perm_count = get_perm_count (op_count); + + perm_table = (int**)malloc (perm_count * sizeof (int*)); + for (i = 0; i < perm_count; i ++) + perm_table [i] = (int*) malloc (op_count * sizeof (int)); + + return perm_table; +} + +void free_perm_table (int ***perm_table, int op_count) +{ + int i; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < perm_count; i ++) + free ((*perm_table)[i]); + + free (*perm_table); + *perm_table = NULL; +} + +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table) +{ + int i; + int elements_copy [MAX_OPS]; + int start_pos; + + if (element_count - 1 == static_part) + { + memcpy (*perm_table, elements, element_count * sizeof (int)); + return; + } + + start_pos = 0; + for (i = 0; i < element_count - static_part; i ++) + { + memcpy (elements_copy, elements, element_count * sizeof (int)); + elements_copy [static_part] = elements [static_part + i]; + elements_copy [static_part + i] = elements [static_part]; + generate_perm_table (elements_copy, element_count, static_part + 1, + &perm_table [start_pos]); + start_pos += get_perm_count (element_count - static_part - 1); + } +} + +int get_perm_count (int op_count) +{ + int i; + int perm_count = 1; + + for (i = 2; i <= op_count; i ++) + perm_count *= i; + + return perm_count; +} + +void apply_add_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].presense_csn < op->csn) + { + entry->values[op->value_index].presense_csn = op->csn; + entry->values[op->value_index].present = 1; + resolve_value_state (entry, op->value_index); + } +} + +void apply_value_delete_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].delete_csn < op->csn) + { + entry->values[op->value_index].delete_csn = op->csn; + resolve_value_state (entry, op->value_index); + } +} + +void apply_attr_delete_operation (Entry_State *entry, Operation *op) +{ + int i; + + if (entry->attr_delete_csn < op->csn) + { + entry->attr_delete_csn = op->csn; + + for (i = 0; i < sim.value_count; i++) + { + resolve_value_state (entry, i); + } + } +} + +void apply_rename_operation (Entry_State *entry, Operation *op) +{ + if (entry->dn_csn < op->csn) + { + entry->dn_index = op->value_index; + entry->dn_csn = op->csn; + } + + make_value_non_distinguished (op->csn, entry, op->old_dn_index); + make_value_distinguished (op->csn, entry, op->value_index); +} + +void make_value_distinguished (int op_csn, Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + + if (value->distinguished_csn < op_csn) + { + value->distinguished_csn = op_csn; + + if (value->presense_csn < op_csn) + { + value->present = 1; + value->presense_csn = op_csn; + } + + resolve_value_state (entry, value_index); + } +} + +void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index) +{ + int i = 0; + int index; + Value_State *value = &(entry->values[value_index]); + + if (op_csn < value->distinguished_csn) + return; + + /* insert into sorted list */ + while (value->non_distinguished_csns[i] && value->non_distinguished_csns[i] < op_csn) + i++; + + if (value->non_distinguished_csns[i] == 0) + value->non_distinguished_csns[i] = op_csn; + else + { + index = i; + + while (value->non_distinguished_csns[i]) + i++; + + memcpy (&(value->non_distinguished_csns[index + 1]), + &(value->non_distinguished_csns[index]), (i - index) * sizeof (int)); + + value->non_distinguished_csns[index] = op_csn; + } + + resolve_value_state (entry, value_index); +} + +void purge_value_state (Value_State *value) +{ + /* value state information can be purged once a value was + readed/made distinguished because at that point we know that the value + existed/was distinguished */ + + purge_non_distinguished_csns (value); + + if (value->delete_csn < max_val (value->distinguished_csn, value->presense_csn)) + value->delete_csn = NOT_PRESENT; +} + +void purge_non_distinguished_csns (Value_State *value) +{ + int i = 0; + int index; + + while (value->non_distinguished_csns[i] && + value->non_distinguished_csns[i] < value->distinguished_csn) + i ++; + + if (i > 0) + { + index = i-1; + while (value->non_distinguished_csns[i]) + i ++; + + if (i > index + 1) + { + memcpy (value->non_distinguished_csns, &value->non_distinguished_csns[index+1], + (i - index - 1) * sizeof (int)); + memset (&(value->non_distinguished_csns[index+1]), 0, sizeof (int) * (i - index - 1)); + } + else + { + memset (value->non_distinguished_csns, 0, sizeof (int) * i); + } + } +} + +int is_list_empty (int *list) +{ + return (list[0] == 0); +} + +int min_list_val (int *list) +{ + return (list [0]); +} + +void resolve_value_state (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + + purge_value_state (value); + + /* no deletes that effect the state */ + if (max_val (value->delete_csn, entry->attr_delete_csn) < + max_val (value->distinguished_csn, value->presense_csn)) + return; + + if (value->present) /* check if it should be removed based on the current state */ + { + if (!value_distinguished_at_delete (value, entry->attr_delete_csn)) + { + /* note that we keep presence csn because we might have to restore + the value in the future */ + value->present = 0; + } + } + else /* not present - check if it should be restored */ + { + if (value_distinguished_at_delete (value, entry->attr_delete_csn)) + { + value->present = 1; + } + } +} + +/* Note we can't trim distinguished_csn (even during regular trimming) + because in some cases we would not be able to figure out whether + a value was distinguished or not at the time of deletion. + + Example 1: Example2: + csn order operation csn order operation + 1 1 make V distinguished 1 1 make V distinguished + 3 3 delete V 2 2 make V non distinguished + 4 2 make V non-distinguished 3 4 delete V + 4 3 make V non distinguished (on another server) + + if the csns up to 2 were triimed, when delete operation is received, the state + is exactly the same in both examples but in example one delete should not go + through while in example 2 it should + + */ +int value_distinguished_at_delete (Value_State *value, int attr_delete_csn) +{ + if (value->distinguished_csn >= 0 && + (is_list_empty (value->non_distinguished_csns) || + min_list_val (value->non_distinguished_csns) > + max_val (value->delete_csn, attr_delete_csn))) + return 1; + else + return 0; +} + +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) +{ + int j; + int error = 0; + + /* first - quick check for present / not present */ + for (j = 0; j < sim.value_count; j++) + { + if (entry1->values[j].present != entry2->values[j].present) + { + fprintf (sim.fout, + "value %s is %s present in the first run but %s present in the %d run\n", + g_values[j], entry1->values[j].present ? "" : "not", + entry2->values[j].present ? "" : "not", run); + error = 1; + } + } + + if (error) + return 3; + + /* compare value state */ + error = 0; + if (entry1->attr_delete_csn != entry2->attr_delete_csn) + { + fprintf (sim.fout, "attribute delete csn is %d for run 1 " + "but is %d for run %d\n", entry1->attr_delete_csn, + entry2->attr_delete_csn, run); + error = 1; + } + + for (j = 0; j < sim.value_count; j++) + { + if (entry1->values[j].presense_csn != entry2->values[j].presense_csn) + { + fprintf (sim.fout, "presence csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[j], entry1->values[j].presense_csn, + entry2->values[j].presense_csn, run); + error = 1; + } + + if (entry1->values[j].distinguished_csn != entry2->values[j].distinguished_csn) + { + fprintf (sim.fout, "distinguished csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[j], entry1->values[j].distinguished_csn, + entry2->values[j].distinguished_csn, run); + error = 1; + } + + if (entry1->values[j].delete_csn != entry2->values[j].delete_csn) + { + fprintf (sim.fout, "delete csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[j], entry1->values[j].delete_csn, + entry2->values[j].delete_csn, run); + error = 1; + } + + if (!list_equal (entry1->values[j].non_distinguished_csns, + entry2->values[j].non_distinguished_csns)) + { + fprintf (sim.fout, "pending list mismatch for valye %s in runs 1 and %d\n", + g_values[j], run); + dump_list (entry1->values[j].non_distinguished_csns); + dump_list (entry2->values[j].non_distinguished_csns); + } + } + + if (error != 0) + { + return 1; + } + else + return 0; +} + +void dump_operations (Operation *ops, int op_count, int *order) +{ + int index; + int i; + + for (i = 0; i < op_count; i ++) + { + if (order == NULL) /* current order */ + index = i; + else + index = order [i]; + + dump_operation (&ops[index]); + } + + fprintf (sim.fout, "\n"); +} + +void dump_operation (Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: + fprintf (sim.fout, "\t%d add value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_VALUE: + fprintf (sim.fout, "\t%d delete value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_ATTR: + fprintf (sim.fout, "\t%d delete attribute\n", op->csn); + break; + case OP_RENAME_ENTRY: + fprintf (sim.fout, "\t%d rename entry from %s to %s\n", op->csn, + g_values [op->old_dn_index], g_values [op->value_index]); + break; + } +} + +void dump_perm_table (int **perm_table, int op_count) +{ + int i, j; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < op_count; i++) + { + for (j = 0; j < perm_count; j++) + { + fprintf (sim.fout, "%d ", perm_table [j][i]); + } + + fprintf (sim.fout, "\n"); + } +} + +void dump_entry_state (Entry_State *entry) +{ + int i; + fprintf (sim.fout, "\tentry dn: %s; dn csn - %d\n", + g_values [entry->dn_index], entry->dn_csn); + + if (sim.verbose) + fprintf (sim.fout, "\n"); + + for (i = 0; i < sim.value_count; i++) + { + dump_value_state (&(entry->values[i])); + } + + fprintf (sim.fout, "\n"); +} + +void dump_value_state (Value_State *value) +{ + fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index], + value->present ? "present" : "not present"); + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presense_csn); + fprintf (sim.fout, "\t\tdistinguished csn: %d\n", value->distinguished_csn); + fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn); + fprintf (sim.fout, "\t\tnon distinguished csns: "); + + dump_list (value->non_distinguished_csns); + + fprintf (sim.fout, "\n"); + } +} + +void dump_list (int *list) +{ + int i = 0; + + while (list[i]) + { + fprintf (sim.fout, "%d ", list[i]); + i ++; + } + + fprintf (sim.fout, "\n"); +} + +/* misc functions */ +int max_val (int a, int b) +{ + if (a >= b) + return a; + else + return b; +} + +int list_equal (int *list1, int *list2) +{ + int i; + + i = 0; + while (list1[i] && list2[i]) + { + if (list1[i] != list2[i]) + return 0; + + i ++; + } + + if (list1[i] != list2[i]) + return 0; + else + return 1; +} diff --git a/ldap/servers/plugins/replication/tests/dnp_sim2.c b/ldap/servers/plugins/replication/tests/dnp_sim2.c new file mode 100644 index 00000000..e1838aa6 --- /dev/null +++ b/ldap/servers/plugins/replication/tests/dnp_sim2.c @@ -0,0 +1,972 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dnp_simulation.c - this file varifies the correctness of dnp algorithm + by generating random sequences of operations, applying + the algorithm and outputing the result + + usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>] + -h - print usage information. + -n <number of simulations> - how many simulations to perform; default - 1. + -v - verbose mode (prints full entry state after each operation execution) + -f <output file> - file where results are stored; by default results are + printed to the screen. + -o <op file> - file that contains operation sequence to execute; by default, + random sequence is generated. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <memory.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#define MAX_OPS 18 /* maximum number of operations in a simulation */ +#define MAX_VALS 10 /* maximum number of values is entry or dn */ +#define NOT_PRESENT -1 + +/* data types */ +typedef struct value_state +{ + int value_index; /* value */ + int presence_csn; /* last time at which we know the value was present */ + int distinguished_index; /* index into dncsn list */ + int delete_csn; /* last attempt to delete this value */ + int present; /* flag that tells whether the value iscurrently present */ +} Value_State; + +typedef struct dn_csn +{ + int csn; /* dn csn */ + int value_index; /* dn value */ +} Dn_Csn; + +typedef struct entry_state +{ + Dn_Csn dn_csns [MAX_VALS + 1]; /* list of dn csns for this entry */ + int dn_csn_count; /* csn of the current dn */ + Value_State values[MAX_VALS]; /* values of the attribute */ + int attr_delete_csn; /* last attempt to delete the entire attribute */ +} Entry_State; + +typedef enum +{ + OP_ADD_VALUE, + OP_DELETE_VALUE, + OP_RENAME_ENTRY, + OP_DELETE_ATTR, + OP_END +} Operation_Type; + +typedef struct operation +{ + Operation_Type type; /* operation type */ + int csn; /* operation type */ + int value_index; /* value to add, remove or rename from */ +}Operation; + +typedef struct simulator_params +{ + int runs; /* number of runs */ + FILE *fout; /* output file */ + int value_count; /* number of values */ + int verbose; /* verbose mode */ + Operation *ops; /* operation sequence to apply */ + int op_count; +}Simulator_Params; + + +/* gloabl data */ +Simulator_Params sim; +char *g_values[] = +{ + "v", + "u", + "w", + NULL +}; + +/* forward declarations */ + +/* initialization */ +void process_cmd (int argc, char **argv); +void set_default_sim_params (); +int count_values (); +void parse_operations_file (char *name); +void parse_operation (char *line, int pos); +int value2index (char *value); +void print_usage (); + +/* simulation run */ +void run_simulation (); +void generate_operations (Operation **ops, int *op_count); +void generate_operation (Operation *op, int csn); +int* generate_operation_order (int op_count, int seq_num); +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); +void init_entry_state (Entry_State *entry); +void init_value_state (Value_State *val, int seq_num); +void apply_operation (Entry_State *entry, Operation *op); +void free_operations (Operation **ops); +int ** new_perm_table (int op_count); +void free_perm_table (int ***perm_table, int op_count); +int get_perm_count (int op_count); +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table); +void apply_add_operation (Entry_State *entry, Operation *op); +void apply_value_delete_operation (Entry_State *entry, Operation *op); +void apply_attr_delete_operation (Entry_State *entry, Operation *op); +void apply_rename_operation (Entry_State *entry, Operation *op); +void purge_value_state (Entry_State *entry, int index); +void resolve_value_state (Entry_State *entry, int value_index); +int value_distinguished_at_delete (Entry_State *entry, int value_index); +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); + +/* dnc_csn handling */ +int dn_csn_add (Entry_State *entry, int value_index, int csn); +int get_value_dn_csn (Entry_State *entry, int value_index); + +/* data tracing */ +void dump_operations (Operation *ops, int op_count, int *order); +void dump_operation (Operation *op); +void dump_perm_table (int **perm_table, int op_count); +void dump_entry_state (Entry_State *entry); +void dump_value_state (Value_State *value); +void dump_dn_csn_list (Entry_State *entry); + +/* misc functions */ +int max_val (int a, int b); + +int main (int argc, char **argv) +{ + int i; + + process_cmd (argc, argv); + + for (i = 0; i < sim.runs; i++) + { + fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); + run_simulation (); + fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); + } + + if (sim.fout != stdout) + fclose (sim.fout); + + return 0; +} + +void process_cmd (int argc, char **argv) +{ + int i; + + set_default_sim_params (); + + if (argc == 1) + { + return; + } + + if (strcmp (argv[1], "-h") == 0) /* print help */ + { + print_usage (); + exit (0); + } + + i = 1; + while (i < argc) + { + if (strcmp (argv[i], "-v") == 0) /* verbose mode */ + { + sim.verbose = 1; + i ++; + } + else if (strcmp (argv[i], "-n") == 0) + { + if (i < argc - 1) + { + int runs = atoi (argv[i + 1]); + if (runs > 0) + sim.runs = runs; + i+=2; + } + else + { + /* ONREPL print warning */ + i++; + } + } + else if (strcmp (argv[i], "-f") == 0) /* output file */ + { + if (i < argc - 1) + { + FILE *f = fopen (argv[i + 1], "w"); + if (f == 0) + { + printf ("failed to open output file; error - %s, using stdout\n", + strerror(errno)); + } + else + { + /* ONREPL print warning */ + sim.fout = f; + } + + i += 2; + } + else + i++; + } + else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ + { + if (i < argc - 1) + { + parse_operations_file (argv[i+1]); + i += 2; + } + else + { + /* ONREPL print warning */ + i ++; + } + } + else /* unknown option */ + { + printf ("unknown option - %s; ignored\n", argv[i]); + i ++; + } + + } +} + +void set_default_sim_params () +{ + memset (&sim, 0, sizeof (sim)); + sim.runs = 1; + sim.fout = stdout; + sim.value_count = count_values (); +} + +/* file format: <op count> + add <value> + delete <value> + delete attribute + rename to <value> + + all spaces are significant + */ +void parse_operations_file (char *name) +{ + FILE *file = fopen (name, "r"); + char line [256]; + int i; + + if (file == NULL) + { + printf ("failed to open operations file %s: error = %d\n", name, errno); + print_usage (); + exit (1); + } + + i = 0; + while (fgets (line, sizeof (line), file)) + { + if (i == 0) + { + /* read operation count */ + sim.op_count = atoi (line); + if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) + { + printf ("invalid operation count - %d; value must be between 1 and %d\n", + sim.op_count, MAX_OPS/2); + print_usage (); + exit (1); + } + else + { + sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); + } + } + else + { + if (strlen (line) == 0) /* skip empty lines */ + continue; + parse_operation (line, i); + } + + i ++; + } +} + +void parse_operation (char *line, int i) +{ + sim.ops [i - 1].csn = i; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + if (strncmp (line, "add", 3) == 0) + { + sim.ops [i - 1].type = OP_ADD_VALUE; + sim.ops [i - 1].value_index = value2index (&line[4]); + } + else if (strncmp (line, "delete attribute", 16) == 0) + { + sim.ops [i - 1].type = OP_DELETE_ATTR; + } + else if (strncmp (line, "delete", 6) == 0) + { + sim.ops [i - 1].type = OP_DELETE_VALUE; + sim.ops [i - 1].value_index = value2index (&line[7]); + } + else if (strncmp (line, "rename to", 6) == 0) + { + sim.ops [i - 1].type = OP_RENAME_ENTRY; + sim.ops [i - 1].value_index = value2index (&line[10]); + } + else + { + /* invalid line */ + printf ("invalid operation: %s\n", line); + exit (1); + } +} + +int value2index (char *value) +{ + int i; + + for (i = 0; i < sim.value_count; i++) + { + if (strcmp (g_values[i], value) == 0) + return i; + } + + return -1; +} + +void print_usage () +{ + printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n" + "\t-h - print usage information\n" + "\t-n <number of simulations>; default - 1\n" + "\t-v - verbose mode\n" + "\t-f <output file> - by default, results are printed to the screen\n" + "\t-o <op file> - file that contains operation sequence to execute;\n" + "\tby default, random sequence is generated.\n"); +} + +int count_values () +{ + int i; + + for (i = 0; g_values[i]; i++); + + return i; +} + +void run_simulation () +{ + int *order; + int i; + int perm_count; + Entry_State entry_first, entry_current; + int error = 0; + + init_entry_state (&entry_first); + fprintf (sim.fout, "initial entry state :\n"); + dump_entry_state (&entry_first); + + if (sim.ops == NULL) + { + generate_operations (&sim.ops, &sim.op_count); + } + fprintf (sim.fout, "initial operation set:\n"); + dump_operations (sim.ops, sim.op_count, NULL/* order */); + + //DebugBreak (); + + perm_count = get_perm_count (sim.op_count); + for (i = 0; i < perm_count; i++) + { + fprintf (sim.fout, "--------------------------------\n"); + fprintf (sim.fout, "simulation run %d\n", i + 1); + fprintf (sim.fout, "--------------------------------\n"); + order = generate_operation_order (sim.op_count, i); + if (i == 0) + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); + else + { + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); + error |= compare_entry_state (&entry_first, &entry_current, i + 1); + } + } + + switch (error) + { + case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); + break; + case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " + "the exact state does not match\n"); + break; + case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); + break; + } + + free_operations (&sim.ops); +} + +void generate_operations (Operation **ops, int *op_count) +{ + int i; + + /* generate number operations in the sequence */ + *op_count = slapi_rand () % (MAX_OPS / 2) + 1; + *ops = (Operation *)malloc (*op_count * sizeof (Operation)); + + for (i = 0; i < *op_count; i ++) + { + generate_operation (&((*ops)[i]), i + 1); + } +} + +void generate_operation (Operation *op, int csn) +{ + /* generate operation type */ + op->type = slapi_rand () % OP_END; + + /* generate value to which operation applies */ + op->value_index = slapi_rand () % sim.value_count; + + op->csn = csn; +} + +int* generate_operation_order (int op_count, int seq_num) +{ + static int **perm_table = NULL; + + /* first request - generate pemutation table */ + if (seq_num == 0) + { + int elements [MAX_OPS]; + int i; + + if (perm_table) + free_perm_table (&perm_table, op_count); + perm_table = new_perm_table (op_count); + + for (i = 0; i < op_count; i++) + elements [i] = i; + + generate_perm_table (elements, op_count, 0 /* static part */, + perm_table); + } + + return perm_table [seq_num]; +} + +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) +{ + int i; + + init_entry_state (entry); + + if (!sim.verbose) + { + if (!sim.verbose) + { + fprintf (sim.fout, "operation_sequence for this run:\n"); + dump_operations (ops, op_count, order); + } + } + + for (i = 0; i < op_count; i++) + { + apply_operation (entry, &(ops [order[i]])); + } + + if (!sim.verbose) + { + fprintf (sim.fout, "final entry state :\n"); + dump_entry_state (entry); + } + +} + +void init_entry_state (Entry_State *entry) +{ + int i; + + memset (entry, 0, sizeof (*entry)); + entry->attr_delete_csn = NOT_PRESENT; + entry->dn_csn_count = 1; + + for (i = 0; i < sim.value_count; i++) + { + init_value_state (&(entry->values[i]), i); + } +} + +void init_value_state (Value_State *val, int seq_num) +{ + memset (val, 0, sizeof (*val)); + val->value_index = seq_num; + val->present = 1; + val->delete_csn = NOT_PRESENT; + if (seq_num > 0) /* only first value is distinguished */ + val->distinguished_index = -1; +} + +void apply_operation (Entry_State *entry, Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: apply_add_operation (entry, op); + break; + + case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); + break; + + case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); + break; + + case OP_RENAME_ENTRY: apply_rename_operation (entry, op); + break; + } + + if (sim.verbose) + { + fprintf (sim.fout, "operation: "); + dump_operation (op); + fprintf (sim.fout, "\n"); + dump_entry_state (entry); + } +} + +void free_operations (Operation **ops) +{ + free (*ops); + *ops = NULL; +} + +int **new_perm_table (int op_count) +{ + int i; + int **perm_table; + int perm_count = get_perm_count (op_count); + + perm_table = (int**)malloc (perm_count * sizeof (int*)); + for (i = 0; i < perm_count; i ++) + perm_table [i] = (int*) malloc (op_count * sizeof (int)); + + return perm_table; +} + +void free_perm_table (int ***perm_table, int op_count) +{ + int i; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < perm_count; i ++) + free ((*perm_table)[i]); + + free (*perm_table); + *perm_table = NULL; +} + +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table) +{ + int i; + int elements_copy [MAX_OPS]; + int start_pos; + + if (element_count - 1 == static_part) + { + memcpy (*perm_table, elements, element_count * sizeof (int)); + return; + } + + start_pos = 0; + for (i = 0; i < element_count - static_part; i ++) + { + memcpy (elements_copy, elements, element_count * sizeof (int)); + elements_copy [static_part] = elements [static_part + i]; + elements_copy [static_part + i] = elements [static_part]; + generate_perm_table (elements_copy, element_count, static_part + 1, + &perm_table [start_pos]); + start_pos += get_perm_count (element_count - static_part - 1); + } +} + +int get_perm_count (int op_count) +{ + int i; + int perm_count = 1; + + for (i = 2; i <= op_count; i ++) + perm_count *= i; + + return perm_count; +} + +void apply_add_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].presence_csn < op->csn) + { + entry->values[op->value_index].presence_csn = op->csn; + resolve_value_state (entry, op->value_index); + } +} + +void apply_value_delete_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].delete_csn < op->csn) + { + entry->values[op->value_index].delete_csn = op->csn; + resolve_value_state (entry, op->value_index); + } +} + +void apply_attr_delete_operation (Entry_State *entry, Operation *op) +{ + int i; + + if (entry->attr_delete_csn < op->csn) + { + entry->attr_delete_csn = op->csn; + + for (i = 0; i < sim.value_count; i++) + { + resolve_value_state (entry, i); + } + } +} + +void apply_rename_operation (Entry_State *entry, Operation *op) +{ + int index; + + if (entry->values[op->value_index].presence_csn == NOT_PRESENT) + entry->values[op->value_index].presence_csn = op->csn; + + index = dn_csn_add (entry, op->value_index, op->csn); + + if (index > 0) + resolve_value_state (entry, entry->dn_csns[index - 1].value_index); + + resolve_value_state (entry, entry->dn_csns[index].value_index); +} + +void purge_value_state (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + int value_distinguished_csn = get_value_dn_csn (entry, value_index); + + if (value_distinguished_csn == -1 && value->presence_csn > value->delete_csn) + value->delete_csn = NOT_PRESENT; + else if (value->delete_csn < max_val (value_distinguished_csn, value->presence_csn)) + value->delete_csn = NOT_PRESENT; +} + +void resolve_value_state (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + int value_distinguished_csn = get_value_dn_csn (entry, value_index); + + purge_value_state (entry, value_index); + + /* no deletes that effect the state */ + if (max_val (value->delete_csn, entry->attr_delete_csn) < + max_val (value_distinguished_csn, value->presence_csn)) + { + value->present = 1; + return; + } + + if (value->present) /* check if it should be removed based on the current state */ + { + if (!value_distinguished_at_delete (entry, value_index)) + { + value->present = 0; + } + } + else /* not present - check if it should be restored */ + { + if (value_distinguished_at_delete (entry, value_index)) + { + value->present = 1; + } + } +} + +int value_distinguished_at_delete (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + int value_distinguished_csn = get_value_dn_csn (entry, value_index); + int delete_csn; + int i; + + /* value has never been distinguished */ + if (value_distinguished_csn == -1) + return 0; + + delete_csn = max_val (entry->attr_delete_csn, value->delete_csn); + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > delete_csn) + break; + } + + /* i is never equal to 0 because the csn of the first element is always + smaller than csn of any operation we can receive */ + return (entry->dn_csns[i-1].value_index == value_index); +} + +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) +{ + int i; + int error = 0; + + /* first - quick check for present / not present */ + for (i = 0; i < sim.value_count; i++) + { + if (entry1->values[i].present != entry2->values[i].present) + { + fprintf (sim.fout, + "value %s is %s present in the first run but %s present in the %d run\n", + g_values[i], entry1->values[i].present ? "" : "not", + entry2->values[i].present ? "" : "not", run); + error = 1; + } + } + + if (error) + return 3; + + /* compare dnc_csn list */ + if (entry1->dn_csn_count != entry2->dn_csn_count) + { + fprintf (sim.fout, "dn_csn count is %d for run 1 and %d for run %d\n", + entry1->dn_csn_count, entry2->dn_csn_count, run); + error = 1; + } + + for (i = 0; i < entry1->dn_csn_count; i++) + { + if (entry1->dn_csns [i].csn != entry2->dn_csns [i].csn || + entry1->dn_csns [i].value_index != entry2->dn_csns [i].value_index) + { + fprintf (sim.fout,"elements %d of dn csn list are different:\n" + "\tfirst run: csn - %d, value - %s\n" + "\t%d run: csn - %d, value - %s\n", i, + entry1->dn_csns [i].csn, + g_values[entry1->dn_csns [i].value_index], + run, entry2->dn_csns [i].csn, + g_values[entry2->dn_csns [i].value_index]); + + error = 1; + } + } + + /* compare value state */ + if (entry1->attr_delete_csn != entry2->attr_delete_csn) + { + fprintf (sim.fout, "attribute delete csn is %d for run 1 " + "but is %d for run %d\n", entry1->attr_delete_csn, + entry2->attr_delete_csn, run); + error = 1; + } + + for (i = 0; i < sim.value_count; i++) + { + if (entry1->values[i].presence_csn != entry2->values[i].presence_csn) + { + fprintf (sim.fout, "presence csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[i], entry1->values[i].presence_csn, + entry2->values[i].presence_csn, run); + error = 1; + } + + if (entry1->values[i].distinguished_index != entry2->values[i].distinguished_index) + { + fprintf (sim.fout, "distinguished index for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[i], + entry1->values[i].distinguished_index, + entry2->values[i].distinguished_index, run); + error = 1; + } + + if (entry1->values[i].delete_csn != entry2->values[i].delete_csn) + { + fprintf (sim.fout, "delete csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[i], entry1->values[i].delete_csn, + entry2->values[i].delete_csn, run); + error = 1; + } + } + + if (error != 0) + { + return 1; + } + else + return 0; +} + +int dn_csn_add (Entry_State *entry, int value_index, int csn) +{ + int i, j; + int distinguished_index; + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > csn) + break; + } + + if (i < entry->dn_csn_count) + { + distinguished_index = i; + for (j = i; j < entry->dn_csn_count; j ++) + { + if (entry->dn_csns[j].value_index == value_index) + distinguished_index = j + 1; + + if (entry->values [entry->dn_csns[j].value_index].distinguished_index == j) + entry->values [entry->dn_csns[j].value_index].distinguished_index ++; + } + + memcpy (&(entry->dn_csns[i+1]), &(entry->dn_csns[i]), + (entry->dn_csn_count - i) * sizeof (Dn_Csn)); + } + else + { + distinguished_index = entry->dn_csn_count; + } + + entry->values[value_index].distinguished_index = distinguished_index; + entry->dn_csns[i].csn = csn; + entry->dn_csns[i].value_index = value_index; + entry->dn_csn_count ++; + + return i; +} + +int get_value_dn_csn (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values [value_index]); + + if (value->distinguished_index == -1) + return -1; + else + return entry->dn_csns [value->distinguished_index].csn; +} + +void dump_operations (Operation *ops, int op_count, int *order) +{ + int index; + int i; + + for (i = 0; i < op_count; i ++) + { + if (order == NULL) /* current order */ + index = i; + else + index = order [i]; + + dump_operation (&ops[index]); + } + + fprintf (sim.fout, "\n"); +} + +void dump_operation (Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: + fprintf (sim.fout, "\t%d add value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_VALUE: + fprintf (sim.fout, "\t%d delete value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_ATTR: + fprintf (sim.fout, "\t%d delete attribute\n", op->csn); + break; + case OP_RENAME_ENTRY: + fprintf (sim.fout, "\t%d rename entry to %s\n", op->csn, + g_values [op->value_index]); + break; + } +} + +void dump_perm_table (int **perm_table, int op_count) +{ + int i, j; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < op_count; i++) + { + for (j = 0; j < perm_count; j++) + { + fprintf (sim.fout, "%d ", perm_table [j][i]); + } + + fprintf (sim.fout, "\n"); + } +} + +void dump_entry_state (Entry_State *entry) +{ + int i; + + dump_dn_csn_list (entry); + + for (i = 0; i < sim.value_count; i++) + { + dump_value_state (&(entry->values[i])); + } + + fprintf (sim.fout, "\n"); +} + +void dump_value_state (Value_State *value) +{ + fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index], + value->present ? "present" : "not present"); + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presence_csn); + fprintf (sim.fout, "\t\tdistinguished index: %d\n", value->distinguished_index); + fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn); + } +} + +void dump_dn_csn_list (Entry_State *entry) +{ + int i; + + fprintf (sim.fout, "\tdn csn list: \n"); + for (i = 0; i < entry->dn_csn_count; i++) + { + fprintf (sim.fout, "\t\tvalue: %s, csn: %d\n", + g_values[entry->dn_csns[i].value_index], entry->dn_csns[i].csn); + } +} + +/* misc functions */ +int max_val (int a, int b) +{ + if (a >= b) + return a; + else + return b; +} diff --git a/ldap/servers/plugins/replication/tests/dnp_sim3.c b/ldap/servers/plugins/replication/tests/dnp_sim3.c new file mode 100644 index 00000000..d018597a --- /dev/null +++ b/ldap/servers/plugins/replication/tests/dnp_sim3.c @@ -0,0 +1,1489 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dnp_simulation.c - this file varifies the correctness of dnp algorithm + by generating random sequences of operations, applying + the algorithm and outputing the result + + usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>] + -h - print usage information. + -n <number of simulations> - how many simulations to perform; default - 1. + -v - verbose mode (prints full entry state after each operation execution) + -f <output file> - file where results are stored; by default results are + printed to the screen. + -o <op file> - file that contains operation sequence to execute; by default, + random sequence is generated. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <memory.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#define MAX_OPS 18 /* maximum number of operations in a simulation */ +#define MAX_VALS 10 /* maximum number of values is entry or dn */ +#define MAX_ATTR_NAME 16 /* max length of the attribute name */ +#define NOT_PRESENT -1 +#define SV_ATTR_NAME "sv_attr" /* name of the singlevalued attribute */ +#define MV_ATTR_NAME "mv_attr" /* name of the multivalued attribute */ + +/* data types */ + +/* value */ +typedef struct value_state +{ + int value_index; /* value */ + int presence_csn; /* last time at which we know the value was present */ + int delete_csn; /* last attempt to delete this value */ + int present; /* flag that tells whether the value is present */ +} Value_State; + +/* shared attribute state */ +typedef struct attr_state +{ + int delete_csn; /* last deletion csn */ + int present; /* flag that tells whether the attribute is present */ +}Attr_State; + +/* singlevalued attribute */ +typedef struct sv_attr_state +{ + Attr_State attr_state; /* shared attribute state */ + Value_State current_value; /* current attribute value */ + Value_State *pending_value; /* latest pending value */ +} SV_Attr_State; + +/* maltivalued attribute */ +typedef struct mv_attr_state +{ + Attr_State attr_state; /* shared attribute state */ + Value_State values [MAX_VALS]; /* latest pending value */ + int value_count; /* number of values in the array */ +} MV_Attr_State; + +/* node of dn_csn_list */ +typedef struct dn_csn +{ + int csn; /* dn csn */ + int sv_attr; /* is this single valued or multivalued attr */ + int value_index; /* dn value */ +} Dn_Csn; + +typedef struct entry_state +{ + Dn_Csn dn_csns [MAX_VALS + 1]; /* list of dn csns for this entry */ + int dn_csn_count; /* csn of the current dn */ + SV_Attr_State sv_attr; /* singlevalued attribute */ + MV_Attr_State mv_attr; /* singlevalued attribute */ +} Entry_State; + +typedef enum +{ + OP_ADD_VALUE, + OP_DELETE_VALUE, + OP_RENAME_ENTRY, + OP_DELETE_ATTR, + OP_END +} Operation_Type; + +typedef struct operation +{ + Operation_Type type; /* operation type */ + int csn; /* operation csn */ + int sv_attr; /* is this applied to singlevalued attribute */ + int value_index; /* value to add, remove or rename from */ + int delete_old_rdn; /* rename only */ + int old_rdn_sv_attr; /* is oldrdn a singlevalued attribute */ + int old_rdn_value_index; /* index of old_rdn */ +}Operation; + +typedef struct simulator_params +{ + int runs; /* number of runs */ + FILE *fout; /* output file */ + int value_count; /* number of values */ + int verbose; /* verbose mode */ + Operation *ops; /* operation sequence to apply */ + int op_count; +}Simulator_Params; + + +/* gloabl data */ +Simulator_Params sim; +char *g_values[] = +{ + "v", + "u", + "w", + NULL +}; + +/* forward declarations */ + +/* initialization */ +void process_cmd (int argc, char **argv); +void set_default_sim_params (); +int count_values (); +void parse_operations_file (char *name); +void parse_operation (char *line, int pos); +int value2index (char *value); +void print_usage (); + +/* simulation run */ +void run_simulation (); +void generate_operations (Operation **ops, int *op_count); +void generate_operation (Operation *op, int csn); +int* generate_operation_order (int op_count, int seq_num); +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); +void init_entry_state (Entry_State *entry); +void init_sv_attr_state (SV_Attr_State *sv_attr); +void init_mv_attr_state (MV_Attr_State *mv_attr); +void init_value_state (Value_State *val, int seq_num); +void free_operations (Operation **ops); +int ** new_perm_table (int op_count); +void free_perm_table (int ***perm_table, int op_count); +int get_perm_count (int op_count); +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table); +void apply_operation (Entry_State *entry, Operation *op); +void apply_add_operation (Entry_State *entry, Operation *op); +void apply_value_delete_operation (Entry_State *entry, Operation *op); +void apply_attr_delete_operation (Entry_State *entry, Operation *op); +void apply_rename_operation (Entry_State *entry, Operation *op); +void resolve_mv_attr_state (Entry_State *entry, Value_State *value); +void resolve_sv_attr_state (Entry_State *entry, Value_State *value); +void purge_sv_attr_state (Entry_State *entry); +void purge_mv_attr_state (Entry_State *entry, Value_State *value); +int value_distinguished_at_csn (Entry_State *entry, int sv_attr, Value_State *value, int csn); + +/* state comparison */ +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); +int compare_entry_state_quick (Entry_State *entry1, Entry_State *entry2, int run); +int compare_sv_attr_state_quick (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run); +int compare_mv_attr_state_quick (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run); +int compare_sv_attr_state (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run); +int compare_mv_attr_state (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run); +int compare_value_state (Value_State *value1, Value_State *value2, int run); + +/* dnc_csn handling */ +int dn_csn_add (Entry_State *entry, int sv_attr, int value_index, int csn); + +/* data tracing */ +void dump_operations (Operation *ops, int op_count, int *order); +void dump_operation (Operation *op); +void dump_perm_table (int **perm_table, int op_count); +void dump_entry_state (Entry_State *entry); +void dump_sv_attr_state (SV_Attr_State *sv_attr); +void dump_mv_attr_state (MV_Attr_State *mv_attr); +void dump_value_state (Value_State *value, int sv_attr); +void dump_dn_csn_list (Entry_State *entry); + +/* misc functions */ +int max_val (int a, int b); + +int main (int argc, char **argv) +{ + int i; + + process_cmd (argc, argv); + + for (i = 0; i < sim.runs; i++) + { + fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); + run_simulation (); + fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); + } + + if (sim.fout != stdout) + fclose (sim.fout); + + return 0; +} + +void process_cmd (int argc, char **argv) +{ + int i; + + set_default_sim_params (); + + if (argc == 1) + { + return; + } + + if (strcmp (argv[1], "-h") == 0) /* print help */ + { + print_usage (); + exit (0); + } + + i = 1; + while (i < argc) + { + if (strcmp (argv[i], "-v") == 0) /* verbose mode */ + { + sim.verbose = 1; + i ++; + } + else if (strcmp (argv[i], "-n") == 0) + { + if (i < argc - 1) + { + int runs = atoi (argv[i + 1]); + if (runs > 0) + sim.runs = runs; + i+=2; + } + else + { + /* ONREPL print warning */ + i++; + } + } + else if (strcmp (argv[i], "-f") == 0) /* output file */ + { + if (i < argc - 1) + { + FILE *f = fopen (argv[i + 1], "w"); + if (f == 0) + { + printf ("failed to open output file; error - %s, using stdout\n", + strerror(errno)); + } + else + { + /* ONREPL print warning */ + sim.fout = f; + } + + i += 2; + } + else + i++; + } + else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ + { + if (i < argc - 1) + { + parse_operations_file (argv[i+1]); + i += 2; + } + else + { + /* ONREPL print warning */ + i ++; + } + } + else /* unknown option */ + { + printf ("unknown option - %s; ignored\n", argv[i]); + i ++; + } + + } +} + +void set_default_sim_params () +{ + memset (&sim, 0, sizeof (sim)); + sim.runs = 1; + sim.fout = stdout; + sim.value_count = count_values (); +} + +/* file format: <operation count> + add <attribute> <value> + delete <attribute>[ <value>] + rename to <attribute> <value>[ delete <attribute> <value>] + + all spaces are significant + */ +void parse_operations_file (char *name) +{ + FILE *file = fopen (name, "r"); + char line [256]; + int i; + + if (file == NULL) + { + printf ("failed to open operations file %s: error = %d\n", name, errno); + print_usage (); + exit (1); + } + + i = 0; + while (fgets (line, sizeof (line), file)) + { + if (i == 0) + { + /* read operation count */ + sim.op_count = atoi (line); + if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) + { + printf ("invalid operation count - %d; value must be between 1 and %d\n", + sim.op_count, MAX_OPS/2); + print_usage (); + exit (1); + } + else + { + sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); + } + } + else + { + if (strlen (line) == 0) /* skip empty lines */ + continue; + parse_operation (line, i); + } + + i ++; + } +} + +#define ADD_KEYWORD "add " +#define DELETE_KEYWORD "delete " +#define RENAME_KEYWORD "rename to " +#define DELET_OLD_RDN_KEYWORD " delete " + +void parse_operation (char *line, int i) +{ + int rc = 0; + char *pos; + char buff [64]; + + sim.ops [i - 1].csn = i; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + /* add <attribute> <value> */ + if (strncmp (line, ADD_KEYWORD, strlen (ADD_KEYWORD)) == 0) + { + sim.ops [i - 1].type = OP_ADD_VALUE; + pos = strchr (&line[strlen (ADD_KEYWORD)], ' '); + if (pos == NULL) + { + rc = -1; + goto done; + } + + memset (buff, 0, sizeof (buff)); + strncpy (buff, &line[strlen (ADD_KEYWORD)], pos - &line[strlen (ADD_KEYWORD)]); + sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME); + sim.ops [i - 1].value_index = value2index (pos + 1); + } + /* delete <attribute>[ <value>] */ + else if (strncmp (line, DELETE_KEYWORD, strlen (DELETE_KEYWORD)) == 0) + { + pos = strchr (&line[strlen (DELETE_KEYWORD)], ' '); + if (pos == NULL) /* delete attribute version */ + { + sim.ops [i - 1].type = OP_DELETE_ATTR; + sim.ops [i - 1].sv_attr = strcmp (&line[strlen (DELETE_KEYWORD)], + MV_ATTR_NAME); + } + else /* delete value version */ + { + memset (buff, 0, sizeof (buff)); + sim.ops [i - 1].type = OP_DELETE_VALUE; + strncpy (buff, &line[strlen (DELETE_KEYWORD)], + pos - &line[strlen (DELETE_KEYWORD)]); + sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME); + sim.ops [i - 1].value_index = value2index (pos + 1); + } + } + /* rename to <attribute> <valued>[ delete <attribute> <value>] */ + else if (strncmp (line, RENAME_KEYWORD, 10) == 0) + { + char *pos2; + + sim.ops [i - 1].type = OP_RENAME_ENTRY; + + pos = strchr (&line[strlen (RENAME_KEYWORD)], ' '); + if (pos == NULL) + { + rc = -1; + goto done; + } + + memset (buff, 0, sizeof (buff)); + strncpy (buff, &line[strlen (RENAME_KEYWORD)], pos - &line[strlen (RENAME_KEYWORD)]); + sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME); + + pos2 = strstr (pos + 1, DELET_OLD_RDN_KEYWORD); + if (pos2 == NULL) /* no delete old rdn part */ + { + sim.ops [i - 1].value_index = value2index (pos + 1); + sim.ops [i - 1].delete_old_rdn = 0; + } + else + { + memset (buff, 0, sizeof (buff)); + strncpy (buff, pos + 1, pos2 - pos - 1); + sim.ops [i - 1].value_index = value2index (buff); + pos2 += strlen (DELET_OLD_RDN_KEYWORD); + pos = strchr (pos2, ' '); + if (pos == NULL) + { + rc = -1; + goto done; + } + + memset (buff, 0, sizeof (buff)); + strncpy (buff, pos2, pos - pos2); + sim.ops [i - 1].delete_old_rdn = 1; + sim.ops [i - 1].old_rdn_sv_attr = strcmp (buff, MV_ATTR_NAME); + sim.ops [i - 1].old_rdn_value_index = value2index (pos + 1); + } + } + else + { + /* error */ + rc = -1; + } + +done: + if (rc) + { + /* invalid line */ + printf ("invalid operation: %s\n", line); + exit (1); + } +} +int value2index (char *value) +{ + int i; + + for (i = 0; i < sim.value_count; i++) + { + if (strcmp (g_values[i], value) == 0) + return i; + } + + return -1; +} + +void print_usage () +{ + printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n" + "\t-h - print usage information\n" + "\t-n <number of simulations>; default - 1\n" + "\t-v - verbose mode\n" + "\t-f <output file> - by default, results are printed to the screen\n" + "\t-o <op file> - file that contains operation sequence to execute;\n" + "\tby default, random sequence is generated.\n"); +} + +int count_values () +{ + int i; + + for (i = 0; g_values[i]; i++); + + return i; +} + +void run_simulation () +{ + int *order; + int i; + int perm_count; + Entry_State entry_first, entry_current; + int error = 0; + + init_entry_state (&entry_first); + fprintf (sim.fout, "initial entry state :\n"); + dump_entry_state (&entry_first); + + if (sim.ops == NULL) + { + generate_operations (&sim.ops, &sim.op_count); + } + fprintf (sim.fout, "initial operation set:\n"); + dump_operations (sim.ops, sim.op_count, NULL/* order */); + + perm_count = get_perm_count (sim.op_count); + for (i = 0; i < perm_count; i++) + { + fprintf (sim.fout, "--------------------------------\n"); + fprintf (sim.fout, "simulation run %d\n", i + 1); + fprintf (sim.fout, "--------------------------------\n"); + order = generate_operation_order (sim.op_count, i); + if (i == 0) + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); + else + { + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); + error |= compare_entry_state (&entry_first, &entry_current, i + 1); + } + } + + switch (error) + { + case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); + break; + case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " + "the exact state does not match\n"); + break; + case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); + break; + } + + free_operations (&sim.ops); +} + +void generate_operations (Operation **ops, int *op_count) +{ + int i; + + /* generate number operations in the sequence */ + *op_count = slapi_rand () % (MAX_OPS / 2) + 1; + *ops = (Operation *)malloc (*op_count * sizeof (Operation)); + + for (i = 0; i < *op_count; i ++) + { + generate_operation (&((*ops)[i]), i + 1); + } +} + +void generate_operation (Operation *op, int csn) +{ + /* generate operation type */ + op->type = slapi_rand () % OP_END; + op->csn = csn; + + /* choose if the operation applies to the single value or + the multivalued attribute */ + op->sv_attr = slapi_rand () % 2; + + /* generate value to which operation applies */ + op->value_index = slapi_rand () % sim.value_count; + + if (op->type == OP_RENAME_ENTRY) + { + op->delete_old_rdn = slapi_rand () % 2; + if (op->delete_old_rdn) + { + op->old_rdn_sv_attr = slapi_rand () % 2; + op->old_rdn_value_index = slapi_rand () % sim.value_count; + + while (op->old_rdn_sv_attr == op->sv_attr && + op->old_rdn_value_index == op->value_index) + { + op->old_rdn_sv_attr = slapi_rand () % 2; + op->old_rdn_value_index = slapi_rand () % sim.value_count; + } + } + } +} + +int* generate_operation_order (int op_count, int seq_num) +{ + static int **perm_table = NULL; + + /* first request - generate pemutation table */ + if (seq_num == 0) + { + int elements [MAX_OPS]; + int i; + + if (perm_table) + free_perm_table (&perm_table, op_count); + perm_table = new_perm_table (op_count); + + for (i = 0; i < op_count; i++) + elements [i] = i; + + generate_perm_table (elements, op_count, 0 /* static part */, + perm_table); + } + + return perm_table [seq_num]; +} + +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) +{ + int i; + + init_entry_state (entry); + + if (!sim.verbose) + { + if (!sim.verbose) + { + fprintf (sim.fout, "operation_sequence for this run:\n"); + dump_operations (ops, op_count, order); + } + } + + for (i = 0; i < op_count; i++) + { + apply_operation (entry, &(ops [order[i]])); + } + + if (!sim.verbose) + { + fprintf (sim.fout, "final entry state :\n"); + dump_entry_state (entry); + } +} + +void init_entry_state (Entry_State *entry) +{ + memset (entry, 0, sizeof (*entry)); + entry->dn_csn_count = 1; + + init_sv_attr_state (&entry->sv_attr); + init_mv_attr_state (&entry->mv_attr); +} + +void init_sv_attr_state (SV_Attr_State *sv_attr) +{ + memset (sv_attr, 0, sizeof (*sv_attr)); + sv_attr->attr_state.delete_csn = NOT_PRESENT; + sv_attr->attr_state.present = 1; + init_value_state (&sv_attr->current_value, 1); +} + +void init_mv_attr_state (MV_Attr_State *mv_attr) +{ + int i; + + memset (mv_attr, 0, sizeof (*mv_attr)); + mv_attr->attr_state.delete_csn = NOT_PRESENT; + mv_attr->attr_state.present = 1; + mv_attr->value_count = sim.value_count; + + for (i = 0; i < mv_attr->value_count; i++) + { + init_value_state (&(mv_attr->values[i]), i); + } +} + +void init_value_state (Value_State *val, int seq_num) +{ + memset (val, 0, sizeof (*val)); + val->value_index = seq_num; + val->present = 1; + val->delete_csn = NOT_PRESENT; +} + +void apply_operation (Entry_State *entry, Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: apply_add_operation (entry, op); + break; + + case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); + break; + + case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); + break; + + case OP_RENAME_ENTRY: apply_rename_operation (entry, op); + break; + } + + if (sim.verbose) + { + fprintf (sim.fout, "operation: "); + dump_operation (op); + fprintf (sim.fout, "\n"); + dump_entry_state (entry); + } +} + +void free_operations (Operation **ops) +{ + free (*ops); + *ops = NULL; +} + +int **new_perm_table (int op_count) +{ + int i; + int **perm_table; + int perm_count = get_perm_count (op_count); + + perm_table = (int**)malloc (perm_count * sizeof (int*)); + for (i = 0; i < perm_count; i ++) + perm_table [i] = (int*) malloc (op_count * sizeof (int)); + + return perm_table; +} + +void free_perm_table (int ***perm_table, int op_count) +{ + int i; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < perm_count; i ++) + free ((*perm_table)[i]); + + free (*perm_table); + *perm_table = NULL; +} + +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table) +{ + int i; + int elements_copy [MAX_OPS]; + int start_pos; + + if (element_count - 1 == static_part) + { + memcpy (*perm_table, elements, element_count * sizeof (int)); + return; + } + + start_pos = 0; + for (i = 0; i < element_count - static_part; i ++) + { + memcpy (elements_copy, elements, element_count * sizeof (int)); + elements_copy [static_part] = elements [static_part + i]; + elements_copy [static_part + i] = elements [static_part]; + generate_perm_table (elements_copy, element_count, static_part + 1, + &perm_table [start_pos]); + start_pos += get_perm_count (element_count - static_part - 1); + } +} + +int get_perm_count (int op_count) +{ + int i; + int perm_count = 1; + + for (i = 2; i <= op_count; i ++) + perm_count *= i; + + return perm_count; +} + +void apply_add_operation (Entry_State *entry, Operation *op) +{ + if (op->sv_attr) + { + Value_State *val; + Value_State temp_val; + + if (op->value_index == entry->sv_attr.current_value.value_index) + { + val = &entry->sv_attr.current_value; + } + else if (entry->sv_attr.pending_value && + op->value_index == entry->sv_attr.pending_value->value_index) + { + val = entry->sv_attr.pending_value; + } + else /* new value */ + { + init_value_state (&temp_val, op->value_index); + val = &temp_val; + } + + if (val->presence_csn < op->csn) + val->presence_csn = op->csn; + + resolve_sv_attr_state (entry, val); + } + else + { + if (entry->mv_attr.values[op->value_index].presence_csn < op->csn) + { + entry->mv_attr.values[op->value_index].presence_csn = op->csn; + resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index])); + } + } +} + +void apply_value_delete_operation (Entry_State *entry, Operation *op) +{ + if (op->sv_attr) + { + if (entry->sv_attr.attr_state.delete_csn < op->csn) + { + entry->sv_attr.attr_state.delete_csn = op->csn; + resolve_sv_attr_state (entry, NULL); + } + } + else /* mv attr */ + { + if (entry->mv_attr.values[op->value_index].delete_csn < op->csn) + { + entry->mv_attr.values[op->value_index].delete_csn = op->csn; + resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index])); + } + } +} + +void apply_attr_delete_operation (Entry_State *entry, Operation *op) +{ + int i; + + if (op->sv_attr) + { + if (entry->sv_attr.attr_state.delete_csn < op->csn) + { + entry->sv_attr.attr_state.delete_csn = op->csn; + resolve_sv_attr_state (entry, NULL); + } + } + else /* mv attr */ + { + if (entry->mv_attr.attr_state.delete_csn < op->csn) + { + entry->mv_attr.attr_state.delete_csn = op->csn; + + for (i = 0; i < sim.value_count; i++) + { + resolve_mv_attr_state (entry, &(entry->mv_attr.values[i])); + } + } + } +} + +void apply_rename_operation (Entry_State *entry, Operation *op) +{ + int index; + Operation del_op; + + /* insert new dn into dn_csn_list */ + index = dn_csn_add (entry, op->sv_attr, op->value_index, op->csn); + + /* issue delete value operation for the old rdn */ + if (op->delete_old_rdn) + { + del_op.type = OP_DELETE_VALUE; + del_op.csn = op->csn; + del_op.sv_attr = op->old_rdn_sv_attr; + del_op.value_index = op->old_rdn_value_index; + + apply_value_delete_operation (entry, &del_op); + } + + /* resolve state of the previous node in dn_csn_list */ + if (index > 0) + { + if (entry->dn_csns[index-1].sv_attr) + { + if (entry->dn_csns[index-1].value_index == + entry->sv_attr.current_value.value_index) + { + resolve_sv_attr_state (entry, &(entry->sv_attr.current_value)); + } + else if (entry->sv_attr.pending_value && + entry->dn_csns[index-1].value_index == + entry->sv_attr.pending_value->value_index) + { + resolve_sv_attr_state (entry, entry->sv_attr.pending_value); + } + } + else + { + int i = entry->dn_csns[index-1].value_index; + resolve_mv_attr_state (entry, &(entry->mv_attr.values[i])); + } + } + + /* resolve state of the new dn */ + if (op->sv_attr) + { + Value_State *value; + Value_State temp_val; + if (op->value_index == entry->sv_attr.current_value.value_index) + { + value = &entry->sv_attr.current_value; + } + else if (entry->sv_attr.pending_value && + op->value_index == entry->sv_attr.pending_value->value_index) + { + value = entry->sv_attr.pending_value; + } + else /* new value */ + { + init_value_state (&temp_val, op->value_index); + value = &temp_val; + } + + if (value->presence_csn == NOT_PRESENT || value->presence_csn < op->csn) + value->presence_csn = op->csn; + resolve_sv_attr_state (entry, value); + } + else + { + if (entry->mv_attr.values[op->value_index].presence_csn == NOT_PRESENT || + entry->mv_attr.values[op->value_index].presence_csn < op->csn) + entry->mv_attr.values[op->value_index].presence_csn = op->csn; + + resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index])); + } +} + +void purge_mv_attr_state (Entry_State *entry, Value_State *value) +{ + if (value->presence_csn > value->delete_csn) + value->delete_csn = NOT_PRESENT; +} + +void purge_sv_attr_state (Entry_State *entry) +{ + if (entry->sv_attr.attr_state.delete_csn != NOT_PRESENT) + { + if (entry->sv_attr.pending_value) + { + if (entry->sv_attr.attr_state.delete_csn < + entry->sv_attr.pending_value->presence_csn) + { + entry->sv_attr.attr_state.delete_csn = NOT_PRESENT; + } + } + else + { + if (entry->sv_attr.attr_state.delete_csn < + entry->sv_attr.current_value.presence_csn) + entry->sv_attr.attr_state.delete_csn = NOT_PRESENT; + } + } +} + +void resolve_mv_attr_state (Entry_State *entry, Value_State *value) +{ + purge_mv_attr_state (entry, value); + + /* no deletes that effect the state */ + if (max_val (value->delete_csn, entry->mv_attr.attr_state.delete_csn) < + value->presence_csn) + { + value->present = 1; + return; + } + + if (value->present) /* check if it should be removed based on the current state */ + { + if (!value_distinguished_at_csn (entry, 0, value, + max (value->delete_csn, entry->mv_attr.attr_state.delete_csn))) + { + value->present = 0; + } + } + else /* not present - check if it should be restored */ + { + if (value_distinguished_at_csn (entry, 0, value, + max (value->delete_csn, entry->mv_attr.attr_state.delete_csn))) + { + value->present = 1; + } + } + + if (entry->mv_attr.attr_state.delete_csn == NOT_PRESENT) + { + entry->mv_attr.attr_state.present = 1; + } + else + { + int i; + int distinguished = 0; + + for (i = 0; i < entry->mv_attr.value_count; i ++) + { + distinguished |= value_distinguished_at_csn (entry, 0, + &(entry->mv_attr.values[i]), + entry->mv_attr.attr_state.delete_csn); + } + + entry->mv_attr.attr_state.present = distinguished; + } +} + +void resolve_sv_attr_state (Entry_State *entry, Value_State *value) +{ + purge_sv_attr_state (entry); + + if (value) + { + /* existing value is modified */ + if (value == &(entry->sv_attr.current_value) || + value == entry->sv_attr.pending_value) + { + /* check if current value should be replaced with the pending value */ + if (entry->sv_attr.pending_value) + { + if (!value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value, + entry->sv_attr.current_value.presence_csn)) + { + /* replace current value with the pending value */ + memcpy (&entry->sv_attr.current_value, entry->sv_attr.pending_value, + sizeof (Value_State)); + free (entry->sv_attr.pending_value); + entry->sv_attr.pending_value = NULL; + } + } + } + else /* addition of a new value */ + { + /* new value is before the current value; note that, for new value, + presence_csn is the same as distinguished_csn */ + if (value->presence_csn < entry->sv_attr.current_value.presence_csn) + { + /* if new value is distinguished, it should become current and the + current can become pending */ + if (value_distinguished_at_csn (entry, 1, value, + entry->sv_attr.current_value.presence_csn)) + { + if (entry->sv_attr.pending_value == NULL) + { + entry->sv_attr.pending_value = (Value_State*) + malloc (sizeof (Value_State)); + memcpy (entry->sv_attr.pending_value, &entry->sv_attr.current_value, + sizeof (Value_State)); + } + + memcpy (&entry->sv_attr.current_value, value, sizeof (Value_State)); + } + } + else /* new value is after the current value */ + { + /* if current value is not distinguished, new value should + become distinguished */ + if (!value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value, + value->presence_csn)) + { + memcpy (&entry->sv_attr.current_value, value, sizeof (Value_State)); + } + else /* current value is distinguished - check if new value should replace + the pending value */ + { if (entry->sv_attr.pending_value) + { + if (value->presence_csn > entry->sv_attr.pending_value->presence_csn) + { + memcpy (entry->sv_attr.pending_value, value, sizeof (Value_State)); + } + } + else + { + entry->sv_attr.pending_value = (Value_State*)malloc (sizeof (Value_State)); + memcpy (entry->sv_attr.pending_value, value, sizeof (Value_State)); + } + } + } + } + } + + /* update the attribute state */ + purge_sv_attr_state (entry); + + /* set attribute state */ + if (entry->sv_attr.attr_state.delete_csn != NOT_PRESENT && + !value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value, + entry->sv_attr.attr_state.delete_csn)) + { + entry->sv_attr.attr_state.present = 0; + } + else + { + entry->sv_attr.attr_state.present = 1; + } +} + +int value_distinguished_at_csn (Entry_State *entry, int sv_attr, Value_State *value, int csn) +{ + int i; + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > csn) + break; + } + + /* i is never equal to 0 because the csn of the first element is always + smaller than csn of any operation we can receive */ + return (entry->dn_csns[i-1].value_index == value->value_index && + entry->dn_csns[i-1].sv_attr == sv_attr); +} + +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) +{ + int i; + int error = 0; + + error = compare_entry_state_quick (entry1, entry2, run); + + if (error) + return 3; + + /* compare dnc_csn list */ + if (entry1->dn_csn_count != entry2->dn_csn_count) + { + fprintf (sim.fout, "dn_csn count is %d for run 1 and %d for run %d\n", + entry1->dn_csn_count, entry2->dn_csn_count, run); + error = 1; + } + + for (i = 0; i < entry1->dn_csn_count; i++) + { + if (entry1->dn_csns [i].csn != entry2->dn_csns [i].csn || + entry1->dn_csns [i].sv_attr != entry2->dn_csns [i].sv_attr || + entry1->dn_csns [i].value_index != entry2->dn_csns [i].value_index) + { + fprintf (sim.fout,"elements %d of dn csn list are different:\n" + "\tfirst run: csn - %d, attr - %s, value - %s\n" + "\t%d run: csn - %d, attr - %s value - %s\n", i, + entry1->dn_csns [i].csn, + entry1->dn_csns [i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values[entry1->dn_csns [i].value_index], + run, entry2->dn_csns [i].csn, + entry2->dn_csns [i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values[entry2->dn_csns [i].value_index]); + + error = 1; + } + } + + error |= compare_sv_attr_state (&entry1->sv_attr, &entry2->sv_attr, run); + + error |= compare_mv_attr_state (&entry1->mv_attr, &entry2->mv_attr, run); + + if (error != 0) + { + return 1; + } + else + return 0; +} + +/* just compare if the same attributes and values are present */ +int compare_entry_state_quick (Entry_State *entry1, Entry_State *entry2, int run) +{ + int error; + + error = compare_sv_attr_state_quick (&entry1->sv_attr, &entry2->sv_attr, run); + + error |= compare_mv_attr_state_quick (&entry1->mv_attr, &entry2->mv_attr, run); + + return error; +} + +int compare_sv_attr_state_quick (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run) +{ + int error = 0; + if (sv_attr1->attr_state.present != sv_attr2->attr_state.present) + { + fprintf (sim.fout, "singlevalued attribute is %s present in the first run " + "but is %s present in the %d run\n", + sv_attr1->attr_state.present ? "" : "not", + sv_attr2->attr_state.present ? "" : "not", run); + return 1; + } + + if (sv_attr1->attr_state.present && + sv_attr1->current_value.value_index != sv_attr2->current_value.value_index) + { + fprintf (sim.fout, "different values for singlevalued attribute: %s for the \n" + "first run and %s for the %d run\n", + g_values [sv_attr1->current_value.value_index], + g_values [sv_attr2->current_value.value_index], run); + return 1; + } + + return 0; +} + +int compare_mv_attr_state_quick (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run) +{ + int i; + int error = 0; + + if (mv_attr1->attr_state.present != mv_attr2->attr_state.present) + { + fprintf (sim.fout, "multivalued attribute is %s present in the first run " + "but is %s present in the %d run\n", + mv_attr1->attr_state.present ? "" : "not", + mv_attr2->attr_state.present ? "" : "not", run); + return 1; + } + + /* value count does not change during the iteration, so we don't have + to check if the count is the same for both attributes */ + for (i = 0; i < mv_attr1->value_count; i++) + { + if (mv_attr1->values[i].present != mv_attr2->values[i].present) + { + fprintf (sim.fout, "value %s is %s present in the multivalued attribute\n" + "in the first run but %s present in the %d run\n", + g_values[i], mv_attr1->values[i].present ? "" : "not", + mv_attr2->values[i].present ? "" : "not", run); + error = 1; + } + } + + return error; +} + +int compare_sv_attr_state (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run) +{ + int error = 0; + + if (sv_attr1->attr_state.delete_csn != sv_attr2->attr_state.delete_csn) + { + fprintf (sim.fout, "singlevalued attribute deletion csn is %d for run 1 " + "but is %d for run %d\n", sv_attr1->attr_state.delete_csn, + sv_attr2->attr_state.delete_csn, run); + error = 1; + } + + error |= compare_value_state (&sv_attr1->current_value, &sv_attr2->current_value, run); + + if ((sv_attr1->pending_value && !sv_attr1->pending_value) || + (!sv_attr1->pending_value && sv_attr1->pending_value)) + { + fprintf (sim.fout, "pending value is %s present in the singlevalued attribute\n" + " in the first run but is %s in the %d run\n", + sv_attr1->pending_value ? "" : "not", + sv_attr2->pending_value ? "" : "not", run); + + return 1; + } + + if (sv_attr1->pending_value) + error |= compare_value_state (sv_attr1->pending_value, sv_attr2->pending_value, run); + + return 0; +} + +int compare_mv_attr_state (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run) +{ + int error = 0; + int i; + + if (mv_attr1->attr_state.delete_csn != mv_attr2->attr_state.delete_csn) + { + fprintf (sim.fout, "multivalued attribute deletion csn is %d for run 1 " + "but is %d for run %d\n", mv_attr1->attr_state.delete_csn, + mv_attr2->attr_state.delete_csn, run); + error = 1; + } + + for (i = 0; i < mv_attr1->value_count; i++) + { + error |= compare_value_state (&mv_attr1->values[i], &mv_attr2->values[i], run); + } + + return error; +} + +int compare_value_state (Value_State *value1, Value_State *value2, int run) +{ + int error = 0; + + if (value1->presence_csn != value2->presence_csn) + { + fprintf (sim.fout, "multivalued attribute: presence csn for value %s is %d " + "in run 1 but is %d in run %d\n", g_values[value1->value_index], + value1->presence_csn, value2->presence_csn, run); + error = 1; + } + + if (value1->delete_csn != value2->delete_csn) + { + fprintf (sim.fout, "multivalued attribute: delete csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[value1->value_index], + value1->delete_csn, value2->delete_csn, run); + error = 1; + } + + return error; +} + +int dn_csn_add (Entry_State *entry, int sv_attr, int value_index, int csn) +{ + int i; + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > csn) + break; + } + + if (i < entry->dn_csn_count) + { + memcpy (&(entry->dn_csns[i+1]), &(entry->dn_csns[i]), + (entry->dn_csn_count - i) * sizeof (Dn_Csn)); + } + + entry->dn_csns[i].csn = csn; + entry->dn_csns[i].sv_attr = sv_attr; + entry->dn_csns[i].value_index = value_index; + entry->dn_csn_count ++; + + return i; +} + +void dump_operations (Operation *ops, int op_count, int *order) +{ + int index; + int i; + + for (i = 0; i < op_count; i ++) + { + if (order == NULL) /* current order */ + index = i; + else + index = order [i]; + + dump_operation (&ops[index]); + } + + fprintf (sim.fout, "\n"); +} + +void dump_operation (Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: + fprintf (sim.fout, "\t%d add value %s to %s\n", op->csn, + g_values [op->value_index], + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME); + break; + case OP_DELETE_VALUE: + fprintf (sim.fout, "\t%d delete value %s from %s\n", op->csn, + g_values [op->value_index], + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME); + break; + case OP_DELETE_ATTR: + fprintf (sim.fout, "\t%d delete %s attribute\n", op->csn, + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME); + break; + case OP_RENAME_ENTRY: + fprintf (sim.fout, "\t%d rename entry to %s=%s", op->csn, + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values [op->value_index]); + if (op->delete_old_rdn) + fprintf (sim.fout, " delete old rdn %s=%s\n", + op->old_rdn_sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values [op->old_rdn_value_index]); + else + fprintf (sim.fout, "\n"); + break; + } +} + +void dump_perm_table (int **perm_table, int op_count) +{ + int i, j; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < op_count; i++) + { + for (j = 0; j < perm_count; j++) + { + fprintf (sim.fout, "%d ", perm_table [j][i]); + } + + fprintf (sim.fout, "\n"); + } +} + +void dump_entry_state (Entry_State *entry) +{ + dump_dn_csn_list (entry); + + dump_sv_attr_state (&entry->sv_attr); + dump_mv_attr_state (&entry->mv_attr); + + fprintf (sim.fout, "\n"); +} + +void dump_sv_attr_state (SV_Attr_State *sv_attr) +{ + fprintf (sim.fout, "\tattribute %s is %s present", SV_ATTR_NAME, + sv_attr->attr_state.present ? "" : "not"); + if (sv_attr->attr_state.present) + { + fprintf (sim.fout, " and has the value of %s\n", + g_values[sv_attr->current_value.value_index]); + } + else + { + fprintf (sim.fout, "\n"); + } + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tdeletion csn: %d\n", sv_attr->attr_state.delete_csn); + fprintf (sim.fout, "\t\tcurrent value: "); + dump_value_state (&sv_attr->current_value, 1/* for single valued attr */); + if (sv_attr->pending_value) + { + fprintf (sim.fout, "\t\tpending value: "); + dump_value_state (sv_attr->pending_value, 1/* for single valued attr */); + } + } +} + +void dump_mv_attr_state (MV_Attr_State *mv_attr) +{ + int i; + + fprintf (sim.fout, "\tattribute %s is %s present\n", MV_ATTR_NAME, + mv_attr->attr_state.present ? "" : "not"); + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tdeletion csn: %d\n", mv_attr->attr_state.delete_csn); + } + + for (i = 0; i < mv_attr->value_count; i++) + { + dump_value_state (&(mv_attr->values[i]), 0); + } +} + +void dump_value_state (Value_State *value, int sv_attr) +{ + if (!sv_attr) + { + fprintf (sim.fout, "\tvalue %s is %s present\n", g_values[value->value_index], + value->present ? "" : "not"); + } + else + { + fprintf (sim.fout, "%s\n", g_values[value->value_index]); + } + + if (sim.verbose) + { + fprintf (sim.fout, "\t\t\tpresence csn: %d\n", value->presence_csn); + fprintf (sim.fout, "\t\t\tdeletion value csn: %d\n", value->delete_csn); + } +} + +void dump_dn_csn_list (Entry_State *entry) +{ + int i; + + fprintf (sim.fout, "\tdn csn list: \n"); + for (i = 0; i < entry->dn_csn_count; i++) + { + fprintf (sim.fout, "\t\t %s=%s, csn: %d\n", + entry->dn_csns[i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values[entry->dn_csns[i].value_index], entry->dn_csns[i].csn); + } +} + +/* misc functions */ +int max_val (int a, int b) +{ + if (a >= b) + return a; + else + return b; +} diff --git a/ldap/servers/plugins/replication/tests/makesim b/ldap/servers/plugins/replication/tests/makesim new file mode 100755 index 00000000..0cedd6e1 --- /dev/null +++ b/ldap/servers/plugins/replication/tests/makesim @@ -0,0 +1,58 @@ +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# gnu makefile for LDAP Server tools. +# + +MCOM_ROOT = ../../../../../.. +LDAP_SRC = ../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip + +OBJDEST = $(OBJDIR)/lib/replication-plugin +BINDIR = $(OBJDIR)/bin + +include $(MCOM_ROOT)/netsite/nsdefs.mk +include $(MCOM_ROOT)/netsite/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +LDFLAGS += $(EXLDFLAGS) + +ifeq ($(ARCH), WINNT) +SUBSYSTEM=console +endif + +DEPLIBS= + +EXTRA_LIBS_DEP = + +EXTRA_LIBS = + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS += user32.lib +endif + +DNP_SIM = $(addsuffix $(EXE_SUFFIX), \ + $(addprefix $(BINDIR)/, dnp_sim)) + + +all: $(OBJDEST) $(BINDIR) $(DNP_SIM) + +$(DNP_SIM): $(OBJDEST)/dnp_sim3.o $(EXTRA_LIBS_DEP) + $(LINK_EXE) $(OBJDEST)/dnp_sim3.o \ + $(EXTRA_LIBS) $< + + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(BINDIR): + $(MKDIR) $(BINDIR) + +clean: + -$(RM) $(ALL_OBJS) + -$(RM) $(BINS) diff --git a/ldap/servers/plugins/replication/urp.c b/ldap/servers/plugins/replication/urp.c new file mode 100644 index 00000000..a4dc86f9 --- /dev/null +++ b/ldap/servers/plugins/replication/urp.c @@ -0,0 +1,1282 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * urp.c - Update Resolution Procedures + */ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "urp.h" + +extern int slapi_log_urp; + +static int urp_add_resolve_parententry (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry, Slapi_Entry *parententry, CSN *opcsn); +static int urp_annotate_dn (char *sessionid, Slapi_Entry *entry, CSN *opcsn, const char *optype); +static int urp_naming_conflict_removal (Slapi_PBlock *pb, char *sessionid, CSN *opcsn, const char *optype); +static int mod_namingconflict_attr (const char *uniqueid, const char*entrydn, const char *conflictdn, CSN *opcsn); +static int del_replconflict_attr (Slapi_Entry *entry, CSN *opcsn, int opflags); +static char *get_dn_plus_uniqueid(char *sessionid,const char *olddn,const char *uniqueid); +static char *get_rdn_plus_uniqueid(char *sessionid,const char *olddn,const char *uniqueid); +static void set_pblock_dn (Slapi_PBlock* pb,int pblock_parameter,char *newdn); +static int is_suffix_entry (Slapi_PBlock *pb, Slapi_Entry *entry, Slapi_DN **parenddn); + +/* + * Return 0 for OK, -1 for Error. + */ +int +urp_modify_operation( Slapi_PBlock *pb ) +{ + Slapi_Entry *modifyentry= NULL; + int op_result= 0; + int rc= 0; /* OK */ + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &modifyentry ); + + if(modifyentry!=NULL) + { + /* + * The entry to be modified exists. + * - the entry could be a tombstone... but that's OK. + * - the entry could be glue... that may not be OK. JCMREPL + */ + rc= 0; /* OK, Modify the entry */ + PROFILE_POINT; /* Modify Conflict; Entry Exists; Apply Modification */ + } + else + { + /* + * The entry to be modified could not be found. + */ + op_result= LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Must discard this Modification */ + PROFILE_POINT; /* Modify Conflict; Entry Does Not Exist; Discard Modification */ + } + return rc; +} + +/* + * Return 0 for OK, + * -1 for Ignore or Error depending on SLAPI_RESULT_CODE, + * >0 for action code + * Action Code Bit 0: Fetch existing entry. + * Action Code Bit 1: Fetch parent entry. + * The function is called as a be pre-op on consumers. + */ +int +urp_add_operation( Slapi_PBlock *pb ) +{ + Slapi_Entry *existing_uniqueid_entry; + Slapi_Entry *existing_dn_entry; + Slapi_Entry *addentry; + const char *adduniqueid; + CSN *opcsn; + const char *basedn; + char sessionid[REPL_SESSION_ID_SIZE]; + int r; + int op_result= 0; + int rc= 0; /* OK */ + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get( pb, SLAPI_ADD_EXISTING_UNIQUEID_ENTRY, &existing_uniqueid_entry ); + if (existing_uniqueid_entry!=NULL) + { + /* + * An entry with this uniqueid already exists. + * - It could be a replay of the same Add, or + * - It could be a UUID generation collision, or + */ + op_result = LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + PROFILE_POINT; /* Add Conflict; UniqueID Exists; Ignore */ + goto bailout; + } + + get_repl_session_id (pb, sessionid, &opcsn); + slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &addentry ); + slapi_pblock_get( pb, SLAPI_ADD_EXISTING_DN_ENTRY, &existing_dn_entry ); + if (existing_dn_entry==NULL) /* The target DN does not exist */ + { + /* Check for parent entry... this could be an orphan. */ + Slapi_Entry *parententry; + slapi_pblock_get( pb, SLAPI_ADD_PARENT_ENTRY, &parententry ); + rc = urp_add_resolve_parententry (pb, sessionid, addentry, parententry, opcsn); + PROFILE_POINT; /* Add Entry */ + goto bailout; + } + + /* + * Naming conflict: an entry with the target DN already exists. + * Compare the DistinguishedNameCSN of the existing entry + * and the OperationCSN. The smaller CSN wins. The loser changes + * its RDN to uniqueid+baserdn, and adds operational attribute + * ATTR_NSDS5_REPLCONFLIC. + */ + basedn = slapi_entry_get_ndn (addentry); + adduniqueid = slapi_entry_get_uniqueid (addentry); + r = csn_compare (entry_get_dncsn(existing_dn_entry), opcsn); + if (r<0) + { + /* Entry to be added is a loser */ + char *newdn= get_dn_plus_uniqueid (sessionid, basedn, adduniqueid); + if(newdn==NULL) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Abort this Operation */ + PROFILE_POINT; /* Add Conflict; Entry Exists; Unique ID already in RDN - Abort this update. */ + } + else + { + /* Add the nsds5ReplConflict attribute in the mods */ + Slapi_Attr *attr = NULL; + Slapi_Value **vals = NULL; + Slapi_RDN *rdn; + char buf[BUFSIZ]; + + sprintf(buf, "%s %s", REASON_ANNOTATE_DN, basedn); + if (slapi_entry_attr_find (addentry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) + { + /* ATTR_NSDS5_REPLCONFLICT exists */ + slapi_log_error (SLAPI_LOG_FATAL, sessionid, "New entry has nsds5ReplConflict already\n"); + vals = attr_get_present_values (attr); /* this returns a pointer to the contents */ + } + if ( vals == NULL || *vals == NULL ) + { + /* Add new attribute */ + slapi_entry_add_string (addentry, ATTR_NSDS5_REPLCONFLICT, buf); + } + else + { + /* + * Replace old attribute. We don't worry about the index + * change here since the entry is yet to be added. + */ + slapi_value_set_string (*vals, buf); + } + slapi_entry_set_dn (addentry,slapi_ch_strdup(newdn)); + set_pblock_dn(pb,SLAPI_ADD_TARGET,newdn); /* consumes newdn */ + + rdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn_const(addentry) ); + slapi_log_error (slapi_log_urp, sessionid, + "Naming conflict ADD. Add %s instead\n", slapi_rdn_get_rdn(rdn) ); + slapi_rdn_free(&rdn); + + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + PROFILE_POINT; /* Add Conflict; Entry Exists; Rename Operation Entry */ + } + } + else if(r>0) + { + /* Existing entry is a loser */ + if (!urp_annotate_dn(sessionid, existing_dn_entry, opcsn, "ADD")) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + } + else + { + /* The backend add code should now search for the existing entry again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY); + } + PROFILE_POINT; /* Add Conflict; Entry Exists; Rename Existing Entry */ + } + else /* r==0 */ + { + /* The CSN of the Operation and the Entry DN are the same. + * This could only happen if: + * a) There are two replicas with the same ReplicaID. + * b) We've seen the Operation before. + * Let's go with (b) and ignore the little bastard. + */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + PROFILE_POINT; /* Add Conflict; Entry Exists; Same CSN */ + } + +bailout: + return rc; +} + +/* + * Return 0 for OK, -1 for Error, >0 for action code + * Action Code Bit 0: Fetch existing entry. + * Action Code Bit 1: Fetch parent entry. + */ +int +urp_modrdn_operation( Slapi_PBlock *pb ) +{ + slapi_operation_parameters *op_params = NULL; + Slapi_Entry *parent_entry; + Slapi_Entry *new_parent_entry; + Slapi_DN *newsuperior = NULL; + char *newsuperiordn; + Slapi_DN *parentdn = NULL; + Slapi_Entry *target_entry; + Slapi_Entry *existing_entry; + const CSN *target_entry_dncsn; + CSN *opcsn= NULL; + char *op_uniqueid = NULL; + const char *existing_uniqueid = NULL; + const char *target_dn; + const char *existing_dn; + char *newrdn; + char sessionid[REPL_SESSION_ID_SIZE]; + int r; + int op_result= 0; + int rc= 0; /* OK */ + int del_old_replconflict_attr = 0; + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get (pb, SLAPI_MODRDN_TARGET_ENTRY, &target_entry); + if(target_entry==NULL) + { + /* An entry can't be found for the Unique Identifier */ + op_result= LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* No entry to modrdn */ + PROFILE_POINT; /* ModRDN Conflict; Entry does not Exist; Discard ModRDN */ + goto bailout; + } + + get_repl_session_id (pb, sessionid, &opcsn); + target_entry_dncsn = entry_get_dncsn (target_entry); + if ( csn_compare (target_entry_dncsn, opcsn) >= 0 ) + { + /* + * The Operation CSN is not newer than the DN CSN. + * Either we're beaten by another ModRDN or we've applied the op. + */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore the modrdn */ + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; OPCSN is not newer. */ + goto bailout; + } + + /* The DN CSN is older than the Operation CSN. Apply the operation */ + target_dn = slapi_entry_get_dn_const ( target_entry); + slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &newrdn); + slapi_pblock_get(pb, SLAPI_TARGET_UNIQUEID, &op_uniqueid); + slapi_pblock_get(pb, SLAPI_MODRDN_PARENT_ENTRY, &parent_entry); + slapi_pblock_get(pb, SLAPI_MODRDN_NEWPARENT_ENTRY, &new_parent_entry); + slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperiordn); + + if ( is_tombstone_entry (target_entry) ) + { + /* + * It is a non-trivial task to rename a tombstone. + * This op has been ignored so far by + * setting SLAPI_RESULT_CODE to LDAP_NO_SUCH_OBJECT + * and rc to -1. + */ + + /* Turn the tombstone to glue before rename it */ + /* + op_result = tombstone_to_glue (pb, sessionid, target_entry, + slapi_entry_get_sdn (target_entry), "renameTombstone", opcsn); + */ + op_result = LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + if (op_result == 0) + { + /* + * Remember to turn this entry back to tombstone in post op. + * We'll just borrow an obsolete pblock type here. + */ + slapi_pblock_set (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, strdup(op_uniqueid)); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_TARGET_ENTRY); + rc = 0; + } + else + { + rc = -1; + } + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; OPCSN is not newer. */ + goto bailout; + } + + slapi_pblock_get(pb, SLAPI_MODRDN_EXISTING_ENTRY, &existing_entry); + if(existing_entry!=NULL) + { + /* + * An entry with the target DN already exists. + * The smaller dncsn wins. The loser changes its RDN to + * uniqueid+baserdn, and adds operational attribute + * ATTR_NSDS5_REPLCONFLIC + */ + + existing_uniqueid = slapi_entry_get_uniqueid (existing_entry); + existing_dn = slapi_entry_get_dn_const ( existing_entry); + + /* + * Dismiss the operation if the existing entry is the same as the target one. + */ + if (strcmp(op_uniqueid, existing_uniqueid) == 0) { + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; /* Ignore the op */ + PROFILE_POINT; /* ModRDN Replay */ + goto bailout; + } + + r= csn_compare ( entry_get_dncsn (existing_entry), opcsn); + if (r == 0) + { + /* + * The CSN of the Operation and the Entry DN are the same + * but the uniqueids are not. + * There might be two replicas with the same ReplicaID. + */ + slapi_log_error(SLAPI_LOG_FATAL, sessionid, + "Duplicated CSN for different uniqueids [%s][%s]", + existing_uniqueid, op_uniqueid); + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Abort */ + PROFILE_POINT; /* ModRDN Conflict; Duplicated CSN for Different Entries */ + goto bailout; + } + + if(r<0) + { + /* The target entry is a loser */ + + char *newrdn_with_uniqueid; + newrdn_with_uniqueid= get_rdn_plus_uniqueid (sessionid, newrdn, op_uniqueid); + if(newrdn_with_uniqueid==NULL) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; + Unique ID already in RDN - Change to Lost and Found entry */ + goto bailout; + } + mod_namingconflict_attr (op_uniqueid, target_dn, existing_dn, opcsn); + set_pblock_dn (pb, SLAPI_MODRDN_NEWRDN, newrdn_with_uniqueid); + slapi_log_error(slapi_log_urp, sessionid, + "Naming conflict MODRDN. Rename target entry to %s\n", + newrdn_with_uniqueid ); + + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; Rename Operation Entry */ + goto bailout; + } + + if ( r>0 ) + { + /* The existing entry is a loser */ + + int resolve = urp_annotate_dn (sessionid, existing_entry, opcsn, "MODRDN"); + if(!resolve) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Abort this Operation */ + goto bailout; + } + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_NEWPARENT_ENTRY); + if (LDAP_NO_SUCH_OBJECT == resolve) { + /* This means that existing_dn_entry did not really exist!!! + * This indicates that a get_copy_of_entry -> dn2entry returned + * an entry (existing_dn_entry) that was already removed from the ldbm. + * This is bad, because it indicates a dn cache or DB corruption. + * However, as far as the conflict is concerned, this error is harmless: + * if the existing_dn_entry did not exist in the first place, there was no + * conflict!! Return 0 for success to break the ldbm_back_modrdn loop + * and get out of this inexistent conflict resolution ASAP. + */ + rc = 0; + } + /* Set flag to remove possible old naming conflict */ + del_old_replconflict_attr = 1; + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; Rename Entry with Target DN */ + goto bailout; + } + } + else + { + /* + * No entry with the target DN exists. + */ + + /* Set flag to remove possible old naming conflict */ + del_old_replconflict_attr = 1; + + if(new_parent_entry!=NULL) + { + /* The new superior entry exists */ + rc= 0; /* OK, Apply the ModRDN */ + PROFILE_POINT; /* ModRDN Conflict; OK */ + goto bailout; + } + + /* The new superior entry doesn't exist */ + + slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperiordn); + if(newsuperiordn == NULL) + { + /* (new_parent_entry==NULL && newsuperiordn==NULL) + * This is ok - SLAPI_MODRDN_NEWPARENT_ENTRY will + * only be set if SLAPI_MODRDN_NEWSUPERIOR was + * suplied by the client. If it wasn't, we're just + * changing the RDN of the entry. In that case, + * if the entry exists, its parent won't change + * when it's renamed, and therefore we can assume + * its parent exists. + */ + rc=0; + PROFILE_POINT; /* ModRDN OK */ + goto bailout; + } + + newsuperior= slapi_sdn_new_dn_byval(newsuperiordn); + + if((0 == slapi_sdn_compare (slapi_entry_get_sdn(parent_entry), newsuperior)) || + is_suffix_dn (pb, newsuperior, &parentdn) ) + { + /* + * The new superior is the same as the current one, or + * this entry is a suffix whose parent can be absent. + */ + rc= 0; /* OK, Move the entry */ + PROFILE_POINT; /* ModRDN Conflict; Absent Target Parent; Create Suffix Entry */ + goto bailout; + } + + /* + * This entry is not a suffix entry, so the parent entry should exist. + * (This shouldn't happen in a ds5 server) + */ + slapi_pblock_get ( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + op_result = create_glue_entry (pb, sessionid, newsuperior, + op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid, opcsn); + if (LDAP_SUCCESS != op_result) + { + /* + * FATAL ERROR + * We should probably just abort the rename + * this will cause replication divergence requiring + * admin intercession + */ + slapi_log_error( SLAPI_LOG_FATAL, sessionid, + "Parent %s couldn't be found, nor recreated as a glue entry\n", newsuperiordn ); + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; + PROFILE_POINT; + goto bailout; + } + + /* The backend add code should now search for the parent again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_NEWPARENT_ENTRY); + PROFILE_POINT; /* ModRDN Conflict; Absent Target Parent - Change to Lost and Found entry */ + goto bailout; + } + +bailout: + if ( del_old_replconflict_attr && rc == 0 ) + { + del_replconflict_attr (target_entry, opcsn, 0); + } + if ( parentdn ) + slapi_sdn_free(&parentdn); + if ( newsuperior ) + slapi_sdn_free(&newsuperior); + return rc; +} + +/* + * Return 0 for OK, -1 for Error + */ +int +urp_delete_operation( Slapi_PBlock *pb ) +{ + Slapi_Entry *deleteentry; + CSN *opcsn= NULL; + char sessionid[REPL_SESSION_ID_SIZE]; + int op_result= 0; + int rc= 0; /* OK */ + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &deleteentry); + + if(deleteentry==NULL) /* uniqueid can't be found */ + { + op_result= LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Don't apply the Delete */ + PROFILE_POINT; /* Delete Operation; Entry not exist. */ + } + else if(is_tombstone_entry(deleteentry)) + { + /* The entry is already a Tombstone, ignore this delete. */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; /* Don't apply the Delete */ + PROFILE_POINT; /* Delete Operation; Already a Tombstone. */ + } + else /* The entry to be deleted exists and is not a tombstone */ + { + get_repl_session_id (pb, sessionid, &opcsn); + + /* Check if the entry has children. */ + if(!slapi_entry_has_children(deleteentry)) + { + /* Remove possible conflict attributes */ + del_replconflict_attr (deleteentry, opcsn, 0); + rc= 0; /* OK, to delete the entry */ + PROFILE_POINT; /* Delete Operation; OK. */ + } + else + { + /* Turn this entry into a glue_absent_parent entry */ + entry_to_glue(sessionid, deleteentry, REASON_RESURRECT_ENTRY, opcsn); + + /* Turn the Delete into a No-Op */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; /* Don't apply the Delete */ + PROFILE_POINT; /* Delete Operation; Entry has children. */ + } + } + return rc; +} + +int urp_post_modrdn_operation (Slapi_PBlock *pb) +{ + CSN *opcsn; + char sessionid[REPL_SESSION_ID_SIZE]; + char *tombstone_uniqueid; + Slapi_Entry *postentry; + Slapi_Operation *op; + + /* + * Do not abandon the post op - the processed CSN needs to be + * committed to keep the consistency between the changelog + * and the backend DB. + * if ( slapi_op_abandoned(pb) ) return 0; + */ + + slapi_pblock_get (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, &tombstone_uniqueid ); + if (tombstone_uniqueid == NULL) + { + /* + * The entry is not resurrected from tombstone. Hence + * we need to check if any naming conflict with its + * old dn can be resolved. + */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + get_repl_session_id (pb, sessionid, &opcsn); + urp_naming_conflict_removal (pb, sessionid, opcsn, "MODRDN"); + } + } + else + { + /* + * The entry was a resurrected tombstone. + * This could happen when we applied a rename + * to a tombstone to avoid server divergence. Now + * it's time to put the entry back to tombstone. + */ + slapi_pblock_get ( pb, SLAPI_ENTRY_POST_OP, &postentry ); + if (postentry && strcmp(tombstone_uniqueid, slapi_entry_get_uniqueid(postentry)) == 0) + { + entry_to_tombstone (pb, postentry); + } + slapi_ch_free ((void**)&tombstone_uniqueid); + slapi_pblock_set (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, NULL); + } + + return 0; +} + +/* + * Conflict removal + */ +int +urp_post_delete_operation( Slapi_PBlock *pb ) +{ + Slapi_Operation *op; + Slapi_Entry *entry; + CSN *opcsn; + char sessionid[REPL_SESSION_ID_SIZE]; + int op_result; + + /* + * Do not abandon the post op - the processed CSN needs to be + * committed to keep the consistency between the changelog + * and the backend DB + * if ( slapi_op_abandoned(pb) ) return 0; + */ + + get_repl_session_id (pb, sessionid, &opcsn); + + /* + * Conflict removal from the parent entry: + * If the parent is glue and has no more children, + * turn the parent to tombstone + */ + slapi_pblock_get ( pb, SLAPI_DELETE_GLUE_PARENT_ENTRY, &entry ); + if ( entry != NULL ) + { + op_result = entry_to_tombstone ( pb, entry ); + if ( op_result == LDAP_SUCCESS ) + { + slapi_log_error ( slapi_log_urp, sessionid, + "Tombstoned glue entry %s since it has no more children\n", + slapi_entry_get_dn_const (entry) ); + } + } + + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + /* + * Conflict removal from the peers of the old dn + */ + urp_naming_conflict_removal (pb, sessionid, opcsn, "DEL"); + } + + return 0; +} + +int +urp_fixup_add_entry (Slapi_Entry *e, const char *target_uniqueid, const char *parentuniqueid, CSN *opcsn, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + int op_result; + + newpb = slapi_pblock_new (); + + /* + * Mark this operation as replicated, so that the front end + * doesn't add extra attributes. + */ + slapi_add_entry_internal_set_pb ( + newpb, + e, + NULL, /*Controls*/ + repl_get_plugin_identity ( PLUGIN_MULTIMASTER_REPLICATION ), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags); + if (target_uniqueid) + { + slapi_pblock_set( newpb, SLAPI_TARGET_UNIQUEID, (void*)target_uniqueid); + } + if (parentuniqueid) + { + struct slapi_operation_parameters *op_params; + slapi_pblock_get( newpb, SLAPI_OPERATION_PARAMETERS, &op_params ); + op_params->p.p_add.parentuniqueid = (char*)parentuniqueid; /* Consumes parentuniqueid */ + } + slapi_pblock_get ( newpb, SLAPI_OPERATION, &op ); + operation_set_csn ( op, opcsn ); + + slapi_add_internal_pb ( newpb ); + slapi_pblock_get ( newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result ); + slapi_pblock_destroy ( newpb ); + + return op_result; +} + +int +urp_fixup_rename_entry (Slapi_Entry *entry, const char *newrdn, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + CSN *opcsn; + int op_result; + + newpb = slapi_pblock_new(); + + /* + * Must mark this operation as replicated, + * so that the frontend doesn't add extra attributes. + */ + slapi_rename_internal_set_pb ( + newpb, + slapi_entry_get_dn_const (entry), + newrdn, /*NewRDN*/ + NULL, /*NewSuperior*/ + 0, /* !Delete Old RDNS */ + NULL, /*Controls*/ + slapi_entry_get_uniqueid (entry), /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags); + + /* set operation csn to the entry's dncsn */ + opcsn = (CSN *)entry_get_dncsn (entry); + slapi_pblock_get (newpb, SLAPI_OPERATION, &op); + operation_set_csn (op, opcsn); + + slapi_modrdn_internal_pb(newpb); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + + slapi_pblock_destroy(newpb); + return op_result; +} + +int +urp_fixup_delete_entry (const char *uniqueid, const char *dn, CSN *opcsn, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + int op_result; + + newpb = slapi_pblock_new (); + + /* + * Mark this operation as replicated, so that the front end + * doesn't add extra attributes. + */ + slapi_delete_internal_set_pb ( + newpb, + dn, + NULL, /*Controls*/ + uniqueid, /*uniqueid*/ + repl_get_plugin_identity ( PLUGIN_MULTIMASTER_REPLICATION ), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags ); + slapi_pblock_get ( newpb, SLAPI_OPERATION, &op ); + operation_set_csn ( op, opcsn ); + + slapi_delete_internal_pb ( newpb ); + slapi_pblock_get ( newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result ); + slapi_pblock_destroy ( newpb ); + + return op_result; +} + +int +urp_fixup_modify_entry (const char *uniqueid, const char *dn, CSN *opcsn, Slapi_Mods *smods, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + int op_result; + + newpb = slapi_pblock_new(); + + slapi_modify_internal_set_pb ( + newpb, + dn, + slapi_mods_get_ldapmods_byref (smods), + NULL, /* Controls */ + uniqueid, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags); + + /* set operation csn */ + slapi_pblock_get (newpb, SLAPI_OPERATION, &op); + operation_set_csn (op, opcsn); + + /* do modify */ + slapi_modify_internal_pb (newpb); + slapi_pblock_get (newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + slapi_pblock_destroy(newpb); + + return op_result; +} + +static int +urp_add_resolve_parententry (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry, Slapi_Entry *parententry, CSN *opcsn) +{ + Slapi_DN *parentdn = NULL; + Slapi_RDN *add_rdn = NULL; + char *newdn = NULL; + int ldap_rc; + int rc = 0; + + if( is_suffix_entry (pb, entry, &parentdn) ) + { + /* It's OK for the suffix entry's parent to be absent */ + rc= 0; + PROFILE_POINT; /* Add Conflict; Suffix Entry */ + goto bailout; + } + + /* The entry is not a suffix. */ + if(parententry==NULL) /* The parent entry was not found. */ + { + /* Create a glue entry to stand in for the absent parent */ + slapi_operation_parameters *op_params; + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + ldap_rc = create_glue_entry (pb, sessionid, parentdn, op_params->p.p_add.parentuniqueid, opcsn); + if ( LDAP_SUCCESS == ldap_rc ) + { + /* The backend code should now search for the parent again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY); + PROFILE_POINT; /* Add Conflict; Orphaned Entry; Glue Parent */ + } + else + { + /* + * Error. The parent can't be created as a glue entry. + * This will cause replication divergence and will + * require admin intercession + */ + ldap_rc= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ldap_rc); + rc= -1; /* Abort this Operation */ + PROFILE_POINT; /* Add Conflict; Orphaned Entry; Impossible to create parent; Refuse Change. */ + } + goto bailout; + } + + if(is_tombstone_entry(parententry)) /* The parent is a tombstone */ + { + /* The parent entry must be resurected from the dead. */ + ldap_rc = tombstone_to_glue (pb, sessionid, parententry, parentdn, REASON_RESURRECT_ENTRY, opcsn); + if ( ldap_rc != LDAP_SUCCESS ) + { + ldap_rc= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ldap_rc); + rc = -1; /* Abort the operation */ + } + else + { + /* The backend add code should now search for the parent again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY); + } + PROFILE_POINT; /* Add Conflict; Orphaned Entry; Parent Was Tombstone */ + goto bailout; + } + + /* The parent is healthy */ + /* Now we need to check that the parent has the correct DN */ + if (slapi_sdn_isparent(slapi_entry_get_sdn(parententry), slapi_entry_get_sdn(entry))) + { + rc= 0; /* OK, Add the entry */ + PROFILE_POINT; /* Add Conflict; Parent Exists */ + goto bailout; + } + + /* + * Parent entry doesn't have a DN parent to the entry. + * This can happen if parententry was renamed due to + * conflict and the child entry was created before + * replication occured. See defect 530942. + * We need to rename the entry to be child of its parent. + */ + add_rdn = slapi_rdn_new_dn(slapi_entry_get_dn_const (entry)); + newdn = slapi_dn_plus_rdn(slapi_entry_get_dn_const (parententry), slapi_rdn_get_rdn(add_rdn)); + slapi_entry_set_dn ( entry,slapi_ch_strdup(newdn)); + set_pblock_dn (pb,SLAPI_ADD_TARGET,newdn); /* consumes newdn */ + slapi_log_error ( slapi_log_urp, sessionid, + "Parent was renamed. Renamed the child to %s\n", newdn ); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + PROFILE_POINT; /* Add Conflict; Parent Renamed; Rename Operation Entry */ + +bailout: + if (parentdn) + slapi_sdn_free(&parentdn); + return rc; +} + +/* + * urp_annotate_dn: + * Returns 0 on failure + * Returns > 0 on success (1 on general conflict resolution success, LDAP_NO_SUCH_OBJECT on no-conflict success) + * + * Use this function to annotate an existing entry only. To annotate + * a new entry (the operation entry) see urp_add_operation. + */ +static int +urp_annotate_dn (char *sessionid, Slapi_Entry *entry, CSN *opcsn, const char *optype) +{ + int rc = 0; /* Fail */ + int op_result; + char *newrdn; + const char *uniqueid; + const char *basedn; + char ebuf[BUFSIZ]; + + uniqueid = slapi_entry_get_uniqueid (entry); + basedn = slapi_entry_get_ndn (entry); + newrdn = get_rdn_plus_uniqueid ( sessionid, basedn, uniqueid ); + if(newrdn!=NULL) + { + mod_namingconflict_attr (uniqueid, basedn, basedn, opcsn); + op_result = urp_fixup_rename_entry ( entry, newrdn, 0 ); + switch(op_result) + { + case LDAP_SUCCESS: + slapi_log_error(slapi_log_urp, sessionid, + "Naming conflict %s. Renamed existing entry to %s\n", + optype, escape_string (newrdn, ebuf)); + rc = 1; + break; + case LDAP_NO_SUCH_OBJECT: + /* This means that entry did not really exist!!! + * This is clearly indicating that there is a + * get_copy_of_entry -> dn2entry returned + * an entry (entry) that was already removed + * from the ldbm database... + * This is bad, because it clearly indicates + * some kind of db or cache corruption. We need to print + * this fact clearly in the errors log to try + * to solve this corruption one day. + * However, as far as the conflict is concerned, + * this error is completely harmless: + * if thew entry did not exist in the first place, + * there was never a room + * for a conflict!! After fix for 558293, this + * state can't be reproduced anymore (5-Oct-01) + */ + slapi_log_error( SLAPI_LOG_FATAL, sessionid, + "Entry %s exists in cache but not in DB\n", + escape_string (basedn, ebuf) ); + rc = LDAP_NO_SUCH_OBJECT; + break; + default: + slapi_log_error( slapi_log_urp, sessionid, + "Failed to annotate %s, err=%d\n", newrdn, op_result); + } + slapi_ch_free ( (void**)&newrdn ); + } + return rc; +} + +/* + * An URP Naming Collision helper function. Retreives a list of entries + * that have the given dn excluding the unique id of the entry. Any + * entries returned will be entries that have been added with the same + * dn, but caused a naming conflict when replicated. The URP to fix + * this constraint violation is to append the unique id of the entry + * to its RDN. + */ +static Slapi_Entry * +urp_get_min_naming_conflict_entry ( Slapi_PBlock *pb, char *sessionid, CSN *opcsn ) +{ + Slapi_PBlock *newpb = NULL; + LDAPControl **server_ctrls = NULL; + Slapi_Entry **entries = NULL; + Slapi_Entry *min_naming_conflict_entry = NULL; + const CSN *min_csn = NULL; + char *filter = NULL; + char *parent_dn = NULL; + char *basedn; + int i = 0; + int min_i = -1; + int op_result = LDAP_SUCCESS; + + slapi_pblock_get (pb, SLAPI_URP_NAMING_COLLISION_DN, &basedn); + if (NULL == basedn || strncmp (basedn, SLAPI_ATTR_UNIQUEID, strlen(SLAPI_ATTR_UNIQUEID)) == 0) + return NULL; + + slapi_log_error ( SLAPI_LOG_REPL, sessionid, + "Enter urp_get_min_naming_conflict_entry for %s\n", basedn); + + filter = slapi_ch_malloc(50 + strlen(basedn)); + sprintf(filter, "(%s=%s %s)", ATTR_NSDS5_REPLCONFLICT, REASON_ANNOTATE_DN, basedn); + + /* server_ctrls will be freed when newpb is destroyed */ + server_ctrls = (LDAPControl **)slapi_ch_calloc (2, sizeof (LDAPControl *)); + server_ctrls[0] = create_managedsait_control(); + server_ctrls[1] = NULL; + + newpb = slapi_pblock_new(); + parent_dn = slapi_dn_parent (basedn); + slapi_search_internal_set_pb(newpb, + parent_dn, /* Base DN */ + LDAP_SCOPE_ONELEVEL, + filter, + NULL, /* Attrs */ + 0, /* AttrOnly */ + server_ctrls, /* Controls */ + NULL, /* UniqueID */ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + slapi_search_internal_pb(newpb); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if ( (op_result != LDAP_SUCCESS) || (entries == NULL) ) + { + /* Log a message */ + goto done; + } + /* For all entries, get the one with the smallest dn csn */ + for (i = 0; NULL != entries[i]; i++) + { + const CSN *dncsn; + dncsn = entry_get_dncsn(entries[i]); + if ((dncsn != opcsn) && + ((min_csn == NULL) || (csn_compare(dncsn, min_csn) < 0)) && + !is_tombstone_entry (entries[i])) + { + min_csn = dncsn; + min_i = i; + } + /* + * If there are too many conflicts, the current urp code has no + * guarantee for all servers to converge anyway, because the + * urp and the backend can't be done in one transaction due + * to either performance or the deadlock problem. + * Don't sacrifice the performance too much for impossible. + */ + if (min_csn && i > 5) + { + break; + } + } + + if (min_csn != NULL) { + /* Found one entry */ + min_naming_conflict_entry = slapi_entry_dup(entries[min_i]); + } + +done: + slapi_ch_free((void **)&parent_dn); + slapi_ch_free((void **)&filter); + slapi_free_search_results_internal(newpb); + slapi_pblock_destroy(newpb); + newpb = NULL; + + slapi_log_error ( SLAPI_LOG_REPL, sessionid, + "Leave urp_get_min_naming_conflict_entry (found %d entries)\n", i); + + return min_naming_conflict_entry; +} + +/* + * If an entry is deleted or renamed, a new winner may be + * chosen from its naming competitors. + * The entry with the smallest dncsn restores its original DN. + */ +static int +urp_naming_conflict_removal ( Slapi_PBlock *pb, char *sessionid, CSN *opcsn, const char *optype ) +{ + Slapi_Entry *min_naming_conflict_entry; + Slapi_RDN *oldrdn, *newrdn; + const char *oldrdnstr, *newrdnstr; + int op_result; + + /* + * Backend op has set SLAPI_URP_NAMING_COLLISION_DN to the basedn. + */ + min_naming_conflict_entry = urp_get_min_naming_conflict_entry (pb, sessionid, opcsn); + if (min_naming_conflict_entry == NULL) + { + return 0; + } + + /* Step 1: Restore the entry's original DN */ + + oldrdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn (min_naming_conflict_entry) ); + oldrdnstr = slapi_rdn_get_rdn ( oldrdn ); + + /* newrdnstr is the old rdn of the entry minus the nsuniqueid part */ + newrdn = slapi_rdn_new_rdn ( oldrdn ); + slapi_rdn_remove_attr (newrdn, SLAPI_ATTR_UNIQUEID ); + newrdnstr = slapi_rdn_get_rdn ( newrdn ); + + /* + * Set OP_FLAG_ACTION_INVOKE_FOR_REPLOP since this operation + * is done after DB lock was released. The backend modrdn + * will acquire the DB lock if it sees this flag. + */ + op_result = urp_fixup_rename_entry (min_naming_conflict_entry, newrdnstr, OP_FLAG_ACTION_INVOKE_FOR_REPLOP); + if ( op_result != LDAP_SUCCESS ) + { + slapi_log_error (slapi_log_urp, sessionid, + "Failed to restore RDN of %s, err=%d\n", oldrdnstr, op_result); + goto bailout; + } + slapi_log_error (slapi_log_urp, sessionid, + "Naming conflict removed by %s. RDN of %s was restored\n", optype, oldrdnstr); + + /* Step2: Remove ATTR_NSDS5_REPLCONFLICT from the winning entry */ + /* + * A fixup op will not invoke urp_modrdn_operation(). Even it does, + * urp_modrdn_operation() will do nothing because of the same CSN. + */ + op_result = del_replconflict_attr (min_naming_conflict_entry, opcsn, OP_FLAG_ACTION_INVOKE_FOR_REPLOP); + if (op_result != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_REPL, sessionid, + "Failed to remove nsds5ReplConflict for %s, err=%d\n", + newrdnstr, op_result); + } + +bailout: + slapi_entry_free (min_naming_conflict_entry); + slapi_rdn_free(&oldrdn); + slapi_rdn_free(&newrdn); + return op_result; +} + +/* The returned value is either null or "uniqueid=<uniqueid>+<basedn>" */ +static char * +get_dn_plus_uniqueid(char *sessionid, const char *olddn, const char *uniqueid) +{ + Slapi_DN *sdn= slapi_sdn_new_dn_byval(olddn); + Slapi_RDN *rdn= slapi_rdn_new(); + char *newdn; + + PR_ASSERT(uniqueid!=NULL); + + /* Check if the RDN already contains the Unique ID */ + slapi_sdn_get_rdn(sdn,rdn); + if(slapi_rdn_contains(rdn,SLAPI_ATTR_UNIQUEID,uniqueid,strlen(uniqueid))) + { + /* The Unique ID is already in the RDN. + * This is a highly improbable collision. + * It suggests that a duplicate UUID was generated. + * This will cause replication divergence and will + * require admin intercession + */ + slapi_log_error(SLAPI_LOG_FATAL, sessionid, + "Annotated DN %s has naming conflict\n", olddn ); + newdn= NULL; + } + else + { + slapi_rdn_add(rdn,SLAPI_ATTR_UNIQUEID,uniqueid); + slapi_sdn_set_rdn(sdn, rdn); + newdn= slapi_ch_strdup(slapi_sdn_get_dn(sdn)); + } + slapi_sdn_free(&sdn); + slapi_rdn_free(&rdn); + return newdn; +} + +static char * +get_rdn_plus_uniqueid(char *sessionid, const char *olddn, const char *uniqueid) +{ + char *newrdn; + /* Check if the RDN already contains the Unique ID */ + Slapi_DN *sdn= slapi_sdn_new_dn_byval(olddn); + Slapi_RDN *rdn= slapi_rdn_new(); + slapi_sdn_get_rdn(sdn,rdn); + PR_ASSERT(uniqueid!=NULL); + if(slapi_rdn_contains(rdn,SLAPI_ATTR_UNIQUEID,uniqueid,strlen(uniqueid))) + { + /* The Unique ID is already in the RDN. + * This is a highly improbable collision. + * It suggests that a duplicate UUID was generated. + * This will cause replication divergence and will + * require admin intercession + */ + slapi_log_error(SLAPI_LOG_FATAL, sessionid, + "Annotated DN %s has naming conflict\n", olddn ); + newrdn= NULL; + } + else + { + slapi_rdn_add(rdn,SLAPI_ATTR_UNIQUEID,uniqueid); + newrdn= slapi_ch_strdup(slapi_rdn_get_rdn(rdn)); + } + slapi_sdn_free(&sdn); + slapi_rdn_free(&rdn); + return newrdn; +} + +static void +set_pblock_dn (Slapi_PBlock* pb,int pblock_parameter,char *newdn) +{ + char *olddn; + slapi_pblock_get( pb, pblock_parameter, &olddn ); + slapi_ch_free((void**)&olddn); + slapi_pblock_set( pb, pblock_parameter, newdn ); +} + +static int +is_suffix_entry ( Slapi_PBlock *pb, Slapi_Entry *entry, Slapi_DN **parentdn ) +{ + return is_suffix_dn ( pb, slapi_entry_get_sdn(entry), parentdn ); +} + +int +is_suffix_dn ( Slapi_PBlock *pb, const Slapi_DN *dn, Slapi_DN **parentdn ) +{ + Slapi_Backend *backend; + int rc; + + *parentdn = slapi_sdn_new(); + slapi_pblock_get( pb, SLAPI_BACKEND, &backend ); + slapi_sdn_get_backend_parent (dn, *parentdn, backend); + + /* A suffix entry doesn't have parent dn */ + rc = slapi_sdn_isempty (*parentdn) ? 1 : 0; + + return rc; +} + +static int +mod_namingconflict_attr (const char *uniqueid, const char *entrydn, const char *conflictdn, CSN *opcsn) +{ + Slapi_Mods smods; + char buf[BUFSIZ]; + int op_result; + + sprintf (buf, "%s %s", REASON_ANNOTATE_DN, conflictdn); + slapi_mods_init (&smods, 2); + if ( strncmp (entrydn, SLAPI_ATTR_UNIQUEID, strlen(SLAPI_ATTR_UNIQUEID)) != 0 ) + { + slapi_mods_add (&smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, strlen(buf), buf); + } + else + { + /* + * If the existing entry is already a naming conflict loser, + * the following replace operation should result in the + * replace of the ATTR_NSDS5_REPLCONFLICT index as well. + */ + slapi_mods_add (&smods, LDAP_MOD_REPLACE, ATTR_NSDS5_REPLCONFLICT, strlen(buf), buf); + } + op_result = urp_fixup_modify_entry (uniqueid, entrydn, opcsn, &smods, 0); + slapi_mods_done (&smods); + return op_result; +} + +static int +del_replconflict_attr (Slapi_Entry *entry, CSN *opcsn, int opflags) +{ + Slapi_Attr *attr; + int op_result = 0; + + if (slapi_entry_attr_find (entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) + { + Slapi_Mods smods; + const char *uniqueid; + const char *entrydn; + + uniqueid = slapi_entry_get_uniqueid (entry); + entrydn = slapi_entry_get_dn_const (entry); + slapi_mods_init (&smods, 2); + slapi_mods_add (&smods, LDAP_MOD_DELETE, ATTR_NSDS5_REPLCONFLICT, 0, NULL); + op_result = urp_fixup_modify_entry (uniqueid, entrydn, opcsn, &smods, opflags); + slapi_mods_done (&smods); + } + return op_result; +} diff --git a/ldap/servers/plugins/replication/urp.h b/ldap/servers/plugins/replication/urp.h new file mode 100644 index 00000000..9db477bd --- /dev/null +++ b/ldap/servers/plugins/replication/urp.h @@ -0,0 +1,45 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + */ + +#define REASON_ANNOTATE_DN "namingConflict" +#define REASON_RESURRECT_ENTRY "deletedEntryHasChildren" + +/* + * urp.c + */ +int urp_modify_operation( Slapi_PBlock *pb ); +int urp_add_operation( Slapi_PBlock *pb ); +int urp_delete_operation( Slapi_PBlock *pb ); +int urp_post_delete_operation( Slapi_PBlock *pb ); +int urp_modrdn_operation( Slapi_PBlock *pb ); +int urp_post_modrdn_operation( Slapi_PBlock *pb ); + +/* urp internal ops */ +int urp_fixup_add_entry (Slapi_Entry *e, const char *target_uniqueid, const char *parentuniqueid, CSN *opcsn, int opflags); +int urp_fixup_delete_entry (const char *uniqueid, const char *dn, CSN *opcsn, int opflags); +int urp_fixup_rename_entry (Slapi_Entry *entry, const char *newrdn, int opflags); +int urp_fixup_modify_entry (const char *uniqueid, const char *dn, CSN *opcsn, Slapi_Mods *smods, int opflags); + +int is_suffix_dn (Slapi_PBlock *pb, const Slapi_DN *dn, Slapi_DN **parenddn); + +/* + * urp_glue.c + */ +int is_glue_entry(const Slapi_Entry* entry); +int create_glue_entry ( Slapi_PBlock *pb, char *sessionid, Slapi_DN *dn, const char *uniqueid, CSN *opcsn ); +int entry_to_glue(char *sessionid, const Slapi_Entry* entry, const char *reason, CSN *opcsn); +int glue_to_entry (Slapi_PBlock *pb, Slapi_Entry *entry ); +PRBool get_glue_csn(const Slapi_Entry *entry, const CSN **gluecsn); + +/* + * urp_tombstone.c + */ +int is_tombstone_entry(const Slapi_Entry* entry); +int tombstone_to_glue(Slapi_PBlock *pb, const char *sessionid, Slapi_Entry *entry, const Slapi_DN *parentdn, const char *reason, CSN *opcsn); +int entry_to_tombstone ( Slapi_PBlock *pb, Slapi_Entry *entry ); +PRBool get_tombstone_csn(const Slapi_Entry *entry, const CSN **delcsn); diff --git a/ldap/servers/plugins/replication/urp_glue.c b/ldap/servers/plugins/replication/urp_glue.c new file mode 100644 index 00000000..dcb2f72d --- /dev/null +++ b/ldap/servers/plugins/replication/urp_glue.c @@ -0,0 +1,235 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * urp_glue.c - Update Resolution Procedures - Glue + */ + +#include "slapi-plugin.h" +#include "repl5.h" +#include "urp.h" + + +#define RDNBUFSIZE 2048 +extern int slapi_log_urp; + +/* + * Check if the entry is glue. + */ +int +is_glue_entry(const Slapi_Entry* entry) +{ + /* JCMREPL - Is there a more efficient way to do this? */ + return slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "glue"); +} + +/* returns PR_TRUE if the entry is a glue entry, PR_FALSE otherwise + sets the gluecsn if it is a glue entry - gluecsn may (but should not) be NULL */ +PRBool +get_glue_csn(const Slapi_Entry *entry, const CSN **gluecsn) +{ + PRBool isglue = PR_FALSE; + Slapi_Attr *oc_attr = NULL; + + /* cast away const - entry */ + if (entry_attr_find_wsi((Slapi_Entry*)entry, SLAPI_ATTR_OBJECTCLASS, &oc_attr) == ATTRIBUTE_PRESENT) + { + Slapi_Value *glue_value = NULL; + struct berval v; + v.bv_val = "glue"; + v.bv_len = strlen(v.bv_val); + if (attr_value_find_wsi(oc_attr, &v, &glue_value) == VALUE_PRESENT) + { + isglue = PR_TRUE; + *gluecsn = value_get_csn(glue_value, CSN_TYPE_VALUE_UPDATED); + } + } + + return isglue; +} + +/* + * Submit a Modify operation to turn the Entry into Glue. + */ +int +entry_to_glue(char *sessionid, const Slapi_Entry* entry, const char *reason, CSN *opcsn) +{ + int op_result = 0; + const char *dn; + char ebuf[BUFSIZ]; + slapi_mods smods; + Slapi_Attr *attr; + + dn = slapi_entry_get_dn_const (entry); + slapi_mods_init(&smods, 4); + /* + richm: sometimes the entry is already a glue entry (how did that happen?) + OR + the entry is already objectclass extensibleObject or already has the + conflict attribute and/or value + */ + if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "glue")) + { + slapi_mods_add_string( &smods, LDAP_MOD_ADD, SLAPI_ATTR_OBJECTCLASS, "glue" ); + + if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "extensibleobject")) + slapi_mods_add_string( &smods, LDAP_MOD_ADD, SLAPI_ATTR_OBJECTCLASS, "extensibleobject" ); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Target entry %s is already a glue entry reason %s\n", + sessionid, escape_string(dn, ebuf), reason); + } + + if (slapi_entry_attr_find (entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) + { + slapi_mods_add_string( &smods, LDAP_MOD_REPLACE, ATTR_NSDS5_REPLCONFLICT, reason); + } + else + { + slapi_mods_add_string( &smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, reason); + } + + if (slapi_mods_get_num_mods(&smods) > 0) + { + op_result = urp_fixup_modify_entry (NULL, dn, opcsn, &smods, 0); + if (op_result == LDAP_SUCCESS) + { + slapi_log_error (slapi_log_urp, repl_plugin_name, + "%s: Turned the entry %s to glue, reason %s\n", + sessionid, escape_string(dn, ebuf), reason); + } + } + + slapi_mods_done(&smods); + return op_result; +} + +static const char *glue_entry = + "dn: %s\n" + "%s" + "objectclass: top\n" + "objectclass: extensibleObject\n" /* JCMREPL - To avoid schema checking. */ + "objectclass: glue\n" + "nsuniqueid: %s\n" + "%s: %s\n"; /* Add why it's been created */ + +static int +do_create_glue_entry(const Slapi_RDN *rdn, const Slapi_DN *superiordn, const char *uniqueid, const char *reason, CSN *opcsn) +{ + int op_result= LDAP_OPERATIONS_ERROR; + int rdnval_index = 0; + int rdntype_len, rdnval_len, rdnpair_len, rdnstr_len, alloc_len; + Slapi_Entry *e; + Slapi_DN *sdn = NULL; + Slapi_RDN *newrdn = slapi_rdn_new_rdn(rdn); + char *estr, *rdnstr, *rdntype, *rdnval, *rdnpair; + sdn = slapi_sdn_new_dn_byval(slapi_sdn_get_ndn(superiordn)); + slapi_sdn_add_rdn(sdn,rdn); + + + /* must take care of multi-valued rdn: split rdn into different lines introducing + * '\n' between each type/value pair. + */ + alloc_len = RDNBUFSIZE; + rdnstr = slapi_ch_malloc(alloc_len); + rdnpair = rdnstr; + *rdnpair = '\0'; /* so that strlen(rdnstr) may return 0 the first time it's called */ + while ((rdnval_index = slapi_rdn_get_next(newrdn, rdnval_index, &rdntype, &rdnval)) != -1) { + rdntype_len = strlen(rdntype); + rdnval_len = strlen(rdnval); + rdnpair_len = LDIF_SIZE_NEEDED(rdntype_len, rdnval_len); + rdnstr_len = strlen(rdnstr); + if ((rdnstr_len + rdnpair_len + 1) > alloc_len) { + alloc_len += (rdnpair_len + 1); + rdnstr = slapi_ch_realloc(rdnstr, alloc_len); + rdnpair = &rdnstr[rdnstr_len]; + } + ldif_put_type_and_value_with_options(&rdnpair, rdntype, + rdnval, rdnval_len, LDIF_OPT_NOWRAP); + *rdnpair = '\0'; + } + estr= slapi_ch_malloc(strlen(glue_entry) + slapi_sdn_get_ndn_len(sdn) + + strlen(rdnstr) + strlen(uniqueid) + + strlen(ATTR_NSDS5_REPLCONFLICT) + strlen(reason) + 1); + sprintf(estr, glue_entry, slapi_sdn_get_ndn(sdn), rdnstr, uniqueid, + ATTR_NSDS5_REPLCONFLICT, reason); + slapi_ch_free((void**)&rdnstr); + slapi_rdn_done(newrdn); + slapi_ch_free((void**)&newrdn); + e = slapi_str2entry( estr, 0 ); + PR_ASSERT(e!=NULL); + if ( e!=NULL ) + { + slapi_entry_set_uniqueid (e, slapi_ch_strdup(uniqueid)); + op_result = urp_fixup_add_entry (e, NULL, NULL, opcsn, 0); + slapi_ch_free ( (void **) &estr ); /* XXXggood - this leaks if e == NULL */ + } + slapi_sdn_free(&sdn); + return op_result; +} + +int +create_glue_entry ( Slapi_PBlock *pb, char *sessionid, Slapi_DN *dn, const char *uniqueid, CSN *opcsn ) +{ + int op_result; + const char *dnstr; + + if ( slapi_sdn_get_dn (dn) ) + dnstr = slapi_sdn_get_dn (dn); + else + dnstr = ""; + + if ( NULL == uniqueid ) + { + op_result = LDAP_OPERATIONS_ERROR; + slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Can't create glue %s, uniqueid=NULL\n", sessionid, dnstr); + } + else + { + Slapi_Backend *backend; + Slapi_DN *superiordn = slapi_sdn_new(); + Slapi_RDN *rdn= slapi_rdn_new(); + int done= 0; + + slapi_pblock_get( pb, SLAPI_BACKEND, &backend ); + slapi_sdn_get_backend_parent ( dn, superiordn, backend ); + slapi_sdn_get_rdn ( dn, rdn ); + + while(!done) + { + op_result= do_create_glue_entry(rdn, superiordn, uniqueid, "missingEntry", opcsn); + switch(op_result) + { + case LDAP_SUCCESS: + slapi_log_error ( SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Created glue entry %s uniqueid=%s reason missingEntry\n", + sessionid, dnstr, uniqueid); + done= 1; + break; + case LDAP_NO_SUCH_OBJECT: + /* The parent is missing */ + { + /* JCMREPL - Create the parent ... recursion?... but what's the uniqueid? */ + PR_ASSERT(0); /* JCMREPL */ + } + default: + slapi_log_error ( SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Can't created glue entry %s uniqueid=%s, error %d\n", + sessionid, dnstr, uniqueid, op_result); + break; + } + /* JCMREPL - Could get trapped in this loop forever! */ + } + + slapi_rdn_free ( &rdn ); + slapi_sdn_free ( &superiordn ); + } + + return op_result; +} diff --git a/ldap/servers/plugins/replication/urp_tombstone.c b/ldap/servers/plugins/replication/urp_tombstone.c new file mode 100644 index 00000000..3b24b928 --- /dev/null +++ b/ldap/servers/plugins/replication/urp_tombstone.c @@ -0,0 +1,210 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * urp_tombstone.c - Update Resolution Procedures - Tombstones + */ + +#include "slapi-plugin.h" +#include "repl5.h" +#include "urp.h" + +extern int slapi_log_urp; + +/* + * Check if the entry is a tombstone. + */ +int +is_tombstone_entry(const Slapi_Entry* entry) +{ + int flag; + + /* LP: This doesn't work very well with entries that we tombstone ourself */ + flag = slapi_entry_flag_is_set (entry, SLAPI_ENTRY_FLAG_TOMBSTONE); + if (flag == 0) + { + /* This is slow */ + flag = slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, SLAPI_ATTR_VALUE_TOMBSTONE); + } + return flag; +} + +PRBool +get_tombstone_csn(const Slapi_Entry *entry, const CSN **delcsn) +{ + PRBool ists = PR_FALSE; + if (is_tombstone_entry(entry)) { + ists = PR_TRUE; + *delcsn = _get_deletion_csn((Slapi_Entry *)entry); /* cast away const */ + } + + return ists; +} + +static int +tombstone_to_glue_resolve_parent ( + Slapi_PBlock *pb, + const char *sessionid, + const Slapi_DN *parentdn, + const char *parentuniqueid, + CSN *opcsn) +{ + /* Let's have a look at the parent of this entry... */ + if(!slapi_sdn_isempty(parentdn) && parentuniqueid!=NULL) + { + int op_result; + Slapi_PBlock *newpb= slapi_pblock_new(); + slapi_search_internal_set_pb( + newpb, + slapi_sdn_get_dn(parentdn), /* JCM - This DN just identifies the backend to be searched. */ + LDAP_SCOPE_BASE, + "objectclass=*", + NULL, /*attrs*/ + 0, /*attrsonly*/ + NULL, /*Controls*/ + parentuniqueid, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + slapi_search_internal_pb(newpb); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + switch(op_result) + { + case LDAP_SUCCESS: + { + Slapi_Entry **entries= NULL; + /* OK, the tombstone entry parent exists. Is it also a tombstone? */ + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if(entries!=NULL && entries[0]!=NULL) + { + if(is_tombstone_entry(entries[0])) + { + tombstone_to_glue (pb, sessionid, entries[0], parentdn, REASON_RESURRECT_ENTRY, opcsn); + } + } + else + { + /* JCM - Couldn't find the entry! */ + } + } + break; + default: + /* So, the tombstone entry had a parent... but it's gone. */ + /* That's probably a bad thing. */ + break; + } + slapi_free_search_results_internal (newpb); + slapi_pblock_destroy(newpb); + } + return 0; +} + +/* + * Convert a tombstone into a glue entry. + */ +int +tombstone_to_glue ( + Slapi_PBlock *pb, + const char *sessionid, + Slapi_Entry *tombstoneentry, + const Slapi_DN *tombstonedn, + const char *reason, + CSN *opcsn) +{ + Slapi_DN *parentdn; + char *parentuniqueid; + const char *tombstoneuniqueid; + Slapi_Entry *addingentry; + const char *addingdn; + int op_result; + + /* JCMREPL + * Nothing logged to the 5.0 Change Log + * Add is logged to the 4.0 Change Log - Core server Add code + * must attach the entry to the Operation + */ + + + /* Resurrect the parent entry first */ + + /* JCM - This DN calculation is odd. It could resolve to NULL + * which won't help us identify the correct backend to search. + */ + is_suffix_dn (pb, tombstonedn, &parentdn); + parentuniqueid= slapi_entry_attr_get_charptr (tombstoneentry, + SLAPI_ATTR_VALUE_PARENT_UNIQUEID); /* Allocated */ + tombstone_to_glue_resolve_parent (pb, sessionid, parentdn, parentuniqueid, opcsn); + slapi_sdn_free(&parentdn); + + /* Submit an Add operation to turn the tombstone entry into glue. */ + /* + * The tombstone is stored with an invalid DN, we must fix this. + */ + addingentry = slapi_entry_dup(tombstoneentry); + addingdn = slapi_sdn_get_dn(tombstonedn); + slapi_entry_set_dn(addingentry,slapi_ch_strdup(addingdn)); /* consumes DN */ + + if (!slapi_entry_attr_hasvalue(addingentry, ATTR_NSDS5_REPLCONFLICT, reason)) + { + /* Add the reason of turning it to glue - The backend code will use it*/ + slapi_entry_add_string(addingentry, ATTR_NSDS5_REPLCONFLICT, reason); + } + tombstoneuniqueid= slapi_entry_get_uniqueid(tombstoneentry); + op_result = urp_fixup_add_entry (addingentry, tombstoneuniqueid, parentuniqueid, opcsn, OP_FLAG_RESURECT_ENTRY); + if (op_result == LDAP_SUCCESS) + { + slapi_log_error (slapi_log_urp, repl_plugin_name, + "%s: Resurrected tombstone %s to glue reason '%s'\n", sessionid, addingdn, reason); + } + else + { + slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Can't resurrect tombstone %s to glue reason '%s', error=%d\n", + sessionid, addingdn, reason, op_result); + } + slapi_entry_free (addingentry); + return op_result; +} + +int +entry_to_tombstone ( Slapi_PBlock *pb, Slapi_Entry *entry ) +{ + Slapi_Operation *op; + Slapi_Mods smods; + CSN *opcsn; + const char *uniqueid; + int op_result = LDAP_SUCCESS; + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + opcsn = operation_get_csn ( op ); + uniqueid = slapi_entry_get_uniqueid ( entry ); + + + slapi_mods_init ( &smods, 2 ); + /* Remove objectclass=glue */ + slapi_mods_add ( &smods, LDAP_MOD_DELETE, SLAPI_ATTR_OBJECTCLASS, strlen("glue"), "glue"); + /* Remove any URP conflict since a tombstone shouldn't + * be retrieved later for conflict removal. + */ + slapi_mods_add ( &smods, LDAP_MOD_DELETE, ATTR_NSDS5_REPLCONFLICT, 0, NULL ); + + op_result = urp_fixup_modify_entry (uniqueid, slapi_entry_get_dn_const (entry), opcsn, &smods, 0); + slapi_mods_done ( &smods ); + + /* + * Delete the entry. + */ + if ( op_result == LDAP_SUCCESS ) + { + /* + * Using internal delete operation since it would go + * through the urp operations and trigger the recursive + * fixup if applicable. + */ + op_result = urp_fixup_delete_entry (uniqueid, slapi_entry_get_dn_const (entry), opcsn, 0); + } + + return op_result; +} diff --git a/ldap/servers/plugins/retrocl/Makefile b/ldap/servers/plugins/retrocl/Makefile new file mode 100644 index 00000000..c81f0dfe --- /dev/null +++ b/ldap/servers/plugins/retrocl/Makefile @@ -0,0 +1,135 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server "Retrocl" plugin + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/retrocl-plugin +BINDIR = $(LDAP_SERVER_RELDIR) +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk +include $(MCOM_ROOT)/ldapserver/ns_usedb.mk +INCLUDES+=-I$(DB_INCLUDE) + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./retrocl.def +endif + +CFLAGS += $(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +CFLAGS += /WX +endif + +ifdef TEST_CL5 +CFLAGS += -DTEST_CL5 +endif + +INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(DB_INCLUDE) + +ifeq ($(ARCH), WINNT) +SUBSYSTEM=console +endif + +LOCAL_OBJS= \ + retrocl.o \ + retrocl_po.o \ + retrocl_rootdse.o \ + retrocl_cn.o \ + retrocl_trim.o \ + retrocl_create.o \ + + + +LIBRETROCL_OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS)) + +ifeq ($(ARCH), WINNT) +RETROCL_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +LIBRETROCL= $(addprefix $(LIBDIR)/, $(RETROCL_DLL).$(DLL_SUFFIX)) + +LT_OBJS = $(addprefix $(OBJDEST)/, linktest.o) + +#EXTRA_LIBS_DEP = $(LDAPSDK_DEP) \ +# $(LDAP_LIBLDIF_DEP) \ +# $(LDAP_SLIBLCACHE_DEP) $(DB_LIB_DEP) $(LIBSLAPD_DEP) \ +# $(LDAP_COMMON_LIBS_DEP) + +#EXTRA_LIBS = $(LIBACCESS) $(LDAP_SDK_LIBSSLDAP_LIB) $(ADMINUTIL_LINK) \ +# $(LDAP_SDK_LIBLDAP_DLL) $(LDAP_SLIBLCACHE) $(DB_LIB) \ +# $(PLATFORM_SPECIFIC_EXTRA_LIBRARY) $(LIBSLAPD) $(LDAP_LIBLITEKEY) \ +# $(NLSLINK) $(ALIBS) \ +# $(LDAP_SDK_LIBSSLDAP_LIB) $(LDAP_SDK_LIBLDAP_DLL) \ +# $(LIBSECURITYLINK) $(NSPRLINK) $(DBMLINK) \ +# $(THREADSLIB) $(LDAP_COMMON_LIBS) $(NSPRLINK) $(SVRCORELINK) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./retrocl.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBRETROCL) + +linktest: $(LIBRETROCL) $(LT_OBJS) + $(LINK_EXE_NOLIBSOBJS) -o linktest $(LT_OBJS) $(LIBRETROCL) -Rlib -Rlib/../bin/slapd/lib -Llib -Llib/../bin/slapd/lib -lslapd $(EXTRA_LIBS) $(NSPRLINK) + + +$(LIBRETROCL): $(LIBRETROCL_OBJS) $(RETROCL_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBRETROCL_OBJS) $(RETROCL_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) $(LDAP_LIBLDIF) $(NSPRLINK) + +tests: $(TEST_PROGS) + +veryclean: clean + +clean: + $(RM) $(LIBRETROCL_OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(RETROCL_DLL_OBJ) +endif + $(RM) $(LIBRETROCL) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(LIBRETROCL_OBJS): diff --git a/ldap/servers/plugins/retrocl/dllmain.c b/ldap/servers/plugins/retrocl/dllmain.c new file mode 100644 index 00000000..276b1fa9 --- /dev/null +++ b/ldap/servers/plugins/retrocl/dllmain.c @@ -0,0 +1,90 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + /* + * Microsoft Windows specifics for LIBRETROCL DLL + */ +#include "ldap.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/retrocl/linktest.c b/ldap/servers/plugins/retrocl/linktest.c new file mode 100644 index 00000000..22812c57 --- /dev/null +++ b/ldap/servers/plugins/retrocl/linktest.c @@ -0,0 +1,16 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* This is a test program. Not linked into the shared library */ + +#include "retrocl.h" + +int main(int a,char **b) +{ + int r; + + r = retrocl_plugin_init(NULL); +} diff --git a/ldap/servers/plugins/retrocl/retrocl.c b/ldap/servers/plugins/retrocl/retrocl.c new file mode 100644 index 00000000..e0d3e325 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl.c @@ -0,0 +1,341 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * Requires that create_instance.c have added a plugin entry similar to: + +dn: cn=Retrocl Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: RetroCL Plugin +nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/retrocl-plugin.so +nsslapd-plugininitfunc: retrocl_plugin_init +nsslapd-plugintype: object +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database +nsslapd-pluginid: retrocl +nsslapd-pluginversion: 5.0b2 +nsslapd-pluginvendor: Sun Microsystems, Inc. +nsslapd-plugindescription: Retrocl Plugin + + * + */ + +#include "retrocl.h" + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +void* g_plg_identity [PLUGIN_MAX]; + +Slapi_Backend *retrocl_be_changelog = NULL; + +/* ----------------------------- Retrocl Plugin */ + +static Slapi_PluginDesc retrocldesc = {"retrocl", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Retrocl Plugin"}; +static Slapi_PluginDesc retroclpostopdesc = {"retrocl-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "retrocl post-operation plugin"}; +static Slapi_PluginDesc retroclinternalpostopdesc = {"retrocl-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "retrocl internal post-operation plugin"}; +static Slapi_PluginDesc retroclbepostopdesc = {"retrocl-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Retrocl bepost-operation plugin"}; + + +/* + * Function: retrocl_* + * + * Returns: LDAP_ + * + * Arguments: Pb of operation + * + * Description: wrappers around retrocl_postob registered as callback + * + */ + +int retrocl_postop_add (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_ADD);} +int retrocl_postop_delete (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_DELETE);} +int retrocl_postop_modify (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_MODIFY);} +int retrocl_postop_modrdn (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_MODRDN);} + +/* + * Function: retrocl_postop_init + * + * Returns: 0/-1 + * + * Arguments: Pb + * + * Description: callback function + * + */ + +int +retrocl_postop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&retroclpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) retrocl_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) retrocl_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) retrocl_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) retrocl_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, "retrocl_postop_init failed\n" ); + rc= -1; + } + + return rc; +} + +/* + * Function: retrocl_internalpostop_init + * + * Returns: 0/-1 + * + * Arguments: Pb + * + * Description: callback function + * + */ + +int +retrocl_internalpostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&retroclinternalpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) retrocl_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) retrocl_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) retrocl_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) retrocl_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, "retrocl_internalpostop_init failed\n" ); + rc= -1; + } + + return rc; +} + +/* + * Function: retrocl_rootdse_init + * + * Returns: LDAP_SUCCESS + * + * Arguments: none + * + * Description: The FE DSE *must* be initialised before we get here. + * + */ +static int retrocl_rootdse_init(void) +{ + + int return_value= LDAP_SUCCESS; + + slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"", + LDAP_SCOPE_BASE,"(objectclass=*)", + retrocl_rootdse_search,NULL); + return return_value; +} + +/* + * Function: retrocl_select_backend + * + * Returns: LDAP_ + * + * Arguments: none + * + * Description: simulates an add of the changelog to see if it exists. If not, + * creates it. Then reads the changenumbers. This function should be called + * exactly once at startup. + * + */ + +static int retrocl_select_backend(void) +{ + int err; + Slapi_PBlock *pb; + Slapi_Backend *be = NULL; + Slapi_Entry *referral = NULL; + Slapi_Operation *op = NULL; + char errbuf[1024]; + + pb = slapi_pblock_new(); + + slapi_pblock_set (pb, SLAPI_PLUGIN_IDENTITY, g_plg_identity[PLUGIN_RETROCL]); + + /* This is a simulated operation; no actual add is performed */ + op = operation_new(OP_FLAG_INTERNAL); + operation_set_type(op,SLAPI_OPERATION_ADD); /* Ensure be not readonly */ + + operation_set_target_spec_str(op,RETROCL_CHANGELOG_DN); + + slapi_pblock_set(pb,SLAPI_OPERATION, op); + + err = slapi_mapping_tree_select(pb,&be,&referral,errbuf); + slapi_entry_free(referral); + + operation_free(&op,NULL); + + if (err != LDAP_SUCCESS || be == NULL || be == defbackend_get_backend()) { + LDAPDebug(LDAP_DEBUG_TRACE,"Mapping tree select failed (%d) %s.\n", + err,errbuf,0); + + /* could not find the backend for cn=changelog, either because + * it doesn't exist + * mapping tree not registered. + */ + err = retrocl_create_config(); + + if (err != LDAP_SUCCESS) return err; + } else { + retrocl_be_changelog = be; + } + + retrocl_create_cle(); + + return retrocl_get_changenumbers(); +} + +/* + * Function: retrocl_get_config_str + * + * Returns: malloc'ed string which must be freed. + * + * Arguments: attribute type name + * + * Description: reads a single-valued string attr from the plugins' own DSE. + * This is called twice: to obtain the trim max age during startup, and to + * obtain the change log directory. No callback is registered; you cannot + * change the trim max age without restarting the server. + * + */ + +char *retrocl_get_config_str(const char *attrt) +{ + Slapi_Entry **entries; + Slapi_PBlock *pb = NULL; + char *ma; + int rc = 0; + char *dn; + + dn = RETROCL_PLUGIN_DN; + + pb = slapi_pblock_new(); + + slapi_search_internal_set_pb (pb, dn, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL, + NULL, g_plg_identity[PLUGIN_RETROCL] , 0); + slapi_search_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != 0) { + slapi_pblock_destroy(pb); + return NULL; + } + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + + ma = slapi_entry_attr_get_charptr(entries[0],attrt); + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return ma; +} + +/* + * Function: retrocl_start + * + * Returns: 0 on success + * + * Arguments: Pb + * + * Description: + * + */ + +static int retrocl_start (Slapi_PBlock *pb) +{ + static int retrocl_started = 0; + int rc = 0; + + if (!retrocl_started) { + retrocl_rootdse_init(); + + rc = retrocl_select_backend(); + + if (rc == 0) { + retrocl_init_trimming(); + } else { + LDAPDebug(LDAP_DEBUG_TRACE,"Couldnt find backend, not trimming retro changelog (%d).\n",rc,0,0); + } + } + + retrocl_started = 1; + return rc; +} + +/* + * Function: retrocl_stop + * + * Returns: 0 + * + * Arguments: Pb + * + * Description: called when the server is shutting down + * + */ + +static int retrocl_stop (Slapi_PBlock *pb) +{ + int rc = 0; + + retrocl_stop_trimming(); + retrocl_be_changelog = NULL; + retrocl_forget_changenumbers(); + + return rc; +} + +/* + * Function: retrocl_plugin_init + * + * Returns: 0 on successs + * + * Arguments: Pb + * + * Description: main entry point for retrocl + * + */ + +int +retrocl_plugin_init(Slapi_PBlock *pb) +{ + static int legacy_initialised= 0; + int rc = 0; + void *identity = NULL; + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + g_plg_identity[PLUGIN_RETROCL] = identity; + + if (!legacy_initialised) { + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&retrocldesc ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) retrocl_start ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) retrocl_stop ); + + rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "retrocl_postop_init", retrocl_postop_init, "Retrocl postoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "retrocl_internalpostop_init", retrocl_internalpostop_init, "Retrocl internal postoperation plugin", NULL, identity); + } + + legacy_initialised = 1; + return rc; +} + + + diff --git a/ldap/servers/plugins/retrocl/retrocl.def b/ldap/servers/plugins/retrocl/retrocl.def new file mode 100644 index 00000000..ce730c44 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl.def @@ -0,0 +1,15 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; + +DESCRIPTION 'Netscape Directory Server 7.0 Retro-Changelog Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + plugin_init_debug_level @1 + retrocl_plugin_init @2 + diff --git a/ldap/servers/plugins/retrocl/retrocl.h b/ldap/servers/plugins/retrocl/retrocl.h new file mode 100644 index 00000000..f96af64c --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl.h @@ -0,0 +1,123 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _H_RETROCL +#define _H_RETROCL 1 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "slapi-private.h" +#include "slapi-plugin.h" +/* #include "portable.h" */ +#include "dirver.h" +#include "ldaplog.h" +#include "ldif.h" +#include "slap.h" +#include <dirlite_strings.h> + +/* max len of a long (2^64), represented as a string, including null byte */ +#define CNUMSTR_LEN 21 +typedef unsigned long changeNumber; + +typedef struct _cnum_result_t { + int crt_nentries; /* number of entries returned from search */ + int crt_err; /* err returned from backend */ + Slapi_Entry *crt_entry; /* The entry returned from the backend */ +} cnum_result_t; + +typedef struct _cnumRet { + changeNumber cr_cnum; + char *cr_time; + int cr_lderr; +} cnumRet; + +/* Operation types */ +#define OP_MODIFY 1 +#define OP_ADD 2 +#define OP_DELETE 3 +#define OP_MODRDN 4 + +/* + * How often the changelog trimming thread runs. This is the minimum trim age. + */ +#define CHANGELOGDB_TRIM_INTERVAL 300*1000 /* 5 minutes */ + +#define RETROCL_DLL_DEFAULT_THREAD_STACKSIZE 131072L +#define RETROCL_BE_CACHEMEMSIZE "2097152" +#define RETROCL_BE_CACHESIZE "-1" +#define RETROCL_PLUGIN_NAME "DSRetroclPlugin" + +/* was originally changelogmaximumage */ +#define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage" +#define CONFIG_CHANGELOG_DIRECTORY_ATTRIBUTE "nsslapd-changelogdir" + +#define RETROCL_CHANGELOG_DN "cn=changelog" +#define RETROCL_MAPPINGTREE_DN "cn=\"cn=changelog\",cn=mapping tree,cn=config" +#define RETROCL_PLUGIN_DN "cn=Retro Changelog Plugin,cn=plugins,cn=config" +#define RETROCL_LDBM_DN "cn=changelog,cn=ldbm database,cn=plugins,cn=config" +#define RETROCL_INDEX_DN "cn=changenumber,cn=index,cn=changelog,cn=ldbm database,cn=plugins,cn=config" + +/* Allow anonymous access to the changelog base only, but not to the + * entries in the changelog. + */ +#define RETROCL_ACL "(target =\"ldap:///cn=changelog\")(targetattr != \"aci\")(version 3.0; acl \"changelog base\"; allow( read,search, compare ) userdn =\"ldap:///anyone\";)" + +enum { + PLUGIN_RETROCL, + PLUGIN_MAX +}; + +extern void* g_plg_identity [PLUGIN_MAX]; +extern Slapi_Backend *retrocl_be_changelog; + +extern const char *attr_changenumber; +extern const char *attr_targetdn; +extern const char *attr_changetype; +extern const char *attr_newrdn; +extern const char *attr_newsuperior; +extern const char *attr_deleteoldrdn; +extern const char *attr_changes; +extern const char *attr_changetime; +extern const char *attr_objectclass; + +extern PRLock *retrocl_internal_lock; + +/* Functions */ + +/* from repl_shared.h: not sure where defined */ +unsigned long strntoul( char *from, size_t len, int base ); + +extern int retrocl_plugin_init(Slapi_PBlock *pb); + +extern int retrocl_bepostop_delete (Slapi_PBlock *pb); +extern int retrocl_postop_add (Slapi_PBlock *pb); +extern int retrocl_postop_delete (Slapi_PBlock *pb); +extern int retrocl_postop_modify (Slapi_PBlock *pb); +extern int retrocl_postop_modrdn (Slapi_PBlock *pb); +extern int retrocl_postob(Slapi_PBlock *,int); + +extern int retrocl_rootdse_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +extern void retrocl_create_cle(void); +extern int retrocl_create_config(void); + +extern changeNumber retrocl_get_first_changenumber(void); +extern void retrocl_set_first_changenumber(changeNumber cn); +extern changeNumber retrocl_get_last_changenumber(void); +extern void retrocl_commit_changenumber(void); +extern void retrocl_release_changenumber(void); +extern changeNumber retrocl_assign_changenumber(void); +extern int retrocl_get_changenumbers(void); +extern void retrocl_forget_changenumbers(void); +extern time_t retrocl_getchangetime( int type, int *err ); + +extern void retrocl_init_trimming(void); +extern void retrocl_stop_trimming(void); +extern char *retrocl_get_config_str(const char *attrt); + +#endif /* _H_RETROCL */ diff --git a/ldap/servers/plugins/retrocl/retrocl.txt b/ldap/servers/plugins/retrocl/retrocl.txt new file mode 100644 index 00000000..e82368e8 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl.txt @@ -0,0 +1,107 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# + +Changelog user documentation +Last Updated October 6, 2000 + +1. Introduction + +This document describes a how DS 6.0 provides a change log broadly +compatible with the Internet Draft draft-good-ldap-changelog-01.txt. + +When enabled, the change log appears in the DIT below cn=changelog. It +consists of a single level of entries, each of class changeLogEntry. This +object class allows the following attributes: + - changeNumber. This attribute is always present and contains a single + value, an integer which is unique for each change. The value for later + changes is larger than those of any change which is already present. + - targetDN. This attribute contains the distinguished name of the entry + which was added, modified or deleted. In the case of a ModifyDN operation, + the targetDN attribute contains the DN of the entry before it was renamed + or moved. + - changeType. This attribute contains one of the following values: "add", + "delete", "modify" or "modrdn". + - changes. This attribute contains the changes made to the entry, in LDIF + format, for a add or modify change. + - newRDN. This attribute contains the new RDN of the entry, for a modifyDN + change. + - deleteOldRDN. This attribute contains whether the old RDN of the entry + was deleted, for a modifyDN change. + - newSuperior. This attribute contains the newSuperior field of the entry, + for a modifyDN change. + +The change log is implemented in an LDBM database. + +2. Configuration + +To enable the change log, the following steps should be performed. First, +change the nsslapd-pluginenabled attribute of the DSE cn=Retrocl Plugin, +cn=plugins,cn=config to "on" instead of "off", Then start or restart the +server. The server will automatically create the change log database. + +3. Trimming + +The entries in the change log may be automatically removed if they are older +than a specified period of time. This is done by setting the +changelogmaximumage attribute in the change log plugin DSE cn=Retrocl Plugin, +cn=plugins,cn=config and restarting the server. If this attribute is not +present, then changed are not trimmed. + +The changelogmaximumage attribute is single-valued, and its value consists of +two parts: a number and a time units code. The time units codes are: + - 's' for seconds, + - 'm' for minutes, + - 'h' for hours, + - 'd' for days, + - 'w' for weeks. + +For example, + +changelogmaximumage: 2d + +The minimum value is 5 minutes. + +4. Access Control + +When the changelog backend is created, the default access control is to allow +anonymous read, search and compare to the changelog base entry, cn=changelog, +by anyone. No access is granted, except implicitly to the Directory Manager, +to any of the entries in the change log. + +Read access to the entries in the change log should not be granted to anonymous +users, as the changes attribute could contain modifications to sensitive +attribute values (such as passwords). Only authenticated services should be +allowed to access this information. + +5. Protocol interaction + +All search and compare operations are supported on the change log database. +Search operations whose filter is of the form +(&(changenumber>=X)(changeNumber<=Y) are optimized. + +Add or modify operations should not be performed on change log entries in the +change log database. Change log entries can be deleted if desired. The +change log base entry, cn=changelog, can be modified if desired, to vary the +access control policy of the change log database. + +6. Caveats + +The change log does not currently record changes which are internally +constructed to resolve conflicts during multi-master replication. As a +result, the change log should not be used in deployments which use multi-master +replication with more than two masters or suppliers for a database. + +== + +root dse firstchangenumber and lastchangenumber + +changelogdir attribute + +test chaining be +if changelog db deleted - what happens? +cannot change trim max age without restarting the server diff --git a/ldap/servers/plugins/retrocl/retrocl_cn.c b/ldap/servers/plugins/retrocl/retrocl_cn.c new file mode 100644 index 00000000..3623f4b3 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl_cn.c @@ -0,0 +1,391 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "retrocl.h" + +static changeNumber retrocl_internal_cn = 0; +static changeNumber retrocl_first_cn = 0; +PRLock *retrocl_internal_lock = NULL; + +/* + * Function: a2changeNumber + * + * Returns: changeNumber (long) + * + * Arguments: string + * + * Description: parses the string to a changenumber. changenumbers are + * positive integers. + * + */ + +static changeNumber a2changeNumber (const char *p) +{ + changeNumber c; + + c = strntoul((char *)p,strlen(p),10); + return c; +} + +/* + * Function: handle_cnum_entry + * Arguments: op - pointer to Operation struct for this operation + * e - pointer to returned entry. + * Returns: nothing + * Description: Search result handler for retrocl_getchangenum(). Sets the + * op->o_handler_data to point to a structure which contains + * the changenumber retrieved and an error code. + */ +static int +handle_cnum_entry( Slapi_Entry *e, void *callback_data ) +{ + cnumRet *cr = (cnumRet *)callback_data; + Slapi_Value *sval=NULL; + const struct berval *value; + + cr->cr_cnum = 0UL; + cr->cr_time = NULL; + + if ( NULL != e ) { + Slapi_Attr *chattr = NULL; + sval = NULL; + value = NULL; + if ( slapi_entry_attr_find( e, attr_changenumber, &chattr ) == 0 ) { + slapi_attr_first_value( chattr,&sval ); + if ( NULL != sval ) { + value = slapi_value_get_berval ( sval ); + if( NULL != value && NULL != value->bv_val && + '\0' != value->bv_val[0]) { + cr->cr_cnum = a2changeNumber( value->bv_val ); + } + } + } + chattr = NULL; + sval = NULL; + value = NULL; + + chattr = NULL; + sval = NULL; + value = NULL; + if ( slapi_entry_attr_find( e, attr_changetime, &chattr ) == 0 ) { + slapi_attr_first_value( chattr,&sval ); + if ( NULL != sval) { + value = slapi_value_get_berval ( sval ); + if (NULL != value && NULL != value->bv_val && + '\0' != value->bv_val[0]) { + cr->cr_time = slapi_ch_strdup( value->bv_val ); + } + } + } + } + return 0; +} + + +/* + * Function: handle_cnum_result + * Arguments: err - error code returned from search + * callback_data - private data for callback + * Returns: nothing + * Description: result handler for retrocl_getchangenum(). Sets the cr_lderr + * field of the cnumRet struct to the error returned + * from the backend. + */ +static void +handle_cnum_result( int err, void *callback_data ) +{ + cnumRet *cr = (cnumRet *)callback_data; + cr->cr_lderr = err; +} + +/* + * Function: retrocl_get_changenumbers + * + * Returns: 0/-1 + * + * Arguments: none + * + * Description: reads the first and last entry in the changelog to obtain + * the starting and ending change numbers. + * + */ + +int retrocl_get_changenumbers(void) +{ + cnumRet cr; + + if (retrocl_internal_lock == NULL) { + retrocl_internal_lock = PR_NewLock(); + + if (retrocl_internal_lock == NULL) return -1; + } + + if (retrocl_be_changelog == NULL) return -1; + + cr.cr_cnum = 0; + cr.cr_time = 0; + + slapi_seq_callback(RETROCL_CHANGELOG_DN,SLAPI_SEQ_FIRST, + (char *)attr_changenumber, /* cast away const */ + NULL,NULL,0,&cr,NULL,handle_cnum_result, + handle_cnum_entry, NULL); + + retrocl_first_cn = cr.cr_cnum; + + slapi_ch_free(( void **) &cr.cr_time ); + + slapi_seq_callback(RETROCL_CHANGELOG_DN,SLAPI_SEQ_LAST, + (char *)attr_changenumber, /* cast away const */ + NULL,NULL,0,&cr,NULL,handle_cnum_result, + handle_cnum_entry, NULL); + + retrocl_internal_cn = cr.cr_cnum; + + slapi_log_error(SLAPI_LOG_PLUGIN,"retrocl","Got changenumbers %d and %d\n", + retrocl_first_cn, + retrocl_internal_cn); + + slapi_ch_free(( void **) &cr.cr_time ); + + return 0; +} + +/* + * Function: retrocl_getchangetime + * Arguments: type - one of SLAPI_SEQ_FIRST, SLAPI_SEQ_LAST + * Returns: The time of the requested change record. If the return value is + * NO_TIME, the changelog could not be read. + * If err is non-NULL, the memory it points to is set the the + * error code returned from the backend. If "type" is not valid, + * *err is set to -1. + * Description: Get the first or last changenumber stored in the changelog, + * depending on the value of argument "type". + */ +time_t retrocl_getchangetime( int type, int *err ) +{ + cnumRet cr; + time_t ret; + + if ( type != SLAPI_SEQ_FIRST && type != SLAPI_SEQ_LAST ) { + if ( err != NULL ) { + *err = -1; + } + return NO_TIME; + } + memset( &cr, '\0', sizeof( cnumRet )); + slapi_seq_callback( RETROCL_CHANGELOG_DN, type, + (char *)attr_changenumber, /* cast away const */ + NULL, + NULL, 0, &cr, NULL, + handle_cnum_result, handle_cnum_entry, NULL ); + + if ( err != NULL ) { + *err = cr.cr_lderr; + } + + if ( NULL == cr.cr_time ) { + ret = NO_TIME; + } else { + ret = parse_localTime( cr.cr_time ); + } + slapi_ch_free(( void **) &cr.cr_time ); + return ret; +} + +/* + * Function: retrocl_forget_changenumbers + * + * Returns: none + * + * Arguments: none + * + * Description: used only when the server is shutting down + * + */ + +void retrocl_forget_changenumbers(void) +{ + if (retrocl_internal_lock == NULL) return; + + PR_Lock(retrocl_internal_lock); + retrocl_first_cn = 0; + retrocl_internal_cn = 0; + PR_Unlock(retrocl_internal_lock); +} + +/* + * Function: retrocl_get_first_changenumber + * + * Returns: changeNumber + * + * Arguments: none + * + * Description: used in root DSE + * + */ + +changeNumber retrocl_get_first_changenumber(void) +{ + changeNumber cn; + PR_Lock(retrocl_internal_lock); + cn = retrocl_first_cn; + PR_Unlock(retrocl_internal_lock); + return cn; +} + +/* + * Function: retrocl_set_first_changenumber + * + * Returns: none + * + * Arguments: changenumber + * + * Description: used in changelog trimming + * + */ + +void retrocl_set_first_changenumber(changeNumber cn) +{ + PR_Lock(retrocl_internal_lock); + retrocl_first_cn = cn; + PR_Unlock(retrocl_internal_lock); +} + + +/* + * Function: retrocl_get_last_changenumber + * + * Returns: + * + * Arguments: + * + * Description: used in root DSE + * + */ + +changeNumber retrocl_get_last_changenumber(void) +{ + changeNumber cn; + PR_Lock(retrocl_internal_lock); + cn = retrocl_internal_cn; + PR_Unlock(retrocl_internal_lock); + return cn; +} + +/* + * Function: retrocl_commit_changenumber + * + * Returns: none + * + * Arguments: none, lock must be held + * + * Description: NOTE! MUST BE PRECEEDED BY retrocl_assign_changenumber + * + */ + +void retrocl_commit_changenumber(void) +{ + if ( retrocl_first_cn == 0) { + retrocl_first_cn = retrocl_internal_cn; + } +} + +/* + * Function: retrocl_release_changenumber + * + * Returns: none + * + * Arguments: none, lock must be held + * + * Description: NOTE! MUST BE PRECEEDED BY retrocl_assign_changenumber + * + */ + +void retrocl_release_changenumber(void) +{ + retrocl_internal_cn--; +} + +/* + * Function: retrocl_update_lastchangenumber + * + * Returns: 0/-1 + * + * Arguments: none + * + * Description: reads the last entry in the changelog to obtain + * the last change number. + * + */ + +int retrocl_update_lastchangenumber(void) +{ + cnumRet cr; + + if (retrocl_internal_lock == NULL) { + retrocl_internal_lock = PR_NewLock(); + + if (retrocl_internal_lock == NULL) return -1; + } + + if (retrocl_be_changelog == NULL) return -1; + + cr.cr_cnum = 0; + cr.cr_time = 0; + slapi_seq_callback(RETROCL_CHANGELOG_DN,SLAPI_SEQ_LAST, + (char *)attr_changenumber, /* cast away const */ + NULL,NULL,0,&cr,NULL,handle_cnum_result, + handle_cnum_entry, NULL); + + + retrocl_internal_cn = cr.cr_cnum; + slapi_log_error(SLAPI_LOG_PLUGIN,"retrocl","Refetched last changenumber = %d \n", + retrocl_internal_cn); + + slapi_ch_free(( void **) &cr.cr_time ); + + return 0; +} + + + +/* + * Function: retrocl_assign_changenumber + * + * Returns: change number, 0 on error + * + * Arguments: none. Lock must be held. + * + * Description: NOTE! MUST BE FOLLOWED BY retrocl_commit_changenumber or + * retrocl_release_changenumber + * + */ + +changeNumber retrocl_assign_changenumber(void) +{ + changeNumber cn; + + if (retrocl_internal_lock == NULL) return 0; + + /* Before we assign the changenumber; we should check for the + * validity of the internal assignment of retrocl_internal_cn + * we had from the startup */ + + if(retrocl_internal_cn <= retrocl_first_cn){ + /* the numbers have become out of sync - retrocl_get_changenumbers + * gets called only once during startup and it may have had a problem + * getting the last changenumber. + * If there was any problem then update the lastchangenumber from the changelog db. + * This function is being called by only the thread that is actually writing + * to the changelog. + */ + retrocl_update_lastchangenumber(); + } + + retrocl_internal_cn++; + cn = retrocl_internal_cn; + return cn; +} diff --git a/ldap/servers/plugins/retrocl/retrocl_create.c b/ldap/servers/plugins/retrocl/retrocl_create.c new file mode 100644 index 00000000..fbda8e36 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl_create.c @@ -0,0 +1,317 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "retrocl.h" + +/* +The changelog is created by + - changing the node in the dse tree which represents the changelog plugin + to enabled on, + - shutting down the server, + - starting the server. + */ + +/******************************/ + +/* + * Function: retrocl_create_be + * + * Returns: LDAP_ + * + * Arguments: location in file system to put changelog, or NULL for default + * + * Description: + * add an entry of class nsBackendInstance below cn=ldbm,cn=plugins,cn=config + * + */ + +static int retrocl_create_be(const char *bedir) +{ + Slapi_PBlock *pb = NULL; + Slapi_Entry *e; + struct berval *vals[2]; + struct berval val; + int rc; + + vals[0] = &val; + vals[1] = NULL; + + e = slapi_entry_alloc(); + slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_LDBM_DN)); + + /* Set the objectclass attribute */ + val.bv_val = "top"; + val.bv_len = 3; + slapi_entry_add_values( e, "objectclass", vals ); + + /* Set the objectclass attribute */ + val.bv_val = "extensibleObject"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + /* Set the objectclass attribute */ + val.bv_val = "nsBackendInstance"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + val.bv_val = "changelog"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "cn", vals ); + + val.bv_val = RETROCL_BE_CACHESIZE; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-cachesize", vals ); + + val.bv_val = RETROCL_CHANGELOG_DN; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-suffix", vals ); + + val.bv_val = RETROCL_BE_CACHEMEMSIZE; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-cachememsize", vals ); + + val.bv_val = "off"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-readonly", vals ); + + if (bedir) { + val.bv_val = (char *)bedir; /* cast const */ + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-directory", vals ); + } + + pb = slapi_pblock_new (); + slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */, + g_plg_identity[PLUGIN_RETROCL], + 0 /* actions */ ); + slapi_add_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + slapi_pblock_destroy(pb); + + if (rc == 0) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "created changelog database node\n"); + } else if (rc == LDAP_ALREADY_EXISTS) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "changelog database node already existed\n"); + } else { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "Changelog LDBM backend could not be created (%d)\n", rc); + return rc; + } + + + /* we need the changenumber indexed */ + e = slapi_entry_alloc(); + slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_INDEX_DN)); + + /* Set the objectclass attribute */ + val.bv_val = "top"; + val.bv_len = 3; + slapi_entry_add_values( e, "objectclass", vals ); + + /* Set the objectclass attribute */ + val.bv_val = "nsIndex"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + val.bv_val = "changenumber"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "cn", vals ); + + val.bv_val = "false"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nssystemindex", vals ); + + val.bv_val = "eq"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsindextype", vals ); + + pb = slapi_pblock_new (); + slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */, + g_plg_identity[PLUGIN_RETROCL], + 0 /* actions */ ); + slapi_add_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + slapi_pblock_destroy(pb); + + if (rc == 0) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "created changenumber index node\n"); + } else if (rc == LDAP_ALREADY_EXISTS) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "changelog index node already existed\n"); + } else { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "Changelog LDBM backend changenumber index could not be created (%d)\n", rc); + return rc; + } + + return rc; +} + +/* + * Function: retrocl_create_config + * + * Returns: LDAP_ + * + * Arguments: none + * + * Description: + * This function is called if there was no mapping tree node or backend for + * cn=changelog. + */ +int retrocl_create_config(void) +{ + Slapi_PBlock *pb = NULL; + Slapi_Entry *e; + struct berval *vals[2]; + struct berval val; + int rc; + + vals[0] = &val; + vals[1] = NULL; + + /* Assume the mapping tree node is missing. It doesn't hurt to + * attempt to add it if it already exists. You will see a warning + * in the errors file when the referenced backend does not exist. + */ + e = slapi_entry_alloc(); + slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_MAPPINGTREE_DN)); + + /* Set the objectclass attribute */ + val.bv_val = "top"; + val.bv_len = 3; + slapi_entry_add_values( e, "objectclass", vals ); + + + /* Set the objectclass attribute */ + val.bv_val = "extensibleObject"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + /* Set the objectclass attribute */ + val.bv_val = "nsMappingTree"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + val.bv_val = "backend"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-state", vals ); + + val.bv_val = RETROCL_CHANGELOG_DN; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "cn", vals ); + + val.bv_val = "changelog"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "nsslapd-backend", vals ); + + pb = slapi_pblock_new (); + slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */, + g_plg_identity[PLUGIN_RETROCL], + 0 /* actions */ ); + slapi_add_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + slapi_pblock_destroy(pb); + + if (rc == 0) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "created changelog mapping tree node\n"); + } else if (rc == LDAP_ALREADY_EXISTS) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "changelog mapping tree node already existed\n"); + } else { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "cn=\"cn=changelog\",cn=mapping tree,cn=config could not be created (%d)\n", rc); + return rc; + } + + retrocl_be_changelog = slapi_be_select_by_instance_name("changelog"); + + if (retrocl_be_changelog == NULL) { + /* This is not the nsslapd-changelogdir from cn=changelog4,cn=config */ + char *bedir; + + bedir = retrocl_get_config_str(CONFIG_CHANGELOG_DIRECTORY_ATTRIBUTE); + + if (bedir == NULL) { + /* none specified */ + } + + rc = retrocl_create_be(bedir); + slapi_ch_free ((void **)&bedir); + if (rc != LDAP_SUCCESS && rc != LDAP_ALREADY_EXISTS) { + return rc; + } + + retrocl_be_changelog = slapi_be_select_by_instance_name("changelog"); + } + + return LDAP_SUCCESS; +} + +/******************************/ + +/* Function: retrocl_create_cle + * + * Arguments: none + * Returns: nothing + * Description: Attempts to create the cn=changelog entry which might already + * exist. + */ + +void retrocl_create_cle (void) +{ + Slapi_PBlock *pb = NULL; + Slapi_Entry *e; + int rc; + struct berval *vals[2]; + struct berval val; + + vals[0] = &val; + vals[1] = NULL; + + e = slapi_entry_alloc(); + slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_CHANGELOG_DN)); + + /* Set the objectclass attribute */ + val.bv_val = "top"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + + /* Set the objectclass attribute */ + val.bv_val = "nsContainer"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "objectclass", vals ); + + + /* Set the objectclass attribute */ + val.bv_val = "changelog"; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "cn", vals ); + + val.bv_val = RETROCL_ACL; + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values( e, "aci", vals ); + + pb = slapi_pblock_new (); + slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */, + g_plg_identity[PLUGIN_RETROCL], + 0 /* actions */ ); + slapi_add_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + slapi_pblock_destroy(pb); + + if (rc == 0) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "created cn=changelog\n"); + } else if (rc == LDAP_ALREADY_EXISTS) { + slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "cn=changelog already existed\n"); + } else { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "cn=changelog could not be created (%d)\n", rc); + } +} + diff --git a/ldap/servers/plugins/retrocl/retrocl_po.c b/ldap/servers/plugins/retrocl/retrocl_po.c new file mode 100644 index 00000000..96512a51 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl_po.c @@ -0,0 +1,529 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "retrocl.h" + +static int +entry2reple( Slapi_Entry *e, Slapi_Entry *oe ); + +static int +mods2reple( Slapi_Entry *e, LDAPMod **ldm ); + +static int +modrdn2reple( Slapi_Entry *e, const char *newrdn, int deloldrdn, + LDAPMod **ldm, const char *newsup ); + +/******************************/ + +const char *attr_changenumber = "changenumber"; +const char *attr_targetdn = "targetdn"; +const char *attr_changetype = "changetype"; +const char *attr_newrdn = "newrdn"; +const char *attr_deleteoldrdn = "deleteoldrdn"; +const char *attr_changes = "changes"; +const char *attr_newsuperior = "newsuperior"; +const char *attr_changetime = "changetime"; +const char *attr_objectclass = "objectclass"; + +/* + * Function: make_changes_string + * + * Returns: + * + * Arguments: + * + * Description: + * loop through the LDAPMod struct and construct the changes attribute/ + * + */ + +static lenstr *make_changes_string(LDAPMod **ldm, const char **includeattrs) +{ + lenstr *l; + int i, j, len; + int skip; + + l = lenstr_new(); + + for ( i = 0; ldm[ i ] != NULL; i++ ) { + /* If a list of explicit attributes was given, only add those */ + if ( NULL != includeattrs ) { + skip = 1; + for ( j = 0; includeattrs[ j ] != NULL; j++ ) { + if ( strcasecmp( includeattrs[ j ], ldm[ i ]->mod_type ) == 0 ) { + skip = 0; + break; + } + } + if ( skip ) { + continue; + } + } + switch ( ldm[ i ]->mod_op & ~LDAP_MOD_BVALUES ) { + case LDAP_MOD_ADD: + addlenstr( l, "add: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + case LDAP_MOD_DELETE: + addlenstr( l, "delete: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + case LDAP_MOD_REPLACE: + addlenstr( l, "replace: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + } + for ( j = 0; ldm[ i ]->mod_bvalues != NULL && + ldm[ i ]->mod_bvalues[ j ] != NULL; j++ ) { + char *buf = NULL; + char *bufp = NULL; + + len = strlen( ldm[ i ]->mod_type ); + len = LDIF_SIZE_NEEDED( len, + ldm[ i ]->mod_bvalues[ j ]->bv_len ) + 1; + buf = slapi_ch_malloc( len ); + bufp = buf; + ldif_put_type_and_value( &bufp, ldm[ i ]->mod_type, + ldm[ i ]->mod_bvalues[ j ]->bv_val, + ldm[ i ]->mod_bvalues[ j ]->bv_len ); + *bufp = '\0'; + + addlenstr( l, buf ); + + free( buf ); + } + addlenstr( l, "-\n" ); + } + return l; +} + +/* + * Function: write_replog_db + * Arguments: be - backend to which this change is being applied + * optype - type of LDAP operation being logged + * dn - distinguished name of entry being changed + * log_m - pointer to the actual change operation on a modify + * flag - only used by modrdn operations - value of deleteoldrdn + * curtime - the current time + * Returns: nothing + * Description: Given a change, construct an entry which is to be added to the + * changelog database. + */ +static void +write_replog_db( + int optype, + char *dn, + LDAPMod **log_m, + int flag, + time_t curtime, + Slapi_Entry *log_e, + const char *newrdn, + LDAPMod **modrdn_mods, + const char *newsuperior +) +{ + char *pat, *edn; + struct berval *vals[ 2 ]; + struct berval val; + Slapi_Entry *e; + char chnobuf[ 20 ]; + int err; + Slapi_PBlock *pb = NULL; + changeNumber changenum; + + PR_Lock(retrocl_internal_lock); + changenum = retrocl_assign_changenumber(); + + PR_ASSERT( changenum > 0UL ); + slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "write_replog_db: write change record %d for dn: \"%s\"\n", + changenum, ( dn == NULL ) ? "NULL" : dn ); + + /* Construct the dn of this change record */ + pat = "%s=%lu,%s"; + edn = slapi_ch_malloc( strlen( pat ) + strlen( RETROCL_CHANGELOG_DN) + 20 ); + sprintf( edn, pat, attr_changenumber, changenum, RETROCL_CHANGELOG_DN); + + /* + * Create the entry struct, and fill in fields common to all types + * of change records. + */ + vals[ 0 ] = &val; + vals[ 1 ] = NULL; + + e = slapi_entry_alloc(); + slapi_entry_set_dn( e, slapi_ch_strdup( edn )); + + /* Set the objectclass attribute */ + val.bv_val = "top"; + val.bv_len = 3; + slapi_entry_add_values( e, "objectclass", vals ); + + val.bv_val = "changelogentry"; + val.bv_len = 14; + slapi_entry_add_values( e, "objectclass", vals ); + + /* Set the changeNumber attribute */ + sprintf( chnobuf, "%lu", changenum ); + val.bv_val = chnobuf; + val.bv_len = strlen( chnobuf ); + slapi_entry_add_values( e, attr_changenumber, vals ); + + /* Set the targetentrydn attribute */ + val.bv_val = dn; + val.bv_len = strlen( dn ); + slapi_entry_add_values( e, attr_targetdn, vals ); + + /* Set the changeTime attribute */ + val.bv_val = format_genTime (curtime); + val.bv_len = strlen( val.bv_val ); + slapi_entry_add_values( e, attr_changetime, vals ); + free( val.bv_val ); + + /* + * Finish constructing the entry. How to do it depends on the type + * of modification being logged. + */ + err = 0; + switch ( optype ) { + case OP_ADD: + if ( entry2reple( e, log_e ) != 0 ) { + err = 1; + } + break; + + case OP_MODIFY: + if ( mods2reple( e, log_m ) != 0 ) { + err = 1; + } + break; + + case OP_MODRDN: + if ( modrdn2reple( e, newrdn, flag, modrdn_mods, newsuperior ) != 0 ) { + err = 1; + } + break; + + case OP_DELETE: + /* Set the changetype attribute */ + val.bv_val = "delete"; + val.bv_len = 6; + slapi_entry_add_values( e, attr_changetype, vals ); + break; + default: + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "replog: Unknown LDAP operation type " + "%d.\n", optype ); + err = 1; + } + + /* Call the repl backend to add this entry */ + if ( 0 == err ) { + int rc; + + pb = slapi_pblock_new (); + slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */, + g_plg_identity[PLUGIN_RETROCL], + 0 /* actions */ ); + slapi_add_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + slapi_pblock_destroy(pb); + if ( 0 != rc ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, + "replog: an error occured while adding change " + "number %d, dn = %s: %s. \n", + changenum, edn, ldap_err2string( rc )); + retrocl_release_changenumber(); + } else { + /* Tell the change numbering system this one's committed to disk */ + retrocl_commit_changenumber( ); + } + } else { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, + "An error occurred while constructing " + "change record number %ld.\n", changenum ); + retrocl_release_changenumber(); + } + PR_Unlock(retrocl_internal_lock); + if ( NULL != edn ) { + slapi_ch_free((void **) &edn); + } + +} + + +/* + * Function: entry2reple + * Arguments: e - a partially-constructed Slapi_Entry structure + * oe - the original entry (an entry being added by a client). + * Returns: 0 on success. + * Description: Given an Slapi_Entry struct, construct a changelog entry which will be + * added to the replication database. It is assumed that e points + * to an entry obtained from slapi_entry_alloc(). + */ +static int +entry2reple( Slapi_Entry *e, Slapi_Entry *oe ) +{ + char *p, *estr; + struct berval *vals[ 2 ]; + struct berval val; + int len; + + vals[ 0 ] = &val; + vals[ 1 ] = NULL; + + /* Set the changetype attribute */ + val.bv_val = "add"; + val.bv_len = 3; + slapi_entry_add_values( e, attr_changetype, vals ); + + estr = slapi_entry2str( oe, &len ); + p = estr; + /* Skip over the dn: line */ + while (( p = strchr( p, '\n' )) != NULL ) { + p++; + if ( !ldap_utf8isspace( p )) { + break; + } + } + val.bv_val = p; + val.bv_len = len - ( p - estr ); /* length + terminating \0 */ + slapi_entry_add_values( e, attr_changes, vals ); + free( estr ); + return 0; +} + +/* + * Function: mods2reple + * Arguments: e - a partially-constructed Slapi_Entry structure + * ldm - an array of pointers to LDAPMod structures describing the + * change applied. + * Returns: 0 on success. + * Description: Given a pointer to an LDAPMod struct and a dn, construct + * a new entry which will be added to the replication database. + * It is assumed that e points to an entry obtained from + * slapi_entry_alloc(). + */ +static int +mods2reple( Slapi_Entry *e, LDAPMod **ldm ) +{ + struct berval val; + struct berval *vals[ 2 ]; + lenstr *l; + + vals[ 0 ] = &val; + vals[ 1 ] = NULL; + + /* Set the changetype attribute */ + val.bv_val = "modify"; + val.bv_len = 6; + slapi_entry_add_values( e, "changetype", vals ); + + if (NULL != ldm) { + l = make_changes_string( ldm, NULL ); + if ( NULL != l ) { + val.bv_val = l->ls_buf; + val.bv_len = l->ls_len + 1; /* string + terminating \0 */ + slapi_entry_add_values( e, attr_changes, vals ); + lenstr_free( &l ); + } + } + return 0; +} + + +/* + * Function: modrdn2reple + * Arguments: e - a partially-constructed Slapi_Entry structure + * newrdn - the new relative distinguished name for the entry + * deloldrdn - the "deleteoldrdn" flag provided by the client + * ldm - any modifications applied as a side-effect of the modrdn + * Returns: 0 on success + * Description: Given a dn, a new rdn, and a deleteoldrdn flag, construct + * a new entry which will be added to the replication database reflecting a + * completed modrdn operation. The entry has the same form as above. + * It is assumed that e points to an entry obtained from slapi_entry_alloc(). + */ +static int +modrdn2reple( + Slapi_Entry *e, + const char *newrdn, + int deloldrdn, + LDAPMod **ldm, + const char *newsuperior +) +{ + struct berval val; + struct berval *vals[ 2 ]; + lenstr *l; + static const char *lastmodattrs[] = {"modifiersname", "modifytimestamp", + "creatorsname", "createtimestamp", + NULL }; + + vals[ 0 ] = &val; + vals[ 1 ] = NULL; + + val.bv_val = "modrdn"; + val.bv_len = 6; + slapi_entry_add_values( e, attr_changetype, vals ); + + if (newrdn) { + val.bv_val = (char *)newrdn; /* cast away const */ + val.bv_len = strlen( newrdn ); + slapi_entry_add_values( e, attr_newrdn, vals ); + } + + if ( deloldrdn == 0 ) { + val.bv_val = "FALSE"; + val.bv_len = 5; + } else { + val.bv_val = "TRUE"; + val.bv_len = 4; + } + slapi_entry_add_values( e, attr_deleteoldrdn, vals ); + + if (newsuperior) { + val.bv_val = (char *)newsuperior; /* cast away const */ + val.bv_len = strlen(newsuperior); + slapi_entry_add_values(e, attr_newsuperior,vals); + } + + if (NULL != ldm) { + l = make_changes_string( ldm, lastmodattrs ); + if ( NULL != l ) { + val.bv_val = l->ls_buf; + val.bv_len = l->ls_len + 1; /* string + terminating \0 */ + slapi_entry_add_values( e, attr_changes, vals ); + lenstr_free( &l ); + } + } + + return 0; +} + +/* + * Function: retrocl_postob + * + * Returns: 0 on success + * + * Arguments: pblock, optype (add, del, modify etc) + * + * Description: called from retrocl.c op-specific plugins. + * + * Please be aware that operation threads may be scheduled out between their + * being performed inside of the LDBM database and the changelog plugin + * running. For example, suppose MA and MB are two modify operations on the + * same entry. MA may be performed on the LDBM database, and then block + * before the changelog runs. MB then runs against the LDBM database and then + * is written to the changelog. MA starts running. In the changelog, MB will + * appear to have been performed before MA, but in the LDBM database the + * opposite will have occured. + * + * + */ + +int retrocl_postob (Slapi_PBlock *pb,int optype) +{ + char *dn; + LDAPMod **log_m = NULL; + int flag = 0; + Slapi_Entry *te = NULL; + Slapi_Operation *op = NULL; + LDAPMod **modrdn_mods = NULL; + char *newrdn = NULL; + char *newsuperior = NULL; + Slapi_Backend *be = NULL; + time_t curtime; + int rc; + + /* + * Check to see if the change was made to the replication backend db. + * If so, don't try to log it to the db (otherwise, we'd get in a loop). + */ + + (void)slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + + if (slapi_be_logchanges(be) == 0) { + LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if not logging\n", + 0,0,0); + return 0; + } + + if (retrocl_be_changelog == NULL || be == retrocl_be_changelog) { + LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if no/cl be\n",0,0,0); + return 0; + } + + slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc); + + if (rc != LDAP_SUCCESS) { + LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if op failed %d\n",rc, + 0,0); + return 0; + } + + if (slapi_op_abandoned(pb)) { + LDAPDebug(LDAP_DEBUG_PLUGIN,"not applying change if op abandoned\n", + 0,0,0); + return 0; + } + + curtime = current_time(); + + (void)slapi_pblock_get( pb, SLAPI_ORIGINAL_TARGET_DN, &dn ); + + /* change number could be retrieved from Operation extension stored in + * the pblock, or else it needs to be made dynamically. */ + + /* get the operation extension and retrieve the change number */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + + if (op == NULL) { + LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if no op\n",0,0,0); + return 0; + } + + if (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY)){ + LDAPDebug(LDAP_DEBUG_TRACE,"not applying change for nsTombstone entries\n",0,0,0); + return 0; + } + + switch ( optype ) { + case OP_MODIFY: + (void)slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &log_m ); + break; + case OP_ADD: + /* + * For adds, we want the unnormalized dn, so we can preserve + * spacing, case, when replicating it. + */ + (void)slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &te ); + if ( NULL != te ) { + dn = slapi_entry_get_dn( te ); + } + break; + case OP_DELETE: + break; + case OP_MODRDN: + (void)slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ); + (void)slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &flag ); + (void)slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &modrdn_mods ); + (void)slapi_pblock_get( pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperior); + break; + } + + + /* check if we should log change to retro changelog, and + * if so, do it here */ + write_replog_db( optype, dn, log_m, flag, curtime, te, + newrdn, modrdn_mods, newsuperior ); + + return 0; +} + + diff --git a/ldap/servers/plugins/retrocl/retrocl_rootdse.c b/ldap/servers/plugins/retrocl/retrocl_rootdse.c new file mode 100644 index 00000000..602858d3 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl_rootdse.c @@ -0,0 +1,64 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "retrocl.h" + + + +/* + * Function: retrocl_rootdse_search + * + * Returns: SLAPI_DSE_CALLBACK_OK always + * + * Arguments: See plugin API + * + * Description: callback function plugged into base object search of root DSE. + * Adds changelog, firstchangenumber and lastchangenumber attributes. + * + */ + +int +retrocl_rootdse_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + + struct berval val; + struct berval *vals[2]; + vals[0] = &val; + vals[1] = NULL; + + /* Changelog information */ + if ( retrocl_be_changelog != NULL ) + { + char buf[BUFSIZ]; + changeNumber cnum; + + /* Changelog suffix */ + val.bv_val = RETROCL_CHANGELOG_DN; + if ( val.bv_val != NULL ) + { + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "changelog", vals ); + } + + /* First change number contained in log */ + cnum = retrocl_get_first_changenumber(); + sprintf( buf, "%lu", cnum ); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "firstchangenumber", vals ); + + /* Last change number contained in log */ + cnum = retrocl_get_last_changenumber(); + sprintf( buf, "%lu", cnum ); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "lastchangenumber", vals ); + } + + return SLAPI_DSE_CALLBACK_OK; +} + + diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c new file mode 100644 index 00000000..5c413097 --- /dev/null +++ b/ldap/servers/plugins/retrocl/retrocl_trim.c @@ -0,0 +1,505 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + +#include "retrocl.h" + +typedef struct _trim_status { + time_t ts_c_max_age; /* Constraint - max age of a changelog entry */ + time_t ts_s_last_trim; /* Status - last time we trimmed */ + int ts_s_initialized; /* Status - non-zero if initialized */ + int ts_s_trimming; /* non-zero if trimming in progress */ + PRLock *ts_s_trim_mutex; /* protects ts_s_trimming */ +} trim_status; +static trim_status ts = {0L, 0L, 0, 0, NULL}; + +/* + * All standard changeLogEntry attributes (initialized in get_cleattrs) + */ +static const char *cleattrs[ 10 ] = { NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL }; + +static int retrocl_trimming = 0; +static Slapi_Eq_Context retrocl_trim_ctx = NULL; + +/* + * Function: get_cleattrs + * + * Returns: an array of pointers to attribute names. + * + * Arguments: None. + * + * Description: Initializes, if necessary, and returns an array of char *s + * with attribute names used for retrieving changeLogEntry + * entries from the directory. + */ +static const char **get_cleattrs(void) +{ + if ( cleattrs[ 0 ] == NULL ) { + cleattrs[ 0 ] = attr_objectclass; + cleattrs[ 1 ] = attr_changenumber; + cleattrs[ 2 ] = attr_targetdn; + cleattrs[ 3 ] = attr_changetype; + cleattrs[ 4 ] = attr_newrdn; + cleattrs[ 5 ] = attr_deleteoldrdn; + cleattrs[ 6 ] = attr_changes; + cleattrs[ 7 ] = attr_newsuperior; + cleattrs[ 8 ] = attr_changetime; + cleattrs[ 9 ] = NULL; + } + return cleattrs; +} + +/* + * Function: delete_changerecord + * + * Returns: LDAP_ error code + * + * Arguments: the number of the change to delete + * + * Description: + * + */ + +static int +delete_changerecord( changeNumber cnum ) +{ + Slapi_PBlock *pb; + char *dnbuf; + int delrc; + + dnbuf = slapi_ch_malloc( strlen( attr_changenumber ) + 20 + + strlen(RETROCL_CHANGELOG_DN)); + /* Delete the record */ + sprintf( dnbuf, "%s=%ld, %s", attr_changenumber, cnum, + RETROCL_CHANGELOG_DN); + pb = slapi_pblock_new (); + slapi_delete_internal_set_pb ( pb, dnbuf, NULL /*controls*/, NULL /* uniqueid */, + g_plg_identity[PLUGIN_RETROCL], 0 /* actions */ ); + slapi_delete_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &delrc ); + slapi_pblock_destroy( pb ); + + if ( delrc != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "delete_changerecord: could not delete " + "change record %d\n", cnum ); + } else { + slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "delete_changerecord: deleted changelog entry \"%s\"\n", dnbuf); + } + slapi_ch_free((void **) &dnbuf ); + return delrc; +} + +/* + * Function: handle_getchangerecord_result + * Arguments: op - pointer to Operation struct for this operation + * err - error code returned from search + * Returns: nothing + * Description: result handler for get_changerecord(). Sets the crt_err + * field of the cnum_result_t struct to the error returned + * from the backend. + */ +static void +handle_getchangerecord_result( int err, void *callback_data ) +{ + cnum_result_t *crt = callback_data; + + if ( crt == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, + "handle_getchangerecord_result: callback_data NULL\n" ); + } else { + crt->crt_err = err; + } +} + +/* + * Function: handle_getchangerecord_search + * Arguments: op - pointer to Operation struct for this operation + * e - entry returned by backend + * Returns: 0 in all cases + * Description: Search result operation handler for get_changerecord(). + * Sets fields in the cnum_result_t struct pointed to by + * op->o_handler_data. + */ +static int +handle_getchangerecord_search( Slapi_Entry *e, void *callback_data) +{ + cnum_result_t *crt = callback_data; + + if ( crt == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, + "handle_getchangerecord_search: op->o_handler_data NULL\n" ); + } else if ( crt->crt_nentries > 0 ) { + /* only return the first entry, I guess */ + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, + "handle_getchangerecord_search: multiple entries returned\n" ); + } else { + crt->crt_nentries++; + crt->crt_entry = e; + } + + return 0; +} + + +/* + * Function: get_changerecord + * Arguments: cnum - number of change record to retrieve + * Returns: Pointer to an entry structure. The caller must free the entry. + * If "err" is non-NULL, an error code is returned in the memory + * location it points to. + * Description: Retrieve the change record entry whose number is "cnum". + */ +static Slapi_Entry *get_changerecord( changeNumber cnum, int *err ) +{ + cnum_result_t crt, *crtp = &crt; + char fstr[ 16 + CNUMSTR_LEN + 2 ]; + Slapi_PBlock *pb; + + if ( cnum == 0UL ) { + if ( err != NULL ) { + *err = LDAP_PARAM_ERROR; + } + return NULL; + } + crtp->crt_nentries = crtp->crt_err = 0; crtp->crt_entry = NULL; + sprintf( fstr, "%s=%ld", attr_changenumber, cnum ); + + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, RETROCL_CHANGELOG_DN, + LDAP_SCOPE_SUBTREE, fstr, + (char **)get_cleattrs(), /* cast const */ + 0 /* attrsonly */, + NULL /* controls */, NULL /* uniqueid */, + g_plg_identity[PLUGIN_RETROCL], + 0 /* actions */); + + slapi_search_internal_callback_pb (pb, crtp, + handle_getchangerecord_result, + handle_getchangerecord_search, NULL ); + if ( err != NULL ) { + *err = crtp->crt_err; + } + + slapi_pblock_destroy (pb); + + return( crtp->crt_entry ); +} + +/* + * Function: trim_changelog + * + * Arguments: none + * + * Returns: 0 on success, -1 on failure + * + * Description: Trims the changelog, according to the constraints + * described by the ts structure. + */ +static int trim_changelog(void) +{ + int rc = 0, ldrc, done; + time_t now; + changeNumber first_in_log = 0, last_in_log = 0; + Slapi_Entry *e = NULL; + int num_deleted = 0; + int me,lt; + + + now = current_time(); + + PR_Lock( ts.ts_s_trim_mutex ); + me = ts.ts_c_max_age; + lt = ts.ts_s_last_trim; + PR_Unlock( ts.ts_s_trim_mutex ); + + if ( now - lt >= (CHANGELOGDB_TRIM_INTERVAL / 1000) ) { + + /* + * Trim the changelog. Read sequentially through all the + * entries, deleting any which do not meet the criteria + * described in the ts structure. + */ + done = 0; + + while ( !done && retrocl_trimming == 1 ) { + int did_delete; + Slapi_Attr *attr; + + did_delete = 0; + first_in_log = retrocl_get_first_changenumber(); + if ( 0UL == first_in_log ) { + slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "trim_changelog: no changelog records " + "to trim\n" ); + /* Bail out - we can't do any useful work */ + break; + } + + last_in_log = retrocl_get_last_changenumber(); + if ( last_in_log == first_in_log ) { + /* Always leave at least one entry in the change log */ + break; + } + if ( me > 0L ) { + e = get_changerecord( first_in_log, &ldrc ); + if ( NULL != e ) { + Slapi_Value *sval = NULL; + const struct berval *val = NULL; + rc = slapi_entry_attr_find( e, attr_changetime, &attr ); + /* Bug 624442: Logic checking for lack of timestamp was + reversed. */ + if ( 0 != rc || slapi_attr_first_value( attr,&sval ) == -1 || + (val = slapi_value_get_berval ( sval )) == NULL || + NULL == val->bv_val ) { + /* What to do if there's no timestamp? Just delete it. */ + retrocl_set_first_changenumber( first_in_log + 1 ); + ldrc = delete_changerecord( first_in_log ); + num_deleted++; + did_delete = 1; + } else { + time_t change_time = parse_localTime( val->bv_val ); + if ( change_time + me < now ) { + retrocl_set_first_changenumber( first_in_log + 1 ); + ldrc = delete_changerecord( first_in_log ); + num_deleted++; + did_delete = 1; + } + /* slapi_entry_free( e ); */ /* XXXggood should we be freeing this? */ + } + } + } + if ( !did_delete ) { + done = 1; + } + } + } else { + LDAPDebug(LDAP_DEBUG_PLUGIN, "not yet time to trim: %d < (%d+%d)\n", + now,lt,(CHANGELOGDB_TRIM_INTERVAL/1000)); + } + PR_Lock( ts.ts_s_trim_mutex ); + ts.ts_s_trimming = 0; + ts.ts_s_last_trim = now; + PR_Unlock( ts.ts_s_trim_mutex ); + if ( num_deleted > 0 ) { + slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, + "trim_changelog: removed %d change records\n", + num_deleted ); + } + return rc; +} + +static int retrocl_active_threads; + +/* + * Function: changelog_trim_thread_fn + * + * Returns: nothing + * + * Arguments: none + * + * Description: the thread creation callback. retrocl_active_threads is + * provided for debugging purposes. + * + */ + +static void +changelog_trim_thread_fn( void *arg ) +{ + PR_AtomicIncrement(&retrocl_active_threads); + trim_changelog(); + PR_AtomicDecrement(&retrocl_active_threads); +} + + + +/* + * Function: retrocl_housekeeping + * Arguments: cur_time - the current time + * Returns: nothing + * Description: Determines if it is time to trim the changelog database, + * and if so, determines if the changelog database needs to + * be trimmed. If so, a thread is started which will trim + * the database. + */ + +void retrocl_housekeeping ( time_t cur_time, void *noarg ) +{ + static time_t thread_start_time; + int ldrc; + + if (retrocl_be_changelog == NULL) { + LDAPDebug(LDAP_DEBUG_TRACE,"not housekeeping if no cl be\n",0,0,0); + return; + } + + if ( !ts.ts_s_initialized ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "changelog_housekeeping called before " + "trimming constraints set\n" ); + return; + } + + PR_Lock( ts.ts_s_trim_mutex ); + if ( !ts.ts_s_trimming ) { + int must_trim = 0; + /* See if we need to trim */ + /* Has enough time elapsed since our last check? */ + if ( cur_time - ts.ts_s_last_trim >= (ts.ts_c_max_age) ) { + /* Is the first entry too old? */ + time_t first_time; + /* + * good we could avoid going to the database to retrieve + * this time information if we cached the last value we'd read. + * But a client might have deleted it over protocol. + */ + first_time = retrocl_getchangetime( SLAPI_SEQ_FIRST, &ldrc ); + LDAPDebug(LDAP_DEBUG_PLUGIN, + "cltrim: ldrc=%d, first_time=%d, cur_time=%d\n", + ldrc,first_time,cur_time); + if ( LDAP_SUCCESS == ldrc && first_time > (time_t) 0L && + first_time + ts.ts_c_max_age < cur_time ) { + must_trim = 1; + } + } + if ( must_trim ) { + LDAPDebug(LDAP_DEBUG_TRACE,"changelog about to create thread\n",0,0,0); + /* Start a thread to trim the changelog */ + thread_start_time = cur_time; + ts.ts_s_trimming = 1; + if ( PR_CreateThread( PR_USER_THREAD, + changelog_trim_thread_fn, NULL, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, + RETROCL_DLL_DEFAULT_THREAD_STACKSIZE ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "unable to create changelog trimming thread\n" ); + } + } else { + LDAPDebug(LDAP_DEBUG_PLUGIN, + "changelog does not need to be trimmed\n",0,0,0); + } + } + PR_Unlock( ts.ts_s_trim_mutex ); +} + + +/* + * Function: age_str2time + * + * Returns: time_t + * + * Arguments: string representation of age (digits and unit s,m,h,d or w) + * + * Description: + * convert time from string like 1h (1 hour) to corresponding time in seconds + * + */ + +static time_t +age_str2time (const char *age) +{ + char *maxage; + char unit; + time_t ageval; + + if (age == NULL || age[0] == '\0' || strcmp (age, "0") == 0) { + return 0; + } + + maxage = slapi_ch_strdup ( age ); + unit = maxage[ strlen( maxage ) - 1 ]; + maxage[ strlen( maxage ) - 1 ] = '\0'; + ageval = strntoul( maxage, strlen( maxage ), 10 ); + if ( maxage) { + slapi_ch_free ( (void **) &maxage ); + } + switch ( unit ) { + case 's': + break; + case 'm': + ageval *= 60; + break; + case 'h': + ageval *= ( 60 * 60 ); + break; + case 'd': + ageval *= ( 24 * 60 * 60 ); + break; + case 'w': + ageval *= ( 7 * 24 * 60 * 60 ); + break; + default: + slapi_log_error( SLAPI_LOG_PLUGIN, "retrocl", + "age_str2time: unknown unit \"%c\" " + "for maxiumum changelog age\n", unit ); + ageval = -1; + } + + return ageval; +} + +/* + * Function: retrocl_init_trimming + * + * Returns: none, exits on fatal error + * + * Arguments: none + * + * Description: called during startup + * + */ + +void retrocl_init_trimming (void) +{ + const char *cl_maxage; + time_t ageval; + + cl_maxage = retrocl_get_config_str(CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE); + + if (cl_maxage == NULL) { + LDAPDebug(LDAP_DEBUG_TRACE,"No maxage, not trimming retro changelog.\n",0,0,0); + return; + } + ageval = age_str2time (cl_maxage); + slapi_ch_free ((void **)&cl_maxage); + + ts.ts_c_max_age = ageval; + ts.ts_s_last_trim = (time_t) 0L; + ts.ts_s_trimming = 0; + if (( ts.ts_s_trim_mutex = PR_NewLock()) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "set_changelog_trim_constraints: " + "cannot create new lock.\n" ); + exit( 1 ); + } + ts.ts_s_initialized = 1; + retrocl_trimming = 1; + + retrocl_trim_ctx = slapi_eq_repeat(retrocl_housekeeping, + NULL,(time_t)0, + CHANGELOGDB_TRIM_INTERVAL); + +} + +/* + * Function: retrocl_stop_trimming + * + * Returns: none + * + * Arguments: none + * + * Description: called when server is shutting down to ensure trimming stops + * eventually. + * + */ + +void retrocl_stop_trimming(void) +{ + retrocl_trimming = 0; + if (retrocl_trim_ctx) { + slapi_eq_cancel(retrocl_trim_ctx); + retrocl_trim_ctx = NULL; + } +} + diff --git a/ldap/servers/plugins/rever/Makefile b/ldap/servers/plugins/rever/Makefile new file mode 100644 index 00000000..76203a9c --- /dev/null +++ b/ldap/servers/plugins/rever/Makefile @@ -0,0 +1,111 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server password_storaged-plugin.so password storage scheme plugins +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libdes +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(MCOM_ROOT)/ldapserver/ns_usepurify.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libdes.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd +INCLUDES += -I$(MCOM_ROOT)/ldapserver/ldap/include + +REVER_OBJS= \ + rever.o des.o + +OBJS = $(addprefix $(OBJDEST)/, $(REVER_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBREVER_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +REVER_DLL = des-plugin +LIBREVER = $(addprefix $(LIBDIR)/, $(REVER_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += \ + $(LIBSLAPD_DEP) \ + $(LDAP_LIBUTIL_DEP) \ + $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += \ + $(LDAPSDK_DEP) \ + $(SECURITY_DEP) +EXTRA_LIBS += \ + $(LIBSLAPD) \ + $(LDAP_SDK_LIBLDAP_DLL) \ + $(LIBUTIL) \ + $(NSPRLINK) \ + $(LDAP_COMMON_LIBS) \ + $(SECURITYLINK) +endif +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += \ + $(LIBSLAPD_DEP) \ + $(LDAP_LIBUTIL_DEP) \ + $(LDAP_COMMON_LIBS_DEP) +EXTRA_LIBS_DEP += \ + $(LDAPSDK_DEP) \ + $(SECURITY_DEP) +EXTRA_LIBS += \ + $(LIBSLAPDLINK) \ + $(LDAP_SDK_LIBLDAP_DLL) \ + $(LIBUTIL) \ + $(NSPRLINK) \ + $(LDAP_COMMON_LIBS) \ + $(SECURITYLINK) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libdes.def" +CFLAGS+= /WX +endif # WINNT + +ifeq ($(ARCH), AIX) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBREVER) + +$(LIBREVER): $(OBJS) $(LIBREVER_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBREVER_DLL_OBJ) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBREVER_DLL_OBJ) +endif + $(RM) $(LIBREVER) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/rever/des.c b/ldap/servers/plugins/rever/des.c new file mode 100644 index 00000000..4b70c91d --- /dev/null +++ b/ldap/servers/plugins/rever/des.c @@ -0,0 +1,465 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* from /usr/project/iplanet/ws/ds5.ke/ns/svrcore/pkcs7/tstarchive.c */ + +#include <string.h> +#include <stdio.h> + +#include <ldap.h> +#include <nspr.h> +#include <nss.h> +#include <secmod.h> +/* +#include <secasn1.h> +#include <secpkcs7.h> +*/ +#include <key.h> +#include <certdb.h> +#include <cert.h> +#include <svrcore.h> +#include <secmodt.h> +#include <prtypes.h> +#include <seccomon.h> +#include <pk11func.h> + +#include "rever.h" +#include <slap.h> +#include "slapi-plugin.h" +#include <uuid.h> + + +struct pk11MechItem +{ + CK_MECHANISM_TYPE type; + const char *mechName; +}; +static const struct pk11MechItem mymech = { CKM_DES_CBC, "DES CBC encryption" }; + + +static Slapi_Mutex *mylock = NULL; + +struct pk11ContextStore +{ + PK11SlotInfo *slot; + const struct pk11MechItem *mech; + + PK11SymKey *key; + SECItem *params; + + int length; + unsigned char *crypt; +}; + +static int encode_path(char *inPlain, char **outCipher, char *path); +static int decode_path(char *inCipher, char **outPlain, char *path); +static SVRCOREError genKey(struct pk11ContextStore **out, const char *token, char *path); +static SVRCOREError cryptPassword(struct pk11ContextStore *store, char * clear, unsigned char **out); +static SVRCOREError decryptPassword(struct pk11ContextStore *store, unsigned char *cipher, char **out, int len); +static void freeDes(struct pk11ContextStore *out); + +static int init = 0; + +void +init_des_plugin() +{ + mylock = slapi_new_mutex(); +} + +int +encode(char *inPlain, char **outCipher) +{ + return encode_path(inPlain, outCipher, NULL); +} + +static int +encode_path(char *inPlain, char **outCipher, char *path) +{ + struct pk11ContextStore *context = NULL; + int err; + + unsigned char *cipher = NULL; + char *tmp = NULL; + char *base = NULL; + + + *outCipher = NULL; + err = 1; + + if ( genKey(&context, tokDes, path) == SVRCORE_Success ) + { + /* Try an encryption */ + if ( cryptPassword(context, inPlain, &cipher) == SVRCORE_Success ) + { + base = BTOA_DataToAscii(cipher, context->length); + if ( base != NULL ) + { + tmp = slapi_ch_malloc( 3 + strlen(REVER_SCHEME_NAME) + strlen(base)); + if ( tmp != NULL ) + { + sprintf( tmp, "%c%s%c%s", PWD_HASH_PREFIX_START, REVER_SCHEME_NAME, PWD_HASH_PREFIX_END, base); + *outCipher = tmp; + tmp = NULL; + err = 0; + } + PORT_Free(base); + } + } + } + + freeDes(context); + slapi_ch_free((void **) &context); + return(err); +} + +int +decode(char *inCipher, char **outPlain) +{ + return decode_path(inCipher, outPlain, NULL); +} + + +static int +decode_path(char *inCipher, char **outPlain, char *path) +{ + struct pk11ContextStore *context = NULL; + char *plain= NULL; + int err; + + unsigned char *base = NULL; + int len = 0; + + + *outPlain = NULL; + err = 1; + + if ( genKey(&context, tokDes, path) == SVRCORE_Success ) + { + /* it seems that there is memory leak in that function: bug 400170 */ + + base = ATOB_AsciiToData(inCipher, (unsigned int*)&len); + if ( base != NULL ) + { + if ( decryptPassword(context, base, &plain, len) == SVRCORE_Success ) + { + *outPlain = plain; + err = 0; + } + } + } + + PORT_Free(base); + freeDes(context); + slapi_ch_free((void **) &context); + return(err); +} + +static void freeDes(struct pk11ContextStore *out) +{ + if (out) + { + if (out->slot) + slapd_pk11_freeSlot(out->slot); + if (out->key) + slapd_pk11_freeSymKey(out->key); + if (out->params) + SECITEM_FreeItem(out->params,PR_TRUE); + if (out->crypt) + free(out->crypt); + } +} + +static SVRCOREError genKey(struct pk11ContextStore **out, const char *token, char *path) +{ + SVRCOREError err = SVRCORE_Success; + struct pk11ContextStore *store = NULL; + SECItem *pwitem = NULL; + SECItem *result = NULL; + SECAlgorithmID *algid = NULL; + SECOidTag algoid; + SECItem *salt = NULL; + CK_MECHANISM pbeMech; + CK_MECHANISM cryptoMech; + + char *instancedir = NULL; + char *iv = NULL; + + store = (struct pk11ContextStore*)slapi_ch_malloc(sizeof(*store)); + if (store == NULL) + { + return (err = SVRCORE_NoMemory_Error); + } + *out = store; + + /* Low-level init */ + store->slot = NULL; + store->key = NULL; + store->params = NULL; + store->crypt = NULL; + + /* Use the tokenName to find a PKCS11 slot */ + store->slot = slapd_pk11_findSlotByName((char *)token); + if (store->slot == NULL) + { + return (err = SVRCORE_NoSuchToken_Error); + } + + /* Generate a key and parameters to do the encryption */ + store->mech = &mymech; + + /* Generate a unique id, used as salt for the key generation */ + if ( path == NULL ) + { + instancedir = config_get_instancedir(); + if ( instancedir == NULL ) + { + return (err = SVRCORE_System_Error); + } + } + else + { + instancedir = slapi_ch_strdup(path); + } + if ( slapi_uniqueIDGenerateFromNameString (&iv, NULL, instancedir, strlen(instancedir)) != UID_SUCCESS ) + { + slapi_ch_free((void**)&instancedir); + return (err = SVRCORE_System_Error); + } + slapi_ch_free((void**)&instancedir); + + pwitem = (SECItem *) PORT_Alloc(sizeof(SECItem)); + if (pwitem == NULL) + { + return (err = SVRCORE_NoMemory_Error); + } + pwitem->type = siBuffer; + pwitem->data = (unsigned char *)PORT_Alloc(strlen(iv)+1); + if (pwitem->data == NULL) + { + return (err = SVRCORE_NoMemory_Error); + } + strcpy((char*)pwitem->data, iv); + pwitem->len = strlen(iv) + 1; + + algoid = SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC; + + salt = (SECItem *) PORT_Alloc(sizeof(SECItem)); + if (salt == NULL) + { + return (err = SVRCORE_NoMemory_Error); + } + salt->type = siBuffer; + salt->data = (unsigned char *)PORT_Alloc(strlen(iv)+1); + if ( salt->data == NULL ) + { + return (err = SVRCORE_NoMemory_Error); + } + strcpy((char*)salt->data, iv); + salt->len = strlen(iv) + 1; + slapi_ch_free((void**)&iv); + + algid = slapd_pk11_createPBEAlgorithmID(algoid, 2, salt); + + slapi_lock_mutex(mylock); + store->key = slapd_pk11_pbeKeyGen(store->slot, algid, pwitem, 0, 0); + if (store->key == 0) + { + slapi_unlock_mutex(mylock); + return (err = SVRCORE_System_Error); + } + + slapi_unlock_mutex(mylock); + pbeMech.mechanism = slapd_pk11_algtagToMechanism(algoid); + result = slapd_pk11_paramFromAlgid(algid); + secoid_destroyAlgorithmID(algid, PR_TRUE); + pbeMech.pParameter = result->data; + pbeMech.ulParameterLen = result->len; + if(slapd_pk11_mapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, pwitem, + PR_FALSE) != CKR_OK) + { + SECITEM_FreeItem(result, PR_TRUE); + return (err = SVRCORE_System_Error); + } + SECITEM_FreeItem(result, PR_TRUE); + SECITEM_FreeItem(pwitem, PR_TRUE); + SECITEM_FreeItem(salt, PR_TRUE); + store->params = (SECItem *) PORT_Alloc(sizeof(SECItem)); + if (store->params == NULL) + { + return (err = SVRCORE_System_Error); + } + store->params->type = store->mech->type; + store->params->data = (unsigned char *)PORT_Alloc(cryptoMech.ulParameterLen); + if (store->params->data == NULL) + { + return (err = SVRCORE_System_Error); + } + memcpy(store->params->data, (unsigned char *)cryptoMech.pParameter, cryptoMech.ulParameterLen); + store->params->len = cryptoMech.ulParameterLen; + PORT_Free(cryptoMech.pParameter); + return (err); +} + +static SVRCOREError decryptPassword(struct pk11ContextStore *store, unsigned char *cipher, char **out, int len) +{ + SVRCOREError err = SVRCORE_Success; + unsigned char *plain = NULL; + unsigned char *cipher_with_padding = NULL; + SECStatus rv; + PK11Context *ctx = 0; + int outLen = 0; + int blocksize = 0; + + blocksize = slapd_pk11_getBlockSize(store->mech->type, 0); + store->length = len; + + /* store->length is the max. length of the returned clear text - + must be >= length of crypted bytes - also must be a multiple + of blocksize */ + if (blocksize != 0) + { + store->length += blocksize - (store->length % blocksize); + } + + /* plain will hold the returned clear text */ + plain = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), + store->length+1); + if (!plain) + { + return (err = SVRCORE_NoMemory_Error); + } + + /* create a buffer holding the original cipher bytes, padded with + zeros to a multiple of blocksize - do not need +1 since buffer is not + a string */ + cipher_with_padding = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), + store->length); + if (!cipher_with_padding) + { + return (err = SVRCORE_NoMemory_Error); + } + memcpy(cipher_with_padding, cipher, len); + + ctx = slapd_pk11_createContextBySymKey(store->mech->type, CKA_DECRYPT, + store->key, store->params); + if (!ctx) + { + return (err = SVRCORE_System_Error); + } + + /* warning - there is a purify UMR in the NSS des code - you may see it when the + password is not a multiple of 8 bytes long */ + rv = slapd_pk11_cipherOp(ctx, plain, &outLen, store->length, + cipher_with_padding, store->length); + if (rv) + { + err = SVRCORE_System_Error; + } + + rv = slapd_pk11_finalize(ctx); + /* we must do the finalize, but we only want to set the err return + code if it is not already set */ + if (rv && (SVRCORE_Success == err)) + err = SVRCORE_System_Error; + + if (err == SVRCORE_Success) + *out = (char *)plain; + + slapi_ch_free((void **)&cipher_with_padding); + /* We should free the PK11Context... Something like : */ + slapd_pk11_destroyContext(ctx, PR_TRUE); + return err; +} + +static SVRCOREError cryptPassword(struct pk11ContextStore *store, char * clear, unsigned char **out) +{ + SVRCOREError err = SVRCORE_Success; + SECStatus rv; + PK11Context *ctx = 0; + int outLen = 0; + int blocksize = 0; + unsigned char *clear_with_padding = NULL; /* clear with padding up to blocksize */ + + blocksize = slapd_pk11_getBlockSize(store->mech->type, 0); + store->length = strlen(clear); + + /* the size of the clear text buffer passed to the des encryption functions + must be a multiple of blocksize (usually 8 bytes) - we allocate a buffer + of this size, copy the clear text password into it, and pad the rest with + zeros */ + if (blocksize != 0) + { + store->length += blocksize - (store->length % blocksize); + } + + /* store->crypt will hold the crypted password - it must be >= clear length */ + store->crypt = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), + store->length+1); + if (!store->crypt) + { + return (err = SVRCORE_NoMemory_Error); + } + + /* create a buffer big enough to hold the clear text password and padding */ + clear_with_padding = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), + store->length+1); + if (!clear_with_padding) + { + return (err = SVRCORE_NoMemory_Error); + } + /* copy the clear text password into the buffer - the calloc insures the + remainder is zero padded */ + strcpy((char *)clear_with_padding, clear); + + ctx = slapd_pk11_createContextBySymKey(store->mech->type, CKA_ENCRYPT, + store->key, store->params); + if (!ctx) + { + return (err = SVRCORE_System_Error); + } + + rv = slapd_pk11_cipherOp(ctx, store->crypt, &outLen, store->length, + clear_with_padding, store->length); + if (rv) + { + err = SVRCORE_System_Error; + } + + rv = slapd_pk11_finalize(ctx); + /* we must do the finalize, but we only want to set the err return + code if it is not already set */ + if (rv && (SVRCORE_Success == err)) + err = SVRCORE_System_Error; + + if (err == SVRCORE_Success) + *out = store->crypt; + + slapi_ch_free((void **)&clear_with_padding); + /* We should free the PK11Context... Something like : */ + slapd_pk11_destroyContext(ctx, PR_TRUE); + return err; +} + +char * +migrateCredentials(char *oldpath, char *newpath, char *oldcred) +{ + char *plain = NULL; + char *cipher = NULL; + + init_des_plugin(); + + slapd_pk11_configurePKCS11(NULL, NULL, tokDes, ptokDes, NULL, NULL, NULL, NULL, 0, 0 ); + NSS_NoDB_Init(NULL); + + if ( decode_path(oldcred, &plain, oldpath) == 0 ) + { + if ( encode_path(plain, &cipher, newpath) != 0 ) + return(NULL); + else + return(cipher); + } + else + return(NULL); +} diff --git a/ldap/servers/plugins/rever/dllmain.c b/ldap/servers/plugins/rever/dllmain.c new file mode 100644 index 00000000..0bb85815 --- /dev/null +++ b/ldap/servers/plugins/rever/dllmain.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + /* + * Microsoft Windows specifics for LIBPWDSTORAGE DLL + */ +#include "rever.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/rever/libdes.def b/ldap/servers/plugins/rever/libdes.def new file mode 100644 index 00000000..048af6f4 --- /dev/null +++ b/ldap/servers/plugins/rever/libdes.def @@ -0,0 +1,13 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Directory Server 6.2.1 Local Credentials Reversible Encryption Plugin' +EXPORTS + des_cmp @2 + des_enc @3 + des_dec @4 + des_init @5 + migrateCredentials @6 diff --git a/ldap/servers/plugins/rever/rever.c b/ldap/servers/plugins/rever/rever.c new file mode 100644 index 00000000..10767944 --- /dev/null +++ b/ldap/servers/plugins/rever/rever.c @@ -0,0 +1,77 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "dirver.h" + +#include "rever.h" + +static Slapi_PluginDesc pdesc = { "des-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "DES storage scheme plugin" }; + +static char *plugin_name = "ReverStoragePlugin"; + +int +des_cmp( char *userpwd, char *dbpwd ) +{ + char *cipher = NULL; + + if ( encode(userpwd, &cipher) != 0 ) + return 1; + else + return( strcmp(cipher, dbpwd) ); +} + +char * +des_enc( char *pwd ) +{ + char *cipher = NULL; + + if ( encode(pwd, &cipher) != 0 ) + return(NULL); + else + return( cipher ); +} + +char * +des_dec( char *pwd ) +{ + char *plain = NULL; + + if ( decode(pwd, &plain) != 0 ) + return(NULL); + else + return( plain ); +} + +int +des_init( Slapi_PBlock *pb ) +{ + int rc; + char *name; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> des_init\n" ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, + (void *) des_enc); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, + (void *) des_cmp ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_DEC_FN, + (void *) des_dec ); + name = slapi_ch_strdup(REVER_SCHEME_NAME); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, + name ); + + init_des_plugin(); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= des_init %d\n\n", rc ); + + return( rc ); +} diff --git a/ldap/servers/plugins/rever/rever.h b/ldap/servers/plugins/rever/rever.h new file mode 100644 index 00000000..0992aea7 --- /dev/null +++ b/ldap/servers/plugins/rever/rever.h @@ -0,0 +1,34 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#ifndef _REVER_H +#define _REVER_H + +#include "slapi-plugin.h" +#include "nspr.h" +#include "base64.h" +#include "slap.h" +#include "ldaplog.h" + +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +#define REVER_SCHEME_NAME "DES" +#define PWD_HASH_PREFIX_START '{' +#define PWD_HASH_PREFIX_END '}' + + +int rever_cmp( char *userpwd, char *dbpwd ); +char *rever_enc( char *pwd ); +char *rever_dec( char *pwd ); +int rever_init( Slapi_PBlock *pb ); +void init_des_plugin(); + +int encode(char *inPlain, char ** outCipher); +int decode(char *inCipher, char **outPlain); + +char *migrateCredentials(char *oldpath, char *newpath, char *oldcred); +typedef char *(*migrate_fn_type)(char *, char *, char *); + +#endif diff --git a/ldap/servers/plugins/roles/Makefile b/ldap/servers/plugins/roles/Makefile new file mode 100644 index 00000000..2e936fad --- /dev/null +++ b/ldap/servers/plugins/roles/Makefile @@ -0,0 +1,95 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libroles +LIBDIR = $(LIB_RELDIR) +ifndef INSTDIR +INSTDIR = c:/netscape/server4/ +endif + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./roles.def +endif + +ROLES_OBJS = roles_plugin.o roles_cache.o +OBJS = $(addprefix $(OBJDEST)/, $(ROLES_OBJS)) + +ROLES_DLL = roles-plugin + +INCLUDES += -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +# DBDB this is clearly all nonsense: the libraries this thing links with should not depend on the platform. +# However, for now I make this AIX-specific change and leave the NT-specifc stuff in place (I think it came +# from the makefile I copied to make this one. After build 3, fix this. + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_LIBAVL) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +ROLES_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), AIX) +LD=ld +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_LIBAVL) +endif + +ROLES= $(addprefix $(LIBDIR)/, $(ROLES_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(ROLES) + +ifeq ($(ARCH), WINNT) +$(ROLES): $(OBJS) $(ROLES_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(ROLES_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(ROLES): $(OBJS) $(ROLES_DLL_OBJ) + $(LINK_DLL) $(ROLES_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(ROLES_DLL_OBJ) +endif + $(RM) $(ROLES) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) + +# Target to push the built binary to an installed server +#ROLES_PUSH = $(addprefix $(INSTDIR)lib/, $(notdir $(ROLES))) +#push: $(ROLES_PUSH) + +#$(ROLES_PUSH): $(ROLES) +# cp $(ROLES) $(ROLES_PUSH) diff --git a/ldap/servers/plugins/roles/dllmain.c b/ldap/servers/plugins/roles/dllmain.c new file mode 100644 index 00000000..fabf8677 --- /dev/null +++ b/ldap/servers/plugins/roles/dllmain.c @@ -0,0 +1,96 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/roles/roles.def b/ldap/servers/plugins/roles/roles.def new file mode 100644 index 00000000..0ae9d85f --- /dev/null +++ b/ldap/servers/plugins/roles/roles.def @@ -0,0 +1,10 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7.0 Roles Plugin' +EXPORTS + roles_init @2 + plugin_init_debug_level @3 diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c new file mode 100644 index 00000000..f4dc31c2 --- /dev/null +++ b/ldap/servers/plugins/roles/roles_cache.c @@ -0,0 +1,2061 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "portable.h" +#include "slapi-plugin.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" + +/* This is naughty ... */ +#include "slapi-private.h" + +/* include NSPR header files */ +#include "slap.h" +#include "prthread.h" +#include "prlock.h" +#include "prerror.h" +#include "prcvar.h" +#include "prio.h" +#include "avl.h" +#include "vattr_spi.h" +#include "roles_cache.h" +#include "views.h" + +#ifdef SOLARIS +#include <tnf/probe.h> +#else +#define TNF_PROBE_0(a,b,c) +#endif + +#define MAX_NESTED_ROLES 30 + +static char *allUserAttributes[] = { + LDAP_ALL_USER_ATTRS, + NULL +}; + +/* views scoping */ +static void **views_api; + +/* Service provider handler */ +static vattr_sp_handle *vattr_handle = NULL; + +/* List of nested roles */ +typedef struct _role_object_nested { + Slapi_DN *dn; /* value of attribute nsroledn in a nested role definition */ +} role_object_nested; + +/* Role object structure */ +typedef struct _role_object { + Slapi_DN *dn; /* dn of a role entry */ + int type; /* ROLE_TYPE_MANAGED|ROLE_TYPE_FILTERED|ROLE_TYPE_NESTED */ + Slapi_Filter *filter; /* if ROLE_TYPE_FILTERED */ + Avlnode *avl_tree; /* if ROLE_TYPE_NESTED: tree of nested DNs (avl_data is a role_object_nested struct) */ +} role_object; + +/* Structure containing the roles definitions for a given suffix */ +typedef struct _roles_cache_def { + + /* Suffix DN*/ + Slapi_DN *suffix_dn; + + /* Module level thread control */ + PRThread *roles_tid; + int keeprunning; + + Slapi_Mutex *cache_lock; + Slapi_Mutex *stop_lock; + + Slapi_Mutex *change_lock; + Slapi_CondVar *something_changed; + + Slapi_Mutex *create_lock; + Slapi_CondVar *suffix_created; + int is_ready; + + /* Root of the avl tree containing all the roles definitions + NB: avl_data field is of type role_object + */ + Avlnode *avl_tree; + + /* Next roles suffix definitions */ + struct _roles_cache_def *next; + + /* Info passed from the server when an notification is sent to the plugin */ + char *notified_dn; + Slapi_Entry *notified_entry; + int notified_operation; + +} roles_cache_def; + + +/* Global list containing all the roles definitions per suffix */ +static roles_cache_def *roles_list = NULL; + +static PRRWLock *global_lock = NULL; + +/* Structure holding the nsrole values */ +typedef struct _roles_cache_build_result +{ + Slapi_ValueSet **nsrole_values; /* nsrole computed values */ + Slapi_Entry *requested_entry; /* entry to get nsrole from */ + int has_value; /* flag to determine if a new value has been added to the result */ + int need_value; /* flag to determine if we need the result */ +} roles_cache_build_result; + +/* Structure used to check if is_entry_member_of is part of a role defined in its suffix */ +typedef struct _roles_cache_search_in_nested +{ + Slapi_Entry *is_entry_member_of; + int present; /* flag to know if the entry is part of a role */ + int hint; /* to check the depth of the nested */ +} roles_cache_search_in_nested; + +/* Structure used to handle roles searches */ +typedef struct _roles_cache_search_roles +{ + roles_cache_def *suffix_def; + int rc; /* to check the depth of the nested */ +} roles_cache_search_roles; + +static roles_cache_def* roles_cache_create_suffix(Slapi_DN *sdn); +static int roles_cache_add_roles_from_suffix(Slapi_DN *suffix_dn, roles_cache_def *suffix_def); +static void roles_cache_wait_on_change(void * arg); +static void roles_cache_trigger_update_suffix(void *handle, char *be_name, int old_be_state, int new_be_state); +static void roles_cache_trigger_update_role(char *dn, Slapi_Entry *role_entry, Slapi_DN *be_dn, int operation); +static int roles_cache_update(roles_cache_def *suffix_to_update); +static int roles_get_roles_from_entry(Slapi_DN * suffix, Slapi_PBlock **int_search_pb); +static int roles_cache_create_role_under(roles_cache_def** roles_cache_suffix, Slapi_Entry *entry); +static int roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **result, int hint); +static int roles_cache_determine_class(Slapi_Entry *role_entry); +static int roles_cache_node_cmp( caddr_t d1, caddr_t d2 ); +static int roles_cache_insert_object(Avlnode **tree, role_object *object); +static int roles_cache_node_nested_cmp( caddr_t d1, caddr_t d2 ); +static int roles_cache_insert_object_nested(Avlnode **tree, role_object_nested *object); +static int roles_cache_object_nested_from_dn(Slapi_DN *role_dn, role_object_nested **result); +static int roles_cache_build_nsrole( caddr_t data, caddr_t arg ); +static int roles_cache_find_node( caddr_t d1, caddr_t d2 ); +static int roles_cache_find_roles_in_suffix(Slapi_DN *target_entry_dn, roles_cache_def **list_of_roles); +static int roles_is_entry_member_of_object(caddr_t data, caddr_t arg ); +static int roles_check_managed(Slapi_Entry *entry_to_check, role_object *role, int *present); +static int roles_check_filtered(Slapi_Entry *entry_to_check, role_object *role, int *present); +static int roles_check_nested(caddr_t data, caddr_t arg); +static int roles_is_inscope(Slapi_Entry *entry_to_check, Slapi_DN *role_dn); +static void berval_set_string(struct berval *bv, const char* string); +static void roles_cache_role_def_delete(roles_cache_def *role_def); +static void roles_cache_role_def_free(roles_cache_def *role_def); +static void roles_cache_role_object_free(role_object *this_role); +static void roles_cache_role_object_nested_free(role_object_nested *this_role); +static int roles_cache_dump( caddr_t data, caddr_t arg ); +static int roles_cache_add_entry_cb(Slapi_Entry* e, void *callback_data); +static void roles_cache_result_cb( int rc, void *callback_data); +static Slapi_DN* roles_cache_get_top_suffix(Slapi_DN *suffix); + +/* ============== FUNCTIONS ================ */ + +/* roles_cache_init + ---------------- + create the cache for all the existing suffixes + starts up the threads which wait for changes + also registers vattr callbacks + + return 0 if OK + return -1 otherwise +*/ +int roles_cache_init() +{ + int rc = 0; + void *node = NULL; + Slapi_DN *sdn = NULL; + roles_cache_def *new_suffix = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_init\n"); + + if ( global_lock == NULL ) + { + global_lock = PR_NewRWLock(0,"roles_cache"); + } + + /* grab the views interface */ + if(slapi_apib_get_interface(Views_v1_0_GUID, &views_api)) + { + /* lets be tolerant if views is disabled */ + views_api = 0; + } + + /* For each top suffix, get the roles definitions defined below it */ + PR_RWLock_Wlock(global_lock); + + sdn = slapi_get_first_suffix(&node, 0); + while (sdn) + { + + if ( (new_suffix = roles_cache_create_suffix(sdn)) == NULL ) + { + PR_DestroyRWLock(global_lock); + global_lock = NULL; + return(-1); + } + + if ( roles_cache_add_roles_from_suffix(sdn, new_suffix) != 0 ) + { + /* No roles in that suffix, stop the thread and remove it ? */ + } + sdn = slapi_get_next_suffix(&node, 0); + } + PR_RWLock_Unlock(global_lock); + + /* to expose roles_check to ACL plugin */ + slapi_register_role_check(roles_check); + + /* Register a callback on backends creation|modification|deletion, + so that we update the corresponding cache */ + slapi_register_backend_state_change(NULL, roles_cache_trigger_update_suffix); + + if ( slapi_vattrspi_register((vattr_sp_handle **)&vattr_handle, + roles_sp_get_value, + roles_sp_compare_value, + roles_sp_list_types) ) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_init: slapi_vattrspi_register failed\n"); + + PR_DestroyRWLock(global_lock); + global_lock = NULL; + return(-1); + } + else if ( slapi_vattrspi_regattr(vattr_handle,NSROLEATTR,"", NULL) ) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_init: slapi_vattrspi_regattr failed\n"); + free(vattr_handle); + PR_DestroyRWLock(global_lock); + global_lock = NULL; + return(-1); + } + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_init\n"); + return rc; +} + +/* roles_cache_create_suffix + ------------------------- + Create a new entry in the global list + return a pointer on the suffix stucture: OK + return NULL: fail + */ +static roles_cache_def *roles_cache_create_suffix(Slapi_DN *sdn) +{ + roles_cache_def *current_suffix = NULL; + roles_cache_def *new_suffix = NULL; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_create_suffix\n"); + + /* Allocate a new suffix block */ + new_suffix = (roles_cache_def*)slapi_ch_calloc(1, sizeof(roles_cache_def)); + if ( new_suffix == NULL ) + { + slapi_log_error(SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_create_suffix: Unable to allocate memory, cannot create role cache\n"); + return(NULL); + } + + new_suffix->cache_lock = slapi_new_mutex(); + new_suffix->change_lock = slapi_new_mutex(); + new_suffix->stop_lock = slapi_new_mutex(); + new_suffix->create_lock = slapi_new_mutex(); + if ( new_suffix->stop_lock == NULL || + new_suffix->change_lock == NULL || + new_suffix->cache_lock == NULL || + new_suffix->create_lock == NULL ) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_create_suffix: Lock creation failed\n"); + roles_cache_role_def_free(new_suffix); + return(NULL); + } + + new_suffix->something_changed = slapi_new_condvar(new_suffix->change_lock); + if ( new_suffix->something_changed == NULL) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_create_suffix: Lock creation failed\n"); + roles_cache_role_def_free(new_suffix); + return(NULL); + } + + new_suffix->suffix_created = slapi_new_condvar(new_suffix->create_lock); + if ( new_suffix->suffix_created == NULL) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_create_suffix: Lock creation failed\n"); + roles_cache_role_def_free(new_suffix); + return(NULL); + } + + new_suffix->keeprunning = 1; + + new_suffix->suffix_dn = slapi_sdn_dup(sdn); + + /* those 3 items are used to give back info to the thread when + it is awakened */ + new_suffix->notified_dn = NULL; + new_suffix->notified_entry = NULL; + new_suffix->notified_operation = 0; + + /* Create the global list */ + if ( roles_list == NULL ) + { + roles_list = new_suffix; + } + else + { + current_suffix = roles_list; + while ( current_suffix != NULL ) + { + if ( current_suffix->next == NULL ) + { + current_suffix->next = new_suffix; + break; + } + else + { + current_suffix = current_suffix->next; + } + } + } + + /* to prevent deadlock */ + new_suffix->is_ready = 0; + if ( (new_suffix->roles_tid = PR_CreateThread (PR_USER_THREAD, + roles_cache_wait_on_change, + (void*)new_suffix, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL ) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_create_suffix: PR_CreateThread failed\n"); + roles_cache_role_def_delete(new_suffix); + return(NULL); + } + + slapi_lock_mutex(new_suffix->create_lock); + if (new_suffix->is_ready != 1) + { + slapi_wait_condvar(new_suffix->suffix_created, NULL); + } + slapi_unlock_mutex(new_suffix->create_lock); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_create_suffix\n"); + return(new_suffix); +} + +/* roles_cache_wait_on_change + -------------------------- + Sit around waiting on a notification that something has + changed, then fires off the updates + */ +static void roles_cache_wait_on_change(void * arg) +{ + roles_cache_def *roles_def = (roles_cache_def*)arg; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_wait_on_change\n"); + + slapi_lock_mutex(roles_def->stop_lock); + slapi_lock_mutex(roles_def->change_lock); + + while ( roles_def->keeprunning) + { + slapi_unlock_mutex(roles_def->change_lock); + slapi_lock_mutex(roles_def->change_lock); + + /* means that the thread corresponding to that suffix is ready to receive notifications + from the server */ + slapi_lock_mutex(roles_def->create_lock); + if ( roles_def->is_ready == 0 ) + { + slapi_notify_condvar( roles_def->suffix_created, 1 ); + roles_def->is_ready = 1; + } + slapi_unlock_mutex(roles_def->create_lock); + + /* XXX In case the BE containing this role_def signaled + a shut down between the unlock/lock above should + test roles_def->keeprunning before + going to sleep. + */ + slapi_wait_condvar(roles_def->something_changed, NULL); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "roles_cache_wait_on_change \n"); + + if ( roles_def->keeprunning ) + { + roles_cache_update(roles_def); + } + } + + /* shut down the cache */ + slapi_unlock_mutex(roles_def->change_lock); + slapi_unlock_mutex(roles_def->stop_lock); + + roles_cache_role_def_free(roles_def); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_wait_on_change thread exit\n"); +} + +/* roles_cache_trigger_update_suffix + -------------------------------- + This is called when a backend changes state (created|modified|deleted) + We simply signal to update the associated role cache in this case + */ +static void roles_cache_trigger_update_suffix(void *handle, char *be_name, int old_be_state, int new_be_state) +{ + roles_cache_def *current_role = roles_list; + const Slapi_DN *be_suffix_dn = NULL; + Slapi_DN *top_suffix_dn = NULL; + Slapi_Backend *backend = NULL; + int found = 0; + + PR_RWLock_Wlock(global_lock); + + if ( (new_be_state == SLAPI_BE_STATE_DELETE) || (new_be_state == SLAPI_BE_STATE_OFFLINE) ) + { + /* Invalidate and rebuild the whole cache */ + roles_cache_def *current_role = NULL; + roles_cache_def *next_role = NULL; + Slapi_DN *sdn = NULL; + void *node = NULL; + roles_cache_def *new_suffix = NULL; + + /* Go through all the roles list and trigger the associated structure */ + current_role = roles_list; + while ( current_role ) + { + slapi_lock_mutex(current_role->change_lock); + current_role->keeprunning = 0; + next_role = current_role->next; + slapi_notify_condvar(current_role->something_changed, 1 ); + slapi_unlock_mutex(current_role->change_lock); + + current_role = next_role; + } + + /* rebuild a new one */ + roles_list = NULL; + + sdn = slapi_get_first_suffix(&node, 0); + while (sdn) + { + + if ( (new_suffix = roles_cache_create_suffix(sdn)) == NULL ) + { + PR_RWLock_Unlock(global_lock); + return; + } + + roles_cache_add_roles_from_suffix(sdn, new_suffix); + sdn = slapi_get_next_suffix(&node, 0); + } + PR_RWLock_Unlock(global_lock); + return; + } + + /* Backend back on line or new one created*/ + backend = slapi_be_select_by_instance_name(be_name); + if ( backend != NULL ) + { + be_suffix_dn = slapi_be_getsuffix(backend, 0); + top_suffix_dn = roles_cache_get_top_suffix((Slapi_DN *)be_suffix_dn); + } + + while ( (current_role != NULL) && !found && (top_suffix_dn != NULL) ) + { + /* The backend already exists (back online): so invalidate "old roles definitions" */ + if ( slapi_sdn_compare(current_role->suffix_dn, top_suffix_dn) == 0 ) + { + roles_cache_role_def_delete(current_role); + found = 1; + } + else + { + current_role = current_role->next; + } + } + + if ( top_suffix_dn != NULL ) + { + /* Add the new definitions in the cache */ + roles_cache_def *new_suffix = roles_cache_create_suffix(top_suffix_dn); + + if ( new_suffix != NULL ) + { + roles_cache_add_roles_from_suffix(top_suffix_dn, new_suffix); + } + slapi_sdn_free(&top_suffix_dn); + } + + PR_RWLock_Unlock(global_lock); +} + +/* roles_cache_trigger_update_role + -------------------------------- + Call when an entry containing a role definition has been added, modified + or deleted + */ +static void roles_cache_trigger_update_role(char *dn, Slapi_Entry *roles_entry, Slapi_DN *be_dn, int operation) +{ + int found = 0; + roles_cache_def *current_role = NULL; + + PR_RWLock_Wlock(global_lock); + + current_role = roles_list; + + slapi_log_error( SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_trigger_update_role: %x \n", roles_list); + + /* Go through all the roles list and trigger the associated structure */ + + /* be_dn is already the top suffix for that dn */ + while ( (current_role != NULL) && !found ) + { + if ( slapi_sdn_compare(current_role->suffix_dn, be_dn) == 0 ) + { + found = 1; + } + else + { + current_role = current_role->next; + } + } + + if ( found ) + { + slapi_lock_mutex(current_role->change_lock); + + slapi_entry_free (current_role->notified_entry); + current_role->notified_entry = roles_entry; + slapi_ch_free ((void**)&(current_role->notified_dn)); + current_role->notified_dn = dn; + current_role->notified_operation = operation; + + roles_cache_update(current_role); + + slapi_unlock_mutex(current_role->change_lock); + } + + PR_RWLock_Unlock(global_lock); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_trigger_update_role: %x \n", roles_list); +} + +/* roles_cache_update + ------------------ + Update the cache associated to a suffix + Return 0: ok + Return -1: fail + */ +static int roles_cache_update(roles_cache_def *suffix_to_update) +{ + int rc = 0; + int operation; + Slapi_Entry *entry = NULL; + Slapi_DN *dn = NULL; + role_object *to_delete = NULL; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_update \n"); + + slapi_lock_mutex(suffix_to_update->cache_lock); + + operation = suffix_to_update->notified_operation; + entry = suffix_to_update->notified_entry; + dn = slapi_sdn_new(); + slapi_sdn_set_dn_byval(dn, suffix_to_update->notified_dn); + + if ( (entry != NULL) && (dn != NULL) ) + { + if ( (operation == SLAPI_OPERATION_MODIFY) || + (operation == SLAPI_OPERATION_DELETE) ) + { + /* delete it */ + int dummy; + + to_delete = (role_object *)avl_delete(&(suffix_to_update->avl_tree), dn, roles_cache_find_node, &dummy); + roles_cache_role_object_free(to_delete); + to_delete = NULL; + if ( slapi_is_loglevel_set(SLAPI_LOG_PLUGIN) ) + { + avl_apply(suffix_to_update->avl_tree, (IFP)roles_cache_dump, &rc, -1, AVL_INORDER); + } + + } + if ( (operation == SLAPI_OPERATION_MODIFY) || + (operation ==SLAPI_OPERATION_ADD) ) + { + rc = roles_cache_create_role_under(&suffix_to_update,entry); + } + if ( entry != NULL ) + { + slapi_entry_free(entry); + } + suffix_to_update->notified_entry = NULL; + + } + slapi_unlock_mutex(suffix_to_update->cache_lock); + + if ( dn != NULL ) + { + slapi_sdn_free(&dn); + } + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_update \n"); + return(rc); +} + +/* roles_cache_stop + ---------------- + + XXX the stop_lock of a roles_cache_def + doesn't seem to serve any useful purpose... + + */ +void roles_cache_stop() +{ + roles_cache_def *current_role = NULL; + roles_cache_def *next_role = NULL; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_stop\n"); + + /* Go through all the roles list and trigger the associated structure */ + PR_RWLock_Wlock(global_lock); + current_role = roles_list; + while ( current_role ) + { + slapi_lock_mutex(current_role->change_lock); + current_role->keeprunning = 0; + next_role = current_role->next; + slapi_notify_condvar(current_role->something_changed, 1 ); + slapi_unlock_mutex(current_role->change_lock); + + current_role = next_role; + } + PR_RWLock_Unlock(global_lock); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_stop\n"); +} + +/* roles_cache_is_role_entry + ------------------------- + Check if the entry is a role + return -1: error in processing + return 0: entry is not a role + return 1: entry is a role +*/ +static int roles_cache_is_role_entry(struct slapi_entry *entry) +{ + Slapi_Attr *pObjclasses = NULL; + Slapi_Value *val = NULL; + char *pObj = NULL; + int index = 0; + + int nsroledefinition = 0; + int nsrolesimpleOrComplex = 0; + int nsroletype = 0; + + if ( entry == NULL ) + { + return(0); + } + + if ( slapi_entry_attr_find(entry, "objectclass", &pObjclasses) ) + { + slapi_log_error( SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_is_role_entry: failed to get objectclass from %s\n",slapi_entry_get_dn_const(entry)); + return(-1); + } + + /* Check out the object classes to see if this was a nsroledefinition */ + + val = 0; + index = slapi_attr_first_value( pObjclasses, &val ); + while(val) + { + const char *p; + int len = 0; + + pObj = (char*)slapi_value_get_string(val); + + for ( p = pObj, len = 0; + (*p != '\0') && (*p != ' '); + p++, len++ ) + { + ; /* NULL */ + } + + if ( !strncasecmp(pObj, (char*)"nsroledefinition", len) ) + { + nsroledefinition = 1; + } + if ( !strncasecmp(pObj, (char*)"nssimpleroledefinition", len) || + !strncasecmp(pObj, (char*)"nscomplexroledefinition", len) ) + { + nsrolesimpleOrComplex = 1; + } + if( !strncasecmp(pObj, (char*)"nsmanagedroledefinition", len) || + !strncasecmp(pObj, (char*)"nsfilteredroledefinition", len) || + !strncasecmp(pObj, (char*)"nsnestedroledefinition", len) + ) + { + nsroletype = 1; + } + index = slapi_attr_next_value( pObjclasses, index, &val ); + } + if ( (nsroledefinition == 0) || + (nsrolesimpleOrComplex == 0) || + (nsroletype == 0) ) + { + return(0); + } + return(1); +} + +/* roles_cache_change_notify + ------------------------- + determines if the change effects the cache and if so + signals a rebuild + -- called when modify|modrdn|add|delete operation is performed -- + -- called from a postoperation on an entry + XXX this implies that the client may have already received his LDAP response, + but that there will be a delay before he sees the effect in the roles cache. + Should we be doing this processing in a BE_POST_MODIFY postop + which is called _before_ the response goes to the client ? +*/ +void roles_cache_change_notify(Slapi_PBlock *pb) +{ + char *dn = NULL; + struct slapi_entry *e = NULL; + struct slapi_entry *pre = NULL; + struct slapi_entry *entry = NULL; + Slapi_Backend *be = NULL; + Slapi_Operation *pb_operation = NULL; + int operation; + int do_update = 0; + int rc = -1; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, + "--> roles_cache_change_notify\n"); + + /* if the current operation has failed, don't even try the post operation */ + slapi_pblock_get( pb, SLAPI_PLUGIN_OPRETURN, &rc ); + if ( rc != LDAP_SUCCESS ) + { + return; + } + + /* Don't update local cache when remote entries are updated */ + slapi_pblock_get( pb, SLAPI_BACKEND, &be ); + if ( ( be!=NULL ) && + ( slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)) ) + { + return; + } + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if( dn == NULL ) + { + return; + } + + slapi_pblock_get (pb, SLAPI_OPERATION, &pb_operation); + operation = operation_get_type(pb_operation); + + switch (operation) + { + case SLAPI_OPERATION_DELETE: + + slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &e); + if( e == NULL ) + { + return; + } + break; + + case SLAPI_OPERATION_ADD: + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e); + if ( e == NULL ) + { + return; + } + break; + + case SLAPI_OPERATION_MODIFY: + case SLAPI_OPERATION_MODRDN: + /* those operations are treated the same way and modify is a deletion followed by an addition. + the only point to take care is that dn is the olddn */ + operation = SLAPI_OPERATION_MODIFY; + slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &pre); + if( pre == NULL ) + { + return; + } + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e); + if ( e == NULL ) + { + return; + } + break; + default: + slapi_log_error( SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_change_notify: unknown operation %d\n",operation); + return; + } + + if ( operation != SLAPI_OPERATION_MODIFY ) + { + if ( roles_cache_is_role_entry(e) != 1 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_change_notify: not a role entry\n"); + return; + } + entry = slapi_entry_dup(e); + do_update = 1; + } + else + { + int is_pre_role = roles_cache_is_role_entry(pre); + int is_post_role = roles_cache_is_role_entry(e); + if ( (is_pre_role==1) && (is_post_role==1) ) /* role definition has changed */ + { + entry = slapi_entry_dup(e); + do_update = 1; + } + else if ( is_pre_role == 1 ) /* entry is no more a role */ + { + operation = SLAPI_OPERATION_DELETE; + do_update = 1; + } + else if ( is_post_role == 1 ) /* entry is now a role */ + { + operation = SLAPI_OPERATION_ADD; + entry = slapi_entry_dup(e); + do_update = 1; + } + else + { + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_change_notify: not a role entry\n"); + return; + } + } + + if ( do_update ) + { +#ifdef moretrace +if ( e != NULL ) +{ + Slapi_Attr *attr = NULL; + int rc; + + /* Get the list of nested roles */ + rc = slapi_entry_attr_find(e,ROLE_NESTED_ATTR_NAME,&attr); + + if ( (rc == 0) && attr) + { + /* Recurse to get the definition objects for them */ + Slapi_Value **va = attr_get_present_values(attr); + int i = 0; + char *string = NULL; + + for ( i = 0; va[i] != NULL; i++ ) + { + string = (char*)slapi_value_get_string(va[i]); + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "roles_cache_change_notify:%s\n",string); + } + } +} +#endif + Slapi_DN *top_suffix = roles_cache_get_top_suffix(*(be->be_suffix)); + + if ( top_suffix != NULL ) + { + roles_cache_trigger_update_role( slapi_ch_strdup(dn), entry, + top_suffix, + operation); + + slapi_sdn_free(&top_suffix); + } + + } + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_change_notify\n"); + +} + +/* roles_cache_get_top_suffix + ------------------------- + The returned Slapi_DN must be freed with slapi_sdn_free(). +*/ +static Slapi_DN* roles_cache_get_top_suffix(Slapi_DN *suffix) +{ + Slapi_DN *current_suffix = NULL; + Slapi_DN parent_suffix; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_get_top_suffix\n"); + + if ( suffix == NULL ) + { + return(NULL); + } + current_suffix = slapi_sdn_new(); + slapi_sdn_init(&parent_suffix); + + /* we must get the top suffix for that DN */ + slapi_sdn_copy(suffix,current_suffix); + while ( !slapi_sdn_isempty(current_suffix) ) + { + if ( slapi_is_root_suffix(current_suffix) != 1 ) + { + slapi_sdn_get_parent(current_suffix,&parent_suffix); + slapi_sdn_copy(&parent_suffix, current_suffix); + } + else + { + slapi_sdn_done(&parent_suffix); + return(current_suffix); + } + } + /* we should not return that way ... */ + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_get_top_suffix\n"); + slapi_sdn_done(&parent_suffix); + slapi_sdn_free(¤t_suffix); + return(NULL); +} + +/* roles_cache_add_roles_from_suffix + ------------------------------- + Get the roles entries under the suffix + return 0: OK + return -1: this suffix has no role defined + */ +static int roles_cache_add_roles_from_suffix(Slapi_DN *suffix_dn, roles_cache_def *suffix_def) +{ + /* Search subtree-level under this entry */ + int rc = -1; + roles_cache_search_roles info; + Slapi_PBlock *int_search_pb = NULL; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_get_roles_from_entry\n"); + + info.suffix_def = suffix_def; + info.rc = LDAP_NO_SUCH_OBJECT; + + /* Get the roles definitions of the given suffix_dn */ + int_search_pb = slapi_pblock_new (); + slapi_search_internal_set_pb(int_search_pb, + (char*)slapi_sdn_get_ndn(suffix_dn), + LDAP_SCOPE_SUBTREE, + ROLE_DEFINITION_FILTER, + allUserAttributes, + 0 /* attrsonly */, + NULL /* controls */, + NULL /* uniqueid */, + roles_get_plugin_identity(), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions : get local roles only */ ); + + slapi_search_internal_callback_pb(int_search_pb, + &info /* callback_data */, + roles_cache_result_cb, + roles_cache_add_entry_cb, + NULL /* referral_callback */); + + slapi_pblock_destroy (int_search_pb); + int_search_pb = NULL; + + if ( info.rc == LDAP_SUCCESS ) + { + rc = 0; + } + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_get_roles_from_entry\n"); + + return(rc); +} + +/* roles_cache_add_entry_cb + ----------------------- +*/ +static int roles_cache_add_entry_cb(Slapi_Entry* e, void *callback_data) +{ + roles_cache_search_roles *info=(roles_cache_search_roles *)callback_data; + + roles_cache_def *suffix = info->suffix_def; + + roles_cache_create_role_under(&suffix, e); + return(0); +} + +/* roles_cache_result_cb + ----------------------- +*/ +static void roles_cache_result_cb( int rc, void *callback_data) { + roles_cache_search_roles *info=(roles_cache_search_roles *)callback_data; + + info->rc = rc; +} + + +/* roles_cache_create_role_under + ---------------------------- + Create the avl tree of roles definitions defined in the scope + of the suffix + Return 0: OK + Return -1: fail +*/ +static int roles_cache_create_role_under(roles_cache_def** roles_cache_suffix, Slapi_Entry *entry) +{ + int rc; + role_object *new_role = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_create_role_under: %s - %x\n", + slapi_sdn_get_dn((*roles_cache_suffix)->suffix_dn), + (*roles_cache_suffix)->avl_tree); + + rc = roles_cache_create_object_from_entry(entry,&new_role,0); + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_create_role_under: create node for entry %s - rc: %d SUFFIX: %x\n", + slapi_entry_get_dn_const(entry), rc, (*roles_cache_suffix)->avl_tree); + + if ( (rc == 0) && new_role) + { + /* Add to the tree where avl_data is a role_object struct */ + rc = roles_cache_insert_object(&((*roles_cache_suffix)->avl_tree),new_role); + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_cache_create_role_under:%s in tree %x rc: %d\n", + (char*)slapi_sdn_get_ndn(new_role->dn), + (*roles_cache_suffix)->avl_tree, rc); + } + return(rc); +} + + +/* roles_cache_create_object_from_entry + ------------------------------------ + Create a node role_object from the information contained in role_entry + Return 0 + Return ENOMEM: fail + Return SLAPI_ROLE_DEFINITION_ERROR: fail + Return SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED: fail + Return SLAPI_ROLE_ERROR_FILTER_BAD: fail +*/ +static int roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **result, int hint) +{ + int rc = 0; + int type = 0; + role_object *this_role = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "--> roles_cache_create_object_from_entry\n"); + + *result = NULL; + + /* Do not allow circular dependencies */ + if ( hint > MAX_NESTED_ROLES ) + { + char *ndn = NULL; + + ndn = slapi_entry_get_ndn( role_entry ); + slapi_log_error( + SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, + "Maximum roles nesting exceeded (%d), not retrieving roles from entry %s--probable circular definition\n", + MAX_NESTED_ROLES, + ndn); + + return (0); + } + + /* Create the role cache definition */ + this_role = (role_object*)slapi_ch_calloc(1, sizeof(role_object)); + if (this_role == NULL ) + { + return ENOMEM; + } + + /* Check the entry is OK */ + /* Determine role type and assign to structure */ + /* We determine the role type by reading the objectclass */ + if ( roles_cache_is_role_entry(role_entry) == 0 ) + { + /* Bad type */ + slapi_ch_free((void**)&this_role); + return SLAPI_ROLE_DEFINITION_ERROR; + } + + type = roles_cache_determine_class(role_entry); + + if (type != 0) + { + this_role->type = type; + } + else + { + /* Bad type */ + slapi_ch_free((void**)&this_role); + return SLAPI_ROLE_DEFINITION_ERROR; + } + + this_role->dn = slapi_sdn_new(); + slapi_sdn_copy(slapi_entry_get_sdn(role_entry),this_role->dn); + + /* Depending upon role type, pull out the remaining information we need */ + switch (this_role->type) + { + case ROLE_TYPE_MANAGED: + + /* Nothing further needed */ + break; + + case ROLE_TYPE_FILTERED: + { + + Slapi_Filter *filter = NULL; + char *filter_attr_value = NULL; + + /* Get the filter and retrieve the filter attribute */ + filter_attr_value = slapi_entry_attr_get_charptr(role_entry,ROLE_FILTER_ATTR_NAME); + if ( filter_attr_value == NULL ) + { + /* Means probably no attribute or no value there */ + slapi_ch_free((void**)&this_role); + return SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED; + } + + /* Turn it into a slapi filter object */ + filter = slapi_str2filter(filter_attr_value); + slapi_ch_free((void**)&filter_attr_value); + + if ( filter == NULL ) + { + /* An error has occured */ + slapi_ch_free((void**)&this_role); + return SLAPI_ROLE_ERROR_FILTER_BAD; + } + /* Store on the object */ + this_role->filter = filter; + + break; + } + + case ROLE_TYPE_NESTED: + { + Slapi_Attr *attr = NULL; + + /* Get the list of nested roles */ + rc = slapi_entry_attr_find(role_entry,ROLE_NESTED_ATTR_NAME,&attr); + + if ( (rc == 0) && attr) + { + /* Recurse to get the definition objects for them */ + Slapi_Value **va = attr_get_present_values(attr); + int i = 0; + char *string = NULL; + Slapi_DN nested_role_dn; + role_object_nested *nested_role_object = NULL; + + for ( i = 0; va[i] != NULL; i++ ) + { + string = (char*)slapi_value_get_string(va[i]); + + /* Make a DN from the string */ + slapi_sdn_init_dn_byref(&nested_role_dn,string); + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_cache_create_object_from_entry: dn %s, nested %s\n", + (char*)slapi_sdn_get_ndn(this_role->dn),string); + + /* Make a role object nested from the DN */ + rc = roles_cache_object_nested_from_dn(&nested_role_dn,&nested_role_object); + + /* Insert it into the nested list */ + if ( (rc == 0) && nested_role_object) + { + /* Add to the tree where avl_data is a role_object_nested struct */ + rc = roles_cache_insert_object_nested(&(this_role->avl_tree),nested_role_object); + } + slapi_sdn_done(&nested_role_dn); + } + } + + break; + } + + default: + slapi_log_error(SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, "wrong role type\n"); + } + + if ( rc == 0 ) + { + *result = this_role; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "<-- roles_cache_create_object_from_entry\n"); + + + return rc; +} + +/* roles_cache_determine_class: + ---------------------------- + Determine the type of role depending on the objectclass + Return the type of the role + */ +static int roles_cache_determine_class(Slapi_Entry *role_entry) +{ + /* Examine the entry's objectclass attribute */ + int found_managed = 0; + int found_filtered = 0; + int found_nested = 0; + Slapi_Attr *attr= NULL; + struct berval bv = {0}; + int rc = 0; + int type = 0; + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "--> roles_cache_determine_class\n"); + + rc = slapi_entry_attr_find(role_entry,"objectclass",&attr); + if ( rc != 0 ) + { + /* No objectclass, definitely an error */ + return 0; + } + + berval_set_string(&bv,ROLE_OBJECTCLASS_MANAGED); + rc = slapi_attr_value_find(attr,&bv); + if ( rc == 0 ) + { + found_managed = 1; + type = ROLE_TYPE_MANAGED; + } + + berval_set_string(&bv,ROLE_OBJECTCLASS_FILTERED); + rc = slapi_attr_value_find(attr,&bv); + if ( rc == 0 ) + { + found_filtered = 1; + type = ROLE_TYPE_FILTERED; + } + + berval_set_string(&bv,ROLE_OBJECTCLASS_NESTED); + rc = slapi_attr_value_find(attr,&bv); + if ( rc == 0 ) + { + found_nested = 1; + type = ROLE_TYPE_NESTED; + } + + if ( (found_managed + found_nested + found_filtered) > 1 ) + { + /* Means some goofball configured a role definition which is trying to be more than one different type. error. */ + return 0; + } + + if ( (found_managed + found_nested + found_filtered) == 0) + { + /* Means this entry isn't any of the role types we handle. error. */ + return 0; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "<-- roles_cache_determine_class\n"); + + /* Return the appropriate type ordinal */ + return type; +} + +/* roles_cache_node_cmp: + --------------------- + Comparison function to add a new node in the avl tree (avl_data is of type role_object) + */ +static int roles_cache_node_cmp( caddr_t d1, caddr_t d2 ) +{ + role_object *role_to_insert = (role_object*)d1; + role_object *current_role = (role_object*)d2; + + /* role_to_insert and current_role are never NULL in that context */ + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_node_cmp\n"); + + return (slapi_sdn_compare((Slapi_DN *)role_to_insert->dn, (Slapi_DN *)current_role->dn)); +} + +/* roles_cache_insert_object: + -------------------------- + Insert a new node in the avl tree of a specific suffix + */ +static int roles_cache_insert_object(Avlnode **tree, role_object *object) +{ + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_cache_insert_object: %s in tree %x\n", + (char*)slapi_sdn_get_ndn(object->dn), + *tree); + return (avl_insert(tree, (caddr_t)object, roles_cache_node_cmp, avl_dup_error)); +} + +/* roles_cache_node_nested_cmp: + ---------------------------- + Comparison function to add a new node in the avl tree + */ +static int roles_cache_node_nested_cmp( caddr_t d1, caddr_t d2 ) +{ + role_object_nested *role_to_insert = (role_object_nested*)d1; + role_object_nested *current_role = (role_object_nested*)d2; + + /* role_to_insert and current_role are never NULL in that context */ + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "roles_cache_node_nested_cmp\n"); + + return slapi_sdn_compare(role_to_insert->dn, current_role->dn); +} + +/* roles_cache_insert_object_nested: + --------------------------------- + Insert a new node in the avl tree of a specific suffix + */ +static int roles_cache_insert_object_nested(Avlnode **tree, role_object_nested *object) +{ + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_cache_insert_object_nested: %s in tree %x: \n", + (char*)slapi_sdn_get_ndn(object->dn), *tree); + + return (avl_insert(tree, (caddr_t)object, roles_cache_node_nested_cmp, avl_dup_error)); +} + +/* roles_cache_object_nested_from_dn + ---------------------------------- + Get the role associated to an entry DN + Return 0: OK + Return ENOMEM: fail + */ +static int roles_cache_object_nested_from_dn(Slapi_DN *role_dn, role_object_nested **result) +{ + role_object_nested *nested_role = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "--> roles_cache_object_nested_from_dn\n"); + + *result = NULL; + + /* Create the role cache definition */ + nested_role = (role_object_nested*)slapi_ch_calloc(1, sizeof(role_object_nested)); + if (nested_role == NULL ) + { + return ENOMEM; + } + + nested_role->dn = slapi_sdn_new(); + slapi_sdn_copy(role_dn,nested_role->dn); + *result = nested_role; + + slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "<-- roles_cache_object_nested_from_dn\n"); + return 0; +} + +/* roles_cache_listroles + -------------------- + Lists all the roles an entry posesses + return_values = 0 means that we don't need the nsrole values + return_values = 1 means that we need the nsrole values + Return 0: the entry has nsrole + Return -1: the entry has no nsrole + */ +int roles_cache_listroles(Slapi_Entry *entry, int return_values, Slapi_ValueSet **valueset_out) +{ + roles_cache_def *roles_cache = NULL; + role_object *this_role = NULL; + int rc = 0; + Avlnode * tree = NULL; + roles_cache_build_result arg; + Slapi_Backend *backend = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_listroles\n"); + + backend = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(entry)); + if ( (backend != NULL) && slapi_be_is_flag_set(backend,SLAPI_BE_FLAG_REMOTE_DATA) ) + { + /* the entry is not local, so don't return anything */ + return (-1); + } + + if ( return_values ) + { + *valueset_out = (Slapi_ValueSet*)slapi_ch_calloc(1,sizeof(Slapi_ValueSet)); + slapi_valueset_init(*valueset_out); + } + + /* First get a list of all the in-scope roles */ + /* XXX really need a mutex for this read operation ? */ + PR_RWLock_Rlock(global_lock); + + rc = roles_cache_find_roles_in_suffix( slapi_entry_get_sdn(entry),&roles_cache); + + PR_RWLock_Unlock(global_lock); + + /* Traverse the tree checking if the entry has any of the roles */ + if ( roles_cache != NULL ) + { + if ( roles_cache->avl_tree ) + { + arg.nsrole_values = valueset_out; + arg.need_value = return_values; + arg.requested_entry = entry; + arg.has_value = 0; + + /* XXX really need a mutex for this read operation ? */ + slapi_lock_mutex(roles_cache->cache_lock); + + avl_apply(roles_cache->avl_tree, (IFP)roles_cache_build_nsrole, &arg, -1, AVL_INORDER); + + slapi_unlock_mutex(roles_cache->cache_lock); + + if( !arg.has_value ) + { + if ( return_values ) + { + slapi_valueset_free(*valueset_out); + *valueset_out = NULL; + } + rc = -1; + } + /* Free the list (we already did that) */ + } + else + { + if ( return_values ) + { + slapi_valueset_free(*valueset_out); + *valueset_out = NULL; + } + rc = -1; + } + } + else + { + /* no roles associated */ + rc = -1; + } + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_listroles\n"); + return rc; +} + +/* roles_cache_build_nsrole + ------------------------ + Traverse the tree containing roles definitions for a suffix and for each + one of them, check wether the entry is a member of it or not + For ones which check out positive, we add their DN to the value + always return 0 to allow to trverse all the tree + */ +static int roles_cache_build_nsrole( caddr_t data, caddr_t arg ) +{ + Slapi_Value *value = NULL; + roles_cache_build_result *result = (roles_cache_build_result*)arg; + role_object *this_role = (role_object*)data; + roles_cache_search_in_nested get_nsrole; + /* Return a value different from the stop flag to be able + to go through all the tree */ + int rc = 0; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_build_nsrole: role %s\n", + (char*) slapi_sdn_get_ndn(this_role->dn)); + + value = slapi_value_new_string(""); + + get_nsrole.is_entry_member_of = result->requested_entry; + get_nsrole.present = 0; + get_nsrole.hint = 0; + + roles_is_entry_member_of_object((caddr_t)this_role, (caddr_t)&get_nsrole); + + /* If so, add its DN to the attribute */ + if (get_nsrole.present) + { + result->has_value = 1; + if ( result->need_value ) + { + slapi_value_set_string(value,(char*) slapi_sdn_get_ndn(this_role->dn)); + slapi_valueset_add_value(*(result->nsrole_values),value); + } + else + { + /* we don't need the value but we already know there is one nsrole. + stop the traversal + */ + rc = -1; + } + } + + slapi_value_free(&value); + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_build_nsrole\n"); + + return rc; +} + + +/* roles_check + ----------- + Checks if an entry has a presented role, assuming that we've already verified +that + the role both exists and is in scope + return 0: no processing error + return -1: error + */ +int roles_check(Slapi_Entry *entry_to_check, Slapi_DN *role_dn, int *present) +{ + roles_cache_def *roles_cache = NULL; + role_object *this_role = NULL; + roles_cache_search_in_nested get_nsrole; + + int rc = 0; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_check\n"); + + *present = 0; + + PR_RWLock_Rlock(global_lock); + + if ( roles_cache_find_roles_in_suffix(slapi_entry_get_sdn(entry_to_check), + &roles_cache) != 0 ) + { + PR_RWLock_Unlock(global_lock); + return -1; + } + PR_RWLock_Unlock(global_lock); + + this_role = (role_object *)avl_find(roles_cache->avl_tree, role_dn, (IFP)roles_cache_find_node); + + /* MAB: For some reason the assumption made by this function (the role exists and is in scope) + * does not seem to be true... this_role might be NULL after the avl_find call (is the avl_tree + * broken? Anyway, this is crashing the 5.1 server on 29-Aug-01, so I am applying the following patch + * to avoid the crash inside roles_is_entry_member_of_object */ + /* Begin patch */ + if (!this_role) { + /* Assume that the entry is not member of the role (*present=0) and leave... */ + return rc; + } + /* End patch */ + + get_nsrole.is_entry_member_of = entry_to_check; + get_nsrole.present = 0; + get_nsrole.hint = 0; + + roles_is_entry_member_of_object((caddr_t)this_role, (caddr_t)&get_nsrole); + *present = get_nsrole.present; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_check\n"); + + return rc; +} + +/* roles_cache_find_node: + --------------------- + Comparison function to add a new node in the avl tree + */ +static int roles_cache_find_node( caddr_t d1, caddr_t d2 ) +{ + Slapi_DN *data = (Slapi_DN *)d1; + role_object *role= (role_object *)d2; + + /* role is not NULL in that context */ + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_cache_find_node: %s %s\n", + slapi_sdn_get_dn(data), slapi_sdn_get_dn(role->dn)); + + return (slapi_sdn_compare(data, (Slapi_DN *)role->dn)); +} + +/* roles_cache_find_roles_in_suffix + ------------------------------- + Find all the roles in scope to an entry + */ +static int roles_cache_find_roles_in_suffix(Slapi_DN *target_entry_dn, roles_cache_def **list_of_roles) +{ + int rc = -1; + Slapi_Backend *backend = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_find_roles_in_suffix\n"); + + *list_of_roles = NULL; + backend = slapi_mapping_tree_find_backend_for_sdn(target_entry_dn); + if ( (backend != NULL) && !slapi_be_is_flag_set(backend,SLAPI_BE_FLAG_REMOTE_DATA) ) + { + Slapi_DN *suffix = roles_cache_get_top_suffix(*(backend->be_suffix)); + roles_cache_def *current_role = roles_list; + + /* Go through all the roles list and trigger the associated structure */ + while ( (current_role != NULL) && (suffix != NULL) ) + { + if ( slapi_sdn_compare(current_role->suffix_dn, suffix) == 0 ) + { + *list_of_roles = current_role; + /* OK, we have found one */ + slapi_sdn_free(&suffix); + return 0; + } + else + { + current_role = current_role->next; + } + } + if ( suffix != NULL ) + { + slapi_sdn_free(&suffix); + } + /* If we got out that way, means that we didn't have find + roles definitions for that suffix */ + return rc; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_find_roles_in_suffix\n"); + return rc; +} + +/* roles_is_entry_member_of_object + -------------------------------- + Check if the entry is part of a role defined in its suffix + return 0: ok + return 1: fail + -> to check the presence, see present + */ +static int roles_is_entry_member_of_object(caddr_t data, caddr_t argument ) +{ + int rc = -1; + + roles_cache_search_in_nested *get_nsrole = (roles_cache_search_in_nested*)argument; + role_object *this_role = (role_object*)data; + + Slapi_Entry *entry_to_check = get_nsrole->is_entry_member_of; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_is_entry_member_of_object\n"); + + if (!roles_is_inscope(entry_to_check, this_role->dn)) + { + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_is_entry_member_of_object-> entry not in scope of role\n"); + return rc; + } + + if ( this_role != NULL ) + { + /* Determine the role type */ + switch (this_role->type) + { + case ROLE_TYPE_MANAGED: + rc = roles_check_managed(entry_to_check,this_role,&get_nsrole->present); + break; + case ROLE_TYPE_FILTERED: + rc = roles_check_filtered(entry_to_check,this_role,&get_nsrole->present); + break; + case ROLE_TYPE_NESTED: + { + /* Go through the tree of the nested DNs */ + get_nsrole->hint++; + avl_apply(this_role->avl_tree, (IFP)roles_check_nested, get_nsrole, 0, AVL_INORDER); + get_nsrole->hint--; + + /* kexcoff?? */ + rc = get_nsrole->present; + break; + } + default: + slapi_log_error(SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, "roles_is_entry_member_of_object-> invalid role type\n"); + } + } + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_is_entry_member_of_object\n"); + return rc; +} + +/* roles_check_managed + ------------------------- + Check a managed role: we just need to check the content of the entry's nsRoleDN attribute + against the role DN + return 0: ok + return 1: fail + -> to check the presence, see present + */ +static int roles_check_managed(Slapi_Entry *entry_to_check, role_object *role, int *present) +{ + int rc = 0; + Slapi_Attr *attr = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_check_managed\n"); + /* Get the attribute */ + rc = slapi_entry_attr_find(entry_to_check,ROLE_MANAGED_ATTR_NAME,&attr); + + if ( rc == 0) + { + struct berval bv = {0}; + char *dn_string = NULL; + + /* Check content against the presented DN */ + /* We assume that this function handles normalization and so on */ + dn_string = (char*) slapi_sdn_get_ndn(role->dn); + berval_set_string(&bv,dn_string); + rc = slapi_attr_value_find(attr,&bv); + if ( rc == 0 ) + { + *present = 1; + } + } + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, + "<-- roles_check_managed: entry %s role %s present %d\n", + slapi_entry_get_dn_const(entry_to_check),(char*)slapi_sdn_get_ndn(role->dn),*present); + return rc; +} + +/* roles_check_filtered + -------------------------- + Check a filtered role: call slapi_filter_test here on the entry + and the filter from the role object + return 0: ok + return 1: fail + -> to check the presence, see present + */ +static int roles_check_filtered(Slapi_Entry *entry_to_check, role_object *role, int *present) +{ + int rc = 0; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_check_filtered\n"); + rc = slapi_filter_test_simple(entry_to_check,role->filter); + if ( rc == 0 ) + { + *present = 1; + } + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, + "<-- roles_check_filtered: entry %s role %s present %d\n", + slapi_entry_get_dn_const(entry_to_check),(char*)slapi_sdn_get_ndn(role->dn),*present); + return rc; +} + + +/* roles_check_nested + ------------------------ + Check a nested role + return 0: ok + return -1: fail + -> to check the presence, see present + */ +static int roles_check_nested(caddr_t data, caddr_t arg) +{ + roles_cache_search_in_nested *get_nsrole = (roles_cache_search_in_nested*)arg; + int rc = -1; + role_object_nested *current_nested_role = (role_object_nested*)data; + + + /* do not allow circular dependencies, the cheap and easy way */ + if( get_nsrole->hint > MAX_NESTED_ROLES) + { + char *ndn = NULL; + + ndn = slapi_entry_get_ndn( get_nsrole->is_entry_member_of ); + slapi_log_error(SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, + "Maximum roles nesting exceeded (max %d current %d), not checking roles in entry %s--probable circular definition\n", + MAX_NESTED_ROLES, + get_nsrole->hint, + ndn); + + /* Stop traversal value */ + return 0; + } + + /* Here we traverse the list of nested roles, calling the appropriate + evaluation function for those in turn */ + + if (current_nested_role) + { + roles_cache_def *roles_cache = NULL; + role_object *this_role = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, + "-->roles_check_nested: entry %s role %s present %d\n", + slapi_entry_get_dn_const(get_nsrole->is_entry_member_of), + (char*)slapi_sdn_get_ndn(current_nested_role->dn), + get_nsrole->present); + + if ( roles_cache_find_roles_in_suffix(current_nested_role->dn, + &roles_cache) != 0 ) + { + return rc; + } + + if ( slapi_is_loglevel_set(SLAPI_LOG_PLUGIN) ) + { + avl_apply(roles_cache->avl_tree, (IFP)roles_cache_dump, &rc, -1, AVL_INORDER); + } + + this_role = (role_object *)avl_find(roles_cache->avl_tree, + current_nested_role->dn, + (IFP)roles_cache_find_node); + + if ( this_role == NULL ) + { + /* the nested role doesn't exist */ + slapi_log_error(SLAPI_LOG_FATAL, + ROLES_PLUGIN_SUBSYSTEM, + "The nested role %s doesn't exist\n", + (char*)slapi_sdn_get_ndn(current_nested_role->dn)); + return rc; + } + /* get the role_object data associated to that dn */ + if ( roles_is_inscope(get_nsrole->is_entry_member_of, this_role->dn) ) + { + /* The list of nested roles is contained in the role definition */ + roles_is_entry_member_of_object((caddr_t)this_role, (caddr_t)get_nsrole); + if ( get_nsrole->present == 1 ) + { + return 0; + } + } + } + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_check_nested\n"); + return rc; +} + +/* roles_is_inscope + ---------------------- + Tells us if a presented role is in scope with respect to the presented entry + */ +static int roles_is_inscope(Slapi_Entry *entry_to_check, Slapi_DN *role_dn) +{ + int rc; + + Slapi_DN role_parent; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_is_inscope\n"); + + slapi_sdn_init(&role_parent); + slapi_sdn_get_parent(role_dn,&role_parent); + + rc = slapi_sdn_scope_test(slapi_entry_get_sdn( entry_to_check ), + &role_parent, + LDAP_SCOPE_SUBTREE); + /* we need to check whether the entry would be returned by a view in scope */ + if(!rc && views_api) + { + rc = views_entry_exists(views_api, (char*)slapi_sdn_get_ndn(&role_parent), entry_to_check); + } + + slapi_sdn_done(&role_parent); + + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_is_inscope: entry %s role %s result %d\n", + slapi_entry_get_dn_const(entry_to_check),(char*)slapi_sdn_get_ndn(role_dn), rc); + + return (rc); +} + +static void berval_set_string(struct berval *bv, const char* string) +{ + bv->bv_len= strlen(string); + bv->bv_val= (void*)string; /* We cast away the const, but we're not going to change anything +*/ +} + +/* roles_cache_role_def_delete + ---------------------------- +*/ +static void roles_cache_role_def_delete(roles_cache_def *role_def) +{ + roles_cache_def *current = roles_list; + roles_cache_def *previous = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_def_delete\n"); + + while ( current!= NULL ) + { + if ( slapi_sdn_compare(current->suffix_dn, role_def->suffix_dn) == 0 ) + { + if ( previous== NULL ) + { + roles_list = current->next; + } + else + { + previous->next = current->next; + } + slapi_lock_mutex(role_def->change_lock); + role_def->keeprunning = 0; + slapi_notify_condvar(role_def->something_changed, 1 ); + slapi_unlock_mutex(role_def->change_lock); + break; + } + else + { + previous = current; + current = current->next; + } + } + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_def_delete\n"); +} + +/* roles_cache_role_def_free + ---------------------------- +*/ +static void roles_cache_role_def_free(roles_cache_def *role_def) +{ + roles_cache_def *next_def = NULL; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_def_free\n"); + if ( role_def == NULL ) + { + return; + } + + slapi_lock_mutex(role_def->stop_lock); + + avl_free(role_def->avl_tree, (IFP)roles_cache_role_object_free); + slapi_sdn_free(&(role_def->suffix_dn)); + slapi_destroy_mutex(role_def->cache_lock); + role_def->cache_lock = NULL; + slapi_destroy_mutex(role_def->change_lock); + role_def->change_lock = NULL; + slapi_destroy_condvar(role_def->something_changed); + slapi_destroy_mutex(role_def->create_lock); + role_def->create_lock = NULL; + slapi_destroy_condvar(role_def->suffix_created); + + slapi_ch_free((void**)&role_def->notified_dn); + if ( role_def->notified_entry != NULL ) + { + slapi_entry_free(role_def->notified_entry); + } + + slapi_unlock_mutex(role_def->stop_lock); + slapi_destroy_mutex(role_def->stop_lock); + role_def->stop_lock = NULL; + + slapi_ch_free((void**)&role_def); + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_def_free\n"); +} + +/* roles_cache_role_object_free + ---------------------------- +*/ +static void roles_cache_role_object_free(role_object *this_role) +{ + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_object_free\n"); + + if ( this_role == NULL ) + { + return; + } + + switch (this_role->type) + { + case ROLE_TYPE_MANAGED: + /* Nothing further needed */ + break; + case ROLE_TYPE_FILTERED: + /* Free the filter */ + if (this_role->filter) + { + slapi_filter_free(this_role->filter,1); + this_role->filter = NULL; + } + break; + case ROLE_TYPE_NESTED: + /* Free the list of nested roles */ + { + avl_free(this_role->avl_tree, roles_cache_role_object_nested_free); + } + break; + } + + slapi_sdn_free(&this_role->dn); + + /* Free the object */ + slapi_ch_free((void**)&this_role); + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_object_free\n"); +} + +/* roles_cache_role_object_nested_free + ------------------------------------ +*/ +static void roles_cache_role_object_nested_free(role_object_nested *this_role) +{ + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_object_nested_free\n"); + + if ( this_role == NULL ) + { + return; + } + + slapi_sdn_free(&this_role->dn); + + /* Free the object */ + slapi_ch_free((void**)&this_role); + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_object_nested_free\n"); +} + +static int roles_cache_dump( caddr_t data, caddr_t arg ) +{ + role_object *this_role = (role_object*)data; + + slapi_log_error(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "roles_cache_dump: %x - %s - %x\n", + this_role, (char*)slapi_sdn_get_ndn(this_role->dn), this_role->avl_tree); + + return 0; +} diff --git a/ldap/servers/plugins/roles/roles_cache.h b/ldap/servers/plugins/roles/roles_cache.h new file mode 100644 index 00000000..fbe6c641 --- /dev/null +++ b/ldap/servers/plugins/roles/roles_cache.h @@ -0,0 +1,52 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#if !defined( _ROLES_CACHE_H ) + +#define SLAPD_ROLES_INTERFACE "roles-slapd" +#define ROLES_PLUGIN_SUBSYSTEM "roles-plugin" +#define NSROLEATTR "nsRole" + +#define ROLE_DEFINITION_FILTER "(&(objectclass=nsRoleDefinition)(objectclass=ldapsubentry))" +#define OBJ_FILTER "(|(objectclass=*)(objectclass=ldapsubentry))" + +#define ROLE_TYPE_MANAGED 1 +#define ROLE_TYPE_FILTERED 2 +#define ROLE_TYPE_NESTED 3 + +#define ROLE_OBJECTCLASS_MANAGED "nsManagedRoleDefinition" +#define ROLE_OBJECTCLASS_FILTERED "nsFilteredRoleDefinition" +#define ROLE_OBJECTCLASS_NESTED "nsNestedRoleDefinition" + +#define ROLE_FILTER_ATTR_NAME "nsRoleFilter" +#define ROLE_MANAGED_ATTR_NAME "nsRoleDN" +#define ROLE_NESTED_ATTR_NAME "nsRoleDN" + +#define SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED -1 +#define SLAPI_ROLE_ERROR_FILTER_BAD -2 +#define SLAPI_ROLE_DEFINITION_DOESNT_EXIST -3 +#define SLAPI_ROLE_DEFINITION_ERROR -4 +#define SLAPI_ROLE_DEFINITION_ALREADY_EXIST -5 + +/* From roles_cache.c */ +int roles_cache_init(); +void roles_cache_stop(); +void roles_cache_change_notify(Slapi_PBlock *pb); +int roles_cache_listroles(Slapi_Entry *entry, int return_value, Slapi_ValueSet **valueset_out); + +int roles_check(Slapi_Entry *entry_to_check, Slapi_DN *role_dn, int *present); + +/* From roles_plugin.c */ +int roles_init( Slapi_PBlock *pb ); +int roles_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint); + +int roles_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result,int flags, void *hint); + +int roles_sp_list_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags); + +void * roles_get_plugin_identity(); + +#endif /* _ROLES_CACHE_H */ diff --git a/ldap/servers/plugins/roles/roles_plugin.c b/ldap/servers/plugins/roles/roles_plugin.c new file mode 100644 index 00000000..d1ce5903 --- /dev/null +++ b/ldap/servers/plugins/roles/roles_plugin.c @@ -0,0 +1,254 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + Code to implement server roles features +*/ + +#include "slap.h" + +#include "vattr_spi.h" + +#include "roles_cache.h" +#include "statechange.h" + + +#ifdef SOURCEFILE +#undef SOURCEFILE +#endif +#define SOURCEFILE "roles_plugin.c" +static char *sourcefile = SOURCEFILE; + +#define STATECHANGE_ROLES_ID "Roles" +#define STATECHANGE_ROLES_CONFG_FILTER "objectclass=nsRoleDefinition" +#define STATECHANGE_ROLES_ENTRY_FILTER "objectclass=*" + +#define ROLES_PLUGIN_SUBSYSTEM "roles-plugin" /* for logging */ +static void * roles_plugin_identity = NULL; + +static Slapi_PluginDesc pdesc = { "roles", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "roles plugin" }; + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +static int roles_start( Slapi_PBlock *pb ); +static int roles_post_op( Slapi_PBlock *pb ); +static int roles_close( Slapi_PBlock *pb ); +static void roles_set_plugin_identity(void * identity); + +/* roles_init + ---------- + Initialization of the plugin + */ +int roles_init( Slapi_PBlock *pb ) +{ + int rc = 0; + void *plugin_identity = NULL; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "=> roles_init\n" ); + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + PR_ASSERT (plugin_identity); + roles_set_plugin_identity(plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *)SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, + (void *)roles_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, + (void *) roles_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, + (void *) roles_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, + (void *) roles_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, + (void *) roles_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) roles_close ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM, + "roles_init failed\n" ); + rc = -1; + } + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "<= roles_init %d\n", rc ); + return rc; +} + +/* roles_start + ----------- + kexcoff: cache build at init or at startup ? + */ +static int roles_start( Slapi_PBlock *pb ) +{ + int rc = 0; + void **statechange_api; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "=> roles_start\n" ); + + roles_cache_init(); + + /* from Pete Rowley for vcache + * PLUGIN DEPENDENCY ON STATECHANGE PLUGIN + * + * register objectclasses which indicate a + * role configuration entry, and therefore + * a globally significant change for the vcache + */ + + if(!slapi_apib_get_interface(StateChange_v1_0_GUID, &statechange_api)) + { + statechange_register(statechange_api, STATECHANGE_ROLES_ID, NULL, STATECHANGE_ROLES_CONFG_FILTER, &vattr_global_invalidate, (notify_callback) statechange_vattr_cache_invalidator_callback(statechange_api)); + } + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "<= roles_start %d\n", rc ); + return rc; +} + +/* roles_close + ----------- + kexcoff: ?? + */ +static int roles_close( Slapi_PBlock *pb ) +{ + int rc = 0; + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "=> roles_close\n" ); + + roles_cache_stop(); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, + "<= roles_close %d\n", rc ); + return rc; +} + +/* roles_sp_get_value + ------------------ + Enumerate the values of the role attribute. + We do this by first locating all the roles which are in scope + Then we iterate over the in-scope roles calling Slapi_Role_Check(). + For those which pass the check, we add their DN to the attribute's value set. +*/ +int roles_sp_get_value(vattr_sp_handle *handle, + vattr_context *c, + Slapi_Entry *e, + char *type, + Slapi_ValueSet** results, + int *type_name_disposition, + char** actual_type_name, + int flags, + int *free_flags, + void *hint) +{ + int rc = -1; + + rc = roles_cache_listroles(e, 1, results); + if (rc == 0) + { + *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; + *actual_type_name = strdup(NSROLEATTR); + + if (type_name_disposition) + { + *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; + } + } + + /* Need to check the return code here because the caller + doesn't understand roles return codes */ + + return rc; +} + + +/* roles_sp_compare_value + ---------------------- + Compare the value of the role attribute with a presented value. + Return true or false to the client. + */ + +int roles_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result,int flags, void *hint) +{ + int rc = 0; + Slapi_DN the_dn; + + /* Extract the role's DN from the value passed in */ + slapi_sdn_init_dn_byref(&the_dn,slapi_value_get_string(test_this)); + + return (roles_check(e,&the_dn,result)); +} + +int roles_sp_list_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags) +{ + static char* test_type_name = NSROLEATTR; + int ret =0; + + if ( 0 == ( flags & SLAPI_VIRTUALATTRS_LIST_OPERATIONAL_ATTRS )) { + /* + * Operational attributes were NOT requested. Since the only + * attribute type we service is nsRole which IS operational, + * there is nothing for us to do in this case. + */ + return 0; + } + + ret = roles_cache_listroles(e, 0, NULL); + if(ret == 0) + { + vattr_type_thang thang = {0}; + thang.type_name = test_type_name; + thang.type_flags = SLAPI_ATTR_FLAG_OPATTR; + slapi_vattrspi_add_type(type_context,&thang,SLAPI_VIRTUALATTRS_REQUEST_POINTERS); + } + return 0; +} + +/* What do we do on shutdown ? */ +int roles_sp_cleanup() +{ + return 0; +} + +/* roles_post_op + ----------- + Catch all for all post operations that change entries + in some way - this simply notifies the cache of a + change - the cache decides if action is necessary +*/ +static int roles_post_op( Slapi_PBlock *pb ) +{ + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_post_op\n"); + + roles_cache_change_notify(pb); + + slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_post_op\n"); + return 0; /* always succeed */ +} + +static void roles_set_plugin_identity(void * identity) +{ + roles_plugin_identity=identity; +} + +void * roles_get_plugin_identity() +{ + return roles_plugin_identity; +} + diff --git a/ldap/servers/plugins/shared/Makefile b/ldap/servers/plugins/shared/Makefile new file mode 100644 index 00000000..847735ee --- /dev/null +++ b/ldap/servers/plugins/shared/Makefile @@ -0,0 +1,56 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for shared components for Directory Server plugins +# +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +SHARED=shared + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +#NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/$(SHARED) +LIBDIR = $(LDAP_LIBDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd + +LOCAL_OBJS= utils.o + +OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS += $(LIBSLAPD) +endif + +all: $(OBJDEST) $(OBJS) + +veryclean: clean + +clean: + $(RM) $(OBJS) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(OBJS): plugin-utils.h + diff --git a/ldap/servers/plugins/shared/plugin-utils.h b/ldap/servers/plugins/shared/plugin-utils.h new file mode 100644 index 00000000..31c956f4 --- /dev/null +++ b/ldap/servers/plugins/shared/plugin-utils.h @@ -0,0 +1,77 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/*********************************************************************** +** +** NAME +** plugin-utils.h +** +** DESCRIPTION +** +** +** AUTHOR +** <rweltman@netscape.com> +** +***********************************************************************/ + +#ifndef _PLUGIN_UTILS_H_ +#define _PLUGIN_UTILS_H_ + +/*********************************************************************** +** Includes +***********************************************************************/ + +#include <slapi-plugin.h> +/* + * slapi-plugin-compat4.h is needed because we use the following deprecated + * functions: + * + * slapi_search_internal() + * slapi_modify_internal() + */ +#include "slapi-plugin-compat4.h" +#include <dirlite_strings.h> +#include <stdio.h> +#include <string.h> +#ifdef _WINDOWS +#undef strcasecmp +#define strcasecmp strcmpi +#endif +#include "dirver.h" + +#ifdef LDAP_DEBUG +#ifndef DEBUG +#define DEBUG +#endif +#endif + +#define BEGIN do { +#define END } while(0); + +int initCounterLock(); +int op_error(int internal_error); +Slapi_PBlock *readPblockAndEntry( const char *baseDN, const char *filter, + char *attrs[] ); +int entryHasObjectClass(Slapi_PBlock *pb, Slapi_Entry *e, + const char *objectClass); +Slapi_PBlock *dnHasObjectClass( const char *baseDN, const char *objectClass ); +Slapi_PBlock *dnHasAttribute( const char *baseDN, const char *attrName ); +int setCounter( Slapi_Entry *e, const char *attrName, int value ); +int updateCounter( Slapi_Entry *e, const char *attrName, int increment ); +int updateCounterByDN( const char *dn, const char *attrName, int increment ); + +typedef struct DNLink { + char *dn; + void *data; + struct DNLink *next; +} DNLink; + +DNLink *cacheInit( void ); +DNLink *cacheAdd( DNLink *root, char *dn, void *data ); +char *cacheRemove( DNLink *root, char *dn ); +int cacheDelete( DNLink *root, char *dn ); +DNLink *cacheFind( DNLink *root, char *dn ); + +#endif /* _PLUGIN_UTILS_H_ */ diff --git a/ldap/servers/plugins/shared/utils.c b/ldap/servers/plugins/shared/utils.c new file mode 100644 index 00000000..d5044b17 --- /dev/null +++ b/ldap/servers/plugins/shared/utils.c @@ -0,0 +1,467 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/*********************************************************************** +** NAME +** utils.c +** +** DESCRIPTION +** +** +** AUTHOR +** <rweltman@netscape.com> +** +***********************************************************************/ + + +/*********************************************************************** +** Includes +***********************************************************************/ + +#include "plugin-utils.h" + +static char *plugin_name = "utils"; + +/* + * Lock for updating a counter (global for all counters) + */ +static Slapi_Mutex *counter_lock = NULL; + +/* ------------------------------------------------------------ */ +/* + * op_error - Record (and report) an operational error. + */ +int +op_error(int internal_error) { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "Internal error: %d\n", internal_error); + + return LDAP_OPERATIONS_ERROR; +} + +int initCounterLock() { + if ( NULL == counter_lock ) { + if ( !(counter_lock = slapi_new_mutex()) ) { + return 200; + } + } + return 0; +} + +/* ------------------------------------------------------------ */ +/* + * readPblockAndEntry - search for and read an entry + * Return: + * A pblock containing the entry, or NULL + */ +Slapi_PBlock * +readPblockAndEntry( const char *baseDN, const char *filter, + char *attrs[] ) { + int result = 0; + Slapi_PBlock *spb = NULL; + + BEGIN + int sres; + + /* Perform the search - the new pblock needs to be freed */ + spb = slapi_search_internal((char *)baseDN, LDAP_SCOPE_BASE, + (char *)filter, NULL, attrs, 0); + if ( !spb ) { + result = op_error(20); + break; + } + + if ( slapi_pblock_get( spb, SLAPI_PLUGIN_INTOP_RESULT, &sres ) ) { + result = op_error(21); + break; + } else if (sres) { + result = op_error(22); + break; + } + END + + return spb; +} + +/* ------------------------------------------------------------ */ +/* + * hasObjectClass - read an entry and check if it has a + * particular object class value + * Return: + * 1 - the entry contains the object class value + * 0 - the entry doesn't contain the object class value + */ +int +entryHasObjectClass(Slapi_PBlock *pb, Slapi_Entry *e, + const char *objectClass) { + Slapi_Attr *attr; + Slapi_Value *v; + const struct berval *bv; + int vhint; + + if ( slapi_entry_attr_find(e, "objectclass", &attr) ) { + return 0; /* no objectclass values! */ + } + + /* + * Check each of the object class values in turn. + */ + for ( vhint = slapi_attr_first_value( attr, &v ); + vhint != -1; + vhint = slapi_attr_next_value( attr, vhint, &v )) { + bv = slapi_value_get_berval(v); + if ( NULL != bv && NULL != bv->bv_val && + !strcasecmp(bv->bv_val, objectClass) ) { + return 1; + } + } + return 0; +} + +/* ------------------------------------------------------------ */ +/* + * dnHasObjectClass - read an entry if it has a particular object class + * Return: + * A pblock containing the entry, or NULL + */ +Slapi_PBlock * +dnHasObjectClass( const char *baseDN, const char *objectClass ) { + int result = 0; + Slapi_PBlock *spb = NULL; + + BEGIN + Slapi_Entry **entries; + char filter[1024]; + char *attrs[2]; + + /* Perform the search - the new pblock needs to be freed */ + attrs[0] = "objectclass"; + attrs[1] = NULL; + sprintf( filter, "objectclass=%s", objectClass ); + if ( !(spb = readPblockAndEntry( baseDN, filter, attrs) ) ) { + break; + } + + if ( slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &entries) ) { + result = op_error(23); + break; + } + /* + * Can only be one entry returned on a base search; just check + * the first one + */ + if ( !*entries ) { + /* Clean up */ + slapi_free_search_results_internal(spb); + slapi_pblock_destroy(spb); + spb = NULL; + } + END + + return spb; +} + +/* ------------------------------------------------------------ */ +/* + * dnHasAttribute - read an entry if it has a particular attribute + * Return: + * The entry, or NULL + */ +Slapi_PBlock * +dnHasAttribute( const char *baseDN, const char *attrName ) { + int result = 0; + Slapi_PBlock *spb = NULL; + + BEGIN + int sres; + Slapi_Entry **entries; + char filter[1024]; + char *attrs[2]; + + /* Perform the search - the new pblock needs to be freed */ + attrs[0] = (char *)attrName; + attrs[1] = NULL; + sprintf( filter, "%s=*", attrName ); + spb = slapi_search_internal((char *)baseDN, LDAP_SCOPE_BASE, + filter, NULL, attrs, 0); + if ( !spb ) { + result = op_error(20); + break; + } + + if ( slapi_pblock_get( spb, SLAPI_PLUGIN_INTOP_RESULT, &sres ) ) { + result = op_error(21); + break; + } else if (sres) { + result = op_error(22); + break; + } + + if ( slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &entries) ) { + result = op_error(23); + break; + } + /* + * Can only be one entry returned on a base search; just check + * the first one + */ + if ( !*entries ) { + /* Clean up */ + slapi_free_search_results_internal(spb); + slapi_pblock_destroy(spb); + spb = NULL; + } + END + + return spb; +} + +/* ------------------------------------------------------------ */ +/* + * setCounter - set the value of a counter + * + * Return: + * LDAP_SUCCESS - updated the attribute + * other - failure to update the count + */ +int +setCounter( Slapi_Entry *e, const char *attrName, int value ) { + int result = LDAP_SUCCESS; + Slapi_PBlock *modifySpb = NULL; + char strValue[16]; + char *strValues[2] = { NULL }; + LDAPMod mod; + LDAPMod *mods[2]; + int res; + + BEGIN + /* Store the updated value */ + strValues[0] = strValue; + sprintf( strValue, "%d", value ); + mod.mod_op = LDAP_MOD_REPLACE; + mod.mod_type = (char *)attrName; + mod.mod_values = strValues; + mods[0] = &mod; + mods[1] = NULL; + modifySpb = slapi_modify_internal( slapi_entry_get_dn(e), mods, + NULL, 1 ); + /* Check if the operation succeeded */ + if ( slapi_pblock_get( modifySpb, SLAPI_PLUGIN_INTOP_RESULT, + &res ) ) { + result = op_error(33); + break; + } else if (res) { + result = op_error(34); + break; + } + slapi_pblock_destroy(modifySpb); + END + return result; +} + +/* ------------------------------------------------------------ */ +/* + * updateCounter - read and increment/decrement the value of a counter + * + * Return: + * LDAP_SUCCESS - updated the attribute + * other - failure to update the count + */ +int +updateCounter( Slapi_Entry *e, const char *attrName, int increment ) { + int result = LDAP_SUCCESS; + Slapi_PBlock *modifySpb = NULL; + Slapi_Attr *attr; + int value = 0; + char strValue[16]; + char *strValues[2] = { NULL }; + LDAPMod mod; + LDAPMod *mods[2]; + int res; + + BEGIN + /* Lock the entry */ + slapi_lock_mutex(counter_lock); + /* Get the count attribute */ + if ( slapi_entry_attr_find(e, (char *)attrName, &attr) ) { + /* No count yet; that's OK */ + } else { + /* Get the first value for the attribute */ + Slapi_Value *v = NULL; + const struct berval *bv = NULL; + + if ( -1 == slapi_attr_first_value( attr, &v ) || NULL == v || + NULL == ( bv = slapi_value_get_berval(v)) || + NULL == bv->bv_val ) { + /* No values yet; that's OK, too */ + } else { + value = atoi( bv->bv_val ); + } + } + /* Add the increment */ + value += increment; + if ( value < 0 ) { + value = 0; + } + /* Store the updated value */ + strValues[0] = strValue; + sprintf( strValue, "%d", value ); + mod.mod_op = LDAP_MOD_REPLACE; + mod.mod_type = (char *)attrName; + mod.mod_values = strValues; + mods[0] = &mod; + mods[1] = NULL; + modifySpb = slapi_modify_internal( slapi_entry_get_dn(e), mods, + NULL, 1 ); + /* Check if the operation succeeded */ + if ( slapi_pblock_get( modifySpb, SLAPI_PLUGIN_INTOP_RESULT, + &res ) ) { + result = op_error(33); + break; + } else if (res) { + result = op_error(34); + break; + } + slapi_pblock_destroy(modifySpb); + /* Unlock the entry */ + slapi_unlock_mutex(counter_lock); +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "adjusted %s in %s by %d to %d\n", + attrName, slapi_entry_get_dn(e), increment, value); +#endif + + END + return result; +} + +/* ------------------------------------------------------------ */ +/* + * updateCounterByDN - read and increment/decrement the value of a counter + * + * Return: + * LDAP_SUCCESS - updated the attribute + * other - failure to update the count + */ +int +updateCounterByDN( const char *dn, const char *attrName, int increment ) { + int result = LDAP_SUCCESS; + Slapi_PBlock *spb = NULL; + Slapi_Entry **entries; + + BEGIN + char *attrs[2]; + int sres; + + /* Perform the search - the new pblock needs to be freed */ + attrs[0] = (char *)attrName; + attrs[1] = NULL; + spb = slapi_search_internal((char *)dn, LDAP_SCOPE_BASE, + "objectclass=*", NULL, attrs, 0); + if ( !spb ) { + result = op_error(20); + break; + } + + if ( slapi_pblock_get( spb, SLAPI_PLUGIN_INTOP_RESULT, &sres ) ) { + result = op_error(21); + break; + } else if (sres) { + result = op_error(22); + break; + } + + if ( slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &entries) ) { + result = op_error(23); + break; + } + END + if ( 0 == result ) { + result = updateCounter( *entries, attrName, increment ); + } + if ( NULL != spb ) { + /* Clean up */ + slapi_free_search_results_internal(spb); + slapi_pblock_destroy(spb); + } + return result; +} + +/* + * Lock for accessing a cache (global for all caches) + */ +static Slapi_Mutex *cache_lock = NULL; + +DNLink *cacheInit() { + DNLink *root; + slapi_lock_mutex(cache_lock); + root = (DNLink *)malloc( sizeof(DNLink) ); + root->next = NULL; + root->data = NULL; + root->dn = (char *)malloc(1); + root->dn[0] = 0; + slapi_unlock_mutex(cache_lock); + return root; +} + +DNLink *cacheAdd( DNLink *root, char *dn, void *data ) { + if ( NULL == root ) { + return NULL; + } + slapi_lock_mutex(cache_lock); + for( ; root->next; root = root->next ) { + } + root->next = (DNLink *)malloc( sizeof(DNLink) ); + root = root->next; + root->dn = dn; + root->data = data; + root->next = NULL; + slapi_unlock_mutex(cache_lock); + return root; +} + +char *cacheRemove( DNLink *root, char *dn ) { + char *found = NULL; + DNLink *current = root; + DNLink *prev = NULL; + if ( NULL == root ) { + return NULL; + } + slapi_lock_mutex(cache_lock); + for( ; current; prev = current, current = current->next ) { + if ( !strcmp( current->dn, dn ) ) { + found = current->dn; + prev->next = current->next; + slapi_ch_free( (void **)¤t ); + break; + } + } + slapi_unlock_mutex(cache_lock); + return found; +} + +int cacheDelete( DNLink *root, char *dn ) { + char *found = cacheRemove( root, dn ); + if ( found ) { + slapi_ch_free( (void **)&found ); + return 0; + } else { + return 1; + } +} + +DNLink *cacheFind( DNLink *root, char *dn ) { + if ( NULL == root ) { + return NULL; + } + slapi_lock_mutex(cache_lock); + for( ; root && strcmp(dn, root->dn); root = root->next ) { + } + slapi_unlock_mutex(cache_lock); + return root; +} diff --git a/ldap/servers/plugins/statechange/Makefile b/ldap/servers/plugins/statechange/Makefile new file mode 100644 index 00000000..bc04a3f0 --- /dev/null +++ b/ldap/servers/plugins/statechange/Makefile @@ -0,0 +1,78 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libstatechange +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./statechange.def +endif + +STATECHANGE_OBJS = statechange.o +OBJS = $(addprefix $(OBJDEST)/, $(STATECHANGE_OBJS)) + +STATECHANGE_DLL = statechange-plugin + +INCLUDES += -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) +STATECHANGE_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), AIX) +LD=ld +EXTRA_LIBS += $(LIBSLAPD) +endif + +STATECHANGE= $(addprefix $(LIBDIR)/, $(STATECHANGE_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(STATECHANGE) + +ifeq ($(ARCH), WINNT) +$(STATECHANGE): $(OBJS) $(STATECHANGE_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(STATECHANGE_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(STATECHANGE): $(OBJS) $(STATECHANGE_DLL_OBJ) + $(LINK_DLL) $(STATECHANGE_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(STATECHANGE_DLL_OBJ) +endif + $(RM) $(STATECHANGE) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) diff --git a/ldap/servers/plugins/statechange/dllmain.c b/ldap/servers/plugins/statechange/dllmain.c new file mode 100644 index 00000000..fabf8677 --- /dev/null +++ b/ldap/servers/plugins/statechange/dllmain.c @@ -0,0 +1,96 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/statechange/statechange.c b/ldap/servers/plugins/statechange/statechange.c new file mode 100644 index 00000000..c0942b41 --- /dev/null +++ b/ldap/servers/plugins/statechange/statechange.c @@ -0,0 +1,450 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* plugin which provides a callback mechanism for state changes in the DS */ + +#include <stdio.h> +#include <string.h> +#include "portable.h" +#include "slapi-plugin.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" +#include "statechange.h" + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +/* the circular list of systems to notify */ +typedef struct _statechange_notify +{ + char *caller_id; + char *dn; + char *filter; + Slapi_Filter *realfilter; + notify_callback func; + void *caller_data; + struct _statechange_notify *next; + struct _statechange_notify *prev; +} SCNotify; + +static SCNotify *head; /* a place to start in the list */ + +#define SCN_PLUGIN_SUBSYSTEM "statechange-plugin" /* used for logging */ + +static void *api[5]; +static Slapi_Mutex *buffer_lock = 0; + +/* other function prototypes */ +int statechange_init( Slapi_PBlock *pb ); +static int statechange_start( Slapi_PBlock *pb ); +static int statechange_close( Slapi_PBlock *pb ); +static int statechange_post_op( Slapi_PBlock *pb, int modtype ); +static int statechange_mod_post_op( Slapi_PBlock *pb ); +static int statechange_modrdn_post_op( Slapi_PBlock *pb ); +static int statechange_add_post_op( Slapi_PBlock *pb ); +static int statechange_delete_post_op( Slapi_PBlock *pb ); +static int _statechange_register(char *caller_id, char *dn, char *filter, void *caller_data, notify_callback func); +static void *_statechange_unregister(char *dn, char *filter, notify_callback func); +static void _statechange_unregister_all(char *caller_id, caller_data_free_callback); +static void _statechange_vattr_cache_invalidator_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data); +static SCNotify *statechange_find_notify(char *dn, char *filter, notify_callback func); + + +static Slapi_PluginDesc pdesc = { "statechange", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "state change notification service plugin" }; + + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + + +/* + statechange_init + -------- + adds our callbacks to the list +*/ +int statechange_init( Slapi_PBlock *pb ) +{ + int ret = 0; + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_init\n"); + + head = 0; + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) statechange_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, + (void *) statechange_mod_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, + (void *) statechange_modrdn_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, + (void *) statechange_add_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, + (void *) statechange_delete_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) statechange_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, + "statechange_init: failed to register plugin\n" ); + ret = -1; + } + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_init\n"); + return ret; +} + + +/* + statechange_start + --------- + This function publishes the interface for this plugin +*/ +static int statechange_start( Slapi_PBlock *pb ) +{ + int ret = 0; + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_start\n"); + + api[0] = 0; /* reserved for api broker use, must be zero */ + api[1] = (void *)_statechange_register; + api[2] = (void *)_statechange_unregister; + api[3] = (void *)_statechange_unregister_all; + api[4] = (void *)_statechange_vattr_cache_invalidator_callback; + + if(0 == (buffer_lock = slapi_new_mutex())) /* we never free this mutex */ + { + /* badness */ + slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, "statechange: failed to create lock\n"); + ret = -1; + } + else + { + if( slapi_apib_register(StateChange_v1_0_GUID, api) ) + { + slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, "statechange: failed to publish state change interface\n"); + ret = -1; + } + } + + head = 0; + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_start\n"); + return ret; +} + +/* + statechange_close + --------- + unregisters the interface for this plugin +*/ +static int statechange_close( Slapi_PBlock *pb ) +{ + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_close\n"); + + slapi_apib_unregister(StateChange_v1_0_GUID); + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_close\n"); + + return 0; +} + + +static int statechange_mod_post_op( Slapi_PBlock *pb ) +{ + return statechange_post_op(pb, LDAP_CHANGETYPE_MODIFY); +} + +static int statechange_modrdn_post_op( Slapi_PBlock *pb ) +{ + return statechange_post_op(pb, LDAP_CHANGETYPE_MODDN); +} + +static int statechange_add_post_op( Slapi_PBlock *pb ) +{ + return statechange_post_op(pb, LDAP_CHANGETYPE_ADD); +} + +static int statechange_delete_post_op( Slapi_PBlock *pb ) +{ + return statechange_post_op(pb, LDAP_CHANGETYPE_DELETE); +} + + +/* + statechange_post_op + ----------- + Catch all for all post operations that change entries + in some way - evaluate the change against the notification + entries and fire off the relevant callbacks - it is called + from the real postop functions which supply it with the + postop type +*/ +static int statechange_post_op( Slapi_PBlock *pb, int modtype ) +{ + SCNotify *notify = head; + int execute; + char *dn = NULL; + struct slapi_entry *e_before = NULL; + struct slapi_entry *e_after = NULL; + + if(head == 0) + return 0; + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_post_op\n"); + + /* evaluate this operation against the notification entries */ + + slapi_lock_mutex(buffer_lock); + if(head) + { + if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn )) + { + slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, "statechange_post_op: failed to get dn of changed entry"); + goto bail; + } + + slapi_dn_normalize( dn ); + + slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &e_before ); + slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &e_after ); + + do + { + execute = 0; + + /* first dn */ + if(notify && notify->dn) + { + if(0 != slapi_dn_issuffix(dn, notify->dn)) + execute = 1; + } + else + /* note, if supplied null for everything in the entry *all* ops match */ + if(notify) + execute = 1; + + if(execute && notify->filter) + { + /* next the filter */ + int filter_test = 0; + + /* need to test entry both before and after op */ + if(e_before && !slapi_filter_test_simple( e_before, notify->realfilter)) + filter_test = 1; + + if(!filter_test && e_after && !slapi_filter_test_simple( e_after, notify->realfilter)) + filter_test = 1; + + if(!filter_test) + execute = 0; + } + + if(execute) + { + if(e_after) + (notify->func)(e_after, dn, modtype, pb, notify->caller_data); + else + (notify->func)(e_before, dn, modtype, pb, notify->caller_data); + } + + notify = notify->next; + } + while(notify != head); + } +bail: + slapi_unlock_mutex(buffer_lock); + + slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_post_op\n"); + return 0; /* always succeed */ +} + + +static int _statechange_register(char *caller_id, char *dn, char *filter, void *caller_data, notify_callback func) +{ + int ret = -1; + SCNotify *item; + + /* simple - we don't check for duplicates */ + + item = (SCNotify*)slapi_ch_malloc(sizeof(SCNotify)); + if(item) + { + char *writable_filter = slapi_ch_strdup(filter); + item->caller_id = slapi_ch_strdup(caller_id); + if(dn) + { + item->dn = slapi_ch_strdup(dn); + slapi_dn_normalize( item->dn ); + } + else + item->dn = 0; + item->filter = slapi_ch_strdup(filter); + item->caller_data = caller_data; + item->realfilter = slapi_str2filter(writable_filter); + item->func = func; + + slapi_lock_mutex(buffer_lock); + if(head == NULL) + { + head = item; + head->next = head; + head->prev = head; + } + else + { + item->next = head; + item->prev = head->prev; + head->prev = item; + item->prev->next = item; + } + slapi_unlock_mutex(buffer_lock); + slapi_ch_free_string(&writable_filter); + + ret = 0; + } + + return ret; +} + +static void *_statechange_unregister(char *dn, char *filter, notify_callback thefunc) +{ + void *ret = NULL; + SCNotify *func = NULL; + + if(buffer_lock == 0) + return ret; + + slapi_lock_mutex(buffer_lock); + + if(func = statechange_find_notify(dn, filter, thefunc)) + { + func->prev->next = func->next; + func->next->prev = func->prev; + + if(func == head) + { + head = func->next; + } + + if(func == head) /* must be the last item, turn off the lights */ + head = 0; + + slapi_ch_free_string(&func->caller_id); + slapi_ch_free_string(&func->dn); + slapi_ch_free_string(&func->filter); + slapi_filter_free( func->realfilter, 1 ); + ret = func->caller_data; + slapi_ch_free((void **)&func); + } + + slapi_unlock_mutex(buffer_lock); + + return ret; +} + +static void _statechange_unregister_all(char *caller_id, caller_data_free_callback callback) +{ + SCNotify *notify = head; + SCNotify *start_notify = head; + + if(buffer_lock == 0) + return; + + slapi_lock_mutex(buffer_lock); + + + if(notify) + { + do + { + SCNotify *notify_next = notify->next; + + if( slapi_utf8casecmp((unsigned char *)caller_id, (unsigned char *)notify->caller_id) ) + { + notify->prev->next = notify->next; + notify->next->prev = notify->prev; + + if(notify == head) + { + head = notify->next; + start_notify = notify->prev; + } + + if(notify == head) /* must be the last item, turn off the lights */ + head = 0; + + if(callback) + callback(notify->caller_data); + slapi_ch_free_string(¬ify->caller_id); + slapi_ch_free_string(¬ify->dn); + slapi_ch_free_string(¬ify->filter); + slapi_filter_free( notify->realfilter, 1 ); + slapi_ch_free((void **)¬ify); + } + + notify = notify_next; + } + while(notify != start_notify && notify != NULL); + } + + slapi_unlock_mutex(buffer_lock); +} + +/* this func needs looking at to make work */ +static SCNotify *statechange_find_notify(char *dn, char *filter, notify_callback func) +{ + SCNotify *notify = head; + SCNotify *start_notify = head; + + if(notify) + { + do + { + if( !slapi_utf8casecmp((unsigned char *)dn, (unsigned char *)notify->dn) && + !slapi_utf8casecmp((unsigned char *)filter, (unsigned char *)notify->filter) && func == notify->func) + { + return notify; + } + + notify = notify->next; + } + while(notify != start_notify); + } + + return 0; +} + +/* intended for use by vattr service providers + * to deal with significant vattr state changes + */ +static void _statechange_vattr_cache_invalidator_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data) +{ + /* simply get the significance data and act */ + switch(*(int*)caller_data) + { + case STATECHANGE_VATTR_ENTRY_INVALIDATE: + if(e) + slapi_entry_vattrcache_watermark_invalidate(e); + break; + + case STATECHANGE_VATTR_GLOBAL_INVALIDATE: + default: + slapi_entrycache_vattrcache_watermark_invalidate(); + break; + } +} + diff --git a/ldap/servers/plugins/statechange/statechange.def b/ldap/servers/plugins/statechange/statechange.def new file mode 100644 index 00000000..ed6dca6b --- /dev/null +++ b/ldap/servers/plugins/statechange/statechange.def @@ -0,0 +1,10 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7.0 State Change Plugin' +EXPORTS + statechange_init @2 + plugin_init_debug_level @3 diff --git a/ldap/servers/plugins/syntaxes/Makefile b/ldap/servers/plugins/syntaxes/Makefile new file mode 100644 index 00000000..edc9bd9c --- /dev/null +++ b/ldap/servers/plugins/syntaxes/Makefile @@ -0,0 +1,87 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server syntax-plugin.so syntax plugins +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libsyntax +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libsyntax.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd + +SYNTAX_OBJS= phonetic.o string.o cis.o sicis.o ces.o bin.o tel.o dn.o int.o \ + value.o debug.o + +OBJS = $(addprefix $(OBJDEST)/, $(SYNTAX_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBSYNTAX_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +LIBSYNTAX= $(addprefix $(LIBDIR)/, $(SYNTAX_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +endif +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libsyntax.def" +CFLAGS+= /WX +endif # WINNT + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBSYNTAX) + +$(LIBSYNTAX): $(OBJS) $(LIBSYNTAX_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBSYNTAX_DLL_OBJ) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBSYNTAX_DLL_OBJ) +endif + $(RM) $(LIBSYNTAX) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/syntaxes/bin.c b/ldap/servers/plugins/syntaxes/bin.c new file mode 100644 index 00000000..da06e55e --- /dev/null +++ b/ldap/servers/plugins/syntaxes/bin.c @@ -0,0 +1,201 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* bin.c - bin syntax routines */ + +/* + * This file actually implements two syntax plugins: OctetString and Binary. + * We treat them identically for now. XXXmcs: check if that is correct. + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int bin_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int bin_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals, + Slapi_Value ***ivals, int ftype ); +static int bin_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *bval, + Slapi_Value ***ivals, int ftype ); + +/* + * Attribute syntaxes. We treat all of these the same for now, even though + * the specifications (e.g., RFC 2252) impose various constraints on the + * the format for each of these. + * + * Note: the first name is the official one from RFC 2252. + */ +static char *bin_names[] = { "Binary", "bin", BINARY_SYNTAX_OID, 0 }; + +static char *octetstring_names[] = { "OctetString", OCTETSTRING_SYNTAX_OID, 0 }; + +static char *jpeg_names[] = { "JPEG", JPEG_SYNTAX_OID, 0 }; + + +static Slapi_PluginDesc bin_pdesc = { + "bin-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "binary attribute syntax plugin" +}; + +static Slapi_PluginDesc octetstring_pdesc = { + "octetstring-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "octet string attribute syntax plugin" +}; + +static Slapi_PluginDesc jpeg_pdesc = { + "jpeg-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "JPEG attribute syntax plugin" +}; + + +/* + * register_bin_like_plugin(): register all items for a bin-like plugin. + */ +static int +register_bin_like_plugin( Slapi_PBlock *pb, Slapi_PluginDesc *pdescp, + char **names, char *oid ) +{ + int rc; + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)pdescp ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) bin_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) bin_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) bin_assertion2keys_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) oid ); + + return( rc ); +} + + +int +bin_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> bin_init\n", 0, 0, 0 ); + rc = register_bin_like_plugin( pb, &bin_pdesc, bin_names, + BINARY_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= bin_init %d\n", rc, 0, 0 ); + return( rc ); +} + + +int +octetstring_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> octetstring_init\n", 0, 0, 0 ); + rc = register_bin_like_plugin( pb, &octetstring_pdesc, octetstring_names, + OCTETSTRING_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= octetstring_init %d\n", rc, 0, 0 ); + return( rc ); +} + + +int +jpeg_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> jpeg_init\n", 0, 0, 0 ); + rc = register_bin_like_plugin( pb, &jpeg_pdesc, jpeg_names, + JPEG_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= jpeg_init %d\n", rc, 0, 0 ); + return( rc ); +} + + +static int +bin_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ) +{ + int i; + + for ( i = 0; bvals[i] != NULL; i++ ) { + if ( slapi_value_get_length(bvals[i]) == bvfilter->bv_len && + 0 == memcmp( slapi_value_get_string(bvals[i]), bvfilter->bv_val, bvfilter->bv_len )) + { + if(retVal!=NULL) + { + *retVal= bvals[i]; + } + return( 0 ); + } + } + if(retVal!=NULL) + { + *retVal= NULL; + } + return( -1 ); +} + +static int +bin_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals, + Slapi_Value ***ivals, int ftype ) +{ + int i; + + if ( ftype != LDAP_FILTER_EQUALITY ) { + return( LDAP_PROTOCOL_ERROR ); + } + + for ( i = 0; bvals[i] != NULL; i++ ) { + /* NULL */ + } + (*ivals) = (Slapi_Value **) slapi_ch_malloc(( i + 1 ) * + sizeof(Slapi_Value *) ); + + for ( i = 0; bvals[i] != NULL; i++ ) + { + (*ivals)[i] = slapi_value_dup(bvals[i]); + } + (*ivals)[i] = NULL; + + return( 0 ); +} + +static int +bin_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *bval, + Slapi_Value ***ivals, int ftype ) +{ + Slapi_Value *tmpval=NULL; + size_t len; + + if (( ftype != LDAP_FILTER_EQUALITY ) && + ( ftype != LDAP_FILTER_EQUALITY_FAST)) + { + return( LDAP_PROTOCOL_ERROR ); + } + if(ftype == LDAP_FILTER_EQUALITY_FAST) { + /* With the fast option, we are trying to avoid creating and freeing + * a bunch of structures - we just do one malloc here - see + * ava_candidates in filterentry.c + */ + len=slapi_value_get_length(bval); + tmpval=(*ivals)[0]; + if (len > tmpval->bv.bv_len) { + tmpval->bv.bv_val=(char *)slapi_ch_malloc(len); + } + tmpval->bv.bv_len=len; + memcpy(tmpval->bv.bv_val,slapi_value_get_string(bval),len); + } else { + (*ivals) = (Slapi_Value **) slapi_ch_malloc( 2 * sizeof(Slapi_Value *) ); + (*ivals)[0] = slapi_value_dup( bval ); + (*ivals)[1] = NULL; + } + return( 0 ); +} diff --git a/ldap/servers/plugins/syntaxes/ces.c b/ldap/servers/plugins/syntaxes/ces.c new file mode 100644 index 00000000..075da03b --- /dev/null +++ b/ldap/servers/plugins/syntaxes/ces.c @@ -0,0 +1,168 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* ces.c - caseexactstring syntax routines */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int ces_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int ces_filter_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value **bvals ); +static int ces_values2keys( Slapi_PBlock *pb, Slapi_Value **val, + Slapi_Value ***ivals, int ftype ); +static int ces_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ); +static int ces_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value ***ivals ); +static int ces_compare(struct berval *v1, struct berval *v2); + +/* the first name is the official one from RFC 2252 */ +static char *ia5_names[] = { "IA5String", "ces", "caseexactstring", + IA5STRING_SYNTAX_OID, 0 }; + +/* the first name is the official one from RFC 2252 */ +static char *uri_names[] = { "URI", "1.3.6.1.4.1.4401.1.1.1",0}; + +static Slapi_PluginDesc ia5_pdesc = { "ces-syntax", PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, "caseExactString attribute syntax plugin" }; + +static Slapi_PluginDesc uri_pdesc = { "uri-syntax", PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, "uri attribute syntax plugin" }; + + +/* + * register_ces_like_plugin(): register all items for a cis-like plugin. + */ +static int +register_ces_like_plugin( Slapi_PBlock *pb, Slapi_PluginDesc *pdescp, + char **names, char *oid ) +{ + int rc, flags; + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *) pdescp ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) ces_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *) ces_filter_sub ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) ces_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) ces_assertion2keys_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *) ces_assertion2keys_sub ); + flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS, + (void *) &flags ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) oid ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE, + (void *) ces_compare ); + + return( rc ); +} + +int +ces_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> ces_init\n", 0, 0, 0 ); + + rc = register_ces_like_plugin(pb,&ia5_pdesc,ia5_names,IA5STRING_SYNTAX_OID); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= ces_init %d\n", rc, 0, 0 ); + return( rc ); +} + +int +uri_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> uri_init\n", 0, 0, 0 ); + + rc = register_ces_like_plugin(pb,&uri_pdesc,uri_names, + "1.3.6.1.4.1.4401.1.1.1"); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= uri_init %d\n", rc, 0, 0 ); + return( rc ); +} + +static int +ces_filter_ava( + Slapi_PBlock *pb, + struct berval *bvfilter, + Slapi_Value **bvals, + int ftype, + Slapi_Value **retVal +) +{ + return( string_filter_ava( bvfilter, bvals, SYNTAX_CES, ftype, + retVal) ); +} + +static int +ces_filter_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value **bvals +) +{ + return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_CES ) ); +} + +static int +ces_values2keys( + Slapi_PBlock *pb, + Slapi_Value **vals, + Slapi_Value ***ivals, + int ftype +) +{ + return( string_values2keys( pb, vals, ivals, SYNTAX_CES, ftype ) ); +} + +static int +ces_assertion2keys_ava( + Slapi_PBlock *pb, + Slapi_Value *val, + Slapi_Value ***ivals, + int ftype +) +{ + return(string_assertion2keys_ava( pb, val, ivals, SYNTAX_CES, ftype )); +} + +static int +ces_assertion2keys_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value ***ivals +) +{ + return( string_assertion2keys_sub( pb, initial, any, final, ivals, + SYNTAX_CES ) ); +} + +static int ces_compare( + struct berval *v1, + struct berval *v2 +) +{ + return value_cmp(v1,v2,SYNTAX_CES,3 /* Normalise both values */); +} diff --git a/ldap/servers/plugins/syntaxes/cis.c b/ldap/servers/plugins/syntaxes/cis.c new file mode 100644 index 00000000..e2047fbc --- /dev/null +++ b/ldap/servers/plugins/syntaxes/cis.c @@ -0,0 +1,298 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cis.c - caseignorestring syntax routines */ + +/* + * This file actually implements three syntax plugins: + * DirectoryString + * Boolean + * GeneralizedTime + * + * We treat them identically for now. XXXmcs: we could do some validation on + * Boolean and GeneralizedTime values (someday, maybe). + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int cis_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int cis_filter_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value **bvals ); +static int cis_values2keys( Slapi_PBlock *pb, Slapi_Value **val, + Slapi_Value ***ivals, int ftype ); +static int cis_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ); +static int cis_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value ***ivals ); +static int cis_compare(struct berval *v1, struct berval *v2); + +/* + * Attribute syntaxes. We treat all of these the same for now, even though + * the specifications (e.g., RFC 2252) impose various constraints on the + * the format for each of these. + * + * Note: the first name is the official one from RFC 2252. + */ +static char *dirstring_names[] = { "DirectoryString", "cis", + "caseignorestring", DIRSTRING_SYNTAX_OID, 0 }; + +static char *boolean_names[] = { "Boolean", BOOLEAN_SYNTAX_OID, 0 }; + +static char *time_names[] = { "GeneralizedTime", "time", + GENERALIZEDTIME_SYNTAX_OID, 0 }; + +static char *country_names[] = { "Country String", + COUNTRYSTRING_SYNTAX_OID, 0}; + +static char *postal_names[] = { "Postal Address", + POSTALADDRESS_SYNTAX_OID, 0}; + +static char *oid_names[] = { "OID", + OID_SYNTAX_OID, 0}; + + +/* + TBD (XXX) + + "1.3.6.1.4.1.1466.115.121.1.16 \"DIT Content Rule Description +\" " + "1.3.6.1.4.1.1466.115.121.1.17 \"DIT Structure Rule Descripti +on\" " + "1.3.6.1.4.1.1466.115.121.1.20 \"DSE Type\" " + "1.3.6.1.4.1.1466.115.121.1.30 \"Matching Rule Description\" +" + "1.3.6.1.4.1.1466.115.121.1.31 \"Matching Rule Use Descriptio +n\" " + "1.3.6.1.4.1.1466.115.121.1.35 \"Name Form Description\" " + + "1.3.6.1.4.1.1466.115.121.1.44 \"Printable String\" " + "1.3.6.1.4.1.1466.115.121.1.45 \"Subtree Specification\" " + "1.3.6.1.4.1.1466.115.121.1.54 \"LDAP Syntax Description\" " + "1.3.6.1.4.1.1466.115.121.1.55 \"Modify Rights\" " + "1.3.6.1.4.1.1466.115.121.1.56 \"LDAP Schema Description\" " + "1.3.6.1.4.1.1466.115.121.1.25 \"Guide\" " + "1.3.6.1.4.1.1466.115.121.1.52 \"Telex Number\" " + "1.3.6.1.4.1.1466.115.121.1.51 \"Teletex Terminal Identifier\ +" " + "1.3.6.1.4.1.1466.115.121.1.14 \"Delivery Method\" " + "1.3.6.1.4.1.1466.115.121.1.43 \"Presentation Address\" " + "1.3.6.1.4.1.1466.115.121.1.21 \"Enhanced Guide\" " + "1.3.6.1.4.1.1466.115.121.1.34 \"Name and Optional UID\" " + "1.2.840.113556.1.4.905 \"CaseIgnoreString\" " + "1.3.6.1.1.1.0.0 \"nisNetgroupTripleSyntax\" " + "1.3.6.1.1.1.0.1 \"bootParameterSyntax\" "); + */ + + +static Slapi_PluginDesc dirstring_pdesc = { "directorystring-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "DirectoryString attribute syntax plugin" }; + +static Slapi_PluginDesc boolean_pdesc = { "boolean-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "Boolean attribute syntax plugin" }; + +static Slapi_PluginDesc time_pdesc = { "time-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "GeneralizedTime attribute syntax plugin" }; + +static Slapi_PluginDesc country_pdesc = { "countrystring-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "Country String attribute syntax plugin" }; + +static Slapi_PluginDesc postal_pdesc = { "postaladdress-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "Postal Address attribute syntax plugin" }; + +static Slapi_PluginDesc oid_pdesc = { "oid-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "OID attribute syntax plugin" }; + + +/* + * register_cis_like_plugin(): register all items for a cis-like plugin. + */ +static int +register_cis_like_plugin( Slapi_PBlock *pb, Slapi_PluginDesc *pdescp, + char **names, char *oid ) +{ + int rc, flags; + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *) pdescp ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) cis_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *) cis_filter_sub ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) cis_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) cis_assertion2keys_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *) cis_assertion2keys_sub ); + flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS, + (void *) &flags ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) oid ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE, + (void *) cis_compare ); + + return( rc ); +} + + +int +cis_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> cis_init\n", 0, 0, 0 ); + rc = register_cis_like_plugin( pb, &dirstring_pdesc, dirstring_names, + DIRSTRING_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= cis_init %d\n", rc, 0, 0 ); + return( rc ); +} + + +int +boolean_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> boolean_init\n", 0, 0, 0 ); + rc = register_cis_like_plugin( pb, &boolean_pdesc, boolean_names, + BOOLEAN_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= boolean_init %d\n", rc, 0, 0 ); + return( rc ); +} + + +int +time_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> time_init\n", 0, 0, 0 ); + rc = register_cis_like_plugin( pb, &time_pdesc, time_names, + GENERALIZEDTIME_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= time_init %d\n", rc, 0, 0 ); + return( rc ); +} + +int +country_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> country_init\n", 0, 0, 0 ); + rc = register_cis_like_plugin( pb, &country_pdesc, country_names, + COUNTRYSTRING_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= country_init %d\n", rc, 0, 0 ); + return( rc ); +} + +int +postal_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> postal_init\n", 0, 0, 0 ); + rc = register_cis_like_plugin( pb, &postal_pdesc, postal_names, + POSTALADDRESS_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= postal_init %d\n", rc, 0, 0 ); + return( rc ); +} + + +int +oid_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> oid_init\n", 0, 0, 0 ); + rc = register_cis_like_plugin( pb, &oid_pdesc, oid_names, OID_SYNTAX_OID ); + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= oid_init %d\n", rc, 0, 0 ); + return( rc ); +} + + + +static int +cis_filter_ava( + Slapi_PBlock *pb, + struct berval *bvfilter, + Slapi_Value **bvals, + int ftype, + Slapi_Value **retVal +) +{ + return( string_filter_ava( bvfilter, bvals, SYNTAX_CIS, ftype, + retVal ) ); +} + + +static int +cis_filter_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value **bvals +) +{ + return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_CIS ) ); +} + +static int +cis_values2keys( + Slapi_PBlock *pb, + Slapi_Value **vals, + Slapi_Value ***ivals, + int ftype +) +{ + return( string_values2keys( pb, vals, ivals, SYNTAX_CIS, ftype ) ); +} + +static int +cis_assertion2keys_ava( + Slapi_PBlock *pb, + Slapi_Value *val, + Slapi_Value ***ivals, + int ftype +) +{ + return(string_assertion2keys_ava( pb, val, ivals, SYNTAX_CIS, ftype )); +} + +static int +cis_assertion2keys_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value ***ivals +) +{ + return( string_assertion2keys_sub( pb, initial, any, final, ivals, + SYNTAX_CIS ) ); +} + +static int cis_compare( + struct berval *v1, + struct berval *v2 +) +{ + return value_cmp(v1,v2,SYNTAX_CIS,3 /* Normalise both values */); +} diff --git a/ldap/servers/plugins/syntaxes/debug.c b/ldap/servers/plugins/syntaxes/debug.c new file mode 100644 index 00000000..e4d69202 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/debug.c @@ -0,0 +1,20 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* debug.c - syntax debug stuff */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif diff --git a/ldap/servers/plugins/syntaxes/dllmain.c b/ldap/servers/plugins/syntaxes/dllmain.c new file mode 100644 index 00000000..1f7aa331 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/dllmain.c @@ -0,0 +1,133 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for syntax-plugin DLL + */ +#include "ldap.h" +#include "syntax.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif + +#ifdef LDAP_DEBUG +#ifndef _WIN32 +#include <stdarg.h> +#include <stdio.h> + +void LDAPDebug( int level, char* fmt, ... ) +{ + static char debugBuf[1024]; + + if (module_ldap_debug && (*module_ldap_debug & level)) + { + va_list ap; + va_start (ap, fmt); + _snprintf (debugBuf, sizeof(debugBuf), fmt, ap); + va_end (ap); + + OutputDebugString (debugBuf); + } +} +#endif +#endif + +#ifndef _WIN32 + +/* The 16-bit version of the RTL does not implement perror() */ + +#include <stdio.h> + +void perror( const char *msg ) +{ + char buf[128]; + wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ; + OutputDebugString( buf ); +} + +#endif diff --git a/ldap/servers/plugins/syntaxes/dn.c b/ldap/servers/plugins/syntaxes/dn.c new file mode 100644 index 00000000..e6a90df0 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/dn.c @@ -0,0 +1,98 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dn.c - dn syntax routines */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int dn_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int dn_filter_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value **bvals ); +static int dn_values2keys( Slapi_PBlock *pb, Slapi_Value **vals, + Slapi_Value ***ivals, int ftype ); +static int dn_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ); +static int dn_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value ***ivals ); + +/* the first name is the official one from RFC 2252 */ +static char *names[] = { "DN", DN_SYNTAX_OID, 0 }; + +static Slapi_PluginDesc pdesc = { "dn-syntax", PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, "distinguished name attribute syntax plugin" }; + +int +dn_init( Slapi_PBlock *pb ) +{ + int rc; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> dn_init\n", 0, 0, 0 ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) dn_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *) dn_filter_sub ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) dn_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) dn_assertion2keys_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *) dn_assertion2keys_sub ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) DN_SYNTAX_OID ); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= dn_init %d\n", rc, 0, 0 ); + return( rc ); +} + +static int +dn_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ) +{ + return( string_filter_ava( bvfilter, bvals, SYNTAX_CIS | SYNTAX_DN, + ftype, retVal ) ); +} + +static int +dn_filter_sub( Slapi_PBlock *pb, char *initial, char **any, char *final, + Slapi_Value **bvals ) +{ + return( string_filter_sub( pb, initial, any, final, bvals, + SYNTAX_CIS | SYNTAX_DN ) ); +} + +static int +dn_values2keys( Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals, + int ftype ) +{ + return( string_values2keys( pb, vals, ivals, SYNTAX_CIS | SYNTAX_DN, + ftype ) ); +} + +static int +dn_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ) +{ + return( string_assertion2keys_ava( pb, val, ivals, + SYNTAX_CIS | SYNTAX_DN, ftype ) ); +} + +static int +dn_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any, char *final, + Slapi_Value ***ivals ) +{ + return( string_assertion2keys_sub( pb, initial, any, final, ivals, + SYNTAX_CIS | SYNTAX_DN ) ); +} diff --git a/ldap/servers/plugins/syntaxes/int.c b/ldap/servers/plugins/syntaxes/int.c new file mode 100644 index 00000000..f81167f2 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/int.c @@ -0,0 +1,188 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* int.c - integer syntax routines */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int int_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int int_values2keys( Slapi_PBlock *pb, Slapi_Value **val, + Slapi_Value ***ivals ); +static int int_assertion2keys( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ); +static int int_compare(struct berval *v1, struct berval *v2); +static long int_to_canonical( long num ); + +/* the first name is the official one from RFC 2252 */ +static char *names[] = { "INTEGER", "int", INTEGER_SYNTAX_OID, 0 }; + +static Slapi_PluginDesc pdesc = { "int-syntax", PLUGIN_MAGIC_VENDOR_STR, + PRODUCTTEXT, "integer attribute syntax plugin" }; + +int +int_init( Slapi_PBlock *pb ) +{ + int rc, flags; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> int_init\n", 0, 0, 0 ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) int_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) int_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) int_assertion2keys ); + flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS, + (void *) &flags ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) INTEGER_SYNTAX_OID ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE, + (void *) int_compare ); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= int_init %d\n", rc, 0, 0 ); + return( rc ); +} + +static int +int_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ) +{ + int i, rc; + long flong, elong; + + if ( ftype == LDAP_FILTER_APPROX ) { + return( LDAP_PROTOCOL_ERROR ); + } + if(retVal) { + *retVal=NULL; + } + flong = atol( bvfilter->bv_val ); + for ( i = 0; bvals[i] != NULL; i++ ) { + elong = atol ( slapi_value_get_string(bvals[i]) ); + rc = elong - flong; + switch ( ftype ) { + case LDAP_FILTER_GE: + if ( rc >= 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + return( 0 ); + } + break; + case LDAP_FILTER_LE: + if ( rc <= 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + return( 0 ); + } + break; + case LDAP_FILTER_EQUALITY: + if ( rc == 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + return( 0 ); + } + break; + } + } + + return( -1 ); +} + +static int +int_values2keys( Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals ) +{ + long num; + int i; + + for ( i = 0; vals[i] != NULL; i++ ) { + /* NULL */ + } + + *ivals = (Slapi_Value **) slapi_ch_malloc(( i + 1 ) * sizeof(Slapi_Value *) ); + + for ( i = 0; vals[i] != NULL; i++ ) + { + num = atol( slapi_value_get_string(vals[i]) ); + num = int_to_canonical( num ); + (*ivals)[i] = slapi_value_new(); + slapi_value_set((*ivals)[i],&num,sizeof(long)); + } + (*ivals)[i] = NULL; + + return( 0 ); +} + +static int +int_assertion2keys( Slapi_PBlock *pb, Slapi_Value *val, Slapi_Value ***ivals, int ftype ) +{ + long num; + size_t len; + unsigned char *b; + Slapi_Value *tmpval=NULL; + + num = atol( slapi_value_get_string(val) ); + num = int_to_canonical( num ); + /* similar to string.c to optimize equality path: avoid malloc/free */ + if(ftype == LDAP_FILTER_EQUALITY_FAST) { + len=sizeof(long); + tmpval=(*ivals)[0]; + if ( len > tmpval->bv.bv_len) { + tmpval->bv.bv_val=(char *)slapi_ch_malloc(len); + } + tmpval->bv.bv_len=len; + b = (unsigned char *)# + memcpy(tmpval->bv.bv_val,b,len); + } else { + *ivals = (Slapi_Value **) slapi_ch_malloc( 2 * sizeof(Slapi_Value *) ); + (*ivals)[0] = (Slapi_Value *) slapi_ch_malloc( sizeof(Slapi_Value) ); + /* XXXSD initialize memory */ + memset((*ivals)[0],0,sizeof(Slapi_Value)); + slapi_value_set((*ivals)[0],&num,sizeof(long)); + (*ivals)[1] = NULL; + } + return( 0 ); +} + +static int int_compare( + struct berval *v1, + struct berval *v2 +) +{ + long value1 = atol(v1->bv_val); + long value2 = atol(v2->bv_val); + + if (value1 == value2) { + return 0; + } + return ( ((value1 - value2) > 0) ? 1 : -1); +} + +static long +int_to_canonical( long num ) +{ + long ret = 0L; + unsigned char *b = (unsigned char *)&ret; + + b[0] = (unsigned char)(num >> 24); + b[1] = (unsigned char)(num >> 16); + b[2] = (unsigned char)(num >> 8); + b[3] = (unsigned char)num; + + return ret; +} diff --git a/ldap/servers/plugins/syntaxes/libsyntax.def b/ldap/servers/plugins/syntaxes/libsyntax.def new file mode 100644 index 00000000..30cb6b40 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/libsyntax.def @@ -0,0 +1,24 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Directory Server 7 syntaxes Plugin' +EXPORTS + cis_init @2 + ces_init @3 + tel_init @4 + dn_init @5 + bin_init @6 + int_init @7 + plugin_init_debug_level @8 + octetstring_init @9 + boolean_init @10 + time_init @11 + uri_init @12 + country_init @13 + postal_init @14 + jpeg_init @15 + oid_init @16 + sicis_init @17 diff --git a/ldap/servers/plugins/syntaxes/phonetic.c b/ldap/servers/plugins/syntaxes/phonetic.c new file mode 100644 index 00000000..1c1c8ba5 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/phonetic.c @@ -0,0 +1,461 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* phonetic.c - routines to do phonetic matching */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include "syntax.h" +#include "portable.h" + +#if !defined(METAPHONE) && !defined(SOUNDEX) +#define METAPHONE +#endif + +#define iswordbreak(s) \ +(isascii(*(s)) \ +? (isspace(*(s)) || \ + ispunct(*(s)) || \ + isdigit(*(s)) || \ + *(s) == '\0') \ +: utf8iswordbreak(s)) + +static int +utf8iswordbreak( const char* s ) +{ + switch( LDAP_UTF8GETCC( s )) { + case 0x00A0: /* non-breaking space */ + case 0x3000: /* ideographic space */ + case 0xFEFF: /* zero-width non-breaking space */ + return 1; + default: break; + } + return 0; +} + +char * +first_word( char *s ) +{ + if ( s == NULL ) { + return( NULL ); + } + + while ( iswordbreak( s ) ) { + if ( *s == '\0' ) { + return( NULL ); + } else { + LDAP_UTF8INC( s ); + } + } + + return( s ); +} + +char * +next_word( char *s ) +{ + if ( s == NULL ) { + return( NULL ); + } + + while ( ! iswordbreak( s ) ) { + LDAP_UTF8INC( s ); + } + + while ( iswordbreak( s ) ) { + if ( *s == '\0' ) { + return( NULL ); + } else { + LDAP_UTF8INC( s ); + } + } + + return( s ); +} + +char * +word_dup( char *w ) +{ + char *s, *ret; + char save; + + for ( s = w; !iswordbreak( s ); LDAP_UTF8INC( s )) + ; /* NULL */ + save = *s; + *s = '\0'; + ret = slapi_ch_strdup( w ); + *s = save; + + return( ret ); +} + +#ifndef MAXPHONEMELEN +#define MAXPHONEMELEN 4 +#endif + +#if defined(SOUNDEX) + +/* lifted from isode-8.0 */ +char * +phonetic( char *s ) +{ + char code, adjacent, ch; + char *p; + char **c; + int i, cmax; + char phoneme[MAXPHONEMELEN + 1]; + + p = s; + if ( p == NULL || *p == '\0' ) { + return( NULL ); + } + + adjacent = '0'; + phoneme[0] = TOUPPER(*p); + + phoneme[1] = '\0'; + for ( i = 0; i < 99 && (! iswordbreak(p)); LDAP_UTF8INC( p )) { + ch = TOUPPER (*p); + + code = '0'; + + switch (ch) { + case 'B': + case 'F': + case 'P': + case 'V': + code = (adjacent != '1') ? '1' : '0'; + break; + case 'S': + case 'C': + case 'G': + case 'J': + case 'K': + case 'Q': + case 'X': + case 'Z': + code = (adjacent != '2') ? '2' : '0'; + break; + case 'D': + case 'T': + code = (adjacent != '3') ? '3' : '0'; + break; + case 'L': + code = (adjacent != '4') ? '4' : '0'; + break; + case 'M': + case 'N': + code = (adjacent != '5') ? '5' : '0'; + break; + case 'R': + code = (adjacent != '6') ? '6' : '0'; + break; + default: + adjacent = '0'; + } + + if ( i == 0 ) { + adjacent = code; + i++; + } else if ( code != '0' ) { + if ( i == MAXPHONEMELEN ) + break; + adjacent = phoneme[i] = code; + i++; + } + } + + if ( i > 0 ) + phoneme[i] = '\0'; + + return( slapi_ch_strdup( phoneme ) ); +} + +#else +#if defined(METAPHONE) + +/* + * Metaphone copied from C Gazette, June/July 1991, pp 56-57, + * author Gary A. Parker, with changes by Bernard Tiffany of the + * University of Michigan, and more changes by Tim Howes of the + * University of Michigan. + */ + +/* Character coding array */ +static char vsvfn[26] = { + 1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2, + /* A B C D E F G H I J K L M */ + 2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0}; + /* N O P Q R S T U V W X Y Z */ + +/* Macros to access character coding array */ +#define vowel(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 1) /* AEIOU */ +#define same(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 2) /* FJLMNR */ +#define varson(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 4) /* CGPST */ +#define frontv(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 8) /* EIY */ +#define noghf(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 16) /* BDH */ + +char * +phonetic( char *Word ) +{ + char *n, *n_start, *n_end; /* pointers to string */ + char *metaph_end; /* pointers to metaph */ + char ntrans[42]; /* word with uppercase letters */ + int KSflag; /* state flag for X -> KS */ + char buf[MAXPHONEMELEN + 2]; + char *Metaph; + + /* + * Copy Word to internal buffer, dropping non-alphabetic characters + * and converting to upper case + */ + n = ntrans + 4; n_end = ntrans + 35; + while (!iswordbreak( Word ) && n < n_end) { + if (isascii(*Word)) { + if (isalpha(*Word)) { + *n++ = TOUPPER(*Word); + } + ++Word; + } else { + auto const size_t len = LDAP_UTF8COPY(n, Word); + n += len; Word += len; + } + } + Metaph = buf; + *Metaph = '\0'; + if (n == ntrans + 4) { + return( slapi_ch_strdup( buf ) ); /* Return if null */ + } + n_end = n; /* Set n_end to end of string */ + + /* ntrans[0] will always be == 0 */ + ntrans[0] = '\0'; + ntrans[1] = '\0'; + ntrans[2] = '\0'; + ntrans[3] = '\0'; + *n++ = 0; + *n++ = 0; + *n++ = 0; + *n = 0; /* Pad with nulls */ + n = ntrans + 4; /* Assign pointer to start */ + + /* Check for PN, KN, GN, AE, WR, WH, and X at start */ + switch (*n) { + case 'P': + case 'K': + case 'G': + /* 'PN', 'KN', 'GN' becomes 'N' */ + if (*(n + 1) == 'N') + *n++ = 0; + break; + case 'A': + /* 'AE' becomes 'E' */ + if (*(n + 1) == 'E') + *n++ = 0; + break; + case 'W': + /* 'WR' becomes 'R', and 'WH' to 'H' */ + if (*(n + 1) == 'R') + *n++ = 0; + else if (*(n + 1) == 'H') { + *(n + 1) = *n; + *n++ = 0; + } + break; + case 'X': + /* 'X' becomes 'S' */ + *n = 'S'; + break; + } + + /* + * Now, loop step through string, stopping at end of string or when + * the computed 'metaph' is MAXPHONEMELEN characters long + */ + + KSflag = 0; /* state flag for KS translation */ + for (metaph_end = Metaph + MAXPHONEMELEN, n_start = n; + n <= n_end && Metaph < metaph_end; n++) { + if (KSflag) { + KSflag = 0; + *Metaph++ = 'S'; + } else if (!isascii(*n)) { + *Metaph++ = *n; + } else { + /* Drop duplicates except for CC */ + if (*(n - 1) == *n && *n != 'C') + continue; + /* Check for F J L M N R or first letter vowel */ + if (same(*n) || (n == n_start && vowel(*n))) { + *Metaph++ = *n; + } else { + switch (*n) { + case 'B': + + /* + * B unless in -MB + */ + if (n < (n_end - 1) && *(n - 1) != 'M') { + *Metaph++ = *n; + } + break; + case 'C': + + /* + * X if in -CIA-, -CH- else S if in + * -CI-, -CE-, -CY- else dropped if + * in -SCI-, -SCE-, -SCY- else K + */ + if (*(n - 1) != 'S' || !frontv(*(n + 1))) { + if (*(n + 1) == 'I' && *(n + 2) == 'A') { + *Metaph++ = 'X'; + } else if (frontv(*(n + 1))) { + *Metaph++ = 'S'; + } else if (*(n + 1) == 'H') { + *Metaph++ = ((n == n_start && !vowel(*(n + 2))) + || *(n - 1) == 'S') + ? (char) 'K' : (char) 'X'; + } else { + *Metaph++ = 'K'; + } + } + break; + case 'D': + + /* + * J if in DGE or DGI or DGY else T + */ + *Metaph++ = (*(n + 1) == 'G' && frontv(*(n + 2))) + ? (char) 'J' : (char) 'T'; + break; + case 'G': + + /* + * F if in -GH and not B--GH, D--GH, + * -H--GH, -H---GH else dropped if + * -GNED, -GN, -DGE-, -DGI-, -DGY- + * else J if in -GE-, -GI-, -GY- and + * not GG else K + */ + if ((*(n + 1) != 'J' || vowel(*(n + 2))) && + (*(n + 1) != 'N' || ((n + 1) < n_end && + (*(n + 2) != 'E' || *(n + 3) != 'D'))) && + (*(n - 1) != 'D' || !frontv(*(n + 1)))) + *Metaph++ = (frontv(*(n + 1)) && + *(n + 2) != 'G') ? (char) 'G' : (char) 'K'; + else if (*(n + 1) == 'H' && !noghf(*(n - 3)) && + *(n - 4) != 'H') + *Metaph++ = 'F'; + break; + case 'H': + + /* + * H if before a vowel and not after + * C, G, P, S, T else dropped + */ + if (!varson(*(n - 1)) && (!vowel(*(n - 1)) || + vowel(*(n + 1)))) + *Metaph++ = 'H'; + break; + case 'K': + + /* + * dropped if after C else K + */ + if (*(n - 1) != 'C') + *Metaph++ = 'K'; + break; + case 'P': + + /* + * F if before H, else P + */ + *Metaph++ = *(n + 1) == 'H' ? + (char) 'F' : (char) 'P'; + break; + case 'Q': + + /* + * K + */ + *Metaph++ = 'K'; + break; + case 'S': + + /* + * X in -SH-, -SIO- or -SIA- else S + */ + *Metaph++ = (*(n + 1) == 'H' || + (*(n + 1) == 'I' && (*(n + 2) == 'O' || + *(n + 2) == 'A'))) + ? (char) 'X' : (char) 'S'; + break; + case 'T': + + /* + * X in -TIA- or -TIO- else 0 (zero) + * before H else dropped if in -TCH- + * else T + */ + if (*(n + 1) == 'I' && (*(n + 2) == 'O' || + *(n + 2) == 'A')) + *Metaph++ = 'X'; + else if (*(n + 1) == 'H') + *Metaph++ = '0'; + else if (*(n + 1) != 'C' || *(n + 2) != 'H') + *Metaph++ = 'T'; + break; + case 'V': + + /* + * F + */ + *Metaph++ = 'F'; + break; + case 'W': + + /* + * W after a vowel, else dropped + */ + case 'Y': + + /* + * Y unless followed by a vowel + */ + if (vowel(*(n + 1))) + *Metaph++ = *n; + break; + case 'X': + + /* + * KS + */ + if (n == n_start) + *Metaph++ = 'S'; + else { + *Metaph++ = 'K'; /* Insert K, then S */ + KSflag = 1; + } + break; + case 'Z': + + /* + * S + */ + *Metaph++ = 'S'; + break; + } + } + } + } + + *Metaph = 0; /* Null terminate */ + return( slapi_ch_strdup( buf ) ); +} + +#endif /* METAPHONE */ +#endif /* !SOUNDEX */ diff --git a/ldap/servers/plugins/syntaxes/sicis.c b/ldap/servers/plugins/syntaxes/sicis.c new file mode 100644 index 00000000..4bd6623d --- /dev/null +++ b/ldap/servers/plugins/syntaxes/sicis.c @@ -0,0 +1,139 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2002 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * sicis.c - space insensitive string syntax routines. + * these strings are also case insensitive. + */ +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int sicis_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int sicis_filter_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value **bvals ); +static int sicis_values2keys( Slapi_PBlock *pb, Slapi_Value **val, + Slapi_Value ***ivals, int ftype ); +static int sicis_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ); +static int sicis_assertion2keys_sub( Slapi_PBlock *pb, char *initial, + char **any, char *final, Slapi_Value ***ivals ); +static int sicis_compare(struct berval *v1, struct berval *v2); + +/* the first name is the official one from RFC 2252 */ +static char *names[] = { "SpaceInsensitiveString", + SPACE_INSENSITIVE_STRING_SYNTAX_OID, 0 }; + +static Slapi_PluginDesc pdesc = { "spaceinsensitivestring-syntax", + PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "space insensitive string attribute syntax plugin" }; + +int +sicis_init( Slapi_PBlock *pb ) +{ + int rc, flags; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> sicis_init\n", 0, 0, 0 ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) sicis_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *) sicis_filter_sub ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) sicis_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) sicis_assertion2keys_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *) sicis_assertion2keys_sub ); + flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS, + (void *) &flags ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) SPACE_INSENSITIVE_STRING_SYNTAX_OID ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE, + (void *) sicis_compare ); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= sicis_init %d\n", rc, 0, 0 ); + return( rc ); +} + +static int +sicis_filter_ava( + Slapi_PBlock *pb, + struct berval *bvfilter, + Slapi_Value **bvals, + int ftype, + Slapi_Value **retVal +) +{ + return( string_filter_ava( bvfilter, bvals, SYNTAX_SI | SYNTAX_CIS, + ftype, retVal ) ); +} + + +static int +sicis_filter_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value **bvals +) +{ + return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_SI | SYNTAX_CIS ) ); +} + +static int +sicis_values2keys( + Slapi_PBlock *pb, + Slapi_Value **vals, + Slapi_Value ***ivals, + int ftype +) +{ + return( string_values2keys( pb, vals, ivals, SYNTAX_SI | SYNTAX_CIS, + ftype ) ); +} + +static int +sicis_assertion2keys_ava( + Slapi_PBlock *pb, + Slapi_Value *val, + Slapi_Value ***ivals, + int ftype +) +{ + return(string_assertion2keys_ava( pb, val, ivals, + SYNTAX_SI | SYNTAX_CIS, ftype )); +} + +static int +sicis_assertion2keys_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value ***ivals +) +{ + return( string_assertion2keys_sub( pb, initial, any, final, ivals, + SYNTAX_SI | SYNTAX_CIS ) ); +} + +static int sicis_compare( + struct berval *v1, + struct berval *v2 +) +{ + return value_cmp(v1, v2, SYNTAX_SI|SYNTAX_CIS, 3 /* Normalise both values */); +} diff --git a/ldap/servers/plugins/syntaxes/string.c b/ldap/servers/plugins/syntaxes/string.c new file mode 100644 index 00000000..d06a15f5 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/string.c @@ -0,0 +1,612 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* string.c - common string syntax routines */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" +#if defined(IRIX) +#include <unistd.h> +#endif +#if defined( MACOS ) || defined( DOS ) || defined( _WIN32 ) || defined( NEED_BSDREGEX ) +#include "regex.h" +#endif + +static int string_filter_approx( struct berval *bvfilter, + Slapi_Value **bvals, Slapi_Value **retVal ); +static void substring_comp_keys( Slapi_Value ***ivals, int *nsubs, char *str, + int prepost, int syntax ); + +int +string_filter_ava( struct berval *bvfilter, Slapi_Value **bvals, int syntax, + int ftype, Slapi_Value **retVal ) +{ + int i, rc; + struct berval bvfilter_norm; + + if(retVal) { + *retVal = NULL; + } + if ( ftype == LDAP_FILTER_APPROX ) { + return( string_filter_approx( bvfilter, bvals, retVal ) ); + } + + bvfilter_norm.bv_val = slapi_ch_malloc( bvfilter->bv_len + 1 ); + SAFEMEMCPY( bvfilter_norm.bv_val, bvfilter->bv_val, bvfilter->bv_len ); + bvfilter_norm.bv_val[bvfilter->bv_len] = '\0'; + value_normalize( bvfilter_norm.bv_val, syntax, 1 /* trim leading blanks */ ); + + for ( i = 0; bvals[i] != NULL; i++ ) { + rc = value_cmp( (struct berval*)slapi_value_get_berval(bvals[i]), &bvfilter_norm, syntax, 1/* Normalise the first value only */ ); + switch ( ftype ) { + case LDAP_FILTER_GE: + if ( rc >= 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + slapi_ch_free ((void**)&bvfilter_norm.bv_val); + return( 0 ); + } + break; + case LDAP_FILTER_LE: + if ( rc <= 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + slapi_ch_free ((void**)&bvfilter_norm.bv_val); + return( 0 ); + } + break; + case LDAP_FILTER_EQUALITY: + if ( rc == 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + slapi_ch_free ((void**)&bvfilter_norm.bv_val); + return( 0 ); + } + break; + } + } + + slapi_ch_free ((void**)&bvfilter_norm.bv_val); + return( -1 ); +} + +static int +string_filter_approx( struct berval *bvfilter, Slapi_Value **bvals, + Slapi_Value **retVal) +{ + int i, rc; + int ava_wordcount; + char *w1, *w2, *c1, *c2; + + /* + * try to match words in each filter value in order + * in the attribute value. + * XXX should do this once for the filter and save it XXX + */ + rc = -1; + if(retVal) { + *retVal = NULL; + } + for ( i = 0; bvals[i] != NULL; i++ ) { + w2 = (char*)slapi_value_get_string(bvals[i]); /* JCM cast */ + ava_wordcount = 0; + /* for each word in the filter value */ + for ( w1 = first_word( bvfilter->bv_val ); w1 != NULL; + w1 = next_word( w1 ) ) { + ++ava_wordcount; + if ( (c1 = phonetic( w1 )) == NULL ) { + break; + } + + /* + * for each word in the attribute value from + * where we left off... + */ + for ( w2 = first_word( w2 ); w2 != NULL; + w2 = next_word( w2 ) ) { + c2 = phonetic( w2 ); + rc = strcmp( c1, c2 ); + slapi_ch_free((void**)&c2 ); + if ( rc == 0 ) { + if(retVal) { + *retVal = bvals[i]; + } + break; + } + } + slapi_ch_free((void**)&c1 ); + + /* + * if we stopped because we ran out of words + * before making a match, go on to the next + * value. otherwise try to keep matching + * words in this value from where we left off. + */ + if ( w2 == NULL ) { + break; + } else { + w2 = next_word( w2 ); + } + } + /* + * if we stopped because we ran out of words and + * we found at leasy one word, we have a match. + */ + if ( w1 == NULL && ava_wordcount > 0 ) { + rc = 0; + break; + } + } + LDAPDebug( LDAP_DEBUG_TRACE, "<= string_filter_approx %d\n", + rc, 0, 0 ); + + return( rc ); +} + +int +string_filter_sub( Slapi_PBlock *pb, char *initial, char **any, char *final, + Slapi_Value **bvals, int syntax ) +{ + int i, j, rc; + char *p, *end, *realval, *tmpbuf; + size_t tmpbufsize; + char pat[BUFSIZ]; + char buf[BUFSIZ]; + char ebuf[BUFSIZ]; + + LDAPDebug( LDAP_DEBUG_FILTER, "=> string_filter_sub\n", + 0, 0, 0 ); + + /* + * construct a regular expression corresponding to the + * filter and let regex do the work for each value + * XXX should do this once and save it somewhere XXX + */ + pat[0] = '\0'; + p = pat; + end = pat + sizeof(pat) - 2; /* leave room for null */ + if ( initial != NULL ) { + value_normalize( initial, syntax, 1 /* trim leading blanks */ ); + strcpy( p, "^" ); + p = strchr( p, '\0' ); + /* 2 * in case every char is special */ + if ( p + 2 * strlen( initial ) > end ) { + LDAPDebug( LDAP_DEBUG_ANY, "not enough pattern space\n", + 0, 0, 0 ); + return( -1 ); + } + filter_strcpy_special( p, initial ); + p = strchr( p, '\0' ); + } + if ( any != NULL ) { + for ( i = 0; any[i] != NULL; i++ ) { + value_normalize( any[i], syntax, 0 /* DO NOT trim leading blanks */ ); + /* ".*" + value */ + if ( p + 2 * strlen( any[i] ) + 2 > end ) { + LDAPDebug( LDAP_DEBUG_ANY, + "not enough pattern space\n", 0, 0, 0 ); + return( -1 ); + } + strcpy( p, ".*" ); + p = strchr( p, '\0' ); + filter_strcpy_special( p, any[i] ); + p = strchr( p, '\0' ); + } + } + if ( final != NULL ) { + value_normalize( final, syntax, 0 /* DO NOT trim leading blanks */ ); + /* ".*" + value */ + if ( p + 2 * strlen( final ) + 2 > end ) { + LDAPDebug( LDAP_DEBUG_ANY, "not enough pattern space\n", + 0, 0, 0 ); + return( -1 ); + } + strcpy( p, ".*" ); + p = strchr( p, '\0' ); + filter_strcpy_special( p, final ); + p = strchr( p, '\0' ); + strcpy( p, "$" ); + } + + /* compile the regex */ + slapd_re_lock(); + if ( (p = slapd_re_comp( pat )) != 0 ) { + LDAPDebug( LDAP_DEBUG_ANY, "re_comp (%s) failed (%s)\n", + pat, p, 0 ); + slapd_re_unlock(); + return( -1 ); + } else { + LDAPDebug( LDAP_DEBUG_TRACE, "re_comp (%s)\n", + escape_string( pat, ebuf ), 0, 0 ); + } + + /* + * test the regex against each value + */ + rc = -1; + tmpbuf = NULL; + tmpbufsize = 0; + for ( j = 0; bvals[j] != NULL; j++ ) { + int tmprc; + size_t len; + const struct berval *bvp = slapi_value_get_berval(bvals[j]); + + len = bvp->bv_len; + if ( len < sizeof(buf) ) { + strcpy( buf, bvp->bv_val ); + realval = buf; + } else if ( len < tmpbufsize ) { + strcpy( buf, bvp->bv_val ); + realval = tmpbuf; + } else { + tmpbuf = (char *) slapi_ch_realloc( tmpbuf, len + 1 ); + strcpy( tmpbuf, bvp->bv_val ); + realval = tmpbuf; + } + value_normalize( realval, syntax, 1 /* trim leading blanks */ ); + + tmprc = slapd_re_exec( realval ); + + LDAPDebug( LDAP_DEBUG_TRACE, "re_exec (%s) %i\n", + escape_string( realval, ebuf ), tmprc, 0 ); + if ( tmprc != 0 ) { + rc = 0; + break; + } + } + slapd_re_unlock(); + if ( tmpbuf != NULL ) { + slapi_ch_free((void**)&tmpbuf ); + } + + LDAPDebug( LDAP_DEBUG_FILTER, "<= string_filter_sub %d\n", + rc, 0, 0 ); + return( rc ); +} + +int +string_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals, + Slapi_Value ***ivals, int syntax, int ftype ) +{ + int nsubs, numbvals, i, n, j; + Slapi_Value **nbvals; + char *w, *c, *p; + char buf[SUBLEN+1]; + + switch ( ftype ) { + case LDAP_FILTER_EQUALITY: + /* allocate a new array for the normalized values */ + for ( numbvals = 0; bvals[numbvals] != NULL; numbvals++ ) { + /* NULL */ + } + nbvals = (Slapi_Value **) slapi_ch_malloc( (numbvals+1) * sizeof(Slapi_Value *)); + + for ( i = 0; i < numbvals; i++ ) + { + c = slapi_ch_strdup(slapi_value_get_string(bvals[i])); + value_normalize( c, syntax, 1 /* trim leading blanks */ ); + nbvals[i] = slapi_value_new_string_passin(c); + } + nbvals[i] = NULL; + *ivals = nbvals; + break; + + case LDAP_FILTER_APPROX: + /* XXX should not do this twice! XXX */ + /* get an upper bound on the number of ivals */ + numbvals = 0; + for ( i = 0; bvals[i] != NULL; i++ ) { + for ( w = first_word( (char*)slapi_value_get_string(bvals[i]) ); w != NULL; + w = next_word( w ) ) { + numbvals++; + } + } + nbvals = (Slapi_Value **) slapi_ch_malloc( (numbvals + 1) * sizeof(Slapi_Value *) ); + + n = 0; + for ( i = 0; bvals[i] != NULL; i++ ) { + for ( w = first_word( (char*)slapi_value_get_string(bvals[i]) ); w != NULL; + w = next_word( w ) ) { + if ( (c = phonetic( w )) != NULL ) { + nbvals[n] = slapi_value_new_string_passin(c); + n++; + } + } + } + nbvals[n] = NULL; + + if ( n == 0 ) { + slapi_ch_free((void**)ivals ); + return( 0 ); + } + *ivals = nbvals; + break; + + case LDAP_FILTER_SUBSTRINGS: + { + /* XXX should remove duplicates! XXX */ + Slapi_Value *bvdup; + const struct berval *bvp; + nsubs = 0; + for ( i = 0; bvals[i] != NULL; i++ ) { + /* + * Note: this calculation may err on the high side, + * because value_normalize(), which is called below + * before we actually create the substring keys, may + * reduce the length of the value in some cases. For + * example, spaces are removed when space insensitive + * strings are normalized. But it's okay for nsubs to + * be too big. Since the ivals array is NULL terminated, + * the only downside is that we allocate more space than + * we really need. + */ + nsubs += slapi_value_get_length(bvals[i]) - SUBLEN + 3; + } + *ivals = (Slapi_Value **) slapi_ch_malloc( (nsubs + 1) * sizeof(Slapi_Value *) ); + + buf[SUBLEN] = '\0'; + n = 0; + + bvdup= slapi_value_new(); + for ( i = 0; bvals[i] != NULL; i++ ) + { + c = slapi_ch_strdup(slapi_value_get_string(bvals[i])); + value_normalize( c, syntax, 1 /* trim leading blanks */ ); + slapi_value_set_string_passin(bvdup, c); + + bvp = slapi_value_get_berval(bvdup); + + /* leading */ + if ( bvp->bv_len > SUBLEN - 2 ) { + buf[0] = '^'; + for ( j = 0; j < SUBLEN - 1; j++ ) { + buf[j + 1] = bvp->bv_val[j]; + } + (*ivals)[n] = slapi_value_new_string(buf); + n++; + } + + /* any */ + for ( p = bvp->bv_val; + p < (bvp->bv_val + bvp->bv_len - SUBLEN + 1); + p++ ) { + for ( j = 0; j < SUBLEN; j++ ) { + buf[j] = p[j]; + } + buf[SUBLEN] = '\0'; + (*ivals)[n] = slapi_value_new_string(buf); + n++; + } + + /* trailing */ + if ( bvp->bv_len > SUBLEN - 2 ) { + p = bvp->bv_val + bvp->bv_len - SUBLEN + 1; + for ( j = 0; j < SUBLEN - 1; j++ ) { + buf[j] = p[j]; + } + buf[SUBLEN - 1] = '$'; + (*ivals)[n] = slapi_value_new_string(buf); + n++; + } + } + slapi_value_free(&bvdup); + (*ivals)[n] = NULL; + } + break; + } + + return( 0 ); +} + + +/* we've added code to make our equality filter processing faster */ + +int +string_assertion2keys_ava( + Slapi_PBlock *pb, + Slapi_Value *val, + Slapi_Value ***ivals, + int syntax, + int ftype +) +{ + int i, numbvals; + size_t len; + char *w, *c; + Slapi_Value *tmpval=NULL; + + switch ( ftype ) { + case LDAP_FILTER_EQUALITY_FAST: + /* this code is trying to avoid multiple malloc/frees */ + len=slapi_value_get_length(val); + tmpval=(*ivals)[0]; + if (len >= tmpval->bv.bv_len) { + tmpval->bv.bv_val=(char *)slapi_ch_malloc(len+1); + } + memcpy(tmpval->bv.bv_val,slapi_value_get_string(val),len); + tmpval->bv.bv_val[len]='\0'; + value_normalize(tmpval->bv.bv_val, syntax, 1 /* trim leading blanks */ ); + tmpval->bv.bv_len=strlen(tmpval->bv.bv_val); + break; + case LDAP_FILTER_EQUALITY: + (*ivals) = (Slapi_Value **) slapi_ch_malloc( 2 * sizeof(Slapi_Value *) ); + (*ivals)[0] = slapi_value_dup( val ); + value_normalize( (*ivals)[0]->bv.bv_val, syntax, 1 /* trim leading blanks */ ); + (*ivals)[0]->bv.bv_len = strlen( (*ivals)[0]->bv.bv_val ); + (*ivals)[1] = NULL; + break; + + case LDAP_FILTER_APPROX: + /* XXX should not do this twice! XXX */ + /* get an upper bound on the number of ivals */ + numbvals = 0; + for ( w = first_word( (char*)slapi_value_get_string(val) ); w != NULL; + w = next_word( w ) ) { + numbvals++; + } + (*ivals) = (Slapi_Value **) slapi_ch_malloc( (numbvals + 1) * + sizeof(Slapi_Value *) ); + + i = 0; + for ( w = first_word( (char*)slapi_value_get_string(val) ); w != NULL; + w = next_word( w ) ) { + if ( (c = phonetic( w )) != NULL ) { + (*ivals)[i] = slapi_value_new_string_passin(c); + i++; + } + } + (*ivals)[i] = NULL; + + if ( i == 0 ) { + slapi_ch_free((void**)ivals ); + return( 0 ); + } + break; + default: + LDAPDebug( LDAP_DEBUG_ANY, + "string_assertion2keys_ava: unknown ftype 0x%x\n", + ftype, 0, 0 ); + break; + } + + return( 0 ); +} + +int +string_assertion2keys_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value ***ivals, + int syntax +) +{ + int nsubs, i, len; + + *ivals = NULL; + + /* + * First figure out how many keys we will return. The answer is based + * on the length of each assertion value. Since normalization may + * reduce the length (such as when spaces are removed from space + * insensitive strings), we call value_normalize() before checking + * the length. + */ + nsubs = 0; + if ( initial != NULL ) { + value_normalize( initial, syntax, 0 /* do not trim leading blanks */ ); + if ( strlen( initial ) > SUBLEN - 2 ) { + nsubs += strlen( initial ) - SUBLEN + 2; + } else { + initial = NULL; /* save some work later */ + } + } + for ( i = 0; any != NULL && any[i] != NULL; i++ ) { + value_normalize( any[i], syntax, 0 /* do not trim leading blanks */ ); + len = strlen( any[i] ); + if ( len >= SUBLEN ) { + nsubs += len - SUBLEN + 1; + } + } + if ( final != NULL ) { + value_normalize( final, syntax, 0 /* do not trim leading blanks */ ); + if ( strlen( final ) > SUBLEN - 2 ) { + nsubs += strlen( final ) - SUBLEN + 2; + } else { + final = NULL; /* save some work later */ + } + } + if ( nsubs == 0 ) { /* no keys to return */ + return( 0 ); + } + + /* + * Next, allocated the ivals array and fill it in with the actual + * keys. *ivals is a NULL terminated array of Slapi_Value pointers. + */ + + *ivals = (Slapi_Value **) slapi_ch_malloc( (nsubs + 1) * sizeof(Slapi_Value *) ); + + nsubs = 0; + if ( initial != NULL ) { + substring_comp_keys( ivals, &nsubs, initial, '^', syntax ); + } + for ( i = 0; any != NULL && any[i] != NULL; i++ ) { + if ( strlen( any[i] ) < SUBLEN ) { + continue; + } + substring_comp_keys( ivals, &nsubs, any[i], 0, syntax ); + } + if ( final != NULL ) { + substring_comp_keys( ivals, &nsubs, final, '$', syntax ); + } + (*ivals)[nsubs] = NULL; + + return( 0 ); +} + +static void +substring_comp_keys( + Slapi_Value ***ivals, + int *nsubs, + char *str, + int prepost, + int syntax +) +{ + int i, len; + char *p; + char buf[SUBLEN + 1]; + + LDAPDebug( LDAP_DEBUG_TRACE, "=> substring_comp_keys (%s) %d\n", + str, prepost, 0 ); + + len = strlen( str ); + + /* prepend ^ for initial substring */ + if ( prepost == '^' ) + { + buf[0] = '^'; + for ( i = 0; i < SUBLEN - 1; i++ ) + { + buf[i + 1] = str[i]; + } + buf[SUBLEN] = '\0'; + (*ivals)[*nsubs] = slapi_value_new_string(buf); + (*nsubs)++; + } + + for ( p = str; p < (str + len - SUBLEN + 1); p++ ) + { + for ( i = 0; i < SUBLEN; i++ ) + { + buf[i] = p[i]; + } + buf[SUBLEN] = '\0'; + (*ivals)[*nsubs] = slapi_value_new_string(buf); + (*nsubs)++; + } + + if ( prepost == '$' ) + { + p = str + len - SUBLEN + 1; + for ( i = 0; i < SUBLEN - 1; i++ ) + { + buf[i] = p[i]; + } + buf[SUBLEN - 1] = '$'; + buf[SUBLEN] = '\0'; + (*ivals)[*nsubs] = slapi_value_new_string(buf); + (*nsubs)++; + } + + LDAPDebug( LDAP_DEBUG_TRACE, "<= substring_comp_keys\n", 0, 0, 0 ); +} diff --git a/ldap/servers/plugins/syntaxes/syntax.h b/ldap/servers/plugins/syntaxes/syntax.h new file mode 100644 index 00000000..d6d883c9 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/syntax.h @@ -0,0 +1,42 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* syntax.h - string syntax definitions */ + +#ifndef _LIBSYNTAX_H_ +#define _LIBSYNTAX_H_ + +#define SLAPD_LOGGING 1 + +#include "slap.h" +#include "slapi-plugin.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +#define SYNTAX_CIS 1 +#define SYNTAX_CES 2 +#define SYNTAX_TEL 4 /* telephone number: used with SYNTAX_CIS */ +#define SYNTAX_DN 8 /* distinguished name: used with SYNTAX_CIS */ +#define SYNTAX_SI 16 /* space insensitive: used with SYNTAX_CIS */ + +#define SUBLEN 3 + +#ifndef MIN +#define MIN( a, b ) (a < b ? a : b ) +#endif + +int string_filter_sub( Slapi_PBlock *pb, char *initial, char **any, char *final,Slapi_Value **bvals, int syntax ); +int string_filter_ava( struct berval *bvfilter, Slapi_Value **bvals, int syntax,int ftype, Slapi_Value **retVal ); +int string_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals,Slapi_Value ***ivals, int syntax, int ftype ); +int string_assertion2keys_ava(Slapi_PBlock *pb,Slapi_Value *val,Slapi_Value ***ivals,int syntax,int ftype ); +int string_assertion2keys_sub(Slapi_PBlock *pb,char *initial,char **any,char *final,Slapi_Value ***ivals,int syntax); +int value_cmp(struct berval *v1,struct berval *v2,int syntax,int normalize); +void value_normalize(char *s,int syntax,int trim_leading_blanks); + +char *first_word( char *s ); +char *next_word( char *s ); +char *phonetic( char *s ); + + +#endif diff --git a/ldap/servers/plugins/syntaxes/tel.c b/ldap/servers/plugins/syntaxes/tel.c new file mode 100644 index 00000000..c2c80d1a --- /dev/null +++ b/ldap/servers/plugins/syntaxes/tel.c @@ -0,0 +1,135 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* tel.c - telephonenumber syntax routines */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +static int tel_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter, + Slapi_Value **bvals, int ftype, Slapi_Value **retVal ); +static int tel_filter_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value **bvals ); +static int tel_values2keys( Slapi_PBlock *pb, Slapi_Value **val, + Slapi_Value ***ivals, int ftype ); +static int tel_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val, + Slapi_Value ***ivals, int ftype ); +static int tel_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any, + char *final, Slapi_Value ***ivals ); +static int tel_compare(struct berval *v1, struct berval *v2); + +/* the first name is the official one from RFC 2252 */ +static char *names[] = { "TelephoneNumber", "tel", TELEPHONE_SYNTAX_OID, 0 }; + +static Slapi_PluginDesc pdesc = { "tele-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "telephoneNumber attribute syntax plugin" }; + +int +tel_init( Slapi_PBlock *pb ) +{ + int rc, flags; + + LDAPDebug( LDAP_DEBUG_PLUGIN, "=> tel_init\n", 0, 0, 0 ); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *) tel_filter_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *) tel_filter_sub ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *) tel_values2keys ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *) tel_assertion2keys_ava ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *) tel_assertion2keys_sub ); + flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING; + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS, + (void *) &flags ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *) names ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *) TELEPHONE_SYNTAX_OID ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE, + (void *) tel_compare ); + + LDAPDebug( LDAP_DEBUG_PLUGIN, "<= tel_init %d\n", rc, 0, 0 ); + return( rc ); +} + +static int +tel_filter_ava( + Slapi_PBlock *pb, + struct berval *bvfilter, + Slapi_Value **bvals, + int ftype, + Slapi_Value **retVal +) +{ + return( string_filter_ava( bvfilter, bvals, SYNTAX_TEL | SYNTAX_CIS, + ftype, retVal ) ); +} + + +static int +tel_filter_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value **bvals +) +{ + return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_TEL | SYNTAX_CIS ) ); +} + +static int +tel_values2keys( + Slapi_PBlock *pb, + Slapi_Value **vals, + Slapi_Value ***ivals, + int ftype +) +{ + return( string_values2keys( pb, vals, ivals, SYNTAX_TEL | SYNTAX_CIS, + ftype ) ); +} + +static int +tel_assertion2keys_ava( + Slapi_PBlock *pb, + Slapi_Value *val, + Slapi_Value ***ivals, + int ftype +) +{ + return(string_assertion2keys_ava( pb, val, ivals, + SYNTAX_TEL | SYNTAX_CIS, ftype )); +} + +static int +tel_assertion2keys_sub( + Slapi_PBlock *pb, + char *initial, + char **any, + char *final, + Slapi_Value ***ivals +) +{ + return( string_assertion2keys_sub( pb, initial, any, final, ivals, + SYNTAX_TEL | SYNTAX_CIS ) ); +} + +static int tel_compare( + struct berval *v1, + struct berval *v2 +) +{ + return value_cmp(v1, v2, SYNTAX_TEL|SYNTAX_CIS, 3 /* Normalise both values */); +} diff --git a/ldap/servers/plugins/syntaxes/value.c b/ldap/servers/plugins/syntaxes/value.c new file mode 100644 index 00000000..be496091 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/value.c @@ -0,0 +1,209 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* value.c - routines for dealing with values */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include "syntax.h" + +/* + * Do not use the SDK ldap_utf8isspace directly until it is faster + * than this one. + */ +static int +utf8isspace_fast( char* s ) +{ + register unsigned char c = *(unsigned char*)s; + if (0x80 & c) return(ldap_utf8isspace(s)); + switch (c) { + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x20: + return 1; + default: break; + } + return 0; +} + +/* +** This function is used to normalizes search filter components, +** and attribute values. +** +** jcm: I added the trim_spaces flag since this function +** was incorrectly modifying search filter components. A search +** of the form "cn=a* b*" (note the space) would be wrongly +** normalized into "cn=a*b*", because this function is called +** once for "a" and once for " b". +*/ +void +value_normalize( + char *s, + int syntax, + int trim_spaces +) +{ + char *d; + int prevspace, curspace; + + if ( ! (syntax & SYNTAX_CIS) && ! (syntax & SYNTAX_CES) ) { + return; + } + + if ( syntax & SYNTAX_DN ) { + (void) slapi_dn_normalize_case( s ); + return; + } + + d = s; + if (trim_spaces) { + /* strip leading blanks */ + while (utf8isspace_fast(s)) { + LDAP_UTF8INC(s); + } + } + /* handle value of all spaces - turn into single space */ + /* unless space insensitive syntax - turn into zero length string */ + if ( *s == '\0' && s != d ) { + if ( ! (syntax & SYNTAX_SI)) { + *d++ = ' '; + } + *d = '\0'; + return; + } + prevspace = 0; + while ( *s ) { + curspace = utf8isspace_fast(s); + + /* ignore spaces and '-' in telephone numbers */ + if ( (syntax & SYNTAX_TEL) && (curspace || *s == '-') ) { + LDAP_UTF8INC(s); + continue; + } + + /* ignore all spaces if this is a space insensitive value */ + if ( (syntax & SYNTAX_SI) && curspace ) { + LDAP_UTF8INC(s); + continue; + } + + /* compress multiple blanks */ + if ( prevspace && curspace ) { + LDAP_UTF8INC(s); + continue; + } + prevspace = curspace; + if ( syntax & SYNTAX_CIS ) { + int ssz, dsz; + slapi_utf8ToLower((unsigned char*)s, (unsigned char *)d, &ssz, &dsz); + s += ssz; + d += dsz; + } else { + char *np; + int sz; + + np = ldap_utf8next(s); + if (np == NULL || np == s) break; + sz = np - s; + memcpy(d,s,sz); + d += sz; + s += sz; + } + } + *d = '\0'; + /* strip trailing blanks */ + if (prevspace && trim_spaces) { + char *nd; + + nd = ldap_utf8prev(d); + while (nd && utf8isspace_fast(nd)) { + d = nd; + nd = ldap_utf8prev(d); + *d = '\0'; + } + } +} + +int +value_cmp( + struct berval *v1, + struct berval *v2, + int syntax, + int normalize +) +{ + int rc; + struct berval bvcopy1; + struct berval bvcopy2; + char little_buffer[64]; + size_t buffer_space = sizeof(little_buffer); + int buffer_offset = 0; + int free_v1 = 0; + int free_v2 = 0; + + /* This code used to call malloc up to four times in the copying + * of attributes to be normalized. Now we attempt to keep everything + * on the stack and only malloc if the data is big + */ + if ( normalize & 1 ) { + /* Do we have space in the little buffer ? */ + if (v1->bv_len < buffer_space) { + bvcopy1.bv_len = v1->bv_len; + SAFEMEMCPY(&little_buffer[buffer_offset],v1->bv_val,v1->bv_len); + bvcopy1.bv_val = &little_buffer[buffer_offset]; + bvcopy1.bv_val[v1->bv_len] = '\0'; + v1 = &bvcopy1; + buffer_space-= v1->bv_len+1; + buffer_offset+= v1->bv_len+1; + } else { + v1 = ber_bvdup( v1 ); + free_v1 = 1; + } + value_normalize( v1->bv_val, syntax, 1 /* trim leading blanks */ ); + } + if ( normalize & 2 ) { + /* Do we have space in the little buffer ? */ + if (v2->bv_len < buffer_space) { + bvcopy2.bv_len = v2->bv_len; + SAFEMEMCPY(&little_buffer[buffer_offset],v2->bv_val,v2->bv_len); + bvcopy2.bv_val = &little_buffer[buffer_offset]; + bvcopy2.bv_val[v2->bv_len] = '\0'; + v2 = &bvcopy2; + buffer_space-= v2->bv_len+1; + buffer_offset+= v2->bv_len+1; + } else { + v2 = ber_bvdup( v2 ); + free_v2 = 1; + } + value_normalize( v2->bv_val, syntax, 1 /* trim leading blanks */ ); + } + + switch ( syntax ) { + case SYNTAX_CIS: + case (SYNTAX_CIS | SYNTAX_TEL): + case (SYNTAX_CIS | SYNTAX_DN): + case (SYNTAX_CIS | SYNTAX_SI): + rc = slapi_utf8casecmp( (unsigned char *)v1->bv_val, + (unsigned char *)v2->bv_val ); + break; + + case SYNTAX_CES: + rc = strcmp( v1->bv_val, v2->bv_val ); + break; + } + + if ( (normalize & 1) && free_v1) { + ber_bvfree( v1 ); + } + if ( (normalize & 2) && free_v2) { + ber_bvfree( v2 ); + } + + return( rc ); +} diff --git a/ldap/servers/plugins/uiduniq/7bit.c b/ldap/servers/plugins/uiduniq/7bit.c new file mode 100644 index 00000000..535dbe9c --- /dev/null +++ b/ldap/servers/plugins/uiduniq/7bit.c @@ -0,0 +1,722 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * 7bit.c + * + * Implements a directory server pre-operation plugin to test + * attributes for 7 bit clean within a defined subtree in the + * directory. + * + */ +#include <stdio.h> +#include <slapi-plugin.h> +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include <string.h> +#include "dirver.h" + +/* DBDB this should be pulled from a common header file */ +#ifdef _WIN32 +#ifndef strcasecmp +#define strcasecmp(x,y) strcmpi(x,y) +#endif +#endif + +#if defined( LDAP_DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +/* + * ISSUES: + * How should this plugin handle ACL issues? It seems wrong to reject + * adds and modifies because there is already a conflicting UID, when + * the request would have failed because of an ACL check anyway. + * + * This code currently defines a maximum filter string size of 512. Is + * this large enough? + * + * This code currently does not quote the value portion of the filter as + * it is created. This is a bug. + */ + +/* */ +#define BEGIN do { +#define END } while(0); + +/* + * Slapi plugin descriptor + */ +static char *plugin_name = "NS7bitAttr"; +static Slapi_PluginDesc +pluginDesc = { "NS7bitAttr", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "Enforce 7-bit clean attribute values" }; + + +/* + * More information about constraint failure + */ +static char *moreInfo = + "The value is not 7-bit clean: "; + +/* ------------------------------------------------------------ */ +/* + * op_error - Record (and report) an operational error. + */ +static int +op_error(int internal_error) +{ + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "Internal error: %d\n", internal_error); + + return LDAP_OPERATIONS_ERROR; +} + +static void +issue_error(Slapi_PBlock *pb, int result, char *type, char *value) +{ + char *moreinfop; + int sz; + + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "%s result %d\n", type, result); + + if (value == NULL) { + value = "unknown"; + } + sz = strlen(moreInfo) + strlen(value) + 1; + moreinfop = (char *)slapi_ch_malloc(sz); + sprintf(moreinfop, "%s%s", moreInfo, value); + + /* Send failure to the client */ + slapi_send_ldap_result(pb, result, 0, moreinfop, 0, 0); + slapi_ch_free((void **)&moreinfop); + + return; +} + + +/* + * Check 'value' for 7-bit cleanliness. + */ +static int +bit_check_one_berval(const struct berval *value, char **violated) +{ + int result; + char *ch; + int i; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "7-bit checking begin\n"); +#endif + + result = LDAP_SUCCESS; + /* If no value, can't possibly be a conflict */ + if ( (struct berval *)NULL == value ) + return result; + + for(i=0, ch=value->bv_val; ch && i < (int)(value->bv_len) ; + ch++, i++) + { + + if (( 0x80 & *ch ) != 0 ) + { + result = LDAP_CONSTRAINT_VIOLATION; + *violated = value->bv_val; + break; + } + } + + return result; +} + + +/* + * Check a set of values for 7-bit cleanliness. + * + * If 'attr' is NULL, the values are taken from 'values'. + * If 'attr' is non-NULL, the values are taken from 'attr'. + */ +static int +bit_check(Slapi_Attr *attr, struct berval **values, char **violated) +{ + int result = LDAP_SUCCESS; + *violated = NULL; + + /* If no values, can't possibly be a conflict */ + if ( (Slapi_Attr *)NULL == attr && (struct berval **)NULL == values ) + return result; + + if ( (Slapi_Attr *)NULL != attr ) + { + Slapi_Value *v = NULL; + int vhint = -1; + + for ( vhint = slapi_attr_first_value( attr, &v ); + vhint != -1 && LDAP_SUCCESS == result; + vhint = slapi_attr_next_value( attr, vhint, &v )) + { + result = bit_check_one_berval(slapi_value_get_berval(v), violated); + } + } + else + { + for (;*values != NULL && LDAP_SUCCESS == result; values++) + { + result = bit_check_one_berval(*values, violated); + } + } + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "7 bit check result = %d\n", result); +#endif + + return result; +} + + +/* ------------------------------------------------------------ */ +/* + * preop_add - pre-operation plug-in for add + */ +static int +preop_add(Slapi_PBlock *pb) +{ + int result; + char *violated; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n"); +#endif + + result = LDAP_SUCCESS; + + /* + * Do constraint check on the added entry. Set result. + */ + BEGIN + int err; + int argc; + char **argv; + char **attrName; + char *dn; + Slapi_Entry *e; + Slapi_Attr *attr; + char **firstSubtree; + char **subtreeDN; + int subtreeCnt; + int is_replicated_operation; + + /* + * Get the arguments + */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) { result = op_error(53); break; } + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) { result = op_error(54); break; } + + /* + * If this is a replication update, just be a noop. + */ + err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation); + if (err) { result = op_error(56); break; } + if (is_replicated_operation) + { + break; + } + + /* + * Get the target DN for this add operation + */ + err = slapi_pblock_get(pb, SLAPI_ADD_TARGET, &dn); + if (err) { result = op_error(50); break; } + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD target=%s\n", dn); +#endif + + /* + * Get the entry data for this add. Check whether it + * contains a value for the unique attribute + */ + err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + if (err) { result = op_error(51); break; } + + for ( firstSubtree = argv; strcmp(*firstSubtree, ",") != 0; + firstSubtree++, argc--) {} + firstSubtree++; + argc--; + + for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++ ) + { + /* + * if the attribute is userpassword, check unhashed#user#password + * instead. "userpassword" is encoded; it will always pass the 7bit + * check. + */ + char *attr_name; + if ( strcasecmp(*attrName, "userpassword") == 0 ) + { + attr_name = "unhashed#user#password"; + } else { + attr_name = *attrName; + } + err = slapi_entry_attr_find(e, attr_name, &attr); + if (err) continue; /* break;*/ /* no 7-bit attribute */ + + /* + * For each DN in the managed list, do 7-bit checking if + * the target DN is a subnode in the tree. + */ + for( subtreeDN=firstSubtree, subtreeCnt=argc ;subtreeCnt > 0; + subtreeCnt--,subtreeDN++) + { + /* + * issuffix determines whether the target is under the + * subtree *subtreeDN + */ + if (slapi_dn_issuffix(dn, *subtreeDN)) + { +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "ADD subtree=%s\n", *subtreeDN); +#endif + + /* + * Check if the value is 7-bit clean + */ + result = bit_check(attr, NULL, &violated); + if (result) break; + } + } + /* don't have to go on if there is a value not 7-bit clean */ + if (result) break; + } + END + + if (result) { + issue_error(pb, result, "ADD", violated); + } + + return (result==LDAP_SUCCESS)?0:-1; +} + +static void +addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd) +{ + if (*nmods == *capacity) { + *capacity += 4; + if (*modary) { + *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *)); + } else { + *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *)); + } + } + *modary[*nmods] = toadd; + (*nmods)++; +} + +/* ------------------------------------------------------------ */ +/* + * preop_modify - pre-operation plug-in for modify + */ +static int +preop_modify(Slapi_PBlock *pb) +{ + int result; + char *violated; + LDAPMod **checkmods = NULL; /* holds mods to check */ + int checkmodsCapacity = 0; /* max capacity of checkmods */ + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODIFY begin\n"); +#endif + + result = LDAP_SUCCESS; + + BEGIN + int err; + int argc; + char **argv; + char **attrName; + LDAPMod **mods; + LDAPMod **firstMods; + LDAPMod *mod; + char *target; + char **firstSubtree; + char **subtreeDN; + int subtreeCnt; + int is_replicated_operation; + + /* + * Get the arguments + */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) { result = op_error(13); break; } + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) { result = op_error(14); break; } + + /* + * If this is a replication update, just be a noop. + */ + err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation); + if (err) { result = op_error(16); break; } + if (is_replicated_operation) + { + break; + } + + err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &firstMods); + if (err) { result = op_error(10); break; } + + /* Get the target DN */ + err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET, &target); + if (err) { result = op_error(11); break; } + + /* + * Look for managed trees that include the target + * Arguments before "," are the 7-bit clean attribute names. Arguemnts + * after "," are subtreeDN's. + */ + for ( firstSubtree = argv; strcmp(*firstSubtree, ",") != 0; + firstSubtree++, argc--) {} + firstSubtree++; + argc--; + + for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++ ) + { + int modcount = 0; + int ii = 0; + + /* + * if the attribute is userpassword, check unhashed#user#password + * instead. "userpassword" is encoded; it will always pass the 7bit + * check. + */ + char *attr_name; + if ( strcasecmp(*attrName, "userpassword") == 0 ) + { + attr_name = "unhashed#user#password"; + } else { + attr_name = *attrName; + } + + /* There may be more than one mod that matches e.g. + changetype: modify + delete: uid + uid: balster1950 + - + add: uid + uid: scottg + + So, we need to first find all mods that contain the attribute + which are add or replace ops and are bvalue encoded + */ + /* find out how many mods meet this criteria */ + for(mods=firstMods;*mods;mods++) + { + mod = *mods; + if ((slapi_attr_type_cmp(mod->mod_type, attr_name, 1) == 0) && /* mod contains target attr */ + (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */ + (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */ + (((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || /* mod is add */ + (mod->mod_op & LDAP_MOD_REPLACE))) /* mod is replace */ + { + addMod(&checkmods, &checkmodsCapacity, &modcount, mod); + } + } + if (modcount == 0) { + continue; /* no mods to check, go to next attr */ + } + + /* + * stop checking at first mod that fails the check + */ + for (ii = 0; (result == 0) && (ii < modcount); ++ii) + { + mod = checkmods[ii]; + /* + * For each DN in the managed list, do 7-bit checking if + * the target DN is a subnode in the tree. + */ + for( subtreeDN=firstSubtree, subtreeCnt=argc ;subtreeCnt > 0; + subtreeCnt--,subtreeDN++) + { + /* + * issuffix determines whether the target is under the + * subtree *subtreeDN + */ + if (slapi_dn_issuffix(target, *subtreeDN)) + { +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODIFY subtree=%s\n", *subtreeDN); +#endif + /* + * Check if the value is 7-bit clean + */ + result = bit_check(NULL, mod->mod_bvalues, &violated); + if (result) break; + } + } + } + /* don't have to go on if there is a value not 7-bit clean */ + if (result) break; + } + END + + slapi_ch_free((void **)&checkmods); + if (result) { + issue_error(pb, result, "MODIFY", violated); + } + + return (result==LDAP_SUCCESS)?0:-1; +} + +/* ------------------------------------------------------------ */ +/* + * preop_modrdn - Pre-operation call for modify RDN + * + * Check that the new RDN does not include attributes that + * cause a constraint violation + */ +static int +preop_modrdn(Slapi_PBlock *pb) +{ + int result; + Slapi_Entry *e; + char *violated; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN begin\n"); +#endif + + /* Init */ + result = LDAP_SUCCESS; + e = 0; + + BEGIN + int err; + int argc; + char **argv; + char **attrName; + char *target; + char *superior; + char *rdn; + Slapi_Attr *attr; + char **firstSubtree; + char **subtreeDN; + int subtreeCnt; + int is_replicated_operation; + + /* + * Get the arguments + */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) { result = op_error(30); break; } + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) { result = op_error(31); break; } + + /* + * If this is a replication update, just be a noop. + */ + err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation); + if (err) { result = op_error(16); break; } + if (is_replicated_operation) + { + break; + } + + /* Get the DN of the entry being renamed */ + err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET, &target); + if (err) { result = op_error(22); break; } + + /* Get superior value - unimplemented in 3.0 DS */ + err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &superior); + if (err) { result = op_error(20); break; } + + /* + * No superior means the entry is just renamed at + * its current level in the tree. Use the target DN for + * determining which managed tree this belongs to + */ + if (!superior) superior = target; + + /* Get the new RDN - this has the attribute values */ + err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn); + if (err) { result = op_error(33); break; } + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN newrdn=%s\n", rdn); +#endif + + /* + * Parse the RDN into attributes by creating a "dummy" entry + * and setting the attributes from the RDN. + * + * The new entry must be freed. + */ + e = slapi_entry_alloc(); + if (!e) { result = op_error(32); break; } + + /* NOTE: strdup on the rdn, since it will be freed when + * the entry is freed */ + + slapi_entry_set_dn(e, slapi_ch_strdup(rdn)); + + err = slapi_entry_add_rdn_values(e); + if (err) + { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN bad rdn value=%s\n", rdn); + break; /* Bad DN */ + } + + /* + * arguments before "," are the 7-bit clean attribute names. Arguemnts + * after "," are subtreeDN's. + */ + for ( firstSubtree = argv; strcmp(*firstSubtree, ",") != 0; + firstSubtree++, argc--) {} + firstSubtree++; + argc--; + + /* + * Find out if the node is being moved into one of + * the managed subtrees + */ + for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++ ) + { + /* + * If the attribut type is userpassword, do not replace it by + * unhashed#user#password because unhashed#user#password does not exist + * in this case. + */ + /* + * Find any 7-bit attribute data in the new RDN + */ + err = slapi_entry_attr_find(e, *attrName, &attr); + if (err) continue; /* break;*/ /* no 7-bit attribute */ + + /* + * For each DN in the managed list, do 7-bit checking if + * the target DN is a subnode in the tree. + */ + for( subtreeDN=firstSubtree, subtreeCnt=argc ;subtreeCnt > 0; + subtreeCnt--,subtreeDN++) + { + /* + * issuffix determines whether the target is under the + * subtree *subtreeDN + */ + if (slapi_dn_issuffix(superior, *subtreeDN)) + { +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN subtree=%s\n", *subtreeDN); +#endif + + /* + * Check if the value is 7-bit clean + */ + result = bit_check(attr, NULL, &violated); + if (result) break; + } + } + /* don't have to go on if there is a value not 7-bit clean */ + if (result) break; + } + END + + /* Clean-up */ + if (e) slapi_entry_free(e); + + if (result) { + issue_error(pb, result, "MODRDN", violated); + } + + return (result==LDAP_SUCCESS)?0:-1; +} + +/* ------------------------------------------------------------ */ +/* + * Initialize the plugin + * + */ +int +NS7bitAttr_Init(Slapi_PBlock *pb) +{ + int err = 0; + + BEGIN + int err; + int argc; + char **argv; + + /* Declare plugin version */ + err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01); + if (err) break; + + /* + * Get and normalize arguments + */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) break; + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) break; + + /* + * Arguments before "," are the 7-bit attribute names. Arguments after + * "," are the subtree DN's. + */ + if (argc < 1) { err = -1; break; } + for(;strcmp(*argv, ",") != 0 && argc > 0; argc--, argv++) + {}; + if (argc == 0) { err = -1; break; } + argv++; argc--; + + for(;argc > 0;argc--, argv++) + slapi_dn_normalize_case(*argv); + + /* Provide descriptive information */ + err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void*)&pluginDesc); + if (err) break; + + /* Register functions */ + err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, + (void*)preop_add); + if (err) break; + + err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, + (void*)preop_modify); + if (err) break; + + err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN, + (void*)preop_modrdn); + if (err) break; + + END + + if (err) { + slapi_log_error(SLAPI_LOG_PLUGIN, "NS7bitAttr_Init", + "Error: %d\n", err); + err = -1; + } + else + slapi_log_error(SLAPI_LOG_PLUGIN, "NS7bitAttr_Init", + "plugin loaded\n"); + + return err; +} + diff --git a/ldap/servers/plugins/uiduniq/Makefile b/ldap/servers/plugins/uiduniq/Makefile new file mode 100644 index 00000000..79b95995 --- /dev/null +++ b/ldap/servers/plugins/uiduniq/Makefile @@ -0,0 +1,99 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server "Pass Through Authentication" plugin +# +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libuidunique +LIBDIR = $(LIB_RELDIR) +SHAREDLIB = $(OBJDIR)/lib/shared/utils.o + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libuiduniq.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd -I../shared + +LOCAL_OBJS= uid.o 7bit.o + +SHAREDDIR= ../shared + +OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS)) + +ifeq ($(ARCH), WINNT) +#LIBUIDUNIQUE_DLL_OBJ = $(addprefix $(OBJDEST)/, uid.o 7bit.o) +endif + +LIBUIDUNIQUE= $(addprefix $(LIBDIR)/, $(UID_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +endif + + + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libuiduniq.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +EXTRA_LIBS += $(SHAREDLIB) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBUIDUNIQUE) + +$(LIBUIDUNIQUE): $(OBJS) $(LIBUIDUNIQUE_DLL_OBJ) $(DEF_FILE) +# $(LINK_DLL) $(LIBUIDUNIQUE_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) + $(LINK_DLL) $(PLATFORMLIBS) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBUIDUNIQUE_DLL_OBJ) +endif + $(RM) $(LIBUIDUNIQUE) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(OBJS): $(LDAP_SRC)/servers/slapd/slapi-plugin.h \ + ../shared/plugin-utils.h + diff --git a/ldap/servers/plugins/uiduniq/UID-Notes b/ldap/servers/plugins/uiduniq/UID-Notes new file mode 100644 index 00000000..3d3617ff --- /dev/null +++ b/ldap/servers/plugins/uiduniq/UID-Notes @@ -0,0 +1,93 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +Unique UID Checking Plugin +-------------------------- + +Terry Hayes, April 16, 1998 + + +GOALS + +The Unique UID Checking Plugin supports the management of user entries in the +directory by enforcing the constraints on the value of an attribute within a +portion of the directory. This provides a central point for enforcing this +constraint, which allows changes from any source to be checked (DSGW, Kingpin, +LDAP utilities, or user application). + +CONFIGURATION + +The software operates as a preoperation plugin to the directory server. An +entry must be added to the slapd.conf file for the server that declares the +plugin and provides arguments required for its operation. + +The plugin is declared as follows (line split for clarity): + + plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so + uidunique_init <attribute_name> <subtree_dn> ... + +The first 5 values are the standard plugin declaration. The uidunique_init +function registers preoperation callbacks for the add, modify and modRDN +directory operations. + +The next argument ("attribute_name") specifies the name of the entry attribute +to check for uniqueness. This attribute must be unique within each of the +subtrees listed in the remainder of the arguments. + +For example: + + plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so + uidunique_init uid o=mcom.com + +This line specifies "uid" as the unique attribute, and lists a single subtree +to be checked. This line is typical of an initial installation (see below). + +A more complex case: + + plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so + uidunique_init uid o=Coke o=Pepsi + plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so + uidunique_init mail "o=Dr. Pepper" + +This configuration specifies a total of three subtrees to check. Two use the +(standard) "uid" attribute as a unique value. The other specifies "mail" +as the unique attribute. + +INSTALLATION + +The standard installation of the directory server will configure this plugin +to check the "uid" attribute on the default suffix. + +OPERATION + +The plugin responds to the following LDAP operations: + + + add + + modify + + modRDN + +For all operations, the plugin forces the LDAP operation to return +CONSTRAINT_VIOLATION if the operation would result in two entries with +the same unique attribute value. + +For an "add" operation that includes the unique attribute, the plugin checks +that no other entry has the same value. + +For a "modify" operation, the operation will fail if the new value of the +attribute exists in any entry OTHER than the target of the modify. If the +value already exists, but is in the node being changed, the operation +succeeds. For example, if a modify operation replaces a 'uid' attribute +with the same set of values, the plugin will find the "new" values already +exist. However since it is in the entry being modified, the operation is +allowed to complete. + +For modRDN, the same checking as for "modify" is performed. + +ModRDN is coded to handle reparenting, but since the LDAP protocol to support +this operation is not present, it cannot be exercised and has not been +tested. + diff --git a/ldap/servers/plugins/uiduniq/libuiduniq.def b/ldap/servers/plugins/uiduniq/libuiduniq.def new file mode 100644 index 00000000..52bbfcca --- /dev/null +++ b/ldap/servers/plugins/uiduniq/libuiduniq.def @@ -0,0 +1,15 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7 Unique Attribute Checking Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + uidunique_init @1 + NSUniqueAttr_Init @2 + NS7bitAttr_Init @3 diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c new file mode 100644 index 00000000..f32e63ac --- /dev/null +++ b/ldap/servers/plugins/uiduniq/uid.c @@ -0,0 +1,1073 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * uid.c + * + * Implements a directory server pre-operation plugin to test + * attributes for uniqueness within a defined subtree in the + * directory. + * + * Called uid.c since the original purpose of the plugin was to + * check the uid attribute in user entries. + */ +#include <slapi-plugin.h> +#include <portable.h> +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include <string.h> +#include "dirver.h" +#include "plugin-utils.h" + +#if defined( LDAP_DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#define UNTAGGED_PARAMETER 12 + +/* Quoting routine - this should be in a library somewhere (slapi?) */ +int ldap_quote_filter_value( + char *value, int len, + char *out, int maxLen, + int *outLen); + + +static int search_one_berval(const char *baseDN, const char *attrName, + const struct berval *value, const char *target); + +/* + * ISSUES: + * How should this plugin handle ACL issues? It seems wrong to reject + * adds and modifies because there is already a conflicting UID, when + * the request would have failed because of an ACL check anyway. + * + * This code currently defines a maximum filter string size of 512. Is + * this large enough? + * + * This code currently does not quote the value portion of the filter as + * it is created. This is a bug. + */ + +/* */ +#define BEGIN do { +#define END } while(0); + +/* + * Slapi plugin descriptor + */ +static char *plugin_name = "NSUniqueAttr"; +static Slapi_PluginDesc +pluginDesc = { + "NSUniqueAttr", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "Enforce unique attribute values" +}; +static void* plugin_identity = NULL; + + +/* + * More information about constraint failure + */ +static char *moreInfo = + "Another entry with the same attribute value already exists"; + +static void +freePblock( Slapi_PBlock *spb ) { + if ( spb ) + { + slapi_free_search_results_internal( spb ); + slapi_pblock_destroy( spb ); + } +} + +/* ------------------------------------------------------------ */ +/* + * op_error - Record (and report) an operational error. + * name changed to uid_op_error so as not to conflict with the external function + * of the same name thereby preventing compiler warnings. + */ +static int +uid_op_error(int internal_error) +{ + slapi_log_error( + SLAPI_LOG_PLUGIN, + plugin_name, + "Internal error: %d\n", + internal_error); + + return LDAP_OPERATIONS_ERROR; +} + +/* ------------------------------------------------------------ */ +/* + * Create an LDAP search filter from the attribute + * name and value supplied. + */ + +static char * +create_filter(const char *attribute, const struct berval *value) +{ + char *filter; + char *fp; + char *max; + int attrLen; + int valueLen; + int filterLen; + + /* Compute the length of the required buffer */ + attrLen = strlen(attribute); + + if (ldap_quote_filter_value(value->bv_val, + value->bv_len, 0, 0, &valueLen)) + return 0; + + filterLen = attrLen + 1 + valueLen + 1; + + /* Allocate the buffer */ + filter = slapi_ch_malloc(filterLen); + fp = filter; + max = &filter[filterLen]; + + /* Place attribute name in filter */ + strcpy(fp, attribute); + fp += attrLen; + + /* Place comparison operator */ + *fp++ = '='; + + /* Place value in filter */ + if (ldap_quote_filter_value(value->bv_val, value->bv_len, + fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; } + fp += valueLen; + + /* Terminate */ + *fp = 0; + + return filter; +} + +/* ------------------------------------------------------------ */ +/* + * search - search a subtree for entries with a named attribute matching + * the list of values. An entry matching the 'target' DN is + * not considered in the search. + * + * If 'attr' is NULL, the values are taken from 'values'. + * If 'attr' is non-NULL, the values are taken from 'attr'. + * + * Return: + * LDAP_SUCCESS - no matches, or the attribute matches the + * target dn. + * LDAP_CONSTRAINT_VIOLATION - an entry was found that already + * contains the attribute value. + * LDAP_OPERATIONS_ERROR - a server failure. + */ +static int +search(const char *baseDN, const char *attrName, Slapi_Attr *attr, + struct berval **values, const char *target) +{ + int result; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "SEARCH baseDN=%s attr=%s target=%s\n", baseDN, attrName, + target?target:"None"); +#endif + + result = LDAP_SUCCESS; + + /* If no values, can't possibly be a conflict */ + if ( (Slapi_Attr *)NULL == attr && (struct berval **)NULL == values ) + return result; + + /* + * Perform the search for each value provided + * + * Another possibility would be to search for all the values at once. + * However, this is more complex (for filter creation) and unique + * attributes values are probably only changed one at a time anyway. + */ + if ( (Slapi_Attr *)NULL != attr ) + { + Slapi_Value *v = NULL; + int vhint = -1; + + for ( vhint = slapi_attr_first_value( attr, &v ); + vhint != -1 && LDAP_SUCCESS == result; + vhint = slapi_attr_next_value( attr, vhint, &v )) + { + result = search_one_berval(baseDN,attrName, + slapi_value_get_berval(v),target); + } + } + else + { + for (;*values != NULL && LDAP_SUCCESS == result; values++) + { + result = search_one_berval(baseDN,attrName,*values,target); + } + } + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "SEARCH result = %d\n", result); +#endif + + return( result ); +} + + +static int +search_one_berval(const char *baseDN, const char *attrName, + const struct berval *value, const char *target) +{ + int result; + char *filter; + Slapi_PBlock *spb; + + result = LDAP_SUCCESS; + + /* If no value, can't possibly be a conflict */ + if ( (struct berval *)NULL == value ) + return result; + + filter = 0; + spb = 0; + + BEGIN + int err; + int sres; + Slapi_Entry **entries; + static char *attrs[] = { "1.1", 0 }; + + /* Create the filter - this needs to be freed */ + filter = create_filter(attrName, value); + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "SEARCH filter=%s\n", filter); +#endif + + /* Perform the search using the new internal API */ + spb = slapi_pblock_new(); + if (!spb) { result = uid_op_error(2); break; } + + slapi_search_internal_set_pb(spb, baseDN, LDAP_SCOPE_SUBTREE, + filter, attrs, 0 /* attrs only */, NULL, NULL, plugin_identity, 0 /* actions */); + slapi_search_internal_pb(spb); + + err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_RESULT, &sres); + if (err) { result = uid_op_error(3); break; } + + /* Allow search to report that there is nothing in the subtree */ + if (sres == LDAP_NO_SUCH_OBJECT) break; + + /* Other errors are bad */ + if (sres) { result = uid_op_error(3); break; } + + err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &entries); + if (err) { result = uid_op_error(4); break; } + + /* + * Look at entries returned. Any entry found must be the + * target entry or the constraint fails. + */ + for(;*entries;entries++) + { + char *dn = slapi_entry_get_dn(*entries); + + /* + * DNs are returned in the original value used to insert + * the entry. This must be "normalized" for comparison. + * + * This normalization is done "in-place" (modifying the value + * in the entry). This is OK, since this is the only user + * of this copy of the entry. + */ + slapi_dn_normalize_case(dn); + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "SEARCH entry dn=%s\n", dn); +#endif + + /* + * It is a Constraint Violation if any entry is found, unless + * the entry is the target entry (if any). + */ + if (!target || strcmp(dn, target) != 0) + { + result = LDAP_CONSTRAINT_VIOLATION; + break; + } + } + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "SEARCH complete result=%d\n", result); +#endif + END + + /* Clean-up */ + if (spb) { + slapi_free_search_results_internal(spb); + slapi_pblock_destroy(spb); + } + + slapi_ch_free((void**)&filter); + + return result; +} + +/* ------------------------------------------------------------ */ +/* + * searchAllSubtrees - search all subtrees in argv for entries + * with a named attribute matching the list of values, by + * calling search for each one. + * + * If 'attr' is NULL, the values are taken from 'values'. + * If 'attr' is non-NULL, the values are taken from 'attr'. + * + * Return: + * LDAP_SUCCESS - no matches, or the attribute matches the + * target dn. + * LDAP_CONSTRAINT_VIOLATION - an entry was found that already + * contains the attribute value. + * LDAP_OPERATIONS_ERROR - a server failure. + */ +static int +searchAllSubtrees(int argc, char *argv[], const char *attrName, + Slapi_Attr *attr, struct berval **values, const char *dn) +{ + int result = LDAP_SUCCESS; + + /* + * For each DN in the managed list, do uniqueness checking if + * the target DN is a subnode in the tree. + */ + for(;argc > 0;argc--,argv++) + { + result = search(*argv, attrName, attr, values, dn); + if (result) break; + } + return result; +} + +/* ------------------------------------------------------------ */ +/* + * getArguments - parse invocation parameters + * Return: + * 0 - success + * >0 - error parsing parameters + */ +static int +getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass, + char **requiredObjectClass) +{ + int argc; + char **argv; + + /* + * Get the arguments + */ + if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc)) + { + return uid_op_error(10); + } + + if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv)) + { + return uid_op_error(11); + } + + /* + * Required arguments: attribute and markerObjectClass + * Optional argument: requiredObjectClass + */ + for(;argc > 0;argc--,argv++) + { + char *param = *argv; + char *delimiter = strchr(param, '='); + if (NULL == delimiter) + { + /* Old style untagged parameter */ + *attrName = *argv; + return UNTAGGED_PARAMETER; + } + if (strncasecmp(param, "attribute", delimiter-param) == 0) + { + /* It's OK to set a pointer here, because ultimately it points + * inside the argv array of the pblock, which will be staying + * arround. + */ + *attrName = delimiter+1; + } else if (strncasecmp(param, "markerobjectclass", delimiter-param) == 0) + { + *markerObjectClass = delimiter+1; + } else if (strncasecmp(param, "requiredobjectclass", delimiter-param) == 0) + { + *requiredObjectClass = delimiter+1; + } + } + if (!*attrName || !*markerObjectClass) + { + return uid_op_error(13); + } + + return 0; +} + +/* ------------------------------------------------------------ */ +/* + * findSubtreeAndSearch - walk up the tree to find an entry with + * the marker object class; if found, call search from there and + * return the result it returns + * + * If 'attr' is NULL, the values are taken from 'values'. + * If 'attr' is non-NULL, the values are taken from 'attr'. + * + * Return: + * LDAP_SUCCESS - no matches, or the attribute matches the + * target dn. + * LDAP_CONSTRAINT_VIOLATION - an entry was found that already + * contains the attribute value. + * LDAP_OPERATIONS_ERROR - a server failure. + */ +static int +findSubtreeAndSearch(char *parentDN, const char *attrName, Slapi_Attr *attr, + struct berval **values, const char *target, const char *markerObjectClass) +{ + int result = LDAP_SUCCESS; + Slapi_PBlock *spb = NULL; + + while (NULL != (parentDN = slapi_dn_parent(parentDN))) + { + if (spb = dnHasObjectClass(parentDN, markerObjectClass)) + { + freePblock(spb); + /* + * Do the search. There is no entry that is allowed + * to have the attribute already. + */ + result = search(parentDN, attrName, attr, values, target); + break; + } + } + return result; +} + + +/* ------------------------------------------------------------ */ +/* + * preop_add - pre-operation plug-in for add + */ +static int +preop_add(Slapi_PBlock *pb) +{ + int result; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n"); +#endif + + result = LDAP_SUCCESS; + + /* + * Do constraint check on the added entry. Set result. + */ + + BEGIN + int err; + char *attrName = NULL; + char *markerObjectClass = NULL; + char *requiredObjectClass = NULL; + char *dn; + int isupdatedn; + Slapi_Entry *e; + Slapi_Attr *attr; + int argc; + char **argv = NULL; + + /* + * If this is a replication update, just be a noop. + */ + err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn); + if (err) { result = uid_op_error(50); break; } + if (isupdatedn) + { + break; + } + + /* + * Get the arguments + */ + result = getArguments(pb, &attrName, &markerObjectClass, + &requiredObjectClass); + if (UNTAGGED_PARAMETER == result) + { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "ADD parameter untagged: %s\n", attrName); + result = LDAP_SUCCESS; + /* Statically defined subtrees to monitor */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) { result = uid_op_error(53); break; } + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) { result = uid_op_error(54); break; } + argc--; argv++; /* First argument was attribute name */ + } else if (0 != result) + { + break; + } + + /* + * Get the target DN for this add operation + */ + err = slapi_pblock_get(pb, SLAPI_ADD_TARGET, &dn); + if (err) { result = uid_op_error(51); break; } + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD target=%s\n", dn); +#endif + + /* + * Get the entry data for this add. Check whether it + * contains a value for the unique attribute + */ + err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + if (err) { result = uid_op_error(52); break; } + + err = slapi_entry_attr_find(e, attrName, &attr); + if (err) break; /* no unique attribute */ + + /* + * Check if it contains the required object class + */ + if (NULL != requiredObjectClass) + { + if (!entryHasObjectClass(pb, e, requiredObjectClass)) + { + /* No, so we don't have to do anything */ + break; + } + } + + /* + * Passed all the requirements - this is an operation we + * need to enforce uniqueness on. Now find all parent entries + * with the marker object class, and do a search for each one. + */ + if (NULL != markerObjectClass) + { + /* Subtree defined by location of marker object class */ + result = findSubtreeAndSearch(dn, attrName, attr, NULL, + dn, markerObjectClass); + } else + { + /* Subtrees listed on invocation line */ + result = searchAllSubtrees(argc, argv, attrName, attr, NULL, dn); + } + END + + if (result) + { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "ADD result %d\n", result); + + /* Send failure to the client */ + slapi_send_ldap_result(pb, result, 0, moreInfo, 0, 0); + } + + return (result==LDAP_SUCCESS)?0:-1; +} + +static void +addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd) +{ + if (*nmods == *capacity) { + *capacity += 4; + if (*modary) { + *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *)); + } else { + *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *)); + } + } + *modary[*nmods] = toadd; + (*nmods)++; +} + +/* ------------------------------------------------------------ */ +/* + * preop_modify - pre-operation plug-in for modify + */ +static int +preop_modify(Slapi_PBlock *pb) +{ + + int result = LDAP_SUCCESS; + Slapi_PBlock *spb = NULL; + LDAPMod **checkmods = NULL; + int checkmodsCapacity = 0; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODIFY begin\n"); +#endif + + BEGIN + int err; + char *attrName; + char *markerObjectClass=NULL; + char *requiredObjectClass=NULL; + LDAPMod **mods; + int modcount = 0; + int ii; + LDAPMod *mod; + char *dn; + int isupdatedn; + int argc; + char **argv = NULL; + + /* + * If this is a replication update, just be a noop. + */ + err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn); + if (err) { result = uid_op_error(60); break; } + if (isupdatedn) + { + break; + } + + /* + * Get the arguments + */ + result = getArguments(pb, &attrName, &markerObjectClass, + &requiredObjectClass); + if (UNTAGGED_PARAMETER == result) + { + result = LDAP_SUCCESS; + /* Statically defined subtrees to monitor */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) { result = uid_op_error(53); break; } + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) { result = uid_op_error(54); break; } + argc--; /* First argument was attribute name */ + argv++; + } else if (0 != result) + { + break; + } + + err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + if (err) { result = uid_op_error(61); break; } + + /* There may be more than one mod that matches e.g. + changetype: modify + delete: uid + uid: balster1950 + - + add: uid + uid: scottg + + So, we need to first find all mods that contain the attribute + which are add or replace ops and are bvalue encoded + */ + /* find out how many mods meet this criteria */ + for(;*mods;mods++) + { + mod = *mods; + if ((slapi_attr_type_cmp(mod->mod_type, attrName, 1) == 0) && /* mod contains target attr */ + (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */ + (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */ + (((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || /* mod is add */ + (mod->mod_op & LDAP_MOD_REPLACE))) /* mod is replace */ + { + addMod(&checkmods, &checkmodsCapacity, &modcount, mod); + } + } + if (modcount == 0) { + break; /* no mods to check, we are done */ + } + + /* Get the target DN */ + err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET, &dn); + if (err) { result = uid_op_error(11); break; } + + if (requiredObjectClass && + !(spb = dnHasObjectClass(dn, requiredObjectClass))) { break; } + + /* + * Passed all the requirements - this is an operation we + * need to enforce uniqueness on. Now find all parent entries + * with the marker object class, and do a search for each one. + */ + /* + * stop checking at first mod that fails the check + */ + for (ii = 0; (result == 0) && (ii < modcount); ++ii) + { + mod = checkmods[ii]; + if (NULL != markerObjectClass) + { + /* Subtree defined by location of marker object class */ + result = findSubtreeAndSearch(dn, attrName, NULL, + mod->mod_bvalues, dn, + markerObjectClass); + } else + { + /* Subtrees listed on invocation line */ + result = searchAllSubtrees(argc, argv, attrName, NULL, + mod->mod_bvalues, dn); + } + } + END + + slapi_ch_free((void **)&checkmods); + freePblock(spb); + if (result) + { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODIFY result %d\n", result); + + slapi_send_ldap_result(pb, result, 0, moreInfo, 0, 0); + } + + return (result==LDAP_SUCCESS)?0:-1; + +} + +/* ------------------------------------------------------------ */ +/* + * preop_modrdn - Pre-operation call for modify RDN + * + * Check that the new RDN does not include attributes that + * cause a constraint violation + */ +static int +preop_modrdn(Slapi_PBlock *pb) +{ + int result; + Slapi_Entry *e; + +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN begin\n"); +#endif + + /* Init */ + result = LDAP_SUCCESS; + e = 0; + + BEGIN + int err; + char *attrName; + char *markerObjectClass=NULL; + char *requiredObjectClass=NULL; + char *dn; + char *superior; + char *rdn; + int isupdatedn; + Slapi_Attr *attr; + int argc; + char **argv = NULL; + + /* + * If this is a replication update, just be a noop. + */ + err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn); + if (err) { result = uid_op_error(30); break; } + if (isupdatedn) + { + break; + } + + /* + * Get the arguments + */ + result = getArguments(pb, &attrName, &markerObjectClass, + &requiredObjectClass); + if (UNTAGGED_PARAMETER == result) + { + result = LDAP_SUCCESS; + /* Statically defined subtrees to monitor */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) { result = uid_op_error(53); break; } + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) { result = uid_op_error(54); break; } + argc--; /* First argument was attribute name */ + argv++; + } else if (0 != result) + { + break; + } + + /* Get the DN of the entry being renamed */ + err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET, &dn); + if (err) { result = uid_op_error(31); break; } + + /* Get superior value - unimplemented in 3.0/4.0/5.0 DS */ + err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &superior); + if (err) { result = uid_op_error(32); break; } + + /* + * No superior means the entry is just renamed at + * its current level in the tree. Use the target DN for + * determining which managed tree this belongs to + */ + if (!superior) superior = dn; + + /* Get the new RDN - this has the attribute values */ + err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn); + if (err) { result = uid_op_error(33); break; } +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN newrdn=%s\n", rdn); +#endif + + /* + * Parse the RDN into attributes by creating a "dummy" entry + * and setting the attributes from the RDN. + * + * The new entry must be freed. + */ + e = slapi_entry_alloc(); + if (!e) { result = uid_op_error(34); break; } + + /* NOTE: strdup on the rdn, since it will be freed when + * the entry is freed */ + + slapi_entry_set_dn(e, slapi_ch_strdup(rdn)); + + err = slapi_entry_add_rdn_values(e); + if (err) + { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN bad rdn value=%s\n", rdn); + break; /* Bad DN */ + } + + /* + * Find any unique attribute data in the new RDN + */ + err = slapi_entry_attr_find(e, attrName, &attr); + if (err) break; /* no UID attribute */ + + /* + * Passed all the requirements - this is an operation we + * need to enforce uniqueness on. Now find all parent entries + * with the marker object class, and do a search for each one. + */ + if (NULL != markerObjectClass) + { + /* Subtree defined by location of marker object class */ + result = findSubtreeAndSearch(dn, attrName, attr, NULL, dn, + markerObjectClass); + } else + { + /* Subtrees listed on invocation line */ + result = searchAllSubtrees(argc, argv, attrName, attr, NULL, dn); + } + END + /* Clean-up */ + if (e) slapi_entry_free(e); + + if (result) + { + slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, + "MODRDN result %d\n", result); + + slapi_send_ldap_result(pb, result, 0, moreInfo, 0, 0); + } + + return (result==LDAP_SUCCESS)?0:-1; + +} + +/* ------------------------------------------------------------ */ +/* + * Initialize the plugin + * + * uidunique_init (the old name) is deprecated + */ +int +NSUniqueAttr_Init(Slapi_PBlock *pb) +{ + int err = 0; + + BEGIN + int err; + int argc; + char **argv; + + /* Declare plugin version */ + err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01); + if (err) break; + + /* + * Get plugin identity and store it for later use + * Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + /* PR_ASSERT (plugin_identity); */ + + /* + * Get and normalize arguments + */ + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc); + if (err) break; + + err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv); + if (err) break; + + /* First argument is the unique attribute name */ + if (argc < 1) { err = -1; break; } + argv++; argc--; + + for(;argc > 0;argc--, argv++) + slapi_dn_normalize_case(*argv); + + /* Provide descriptive information */ + err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void*)&pluginDesc); + if (err) break; + + /* Register functions */ + err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, + (void*)preop_add); + if (err) break; + + err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, + (void*)preop_modify); + if (err) break; + + err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN, + (void*)preop_modrdn); + if (err) break; + + END + + if (err) { + slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init", + "Error: %d\n", err); + err = -1; + } + else + slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init", + "plugin loaded\n"); + + return err; +} + +int +uidunique_init(Slapi_PBlock *pb) +{ + return NSUniqueAttr_Init(pb); +} + + +/* ------------------------------------------------------------ */ +/* + * ldap_quote_filter_value + * + * Quote the filter value according to RFC 2254 (Dec 1997) + * + * value - a UTF8 string containing the value. It may contain + * any of the chars needing quotes ( '(' ')' '*' '/' and NUL ). + * len - the length of the UTF8 value + * out - a buffer to recieve the converted value. May be NULL, in + * which case, only the length of the output is computed (and placed in + * outLen). + * maxLen - the size of the output buffer. It is an error if this length + * is exceeded. Ignored if out is NULL. + * outLen - recieves the size of the output. If an error occurs, this + * result is not available. + * + * Returns + * 0 - success + * -1 - failure (usually a buffer overflow) + */ +int /* Error value */ +ldap_quote_filter_value( + char *value, int len, + char *out, int maxLen, + int *outLen) +{ + int err; + char *eValue; + int resLen; +#ifdef SLAPI_SUPPORTS_V3_ESCAPING + static char hexchars[16] = "0123456789abcdef"; +#endif + + err = 0; + eValue = &value[len]; + resLen = 0; + + /* + * Convert each character in the input string + */ + while(value < eValue) + { + switch(*value) + { + case '(': + case ')': + case '*': + case '\\': +#ifdef SLAPI_SUPPORTS_V3_ESCAPING + case 0: +#endif + /* Handle characters needing special escape sequences */ + + /* Compute size of output */ +#ifdef SLAPI_SUPPORTS_V3_ESCAPING + resLen += 3; +#else + resLen += 2; +#endif + + /* Generate output if requested */ + if (out) + { + /* Check for overflow */ + if (resLen > maxLen) { err = -1; break; } + + *out++ = '\\'; +#ifdef SLAPI_SUPPORTS_V3_ESCAPING + *out++ = hexchars[(*value >> 4) & 0xF]; + *out++ = hexchars[*value & 0xF]; +#else + *out++ = *value; +#endif + } + + break; + + default: + /* Compute size of output */ + resLen += 1; + + /* Generate output if requested */ + if (out) + { + if (resLen > maxLen) { err = -1; break; } + *out++ = *value; + } + + break; + } + + if (err) break; + + value++; + } + + if (!err) *outLen = resLen; + + return err; +} diff --git a/ldap/servers/plugins/vattrsp_template/Makefile b/ldap/servers/plugins/vattrsp_template/Makefile new file mode 100644 index 00000000..0eb9e072 --- /dev/null +++ b/ldap/servers/plugins/vattrsp_template/Makefile @@ -0,0 +1,79 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libvattrsp +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./vattrsp.def +endif + +VATTRSP_OBJS = vattrsp.o +OBJS = $(addprefix $(OBJDEST)/, $(VATTRSP_OBJS)) + +VATTRSP_DLL = vattrsp-plugin + +INCLUDES += -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +VATTRSP_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +VATTRSP= $(addprefix $(LIBDIR)/, $(VATTRSP_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(VATTRSP) + +ifeq ($(ARCH), WINNT) +$(VATTRSP): $(OBJS) $(VATTRSP_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(VATTRSP_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(VATTRSP): $(OBJS) $(VATTRSP_DLL_OBJ) + $(LINK_DLL) $(VATTRSP_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(VATTRSP_DLL_OBJ) +endif + $(RM) $(VATTRSP) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) diff --git a/ldap/servers/plugins/vattrsp_template/dllmain.c b/ldap/servers/plugins/vattrsp_template/dllmain.c new file mode 100644 index 00000000..fabf8677 --- /dev/null +++ b/ldap/servers/plugins/vattrsp_template/dllmain.c @@ -0,0 +1,96 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/vattrsp_template/vattrsp.c b/ldap/servers/plugins/vattrsp_template/vattrsp.c new file mode 100644 index 00000000..52534b26 --- /dev/null +++ b/ldap/servers/plugins/vattrsp_template/vattrsp.c @@ -0,0 +1,397 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include <stdio.h> +#include <string.h> +#include "portable.h" +#include "nspr.h" +#include "slapi-plugin.h" +#include "slapi-private.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" +#include "vattr_spi.h" + +/* the global plugin handle */ +static volatile vattr_sp_handle *vattr_handle = NULL; + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + + +#define VATTRSP_PLUGIN_SUBSYSTEM "vattrsp-template-plugin" /* used for logging */ + +/* function prototypes */ +int vattrsp_init( Slapi_PBlock *pb ); +static int vattrsp_start( Slapi_PBlock *pb ); +static int vattrsp_close( Slapi_PBlock *pb ); +static int vattrsp_vattr_get( + vattr_sp_handle *handle, + vattr_context *c, + Slapi_Entry *e, + char *type, + Slapi_ValueSet** results, + int *type_name_disposition, + char** actual_type_name, + int flags, + int *free_flags, + void *hint + ); +static int vattrsp_vattr_compare( + vattr_sp_handle *handle, + vattr_context *c, + Slapi_Entry *e, + char *type, + Slapi_Value *test_this, + int* result, + int flags, + void *hint + ); +static int vattrsp_vattr_types( + vattr_sp_handle *handle, + Slapi_Entry *e, + vattr_type_list_context *type_context, + int flags + ); + + +static Slapi_PluginDesc pdesc = { "vattrsp", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "class of service plugin" }; + +static void * vattrsp_plugin_identity = NULL; + + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/* + * Plugin identity mgmt + * -------------------- + * Used for internal search api's + */ + +void vattrsp_set_plugin_identity(void * identity) +{ + vattrsp_plugin_identity=identity; +} + +void * vattrsp_get_plugin_identity() +{ + return vattrsp_plugin_identity; +} + +/* + * vattrsp_init + * -------- + * adds our callbacks to the list + * this is called even if the plugin is disabled + * so do not do anything here which enables the + * plugin functionality - also no other plugin has been started yet + * so you cant use the functionality of other plugins here + * + * When does this get called? + * At server start up. This is the first function in + * the plugin to get called, and no guarantees are made + * about whether the init() function of other plugins + * have been called. It is really only safe to register + * the standard SLAPI_PLUGIN_* interfaces here. + * + * returns 0 on success +*/ +int vattrsp_init( Slapi_PBlock *pb ) +{ + int ret = 0; + void * plugin_identity=NULL; + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_init\n"); + + /* + * Store the plugin identity for later use. + * Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + PR_ASSERT (plugin_identity); + vattrsp_set_plugin_identity(plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) vattrsp_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) vattrsp_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, VATTRSP_PLUGIN_SUBSYSTEM, + "vattrsp_init: failed to register plugin\n" ); + ret = -1; + } + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_init\n"); + return ret; +} + + +/* + * vattrsp_start + * --------- + * This is called after vattrsp_init, this is where plugin init starts. + * Dependencies on other plugins have been satisfied so + * feel free to use them e.g. statechange plugin to keep + * an eye on configuration changes + * + * pb should contain SLAPI_TARGET_DN, which is the dn + * of the entry representing this plugin, usually in + * cn=plugins, cn=config. Use this to get configuration + * specific to your plugin from the entry + * + * When does this get called? + * At server start up, after the vattrsp_init() function is + * called and when the start function of all plugins this one + * depends on have been called. It is safe to rely + * on dependencies from now on e.g. perform search operations + * + * returns 0 on success +*/ +int vattrsp_start( Slapi_PBlock *pb ) +{ + int ret = 0; + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_start\n"); + + /* register this vattr service provider with vattr subsystem */ + if (slapi_vattrspi_register((vattr_sp_handle **)&vattr_handle, + vattrsp_vattr_get, + vattrsp_vattr_compare, + vattrsp_vattr_types) != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, VATTRSP_PLUGIN_SUBSYSTEM, + "vattrsp_start: cannot register as service provider\n" ); + ret = -1; + goto out; + } + + /* register a vattr */ + /* TODO: change dummyAttr to your attribute type, + * or write some configuration code which discovers + * the attributes to register + */ + slapi_vattrspi_regattr((vattr_sp_handle *)vattr_handle, "dummyAttr", NULL, NULL); + +out: + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_start\n"); + return ret; +} + +/* + * vattrsp_close + * --------- + * closes down the plugin + * + * When does this get called? + * On server shutdown + * + * returns 0 on success +*/ +int vattrsp_close( Slapi_PBlock *pb ) +{ + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_close\n"); + + /* clean up stuff here */ + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_close\n"); + + return 0; +} + + +/* + * vattrsp_vattr_get + * ----------------- + * vattr subsystem is requesting the value(s) for "type" + * + * When does this get called? + * Whenever a client requests the attribute "type" + * this may be indirectly by a request for all + * attributes + * + * returns 0 on success + */ +int vattrsp_vattr_get( + vattr_sp_handle *handle, /* pass through to subsequent vattr calls */ + vattr_context *c, /* opaque context, pass through to subsequent vattr calls */ + Slapi_Entry *e, /* the entry that the values are for */ + char *type, /* the type that the values are requested for */ + Slapi_ValueSet** results, /* put the values here */ + int *type_name_disposition, /* whether the type is a sub-type etc. */ + char** actual_type_name, /* maybe you call this another type */ + int flags, /* see slapi-plugin.h to support these flags */ + int *free_flags, /* let vattr subsystem know if you supplied value copies it must free */ + void *hint /* opaque hint, pass through to subsequent vattr calls */ + ) +{ + int ret = SLAPI_VIRTUALATTRS_NOT_FOUND; + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_vattr_get\n"); + + /* usual to schema check an attribute + * there may be sanity checks which can + * be done prior to this "relatively" + * slow function to ensure least work done + * to fail + */ + if(!slapi_vattr_schema_check_type(e, type)) + return ret; + + /* TODO: do your thing, resolve the attribute */ + /* some vattr sps may look after more than one + * attribute, this one does not, so no need to + * check against "type", we know its our "dummyAttr" + */ + { + /* TODO: replace this with your resolver */ + Slapi_Value *val = slapi_value_new_string("dummyValue"); + + *results = slapi_valueset_new(); + slapi_valueset_add_value(*results, val); + + ret = 0; + } + + if(ret == 0) + { + /* TODO: set *free_flags + * if allocated memory for values + * use: SLAPI_VIRTUALATTRS_RETURNED_COPIES + * + * otherwise, if you can guarantee that + * this value will live beyond this operation + * use: SLAPI_VIRTUALATTRS_RETURNED_POINTERS + */ + *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; + + /* say the type is the same as the one requested + * could be your vattr needs to switch types, say to + * make one type look like another or something, but + * that is unusual + */ + *actual_type_name = slapi_ch_strdup(type); + + /* TODO: set *type_name_disposition + * if the type matched exactly or it + * is an alias for the type then + * use: SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS + * + * otherwise, the actual_type_name is a subtype + * of type + * use: SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE + */ + *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; + } + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_cache_vattr_get\n"); + return ret; +} + + + +/* + * vattrsp_vattr_compare + * --------------------- + * vattr subsystem is requesting that the values are compared + * + * When does this get called? + * When an LDAP compare operation against this + * type is requested by the client + * + * returns 0 on success + */ +int vattrsp_vattr_compare( + vattr_sp_handle *handle, /* pass through to subsequent vattr calls */ + vattr_context *c, /* opaque context, pass through to subsequent vattr calls */ + Slapi_Entry *e, /* the entry that the values are for */ + char *type, /* the type that the values belong to */ + Slapi_Value *test_this, /* the value to compare against */ + int* result, /* 1 for matched, 0 for not matched */ + int flags, /* see slapi-plugin.h to support these flags */ + void *hint /* opaque hint, pass through to subsequent vattr calls */ + ) +{ + int ret = SLAPI_VIRTUALATTRS_NOT_FOUND; /* assume failure */ + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_vattr_compare\n"); + + /* TODO: do your thing, compare the attribute */ + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_vattr_compare\n"); + return ret; +} + + + +/* + * vattrsp_vattr_types + * ------------------- + * vattr subsystem is requesting the types that you + * promise to supply values for if it calls + * vattrsp_vattr_get() with each type. + * Guesses are not good enough, the wrong answer + * effects what happens when all attributes + * are requested in the LDAP search operation. + * If you do not own up to an attribute, + * vattrsp_vattr_get() will never get called for it. + * If you say you will supply an attribute but + * vattrsp_vattr_get() does not supply a value + * then the attribute is returned, but *with* *no* + * *values* + * + * When does this get called? + * Only when all attributes are requested by the client + * and just prior to multiple calls to vattrsp_vattr_get() + * + * returns 0 on success + */ +int vattrsp_vattr_types( + vattr_sp_handle *handle, /* pass through to subsequent vattr calls */ + Slapi_Entry *e, /* the entry that the types should have values for */ + vattr_type_list_context *type_context, /* where we put the types */ + int flags /* see slapi-plugin.h to support these flags */ + ) +{ + int ret = 0; /* assume success */ + char *attr = "dummyAttr"; /* an attribute type that we will deliver */ + int props = 0; /* properties of the attribute, make this SLAPI_ATTR_FLAG_OPATTR for operational attributes */ + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_vattr_types\n"); + + /* TODO: for each type you will supply... */ + if(ret) + { + /* ...do this */ + + /* entry contains this attr */ + vattr_type_thang thang = {0}; + + thang.type_name = strcpy(thang.type_name,attr); + thang.type_flags = props; + + /* add the type to the type context */ + slapi_vattrspi_add_type(type_context,&thang,0); + } + + slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_vattr_types\n"); + return ret; +} + + + diff --git a/ldap/servers/plugins/vattrsp_template/vattrsp.def b/ldap/servers/plugins/vattrsp_template/vattrsp.def new file mode 100644 index 00000000..a0e2d8c0 --- /dev/null +++ b/ldap/servers/plugins/vattrsp_template/vattrsp.def @@ -0,0 +1,10 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 6.2.1 Virtual Attribute Service Provider Template Plugin' +EXPORTS + vattrsp_init @2 + plugin_init_debug_level @3 diff --git a/ldap/servers/plugins/views/Makefile b/ldap/servers/plugins/views/Makefile new file mode 100644 index 00000000..495c7d97 --- /dev/null +++ b/ldap/servers/plugins/views/Makefile @@ -0,0 +1,79 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libviews +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsdefs.mk +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./views.def +endif + +VIEWS_OBJS = views.o +OBJS = $(addprefix $(OBJDEST)/, $(VIEWS_OBJS)) + +VIEWS_DLL = views-plugin + +INCLUDES += -I../../slapd -I../../../include +CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) +VIEWS_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), AIX) +LD=ld +EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL) +endif + +VIEWS= $(addprefix $(LIBDIR)/, $(VIEWS_DLL).$(DLL_SUFFIX)) + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(VIEWS) + +ifeq ($(ARCH), WINNT) +$(VIEWS): $(OBJS) $(VIEWS_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(VIEWS_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE) +else +$(VIEWS): $(OBJS) $(VIEWS_DLL_OBJ) + $(LINK_DLL) $(VIEWS_DLL_OBJ) $(EXTRA_LIBS) +endif + + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(VIEWS_DLL_OBJ) +endif + $(RM) $(VIEWS) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(LIBDIR): + $(MKDIR) $(LIBDIR) diff --git a/ldap/servers/plugins/views/dllmain.c b/ldap/servers/plugins/views/dllmain.c new file mode 100644 index 00000000..fabf8677 --- /dev/null +++ b/ldap/servers/plugins/views/dllmain.c @@ -0,0 +1,96 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * Microsoft Windows specifics for BACK-LDBM DLL + */ +#include "ldap.h" +#include "lber.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/views/views.c b/ldap/servers/plugins/views/views.c new file mode 100644 index 00000000..b052167c --- /dev/null +++ b/ldap/servers/plugins/views/views.c @@ -0,0 +1,1779 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* plugin which implements directory server views */ + +#include <stdio.h> +#include <string.h> +#include "portable.h" +#include "slapi-plugin.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ +#include "dirver.h" +#include "statechange.h" +#include "views.h" + +#include "slapi-plugin-compat4.h" +#include "slapi-private.h" + + +#define VIEW_OBJECTCLASS "nsView" +#define VIEW_FILTER_ATTR "nsViewFilter" +#define STATECHANGE_VIEWS_ID "Views" +#define STATECHANGE_VIEWS_CONFG_FILTER "objectclass=" VIEW_OBJECTCLASS + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +#define VIEWS_PLUGIN_SUBSYSTEM "views-plugin" /* used for logging */ + +/* cache data structs */ + +struct _viewLinkedList +{ + void *pNext; + void *pPrev; +}; +typedef struct _viewLinkedList viewLinkedList; + +#if defined(DEBUG) +#define _VIEW_DEBUG_FILTERS /* Turning on hurts performance */ +#endif + +struct _viewEntry +{ + viewLinkedList list; + char *pDn; + char *viewfilter; /* the raw view */ + Slapi_Filter *includeAncestorFiltersFilter; /* the filter with all ancestor filters */ + Slapi_Filter *excludeAllButDescendentViewsFilter; /* for building the view of views */ + Slapi_Filter *excludeChildFiltersFilter; /* NOT all children views, for one level searches */ + Slapi_Filter *excludeGrandChildViewsFilter; /* view filter for one level searches */ + Slapi_Filter *includeChildViewsFilter; /* view filter for subtree searches */ +#ifdef _VIEW_DEBUG_FILTERS + /* monitor the cached filters with these */ + char includeAncestorFiltersFilter_str[1024]; /* the filter with all ancestor filters */ + char excludeAllButDescendentViewsFilter_str[1024]; /* for building the view of views */ + char excludeChildFiltersFilter_str[1024]; /* NOT all children views, for one level searches */ + char excludeGrandChildViewsFilter_str[1024]; /* view filter for one level searches */ + char includeChildViewsFilter_str[1024]; /* view filter for subtree searches */ +#endif + char *pSearch_base; /* the parent of the top most view */ + void *pParent; + void **pChildren; + int child_count; + unsigned long entryid; /* unique identifier for this entry */ + unsigned long parentid; /* unique identifier for the parent entry */ +}; +typedef struct _viewEntry viewEntry; + +struct _globalViewCache +{ + viewEntry *pCacheViews; + viewEntry **ppViewIndex; + int cache_built; + int view_count; + PRThread *currentUpdaterThread; +}; +typedef struct _globalViewCache golbalViewCache; + +static golbalViewCache theCache; + +/* other function prototypes */ +int views_init( Slapi_PBlock *pb ); +static int views_start( Slapi_PBlock *pb ); +static int views_close( Slapi_PBlock *pb ); +static int views_cache_create(); +static void views_update_views_cache( Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data ); +static int views_cache_build_view_list(viewEntry **pViews); +static int views_cache_index(); +static int views_dn_views_cb (Slapi_Entry* e, void *callback_data); +static int views_cache_add_dn_views(char *dn, viewEntry **pViews); +static void views_cache_add_ll_entry(void** attrval, void *theVal); +static void views_cache_discover_parent(viewEntry *pView); +static void views_cache_discover_children(viewEntry *pView); +static void views_cache_discover_view_scope(viewEntry *pView); +static void views_cache_create_applied_filter(viewEntry *pView); +static void views_cache_create_exclusion_filter(viewEntry *pView); +static void views_cache_create_inclusion_filter(viewEntry *pView); +Slapi_Filter *views_cache_create_descendent_filter(viewEntry *ancestor, PRBool useID); +static int view_search_rewrite_callback(Slapi_PBlock *pb); +static void views_cache_backend_state_change(void *handle, char *be_name, int old_be_state, int new_be_state); +static void views_cache_act_on_change_thread(void *arg); +static viewEntry *views_cache_find_view(char *view); + +/* our api broker published api */ +static void *api[3]; +static int _internal_api_views_entry_exists(char *view_dn, Slapi_Entry *e); +static int _internal_api_views_entry_dn_exists(char *view_dn, char *e_dn); +static int _internal_api_views_entry_exists_general(char *view_dn, Slapi_Entry *e, char *e_dn); + + +static Slapi_PluginDesc pdesc = { "views", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, + "virtual directory information tree views plugin" }; + +static void * view_plugin_identity = NULL; + +static PRRWLock *g_views_cache_lock; + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/* +** Plugin identity mgmt +*/ + +void view_set_plugin_identity(void * identity) +{ + view_plugin_identity=identity; +} + +void * view_get_plugin_identity() +{ + return view_plugin_identity; +} + +/* + views_init + -------- + adds our callbacks to the list +*/ +int views_init( Slapi_PBlock *pb ) +{ + int ret = 0; + void * plugin_identity=NULL; + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_init\n"); + + /* + ** Store the plugin identity for later use. + ** Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + view_set_plugin_identity(plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) views_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) views_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM, + "views_init: failed to register plugin\n" ); + ret = -1; + } + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_init\n"); + return ret; +} + +void views_read_lock() +{ + PR_RWLock_Rlock(g_views_cache_lock); +} + +void views_write_lock() +{ + PR_RWLock_Wlock(g_views_cache_lock); +} + +void views_unlock() +{ + PR_RWLock_Unlock(g_views_cache_lock); +} + +/* + views_start + --------- + This function publishes the interface for this plugin +*/ +static int views_start( Slapi_PBlock *pb ) +{ + int ret = 0; + void **statechange_api; + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_start\n"); + + theCache.cache_built = 0; + g_views_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "views"); + + /* first register our backend state change func (we'll use func pointer as handle) */ + slapi_register_backend_state_change((void *)views_cache_backend_state_change, views_cache_backend_state_change); + + /* create the view cache */ + + views_cache_create(); + + /* register callbacks for filter and search rewriting */ + slapi_compute_add_search_rewriter(view_search_rewrite_callback); + + /* register for state changes to view configuration */ + if(!slapi_apib_get_interface(StateChange_v1_0_GUID, &statechange_api)) + { + statechange_register(statechange_api, STATECHANGE_VIEWS_ID, NULL, STATECHANGE_VIEWS_CONFG_FILTER, NULL, views_update_views_cache); + } + + /* register our api so that other subsystems can be views aware */ + api[0] = NULL; /* reserved for api broker use */ + api[1] = (void *)_internal_api_views_entry_exists; + api[2] = (void *)_internal_api_views_entry_dn_exists; + + if( slapi_apib_register(Views_v1_0_GUID, api) ) + { + slapi_log_error( SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM, "views: failed to publish views interface\n"); + ret = -1; + } + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_start\n"); + return ret; +} + +/* _internal_api_views_entry_exists() + * ---------------------------------- + * externally published api to allow other subsystems to + * be views aware. Given a view and an entry, this function + * returns PR_TRUE if the entry would be returned by a subtree + * search on the view, PR_FALSE otherwise. + */ +static int _internal_api_views_entry_exists(char *view_dn, Slapi_Entry *e) +{ + return _internal_api_views_entry_exists_general(view_dn, e, NULL); +} + +static int _internal_api_views_entry_dn_exists(char *view_dn, char *e_dn) +{ + return _internal_api_views_entry_exists_general(view_dn, NULL, e_dn); +} + +static int _internal_api_views_entry_exists_general(char *view_dn, Slapi_Entry *e, char *e_dn) +{ + int ret = 0; + viewEntry *view; + char *dn; + + /* there are two levels of scope for a view, + * from the parent of the view without a view filter + * and the parent of the top most view including a + * view filter - either match will do + */ + + /* find the view */ + view = views_cache_find_view(view_dn); + if(0==view) + { + /* this is not the entry you are looking for */ + goto bail; + } + + /* normal scope - is the view an ancestor of the entry */ + if(e_dn) + dn = e_dn; + else + dn = slapi_entry_get_ndn(e); + + if(slapi_dn_issuffix(dn, view_dn)) + { + /* this entry is physically contained in the view hiearchy */ + ret = -1; + goto bail; + } + + /* view scope - view hiearchy scope plus view filter */ + if(slapi_dn_issuffix(dn, view->pSearch_base)) + { + if(0==e) + { + Slapi_DN *sdn = slapi_sdn_new_dn_byref(dn); + + slapi_search_internal_get_entry( sdn, NULL, &e , view_get_plugin_identity()); + + slapi_sdn_free(&sdn); + } + + /* so far so good, apply filter */ + if(0==slapi_filter_test_simple(e,view->includeAncestorFiltersFilter)) + { + /* this entry would appear in the view */ + ret = -1; + } + } + +bail: + return ret; +} + +void views_cache_free() +{ + viewEntry *head = theCache.pCacheViews; + viewEntry *current; + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_free\n"); + + /* free the cache */ + current = head; + + while(current != NULL) + { + viewEntry *theView = current; + current = current->list.pNext; + + /* free the view */ + slapi_ch_free((void**)&theView->pDn); + slapi_ch_free((void**)&theView->viewfilter); + slapi_filter_free(theView->includeAncestorFiltersFilter,1); + slapi_filter_free(theView->excludeAllButDescendentViewsFilter,1); + slapi_filter_free(theView->excludeChildFiltersFilter,1); + slapi_filter_free(theView->excludeGrandChildViewsFilter,1); + slapi_filter_free(theView->includeChildViewsFilter,1); + slapi_ch_free((void**)&theView->pSearch_base); + slapi_ch_free((void**)&theView->pChildren); + slapi_ch_free((void**)&theView); + } + + theCache.pCacheViews = NULL; + + slapi_ch_free((void**)&theCache.ppViewIndex); + + theCache.view_count = 0; + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_free\n"); +} + +/* + views_close + --------- + unregisters the interface for this plugin +*/ +static int views_close( Slapi_PBlock *pb ) +{ + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_close\n"); + + /* unregister backend state change notification */ + slapi_unregister_backend_state_change((void *)views_cache_backend_state_change); + + views_cache_free(); + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_close\n"); + + return 0; +} + + +/* + views_cache_create + --------------------- + Walks the views in the DIT and populates the cache. +*/ +static int views_cache_create() +{ + int ret = -1; + static int firstTime = 1; + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_create\n"); + + + /* lock cache */ + views_write_lock(); + + theCache.currentUpdaterThread = PR_GetCurrentThread(); /* to avoid deadlock */ + + if(theCache.pCacheViews) + { + /* need to get rid of the existing views */ + views_cache_free(); + } + + /* grab the view entries */ + ret = views_cache_build_view_list(&(theCache.pCacheViews)); + if(!ret && theCache.pCacheViews) + { + viewEntry *head = theCache.pCacheViews; + viewEntry *current; + + /* OK, we have a basic cache, now we need to + * fix up parent and children pointers + */ + for(current = head; current != NULL; current = current->list.pNext) + { + views_cache_discover_parent(current); + views_cache_discover_children(current); + } + + /* scope of views and cache search filters... */ + for(current = head; current != NULL; current = current->list.pNext) + { + views_cache_discover_view_scope(current); + views_cache_create_applied_filter(current); + views_cache_create_exclusion_filter(current); + views_cache_create_inclusion_filter(current); + } + + /* create the view index */ + ret = views_cache_index(); + if(ret != 0) + { + /* currently we cannot go on without the indexes */ + slapi_log_error(SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM, "views_cache_create: failed to index cache\n"); + } + else + theCache.cache_built = 1; + } + else + { + /* its ok to not have views to cache */ + theCache.cache_built = 0; + ret = 0; + } + + theCache.currentUpdaterThread = 0; + + /* unlock cache */ + views_unlock(); + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_create\n"); + return ret; +} + +/* + * views_cache_view_compare + * ----------------------- + * compares the dns of two views - used for sorting the index + */ +int views_cache_view_compare(const void *e1, const void *e2) +{ + int ret; + Slapi_DN *dn1 = slapi_sdn_new_dn_byval((*(viewEntry**)e1)->pDn); + Slapi_DN *dn2 = slapi_sdn_new_dn_byval((*(viewEntry**)e2)->pDn); + + ret = slapi_sdn_compare(dn1, dn2); + + slapi_sdn_free(&dn1); + slapi_sdn_free(&dn2); + + return ret; +} + +/* + * views_cache_dn_compare + * ----------------------- + * compares a dn with the dn of a view - used for searching the index + */ +int views_cache_dn_compare(const void *e1, const void *e2) +{ + int ret; + Slapi_DN *dn1 = slapi_sdn_new_dn_byval((char*)e1); + Slapi_DN *dn2 = slapi_sdn_new_dn_byval(((viewEntry*)e2)->pDn); + + ret = slapi_sdn_compare(dn1, dn2); + + slapi_sdn_free(&dn1); + slapi_sdn_free(&dn2); + + return ret; +} +/* + * views_cache_index + * ---------------- + * indexes the cache for fast look up of views + */ +static int views_cache_index() +{ + int ret = -1; + int i; + viewEntry *theView = theCache.pCacheViews; + viewEntry *current = 0; + + if(theCache.ppViewIndex) + slapi_ch_free((void**)&theCache.ppViewIndex); + + theCache.view_count = 0; + + /* lets count the views */ + for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext) + theCache.view_count++; + + + theCache.ppViewIndex = (viewEntry**)calloc(theCache.view_count, sizeof(viewEntry*)); + if(theCache.ppViewIndex) + { + /* copy over the views */ + for(i=0; i<theCache.view_count; i++) + { + theCache.ppViewIndex[i] = theView; + theView = theView->list.pNext; + } + + /* sort the views */ + qsort(theCache.ppViewIndex, theCache.view_count, sizeof(viewEntry*), views_cache_view_compare); + + ret = 0; + } + + return ret; +} + + +/* + views_cache_view_index_bsearch - RECURSIVE + ---------------------------------------- + performs a binary search on the cache view index + return -1 if key is not found +*/ +viewEntry *views_cache_view_index_bsearch( const char *key, int lower, int upper ) +{ + viewEntry *ret = 0; + int index = 0; + int compare_ret = 0; + + if(upper >= lower) + { + if(upper != 0) + index = ((upper-lower)/2) + lower; + else + index = 0; + + compare_ret = views_cache_dn_compare(key, theCache.ppViewIndex[index]); + + if(!compare_ret) + { + ret = (theCache.ppViewIndex)[index]; + } + else + { + /* seek elsewhere */ + if(compare_ret < 0) + { + /* take the low road */ + ret = views_cache_view_index_bsearch(key, lower, index-1); + } + else + { + /* go high */ + ret = views_cache_view_index_bsearch(key, index+1, upper); + } + } + } + + return ret; +} + + +/* + views_cache_find_view + ------------------- + searches for a view, and if found returns it, null otherwise +*/ +static viewEntry *views_cache_find_view(char *view) +{ + viewEntry *ret = 0; /* assume failure */ + + if(theCache.view_count != 1) + ret = views_cache_view_index_bsearch(view, 0, theCache.view_count-1); + else + { + /* only one view (that will fool our bsearch) lets check it here */ + if(!slapi_utf8casecmp((unsigned char*)view, (unsigned char*)theCache.ppViewIndex[0]->pDn)) + { + ret = theCache.ppViewIndex[0]; + } + } + + return ret; +} + + +/* + views_cache_discover_parent + ------------------------------ + finds the parent of this view and caches it in view +*/ +static void views_cache_discover_parent(viewEntry *pView) +{ + viewEntry *head = theCache.pCacheViews; + viewEntry *current; + int found = 0; + + for(current = head; current != NULL && !found; current = current->list.pNext) + { + if(slapi_dn_isparent( current->pDn, pView->pDn )) + { + found = 1; + pView->pParent = current; + } + } + + if(!found) + { + /* this is a top view */ + pView->pParent = NULL; + } +} + + +/* + views_cache_discover_children + ------------------------------ + finds the children of this view and caches them in view +*/ +static void views_cache_discover_children(viewEntry *pView) +{ + viewEntry *head = theCache.pCacheViews; + viewEntry *current; + int child_count = 0; + int add_count = 0; + + if(pView->pChildren) + { + slapi_ch_free((void**)&pView->pChildren); + pView->pChildren = NULL; + } + + /* first lets count the children */ + for(current = head; current != NULL; current = current->list.pNext) + { + if(slapi_dn_isparent(pView->pDn, current->pDn)) + child_count++; + } + + /* make the space for them */ + pView->child_count = child_count; + + pView->pChildren = calloc(child_count, sizeof(viewEntry*)); + + /* add them */ + for(current = head; current != NULL; current = current->list.pNext) + { + if(slapi_dn_isparent(pView->pDn, current->pDn)) + { + ((viewEntry**)pView->pChildren)[add_count] = current; + add_count++; + } + } +} + + +/* + views_cache_discover_view_scope + ------------------------------ + finds the parent of the top most view and sets the scope of the view search +*/ + +static void views_cache_discover_view_scope(viewEntry *pView) +{ + viewEntry *current = pView; + + if(pView->pSearch_base) + slapi_ch_free((void**)&pView->pSearch_base); + + while(current != NULL) + { + if(current->pParent == NULL) + { + /* found top */ + pView->pSearch_base = slapi_dn_parent(current->pDn); + } + + current = current->pParent; + } + +} + + +/* + views_cache_create_applied_filter + -------------------------------- + builds the filters for: + char *includeAncestorFiltersFilter; the view with all ancestor views +*/ +static void views_cache_create_applied_filter(viewEntry *pView) +{ + viewEntry *current = pView; + Slapi_Filter *pCurrentFilter = 0; + Slapi_Filter *pBuiltFilter = 0; + Slapi_Filter *pViewEntryExcludeFilter = 0; + char *buf = 0; + int len = 0; + + if(pView->includeAncestorFiltersFilter) + { + /* release the current filter */ + slapi_filter_free(pView->includeAncestorFiltersFilter, 1); + pView->includeAncestorFiltersFilter = 0; + } + + /* create applied view filter (this view filter plus ancestors) */ + while(current != NULL) + { + /* add this view filter to the built filter using AND */ + char *buf; + + if(!current->viewfilter) + { + current = current->pParent; + continue; /* skip this view */ + } + + buf = slapi_ch_strdup(current->viewfilter); + + pCurrentFilter = slapi_str2filter( buf ); + if(pBuiltFilter && pCurrentFilter) + pBuiltFilter = slapi_filter_join_ex( LDAP_FILTER_AND, pBuiltFilter, pCurrentFilter, 0 ); + else + pBuiltFilter = pCurrentFilter; + + slapi_ch_free((void **)&buf); + + current = current->pParent; + } + + /* filter for removing view entries from search */ + pViewEntryExcludeFilter = slapi_str2filter( "(!(objectclass=" VIEW_OBJECTCLASS "))" ); + + if(pBuiltFilter) + pView->includeAncestorFiltersFilter = slapi_filter_join_ex( LDAP_FILTER_AND, pBuiltFilter, pViewEntryExcludeFilter, 0 ); + else + pView->includeAncestorFiltersFilter = pViewEntryExcludeFilter; + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(pView->includeAncestorFiltersFilter, pView->includeAncestorFiltersFilter_str, sizeof(pView->includeAncestorFiltersFilter_str)); +#endif +} + +/* views_cache_create_exclusion_filter + * ---------------------------------- + * makes a filter which is used for one level searches + * so that views show up correctly if the client filter + * allows: excludeGrandChildViewsFilter + * + * Also makes the filter which excludes entries which + * belong in descendent views: excludeChildFiltersFilter + */ +static void views_cache_create_exclusion_filter(viewEntry *pView) +{ + viewEntry *current = pView; + Slapi_Filter *pOrSubFilter = 0; + Slapi_Filter *excludeChildFiltersFilter = 0; + Slapi_Filter *pChildExcludeSubFilter = 0; + Slapi_Filter *pViewEntryExcludeFilter = 0; + int child_count = 0; + int len = 0; + char *buf = 0; + Slapi_RDN *rdn = 0; + char *str_rdn = 0; + Slapi_Filter *pCurrentFilter = 0; + + /* create exclusion filter for one level searches + * this requires the rdns of the grandchildren of + * this view to be in a filter + */ + + if(pView->excludeGrandChildViewsFilter) + { + /* release the current filter */ + slapi_filter_free(pView->excludeGrandChildViewsFilter, 1); + pView->excludeGrandChildViewsFilter = 0; + } + + if(pView->excludeChildFiltersFilter) + { + /* release the current filter */ + slapi_filter_free(pView->excludeChildFiltersFilter, 1); + pView->excludeChildFiltersFilter = 0; + } + +/* if(pView->child_count == 0) + { +*/ /* this view has no children */ +/* pView->excludeGrandChildViewsFilter = 0; + pView->excludeChildFiltersFilter = 0; + return; + } + + + while(child_count < pView->child_count) + { + current = pView->pChildren[child_count]; + + if(current->child_count == 0) + { +*/ /* no grandchildren here, skip */ +/* child_count++; + continue; + } +*/ + /* for each child we need to add its descendants */ +/* if(pOrSubFilter) + { + Slapi_Filter *pDescendents = views_cache_create_descendent_filter(current, TRUE); + if(pDescendents) + pOrSubFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pOrSubFilter, pDescendents, 0 ); + } + else + pOrSubFilter = views_cache_create_descendent_filter(current, TRUE); + + child_count++; + } +*/ + buf=PR_smprintf("(parentid=%lu)", pView->entryid); + pView->excludeGrandChildViewsFilter = slapi_str2filter( buf ); + PR_smprintf_free(buf); + +/* if(pOrSubFilter) + pView->excludeGrandChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_NOT, pOrSubFilter, NULL, 0 );*/ + + excludeChildFiltersFilter = views_cache_create_descendent_filter(pView, PR_FALSE); + if(excludeChildFiltersFilter) + pView->excludeChildFiltersFilter = slapi_filter_join_ex( LDAP_FILTER_NOT, excludeChildFiltersFilter, NULL, 0 ); + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(pView->excludeGrandChildViewsFilter, pView->excludeGrandChildViewsFilter_str, sizeof(pView->excludeGrandChildViewsFilter_str)); + slapi_filter_to_string(pView->excludeChildFiltersFilter, pView->excludeChildFiltersFilter_str, sizeof(pView->excludeChildFiltersFilter_str)); +#endif +} + + +Slapi_Filter *views_cache_create_descendent_filter(viewEntry *ancestor, PRBool useEntryID) +{ + int child_count = 0; + Slapi_Filter *pOrSubFilter = 0; + + while(child_count < ancestor->child_count) + { + Slapi_Filter *pDescendentSubFilter = 0; + Slapi_RDN *rdn = 0; + char *str_rdn = 0; + Slapi_Filter *pCurrentFilter = 0; + viewEntry *currentChild = ancestor->pChildren[child_count]; + char *buf = 0; + int len = 0; + + /* for each child we need to add its descendants + * we do this now before processing this view + * to try to help the filter code out by having + * the most significant filters first + */ + pDescendentSubFilter = views_cache_create_descendent_filter(currentChild, useEntryID); + if(pDescendentSubFilter) + if(pOrSubFilter) + pOrSubFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pOrSubFilter, pDescendentSubFilter, 0 ); + else + pOrSubFilter = pDescendentSubFilter; + + if(useEntryID) + { + /* we need the RDN of this child */ +/* rdn = slapi_rdn_new_dn(currentChild->pDn); + str_rdn = (char *)slapi_rdn_get_rdn(rdn); + len = strlen(str_rdn); + + buf=PR_smprintf("(%s)", str_rdn);*/ + + /* uniquely identify this child */ + buf=PR_smprintf("(parentid=%lu)", currentChild->entryid); + } + else + { + /* this is a filter based filter */ + if(currentChild->viewfilter) + { + buf=PR_smprintf("%s",currentChild->viewfilter); + } + } + + if(buf) + { + pCurrentFilter = slapi_str2filter( buf ); + if(pOrSubFilter) + pOrSubFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pOrSubFilter, pCurrentFilter, 0 ); + else + pOrSubFilter = pCurrentFilter; + + PR_smprintf_free(buf); + } + + + child_count++; + } + + return pOrSubFilter; +} + + +/* views_cache_create_inclusion_filter + * ---------------------------------- + * makes a filter which is used for subtree searches + * so that views show up correctly if the client filter + * allows + */ +static void views_cache_create_inclusion_filter(viewEntry *pView) +{ + viewEntry *head = theCache.pCacheViews; +/* viewEntry *current; */ +/* Slapi_Filter *view_filter; */ + char *view_filter_str; + + if(pView->includeChildViewsFilter) + { + /* release the current filter */ + slapi_filter_free(pView->includeChildViewsFilter, 1); + pView->includeChildViewsFilter = 0; + } +#if 0 + for(current = head; current != NULL; current = current->list.pNext) + { + Slapi_DN *viewDN; + Slapi_RDN *viewRDN; + char *viewRDNstr; + char *buf = 0; + Slapi_Filter *viewSubFilter; + + /* if this is this a descendent, ignore it */ + if(slapi_dn_issuffix(current->pDn,pView->pDn) && !(current == pView)) + continue; + + viewDN = slapi_sdn_new_dn_byref(current->pDn); + viewRDN = slapi_rdn_new(); + + slapi_sdn_get_rdn(viewDN,viewRDN); + viewRDNstr = (char *)slapi_rdn_get_rdn(viewRDN); + + buf = calloc(1, strlen(viewRDNstr) + 11 ); /* 3 for filter */ + sprintf(buf, "(%s)", viewRDNstr ); + viewSubFilter = slapi_str2filter( buf ); + + if(pView->includeChildViewsFilter) + pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pView->includeChildViewsFilter, viewSubFilter, 0 ); + else + pView->includeChildViewsFilter = viewSubFilter; + + slapi_ch_free((void **)&buf); + slapi_sdn_free(&viewDN); + slapi_rdn_free(&viewRDN); + + child_count++; + } +#endif + + /* exclude all other view entries but decendents */ +/* pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_NOT, pView->includeChildViewsFilter, NULL, 0 ); +*/ + /* it seems reasonable to include entries which + * may not fit the view decription but which + * are actually *contained* in the view + * therefore we use parentids for the view + * filter + */ + + + /* add decendents */ + pView->includeChildViewsFilter = views_cache_create_descendent_filter(pView, PR_TRUE); + + /* add this view */ + view_filter_str = PR_smprintf("(parentid=%lu)", pView->entryid); + + if(pView->includeChildViewsFilter) + { + pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_OR, slapi_str2filter( view_filter_str ), pView->includeChildViewsFilter, PR_FALSE); + } + else + { + pView->includeChildViewsFilter = slapi_str2filter( view_filter_str ); + } + PR_smprintf_free(view_filter_str); + view_filter_str = NULL; + + /* and make sure the this applies only to views */ + +/* if(pView->includeChildViewsFilter) + {*/ +/* Not necessary since we now use entryid in the filter, + so all will be views anyway, and the less sub-filters + the better + view_filter_str = strdup("(objectclass=" VIEW_OBJECTCLASS ")"); + view_filter = slapi_str2filter( view_filter_str ); +*/ + /* child views first because entryid indexed + * and makes evaluation faster when a bunch + * of indexed filter evaluations with only one + * target are evaluated first rather than an + * indexed filter which will provide many entries + * that may trigger an index evaluation short + * circuit. i.e. if one of the child filters is + * true then we have one entry, if not, then we + * have used indexes completely to determine that + * no entry matches and (objectclass=nsview) is never + * evaluated. + * I should imagine this will hold for all but the + * very deepest, widest view trees when subtree + * searches are performed from the top + */ +/* pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_AND, pView->includeChildViewsFilter, view_filter, 0 ); + } + else + { + view_filter_str = strdup("(objectclass=nsviewincludenone)"); *//* hackery to get the right result */ +/* pView->includeChildViewsFilter = slapi_str2filter( view_filter_str ); + } +*/ +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(pView->includeChildViewsFilter, pView->includeChildViewsFilter_str, sizeof(pView->includeChildViewsFilter_str)); +#endif +} + + +/* + views_cache_build_view_list + ------------------------------- + builds the list of views by searching for them throughout the DIT +*/ +static int views_cache_build_view_list(viewEntry **pViews) +{ + int ret = 0; + Slapi_PBlock *pSuffixSearch = 0; + Slapi_Entry **pSuffixList = 0; + Slapi_Attr *suffixAttr; + struct berval **suffixVals; + char *attrType = 0; + char *attrs[2]; + int suffixIndex = 0; + int valIndex = 0; + int cos_def_available = 0; + static int firstTime = 1; + + slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_build_view_list\n"); + + /* + the views may be anywhere in the DIT, + so our first task is to find them. + */ + + attrs[0] = "namingcontexts"; + attrs[1] = 0; + + slapi_log_error(SLAPI_LOG_PLUGIN, VIEWS_PLUGIN_SUBSYSTEM, "views: Building view cache.\n"); + + pSuffixSearch = slapi_search_internal("",LDAP_SCOPE_BASE,"(objectclass=*)",NULL,attrs,0); + if(pSuffixSearch) + slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_RESULT, &ret); + + if(pSuffixSearch && ret == LDAP_SUCCESS) + { + /* iterate through the suffixes and search for views */ + slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &pSuffixList); + if(pSuffixList) + { + while(pSuffixList[suffixIndex]) + { + if(!slapi_entry_first_attr(pSuffixList[suffixIndex], &suffixAttr)) + { + do + { + attrType = 0; + slapi_attr_get_type(suffixAttr, &attrType); + if(attrType && !slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"namingcontexts")) + { + if(!slapi_attr_get_bervals_copy(suffixAttr, &suffixVals)) + { + valIndex = 0; + + if(suffixVals) + { + while(suffixVals[valIndex]) + { + /* here's a suffix, lets search it... */ + if(suffixVals[valIndex]->bv_val) + views_cache_add_dn_views(suffixVals[valIndex]->bv_val ,pViews); + + valIndex++; + } + + + ber_bvecfree( suffixVals ); + suffixVals = NULL; + } + } + } + + } while(!slapi_entry_next_attr(pSuffixList[suffixIndex], suffixAttr, &suffixAttr)); + } + suffixIndex++; + } + } + } + else + { + slapi_log_error(SLAPI_LOG_PLUGIN, VIEWS_PLUGIN_SUBSYSTEM, "views_cache_build_view_list: failed to find suffixes\n"); + ret = -1; + } + + /* clean up */ + if(pSuffixSearch) + { + slapi_free_search_results_internal(pSuffixSearch); + slapi_pblock_destroy(pSuffixSearch); + } + + + slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_build_view_list\n"); + return ret; +} + +/* struct to support search callback API */ +struct dn_views_info { + viewEntry **pViews; + int ret; +}; + +/* does same funcationality as views_add_dn_views except it is invoked via a callback */ + +static int views_dn_views_cb (Slapi_Entry* e, void *callback_data) { + struct dn_views_info *info; + char *filter = 0; + char *pDn = 0; + struct berval **dnVals; + Slapi_Attr *dnAttr; + char *attrType = 0; + char *attrs[3]; + viewEntry *pView; + + attrs[0] = VIEW_FILTER_ATTR; + attrs[1] = "entryid"; + attrs[2] = 0; + info=(struct dn_views_info *)callback_data; + + info->ret = 0; + + pDn = slapi_entry_get_ndn(e); + + /* create the view */ + pView = calloc(1, sizeof(viewEntry)); + pView->pDn = slapi_ch_strdup(pDn); + + if(!slapi_entry_first_attr(e, &dnAttr)) + { + do + { + attrType = 0; + + + /* get the filter */ + slapi_attr_get_type(dnAttr, &attrType); + if(attrType && !strcasecmp(attrType,VIEW_FILTER_ATTR)) + { + if(!slapi_attr_get_bervals_copy(dnAttr, &dnVals)) + { + /* add filter */ + pView->viewfilter = slapi_ch_strdup(dnVals[0]->bv_val); + } + + ber_bvecfree( dnVals ); + dnVals = NULL; + } + + if(attrType && !strcasecmp(attrType,"entryid")) + { + Slapi_Value *val = 0; + + slapi_attr_first_value(dnAttr, &val); + pView->entryid = slapi_value_get_ulong(val); + } + + if(attrType && !strcasecmp(attrType,"parentid")) + { + Slapi_Value *val = 0; + + slapi_attr_first_value(dnAttr, &val); + pView->parentid = slapi_value_get_ulong(val); + } + + } while(!slapi_entry_next_attr(e, dnAttr, &dnAttr)); + + } + + /* add view to the cache */ + views_cache_add_ll_entry((void**)info->pViews, (void *)pView); + + return info->ret; +} + + +/* + views_cache_add_dn_views + ------------------------- + takes a dn as argument and searches the dn for views, + adding any found to the view cache. Change to use search callback API +*/ + +#define DN_VIEW_FILTER "(objectclass=" VIEW_OBJECTCLASS ")" + +static int views_cache_add_dn_views(char *dn, viewEntry **pViews) +{ + Slapi_PBlock *pDnSearch = 0; + struct dn_views_info info; + pDnSearch = slapi_pblock_new(); + if (pDnSearch) { + info.ret=-1; + info.pViews=pViews; + slapi_search_internal_set_pb(pDnSearch, dn, LDAP_SCOPE_SUBTREE, + DN_VIEW_FILTER,NULL,0, + NULL,NULL,view_get_plugin_identity(),0); + slapi_search_internal_callback_pb(pDnSearch, + &info /* callback_data */, + NULL/* result_callback */, + views_dn_views_cb, + NULL /* referral_callback */); + slapi_pblock_destroy (pDnSearch); + } + return info.ret; +} + +/* + views_cache_add_ll_entry + --------------------------------------------------- + the element is added to the head of the linked list + + *NOTE* this function assumes and *requires* that the structures + passed to it in "attrval" and "theVal" have a viewLinkedList + member, and it is the *first* member of the structure. This + is safe because this is a module level function, and all functions + which call this one are part of the same sub-system. +*/ +static void views_cache_add_ll_entry(void** attrval, void *theVal) +{ + slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_add_ll_entry\n"); + + if(*attrval) + { + /* push this to the start of the list (because its quick) */ + ((viewLinkedList*)theVal)->pNext = *attrval; + ((viewLinkedList*)(*attrval))->pPrev = theVal; + *attrval = theVal; + } + else + { + /* new or end of list */ + ((viewLinkedList*)theVal)->pNext = NULL; + ((viewLinkedList*)theVal)->pPrev = NULL; + *attrval = theVal; + } + + slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_add_ll_entry\n"); +} + + +/* + views_update_views_cache + ----------------------- + + update internal view cache after state change +*/ +static void views_update_views_cache( Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data ) +{ + char *pDn; + viewEntry *theView; + viewEntry *current; + Slapi_Attr *attr; + struct berval val; + int build_cache = 0; + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_update_views_cache\n"); + + views_write_lock(); + + if(!theCache.cache_built) + { + /* zarro views = no cache, + * this is probably an add op + * lets build the cache + */ + build_cache = 1; + goto unlock_cache; + } + + pDn = slapi_entry_get_ndn(e); + theView = views_cache_find_view(pDn); + + switch(modtype) + { + case LDAP_CHANGETYPE_MODIFY: + /* if still a view and exists + * update string filter + * update the filters of all views + * if just became a view fall through to add op + * if stopped being a view fall through to delete op + */ + + /* determine what happenned - does the view exist currently? */ + if(theView) + { + /* does it have the view objectclass? */ + if(!slapi_entry_attr_find( e, "objectclass", &attr )) + { + val.bv_len = 8; + val.bv_val = VIEW_OBJECTCLASS; + + if(!slapi_attr_value_find( attr, &val)) + { + /* it is a view */ + attr = 0; + + /* has the filter changed? */ + slapi_entry_attr_find( e, VIEW_FILTER_ATTR, &attr ); + + if(attr) + { + if(theView->viewfilter) /* NULL means a filter added */ + { + /* we could translate the string filter into + * a real filter and compare against + * the view - that would tell us if the filter + * was substantively changed. + * + * But we're not gonna do that :) + */ + val.bv_len = strlen(theView->viewfilter)+1; + val.bv_val = theView->viewfilter; + + if(!slapi_attr_value_find( attr, &val)) + { + /* filter unchanged */ + break; + } + } + } + else + { + /* if no filter in view, then no change */ + if(theView->viewfilter == 0) + break; + } + + /* this was indeed a significant mod, add the new filter */ + if(theView->viewfilter) + slapi_ch_free((void**)&theView->viewfilter); + + if(attr) + { + Slapi_Value *v; + slapi_attr_first_value( attr, &v ); + theView->viewfilter = slapi_ch_strdup(slapi_value_get_string(v)); + } + + /* update all filters */ + for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext) + { + views_cache_create_applied_filter(current); + views_cache_create_exclusion_filter(current); + views_cache_create_inclusion_filter(current); + } + } + else + { + /* this is a delete operation */ + modtype = LDAP_CHANGETYPE_DELETE; + } + } + else + /* thats bad */ + break; + } + else + { + /* this is an add operation */ + modtype = LDAP_CHANGETYPE_ADD; + } + + case LDAP_CHANGETYPE_DELETE: + /* remove view entry from list + * update children of parent + * update all child filters + * re-index + */ + if(modtype == LDAP_CHANGETYPE_DELETE) + { + + if(theCache.view_count-1) + { + /* detach view */ + if(theView->list.pPrev) + ((viewEntry*)(theView->list.pPrev))->list.pNext = theView->list.pNext; + + if(theView->list.pNext) + { + ((viewEntry*)(theView->list.pNext))->list.pPrev = theView->list.pPrev; + + if(theView->list.pPrev == NULL) /* if this is the head */ + theCache.pCacheViews = (viewEntry*)(theView->list.pNext); + } + + /* update children */ + if(theView->pParent) + views_cache_discover_children((viewEntry*)theView->pParent); + + /* update filters */ + for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext) + { + views_cache_create_applied_filter(current); + views_cache_create_exclusion_filter(current); + views_cache_create_inclusion_filter(current); + } + + /* reindex */ + views_cache_index(); + } + else + { + theCache.pCacheViews = NULL; + theCache.view_count = 0; + theCache.cache_built = 0; + } + + /* free the view */ + slapi_ch_free((void**)&theView->pDn); + slapi_ch_free((void**)&theView->viewfilter); + slapi_filter_free(theView->includeAncestorFiltersFilter,1); + slapi_filter_free(theView->excludeAllButDescendentViewsFilter,1); + slapi_filter_free(theView->excludeChildFiltersFilter,1); + slapi_filter_free(theView->excludeGrandChildViewsFilter,1); + slapi_filter_free(theView->includeChildViewsFilter,1); + slapi_ch_free((void**)&theView->pSearch_base); + slapi_ch_free((void**)&theView->pChildren); + slapi_ch_free((void**)&theView); + + break; + } + + case LDAP_CHANGETYPE_ADD: + /* create view entry + * add it to list + * update children of parent + * update all child filters + * re-index + */ + if(modtype == LDAP_CHANGETYPE_ADD) + { + theView = calloc(1, sizeof(viewEntry)); + theView->pDn = slapi_ch_strdup(pDn); + + /* get the view filter, the entryid, and the parentid */ + slapi_entry_attr_find( e, VIEW_FILTER_ATTR, &attr ); + + if(attr) + { + Slapi_Value *v; + slapi_attr_first_value( attr, &v ); + theView->viewfilter = slapi_ch_strdup(slapi_value_get_string(v)); + } + else + theView->viewfilter = NULL; + + slapi_entry_attr_find( e, "entryid", &attr ); + + if(attr) + { + Slapi_Value *v; + slapi_attr_first_value( attr, &v ); + theView->entryid = slapi_value_get_ulong(v); + } + else + theView->entryid = 0; + + slapi_entry_attr_find( e, "parentid", &attr ); + + if(attr) + { + Slapi_Value *v; + slapi_attr_first_value( attr, &v ); + theView->parentid = slapi_value_get_ulong(v); + } + else + theView->parentid = 0; + + /* add view to the cache */ + views_cache_add_ll_entry((void**)theCache.pCacheViews, (void *)theView); + + views_cache_discover_parent(theView); + if(theView->pParent) + views_cache_discover_children((viewEntry*)theView->pParent); + + /* update filters */ + for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext) + { + views_cache_discover_view_scope(current); /* if ns-view oc added, new view may be top */ + views_cache_create_applied_filter(current); + views_cache_create_exclusion_filter(current); + views_cache_create_inclusion_filter(current); + } + + /* reindex */ + views_cache_index(); + break; + } + + case LDAP_CHANGETYPE_MODDN: + /* get old dn to find the view + * change dn + * update parents and children + * update all filters + * reindex + */ + + { + char *old_dn; + Slapi_Entry *old_entry; + + slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &old_entry ); + old_dn = slapi_entry_get_ndn(old_entry); + + theView = views_cache_find_view(old_dn); + if(theView) + { + slapi_ch_free((void**)&theView->pDn); + theView->pDn = slapi_ch_strdup(pDn); + + for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext) + { + views_cache_discover_parent(current); + views_cache_discover_children(current); + } + + for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext) + { + views_cache_discover_view_scope(current); + views_cache_create_applied_filter(current); + views_cache_create_exclusion_filter(current); + views_cache_create_inclusion_filter(current); + } + } + /* reindex */ + views_cache_index(); + break; + } + + default: + /* we don't care about this op */ + break; + } + +unlock_cache: + views_unlock(); + + if(build_cache) + { + views_cache_create(); + } + + slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_update_views_cache\n"); +} + + + +/* + * view_search_rewrite_callback + * ---------------------------- + * this is the business end of the plugin + * this function is called from slapd + * rewrites the search to conform to the view + * Meaning of the return code : + * -1 : keep looking + * 0 : rewrote OK + * 1 : refuse to do this search + * 2 : operations error + */ +static int view_search_rewrite_callback(Slapi_PBlock *pb) +{ + int ret = -1; + char *base = 0; + Slapi_Filter *clientFilter = 0; + Slapi_Filter *includeAncestorFiltersFilter = 0; /* the view with all ancestor views */ + Slapi_Filter *excludeChildFiltersFilter = 0; /* NOT all children views, for one level searches */ + Slapi_Filter *excludeGrandChildViewsFilter = 0; /* view filter for one level searches */ + Slapi_Filter *includeChildViewsFilter = 0; /* view filter for subtree searches */ + Slapi_Filter *seeViewsFilter = 0; /* view filter to see views */ + Slapi_Filter *outFilter = 0; + int scope = 0; + int set_scope = LDAP_SCOPE_SUBTREE; + viewEntry *theView = 0; + +#ifdef _VIEW_DEBUG_FILTERS + char outFilter_str[1024]; + char clientFilter_str[1024]; + char includeAncestorFiltersFilter_str[1024]; + char excludeChildFiltersFilter_str[1024]; + char excludeGrandChildViewsFilter_str[1024]; + char includeChildViewsFilter_str[1024]; +#endif + + /* if no cache, no views */ + if(!theCache.cache_built) + goto end; + + /* avoid locking if this thread is the updater */ + if(theCache.currentUpdaterThread) + { + PRThread *thisThread = PR_GetCurrentThread(); + if(thisThread == theCache.currentUpdaterThread) + goto end; + } + + /* first, find out if this is a base search (we do nothing) */ + slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &scope); + if(scope == LDAP_SCOPE_BASE) + goto end; + + /* if base of the search is a view */ + slapi_pblock_get(pb, SLAPI_SEARCH_TARGET, &base); + + /* Read lock the cache */ + views_read_lock(); + + theView = views_cache_find_view(base); + + /* if the view is disabled (we service subtree searches in this case) */ + if(!theView || !theView->viewfilter && scope == LDAP_SCOPE_ONELEVEL) + { + /* unlock the cache */ + views_unlock(); + goto end; + } + + + /* this is a view search, and we are smokin' */ + + /* grab the view filters we are going to need now so we can release the cache lock */ + if(scope == LDAP_SCOPE_ONELEVEL) + { + excludeChildFiltersFilter = slapi_filter_dup(theView->excludeChildFiltersFilter); + excludeGrandChildViewsFilter = slapi_filter_dup(theView->excludeGrandChildViewsFilter); + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(excludeChildFiltersFilter, excludeChildFiltersFilter_str, sizeof(excludeChildFiltersFilter_str)); + slapi_filter_to_string(excludeGrandChildViewsFilter, excludeGrandChildViewsFilter_str, sizeof(excludeGrandChildViewsFilter_str)); +#endif + + } + + includeChildViewsFilter = slapi_filter_dup(theView->includeChildViewsFilter); + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(includeChildViewsFilter, includeChildViewsFilter_str, sizeof(includeChildViewsFilter_str)); +#endif + + /* always used */ + includeAncestorFiltersFilter = slapi_filter_dup(theView->includeAncestorFiltersFilter); + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(includeAncestorFiltersFilter, includeAncestorFiltersFilter_str, sizeof(includeAncestorFiltersFilter_str)); +#endif + + /* unlock the cache */ + views_unlock(); + + /* rewrite search scope and base*/ + slapi_pblock_set(pb, SLAPI_SEARCH_SCOPE, &set_scope); + + base = slapi_ch_strdup(theView->pSearch_base); + slapi_pblock_set(pb, SLAPI_SEARCH_TARGET, base); + + /* concatenate the filters */ + + /* grab the client filter - we need 2 copies */ + slapi_pblock_get(pb, SLAPI_SEARCH_FILTER, &clientFilter); + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(clientFilter, clientFilter_str, sizeof(clientFilter_str)); +#endif + + /* client supplied filter AND inclusion filter - make sure we can see views */ + if(scope == LDAP_SCOPE_ONELEVEL) + { + Slapi_Filter *clientSeeViewsFilter = 0; /* view filter to see views */ + + clientSeeViewsFilter = slapi_filter_dup(clientFilter); + if(excludeGrandChildViewsFilter) + seeViewsFilter = slapi_filter_join_ex( LDAP_FILTER_AND, excludeGrandChildViewsFilter, clientSeeViewsFilter, 0 ); + else + seeViewsFilter = clientSeeViewsFilter; + } + + /* this filter is to lock our view to the subtree at hand */ + if(seeViewsFilter && includeChildViewsFilter) + seeViewsFilter = slapi_filter_join_ex( LDAP_FILTER_AND, includeChildViewsFilter, seeViewsFilter, 0 ); + else + { + if(includeChildViewsFilter) + seeViewsFilter = includeChildViewsFilter; + } + + /* create target filter */ + if(includeAncestorFiltersFilter) + outFilter = slapi_filter_join_ex( LDAP_FILTER_AND, includeAncestorFiltersFilter, clientFilter, 0 ); + else + outFilter = clientFilter; + + if(scope == LDAP_SCOPE_ONELEVEL) + { + if(excludeChildFiltersFilter) + outFilter = slapi_filter_join_ex( LDAP_FILTER_AND, outFilter, excludeChildFiltersFilter, 0 ); + } + + if(seeViewsFilter) + outFilter = slapi_filter_join_ex( LDAP_FILTER_OR, outFilter, seeViewsFilter, 0 ); + +#ifdef _VIEW_DEBUG_FILTERS + slapi_filter_to_string(outFilter, outFilter_str, sizeof(outFilter_str)); +#endif + + /* make it happen */ + slapi_pblock_set(pb, SLAPI_SEARCH_FILTER, outFilter); + + ret = -2; + +end: + return ret; +} + +/* + * views_cache_backend_state_change() + * -------------------------------- + * This is called when a backend changes state + * We simply signal to rebuild the cache in this case + * + */ +static void views_cache_backend_state_change(void *handle, char *be_name, + int old_be_state, int new_be_state) +{ + /* we will create a thread to do this since + * calling views_cache_create() directly will + * hold up the op + */ + if ((PR_CreateThread (PR_USER_THREAD, + views_cache_act_on_change_thread, + NULL, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL ) + { + slapi_log_error( SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM, + "views_cache_backend_state_change: PR_CreateThread failed\n" ); + } +} + +static void views_cache_act_on_change_thread(void *arg) +{ + views_cache_create(); +} diff --git a/ldap/servers/plugins/views/views.def b/ldap/servers/plugins/views/views.def new file mode 100644 index 00000000..26d28966 --- /dev/null +++ b/ldap/servers/plugins/views/views.def @@ -0,0 +1,10 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +DESCRIPTION 'Netscape Directory Server 7.0 State Change Plugin' +EXPORTS + views_init @2 + plugin_init_debug_level @3 |