summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules/extended_dn_out.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/extended_dn_out.c')
-rw-r--r--source4/dsdb/samdb/ldb_modules/extended_dn_out.c672
1 files changed, 672 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_out.c b/source4/dsdb/samdb/ldb_modules/extended_dn_out.c
new file mode 100644
index 0000000..a949bfb
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/extended_dn_out.c
@@ -0,0 +1,672 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007-2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb extended dn control module
+ *
+ * Description: this module builds a special dn for returned search
+ * results, and fixes some other aspects of the result (returned case issues)
+ * values.
+ *
+ * Authors: Simo Sorce
+ * Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+#undef strncasecmp
+
+struct extended_dn_out_private {
+ bool dereference;
+ bool normalise;
+ const char **attrs;
+};
+
+static char **copy_attrs(void *mem_ctx, const char * const * attrs)
+{
+ char **nattrs;
+ unsigned int i, num;
+
+ for (num = 0; attrs[num]; num++);
+
+ nattrs = talloc_array(mem_ctx, char *, num + 1);
+ if (!nattrs) return NULL;
+
+ for(i = 0; i < num; i++) {
+ nattrs[i] = talloc_strdup(nattrs, attrs[i]);
+ if (!nattrs[i]) {
+ talloc_free(nattrs);
+ return NULL;
+ }
+ }
+ nattrs[i] = NULL;
+
+ return nattrs;
+}
+
+static bool add_attrs(void *mem_ctx, char ***attrs, const char *attr)
+{
+ char **nattrs;
+ unsigned int num;
+
+ for (num = 0; (*attrs)[num]; num++);
+
+ nattrs = talloc_realloc(mem_ctx, *attrs, char *, num + 2);
+ if (!nattrs) return false;
+
+ *attrs = nattrs;
+
+ nattrs[num] = talloc_strdup(nattrs, attr);
+ if (!nattrs[num]) return false;
+
+ nattrs[num + 1] = NULL;
+
+ return true;
+}
+
+/* Inject the extended DN components, so the DN cn=Administrator,cn=users,dc=samba,dc=example,dc=com becomes
+ <GUID=541203ae-f7d6-47ef-8390-bfcf019f9583>;<SID=S-1-5-21-4177067393-1453636373-93818737-500>;cn=Administrator,cn=users,dc=samba,dc=example,dc=com */
+
+static int inject_extended_dn_out(struct ldb_reply *ares,
+ struct ldb_context *ldb,
+ int type,
+ bool remove_guid,
+ bool remove_sid)
+{
+ int ret;
+ const DATA_BLOB *guid_blob;
+ const DATA_BLOB *sid_blob;
+
+ guid_blob = ldb_msg_find_ldb_val(ares->message, "objectGUID");
+ sid_blob = ldb_msg_find_ldb_val(ares->message, "objectSid");
+
+ if (!guid_blob) {
+ ldb_set_errstring(ldb, "Did not find objectGUID to inject into extended DN");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_dn_set_extended_component(ares->message->dn, "GUID", guid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (sid_blob) {
+ ret = ldb_dn_set_extended_component(ares->message->dn, "SID", sid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (remove_guid) {
+ ldb_msg_remove_attr(ares->message, "objectGUID");
+ }
+
+ if (sid_blob && remove_sid) {
+ ldb_msg_remove_attr(ares->message, "objectSid");
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* search */
+struct extended_search_context {
+ struct ldb_module *module;
+ const struct dsdb_schema *schema;
+ struct ldb_request *req;
+ bool inject;
+ bool remove_guid;
+ bool remove_sid;
+ int extended_type;
+};
+
+
+/*
+ fix one-way links to have the right string DN, to cope with
+ renames of the target
+*/
+static int fix_one_way_link(struct extended_search_context *ac, struct ldb_dn *dn,
+ bool is_deleted_objects, bool *remove_value,
+ uint32_t linkID)
+{
+ struct GUID guid;
+ NTSTATUS status;
+ int ret;
+ struct ldb_dn *real_dn;
+ uint32_t search_flags;
+ TALLOC_CTX *tmp_ctx = talloc_new(ac);
+ const char *attrs[] = { NULL };
+ struct ldb_result *res;
+
+ (*remove_value) = false;
+
+ status = dsdb_get_extended_dn_guid(dn, &guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ /* this is a strange DN that doesn't have a GUID! just
+ return the current DN string?? */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ search_flags = DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SEARCH_ALL_PARTITIONS | DSDB_SEARCH_ONE_ONLY;
+
+ if (linkID == 0) {
+ /* You must ALWAYS show one-way links regardless of the state of the target */
+ search_flags |= (DSDB_SEARCH_SHOW_DELETED | DSDB_SEARCH_SHOW_RECYCLED);
+ }
+
+ ret = dsdb_module_search(ac->module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs,
+ search_flags, ac->req, "objectguid=%s", GUID_string(tmp_ctx, &guid));
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ /* if we can't resolve this GUID, then we don't
+ display the link. This could be a link to a NC that we don't
+ have, or it could be a link to a deleted object
+ */
+ (*remove_value) = true;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ real_dn = res->msgs[0]->dn;
+
+ if (strcmp(ldb_dn_get_linearized(dn), ldb_dn_get_linearized(real_dn)) == 0) {
+ /* its already correct */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* fix the DN by replacing its components with those from the
+ * real DN
+ */
+ if (!ldb_dn_replace_components(dn, real_dn)) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(ac->module));
+ }
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ this is called to post-process the results from the search
+ */
+static int extended_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct extended_search_context *ac;
+ int ret;
+ unsigned int i, j, k;
+ struct ldb_message *msg;
+ struct extended_dn_out_private *p;
+ struct ldb_context *ldb;
+ bool have_reveal_control=false;
+
+ ac = talloc_get_type(req->context, struct extended_search_context);
+ p = talloc_get_type(ldb_module_get_private(ac->module), struct extended_dn_out_private);
+ 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);
+ }
+
+ msg = ares->message;
+
+ switch (ares->type) {
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ case LDB_REPLY_ENTRY:
+ break;
+ }
+
+ if (p && p->normalise) {
+ ret = dsdb_fix_dn_rdncase(ldb, ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (ac->inject) {
+ /* for each record returned post-process to add any derived
+ attributes that have been asked for */
+ ret = inject_extended_dn_out(ares, ldb,
+ ac->extended_type, ac->remove_guid,
+ ac->remove_sid);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if ((p && p->normalise) || ac->inject) {
+ const struct ldb_val *val = ldb_msg_find_ldb_val(ares->message, "distinguishedName");
+ if (val) {
+ ldb_msg_remove_attr(ares->message, "distinguishedName");
+ if (ac->inject) {
+ ret = ldb_msg_add_steal_string(ares->message, "distinguishedName",
+ ldb_dn_get_extended_linearized(ares->message, ares->message->dn, ac->extended_type));
+ } else {
+ ret = ldb_msg_add_linearized_dn(ares->message,
+ "distinguishedName",
+ ares->message->dn);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+ }
+ }
+
+ have_reveal_control =
+ dsdb_request_has_control(req, LDB_CONTROL_REVEAL_INTERNALS);
+
+ /*
+ * Shortcut for repl_meta_data. We asked for the data
+ * 'as-is', so stop processing here!
+ */
+ if (have_reveal_control && (p == NULL || !p->normalise) && ac->inject) {
+ return ldb_module_send_entry(ac->req, msg, ares->controls);
+ }
+
+ /* Walk the returned elements (but only if we have a schema to
+ * interpret the list with) */
+ for (i = 0; ac->schema && i < msg->num_elements; i++) {
+ bool make_extended_dn;
+ bool bl_requested = true;
+ const struct dsdb_attribute *attribute;
+
+ attribute = dsdb_attribute_by_lDAPDisplayName(ac->schema, msg->elements[i].name);
+ if (!attribute) {
+ continue;
+ }
+
+ if (p && p->normalise) {
+ /* If we are also in 'normalise' mode, then
+ * fix the attribute names to be in the
+ * correct case */
+ msg->elements[i].name = talloc_strdup(msg->elements, attribute->lDAPDisplayName);
+ if (!msg->elements[i].name) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ /* distinguishedName has been dealt with above */
+ if (ldb_attr_cmp(msg->elements[i].name, "distinguishedName") == 0) {
+ continue;
+ }
+
+ /* Look to see if this attributeSyntax is a DN */
+ if (attribute->dn_format == DSDB_INVALID_DN) {
+ continue;
+ }
+
+ make_extended_dn = ac->inject;
+
+ /* Always show plain DN in case of Object(OR-Name) syntax */
+ if (make_extended_dn) {
+ make_extended_dn = (strcmp(attribute->syntax->ldap_oid, DSDB_SYNTAX_OR_NAME) != 0);
+ }
+
+ if (attribute->linkID & 1 &&
+ attribute->bl_maybe_invisible &&
+ !have_reveal_control)
+ {
+ const char * const *attrs = ac->req->op.search.attrs;
+
+ if (attrs != NULL) {
+ bl_requested = ldb_attr_in_list(attrs,
+ attribute->lDAPDisplayName);
+ } else {
+ bl_requested = false;
+ }
+ }
+
+ for (k = 0, j = 0; j < msg->elements[i].num_values; j++) {
+ const char *dn_str;
+ struct ldb_dn *dn;
+ struct dsdb_dn *dsdb_dn = NULL;
+ struct ldb_val *plain_dn = &msg->elements[i].values[j];
+ bool is_deleted_objects = false;
+ uint32_t rmd_flags;
+
+ /* this is a fast method for detecting deleted
+ linked attributes, working on the unparsed
+ ldb_val */
+ rmd_flags = dsdb_dn_val_rmd_flags(plain_dn);
+ if (rmd_flags & DSDB_RMD_FLAG_DELETED && !have_reveal_control) {
+ /* it's a deleted linked attribute,
+ and we don't have the reveal control */
+ /* we won't keep this one, so not incrementing k */
+ continue;
+ }
+ if (rmd_flags & DSDB_RMD_FLAG_HIDDEN_BL && !bl_requested) {
+ /*
+ * Hidden backlinks are not revealed unless
+ * requested.
+ *
+ * we won't keep this one, so not incrementing k
+ */
+ continue;
+ }
+
+ dsdb_dn = dsdb_dn_parse_trusted(msg, ldb, plain_dn, attribute->syntax->ldap_oid);
+
+ if (!dsdb_dn) {
+ ldb_asprintf_errstring(ldb,
+ "could not parse %.*s in %s on %s as a %s DN",
+ (int)plain_dn->length, plain_dn->data,
+ msg->elements[i].name, ldb_dn_get_linearized(msg->dn),
+ attribute->syntax->ldap_oid);
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_INVALID_DN_SYNTAX);
+ }
+ dn = dsdb_dn->dn;
+
+ /* we need to know if this is a link to the
+ deleted objects container for fixing one way
+ links */
+ if (dsdb_dn->extra_part.length == 16) {
+ char *hex_string = data_blob_hex_string_upper(req, &dsdb_dn->extra_part);
+ if (hex_string && strcmp(hex_string, DS_GUID_DELETED_OBJECTS_CONTAINER) == 0) {
+ is_deleted_objects = true;
+ }
+ talloc_free(hex_string);
+ }
+
+ if (p != NULL && p->normalise) {
+ ret = dsdb_fix_dn_rdncase(ldb, dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ /* Look for this value in the attribute */
+
+ /* note that we don't fixup objectCategory as
+ it should not be possible to move
+ objectCategory elements in the schema */
+ if (attribute->one_way_link &&
+ strcasecmp(attribute->lDAPDisplayName, "objectCategory") != 0) {
+ bool remove_value;
+ ret = fix_one_way_link(ac, dn, is_deleted_objects, &remove_value,
+ attribute->linkID);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ if (remove_value && !have_reveal_control) {
+ /* we show these with REVEAL
+ to allow dbcheck to find and
+ cleanup these orphaned links */
+ /* we won't keep this one, so not incrementing k */
+ continue;
+ }
+ }
+
+ if (make_extended_dn) {
+ if (!ldb_dn_validate(dsdb_dn->dn)) {
+ ldb_asprintf_errstring(ldb,
+ "could not parse %.*s in %s on %s as a %s DN",
+ (int)plain_dn->length, plain_dn->data,
+ msg->elements[i].name, ldb_dn_get_linearized(msg->dn),
+ attribute->syntax->ldap_oid);
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_INVALID_DN_SYNTAX);
+ }
+ /* don't let users see the internal extended
+ GUID components */
+ if (!have_reveal_control) {
+ const char *accept[] = { "GUID", "SID", NULL };
+ ldb_dn_extended_filter(dn, accept);
+ }
+ dn_str = dsdb_dn_get_extended_linearized(msg->elements[i].values,
+ dsdb_dn, ac->extended_type);
+ } else {
+ dn_str = dsdb_dn_get_linearized(msg->elements[i].values,
+ dsdb_dn);
+ }
+
+ if (!dn_str) {
+ ldb_oom(ldb);
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ msg->elements[i].values[k] = data_blob_string_const(dn_str);
+ talloc_free(dsdb_dn);
+ k++;
+ }
+
+ if (k == 0) {
+ /* we've deleted all of the values from this
+ * element - remove the element */
+ ldb_msg_remove_element(msg, &msg->elements[i]);
+ i--;
+ } else {
+ msg->elements[i].num_values = k;
+ }
+ }
+ return ldb_module_send_entry(ac->req, msg, ares->controls);
+}
+
+static int extended_callback_ldb(struct ldb_request *req, struct ldb_reply *ares)
+{
+ return extended_callback(req, ares);
+}
+
+static int extended_dn_out_search(struct ldb_module *module, struct ldb_request *req,
+ int (*callback)(struct ldb_request *req, struct ldb_reply *ares))
+{
+ struct ldb_control *control;
+ struct ldb_control *storage_format_control;
+ struct ldb_extended_dn_control *extended_ctrl = NULL;
+ struct extended_search_context *ac;
+ struct ldb_request *down_req;
+ char **new_attrs;
+ const char * const *const_attrs;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ struct extended_dn_out_private *p = talloc_get_type(ldb_module_get_private(module), struct extended_dn_out_private);
+
+ /* The schema manipulation does not apply to special DNs */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* check if there's an extended dn control */
+ control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID);
+ if (control && control->data) {
+ extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control);
+ if (!extended_ctrl) {
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ }
+
+ /* Look to see if, as we are in 'store DN+GUID+SID' mode, the
+ * client is after the storage format (to fill in linked
+ * attributes) */
+ storage_format_control = ldb_request_get_control(req, DSDB_CONTROL_DN_STORAGE_FORMAT_OID);
+ if (!control && storage_format_control && storage_format_control->data) {
+ extended_ctrl = talloc_get_type(storage_format_control->data, struct ldb_extended_dn_control);
+ if (!extended_ctrl) {
+ ldb_set_errstring(ldb, "extended_dn_out: extended_ctrl was of the wrong data type");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ }
+
+ ac = talloc_zero(req, struct extended_search_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ac->module = module;
+ ac->schema = dsdb_get_schema(ldb, ac);
+ ac->req = req;
+ ac->inject = false;
+ ac->remove_guid = false;
+ ac->remove_sid = false;
+
+ const_attrs = req->op.search.attrs;
+
+ /* We only need to do special processing if we were asked for
+ * the extended DN, or we are 'store DN+GUID+SID'
+ * (!dereference) mode. (This is the normal mode for LDB on
+ * tdb). */
+ if (control || (storage_format_control && p)) {
+ ac->inject = true;
+ if (extended_ctrl) {
+ ac->extended_type = extended_ctrl->type;
+ } else {
+ ac->extended_type = 0;
+ }
+
+ /* check if attrs only is specified, in that case check whether we need to modify them */
+ if (req->op.search.attrs && !ldb_attr_in_list(req->op.search.attrs, "*")) {
+ if (! ldb_attr_in_list(req->op.search.attrs, "objectGUID")) {
+ ac->remove_guid = true;
+ }
+ if (! ldb_attr_in_list(req->op.search.attrs, "objectSid")) {
+ ac->remove_sid = true;
+ }
+ if (ac->remove_guid || ac->remove_sid) {
+ new_attrs = copy_attrs(ac, req->op.search.attrs);
+ if (new_attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (ac->remove_guid) {
+ if (!add_attrs(ac, &new_attrs, "objectGUID"))
+ return ldb_operr(ldb);
+ }
+ if (ac->remove_sid) {
+ if (!add_attrs(ac, &new_attrs, "objectSid"))
+ return ldb_operr(ldb);
+ }
+ const_attrs = (const char * const *)new_attrs;
+ }
+ }
+ }
+
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ const_attrs,
+ req->controls,
+ ac, callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* mark extended DN and storage format controls as done */
+ if (control) {
+ control->critical = 0;
+ }
+
+ if (storage_format_control) {
+ storage_format_control->critical = 0;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int extended_dn_out_ldb_search(struct ldb_module *module, struct ldb_request *req)
+{
+ return extended_dn_out_search(module, req, extended_callback_ldb);
+}
+
+static int extended_dn_out_ldb_init(struct ldb_module *module)
+{
+ int ret;
+
+ struct extended_dn_out_private *p = talloc(module, struct extended_dn_out_private);
+ struct dsdb_extended_dn_store_format *dn_format;
+
+ ldb_module_set_private(module, p);
+
+ if (!p) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ dn_format = talloc(p, struct dsdb_extended_dn_store_format);
+ if (!dn_format) {
+ talloc_free(p);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ dn_format->store_extended_dn_in_ldb = true;
+ ret = ldb_set_opaque(ldb_module_get_ctx(module), DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME, dn_format);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(p);
+ return ret;
+ }
+
+ p->dereference = false;
+ p->normalise = false;
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_EXTENDED_DN_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_ERROR,
+ "extended_dn_out: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_extended_dn_out_ldb_module_ops = {
+ .name = "extended_dn_out_ldb",
+ .search = extended_dn_out_ldb_search,
+ .init_context = extended_dn_out_ldb_init,
+};
+
+/*
+ initialise the module
+ */
+_PUBLIC_ int ldb_extended_dn_out_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_extended_dn_out_ldb_module_ops);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return LDB_SUCCESS;
+}