/* * 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 */ char sessionid[REPL_SESSION_ID_SIZE]; CSN *opcsn; if ( slapi_op_abandoned(pb) ) { return rc; } get_repl_session_id (pb, sessionid, &opcsn); 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 = SLAPI_PLUGIN_NOOP; /* Must discard this Modification */ slapi_log_error (slapi_log_urp, sessionid, "urp_modify: No such entry\n"); PROFILE_POINT; /* Modify Conflict; Entry Does Not Exist; Discard Modification */ } return rc; }
/* * Return 0 for OK, -1 for Error */ int urp_delete_operation( Slapi_PBlock *pb ) { const Slapi_Entry *deleteentry; CSN *opcsn= NULL; char sessionid[REPL_SESSION_ID_SIZE]; int op_result= 0; int rc = SLAPI_PLUGIN_SUCCESS; /* OK */ if ( slapi_op_abandoned(pb) ) { return rc; } slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &deleteentry); get_repl_session_id (pb, sessionid, &opcsn); if(deleteentry==NULL) /* uniqueid can't be found */ { op_result= LDAP_NO_SUCH_OBJECT; slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); rc = SLAPI_PLUGIN_FAILURE; /* 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 = SLAPI_PLUGIN_NOOP; /* Don't apply the Delete */ slapi_log_error(slapi_log_urp, sessionid, "urp_delete: Entry \"%s\" is already a Tombstone.\n", slapi_entry_get_dn_const(deleteentry)); 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 = SLAPI_PLUGIN_SUCCESS; /* 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 = SLAPI_PLUGIN_NOOP; /* Don't apply the Delete */ slapi_log_error(slapi_log_urp, sessionid, "urp_delete: Turn entry \"%s\" into a glue_absent_parent entry.\n", slapi_entry_get_dn_const(deleteentry)); PROFILE_POINT; /* Delete Operation; Entry has children. */ } } 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; Slapi_DN *parentdn = NULL; const 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 Slapi_DN *target_sdn; const Slapi_DN *existing_sdn; 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; */ /* * This operation won't be replayed. That is, this CSN won't update * the max csn in RUV. The CSN is left uncommitted in RUV unless an * error is set to op_result. Just to get rid of this CSN from RUV, * setting an error to op_result */ slapi_log_error(slapi_log_urp, sessionid, "urp_modrdn (%s): operation CSN is newer than the DN CSN.\n", slapi_entry_get_dn_const(target_entry)); op_result= LDAP_UNWILLING_TO_PERFORM; slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); rc = SLAPI_PLUGIN_NOOP; /* 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_sdn = slapi_entry_get_sdn_const (target_entry); /* newrdn is no need to be case-ignored (get_rdn_plus_uniqueid) */ 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_SDN, &newsuperior); 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, slapi_ch_strdup(op_uniqueid)); rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_TARGET_ENTRY); rc = 0; } else { slapi_log_error(slapi_log_urp, sessionid, "urp_modrdn (%s): target entry is a tombstone.\n", slapi_entry_get_dn_const(target_entry)); rc = SLAPI_PLUGIN_NOOP; /* Ignore the modrdn */ } 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_sdn = slapi_entry_get_sdn_const ( existing_entry); /* * It used to dismiss the operation if the existing entry is * the same as the target one. * But renaming the RDN with the one which only cases are different, * cn=ABC --> cn=Abc, this case matches. We should go forward the op. */ if (strcmp(op_uniqueid, existing_uniqueid) == 0) { op_result= LDAP_SUCCESS; slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); rc = 0; /* Don't 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_urp, sessionid, "urp_modrdn: 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 = SLAPI_PLUGIN_NOOP; /* Ignore this Operation */ 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_sdn, existing_sdn, opcsn); slapi_pblock_set(pb, SLAPI_MODRDN_NEWRDN, newrdn_with_uniqueid); slapi_log_error(slapi_log_urp, sessionid, "urp_modrdn: 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_SDN, &newsuperior); if(newsuperior == NULL) { /* (new_parent_entry==NULL && newsuperiordn==NULL) * This is ok - SLAPI_MODRDN_NEWPARENT_ENTRY will * only be set if SLAPI_MODRDN_NEWSUPERIOR_SDN 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; } 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, "urp_modrdn: Parent %s couldn't be found, nor recreated as a glue entry\n", slapi_sdn_get_dn(newsuperior) ); op_result= LDAP_OPERATIONS_ERROR; slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); rc = SLAPI_PLUGIN_FAILURE; /* Ignore this Operation */ 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); 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 */ Slapi_DN *sdn = NULL; if ( slapi_op_abandoned(pb) ) { return rc; } get_repl_session_id (pb, sessionid, &opcsn); 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 */ /* * This operation won't be replayed. That is, this CSN won't update * the max csn in RUV. The CSN is left uncommitted in RUV unless an * error is set to op_result. Just to get rid of this CSN from RUV, * setting an error to op_result */ /* op_result = LDAP_SUCCESS; */ slapi_log_error(slapi_log_urp, sessionid, "urp_add (%s): an entry with this uniqueid already exists.\n", slapi_entry_get_dn_const(existing_uniqueid_entry)); op_result= LDAP_UNWILLING_TO_PERFORM; slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); rc = SLAPI_PLUGIN_NOOP; /* Ignore this Operation */ PROFILE_POINT; /* Add Conflict; UniqueID Exists; Ignore */ goto bailout; } 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 = SLAPI_PLUGIN_NOOP; /* Abort this Operation */ slapi_log_error(slapi_log_urp, sessionid, "urp_add (%s): Add conflict; Unique ID (%s) already in RDN\n", basedn, adduniqueid); 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]; PR_snprintf(buf, BUFSIZ, "%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, "urp_add: 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_pblock_get(pb, SLAPI_ADD_TARGET, &dn); */ slapi_pblock_get(pb, SLAPI_ADD_TARGET_SDN, &sdn); slapi_sdn_free(&sdn); slapi_entry_set_normdn(addentry, newdn); /* dn: passin */ sdn = slapi_sdn_dup(slapi_entry_get_sdn_const(addentry)); slapi_pblock_set(pb, SLAPI_ADD_TARGET_SDN, sdn); rdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn_const(addentry) ); slapi_log_error (slapi_log_urp, sessionid, "urp_add: 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); slapi_log_error(slapi_log_urp, sessionid, "urp_add (%s): Entry to be added is a loser; " "urp_annotate_dn failed.\n", basedn); rc = SLAPI_PLUGIN_NOOP; /* 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. */ /* * This operation won't be replayed. That is, this CSN won't update * the max csn in RUV. The CSN is left uncommitted in RUV unless an * error is set to op_result. Just to get rid of this CSN from RUV, * setting an error to op_result */ /* op_result = LDAP_SUCCESS; */ slapi_log_error(slapi_log_urp, sessionid, "urp_add (%s): The CSN of the Operation and the Entry DN are the same.", slapi_entry_get_dn_const(existing_dn_entry)); op_result= LDAP_UNWILLING_TO_PERFORM; slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); rc = SLAPI_PLUGIN_NOOP; /* Ignore this Operation */ PROFILE_POINT; /* Add Conflict; Entry Exists; Same CSN */ } bailout: return rc; }
/** Apply the mods to the ec entry. Check for syntax, schema problems. Check for abandon. Return code: -1 - error - result code and message are set appropriately 0 - successfully applied and checked 1 - not an error - no mods to apply or op abandoned */ static int modify_apply_check_expand( Slapi_PBlock *pb, Slapi_Operation *operation, LDAPMod **mods, /* list of mods to apply */ struct backentry *e, /* original "before" entry */ struct backentry *ec, /* "after" entry with mods applied */ Slapi_Entry **postentry, int *ldap_result_code, char **ldap_result_message ) { int rc = 0; int i; int repl_op; int change_entry = 0; Slapi_Mods smods = {0}; CSN *csn = operation_get_csn(operation); slapi_pblock_get (pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op); slapi_mods_init_byref( &smods, mods ); if ( (change_entry = mods_have_effect (ec->ep_entry, &smods)) ) { *ldap_result_code = entry_apply_mods_wsi(ec->ep_entry, &smods, csn, operation_is_flag_set(operation, OP_FLAG_REPLICATED)); /* * XXXmcs: it would be nice to get back an error message from * the above call so we could pass it along to the client, e.g., * "duplicate value for attribute givenName." */ } else { Slapi_Entry *epostop = NULL; /* If the entry was not actually changed, we still need to * set the SLAPI_ENTRY_POST_OP field in the pblock (post-op * plugins expect that field to be present for all modify * operations that return LDAP_SUCCESS). */ slapi_pblock_get ( pb, SLAPI_ENTRY_POST_OP, &epostop ); slapi_entry_free ( epostop ); /* free existing one, if any */ slapi_pblock_set ( pb, SLAPI_ENTRY_POST_OP, slapi_entry_dup( e->ep_entry ) ); *postentry = NULL; /* to avoid free in main error cleanup code */ } if ( !change_entry || *ldap_result_code != 0 ) { /* change_entry == 0 is not an error just a no-op */ rc = change_entry ? -1 : 1; goto done; } /* * If the objectClass attribute type was modified in any way, expand * the objectClass values to reflect the inheritance hierarchy. */ for ( i = 0; mods && mods[i]; ++i ) { if ( 0 == strcasecmp( SLAPI_ATTR_OBJECTCLASS, mods[i]->mod_type )) { slapi_schema_expand_objectclasses( ec->ep_entry ); break; } } /* * We are about to pass the last abandon test, so from now on we are * committed to finish this operation. Set status to "will complete" * before we make our last abandon check to avoid race conditions in * the code that processes abandon operations. */ operation->o_status = SLAPI_OP_STATUS_WILL_COMPLETE; if ( slapi_op_abandoned( pb ) ) { rc = 1; goto done; } /* multimaster replication can result in a schema violation, * although the individual operations on each master were valid * It is too late to resolve this. But we can check schema and * add a replication conflict attribute. */ /* check that the entry still obeys the schema */ if ((operation_is_flag_set(operation,OP_FLAG_ACTION_SCHEMA_CHECK)) && slapi_entry_schema_check_ext( pb, ec->ep_entry, 1 ) != 0 ) { if(repl_op){ Slapi_Attr *attr; Slapi_Mods smods; LDAPMod **lmods; if (slapi_entry_attr_find (ec->ep_entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) { /* add value */ Slapi_Value *val = slapi_value_new_string("Schema violation"); slapi_attr_add_value(attr,val); slapi_value_free(&val); } else { /* Add new attribute */ slapi_entry_add_string (ec->ep_entry, ATTR_NSDS5_REPLCONFLICT, "Schema violation"); } /* the replconflict attribute is indexed and the index is built from the mods, * so we need to extend the mods */ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &lmods); slapi_mods_init_passin(&smods, lmods); slapi_mods_add_string (&smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, "Schema violation"); lmods = slapi_mods_get_ldapmods_passout(&smods); slapi_pblock_set(pb, SLAPI_MODIFY_MODS, lmods); slapi_mods_done(&smods); } else { *ldap_result_code = LDAP_OBJECT_CLASS_VIOLATION; slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, ldap_result_message); rc = -1; goto done; } } if(!repl_op){ /* check attribute syntax for the new values */ if (slapi_mods_syntax_check(pb, mods, 0) != 0) { *ldap_result_code = LDAP_INVALID_SYNTAX; slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, ldap_result_message); rc = -1; goto done; } /* * make sure the entry contains all values in the RDN. * if not, the modification must have removed them. */ if ( ! slapi_entry_rdn_values_present( ec->ep_entry ) ) { *ldap_result_code= LDAP_NOT_ALLOWED_ON_RDN; rc = -1; goto done; } } done: slapi_mods_done( &smods ); return rc; }
/* * Check if there are any persistent searches. If so, * the check to see if the chgtype is one of those the * client is interested in. If so, then check to see if * the entry matches any of the filters the searches. * If so, then enqueue the entry on that persistent search's * ps_entryqueue and signal it to wake up and send the entry. * * Note that if eprev is NULL we assume that the entry's DN * was not changed by the op. that called this function. If * chgnum is 0 it is unknown so we won't ever send it to a * client in the EntryChangeNotification control. */ void ps_service_persistent_searches( Slapi_Entry *e, Slapi_Entry *eprev, ber_int_t chgtype, ber_int_t chgnum ) { LDAPControl *ctrl = NULL; PSearch *ps = NULL; PSEQNode *pe = NULL; int matched = 0; const char *edn; if ( !PS_IS_INITIALIZED()) { return; } if ( NULL == e ) { /* For now, some backends such as the chaining backend do not provide a post-op entry */ return; } PSL_LOCK_READ(); edn = slapi_entry_get_dn_const(e); for ( ps = psearch_list ? psearch_list->pl_head : NULL; NULL != ps; ps = ps->ps_next ) { char *origbase = NULL; Slapi_DN *base = NULL; Slapi_Filter *f; int scope; /* Skip the node that doesn't meet the changetype, * or is unable to use the change in ps_send_results() */ if (( ps->ps_changetypes & chgtype ) == 0 || ps->ps_pblock->pb_op == NULL || slapi_op_abandoned( ps->ps_pblock ) ) { continue; } slapi_log_error(SLAPI_LOG_CONNS, "Persistent Search", "conn=%" NSPRIu64 " op=%d entry %s with chgtype %d " "matches the ps changetype %d\n", ps->ps_pblock->pb_conn->c_connid, ps->ps_pblock->pb_op->o_opid, edn, chgtype, ps->ps_changetypes); slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_FILTER, &f ); slapi_pblock_get( ps->ps_pblock, SLAPI_ORIGINAL_TARGET_DN, &origbase ); slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_TARGET_SDN, &base ); slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_SCOPE, &scope ); if (NULL == base) { base = slapi_sdn_new_dn_byref(origbase); slapi_pblock_set(ps->ps_pblock, SLAPI_SEARCH_TARGET_SDN, base); } /* * See if the entry meets the scope and filter criteria. * We cannot do the acl check here as this thread * would then potentially clash with the ps_send_results() * thread on the aclpb in ps->ps_pblock. * By avoiding the acl check in this thread, and leaving all the acl * checking to the ps_send_results() thread we avoid * the ps_pblock contention problem. * The lesson here is "Do not give multiple threads arbitary access * to the same pblock" this kind of muti-threaded access * to the same pblock must be done carefully--there is currently no * generic satisfactory way to do this. */ if ( slapi_sdn_scope_test( slapi_entry_get_sdn_const(e), base, scope ) && slapi_vattr_filter_test( ps->ps_pblock, e, f, 0 /* verify_access */ ) == 0 ) { PSEQNode *pOldtail; /* The scope and the filter match - enqueue it */ matched++; pe = (PSEQNode *)slapi_ch_calloc( 1, sizeof( PSEQNode )); pe->pe_entry = slapi_entry_dup( e ); if ( ps->ps_send_entchg_controls ) { /* create_entrychange_control() is more * expensive than slapi_dup_control() */ if ( ctrl == NULL ) { int rc; rc = create_entrychange_control( chgtype, chgnum, eprev ? slapi_entry_get_dn_const(eprev) : NULL, &ctrl ); if ( rc != LDAP_SUCCESS ) { LDAPDebug( LDAP_DEBUG_ANY, "ps_service_persistent_searches:" " unable to create EntryChangeNotification control for" " entry \"%s\" -- control won't be sent.\n", slapi_entry_get_dn_const(e), 0, 0 ); } } if ( ctrl ) { pe->pe_ctrls[0] = slapi_dup_control( ctrl ); } } /* Put it on the end of the list for this pers search */ PR_Lock( ps->ps_lock ); pOldtail = ps->ps_eq_tail; ps->ps_eq_tail = pe; if ( NULL == ps->ps_eq_head ) { ps->ps_eq_head = ps->ps_eq_tail; } else { pOldtail->pe_next = ps->ps_eq_tail; } PR_Unlock( ps->ps_lock ); } } PSL_UNLOCK_READ(); /* Were there any matches? */ if ( matched ) { ldap_control_free( ctrl ); /* Turn 'em loose */ ps_wakeup_all(); LDAPDebug( LDAP_DEBUG_TRACE, "ps_service_persistent_searches: enqueued entry " "\"%s\" on %d persistent search lists\n", slapi_entry_get_dn_const(e), matched, 0 ); } else { LDAPDebug( LDAP_DEBUG_TRACE, "ps_service_persistent_searches: entry " "\"%s\" not enqueued on any persistent search lists\n", slapi_entry_get_dn_const(e), 0, 0 ); } }
/* * Thread routine for sending search results to a client * which is persistently waiting for them. * * This routine will terminate when either (a) the ps_complete * flag is set, or (b) the associated operation is abandoned. * In any case, the thread won't notice until it wakes from * sleeping on the ps_list condition variable, so it needs * to be awakened. */ static void ps_send_results( void *arg ) { PSearch *ps = (PSearch *)arg; PSEQNode *peq, *peqnext; struct slapi_filter *filter = 0; char *base = NULL; Slapi_DN *sdn = NULL; char *fstr = NULL; char **pbattrs = NULL; int conn_acq_flag = 0; g_incr_active_threadcnt(); /* need to acquire a reference to this connection so that it will not be released or cleaned up out from under us */ PR_Lock( ps->ps_pblock->pb_conn->c_mutex ); conn_acq_flag = connection_acquire_nolock(ps->ps_pblock->pb_conn); PR_Unlock( ps->ps_pblock->pb_conn->c_mutex ); if (conn_acq_flag) { slapi_log_error(SLAPI_LOG_CONNS, "Persistent Search", "conn=%" NSPRIu64 " op=%d Could not acquire the connection - psearch aborted\n", ps->ps_pblock->pb_conn->c_connid, ps->ps_pblock->pb_op->o_opid); } PR_Lock( psearch_list->pl_cvarlock ); while ( (conn_acq_flag == 0) && !ps->ps_complete ) { /* Check for an abandoned operation */ if ( ps->ps_pblock->pb_op == NULL || slapi_op_abandoned( ps->ps_pblock ) ) { slapi_log_error(SLAPI_LOG_CONNS, "Persistent Search", "conn=%" NSPRIu64 " op=%d The operation has been abandoned\n", ps->ps_pblock->pb_conn->c_connid, ps->ps_pblock->pb_op->o_opid); break; } if ( NULL == ps->ps_eq_head ) { /* Nothing to do */ PR_WaitCondVar( psearch_list->pl_cvar, PR_INTERVAL_NO_TIMEOUT ); } else { /* dequeue the item */ int attrsonly; char **attrs; LDAPControl **ectrls; Slapi_Entry *ec; Slapi_Filter *f = NULL; PR_Lock( ps->ps_lock ); peq = ps->ps_eq_head; ps->ps_eq_head = peq->pe_next; if ( NULL == ps->ps_eq_head ) { ps->ps_eq_tail = NULL; } PR_Unlock( ps->ps_lock ); /* Get all the information we need to send the result */ ec = peq->pe_entry; slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_ATTRS, &attrs ); slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_ATTRSONLY, &attrsonly ); if ( !ps->ps_send_entchg_controls || peq->pe_ctrls[0] == NULL ) { ectrls = NULL; } else { ectrls = peq->pe_ctrls; } /* * Send the result. Since send_ldap_search_entry can block for * up to 30 minutes, we relinquish all locks before calling it. */ PR_Unlock(psearch_list->pl_cvarlock); /* * The entry is in the right scope and matches the filter * but we need to redo the filter test here to check access * controls. See the comments at the slapi_filter_test() * call in ps_service_persistent_searches(). */ slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_FILTER, &f ); /* See if the entry meets the filter and ACL criteria */ if ( slapi_vattr_filter_test( ps->ps_pblock, ec, f, 1 /* verify_access */ ) == 0 ) { int rc = 0; slapi_pblock_set( ps->ps_pblock, SLAPI_SEARCH_RESULT_ENTRY, ec ); rc = send_ldap_search_entry( ps->ps_pblock, ec, ectrls, attrs, attrsonly ); if (rc) { slapi_log_error(SLAPI_LOG_CONNS, "Persistent Search", "conn=%" NSPRIu64 " op=%d Error %d sending entry %s with op status %d\n", ps->ps_pblock->pb_conn->c_connid, ps->ps_pblock->pb_op->o_opid, rc, slapi_entry_get_dn_const(ec), ps->ps_pblock->pb_op->o_status); } } PR_Lock(psearch_list->pl_cvarlock); /* Deallocate our wrapper for this entry */ pe_ch_free( &peq ); } } PR_Unlock( psearch_list->pl_cvarlock ); ps_remove( ps ); /* indicate the end of search */ plugin_call_plugins( ps->ps_pblock , SLAPI_PLUGIN_POST_SEARCH_FN ); /* free things from the pblock that were not free'd in do_search() */ /* we strdup'd this in search.c - need to free */ slapi_pblock_get( ps->ps_pblock, SLAPI_ORIGINAL_TARGET_DN, &base ); slapi_pblock_set( ps->ps_pblock, SLAPI_ORIGINAL_TARGET_DN, NULL ); slapi_ch_free_string(&base); /* Free SLAPI_SEARCH_* before deleting op since those are held by op */ slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_TARGET_SDN, &sdn ); slapi_pblock_set( ps->ps_pblock, SLAPI_SEARCH_TARGET_SDN, NULL ); slapi_sdn_free(&sdn); slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_STRFILTER, &fstr ); slapi_pblock_set( ps->ps_pblock, SLAPI_SEARCH_STRFILTER, NULL ); slapi_ch_free_string(&fstr); slapi_pblock_get( ps->ps_pblock, SLAPI_SEARCH_ATTRS, &pbattrs ); slapi_pblock_set( ps->ps_pblock, SLAPI_SEARCH_ATTRS, NULL ); if ( pbattrs != NULL ) { charray_free( pbattrs ); } slapi_pblock_get(ps->ps_pblock, SLAPI_SEARCH_FILTER, &filter ); slapi_pblock_set(ps->ps_pblock, SLAPI_SEARCH_FILTER, NULL ); slapi_filter_free(filter, 1); /* Clean up the connection structure */ PR_Lock( ps->ps_pblock->pb_conn->c_mutex ); slapi_log_error(SLAPI_LOG_CONNS, "Persistent Search", "conn=%" NSPRIu64 " op=%d Releasing the connection and operation\n", ps->ps_pblock->pb_conn->c_connid, ps->ps_pblock->pb_op->o_opid); /* Delete this op from the connection's list */ connection_remove_operation( ps->ps_pblock->pb_conn, ps->ps_pblock->pb_op ); operation_free(&(ps->ps_pblock->pb_op),ps->ps_pblock->pb_conn); ps->ps_pblock->pb_op=NULL; /* Decrement the connection refcnt */ if (conn_acq_flag == 0) { /* we acquired it, so release it */ connection_release_nolock (ps->ps_pblock->pb_conn); } PR_Unlock( ps->ps_pblock->pb_conn->c_mutex ); PR_DestroyLock ( ps->ps_lock ); ps->ps_lock = NULL; slapi_ch_free((void **) &ps->ps_pblock ); for ( peq = ps->ps_eq_head; peq; peq = peqnext) { peqnext = peq->pe_next; pe_ch_free( &peq ); } slapi_ch_free((void **) &ps ); g_decr_active_threadcnt(); }