diff options
| -rw-r--r-- | source4/dsdb/samdb/ldb_modules/linked_attributes.c | 388 | 
1 files changed, 281 insertions, 107 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/linked_attributes.c b/source4/dsdb/samdb/ldb_modules/linked_attributes.c index d83f6b9269..561fc66941 100644 --- a/source4/dsdb/samdb/ldb_modules/linked_attributes.c +++ b/source4/dsdb/samdb/ldb_modules/linked_attributes.c @@ -33,6 +33,10 @@  #include "dlinklist.h"  #include "dsdb/samdb/samdb.h" +struct la_private { +	struct la_context *la_list; +}; +  struct la_op_store {  	struct la_op_store *next;  	struct la_op_store *prev; @@ -49,9 +53,11 @@ struct replace_context {  };  struct la_context { +	struct la_context *next, *prev;  	const struct dsdb_schema *schema;  	struct ldb_module *module;  	struct ldb_request *req; +	struct ldb_dn *partition_dn;  	struct ldb_dn *add_dn;  	struct ldb_dn *del_dn;  	struct replace_context *rc; @@ -65,6 +71,7 @@ static struct la_context *linked_attributes_init(struct ldb_module *module,  {  	struct ldb_context *ldb;  	struct la_context *ac; +	const struct ldb_control *partition_ctrl;  	ldb = ldb_module_get_ctx(module); @@ -78,6 +85,17 @@ static struct la_context *linked_attributes_init(struct ldb_module *module,  	ac->module = module;  	ac->req = req; +	/* remember the partition DN that came in, if given */ +	partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID); +	if (partition_ctrl) { +		const struct dsdb_control_current_partition *partition; +		partition = talloc_get_type(partition_ctrl->data, +					    struct dsdb_control_current_partition); +		SMB_ASSERT(partition && partition->version == DSDB_CONTROL_CURRENT_PARTITION_VERSION); +	 +		ac->partition_dn = ldb_dn_copy(ac, partition->dn); +	} +  	return ac;  } @@ -130,9 +148,7 @@ static int la_store_op(struct la_context *ac,  static int la_op_search_callback(struct ldb_request *req,  				 struct ldb_reply *ares); -static int la_do_mod_request(struct la_context *ac); -static int la_mod_callback(struct ldb_request *req, -			   struct ldb_reply *ares); +static int la_queue_mod_request(struct la_context *ac);  static int la_down_req(struct la_context *ac); @@ -328,7 +344,7 @@ static int la_mod_search_callback(struct ldb_request *req, struct ldb_reply *are  		if (ac->req->operation == LDB_ADD) {  			/* Start the modifies to the backlinks */ -			ret = la_do_mod_request(ac); +			ret = la_queue_mod_request(ac);  			if (ret != LDB_SUCCESS) {  				return ldb_module_done(ac->req, NULL, NULL, @@ -745,7 +761,7 @@ static int la_op_search_callback(struct ldb_request *req,  		case LDB_RENAME:	  			/* start the mod requests chain */ -			ret = la_do_mod_request(ac); +			ret = la_queue_mod_request(ac);  			if (ret != LDB_SUCCESS) {  				return ldb_module_done(ac->req, NULL, NULL,  						       ret); @@ -765,112 +781,23 @@ static int la_op_search_callback(struct ldb_request *req,  	return LDB_SUCCESS;  } -/* do a linked attributes modify request */ -static int la_do_mod_request(struct la_context *ac) +/* queue a linked attributes modify request in the la_private +   structure */ +static int la_queue_mod_request(struct la_context *ac)  { -	struct ldb_message_element *ret_el; -	struct ldb_request *mod_req; -	struct ldb_message *new_msg; -	struct ldb_context *ldb; -	int ret; - -	/* If we have no modifies in the queue, we are done! */ -	if (!ac->ops) { -		return ldb_module_done(ac->req, ac->op_controls, -				       ac->op_response, LDB_SUCCESS); -	} +	struct la_private *la_private =  +		talloc_get_type(ldb_module_get_private(ac->module), struct la_private); -	ldb = ldb_module_get_ctx(ac->module); - -	/* Create the modify request */ -	new_msg = ldb_msg_new(ac); -	if (!new_msg) { -		ldb_oom(ldb); -		return LDB_ERR_OPERATIONS_ERROR; -	} -	new_msg->dn = ac->ops->dn; - -	if (ac->ops->op == LA_OP_ADD) { -		ret = ldb_msg_add_empty(new_msg, ac->ops->name, -					LDB_FLAG_MOD_ADD, &ret_el); -	} else { -		ret = ldb_msg_add_empty(new_msg, ac->ops->name, -					LDB_FLAG_MOD_DELETE, &ret_el); -	} -	if (ret != LDB_SUCCESS) { -		return ret; -	} -	ret_el->values = talloc_array(new_msg, struct ldb_val, 1); -	if (!ret_el->values) { -		ldb_oom(ldb); +	if (la_private == NULL) { +		ldb_debug(ldb_module_get_ctx(ac->module), LDB_DEBUG_ERROR, __location__ ": No la_private transaction setup\n");  		return LDB_ERR_OPERATIONS_ERROR;  	} -	ret_el->num_values = 1; -	if (ac->ops->op == LA_OP_ADD) { -		ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->add_dn, 1)); -	} else { -		ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->del_dn, 1)); -	} -#if 0 -	ldb_debug(ldb, LDB_DEBUG_WARNING, -		  "link on %s %s: %s %s\n",  -		  ldb_dn_get_linearized(new_msg->dn), ret_el->name,  -		  ret_el->values[0].data, ac->ops->op == LA_OP_ADD ? "added" : "deleted"); -#endif	 +	talloc_steal(la_private, ac); +	DLIST_ADD(la_private->la_list, ac); -	/* use ac->ops as the mem_ctx so that the request will be freed -	 * in the callback as soon as completed */ -	ret = ldb_build_mod_req(&mod_req, ldb, ac->ops, -				new_msg, -				NULL, -				ac, la_mod_callback, -				ac->req); -	if (ret != LDB_SUCCESS) { -		return ret; -	} -	talloc_steal(mod_req, new_msg); - -	/* Run the new request */ -	return ldb_next_request(ac->module, mod_req); -} - -static int la_mod_callback(struct ldb_request *req, struct ldb_reply *ares) -{ -	struct la_context *ac; -	struct ldb_context *ldb; -	struct la_op_store *os; - -	ac = talloc_get_type(req->context, struct la_context); -	ldb = ldb_module_get_ctx(ac->module); - -	if (!ares) { -		return ldb_module_done(ac->req, NULL, NULL, -					LDB_ERR_OPERATIONS_ERROR); -	} -	if (ares->error != LDB_SUCCESS) { -		return ldb_module_done(ac->req, ares->controls, -					ares->response, ares->error); -	} - -	if (ares->type != LDB_REPLY_DONE) { -		ldb_set_errstring(ldb, -				  "invalid ldb_reply_type in callback"); -		talloc_free(ares); -		return ldb_module_done(ac->req, NULL, NULL, -					LDB_ERR_OPERATIONS_ERROR); -	} - -	talloc_free(ares); - -	os = ac->ops; -	DLIST_REMOVE(ac->ops, os); - -	/* this frees the request too -	 * DO NOT access 'req' after this point */ -	talloc_free(os); - -	return la_do_mod_request(ac); +	return ldb_module_done(ac->req, ac->op_controls, +			       ac->op_response, LDB_SUCCESS);  }  /* Having done the original operation, then try to fix up all the linked attributes for modify and delete */ @@ -904,14 +831,14 @@ static int la_mod_del_callback(struct ldb_request *req, struct ldb_reply *ares)  	ac->op_response = talloc_steal(ac, ares->response);  	/* If we have modfies to make, this is the time to do them for modify and delete */ -	ret = la_do_mod_request(ac); +	ret = la_queue_mod_request(ac);  	if (ret != LDB_SUCCESS) {  		return ldb_module_done(ac->req, NULL, NULL, ret);  	}  	talloc_free(ares); -	/* la_do_mod_request has already sent the callbacks */ +	/* la_queue_mod_request has already sent the callbacks */  	return LDB_SUCCESS;  } @@ -1094,6 +1021,250 @@ static int la_down_req(struct la_context *ac)  	return ldb_next_request(ac->module, down_req);  } +/* +  use the GUID part of an extended DN to find the target DN, in case +  it has moved + */ +static int la_find_dn_target(struct ldb_module *module, struct la_context *ac, struct ldb_dn **dn) +{ +	const struct ldb_val *guid; +	struct ldb_context *ldb; +	int ret; +	struct ldb_result *res; +	const char *attrs[] = { NULL }; +	struct ldb_request *search_req; +	char *expression; +	struct ldb_search_options_control *options; + +	ldb = ldb_module_get_ctx(ac->module); + +	guid = ldb_dn_get_extended_component(*dn, "GUID"); +	if (guid == NULL) { +		return LDB_SUCCESS; +	} + +	expression = talloc_asprintf(ac, "objectGUID=%s", ldb_binary_encode(ac, *guid)); +	if (!expression) { +		ldb_oom(ldb); +		return LDB_ERR_OPERATIONS_ERROR; +	} + +	res = talloc_zero(ac, struct ldb_result); +	if (!res) { +		ldb_oom(ldb); +		return LDB_ERR_OPERATIONS_ERROR; +	} + +	ret = ldb_build_search_req(&search_req, ldb, ac, +				   ldb_get_default_basedn(ldb), +				   LDB_SCOPE_SUBTREE, +				   expression, attrs, +				   NULL, +				   res, ldb_search_default_callback, +				   NULL); +	if (ret != LDB_SUCCESS) { +		return ret; +	} + +	/* we need to cope with cross-partition links, so search for +	   the GUID over all partitions */ +	options = talloc(search_req, struct ldb_search_options_control); +	if (options == NULL) { +		ldb_oom(ldb); +		return LDB_ERR_OPERATIONS_ERROR; +	} +	options->search_options = LDB_SEARCH_OPTION_PHANTOM_ROOT; + +	ret = ldb_request_add_control(search_req, +				      LDB_CONTROL_SEARCH_OPTIONS_OID, +				      true, options); +	if (ret != LDB_SUCCESS) { +		return ret; +	} + +	ret = ldb_next_request(module, search_req); +	if (ret != LDB_SUCCESS) { +		return ret; +	} + +	ret = ldb_wait(search_req->handle, LDB_WAIT_ALL); +	if (ret != LDB_SUCCESS) { +		ldb_debug(ldb, LDB_DEBUG_ERROR, "GUID search failed (%s) for %s\n",  +			  ldb_errstring(ldb), ldb_dn_get_extended_linearized(ac, *dn, 1)); +		return ret; +	} + +	/* this really should be exactly 1, but there is a bug in the +	   partitions module that can return two here with the +	   search_options control set */ +	if (res->count < 1) { +		ldb_debug(ldb, LDB_DEBUG_ERROR, "GUID search gave count=%d for %s\n",  +			  res->count, ldb_dn_get_extended_linearized(ac, *dn, 1)); +		return LDB_ERR_OPERATIONS_ERROR; +	} + +	*dn = res->msgs[0]->dn; + +	return LDB_SUCCESS; +} + +/* apply one la_context op change */ +static int la_do_op_request(struct ldb_module *module, struct la_context *ac, struct la_op_store *op) +{ +	struct ldb_message_element *ret_el; +	struct ldb_request *mod_req; +	struct ldb_message *new_msg; +	struct ldb_context *ldb; +	int ret; + +	ldb = ldb_module_get_ctx(ac->module); + +	/* Create the modify request */ +	new_msg = ldb_msg_new(ac); +	if (!new_msg) { +		ldb_oom(ldb); +		return LDB_ERR_OPERATIONS_ERROR; +	} + +	new_msg->dn = op->dn; +	ret = la_find_dn_target(module, ac, &new_msg->dn); +	if (ret != LDB_SUCCESS) { +		return ret; +	} + +	if (op->op == LA_OP_ADD) { +		ret = ldb_msg_add_empty(new_msg, op->name, +					LDB_FLAG_MOD_ADD, &ret_el); +	} else { +		ret = ldb_msg_add_empty(new_msg, op->name, +					LDB_FLAG_MOD_DELETE, &ret_el); +	} +	if (ret != LDB_SUCCESS) { +		return ret; +	} +	ret_el->values = talloc_array(new_msg, struct ldb_val, 1); +	if (!ret_el->values) { +		ldb_oom(ldb); +		return LDB_ERR_OPERATIONS_ERROR; +	} +	ret_el->num_values = 1; +	if (op->op == LA_OP_ADD) { +		ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->add_dn, 1)); +	} else { +		ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->del_dn, 1)); +	} + +#if 0 +	ldb_debug(ldb, LDB_DEBUG_WARNING, +		  "link on %s %s: %s %s\n",  +		  ldb_dn_get_linearized(new_msg->dn), ret_el->name,  +		  ret_el->values[0].data, ac->ops->op == LA_OP_ADD ? "added" : "deleted"); +#endif	 + +	ret = ldb_build_mod_req(&mod_req, ldb, op, +				new_msg, +				NULL, +				NULL,  +				ldb_op_default_callback, +				NULL); +	if (ret != LDB_SUCCESS) { +		return ret; +	} +	talloc_steal(mod_req, new_msg); + +	if (DEBUGLVL(4)) { +		DEBUG(4,("Applying linked attribute change:\n%s\n", +			 ldb_ldif_message_string(ldb, op, LDB_CHANGETYPE_MODIFY, new_msg))); +	} + +	/* Run the new request */ +	ret = ldb_next_request(module, mod_req); + +	/* we need to wait for this to finish, as we are being called +	   from the synchronous end_transaction hook of this module */ +	if (ret == LDB_SUCCESS) { +		ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL); +	} + +	if (ret != LDB_SUCCESS) { +		ldb_debug(ldb, LDB_DEBUG_WARNING, "Failed to apply linked attribute change '%s' %s\n", +			  ldb_errstring(ldb), +			  ldb_ldif_message_string(ldb, op, LDB_CHANGETYPE_MODIFY, new_msg)); +	} + +	return ret; +} + +/* apply one set of la_context changes */ +static int la_do_mod_request(struct ldb_module *module, struct la_context *ac) +{ +	struct la_op_store *op; + +	for (op = ac->ops; op; op=op->next) { +		int ret = la_do_op_request(module, ac, op); +		if (ret != LDB_SUCCESS) { +			return ret; +		} +	} + +	return LDB_SUCCESS; +} + + +/* +  we hook into the transaction operations to allow us to  +  perform the linked attribute updates at the end of the whole +  transaction. This allows a forward linked attribute to be created +  before the target is created, as long as the target is created +  in the same transaction + */ +static int linked_attributes_start_transaction(struct ldb_module *module) +{ +	/* create our private structure for this transaction */ +	struct la_private *la_private = talloc_get_type(ldb_module_get_private(module), +							struct la_private); +	talloc_free(la_private); +	la_private = talloc(module, struct la_private); +	if (la_private == NULL) { +		return LDB_ERR_OPERATIONS_ERROR; +	} +	la_private->la_list = NULL; +	ldb_module_set_private(module, la_private); +	return LDB_SUCCESS; +} + +/* +  on end transaction we loop over our queued la_context structures and +  apply each of them   + */ +static int linked_attributes_end_transaction(struct ldb_module *module) +{ +	struct la_private *la_private =  +		talloc_get_type(ldb_module_get_private(module), struct la_private); +	struct la_context *ac; + +	for (ac=la_private->la_list; ac; ac=ac->next) { +		int ret; +		ac->req = NULL; +		ret = la_do_mod_request(module, ac); +		if (ret != LDB_SUCCESS) { +			ret = la_do_mod_request(module, ac); +			return ret; +		} +	} +	 +	return LDB_SUCCESS; +} + +static int linked_attributes_del_transaction(struct ldb_module *module) +{ +	struct la_private *la_private =  +		talloc_get_type(ldb_module_get_private(module), struct la_private); +	talloc_free(la_private); +	ldb_module_set_private(module, NULL); +	return LDB_SUCCESS; +} +  _PUBLIC_ const struct ldb_module_ops ldb_linked_attributes_module_ops = {  	.name		   = "linked_attributes", @@ -1101,4 +1272,7 @@ _PUBLIC_ const struct ldb_module_ops ldb_linked_attributes_module_ops = {  	.modify            = linked_attributes_modify,  	.del               = linked_attributes_del,  	.rename            = linked_attributes_rename, +	.start_transaction = linked_attributes_start_transaction, +	.end_transaction   = linked_attributes_end_transaction, +	.del_transaction   = linked_attributes_del_transaction,  };  | 
