Copyright (C) Andrew Tridgell 2009
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+ Copyright (C) Matthieu Patou <mat@matws.net> 2011
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
#include "dsdb/samdb/samdb.h"
#include "util.h"
#include "libcli/security/security.h"
-#include "lib/ldb/include/ldb_private.h"
/*
search for attrs on one DN, in the modules below
struct ldb_result **_res,
struct ldb_dn *basedn,
const char * const *attrs,
- uint32_t dsdb_flags)
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
{
int ret;
struct ldb_request *req;
NULL,
res,
ldb_search_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
/* Run the new request */
if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
ret = ldb_next_request(module, req);
return ret;
}
-/*
- search for attrs in the modules below
- */
-int dsdb_module_search(struct ldb_module *module,
+int dsdb_module_search_tree(struct ldb_module *module,
TALLOC_CTX *mem_ctx,
struct ldb_result **_res,
- struct ldb_dn *basedn, enum ldb_scope scope,
+ struct ldb_dn *basedn,
+ enum ldb_scope scope,
+ struct ldb_parse_tree *tree,
const char * const *attrs,
- int dsdb_flags,
- const char *format, ...) _PRINTF_ATTRIBUTE(8, 9)
+ int dsdb_flags,
+ struct ldb_request *parent)
{
int ret;
struct ldb_request *req;
TALLOC_CTX *tmp_ctx;
struct ldb_result *res;
- va_list ap;
- char *expression;
tmp_ctx = talloc_new(mem_ctx);
- if (format) {
- va_start(ap, format);
- expression = talloc_vasprintf(tmp_ctx, format, ap);
- va_end(ap);
-
- if (!expression) {
- talloc_free(tmp_ctx);
- return ldb_oom(ldb_module_get_ctx(module));
- }
- } else {
- expression = NULL;
- }
res = talloc_zero(tmp_ctx, struct ldb_result);
if (!res) {
return ldb_oom(ldb_module_get_ctx(module));
}
- ret = ldb_build_search_req(&req, ldb_module_get_ctx(module), tmp_ctx,
+ ret = ldb_build_search_req_ex(&req, ldb_module_get_ctx(module), tmp_ctx,
basedn,
scope,
- expression,
+ tree,
attrs,
NULL,
res,
ldb_search_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
ret = ldb_next_request(module, req);
} else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
return ret;
}
+/*
+ search for attrs in the modules below
+ */
+int dsdb_module_search(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ struct ldb_dn *basedn, enum ldb_scope scope,
+ const char * const *attrs,
+ int dsdb_flags,
+ struct ldb_request *parent,
+ const char *format, ...) _PRINTF_ATTRIBUTE(9, 10)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+ va_list ap;
+ char *expression;
+ struct ldb_parse_tree *tree;
+
+ tmp_ctx = talloc_new(mem_ctx);
+
+ if (format) {
+ va_start(ap, format);
+ expression = talloc_vasprintf(tmp_ctx, format, ap);
+ va_end(ap);
+
+ if (!expression) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ } else {
+ expression = NULL;
+ }
+
+ tree = ldb_parse_tree(tmp_ctx, expression);
+ if (tree == NULL) {
+ talloc_free(tmp_ctx);
+ ldb_set_errstring(ldb_module_get_ctx(module),
+ "Unable to parse search expression");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_module_search_tree(module,
+ mem_ctx,
+ _res,
+ basedn,
+ scope,
+ tree,
+ attrs,
+ dsdb_flags,
+ parent);
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
/*
find a DN given a GUID. This searches across all partitions
*/
int dsdb_module_dn_by_guid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
- const struct GUID *guid, struct ldb_dn **dn)
+ const struct GUID *guid, struct ldb_dn **dn,
+ struct ldb_request *parent)
{
struct ldb_result *res;
const char *attrs[] = { NULL };
DSDB_SEARCH_SHOW_RECYCLED |
DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ parent,
"objectGUID=%s", GUID_string(tmp_ctx, guid));
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
/*
find a GUID given a DN.
*/
-int dsdb_module_guid_by_dn(struct ldb_module *module, struct ldb_dn *dn, struct GUID *guid)
+int dsdb_module_guid_by_dn(struct ldb_module *module, struct ldb_dn *dn, struct GUID *guid,
+ struct ldb_request *parent)
{
const char *attrs[] = { NULL };
struct ldb_result *res;
ret = dsdb_module_search_dn(module, tmp_ctx, &res, dn, attrs,
DSDB_FLAG_NEXT_MODULE |
DSDB_SEARCH_SHOW_RECYCLED |
- DSDB_SEARCH_SHOW_EXTENDED_DN);
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ parent);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb_module_get_ctx(module), "Failed to find GUID for %s",
ldb_dn_get_linearized(dn));
talloc_free(tmp_ctx);
return LDB_SUCCESS;
}
+/*
+ a ldb_extended request operating on modules below the
+ current module
+ */
+int dsdb_module_extended(struct ldb_module *module,
+ const char* oid, void* data,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_request *req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_extended_req(&req, ldb,
+ tmp_ctx,
+ oid,
+ data,
+ NULL,
+ res, ldb_extended_default_callback,
+ parent);
+
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->extended(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
/*
a ldb_modify request operating on modules below the
current module
*/
int dsdb_module_modify(struct ldb_module *module,
const struct ldb_message *message,
- uint32_t dsdb_flags)
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
{
struct ldb_request *mod_req;
int ret;
NULL,
res,
ldb_modify_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(mod_req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(mod_req);
+ }
+
/* Run the new request */
if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
ret = ldb_next_request(module, mod_req);
current module
*/
int dsdb_module_rename(struct ldb_module *module,
- struct ldb_dn *olddn, struct ldb_dn *newdn,
- uint32_t dsdb_flags)
+ struct ldb_dn *olddn, struct ldb_dn *newdn,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
{
struct ldb_request *req;
int ret;
NULL,
res,
ldb_modify_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
/* Run the new request */
if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
ret = ldb_next_request(module, req);
*/
int dsdb_module_add(struct ldb_module *module,
const struct ldb_message *message,
- uint32_t dsdb_flags)
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
{
struct ldb_request *req;
int ret;
NULL,
res,
ldb_modify_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
/* Run the new request */
if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
ret = ldb_next_request(module, req);
*/
int dsdb_module_del(struct ldb_module *module,
struct ldb_dn *dn,
- uint32_t dsdb_flags)
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
{
struct ldb_request *req;
int ret;
NULL,
res,
ldb_modify_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
/* Run the new request */
if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
ret = ldb_next_request(module, req);
return LDB_SUCCESS;
}
+/*
+ find the NTDS GUID from a computers DN record
+ */
+int dsdb_module_find_ntdsguid_for_computer(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *computer_dn,
+ struct GUID *ntds_guid,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_dn *dn;
+
+ *ntds_guid = GUID_zero();
+
+ ret = dsdb_module_reference_dn(module, mem_ctx, computer_dn,
+ "serverReferenceBL", &dn, parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!ldb_dn_add_child_fmt(dn, "CN=NTDS Settings")) {
+ talloc_free(dn);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_module_guid_by_dn(module, dn, ntds_guid, parent);
+ talloc_free(dn);
+ return ret;
+}
+
/*
find a 'reference' DN that points at another object
(eg. serverReference, rIDManagerReference etc)
*/
int dsdb_module_reference_dn(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn *base,
- const char *attribute, struct ldb_dn **dn)
+ const char *attribute, struct ldb_dn **dn, struct ldb_request *parent)
{
const char *attrs[2];
struct ldb_result *res;
attrs[1] = NULL;
ret = dsdb_module_search_dn(module, mem_ctx, &res, base, attrs,
- DSDB_FLAG_NEXT_MODULE);
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_EXTENDED_DN, parent);
if (ret != LDB_SUCCESS) {
return ret;
}
find the RID Manager$ DN via the rIDManagerReference attribute in the
base DN
*/
-int dsdb_module_rid_manager_dn(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn **dn)
+int dsdb_module_rid_manager_dn(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn **dn,
+ struct ldb_request *parent)
{
return dsdb_module_reference_dn(module, mem_ctx,
ldb_get_default_basedn(ldb_module_get_ctx(module)),
- "rIDManagerReference", dn);
-}
-
-
-/*
- update an integer attribute safely via a constrained delete/add
- */
-int dsdb_module_constrainted_update_integer(struct ldb_module *module, struct ldb_dn *dn,
- const char *attr, uint64_t old_val, uint64_t new_val)
-{
- struct ldb_message *msg;
- struct ldb_message_element *el;
- struct ldb_val v1, v2;
- int ret;
- char *vstring;
-
- msg = ldb_msg_new(module);
- msg->dn = dn;
-
- ret = ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_DELETE, &el);
- if (ret != LDB_SUCCESS) {
- talloc_free(msg);
- return ret;
- }
- el->num_values = 1;
- el->values = &v1;
- vstring = talloc_asprintf(msg, "%llu", (unsigned long long)old_val);
- if (!vstring) {
- talloc_free(msg);
- return ldb_module_oom(module);
- }
- v1 = data_blob_string_const(vstring);
-
- ret = ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_ADD, &el);
- if (ret != LDB_SUCCESS) {
- talloc_free(msg);
- return ret;
- }
- el->num_values = 1;
- el->values = &v2;
- vstring = talloc_asprintf(msg, "%llu", (unsigned long long)new_val);
- if (!vstring) {
- talloc_free(msg);
- return ldb_module_oom(module);
- }
- v2 = data_blob_string_const(vstring);
-
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE);
- talloc_free(msg);
- return ret;
+ "rIDManagerReference", dn, parent);
}
/*
return up_req->callback(up_req, ares);
}
-
-/*
- set an integer attribute
- */
-int dsdb_module_set_integer(struct ldb_module *module, struct ldb_dn *dn,
- const char *attr, uint64_t new_val)
-{
- struct ldb_message *msg;
- int ret;
-
- msg = ldb_msg_new(module);
- msg->dn = dn;
-
- ret = ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)new_val);
- if (ret != LDB_SUCCESS) {
- talloc_free(msg);
- return ret;
- }
- msg->elements[0].flags = LDB_FLAG_MOD_REPLACE;
-
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE);
- talloc_free(msg);
- return ret;
-}
-
/*
load the uSNHighest and the uSNUrgent attributes from the @REPLCHANGED
object for a partition
*/
int dsdb_module_load_partition_usn(struct ldb_module *module, struct ldb_dn *dn,
- uint64_t *uSN, uint64_t *urgent_uSN)
+ uint64_t *uSN, uint64_t *urgent_uSN, struct ldb_request *parent)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_request *req;
NULL, NULL,
NULL,
res, ldb_search_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
an implicit value of zero */
*uSN = 0;
talloc_free(tmp_ctx);
+ ldb_reset_err_string(ldb);
return LDB_SUCCESS;
}
partition
*/
int dsdb_module_save_partition_usn(struct ldb_module *module, struct ldb_dn *dn,
- uint64_t uSN, uint64_t urgent_uSN)
+ uint64_t uSN, uint64_t urgent_uSN,
+ struct ldb_request *parent)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_request *req;
return ldb_module_oom(module);
}
- ret = ldb_msg_add_fmt(msg, "uSNHighest", "%llu", (unsigned long long)uSN);
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNHighest", uSN);
if (ret != LDB_SUCCESS) {
talloc_free(msg);
return ret;
/* urgent_uSN is optional so may not be stored */
if (urgent_uSN) {
- ret = ldb_msg_add_fmt(msg, "uSNUrgent", "%llu", (unsigned long long)urgent_uSN);
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNUrgent",
+ urgent_uSN);
if (ret != LDB_SUCCESS) {
talloc_free(msg);
return ret;
NULL,
res,
ldb_modify_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
again:
if (ret != LDB_SUCCESS) {
NULL,
res,
ldb_modify_default_callback,
- NULL);
+ parent);
LDB_REQ_SET_LOCATION(req);
goto again;
}
struct ldb_dn *dn,
const char *attr,
const int32_t *old_val,
- const int32_t *new_val)
+ const int32_t *new_val,
+ struct ldb_request *parent)
{
struct ldb_message *msg;
int ret;
return ret;
}
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE);
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
talloc_free(msg);
return ret;
}
struct ldb_dn *dn,
const char *attr,
const uint32_t *old_val,
- const uint32_t *new_val)
+ const uint32_t *new_val,
+ struct ldb_request *parent)
{
return dsdb_module_constrainted_update_int32(module, dn, attr,
(const int32_t *)old_val,
- (const int32_t *)new_val);
+ (const int32_t *)new_val, parent);
}
/*
struct ldb_dn *dn,
const char *attr,
const int64_t *old_val,
- const int64_t *new_val)
+ const int64_t *new_val,
+ struct ldb_request *parent)
{
struct ldb_message *msg;
int ret;
return ret;
}
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE);
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
talloc_free(msg);
return ret;
}
struct ldb_dn *dn,
const char *attr,
const uint64_t *old_val,
- const uint64_t *new_val)
+ const uint64_t *new_val,
+ struct ldb_request *parent)
{
return dsdb_module_constrainted_update_int64(module, dn, attr,
(const int64_t *)old_val,
- (const int64_t *)new_val);
+ (const int64_t *)new_val,
+ parent);
}
const struct ldb_val *dsdb_module_find_dsheuristics(struct ldb_module *module,
- TALLOC_CTX *mem_ctx)
+ TALLOC_CTX *mem_ctx, struct ldb_request *parent)
{
int ret;
struct ldb_dn *new_dn;
struct ldb_context *ldb = ldb_module_get_ctx(module);
- static const char *attrs[] = { "dsHeuristics", NULL };
+ static const char *attrs[] = { "dSHeuristics", NULL };
struct ldb_result *res;
new_dn = ldb_dn_copy(mem_ctx, ldb_get_config_basedn(ldb));
ret = dsdb_module_search_dn(module, mem_ctx, &res,
new_dn,
attrs,
- DSDB_FLAG_NEXT_MODULE);
+ DSDB_FLAG_NEXT_MODULE,
+ parent);
if (ret == LDB_SUCCESS && res->count == 1) {
talloc_free(new_dn);
return ldb_msg_find_ldb_val(res->msgs[0],
- "dsHeuristics");
+ "dSHeuristics");
}
talloc_free(new_dn);
return NULL;
}
-bool dsdb_block_anonymous_ops(struct ldb_module *module,
- TALLOC_CTX *mem_ctx)
+bool dsdb_block_anonymous_ops(struct ldb_module *module, struct ldb_request *parent)
{
- TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
bool result;
const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
- tmp_ctx);
+ tmp_ctx, parent);
if (hr_val == NULL || hr_val->length < DS_HR_BLOCK_ANONYMOUS_OPS) {
result = true;
} else if (hr_val->data[DS_HR_BLOCK_ANONYMOUS_OPS -1] == '2') {
return result;
}
+bool dsdb_user_password_support(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ bool result;
+ const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
+ tmp_ctx,
+ parent);
+ if (hr_val == NULL || hr_val->length < DS_HR_USER_PASSWORD_SUPPORT) {
+ result = false;
+ } else if ((hr_val->data[DS_HR_USER_PASSWORD_SUPPORT -1] == '2') ||
+ (hr_val->data[DS_HR_USER_PASSWORD_SUPPORT -1] == '0')) {
+ result = false;
+ } else {
+ result = true;
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
/*
show the chain of requests, useful for debugging async requests
*/
void dsdb_req_chain_debug(struct ldb_request *req, int level)
{
- int i=0;
+ char *s = ldb_module_call_chain(req, req);
+ DEBUG(level, ("%s\n", s));
+ talloc_free(s);
+}
- while (req && req->handle) {
- DEBUG(level,("req[%u] %p : %s\n", i++, req, ldb_req_location(req)));
- req = req->handle->parent;
+/*
+ * Gets back a single-valued attribute by the rules of the DSDB triggers when
+ * performing a modify operation.
+ *
+ * In order that the constraint checking by the "objectclass_attrs" LDB module
+ * does work properly, the change request should remain similar or only be
+ * enhanced (no other modifications as deletions, variations).
+ */
+struct ldb_message_element *dsdb_get_single_valued_attr(const struct ldb_message *msg,
+ const char *attr_name,
+ enum ldb_request_type operation)
+{
+ struct ldb_message_element *el = NULL;
+ unsigned int i;
+
+ /* We've to walk over all modification entries and consider the last
+ * non-delete one which belongs to "attr_name".
+ *
+ * If "el" is NULL afterwards then that means there was no interesting
+ * change entry. */
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) {
+ if ((operation == LDB_MODIFY) &&
+ (LDB_FLAG_MOD_TYPE(msg->elements[i].flags)
+ == LDB_FLAG_MOD_DELETE)) {
+ continue;
+ }
+ el = &msg->elements[i];
+ }
}
+
+ return el;
}