diff options
Diffstat (limited to 'lib/ldb-samba')
-rw-r--r-- | lib/ldb-samba/README | 7 | ||||
-rw-r--r-- | lib/ldb-samba/ldb_ildap.c | 899 | ||||
-rw-r--r-- | lib/ldb-samba/ldb_matching_rules.c | 636 | ||||
-rw-r--r-- | lib/ldb-samba/ldb_matching_rules.h | 30 | ||||
-rw-r--r-- | lib/ldb-samba/ldb_wrap.c | 377 | ||||
-rw-r--r-- | lib/ldb-samba/ldb_wrap.h | 70 | ||||
-rw-r--r-- | lib/ldb-samba/ldif_handlers.c | 1836 | ||||
-rw-r--r-- | lib/ldb-samba/ldif_handlers.h | 30 | ||||
-rw-r--r-- | lib/ldb-samba/pyldb.c | 320 | ||||
-rw-r--r-- | lib/ldb-samba/samba_extensions.c | 169 | ||||
-rw-r--r-- | lib/ldb-samba/tests/index.py | 192 | ||||
-rwxr-xr-x | lib/ldb-samba/tests/match_rules.py | 1797 | ||||
-rwxr-xr-x | lib/ldb-samba/tests/match_rules_remote.py | 104 | ||||
-rw-r--r-- | lib/ldb-samba/wscript_build | 44 |
14 files changed, 6511 insertions, 0 deletions
diff --git a/lib/ldb-samba/README b/lib/ldb-samba/README new file mode 100644 index 0000000..3fa4715 --- /dev/null +++ b/lib/ldb-samba/README @@ -0,0 +1,7 @@ +This directory contains Samba specific extensions to ldb. It also +serves as example code on how to extend ldb for your own application. + +The main extension Samba uses is to provide ldif encode/decode +routines for specific attributes, so users can get nice pretty +printing of attributes in ldbedit, while the attributes are stored in +the standard NDR format in the database. diff --git a/lib/ldb-samba/ldb_ildap.c b/lib/ldb-samba/ldb_ildap.c new file mode 100644 index 0000000..b15f38b --- /dev/null +++ b/lib/ldb-samba/ldb_ildap.c @@ -0,0 +1,899 @@ +/* + ldb database library - ildap backend + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Simo Sorce 2008 + + ** NOTE! The following LGPL license applies to the ldb + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * Name: ldb_ildap + * + * Component: ldb ildap backend + * + * Description: This is a ldb backend for the internal ldap + * client library in Samba4. By using this backend we are + * independent of a system ldap library + * + * Author: Andrew Tridgell + * + * Modifications: + * + * - description: make the module use asynchronous calls + * date: Feb 2006 + * author: Simo Sorce + */ + +#include "includes.h" +#include "ldb_module.h" +#include "util/dlinklist.h" + +#include "libcli/ldap/libcli_ldap.h" +#include "libcli/ldap/ldap_client.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" +#include "dsdb/common/util.h" + +struct ildb_private { + struct ldap_connection *ldap; + struct tevent_context *event_ctx; +}; + +struct ildb_context { + struct ldb_module *module; + struct ldb_request *req; + + struct ildb_private *ildb; + struct ldap_request *ireq; + + /* indicate we are already processing + * the ldap_request in ildb_callback() */ + bool in_ildb_callback; + + bool done; + + struct ildb_destructor_ctx *dc; +}; + +static void ildb_request_done(struct ildb_context *ctx, + struct ldb_control **ctrls, int error) +{ + struct ldb_context *ldb; + struct ldb_reply *ares; + + ldb = ldb_module_get_ctx(ctx->module); + + ctx->done = true; + + if (ctx->req == NULL) { + /* if the req has been freed already just return */ + return; + } + + ares = talloc_zero(ctx->req, struct ldb_reply); + if (!ares) { + ldb_oom(ldb); + ctx->req->callback(ctx->req, NULL); + return; + } + ares->type = LDB_REPLY_DONE; + ares->controls = talloc_steal(ares, ctrls); + ares->error = error; + + ctx->req->callback(ctx->req, ares); +} + +static void ildb_auto_done_callback(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *private_data) +{ + struct ildb_context *ac; + + ac = talloc_get_type(private_data, struct ildb_context); + ildb_request_done(ac, NULL, LDB_SUCCESS); +} + +/* + convert a ldb_message structure to a list of ldap_mod structures + ready for ildap_add() or ildap_modify() +*/ +static struct ldap_mod **ildb_msg_to_mods(void *mem_ctx, unsigned int *num_mods, + const struct ldb_message *msg, + int use_flags) +{ + struct ldap_mod **mods; + unsigned int i; + unsigned int n = 0; + + /* allocate maximum number of elements needed */ + mods = talloc_array(mem_ctx, struct ldap_mod *, msg->num_elements+1); + if (!mods) { + errno = ENOMEM; + return NULL; + } + mods[0] = NULL; + + for (i = 0; i < msg->num_elements; i++) { + const struct ldb_message_element *el = &msg->elements[i]; + + mods[n] = talloc(mods, struct ldap_mod); + if (!mods[n]) { + goto failed; + } + mods[n + 1] = NULL; + mods[n]->type = 0; + mods[n]->attrib = *el; + if (use_flags) { + switch (el->flags & LDB_FLAG_MOD_MASK) { + case LDB_FLAG_MOD_ADD: + mods[n]->type = LDAP_MODIFY_ADD; + break; + case LDB_FLAG_MOD_DELETE: + mods[n]->type = LDAP_MODIFY_DELETE; + break; + case LDB_FLAG_MOD_REPLACE: + mods[n]->type = LDAP_MODIFY_REPLACE; + break; + } + } + n++; + } + + *num_mods = n; + return mods; + +failed: + talloc_free(mods); + return NULL; +} + + +/* + map an ildap NTSTATUS to a ldb error code +*/ +static int ildb_map_error(struct ldb_module *module, NTSTATUS status) +{ + struct ildb_private *ildb; + struct ldb_context *ldb; + TALLOC_CTX *mem_ctx; + + ildb = talloc_get_type(ldb_module_get_private(module), struct ildb_private); + ldb = ldb_module_get_ctx(module); + + if (NT_STATUS_IS_OK(status)) { + return LDB_SUCCESS; + } + + mem_ctx = talloc_new(ildb); + if (!mem_ctx) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ldb_set_errstring(ldb, + ldap_errstr(ildb->ldap, mem_ctx, status)); + talloc_free(mem_ctx); + if (NT_STATUS_IS_LDAP(status)) { + return NT_STATUS_LDAP_CODE(status); + } + return LDB_ERR_OPERATIONS_ERROR; +} + +static void ildb_request_timeout(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *private_data) +{ + struct ildb_context *ac = talloc_get_type(private_data, struct ildb_context); + + if (ac->ireq->state == LDAP_REQUEST_PENDING) { + DLIST_REMOVE(ac->ireq->conn->pending, ac->ireq); + } + + ildb_request_done(ac, NULL, LDB_ERR_TIME_LIMIT_EXCEEDED); +} + +static void ildb_callback(struct ldap_request *req) +{ + struct ldb_context *ldb; + struct ildb_context *ac; + NTSTATUS status; + struct ldap_SearchResEntry *search; + struct ldap_message *msg; + struct ldb_control **controls; + struct ldb_message *ldbmsg; + char *referral; + bool callback_failed; + bool request_done; + int ret; + int i; + + ac = talloc_get_type(req->async.private_data, struct ildb_context); + ldb = ldb_module_get_ctx(ac->module); + callback_failed = false; + request_done = false; + controls = NULL; + + /* check if we are already processing this request */ + if (ac->in_ildb_callback) { + return; + } + /* mark the request as being in process */ + ac->in_ildb_callback = true; + + if (!NT_STATUS_IS_OK(req->status)) { + ret = ildb_map_error(ac->module, req->status); + ildb_request_done(ac, NULL, ret); + return; + } + + if (req->num_replies < 1) { + ret = LDB_ERR_OPERATIONS_ERROR; + ildb_request_done(ac, NULL, ret); + return; + } + + switch (req->type) { + + case LDAP_TAG_ModifyRequest: + if (req->replies[0]->type != LDAP_TAG_ModifyResponse) { + ret = LDB_ERR_PROTOCOL_ERROR; + break; + } + status = ldap_check_response(ac->ireq->conn, &req->replies[0]->r.GeneralResult); + ret = ildb_map_error(ac->module, status); + request_done = true; + break; + + case LDAP_TAG_AddRequest: + if (req->replies[0]->type != LDAP_TAG_AddResponse) { + ret = LDB_ERR_PROTOCOL_ERROR; + return; + } + status = ldap_check_response(ac->ireq->conn, &req->replies[0]->r.GeneralResult); + ret = ildb_map_error(ac->module, status); + request_done = true; + break; + + case LDAP_TAG_DelRequest: + if (req->replies[0]->type != LDAP_TAG_DelResponse) { + ret = LDB_ERR_PROTOCOL_ERROR; + return; + } + status = ldap_check_response(ac->ireq->conn, &req->replies[0]->r.GeneralResult); + ret = ildb_map_error(ac->module, status); + request_done = true; + break; + + case LDAP_TAG_ModifyDNRequest: + if (req->replies[0]->type != LDAP_TAG_ModifyDNResponse) { + ret = LDB_ERR_PROTOCOL_ERROR; + return; + } + status = ldap_check_response(ac->ireq->conn, &req->replies[0]->r.GeneralResult); + ret = ildb_map_error(ac->module, status); + request_done = true; + break; + + case LDAP_TAG_SearchRequest: + /* loop over all messages */ + for (i = 0; i < req->num_replies; i++) { + + msg = req->replies[i]; + switch (msg->type) { + + case LDAP_TAG_SearchResultDone: + + status = ldap_check_response(ac->ireq->conn, &msg->r.GeneralResult); + if (!NT_STATUS_IS_OK(status)) { + ret = ildb_map_error(ac->module, status); + break; + } + + controls = talloc_steal(ac, msg->controls); + if (msg->r.SearchResultDone.resultcode) { + if (msg->r.SearchResultDone.errormessage) { + ldb_set_errstring(ldb, msg->r.SearchResultDone.errormessage); + } + } + + ret = msg->r.SearchResultDone.resultcode; + request_done = true; + break; + + case LDAP_TAG_SearchResultEntry: + + ldbmsg = ldb_msg_new(ac); + if (!ldbmsg) { + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + search = &(msg->r.SearchResultEntry); + + ldbmsg->dn = ldb_dn_new(ldbmsg, ldb, search->dn); + if ( ! ldb_dn_validate(ldbmsg->dn)) { + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + ldbmsg->num_elements = search->num_attributes; + ldbmsg->elements = talloc_move(ldbmsg, &search->attributes); + + controls = talloc_steal(ac, msg->controls); + + ret = ldb_module_send_entry(ac->req, ldbmsg, controls); + if (ret != LDB_SUCCESS) { + callback_failed = true; + } + + break; + + case LDAP_TAG_SearchResultReference: + + referral = talloc_strdup(ac, msg->r.SearchResultReference.referral); + + ret = ldb_module_send_referral(ac->req, referral); + if (ret != LDB_SUCCESS) { + callback_failed = true; + } + + break; + + default: + /* TAG not handled, fail ! */ + ret = LDB_ERR_PROTOCOL_ERROR; + break; + } + + if (ret != LDB_SUCCESS) { + break; + } + } + + talloc_free(req->replies); + req->replies = NULL; + req->num_replies = 0; + + break; + + default: + ret = LDB_ERR_PROTOCOL_ERROR; + break; + } + + if (ret != LDB_SUCCESS) { + + /* if the callback failed the caller will have freed the + * request. Just return and don't try to use it */ + if ( ! callback_failed) { + request_done = true; + } + } + + /* mark the request as not being in progress */ + ac->in_ildb_callback = false; + + if (request_done) { + ildb_request_done(ac, controls, ret); + } + + return; +} + +static int ildb_request_send(struct ildb_context *ac, struct ldap_message *msg) +{ + struct ldb_context *ldb; + struct ldap_request *req; + + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ldb = ldb_module_get_ctx(ac->module); + + ldb_request_set_state(ac->req, LDB_ASYNC_PENDING); + + req = ldap_request_send(ac->ildb->ldap, msg); + if (req == NULL) { + ldb_set_errstring(ldb, "async send request failed"); + return LDB_ERR_OPERATIONS_ERROR; + } + ac->ireq = talloc_reparent(ac->ildb->ldap, ac, req); + + if (!ac->ireq->conn) { + ldb_set_errstring(ldb, "connection to remote LDAP server dropped?"); + return LDB_ERR_OPERATIONS_ERROR; + } + + TALLOC_FREE(req->time_event); + if (ac->req->timeout > 0) { + struct timeval tv = { + .tv_sec = ac->req->starttime + ac->req->timeout, + }; + + req->time_event = tevent_add_timer(ac->ildb->event_ctx, ac, tv, + ildb_request_timeout, ac); + } + + req->async.fn = ildb_callback; + req->async.private_data = ac; + + return LDB_SUCCESS; +} + +/* + search for matching records using an asynchronous function + */ +static int ildb_search(struct ildb_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *req = ac->req; + struct ldap_message *msg; + int n; + + ldb = ldb_module_get_ctx(ac->module); + + if (!req->callback || !req->context) { + ldb_set_errstring(ldb, "Async interface called with NULL callback function or NULL context"); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (req->op.search.tree == NULL) { + ldb_set_errstring(ldb, "Invalid expression parse tree"); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg = new_ldap_message(req); + if (msg == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->type = LDAP_TAG_SearchRequest; + + if (req->op.search.base == NULL) { + msg->r.SearchRequest.basedn = talloc_strdup(msg, ""); + } else { + msg->r.SearchRequest.basedn = ldb_dn_get_extended_linearized(msg, req->op.search.base, 0); + } + if (msg->r.SearchRequest.basedn == NULL) { + ldb_set_errstring(ldb, "Unable to determine baseDN"); + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (req->op.search.scope == LDB_SCOPE_DEFAULT) { + msg->r.SearchRequest.scope = LDAP_SEARCH_SCOPE_SUB; + } else { + msg->r.SearchRequest.scope = req->op.search.scope; + } + + msg->r.SearchRequest.deref = LDAP_DEREFERENCE_NEVER; + msg->r.SearchRequest.timelimit = 0; + msg->r.SearchRequest.sizelimit = 0; + msg->r.SearchRequest.attributesonly = 0; + msg->r.SearchRequest.tree = discard_const(req->op.search.tree); + + for (n = 0; req->op.search.attrs && req->op.search.attrs[n]; n++) /* noop */ ; + msg->r.SearchRequest.num_attributes = n; + msg->r.SearchRequest.attributes = req->op.search.attrs; + msg->controls = req->controls; + + return ildb_request_send(ac, msg); +} + +/* + add a record +*/ +static int ildb_add(struct ildb_context *ac) +{ + struct ldb_request *req = ac->req; + struct ldap_message *msg; + struct ldap_mod **mods; + unsigned int i,n; + + msg = new_ldap_message(req); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->type = LDAP_TAG_AddRequest; + + msg->r.AddRequest.dn = ldb_dn_get_extended_linearized(msg, req->op.add.message->dn, 0); + if (msg->r.AddRequest.dn == NULL) { + talloc_free(msg); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + mods = ildb_msg_to_mods(msg, &n, req->op.add.message, 0); + if (mods == NULL) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->r.AddRequest.num_attributes = n; + msg->r.AddRequest.attributes = talloc_array(msg, struct ldb_message_element, n); + if (msg->r.AddRequest.attributes == NULL) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i = 0; i < n; i++) { + msg->r.AddRequest.attributes[i] = mods[i]->attrib; + } + msg->controls = req->controls; + + return ildb_request_send(ac, msg); +} + +/* + modify a record +*/ +static int ildb_modify(struct ildb_context *ac) +{ + struct ldb_request *req = ac->req; + struct ldap_message *msg; + struct ldap_mod **mods; + unsigned int i,n; + + msg = new_ldap_message(req); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->type = LDAP_TAG_ModifyRequest; + + msg->r.ModifyRequest.dn = ldb_dn_get_extended_linearized(msg, req->op.mod.message->dn, 0); + if (msg->r.ModifyRequest.dn == NULL) { + talloc_free(msg); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + mods = ildb_msg_to_mods(msg, &n, req->op.mod.message, 1); + if (mods == NULL) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->r.ModifyRequest.num_mods = n; + msg->r.ModifyRequest.mods = talloc_array(msg, struct ldap_mod, n); + if (msg->r.ModifyRequest.mods == NULL) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i = 0; i < n; i++) { + msg->r.ModifyRequest.mods[i] = *mods[i]; + } + msg->controls = req->controls; + return ildb_request_send(ac, msg); +} + +/* + delete a record +*/ +static int ildb_delete(struct ildb_context *ac) +{ + struct ldb_request *req = ac->req; + struct ldap_message *msg; + + msg = new_ldap_message(req); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->type = LDAP_TAG_DelRequest; + + msg->r.DelRequest.dn = ldb_dn_get_extended_linearized(msg, req->op.del.dn, 0); + if (msg->r.DelRequest.dn == NULL) { + talloc_free(msg); + return LDB_ERR_INVALID_DN_SYNTAX; + } + msg->controls = req->controls; + + return ildb_request_send(ac, msg); +} + +/* + rename a record +*/ +static int ildb_rename(struct ildb_context *ac) +{ + struct ldb_request *req = ac->req; + struct ldap_message *msg; + const char *rdn_name; + const struct ldb_val *rdn_val; + + msg = new_ldap_message(req); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->type = LDAP_TAG_ModifyDNRequest; + msg->r.ModifyDNRequest.dn = ldb_dn_get_extended_linearized(msg, req->op.rename.olddn, 0); + if (msg->r.ModifyDNRequest.dn == NULL) { + talloc_free(msg); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + rdn_name = ldb_dn_get_rdn_name(req->op.rename.newdn); + rdn_val = ldb_dn_get_rdn_val(req->op.rename.newdn); + + if ((rdn_name != NULL) && (rdn_val != NULL)) { + msg->r.ModifyDNRequest.newrdn = + talloc_asprintf(msg, "%s=%s", rdn_name, + rdn_val->length > 0 ? ldb_dn_escape_value(msg, *rdn_val) : ""); + } else { + msg->r.ModifyDNRequest.newrdn = talloc_strdup(msg, ""); + } + if (msg->r.ModifyDNRequest.newrdn == NULL) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->r.ModifyDNRequest.newsuperior = + ldb_dn_alloc_linearized(msg, ldb_dn_get_parent(msg, req->op.rename.newdn)); + if (msg->r.ModifyDNRequest.newsuperior == NULL) { + talloc_free(msg); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + msg->r.ModifyDNRequest.deleteolddn = true; + msg->controls = req->controls; + + return ildb_request_send(ac, msg); +} + +static int ildb_start_trans(struct ldb_module *module) +{ + /* TODO implement a local locking mechanism here */ + + return LDB_SUCCESS; +} + +static int ildb_end_trans(struct ldb_module *module) +{ + /* TODO implement a local transaction mechanism here */ + + return LDB_SUCCESS; +} + +static int ildb_del_trans(struct ldb_module *module) +{ + /* TODO implement a local locking mechanism here */ + + return LDB_SUCCESS; +} + +static bool ildb_dn_is_special(struct ldb_request *req) +{ + struct ldb_dn *dn = NULL; + + switch (req->operation) { + case LDB_SEARCH: + dn = req->op.search.base; + break; + case LDB_ADD: + dn = req->op.add.message->dn; + break; + case LDB_MODIFY: + dn = req->op.mod.message->dn; + break; + case LDB_DELETE: + dn = req->op.del.dn; + break; + case LDB_RENAME: + dn = req->op.rename.olddn; + break; + default: + break; + } + + if (dn && ldb_dn_is_special(dn)) { + return true; + } + return false; +} + +static int ildb_handle_request(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ildb_private *ildb; + struct ildb_context *ac; + struct tevent_timer *te; + int ret; + + ildb = talloc_get_type(ldb_module_get_private(module), struct ildb_private); + ldb = ldb_module_get_ctx(module); + + if (req->starttime == 0 || req->timeout == 0) { + ldb_set_errstring(ldb, "Invalid timeout settings"); + return LDB_ERR_TIME_LIMIT_EXCEEDED; + } + + ac = talloc_zero(req, struct ildb_context); + if (ac == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->module = module; + ac->req = req; + ac->ildb = ildb; + + if (ildb_dn_is_special(req)) { + + te = tevent_add_timer(ac->ildb->event_ctx, + ac, timeval_zero(), + ildb_auto_done_callback, ac); + if (NULL == te) { + return LDB_ERR_OPERATIONS_ERROR; + } + + return LDB_SUCCESS; + } + + switch (ac->req->operation) { + case LDB_SEARCH: + ret = ildb_search(ac); + break; + case LDB_ADD: + ret = ildb_add(ac); + break; + case LDB_MODIFY: + ret = ildb_modify(ac); + break; + case LDB_DELETE: + ret = ildb_delete(ac); + break; + case LDB_RENAME: + ret = ildb_rename(ac); + break; + default: + /* no other op supported */ + ret = LDB_ERR_PROTOCOL_ERROR; + break; + } + + return ret; +} + +static const struct ldb_module_ops ildb_ops = { + .name = "ldap", + .search = ildb_handle_request, + .add = ildb_handle_request, + .modify = ildb_handle_request, + .del = ildb_handle_request, + .rename = ildb_handle_request, +/* .request = ildb_handle_request, */ + .start_transaction = ildb_start_trans, + .end_transaction = ildb_end_trans, + .del_transaction = ildb_del_trans, +}; + +/* + connect to the database +*/ +static int ildb_connect(struct ldb_context *ldb, const char *url, + unsigned int flags, const char *options[], + struct ldb_module **_module) +{ + struct ldb_module *module; + struct ildb_private *ildb; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct cli_credentials *creds; + struct loadparm_context *lp_ctx; + + module = ldb_module_new(ldb, ldb, "ldb_ildap backend", &ildb_ops); + if (!module) return LDB_ERR_OPERATIONS_ERROR; + + ildb = talloc(module, struct ildb_private); + if (!ildb) { + ldb_oom(ldb); + goto failed; + } + ldb_module_set_private(module, ildb); + + ildb->event_ctx = ldb_get_event_context(ldb); + + lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + + ildb->ldap = ldap4_new_connection(ildb, lp_ctx, + ildb->event_ctx); + if (!ildb->ldap) { + ldb_oom(ldb); + goto failed; + } + + if (flags & LDB_FLG_RECONNECT) { + ldap_set_reconn_params(ildb->ldap, 10); + } + + status = ldap_connect(ildb->ldap, url); + if (!NT_STATUS_IS_OK(status)) { + ldb_debug(ldb, LDB_DEBUG_ERROR, "Failed to connect to ldap URL '%s' - %s", + url, ldap_errstr(ildb->ldap, module, status)); + goto failed; + } + + /* caller can optionally setup credentials using the opaque token 'credentials' */ + creds = talloc_get_type(ldb_get_opaque(ldb, "credentials"), struct cli_credentials); + if (creds == NULL) { + struct auth_session_info *session_info = talloc_get_type( + ldb_get_opaque(ldb, DSDB_SESSION_INFO), + struct auth_session_info); + if (session_info) { + creds = session_info->credentials; + } + } + + if (creds != NULL && cli_credentials_authentication_requested(creds)) { + const char *bind_dn = cli_credentials_get_bind_dn(creds); + if (bind_dn) { + const char *password = cli_credentials_get_password(creds); + status = ldap_bind_simple(ildb->ldap, bind_dn, password); + if (!NT_STATUS_IS_OK(status)) { + ldb_debug(ldb, LDB_DEBUG_ERROR, "Failed to bind - %s", + ldap_errstr(ildb->ldap, module, status)); + goto failed; + } + } else { + status = ldap_bind_sasl(ildb->ldap, creds, lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + ldb_debug(ldb, LDB_DEBUG_ERROR, "Failed to bind - %s", + ldap_errstr(ildb->ldap, module, status)); + goto failed; + } + } + } + + *_module = module; + return LDB_SUCCESS; + +failed: + if (ildb != NULL && ildb->ldap != NULL) { + ldb_set_errstring(ldb, ldap_errstr(ildb->ldap, module, status)); + } + talloc_free(module); + if (NT_STATUS_IS_LDAP(status)) { + return NT_STATUS_LDAP_CODE(status); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD) + || NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER) + || NT_STATUS_EQUAL(status, NT_STATUS_LOGON_FAILURE) + || NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + return LDB_ERR_INVALID_CREDENTIALS; + } + return LDB_ERR_OPERATIONS_ERROR; +} + +/* + initialise the module + */ +_PUBLIC_ int ldb_ildap_init(const char *ldb_version) +{ + int ret, i; + const char *names[] = { "ldap", "ldaps", "ldapi", NULL }; + for (i=0; names[i]; i++) { + ret = ldb_register_backend(names[i], ildb_connect, true); + if (ret != LDB_SUCCESS) { + return ret; + } + } + return LDB_SUCCESS; +} diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c new file mode 100644 index 0000000..59d1385 --- /dev/null +++ b/lib/ldb-samba/ldb_matching_rules.c @@ -0,0 +1,636 @@ +/* + Unix SMB/CIFS implementation. + + ldb database library - Extended match rules + + Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me> + Copyright (C) Andrew Bartlett <abartlet@samba.org> + + 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/>. +*/ + +#include "includes.h" +#include <ldb_module.h> +#include "dsdb/samdb/samdb.h" +#include "ldb_matching_rules.h" +#include "libcli/security/security.h" +#include "dsdb/common/util.h" +#include "librpc/gen_ndr/ndr_dnsp.h" +#include "lib/util/smb_strtox.h" + +#undef strcasecmp + +static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *attr, + const struct dsdb_dn *dn_to_match, + const char *dn_oid, + struct dsdb_dn *to_visit, + struct dsdb_dn ***visited, + unsigned int *visited_count, + bool *matched) +{ + TALLOC_CTX *tmp_ctx; + int ret, i, j; + struct ldb_result *res; + struct ldb_message *msg; + struct ldb_message_element *el; + const char *attrs[] = { attr, NULL }; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* + * Fetch the entry to_visit + * + * NOTE: This is a new LDB search from the TOP of the module + * stack. This means that this search runs the whole stack + * from top to bottom. + * + * This may seem to be in-efficient, but it is also the only + * way to ensure that the ACLs for this search are applied + * correctly. + * + * Note also that we don't have the original request + * here, so we can not apply controls or timeouts here. + */ + ret = dsdb_search_dn(ldb, + tmp_ctx, + &res, + to_visit->dn, + attrs, + DSDB_MARK_REQ_UNTRUSTED); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + if (res->count != 1) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + msg = res->msgs[0]; + + /* Fetch the attribute to match from the entry being visited */ + el = ldb_msg_find_element(msg, attr); + if (el == NULL) { + /* This entry does not have the attribute to match */ + talloc_free(tmp_ctx); + *matched = false; + return LDB_SUCCESS; + } + + /* + * If the value to match is present in the attribute values of the + * current entry being visited, set matched to true and return OK + */ + for (i=0; i<el->num_values; i++) { + struct dsdb_dn *dn; + dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid); + if (dn == NULL) { + talloc_free(tmp_ctx); + *matched = false; + return LDB_ERR_INVALID_DN_SYNTAX; + } + + if (ldb_dn_compare(dn_to_match->dn, dn->dn) == 0) { + talloc_free(tmp_ctx); + *matched = true; + return LDB_SUCCESS; + } + } + + /* + * If arrived here, the value to match is not in the values of the + * entry being visited. Add the entry being visited (to_visit) + * to the visited array. The array is (re)allocated in the parent + * memory context. + */ + if (visited == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } else if (*visited == NULL) { + *visited = talloc_array(mem_ctx, struct dsdb_dn *, 1); + if (*visited == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + (*visited)[0] = to_visit; + (*visited_count) = 1; + } else { + *visited = talloc_realloc(mem_ctx, *visited, struct dsdb_dn *, + (*visited_count) + 1); + if (*visited == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + (*visited)[(*visited_count)] = to_visit; + (*visited_count)++; + } + + /* + * steal to_visit into visited array context, as it has to live until + * the array is freed. + */ + talloc_steal(*visited, to_visit); + + /* + * Iterate over the values of the attribute of the entry being + * visited (to_visit) and follow them, calling this function + * recursively. + * If the value is in the visited array, skip it. + * Otherwise, follow the link and visit it. + */ + for (i=0; i<el->num_values; i++) { + struct dsdb_dn *next_to_visit; + bool skip = false; + + next_to_visit = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid); + if (next_to_visit == NULL) { + talloc_free(tmp_ctx); + *matched = false; + return LDB_ERR_INVALID_DN_SYNTAX; + } + + /* + * If the value is already in the visited array, skip it. + * Note the last element of the array is ignored because it is + * the current entry DN. + */ + for (j=0; j < (*visited_count) - 1; j++) { + struct dsdb_dn *visited_dn = (*visited)[j]; + if (ldb_dn_compare(visited_dn->dn, + next_to_visit->dn) == 0) { + skip = true; + break; + } + } + if (skip) { + talloc_free(next_to_visit); + continue; + } + + /* If the value is not in the visited array, evaluate it */ + ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr, + dn_to_match, dn_oid, + next_to_visit, + visited, visited_count, + matched); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + if (*matched) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + } + + talloc_free(tmp_ctx); + *matched = false; + return LDB_SUCCESS; +} + +/* + * This function parses the linked attribute value to match, whose syntax + * will be one of the different DN syntaxes, into a ldb_dn struct. + */ +static int ldb_eval_transitive_filter(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *attr, + const struct ldb_val *value_to_match, + struct dsdb_dn *current_object_dn, + bool *matched) +{ + const struct dsdb_schema *schema; + const struct dsdb_attribute *schema_attr; + struct dsdb_dn *dn_to_match; + const char *dn_oid; + unsigned int count; + struct dsdb_dn **visited = NULL; + + schema = dsdb_get_schema(ldb, mem_ctx); + if (schema == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attr); + if (schema_attr == NULL) { + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + + /* This is the DN syntax of the attribute being matched */ + dn_oid = schema_attr->syntax->ldap_oid; + + /* + * Build a ldb_dn struct holding the value to match, which is the + * value entered in the search filter + */ + dn_to_match = dsdb_dn_parse(mem_ctx, ldb, value_to_match, dn_oid); + if (dn_to_match == NULL) { + *matched = false; + return LDB_SUCCESS; + } + + return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr, + dn_to_match, dn_oid, + current_object_dn, + &visited, &count, matched); +} + +/* + * This rule provides recursive search of a link attribute + * + * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL + * This allows a search filter such as: + * + * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com + * + * This searches not only the member attribute, but also any member + * attributes that point at an object with this member in them. All the + * various DN syntax types are supported, not just plain DNs. + * + */ +static int ldb_comparator_trans(struct ldb_context *ldb, + const char *oid, + const struct ldb_message *msg, + const char *attribute_to_match, + const struct ldb_val *value_to_match, + bool *matched) +{ + const struct dsdb_schema *schema; + const struct dsdb_attribute *schema_attr; + struct ldb_dn *msg_dn; + struct dsdb_dn *dsdb_msg_dn; + TALLOC_CTX *tmp_ctx; + int ret; + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* + * If the target attribute to match is not a linked attribute, then + * the filter evaluates to undefined + */ + schema = dsdb_get_schema(ldb, tmp_ctx); + if (schema == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match); + if (schema_attr == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + + /* + * This extended match filter is only valid for linked attributes, + * following the MS definition (the schema attribute has a linkID + * defined). See dochelp request 114111212024789 on cifs-protocols + * mailing list. + */ + if (schema_attr->linkID == 0) { + *matched = false; + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + /* Duplicate original msg dn as the msg must not be modified */ + msg_dn = ldb_dn_copy(tmp_ctx, msg->dn); + if (msg_dn == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* + * Build a dsdb dn from the message copied DN, which should be a plain + * DN syntax. + */ + dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null, + LDB_SYNTAX_DN); + if (dsdb_msg_dn == NULL) { + *matched = false; + return LDB_ERR_INVALID_DN_SYNTAX; + } + + ret = ldb_eval_transitive_filter(tmp_ctx, ldb, + attribute_to_match, + value_to_match, + dsdb_msg_dn, matched); + talloc_free(tmp_ctx); + return ret; +} + + +/* + * This rule provides match of a dns object with expired records. + * + * This allows a search filter such as: + * + * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=3694869 + * + * where the value is a number of hours since the start of 1601. + * + * This allows the caller to find records that should become a DNS + * tomestone, despite that information being deep within an NDR packed + * object + */ +static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb, + const char *oid, + const struct ldb_message *msg, + const char *attribute_to_match, + const struct ldb_val *value_to_match, + bool *matched) +{ + TALLOC_CTX *tmp_ctx; + unsigned int i; + struct ldb_message_element *el = NULL; + struct auth_session_info *session_info = NULL; + uint64_t tombstone_time; + struct dnsp_DnssrvRpcRecord *rec = NULL; + enum ndr_err_code err; + *matched = false; + + /* Needs to be dnsRecord, no match otherwise */ + if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) { + return LDB_SUCCESS; + } + + el = ldb_msg_find_element(msg, attribute_to_match); + if (el == NULL) { + return LDB_SUCCESS; + } + + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + + session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"), + struct auth_session_info); + if (session_info == NULL) { + return ldb_oom(ldb); + } + if (security_session_user_level(session_info, NULL) + != SECURITY_SYSTEM) { + + DBG_ERR("unauthorised access\n"); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + + /* We only expect uint32_t <= 10 digits */ + if (value_to_match->length >= 12) { + DBG_ERR("Invalid timestamp passed\n"); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } else { + int error = 0; + char s[12]; + + memcpy(s, value_to_match->data, value_to_match->length); + s[value_to_match->length] = 0; + if (s[0] == '\0') { + DBG_ERR("Empty timestamp passed\n"); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + tombstone_time = smb_strtoull(s, + NULL, + 10, + &error, + SMB_STR_FULL_STR_CONV); + if (error != 0) { + DBG_ERR("Invalid timestamp string passed\n"); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + } + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + for (i = 0; i < el->num_values; i++) { + rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord); + if (rec == NULL) { + TALLOC_FREE(tmp_ctx); + return ldb_oom(ldb); + } + err = ndr_pull_struct_blob( + &(el->values[i]), + tmp_ctx, + rec, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(err)){ + DBG_ERR("Failed to pull dns rec blob.\n"); + TALLOC_FREE(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (rec->wType == DNS_TYPE_SOA || rec->wType == DNS_TYPE_NS) { + TALLOC_FREE(rec); + continue; + } + + if (rec->wType == DNS_TYPE_TOMBSTONE) { + TALLOC_FREE(rec); + continue; + } + if (rec->dwTimeStamp == 0) { + TALLOC_FREE(rec); + continue; + } + if (rec->dwTimeStamp > tombstone_time) { + TALLOC_FREE(rec); + continue; + } + + *matched = true; + break; + } + + TALLOC_FREE(tmp_ctx); + return LDB_SUCCESS; +} + + +/* + * This rule provides match of a link attribute against a 'should be expunged' criteria + * + * This allows a search filter such as: + * + * member:1.3.6.1.4.1.7165.4.5.2:=131139216000000000 + * + * This searches the member attribute, but also any member attributes + * that are deleted and should be expunged after the specified NTTIME + * time. + * + */ +static int dsdb_match_for_expunge(struct ldb_context *ldb, + const char *oid, + const struct ldb_message *msg, + const char *attribute_to_match, + const struct ldb_val *value_to_match, + bool *matched) +{ + const struct dsdb_schema *schema; + const struct dsdb_attribute *schema_attr; + TALLOC_CTX *tmp_ctx; + unsigned int i; + struct ldb_message_element *el; + struct auth_session_info *session_info; + uint64_t tombstone_time; + *matched = false; + + el = ldb_msg_find_element(msg, attribute_to_match); + if (el == NULL) { + return LDB_SUCCESS; + } + + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + + session_info + = talloc_get_type(ldb_get_opaque(ldb, DSDB_SESSION_INFO), + struct auth_session_info); + if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) { + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + + /* + * If the target attribute to match is not a linked attribute, then + * the filter evaluates to undefined + */ + schema = dsdb_get_schema(ldb, NULL); + if (schema == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* TODO this is O(log n) per attribute */ + schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match); + if (schema_attr == NULL) { + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + + /* + * This extended match filter is only valid for forward linked attributes. + */ + if (schema_attr->linkID == 0 || (schema_attr->linkID & 1) == 1) { + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + + /* Just check we don't allow the caller to fill our stack */ + if (value_to_match->length >=64) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } else { + int error = 0; + char s[value_to_match->length+1]; + + memcpy(s, value_to_match->data, value_to_match->length); + s[value_to_match->length] = 0; + if (s[0] == '\0' || s[0] == '-') { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + tombstone_time = smb_strtoull(s, + NULL, + 10, + &error, + SMB_STR_FULL_STR_CONV); + if (error != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + } + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i = 0; i < el->num_values; i++) { + NTSTATUS status; + struct dsdb_dn *dn; + uint64_t rmd_changetime; + if (dsdb_dn_is_deleted_val(&el->values[i]) == false) { + continue; + } + + dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], + schema_attr->syntax->ldap_oid); + if (dn == NULL) { + DEBUG(1, ("Error: Failed to parse linked attribute blob of %s.\n", el->name)); + continue; + } + + status = dsdb_get_extended_dn_uint64(dn->dn, &rmd_changetime, + "RMD_CHANGETIME"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Error: RMD_CHANGETIME is missing on a forward link.\n")); + continue; + } + + if (rmd_changetime > tombstone_time) { + continue; + } + + *matched = true; + break; + } + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + +int ldb_register_samba_matching_rules(struct ldb_context *ldb) +{ + struct ldb_extended_match_rule *transitive_eval = NULL, + *match_for_expunge = NULL, + *match_for_dns_to_tombstone_time = NULL; + int ret; + + transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule); + transitive_eval->oid = SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL; + transitive_eval->callback = ldb_comparator_trans; + ret = ldb_register_extended_match_rule(ldb, transitive_eval); + if (ret != LDB_SUCCESS) { + talloc_free(transitive_eval); + return ret; + } + + match_for_expunge = talloc_zero(ldb, struct ldb_extended_match_rule); + match_for_expunge->oid = DSDB_MATCH_FOR_EXPUNGE; + match_for_expunge->callback = dsdb_match_for_expunge; + ret = ldb_register_extended_match_rule(ldb, match_for_expunge); + if (ret != LDB_SUCCESS) { + talloc_free(match_for_expunge); + return ret; + } + + match_for_dns_to_tombstone_time = talloc_zero( + ldb, + struct ldb_extended_match_rule); + match_for_dns_to_tombstone_time->oid = DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME; + match_for_dns_to_tombstone_time->callback + = dsdb_match_for_dns_to_tombstone_time; + ret = ldb_register_extended_match_rule(ldb, + match_for_dns_to_tombstone_time); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(match_for_dns_to_tombstone_time); + return ret; + } + + return LDB_SUCCESS; +} diff --git a/lib/ldb-samba/ldb_matching_rules.h b/lib/ldb-samba/ldb_matching_rules.h new file mode 100644 index 0000000..28c4e3d --- /dev/null +++ b/lib/ldb-samba/ldb_matching_rules.h @@ -0,0 +1,30 @@ +/* + Unix SMB/CIFS implementation. + + ldb database library - Extended match rules + + Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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/>. +*/ + +#ifndef _LDB_MATCHING_RULES_H_ +#define _LDB_MATCHING_RULES_H_ + +/* This rule provides recursive search of a link attribute */ +#define SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL "1.2.840.113556.1.4.1941" +#define DSDB_MATCH_FOR_EXPUNGE "1.3.6.1.4.1.7165.4.5.2" +#define DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME "1.3.6.1.4.1.7165.4.5.3" + +#endif /* _LDB_MATCHING_RULES_H_ */ diff --git a/lib/ldb-samba/ldb_wrap.c b/lib/ldb-samba/ldb_wrap.c new file mode 100644 index 0000000..cfc8732 --- /dev/null +++ b/lib/ldb-samba/ldb_wrap.c @@ -0,0 +1,377 @@ +/* + Unix SMB/CIFS implementation. + + LDB wrap functions + + Copyright (C) Andrew Tridgell 2004-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/>. +*/ + +/* + the stupidity of the unix fcntl locking design forces us to never + allow a database file to be opened twice in the same process. These + wrappers provide convenient access to a tdb or ldb, taking advantage + of talloc destructors to ensure that only a single open is done +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "lib/ldb-samba/ldif_handlers.h" +#include "ldb_wrap.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "param/param.h" +#include "../lib/util/dlinklist.h" +#include "lib/util/util_paths.h" +#include <tdb.h> +#include <unistd.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_LDB + +/* + this is used to catch debug messages from ldb +*/ +static void ldb_wrap_debug(void *context, enum ldb_debug_level level, + const char *fmt, va_list ap) PRINTF_ATTRIBUTE(3,0); + +static void ldb_wrap_debug(void *context, enum ldb_debug_level level, + const char *fmt, va_list ap) +{ + int samba_level = -1; + switch (level) { + case LDB_DEBUG_FATAL: + samba_level = DBGLVL_ERR; + break; + case LDB_DEBUG_ERROR: + samba_level = DBGLVL_WARNING; + break; + case LDB_DEBUG_WARNING: + samba_level = DBGLVL_NOTICE; + break; + case LDB_DEBUG_TRACE: + samba_level = DBGLVL_DEBUG + 1; + break; + + }; + if (CHECK_DEBUGLVL(samba_level)) { + char *s = NULL; + int ret; + + ret = vasprintf(&s, fmt, ap); + if (ret == -1) { + return; + } + DEBUG(samba_level, ("ldb: %s\n", s)); + free(s); + } +} + + +/* + connecting to a ldb can be a relatively expensive operation because + of the schema and partition loads. We keep a list of open ldb + contexts here, and try to re-use when possible. + + This means callers of ldb_wrap_connect() must use talloc_unlink() or + the free of a parent to destroy the context + */ +static struct ldb_wrap { + struct ldb_wrap *next, *prev; + struct ldb_wrap_context { + /* the context is what we use to tell if two ldb + * connections are exactly equivalent + */ + pid_t pid; /* We want to re-open in a new PID due to + * the LMDB backend */ + const char *url; + struct tevent_context *ev; + struct loadparm_context *lp_ctx; + struct auth_session_info *session_info; + struct cli_credentials *credentials; + unsigned int flags; + } context; + struct ldb_context *ldb; +} *ldb_wrap_list; + +/* + free a ldb_wrap structure + */ +static int ldb_wrap_destructor(struct ldb_wrap *w) +{ + DLIST_REMOVE(ldb_wrap_list, w); + return 0; +} + +/* + * The casefolder for s4's LDB databases - Unicode-safe + */ +char *wrap_casefold(void *context, void *mem_ctx, const char *s, size_t n) +{ + return strupper_talloc_n(mem_ctx, s, n); +} + + + struct ldb_context *samba_ldb_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials) +{ + struct ldb_context *ldb; + int ret; + + ldb = ldb_init(mem_ctx, ev); + if (ldb == NULL) { + return NULL; + } + + ldb_set_modules_dir(ldb, modules_path(ldb, "ldb")); + + ldb_set_debug(ldb, ldb_wrap_debug, NULL); + + ldb_set_utf8_fns(ldb, NULL, wrap_casefold); + + if (session_info) { + if (ldb_set_opaque(ldb, DSDB_SESSION_INFO, session_info)) { + talloc_free(ldb); + return NULL; + } + } + + if (credentials) { + if (ldb_set_opaque(ldb, "credentials", credentials)) { + talloc_free(ldb); + return NULL; + } + } + + if (ldb_set_opaque(ldb, "loadparm", lp_ctx)) { + talloc_free(ldb); + return NULL; + } + + /* This must be done before we load the schema, as these + * handlers for objectSid and objectGUID etc must take + * precedence over the 'binary attribute' declaration in the + * schema */ + ret = ldb_register_samba_handlers(ldb); + if (ret != LDB_SUCCESS) { + talloc_free(ldb); + return NULL; + } + + /* we usually want Samba databases to be private. If we later + find we need one public, we will need to add a parameter to + ldb_wrap_connect() */ + ldb_set_create_perms(ldb, 0600); + + return ldb; +} + + struct ldb_context *ldb_wrap_find(const char *url, + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + unsigned int flags) +{ + pid_t pid = getpid(); + struct ldb_wrap *w; + /* see if we can re-use an existing ldb */ + for (w=ldb_wrap_list; w; w=w->next) { + if (w->context.pid == pid && + w->context.ev == ev && + w->context.lp_ctx == lp_ctx && + w->context.session_info == session_info && + w->context.credentials == credentials && + w->context.flags == flags && + (w->context.url == url || strcmp(w->context.url, url) == 0)) + return w->ldb; + } + + return NULL; +} + +int samba_ldb_connect(struct ldb_context *ldb, struct loadparm_context *lp_ctx, + const char *url, unsigned int flags) +{ + int ret; + char *real_url = NULL; + + /* allow admins to force non-sync ldb for all databases */ + if (lpcfg_parm_bool(lp_ctx, NULL, "ldb", "nosync", false)) { + flags |= LDB_FLG_NOSYNC; + } + + if (DEBUGLVL(10)) { + flags |= LDB_FLG_ENABLE_TRACING; + } + + real_url = lpcfg_private_path(ldb, lp_ctx, url); + if (real_url == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_connect(ldb, real_url, flags, NULL); + + if (ret != LDB_SUCCESS) { + return ret; + } + + /* setup for leak detection */ + ldb_set_opaque(ldb, "wrap_url", real_url); + + return LDB_SUCCESS; +} + + bool ldb_wrap_add(const char *url, struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + unsigned int flags, + struct ldb_context *ldb) +{ + struct ldb_wrap *w; + struct ldb_wrap_context c; + + /* add to the list of open ldb contexts */ + w = talloc(ldb, struct ldb_wrap); + if (w == NULL) { + return false; + } + + c.pid = getpid(); + c.url = url; + c.ev = ev; + c.lp_ctx = lp_ctx; + c.session_info = session_info; + c.credentials = credentials; + c.flags = flags; + + w->context = c; + w->context.url = talloc_strdup(w, url); + if (w->context.url == NULL) { + return false; + } + + if (session_info) { + /* take a reference to the session_info, as it is + * possible for the ldb to live longer than the + * session_info. This happens when a DRS DsBind call + * reuses a handle, but the original connection is + * shutdown. The token for the new connection is still + * valid, so we need the session_info to remain valid for + * ldb modules to use + */ + if (talloc_reference(w, session_info) == NULL) { + return false; + } + } + + w->ldb = ldb; + + DLIST_ADD(ldb_wrap_list, w); + + talloc_set_destructor(w, ldb_wrap_destructor); + + return true; +} + + +/* + wrapped connection to a ldb database + to close just talloc_free() the returned ldb_context + + TODO: We need an error_string parameter + */ + struct ldb_context *ldb_wrap_connect(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + const char *url, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + unsigned int flags) +{ + struct ldb_context *ldb; + int ret; + + /* + * Unlike samdb_connect_url() do not try and cache the LDB + * handle, get a new one each time. Only sam.ldb is + * punitively expensive to open and helpful caches like this + * cause challenges (such as if the value for 'private dir' + * changes). + */ + + ldb = samba_ldb_init(mem_ctx, ev, lp_ctx, session_info, credentials); + + if (ldb == NULL) + return NULL; + + ret = samba_ldb_connect(ldb, lp_ctx, url, flags); + if (ret != LDB_SUCCESS) { + talloc_free(ldb); + return NULL; + } + + DEBUG(3,("ldb_wrap open of %s\n", url)); + + return ldb; +} + +/* + call tdb_reopen_all() in case there is a TDB open so we are + not blocked from re-opening it inside ldb_tdb. +*/ + void ldb_wrap_fork_hook(void) +{ + if (tdb_reopen_all(1) != 0) { + smb_panic("tdb_reopen_all failed\n"); + } +} + + char *ldb_relative_path(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *name) +{ + const char *base_url = + (const char *)ldb_get_opaque(ldb, "ldb_url"); + char *path, *p, *full_name; + if (name == NULL) { + return NULL; + } + if (strncmp("tdb://", base_url, 6) == 0) { + base_url = base_url+6; + } else if (strncmp("mdb://", base_url, 6) == 0) { + base_url = base_url+6; + } else if (strncmp("ldb://", base_url, 6) == 0) { + base_url = base_url+6; + } + path = talloc_strdup(mem_ctx, base_url); + if (path == NULL) { + return NULL; + } + if ( (p = strrchr(path, '/')) != NULL) { + p[0] = '\0'; + full_name = talloc_asprintf(mem_ctx, "%s/%s", path, name); + } else { + full_name = talloc_asprintf(mem_ctx, "./%s", name); + } + talloc_free(path); + return full_name; +} diff --git a/lib/ldb-samba/ldb_wrap.h b/lib/ldb-samba/ldb_wrap.h new file mode 100644 index 0000000..aa7ccb3 --- /dev/null +++ b/lib/ldb-samba/ldb_wrap.h @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + database wrap headers + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ + +#ifndef _LDB_WRAP_H_ +#define _LDB_WRAP_H_ + +#include <talloc.h> + +struct auth_session_info; +struct ldb_message; +struct ldb_dn; +struct cli_credentials; +struct loadparm_context; +struct tevent_context; + +char *wrap_casefold(void *context, void *mem_ctx, const char *s, size_t n); + +struct ldb_context *ldb_wrap_connect(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + const char *url, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + unsigned int flags); + +void ldb_wrap_fork_hook(void); + +struct ldb_context *samba_ldb_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials); +struct ldb_context *ldb_wrap_find(const char *url, + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + unsigned int flags); +bool ldb_wrap_add(const char *url, struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + unsigned int flags, + struct ldb_context *ldb); +char *ldb_relative_path(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *name); + +int samba_ldb_connect(struct ldb_context *ldb, struct loadparm_context *lp_ctx, + const char *url, unsigned int flags); + +#endif /* _LDB_WRAP_H_ */ diff --git a/lib/ldb-samba/ldif_handlers.c b/lib/ldb-samba/ldif_handlers.c new file mode 100644 index 0000000..78a4337 --- /dev/null +++ b/lib/ldb-samba/ldif_handlers.c @@ -0,0 +1,1836 @@ +/* + ldb database library - ldif handlers for Samba + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Andrew Bartlett 2006-2009 + Copyright (C) Matthias Dieter Wallnöfer 2009 + ** NOTE! The following LGPL license applies to the ldb + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <ldb.h> +#include <ldb_module.h> +#include "ldb_handlers.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_dnsp.h" +#include "librpc/ndr/libndr.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "../lib/util/asn1.h" +#include "lib/util/smb_strtox.h" + +/* + use ndr_print_* to convert a NDR formatted blob to a ldif formatted blob + + If mask_errors is true, then function succeeds but out data + is set to "<Unable to decode binary data>" message + + \return 0 on success; -1 on error +*/ +static int ldif_write_NDR(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out, + size_t struct_size, + ndr_pull_flags_fn_t pull_fn, + ndr_print_fn_t print_fn, + bool mask_errors) +{ + uint8_t *p; + enum ndr_err_code err; + if (!(ldb_get_flags(ldb) & LDB_FLG_SHOW_BINARY)) { + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + p = talloc_size(mem_ctx, struct_size); + err = ndr_pull_struct_blob(in, mem_ctx, + p, pull_fn); + if (err != NDR_ERR_SUCCESS) { + /* fail in not in mask_error mode */ + if (!mask_errors) { + return -1; + } + talloc_free(p); + out->data = (uint8_t *)talloc_strdup(mem_ctx, "<Unable to decode binary data>"); + out->length = strlen((const char *)out->data); + return 0; + } + out->data = (uint8_t *)ndr_print_struct_string(mem_ctx, print_fn, "NDR", p); + talloc_free(p); + if (out->data == NULL) { + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + out->length = strlen((char *)out->data); + return 0; +} + +/* + convert a ldif formatted objectSid to a NDR formatted blob +*/ +static int ldif_read_objectSid(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + bool ret; + enum ndr_err_code ndr_err; + struct dom_sid sid; + if (in->length > DOM_SID_STR_BUFLEN) { + return -1; + } else { + char p[in->length+1]; + memcpy(p, in->data, in->length); + p[in->length] = '\0'; + + ret = dom_sid_parse(p, &sid); + if (ret == false) { + return -1; + } + + *out = data_blob_talloc(mem_ctx, NULL, + ndr_size_dom_sid(&sid, 0)); + if (out->data == NULL) { + return -1; + } + + ndr_err = ndr_push_struct_into_fixed_blob(out, &sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return -1; + } + } + return 0; +} + +/* + convert a NDR formatted blob to a ldif formatted objectSid +*/ +int ldif_write_objectSid(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct dom_sid sid; + enum ndr_err_code ndr_err; + + ndr_err = ndr_pull_struct_blob_all_noalloc(in, &sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return -1; + } + *out = data_blob_string_const(dom_sid_string(mem_ctx, &sid)); + if (out->data == NULL) { + return -1; + } + return 0; +} + +bool ldif_comparision_objectSid_isString(const struct ldb_val *v) +{ + if (v->length < 3) { + return false; + } + + if (strncmp("S-", (const char *)v->data, 2) != 0) return false; + + return true; +} + +/* + compare two objectSids +*/ +static int ldif_comparison_objectSid(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, const struct ldb_val *v2) +{ + if (ldif_comparision_objectSid_isString(v1) && ldif_comparision_objectSid_isString(v2)) { + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); + } else if (ldif_comparision_objectSid_isString(v1) + && !ldif_comparision_objectSid_isString(v2)) { + struct ldb_val v; + int ret; + if (ldif_read_objectSid(ldb, mem_ctx, v1, &v) != 0) { + /* Perhaps not a string after all */ + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); + } + ret = ldb_comparison_binary(ldb, mem_ctx, &v, v2); + talloc_free(v.data); + return ret; + } else if (!ldif_comparision_objectSid_isString(v1) + && ldif_comparision_objectSid_isString(v2)) { + struct ldb_val v; + int ret; + if (ldif_read_objectSid(ldb, mem_ctx, v2, &v) != 0) { + /* Perhaps not a string after all */ + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); + } + ret = ldb_comparison_binary(ldb, mem_ctx, v1, &v); + talloc_free(v.data); + return ret; + } + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); +} + +/* + canonicalise a objectSid +*/ +static int ldif_canonicalise_objectSid(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + if (ldif_comparision_objectSid_isString(in)) { + if (ldif_read_objectSid(ldb, mem_ctx, in, out) != 0) { + /* Perhaps not a string after all */ + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + return 0; + } + return ldb_handler_copy(ldb, mem_ctx, in, out); +} + +static int extended_dn_read_SID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct dom_sid sid; + enum ndr_err_code ndr_err; + if (ldif_comparision_objectSid_isString(in)) { + if (ldif_read_objectSid(ldb, mem_ctx, in, out) == 0) { + return 0; + } + } + + /* Perhaps not a string after all */ + *out = data_blob_talloc(mem_ctx, NULL, in->length/2+1); + + if (!out->data) { + return -1; + } + + (*out).length = strhex_to_str((char *)out->data, out->length, + (const char *)in->data, in->length); + + /* Check it looks like a SID */ + ndr_err = ndr_pull_struct_blob_all_noalloc(out, &sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return -1; + } + return 0; +} + +/* + convert a ldif formatted objectGUID to a NDR formatted blob +*/ +static int ldif_read_objectGUID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct GUID guid; + NTSTATUS status; + + status = GUID_from_data_blob(in, &guid); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + status = GUID_to_ndr_blob(&guid, mem_ctx, out); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + return 0; +} + +/* + convert a NDR formatted blob to a ldif formatted objectGUID +*/ +static int ldif_write_objectGUID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct GUID guid; + NTSTATUS status; + + status = GUID_from_ndr_blob(in, &guid); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + out->data = (uint8_t *)GUID_string(mem_ctx, &guid); + if (out->data == NULL) { + return -1; + } + out->length = strlen((const char *)out->data); + return 0; +} + +static bool ldif_comparision_objectGUID_isString(const struct ldb_val *v) +{ + if (v->length != 36 && v->length != 38) return false; + + /* Might be a GUID string, can't be a binary GUID (fixed 16 bytes) */ + return true; +} + +static int extended_dn_read_GUID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + + if (in->length == 36 && ldif_read_objectGUID(ldb, mem_ctx, in, out) == 0) { + return 0; + } + + /* Try as 'hex' form */ + if (in->length != 32) { + return -1; + } + + *out = data_blob_talloc(mem_ctx, NULL, in->length/2+1); + + if (!out->data) { + return -1; + } + + (*out).length = strhex_to_str((char *)out->data, out->length, + (const char *)in->data, in->length); + + /* Check it looks like a GUID */ + if ((*out).length != 16) { + data_blob_free(out); + return -1; + } + + return 0; +} + +/* + compare two objectGUIDs +*/ +static int ldif_comparison_objectGUID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, const struct ldb_val *v2) +{ + if (ldif_comparision_objectGUID_isString(v1) && ldif_comparision_objectGUID_isString(v2)) { + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); + } else if (ldif_comparision_objectGUID_isString(v1) + && !ldif_comparision_objectGUID_isString(v2)) { + struct ldb_val v; + int ret; + if (ldif_read_objectGUID(ldb, mem_ctx, v1, &v) != 0) { + /* Perhaps it wasn't a valid string after all */ + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); + } + ret = ldb_comparison_binary(ldb, mem_ctx, &v, v2); + talloc_free(v.data); + return ret; + } else if (!ldif_comparision_objectGUID_isString(v1) + && ldif_comparision_objectGUID_isString(v2)) { + struct ldb_val v; + int ret; + if (ldif_read_objectGUID(ldb, mem_ctx, v2, &v) != 0) { + /* Perhaps it wasn't a valid string after all */ + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); + } + ret = ldb_comparison_binary(ldb, mem_ctx, v1, &v); + talloc_free(v.data); + return ret; + } + return ldb_comparison_binary(ldb, mem_ctx, v1, v2); +} + +/* + canonicalise a objectGUID +*/ +static int ldif_canonicalise_objectGUID(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + if (ldif_comparision_objectGUID_isString(in)) { + if (ldif_read_objectGUID(ldb, mem_ctx, in, out) != 0) { + /* Perhaps it wasn't a valid string after all */ + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + return 0; + } + return ldb_handler_copy(ldb, mem_ctx, in, out); +} + + +/* + convert a ldif (SDDL) formatted ntSecurityDescriptor to a NDR formatted blob +*/ +static int ldif_read_ntSecurityDescriptor(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct security_descriptor *sd; + enum ndr_err_code ndr_err; + + sd = talloc(mem_ctx, struct security_descriptor); + if (sd == NULL) { + return -1; + } + + ndr_err = ndr_pull_struct_blob(in, sd, sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* If this does not parse, then it is probably SDDL, and we should try it that way */ + + const struct dom_sid *sid = samdb_domain_sid(ldb); + talloc_free(sd); + sd = sddl_decode(mem_ctx, (const char *)in->data, sid); + if (sd == NULL) { + return -1; + } + } + + ndr_err = ndr_push_struct_blob(out, mem_ctx, sd, + (ndr_push_flags_fn_t)ndr_push_security_descriptor); + talloc_free(sd); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return -1; + } + + return 0; +} + +/* + convert a NDR formatted blob to a ldif formatted ntSecurityDescriptor (SDDL format) +*/ +static int ldif_write_ntSecurityDescriptor(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct security_descriptor *sd; + enum ndr_err_code ndr_err; + + if (ldb_get_flags(ldb) & LDB_FLG_SHOW_BINARY) { + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct security_descriptor), + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor, + (ndr_print_fn_t)ndr_print_security_descriptor, + true); + + } + + sd = talloc(mem_ctx, struct security_descriptor); + if (sd == NULL) { + return -1; + } + /* We can't use ndr_pull_struct_blob_all because this contains relative pointers */ + ndr_err = ndr_pull_struct_blob(in, sd, sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(sd); + return -1; + } + out->data = (uint8_t *)sddl_encode(mem_ctx, sd, samdb_domain_sid_cache_only(ldb)); + talloc_free(sd); + if (out->data == NULL) { + return -1; + } + out->length = strlen((const char *)out->data); + return 0; +} + +/* + convert a string formatted SDDL to a ldif formatted ntSecurityDescriptor (SDDL format) +*/ +static int ldif_write_sddlSecurityDescriptor(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + if (ldb_get_flags(ldb) & LDB_FLG_SHOW_BINARY) { + struct security_descriptor *sd; + const struct dom_sid *sid = samdb_domain_sid(ldb); + + sd = sddl_decode(mem_ctx, (const char *)in->data, sid); + out->data = (uint8_t *)ndr_print_struct_string(mem_ctx, + (ndr_print_fn_t)ndr_print_security_descriptor, + "SDDL", sd); + out->length = strlen((const char *)out->data); + talloc_free(sd); + return 0; + } + + return ldb_handler_copy(ldb, mem_ctx, in, out); +} + +/* + canonicalise an objectCategory. We use the long form as the canonical form: + 'person' becomes cn=Person,cn=Schema,cn=Configuration,<basedn> + + Also any short name of an objectClass that points to a different + class (such as user) has the canonical form of the class it's + defaultObjectCategory points to (eg + cn=Person,cn=Schema,cn=Configuration,<basedn>) +*/ + +static int ldif_canonicalise_objectCategory(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct ldb_dn *dn1 = NULL; + const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL); + const struct dsdb_class *sclass; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!schema) { + talloc_free(tmp_ctx); + *out = data_blob_talloc(mem_ctx, in->data, in->length); + if (in->data && !out->data) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; + } + dn1 = ldb_dn_from_ldb_val(tmp_ctx, ldb, in); + if ( ! ldb_dn_validate(dn1)) { + const char *lDAPDisplayName = talloc_strndup(tmp_ctx, (char *)in->data, in->length); + sclass = dsdb_class_by_lDAPDisplayName(schema, lDAPDisplayName); + if (sclass) { + struct ldb_dn *dn = ldb_dn_new(tmp_ctx, ldb, + sclass->defaultObjectCategory); + if (dn == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + *out = data_blob_string_const(ldb_dn_alloc_casefold(mem_ctx, dn)); + talloc_free(tmp_ctx); + + if (!out->data) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; + } else { + *out = data_blob_talloc(mem_ctx, in->data, in->length); + talloc_free(tmp_ctx); + + if (in->data && !out->data) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; + } + } + *out = data_blob_string_const(ldb_dn_alloc_casefold(mem_ctx, dn1)); + talloc_free(tmp_ctx); + + if (!out->data) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; +} + +static int ldif_comparison_objectCategory(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, + const struct ldb_val *v2) +{ + return ldb_any_comparison(ldb, mem_ctx, ldif_canonicalise_objectCategory, + v1, v2); +} + +/* + convert a NDR formatted blob to a ldif formatted schemaInfo +*/ +static int ldif_write_schemaInfo(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct repsFromToBlob), + (ndr_pull_flags_fn_t)ndr_pull_schemaInfoBlob, + (ndr_print_fn_t)ndr_print_schemaInfoBlob, + true); +} + +/* + convert a ldif formatted prefixMap to a NDR formatted blob +*/ +static int ldif_read_prefixMap(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct prefixMapBlob *blob; + enum ndr_err_code ndr_err; + char *string, *line, *p, *oid; + DATA_BLOB oid_blob; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (tmp_ctx == NULL) { + return -1; + } + + blob = talloc_zero(tmp_ctx, struct prefixMapBlob); + if (blob == NULL) { + talloc_free(tmp_ctx); + return -1; + } + + /* use the switch value to detect if this is in the binary + * format + */ + if (in->length >= 4 && IVAL(in->data, 0) == PREFIX_MAP_VERSION_DSDB) { + ndr_err = ndr_pull_struct_blob(in, tmp_ctx, blob, + (ndr_pull_flags_fn_t)ndr_pull_prefixMapBlob); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ndr_err = ndr_push_struct_blob(out, mem_ctx, + blob, + (ndr_push_flags_fn_t)ndr_push_prefixMapBlob); + talloc_free(tmp_ctx); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return -1; + } + return 0; + } + } + + /* If this does not parse, then it is probably the text version, and we should try it that way */ + blob->version = PREFIX_MAP_VERSION_DSDB; + + string = talloc_strndup(mem_ctx, (const char *)in->data, in->length); + if (string == NULL) { + talloc_free(blob); + return -1; + } + + line = string; + while (line && line[0]) { + int error = 0; + + p=strchr(line, ';'); + if (p) { + p[0] = '\0'; + } else { + p=strchr(line, '\n'); + if (p) { + p[0] = '\0'; + } + } + /* allow a trailing separator */ + if (line == p) { + break; + } + + blob->ctr.dsdb.mappings = talloc_realloc(blob, + blob->ctr.dsdb.mappings, + struct drsuapi_DsReplicaOIDMapping, + blob->ctr.dsdb.num_mappings+1); + if (!blob->ctr.dsdb.mappings) { + talloc_free(tmp_ctx); + return -1; + } + + blob->ctr.dsdb.mappings[blob->ctr.dsdb.num_mappings].id_prefix = + smb_strtoul(line, &oid, 10, &error, SMB_STR_STANDARD); + + if (oid[0] != ':' || error != 0) { + talloc_free(tmp_ctx); + return -1; + } + + /* we know there must be at least ":" */ + oid++; + + if (!ber_write_partial_OID_String(blob->ctr.dsdb.mappings, &oid_blob, oid)) { + talloc_free(tmp_ctx); + return -1; + } + blob->ctr.dsdb.mappings[blob->ctr.dsdb.num_mappings].oid.length = oid_blob.length; + blob->ctr.dsdb.mappings[blob->ctr.dsdb.num_mappings].oid.binary_oid = oid_blob.data; + + blob->ctr.dsdb.num_mappings++; + + /* Now look past the terminator we added above */ + if (p) { + line = p + 1; + } else { + line = NULL; + } + } + + ndr_err = ndr_push_struct_blob(out, mem_ctx, + blob, + (ndr_push_flags_fn_t)ndr_push_prefixMapBlob); + talloc_free(tmp_ctx); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return -1; + } + return 0; +} + +/* + convert a NDR formatted blob to a ldif formatted prefixMap +*/ +static int ldif_write_prefixMap(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct prefixMapBlob *blob; + enum ndr_err_code ndr_err; + char *string; + uint32_t i; + + if (ldb_get_flags(ldb) & LDB_FLG_SHOW_BINARY) { + int err; + /* try to decode the blob as S4 prefixMap */ + err = ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct prefixMapBlob), + (ndr_pull_flags_fn_t)ndr_pull_prefixMapBlob, + (ndr_print_fn_t)ndr_print_prefixMapBlob, + false); + if (0 == err) { + return err; + } + /* try parsing it as Windows PrefixMap value */ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct drsuapi_MSPrefixMap_Ctr), + (ndr_pull_flags_fn_t)ndr_pull_drsuapi_MSPrefixMap_Ctr, + (ndr_print_fn_t)ndr_print_drsuapi_MSPrefixMap_Ctr, + true); + } + + blob = talloc(mem_ctx, struct prefixMapBlob); + if (blob == NULL) { + return -1; + } + ndr_err = ndr_pull_struct_blob_all(in, blob, + blob, + (ndr_pull_flags_fn_t)ndr_pull_prefixMapBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + goto failed; + } + if (blob->version != PREFIX_MAP_VERSION_DSDB) { + goto failed; + } + string = talloc_strdup(mem_ctx, ""); + if (string == NULL) { + goto failed; + } + + for (i=0; i < blob->ctr.dsdb.num_mappings; i++) { + DATA_BLOB oid_blob; + char *partial_oid = NULL; + + if (i > 0) { + string = talloc_asprintf_append(string, ";"); + } + + oid_blob = data_blob_const(blob->ctr.dsdb.mappings[i].oid.binary_oid, + blob->ctr.dsdb.mappings[i].oid.length); + if (!ber_read_partial_OID_String(blob, oid_blob, &partial_oid)) { + DEBUG(0, ("ber_read_partial_OID failed on prefixMap item with id: 0x%X", + blob->ctr.dsdb.mappings[i].id_prefix)); + goto failed; + } + string = talloc_asprintf_append(string, "%u:%s", + blob->ctr.dsdb.mappings[i].id_prefix, + partial_oid); + talloc_free(discard_const(partial_oid)); + if (string == NULL) { + goto failed; + } + } + + talloc_free(blob); + *out = data_blob_string_const(string); + return 0; + +failed: + talloc_free(blob); + return -1; +} + +static bool ldif_comparision_prefixMap_isString(const struct ldb_val *v) +{ + if (v->length < 4) { + return true; + } + + if (IVAL(v->data, 0) == PREFIX_MAP_VERSION_DSDB) { + return false; + } + + return true; +} + +/* + canonicalise a prefixMap +*/ +static int ldif_canonicalise_prefixMap(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + if (ldif_comparision_prefixMap_isString(in)) { + return ldif_read_prefixMap(ldb, mem_ctx, in, out); + } + return ldb_handler_copy(ldb, mem_ctx, in, out); +} + +static int ldif_comparison_prefixMap(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, + const struct ldb_val *v2) +{ + return ldb_any_comparison(ldb, mem_ctx, ldif_canonicalise_prefixMap, + v1, v2); +} + +/* length limited conversion of a ldb_val to a int32_t */ +static int val_to_int32(const struct ldb_val *in, int32_t *v) +{ + char *end; + char buf[64]; + + /* make sure we don't read past the end of the data */ + if (in->length > sizeof(buf)-1) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + strncpy(buf, (char *)in->data, in->length); + buf[in->length] = 0; + + /* We've to use "strtoll" here to have the intended overflows. + * Otherwise we may get "LONG_MAX" and the conversion is wrong. */ + *v = (int32_t) strtoll(buf, &end, 0); + if (*end != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + return LDB_SUCCESS; +} + +/* length limited conversion of a ldb_val to a int64_t */ +static int val_to_int64(const struct ldb_val *in, int64_t *v) +{ + char *end; + char buf[64]; + + /* make sure we don't read past the end of the data */ + if (in->length > sizeof(buf)-1) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + strncpy(buf, (char *)in->data, in->length); + buf[in->length] = 0; + + *v = (int64_t) strtoll(buf, &end, 0); + if (*end != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + return LDB_SUCCESS; +} + +/* Canonicalisation of two 32-bit integers */ +static int ldif_canonicalise_int32(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + int32_t i; + int ret; + + ret = val_to_int32(in, &i); + if (ret != LDB_SUCCESS) { + return ret; + } + out->data = (uint8_t *) talloc_asprintf(mem_ctx, "%d", i); + if (out->data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + out->length = strlen((char *)out->data); + return 0; +} + +/* + * Lexicographically sorted representation for a 32-bit integer + * + * [ INT32_MIN ... -3, -2, -1 | 0 | +1, +2, +3 ... INT32_MAX ] + * n o p + * + * Refer to the comment in lib/ldb/common/attrib_handlers.c for the + * corresponding documentation for 64-bit integers. + * + * The same rules apply but use INT32_MIN and INT32_MAX. + * + * String representation padding is done to 10 characters. + * + * INT32_MAX = 2^31 - 1 = 2147483647 (10 characters long) + * + */ +static int ldif_index_format_int32(struct ldb_context *ldb, + void *mem_ctx, + const struct ldb_val *in, + struct ldb_val *out) +{ + int32_t i; + int ret; + char prefix; + size_t len; + + ret = val_to_int32(in, &i); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (i < 0) { + /* + * i is negative, so this is subtraction rather than + * wrap-around. + */ + prefix = 'n'; + i = INT32_MAX + i + 1; + } else if (i > 0) { + prefix = 'p'; + } else { + prefix = 'o'; + } + + out->data = (uint8_t *) talloc_asprintf(mem_ctx, "%c%010ld", prefix, (long)i); + if (out->data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + len = talloc_array_length(out->data) - 1; + if (len != 11) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + __location__ ": expected index format str %s to" + " have length 11 but got %zu", + (char*)out->data, len); + return LDB_ERR_OPERATIONS_ERROR; + } + + out->length = 11; + return 0; +} + +/* Comparison of two 32-bit integers */ +static int ldif_comparison_int32(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, const struct ldb_val *v2) +{ + int32_t i1=0, i2=0; + val_to_int32(v1, &i1); + val_to_int32(v2, &i2); + if (i1 == i2) return 0; + return i1 > i2? 1 : -1; +} + +/* Canonicalisation of two 64-bit integers */ +static int ldif_canonicalise_int64(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + int64_t i; + int ret; + + ret = val_to_int64(in, &i); + if (ret != LDB_SUCCESS) { + return ret; + } + out->data = (uint8_t *) talloc_asprintf(mem_ctx, "%lld", (long long)i); + if (out->data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + out->length = strlen((char *)out->data); + return 0; +} + +/* Comparison of two 64-bit integers */ +static int ldif_comparison_int64(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, const struct ldb_val *v2) +{ + int64_t i1=0, i2=0; + val_to_int64(v1, &i1); + val_to_int64(v2, &i2); + if (i1 == i2) return 0; + return i1 > i2? 1 : -1; +} + +/* + convert a NDR formatted blob to a ldif formatted repsFromTo +*/ +static int ldif_write_repsFromTo(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct repsFromToBlob), + (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob, + (ndr_print_fn_t)ndr_print_repsFromToBlob, + true); +} + +/* + convert a NDR formatted blob to a ldif formatted replPropertyMetaData +*/ +static int ldif_write_replPropertyMetaData(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct replPropertyMetaDataBlob), + (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob, + (ndr_print_fn_t)ndr_print_replPropertyMetaDataBlob, + true); +} + +/* + convert a NDR formatted blob to a ldif formatted replUpToDateVector +*/ +static int ldif_write_replUpToDateVector(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct replUpToDateVectorBlob), + (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob, + (ndr_print_fn_t)ndr_print_replUpToDateVectorBlob, + true); +} + +static int ldif_write_dn_binary_NDR(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out, + size_t struct_size, + ndr_pull_flags_fn_t pull_fn, + ndr_print_fn_t print_fn, + bool mask_errors) +{ + uint8_t *p = NULL; + enum ndr_err_code err; + struct dsdb_dn *dsdb_dn = NULL; + char *dn_str = NULL; + char *str = NULL; + + if (!(ldb_get_flags(ldb) & LDB_FLG_SHOW_BINARY)) { + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + dsdb_dn = dsdb_dn_parse(mem_ctx, ldb, in, DSDB_SYNTAX_BINARY_DN); + if (dsdb_dn == NULL) { + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + p = talloc_size(dsdb_dn, struct_size); + if (p == NULL) { + TALLOC_FREE(dsdb_dn); + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + err = ndr_pull_struct_blob(&dsdb_dn->extra_part, p, p, pull_fn); + if (err != NDR_ERR_SUCCESS) { + /* fail in not in mask_error mode */ + if (!mask_errors) { + return -1; + } + TALLOC_FREE(dsdb_dn); + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + dn_str = ldb_dn_get_extended_linearized(dsdb_dn, dsdb_dn->dn, 1); + if (dn_str == NULL) { + TALLOC_FREE(dsdb_dn); + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + str = ndr_print_struct_string(mem_ctx, print_fn, dn_str, p); + TALLOC_FREE(dsdb_dn); + if (str == NULL) { + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + *out = data_blob_string_const(str); + return 0; +} + +static int ldif_write_msDS_RevealedUsers(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_dn_binary_NDR(ldb, mem_ctx, in, out, + sizeof(struct replPropertyMetaData1), + (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaData1, + (ndr_print_fn_t)ndr_print_replPropertyMetaData1, + true); +} + +/* + convert a NDR formatted blob to a ldif formatted dnsRecord +*/ +static int ldif_write_dnsRecord(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct dnsp_DnssrvRpcRecord), + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord, + (ndr_print_fn_t)ndr_print_dnsp_DnssrvRpcRecord, + true); +} + +/* + convert a NDR formatted blob to a ldif formatted dnsProperty +*/ +static int ldif_write_dnsProperty(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct dnsp_DnsProperty), + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty, + (ndr_print_fn_t)ndr_print_dnsp_DnsProperty, + true); +} + +/* + convert a NDR formatted blob of a supplementalCredentials into text +*/ +static int ldif_write_supplementalCredentialsBlob(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct supplementalCredentialsBlob), + (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob, + (ndr_print_fn_t)ndr_print_supplementalCredentialsBlob, + true); +} + +/* + convert a NDR formatted blob to a ldif formatted trustAuthInOutBlob +*/ +static int ldif_write_trustAuthInOutBlob(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct trustAuthInOutBlob), + (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob, + (ndr_print_fn_t)ndr_print_trustAuthInOutBlob, + true); +} + +/* + convert a NDR formatted blob to a ldif formatted msDS-TrustForestTrustInfo +*/ +static int ldif_write_ForestTrustInfo(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct ForestTrustInfo), + (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo, + (ndr_print_fn_t)ndr_print_ForestTrustInfo, + true); +} +/* + convert a NDR formatted blob of a partialAttributeSet into text +*/ +static int ldif_write_partialAttributeSet(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + return ldif_write_NDR(ldb, mem_ctx, in, out, + sizeof(struct partialAttributeSetBlob), + (ndr_pull_flags_fn_t)ndr_pull_partialAttributeSetBlob, + (ndr_print_fn_t)ndr_print_partialAttributeSetBlob, + true); +} + + +static int extended_dn_write_hex(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + *out = data_blob_string_const(data_blob_hex_string_lower(mem_ctx, in)); + if (!out->data) { + return -1; + } + return 0; +} + +/* + compare two dns +*/ +static int samba_ldb_dn_link_comparison(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, const struct ldb_val *v2) +{ + struct ldb_dn *dn1 = NULL, *dn2 = NULL; + int ret; + + if (dsdb_dn_is_deleted_val(v1)) { + /* If the DN is deleted, then we can't search for it */ + return -1; + } + + if (dsdb_dn_is_deleted_val(v2)) { + /* If the DN is deleted, then we can't search for it */ + return -1; + } + + dn1 = ldb_dn_from_ldb_val(mem_ctx, ldb, v1); + if ( ! ldb_dn_validate(dn1)) return -1; + + dn2 = ldb_dn_from_ldb_val(mem_ctx, ldb, v2); + if ( ! ldb_dn_validate(dn2)) { + talloc_free(dn1); + return -1; + } + + ret = ldb_dn_compare(dn1, dn2); + + talloc_free(dn1); + talloc_free(dn2); + return ret; +} + +static int samba_ldb_dn_link_canonicalise(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct ldb_dn *dn; + int ret = -1; + + out->length = 0; + out->data = NULL; + + dn = ldb_dn_from_ldb_val(mem_ctx, ldb, in); + if ( ! ldb_dn_validate(dn)) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + + /* By including the RMD_FLAGS of a deleted DN, we ensure it + * does not casually match a not deleted DN */ + if (dsdb_dn_is_deleted_val(in)) { + out->data = (uint8_t *)talloc_asprintf(mem_ctx, + "<RMD_FLAGS=%u>%s", + dsdb_dn_val_rmd_flags(in), + ldb_dn_get_casefold(dn)); + } else { + out->data = (uint8_t *)ldb_dn_alloc_casefold(mem_ctx, dn); + } + + if (out->data == NULL) { + goto done; + } + out->length = strlen((char *)out->data); + + ret = 0; + +done: + talloc_free(dn); + + return ret; +} + + +/* + write a 64 bit 2-part range +*/ +static int ldif_write_range64(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + int64_t v; + int ret; + ret = val_to_int64(in, &v); + if (ret != LDB_SUCCESS) { + return ret; + } + out->data = (uint8_t *)talloc_asprintf(mem_ctx, "%lu-%lu", + (unsigned long)(v&0xFFFFFFFF), + (unsigned long)(v>>32)); + if (out->data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + out->length = strlen((char *)out->data); + return LDB_SUCCESS; +} + +/* + read a 64 bit 2-part range +*/ +static int ldif_read_range64(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + unsigned long high, low; + char buf[64]; + + if (memchr(in->data, '-', in->length) == NULL) { + return ldb_handler_copy(ldb, mem_ctx, in, out); + } + + if (in->length > sizeof(buf)-1) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + strncpy(buf, (const char *)in->data, in->length); + buf[in->length] = 0; + + if (sscanf(buf, "%lu-%lu", &low, &high) != 2) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + out->data = (uint8_t *)talloc_asprintf(mem_ctx, "%llu", + (unsigned long long)(((uint64_t)high)<<32) | (low)); + + if (out->data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + out->length = strlen((char *)out->data); + return LDB_SUCCESS; +} + +/* + when this operator_fn is set for a syntax, the backend calls is in + preference to the comparison function. We are told the exact + comparison operation that is needed, and we can return errors + */ +static int samba_syntax_operator_fn(struct ldb_context *ldb, enum ldb_parse_op operation, + const struct ldb_schema_attribute *a, + const struct ldb_val *v1, const struct ldb_val *v2, bool *matched) +{ + switch (operation) { + case LDB_OP_AND: + case LDB_OP_OR: + case LDB_OP_NOT: + case LDB_OP_SUBSTRING: + case LDB_OP_APPROX: + case LDB_OP_EXTENDED: + /* handled in the backends */ + return LDB_ERR_INAPPROPRIATE_MATCHING; + + case LDB_OP_GREATER: + case LDB_OP_LESS: + case LDB_OP_EQUALITY: + { + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + int ret; + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + ret = a->syntax->comparison_fn(ldb, tmp_ctx, v1, v2); + talloc_free(tmp_ctx); + if (operation == LDB_OP_GREATER) { + *matched = (ret >= 0); + } else if (operation == LDB_OP_LESS) { + *matched = (ret <= 0); + } else { + *matched = (ret == 0); + } + return LDB_SUCCESS; + } + + case LDB_OP_PRESENT: + *matched = true; + return LDB_SUCCESS; + } + + /* we shouldn't get here */ + return LDB_ERR_INAPPROPRIATE_MATCHING; +} + +/* + compare two binary objects. This is correct for sorting as the sort order is: + + a + aa + b + bb + + rather than ldb_comparison_binary() which is: + + a + b + aa + bb + +*/ +static int samba_ldb_comparison_binary(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, const struct ldb_val *v2) +{ + return data_blob_cmp(v1, v2); +} + +/* + when this operator_fn is set for a syntax, the backend calls is in + preference to the comparison function. We are told the exact + comparison operation that is needed, and we can return errors. + + This mode optimises for ldb_comparison_binary() if we need equality, + as this should be faster as it can do a length-check first. + */ +static int samba_syntax_binary_operator_fn(struct ldb_context *ldb, enum ldb_parse_op operation, + const struct ldb_schema_attribute *a, + const struct ldb_val *v1, const struct ldb_val *v2, bool *matched) +{ + if (operation == LDB_OP_EQUALITY) { + *matched = (ldb_comparison_binary(ldb, NULL, v1, v2) == 0); + return LDB_SUCCESS; + } + return samba_syntax_operator_fn(ldb, operation, a, v1, v2, matched); +} + +/* + see if two DNs match, comparing first by GUID, then by SID, and + finally by string components + */ +static int samba_dn_extended_match(struct ldb_context *ldb, + const struct ldb_val *v1, + const struct ldb_val *v2, + bool *matched) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *dn1, *dn2; + const struct ldb_val *guid1, *guid2, *sid1, *sid2; + uint32_t rmd_flags1, rmd_flags2; + + tmp_ctx = talloc_new(ldb); + + dn1 = ldb_dn_from_ldb_val(tmp_ctx, ldb, v1); + dn2 = ldb_dn_from_ldb_val(tmp_ctx, ldb, v2); + if (!dn1 || !dn2) { + /* couldn't parse as DN's */ + talloc_free(tmp_ctx); + (*matched) = false; + return LDB_SUCCESS; + } + + rmd_flags1 = dsdb_dn_rmd_flags(dn1); + rmd_flags2 = dsdb_dn_rmd_flags(dn2); + + if ((rmd_flags1 & DSDB_RMD_FLAG_DELETED) != + (rmd_flags2 & DSDB_RMD_FLAG_DELETED)) { + /* only match if they have the same deletion status */ + talloc_free(tmp_ctx); + (*matched) = false; + return LDB_SUCCESS; + } + + + guid1 = ldb_dn_get_extended_component(dn1, "GUID"); + guid2 = ldb_dn_get_extended_component(dn2, "GUID"); + if (guid1 && guid2) { + (*matched) = (data_blob_cmp(guid1, guid2) == 0); + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + sid1 = ldb_dn_get_extended_component(dn1, "SID"); + sid2 = ldb_dn_get_extended_component(dn2, "SID"); + if (sid1 && sid2) { + (*matched) = (data_blob_cmp(sid1, sid2) == 0); + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + (*matched) = (ldb_dn_compare(dn1, dn2) == 0); + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +/* + special operation for DNs, to take account of the RMD_FLAGS deleted bit + */ +static int samba_syntax_operator_dn(struct ldb_context *ldb, enum ldb_parse_op operation, + const struct ldb_schema_attribute *a, + const struct ldb_val *v1, const struct ldb_val *v2, bool *matched) +{ + if (operation == LDB_OP_PRESENT && dsdb_dn_is_deleted_val(v1)) { + /* If the DN is deleted, then we can't search for it */ + + /* should this be for equality too? */ + *matched = false; + return LDB_SUCCESS; + } + + if (operation == LDB_OP_EQUALITY && + samba_dn_extended_match(ldb, v1, v2, matched) == LDB_SUCCESS) { + return LDB_SUCCESS; + } + + return samba_syntax_operator_fn(ldb, operation, a, v1, v2, matched); +} + + +static const struct ldb_schema_syntax samba_syntaxes[] = { + { + .name = LDB_SYNTAX_SAMBA_SID, + .ldif_read_fn = ldif_read_objectSid, + .ldif_write_fn = ldif_write_objectSid, + .canonicalise_fn = ldif_canonicalise_objectSid, + .comparison_fn = ldif_comparison_objectSid, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_SECURITY_DESCRIPTOR, + .ldif_read_fn = ldif_read_ntSecurityDescriptor, + .ldif_write_fn = ldif_write_ntSecurityDescriptor, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_SDDL_SECURITY_DESCRIPTOR, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_sddlSecurityDescriptor, + .canonicalise_fn = ldb_handler_fold, + .comparison_fn = ldb_comparison_fold, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_GUID, + .ldif_read_fn = ldif_read_objectGUID, + .ldif_write_fn = ldif_write_objectGUID, + .canonicalise_fn = ldif_canonicalise_objectGUID, + .comparison_fn = ldif_comparison_objectGUID, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_OBJECT_CATEGORY, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldb_handler_copy, + .canonicalise_fn = ldif_canonicalise_objectCategory, + .comparison_fn = ldif_comparison_objectCategory, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_SCHEMAINFO, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_schemaInfo, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_PREFIX_MAP, + .ldif_read_fn = ldif_read_prefixMap, + .ldif_write_fn = ldif_write_prefixMap, + .canonicalise_fn = ldif_canonicalise_prefixMap, + .comparison_fn = ldif_comparison_prefixMap, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_INT32, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldb_handler_copy, + .canonicalise_fn = ldif_canonicalise_int32, + .index_format_fn = ldif_index_format_int32, + .comparison_fn = ldif_comparison_int32, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_REPSFROMTO, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_repsFromTo, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_REPLPROPERTYMETADATA, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_replPropertyMetaData, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_REPLUPTODATEVECTOR, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_replUpToDateVector, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_REVEALEDUSERS, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_msDS_RevealedUsers, + .canonicalise_fn = dsdb_dn_binary_canonicalise, + .comparison_fn = dsdb_dn_binary_comparison, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_TRUSTAUTHINOUTBLOB, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_trustAuthInOutBlob, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_FORESTTRUSTINFO, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_ForestTrustInfo, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = DSDB_SYNTAX_BINARY_DN, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldb_handler_copy, + .canonicalise_fn = dsdb_dn_binary_canonicalise, + .comparison_fn = dsdb_dn_binary_comparison, + .operator_fn = samba_syntax_operator_fn + },{ + .name = DSDB_SYNTAX_STRING_DN, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldb_handler_copy, + .canonicalise_fn = dsdb_dn_string_canonicalise, + .comparison_fn = dsdb_dn_string_comparison, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_DN, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldb_handler_copy, + .canonicalise_fn = samba_ldb_dn_link_canonicalise, + .comparison_fn = samba_ldb_dn_link_comparison, + .operator_fn = samba_syntax_operator_dn + },{ + .name = LDB_SYNTAX_SAMBA_RANGE64, + .ldif_read_fn = ldif_read_range64, + .ldif_write_fn = ldif_write_range64, + .canonicalise_fn = ldif_canonicalise_int64, + .comparison_fn = ldif_comparison_int64, + .operator_fn = samba_syntax_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_DNSRECORD, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_dnsRecord, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_DNSPROPERTY, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_dnsProperty, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_SUPPLEMENTALCREDENTIALS, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_supplementalCredentialsBlob, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_PARTIALATTRIBUTESET, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldif_write_partialAttributeSet, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + },{ + .name = LDB_SYNTAX_SAMBA_OCTET_STRING, + .ldif_read_fn = ldb_handler_copy, + .ldif_write_fn = ldb_handler_copy, + .canonicalise_fn = ldb_handler_copy, + .comparison_fn = samba_ldb_comparison_binary, + .operator_fn = samba_syntax_binary_operator_fn + } +}; + +static const struct ldb_dn_extended_syntax samba_dn_syntax[] = { + { + .name = "SID", + .read_fn = extended_dn_read_SID, + .write_clear_fn = ldif_write_objectSid, + .write_hex_fn = extended_dn_write_hex + },{ + .name = "GUID", + .read_fn = extended_dn_read_GUID, + .write_clear_fn = ldif_write_objectGUID, + .write_hex_fn = extended_dn_write_hex + },{ + .name = "WKGUID", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + },{ + .name = "RMD_INVOCID", + .read_fn = extended_dn_read_GUID, + .write_clear_fn = ldif_write_objectGUID, + .write_hex_fn = extended_dn_write_hex + },{ + .name = "RMD_FLAGS", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + },{ + .name = "RMD_ADDTIME", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + },{ + .name = "RMD_CHANGETIME", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + },{ + .name = "RMD_LOCAL_USN", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + },{ + .name = "RMD_ORIGINATING_USN", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + },{ + .name = "RMD_VERSION", + .read_fn = ldb_handler_copy, + .write_clear_fn = ldb_handler_copy, + .write_hex_fn = ldb_handler_copy + } +}; + +/* TODO: Should be dynamic at some point */ +static const struct { + const char *name; + const char *syntax; +} samba_attributes[] = { + { "ntSecurityDescriptor", LDB_SYNTAX_SAMBA_SECURITY_DESCRIPTOR }, + { "oMSyntax", LDB_SYNTAX_SAMBA_INT32 }, + { "objectCategory", LDB_SYNTAX_SAMBA_OBJECT_CATEGORY }, + { "schemaInfo", LDB_SYNTAX_SAMBA_SCHEMAINFO }, + { "prefixMap", LDB_SYNTAX_SAMBA_PREFIX_MAP }, + { "repsFrom", LDB_SYNTAX_SAMBA_REPSFROMTO }, + { "repsTo", LDB_SYNTAX_SAMBA_REPSFROMTO }, + { "replPropertyMetaData", LDB_SYNTAX_SAMBA_REPLPROPERTYMETADATA }, + { "replUpToDateVector", LDB_SYNTAX_SAMBA_REPLUPTODATEVECTOR }, + { "msDS-RevealedUsers", LDB_SYNTAX_SAMBA_REVEALEDUSERS }, + { "trustAuthIncoming", LDB_SYNTAX_SAMBA_TRUSTAUTHINOUTBLOB }, + { "trustAuthOutgoing", LDB_SYNTAX_SAMBA_TRUSTAUTHINOUTBLOB }, + { "msDS-TrustForestTrustInfo", LDB_SYNTAX_SAMBA_FORESTTRUSTINFO }, + { "rIDAllocationPool", LDB_SYNTAX_SAMBA_RANGE64 }, + { "rIDPreviousAllocationPool", LDB_SYNTAX_SAMBA_RANGE64 }, + { "rIDAvailablePool", LDB_SYNTAX_SAMBA_RANGE64 }, + { "defaultSecurityDescriptor", LDB_SYNTAX_SAMBA_SDDL_SECURITY_DESCRIPTOR }, + + /* + * these are extracted by searching + * (&(attributeSyntax=2.5.5.17)(omSyntax=4)) + * + * Except: msAuthz-CentralAccessPolicyID as it might be a GUID see: + * adminDescription: For a Central Access Policy, this attribute defines a GUID t + * hat can be used to identify the set of policies when applied to a resource. + * Until we see a msAuthz-CentralAccessPolicyID value on a windows + * server, we ignore it here. + */ + { "mS-DS-CreatorSID", LDB_SYNTAX_SAMBA_SID }, + { "msDS-QuotaTrustee", LDB_SYNTAX_SAMBA_SID }, + { "objectSid", LDB_SYNTAX_SAMBA_SID }, + { "tokenGroups", LDB_SYNTAX_SAMBA_SID }, + { "tokenGroupsGlobalAndUniversal", LDB_SYNTAX_SAMBA_SID }, + { "tokenGroupsNoGCAcceptable", LDB_SYNTAX_SAMBA_SID }, + { "securityIdentifier", LDB_SYNTAX_SAMBA_SID }, + { "sIDHistory", LDB_SYNTAX_SAMBA_SID }, + { "syncWithSID", LDB_SYNTAX_SAMBA_SID }, + + /* + * these are extracted by searching + * (&(attributeSyntax=2.5.5.10)(rangeLower=16)(rangeUpper=16)(omSyntax=4)) + */ + { "attributeSecurityGUID", LDB_SYNTAX_SAMBA_GUID }, + { "categoryId", LDB_SYNTAX_SAMBA_GUID }, + { "controlAccessRights", LDB_SYNTAX_SAMBA_GUID }, + { "currMachineId", LDB_SYNTAX_SAMBA_GUID }, + { "fRSReplicaSetGUID", LDB_SYNTAX_SAMBA_GUID }, + { "fRSVersionGUID", LDB_SYNTAX_SAMBA_GUID }, + { "implementedCategories", LDB_SYNTAX_SAMBA_GUID }, + { "msDS-AzObjectGuid", LDB_SYNTAX_SAMBA_GUID }, + { "msDS-GenerationId", LDB_SYNTAX_SAMBA_GUID }, + { "msDS-OptionalFeatureGUID", LDB_SYNTAX_SAMBA_GUID }, + { "msDFSR-ContentSetGuid", LDB_SYNTAX_SAMBA_GUID }, + { "msDFSR-ReplicationGroupGuid", LDB_SYNTAX_SAMBA_GUID }, + { "mSMQDigests", LDB_SYNTAX_SAMBA_GUID }, + { "mSMQOwnerID", LDB_SYNTAX_SAMBA_GUID }, + { "mSMQQMID", LDB_SYNTAX_SAMBA_GUID }, + { "mSMQQueueType", LDB_SYNTAX_SAMBA_GUID }, + { "mSMQSites", LDB_SYNTAX_SAMBA_GUID }, + { "netbootGUID", LDB_SYNTAX_SAMBA_GUID }, + { "objectGUID", LDB_SYNTAX_SAMBA_GUID }, + { "pKTGuid", LDB_SYNTAX_SAMBA_GUID }, + { "requiredCategories", LDB_SYNTAX_SAMBA_GUID }, + { "schemaIDGUID", LDB_SYNTAX_SAMBA_GUID }, + { "siteGUID", LDB_SYNTAX_SAMBA_GUID }, + { "msDFS-GenerationGUIDv2", LDB_SYNTAX_SAMBA_GUID }, + { "msDFS-LinkIdentityGUIDv2", LDB_SYNTAX_SAMBA_GUID }, + { "msDFS-NamespaceIdentityGUIDv2", LDB_SYNTAX_SAMBA_GUID }, + { "msSPP-CSVLKSkuId", LDB_SYNTAX_SAMBA_GUID }, + { "msSPP-KMSIds", LDB_SYNTAX_SAMBA_GUID }, + + /* + * these are known to be GUIDs + */ + { "invocationId", LDB_SYNTAX_SAMBA_GUID }, + { "parentGUID", LDB_SYNTAX_SAMBA_GUID }, + + /* These NDR encoded things we want to be able to read with --show-binary */ + { "dnsRecord", LDB_SYNTAX_SAMBA_DNSRECORD }, + { "dNSProperty", LDB_SYNTAX_SAMBA_DNSPROPERTY }, + { "supplementalCredentials", LDB_SYNTAX_SAMBA_SUPPLEMENTALCREDENTIALS}, + { "partialAttributeSet", LDB_SYNTAX_SAMBA_PARTIALATTRIBUTESET} +}; + +const struct ldb_schema_syntax *ldb_samba_syntax_by_name(struct ldb_context *ldb, const char *name) +{ + unsigned int j; + const struct ldb_schema_syntax *s = NULL; + + for (j=0; j < ARRAY_SIZE(samba_syntaxes); j++) { + if (strcmp(name, samba_syntaxes[j].name) == 0) { + s = &samba_syntaxes[j]; + break; + } + } + return s; +} + +const struct ldb_schema_syntax *ldb_samba_syntax_by_lDAPDisplayName(struct ldb_context *ldb, const char *name) +{ + unsigned int j; + const struct ldb_schema_syntax *s = NULL; + + for (j=0; j < ARRAY_SIZE(samba_attributes); j++) { + if (strcmp(samba_attributes[j].name, name) == 0) { + s = ldb_samba_syntax_by_name(ldb, samba_attributes[j].syntax); + break; + } + } + + return s; +} + +static const char *secret_attributes[] = {DSDB_SECRET_ATTRIBUTES, "secret", + "priorSecret", NULL}; + +/* + register the samba ldif handlers +*/ +int ldb_register_samba_handlers(struct ldb_context *ldb) +{ + unsigned int i; + int ret; + + if (ldb_get_opaque(ldb, "SAMBA_HANDLERS_REGISTERED") != NULL) { + return LDB_SUCCESS; + } + + ret = ldb_set_opaque(ldb, LDB_SECRET_ATTRIBUTE_LIST_OPAQUE, discard_const_p(char *, secret_attributes)); + if (ret != LDB_SUCCESS) { + return ret; + } + + for (i=0; i < ARRAY_SIZE(samba_attributes); i++) { + const struct ldb_schema_syntax *s = NULL; + + s = ldb_samba_syntax_by_name(ldb, samba_attributes[i].syntax); + + if (!s) { + s = ldb_standard_syntax_by_name(ldb, samba_attributes[i].syntax); + } + + if (!s) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_schema_attribute_add_with_syntax(ldb, samba_attributes[i].name, LDB_ATTR_FLAG_FIXED, s); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + for (i=0; i < ARRAY_SIZE(samba_dn_syntax); i++) { + ret = ldb_dn_extended_add_syntax(ldb, LDB_ATTR_FLAG_FIXED, &samba_dn_syntax[i]); + if (ret != LDB_SUCCESS) { + return ret; + } + + } + + ret = ldb_register_samba_matching_rules(ldb); + if (ret != LDB_SUCCESS) { + talloc_free(ldb); + return LDB_SUCCESS; + } + + ret = ldb_set_opaque(ldb, "SAMBA_HANDLERS_REGISTERED", (void*)1); + if (ret != LDB_SUCCESS) { + return ret; + } + + return LDB_SUCCESS; +} diff --git a/lib/ldb-samba/ldif_handlers.h b/lib/ldb-samba/ldif_handlers.h new file mode 100644 index 0000000..8b2ade6 --- /dev/null +++ b/lib/ldb-samba/ldif_handlers.h @@ -0,0 +1,30 @@ +#ifndef __LIB_LDB_SAMBA_LDIF_HANDLERS_H__ +#define __LIB_LDB_SAMBA_LDIF_HANDLERS_H__ + +#define LDB_SYNTAX_SAMBA_SID "LDB_SYNTAX_SAMBA_SID" +#define LDB_SYNTAX_SAMBA_SECURITY_DESCRIPTOR "1.2.840.113556.1.4.907" +#define LDB_SYNTAX_SAMBA_GUID "LDB_SYNTAX_SAMBA_GUID" +#define LDB_SYNTAX_SAMBA_OBJECT_CATEGORY "LDB_SYNTAX_SAMBA_OBJECT_CATEGORY" +#define LDB_SYNTAX_SAMBA_SCHEMAINFO "LDB_SYNTAX_SAMBA_SCHEMAINFO" +#define LDB_SYNTAX_SAMBA_PREFIX_MAP "LDB_SYNTAX_SAMBA_PREFIX_MAP" +#define LDB_SYNTAX_SAMBA_INT32 "LDB_SYNTAX_SAMBA_INT32" +#define LDB_SYNTAX_SAMBA_REPSFROMTO "LDB_SYNTAX_SAMBA_REPSFROMTO" +#define LDB_SYNTAX_SAMBA_REPLPROPERTYMETADATA "LDB_SYNTAX_SAMBA_REPLPROPERTYMETADATA" +#define LDB_SYNTAX_SAMBA_REPLUPTODATEVECTOR "LDB_SYNTAX_SAMBA_REPLUPTODATEVECTOR" +#define LDB_SYNTAX_SAMBA_REVEALEDUSERS "LDB_SYNTAX_SAMBA_REVEALEDUSERS" +#define LDB_SYNTAX_SAMBA_RANGE64 "LDB_SYNTAX_SAMBA_RANGE64" +#define LDB_SYNTAX_SAMBA_DNSRECORD "LDB_SYNTAX_SAMBA_DNSRECORD" +#define LDB_SYNTAX_SAMBA_DNSPROPERTY "LDB_SYNTAX_SAMBA_DNSPROPERTY" +#define LDB_SYNTAX_SAMBA_SUPPLEMENTALCREDENTIALS "LDB_SYNTAX_SAMBA_SUPPLEMENTALCREDENTIALS" +#define LDB_SYNTAX_SAMBA_SDDL_SECURITY_DESCRIPTOR "LDB_SYNTAX_SAMBA_SDDL" +#define LDB_SYNTAX_SAMBA_TRUSTAUTHINOUTBLOB "LDB_SYNTAX_SAMBA_TRUSTAUTHINOUTBLOB" +#define LDB_SYNTAX_SAMBA_FORESTTRUSTINFO "LDB_SYNTAX_SAMBA_FORESTTRUSTINFO" +#define LDB_SYNTAX_SAMBA_PARTIALATTRIBUTESET "LDB_SYNTAX_SAMBA_PARTIALATTRIBUTESET" +#define LDB_SYNTAX_SAMBA_OCTET_STRING "LDB_SYNTAX_SAMBA_OCTET_STRING" +#include "lib/ldb-samba/ldif_handlers_proto.h" + +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) + +#endif /* __LIB_LDB_SAMBA_LDIF_HANDLERS_H__ */ + diff --git a/lib/ldb-samba/pyldb.c b/lib/ldb-samba/pyldb.c new file mode 100644 index 0000000..bea4894 --- /dev/null +++ b/lib/ldb-samba/pyldb.c @@ -0,0 +1,320 @@ +/* + Unix SMB/CIFS implementation. + + Python interface to ldb, Samba-specific functions + + Copyright (C) 2007-2010 Jelmer Vernooij <jelmer@samba.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <Python.h> +#include "python/py3compat.h" +#include "includes.h" +#include <ldb.h> +#include <pyldb.h> +#include "param/pyparam.h" +#include "auth/credentials/pycredentials.h" +#include "ldb_wrap.h" +#include "lib/ldb-samba/ldif_handlers.h" +#include "auth/pyauth.h" +#include "source4/dsdb/common/util.h" +#include "lib/ldb/include/ldb_private.h" + + +static PyObject *pyldb_module; +static PyObject *py_ldb_error; +static PyTypeObject PySambaLdb; + +static void PyErr_SetLdbError(PyObject *error, int ret, struct ldb_context *ldb_ctx) +{ + if (ret == LDB_ERR_PYTHON_EXCEPTION) + return; /* Python exception should already be set, just keep that */ + + PyErr_SetObject(error, + Py_BuildValue(discard_const_p(char, "(i,s)"), ret, + ldb_ctx == NULL?ldb_strerror(ret):ldb_errstring(ldb_ctx))); +} + +static PyObject *py_ldb_set_loadparm(PyObject *self, PyObject *args) +{ + PyObject *py_lp_ctx; + struct loadparm_context *lp_ctx; + struct ldb_context *ldb; + + if (!PyArg_ParseTuple(args, "O", &py_lp_ctx)) + return NULL; + + ldb = pyldb_Ldb_AS_LDBCONTEXT(self); + + lp_ctx = lpcfg_from_py_object(ldb, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm object"); + return NULL; + } + + ldb_set_opaque(ldb, "loadparm", lp_ctx); + + Py_RETURN_NONE; +} + +static PyObject *py_ldb_set_credentials(PyObject *self, PyObject *args) +{ + PyObject *py_creds; + struct cli_credentials *creds; + struct ldb_context *ldb; + + if (!PyArg_ParseTuple(args, "O", &py_creds)) + return NULL; + + creds = cli_credentials_from_py_object(py_creds); + if (creds == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected credentials object"); + return NULL; + } + + ldb = pyldb_Ldb_AS_LDBCONTEXT(self); + + ldb_set_opaque(ldb, "credentials", creds); + + Py_RETURN_NONE; +} + +/* XXX: This function really should be in libldb's pyldb.c */ +static PyObject *py_ldb_set_opaque_integer(PyObject *self, PyObject *args) +{ + int value; + int *old_val, *new_val; + char *py_opaque_name, *opaque_name_talloc; + struct ldb_context *ldb; + int ret; + TALLOC_CTX *tmp_ctx; + + if (!PyArg_ParseTuple(args, "si", &py_opaque_name, &value)) + return NULL; + + ldb = pyldb_Ldb_AS_LDBCONTEXT(self); + + /* see if we have a cached copy */ + old_val = (int *)ldb_get_opaque(ldb, py_opaque_name); + /* XXX: We shouldn't just blindly assume that the value that is + * already present has the size of an int and is not shared + * with other code that may rely on it not changing. + * JRV 20100403 */ + + if (old_val) { + *old_val = value; + Py_RETURN_NONE; + } + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + new_val = talloc(tmp_ctx, int); + if (new_val == NULL) { + talloc_free(tmp_ctx); + PyErr_NoMemory(); + return NULL; + } + + opaque_name_talloc = talloc_strdup(tmp_ctx, py_opaque_name); + if (opaque_name_talloc == NULL) { + talloc_free(tmp_ctx); + PyErr_NoMemory(); + return NULL; + } + + *new_val = value; + + /* cache the domain_sid in the ldb */ + ret = ldb_set_opaque(ldb, opaque_name_talloc, new_val); + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + PyErr_SetLdbError(py_ldb_error, ret, ldb); + return NULL; + } + + talloc_steal(ldb, new_val); + talloc_steal(ldb, opaque_name_talloc); + talloc_free(tmp_ctx); + + Py_RETURN_NONE; +} + +static PyObject *py_ldb_set_utf8_casefold(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + struct ldb_context *ldb; + + ldb = pyldb_Ldb_AS_LDBCONTEXT(self); + + ldb_set_utf8_fns(ldb, NULL, wrap_casefold); + + Py_RETURN_NONE; +} + +static PyObject *py_ldb_set_session_info(PyObject *self, PyObject *args) +{ + PyObject *py_session_info; + struct auth_session_info *info; + struct ldb_context *ldb; + PyObject *mod_samba_auth; + PyObject *PyAuthSession_Type; + bool ret; + + mod_samba_auth = PyImport_ImportModule("samba.dcerpc.auth"); + if (mod_samba_auth == NULL) + return NULL; + + PyAuthSession_Type = PyObject_GetAttrString(mod_samba_auth, "session_info"); + if (PyAuthSession_Type == NULL) { + Py_CLEAR(mod_samba_auth); + return NULL; + } + + ret = PyArg_ParseTuple(args, "O!", PyAuthSession_Type, &py_session_info); + + Py_DECREF(PyAuthSession_Type); + Py_DECREF(mod_samba_auth); + + if (!ret) + return NULL; + + ldb = pyldb_Ldb_AS_LDBCONTEXT(self); + + info = PyAuthSession_AsSession(py_session_info); + + ldb_set_opaque(ldb, DSDB_SESSION_INFO, info); + + Py_RETURN_NONE; +} + +static PyObject *py_ldb_samba_schema_attribute_add(PyLdbObject *self, + PyObject *args) +{ + char *attribute, *syntax; + const struct ldb_schema_syntax *s; + unsigned int flags; + int ret; + struct ldb_context *ldb_ctx; + + if (!PyArg_ParseTuple(args, "sIs", &attribute, &flags, &syntax)) + return NULL; + + ldb_ctx = pyldb_Ldb_AsLdbContext((PyObject *)self); + + s = ldb_samba_syntax_by_name(ldb_ctx, syntax); + ret = ldb_schema_attribute_add_with_syntax(ldb_ctx, attribute, + flags, s); + + PyErr_LDB_ERROR_IS_ERR_RAISE(py_ldb_error, ret, ldb_ctx); + + Py_RETURN_NONE; +} + +static PyObject *py_ldb_register_samba_handlers(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + struct ldb_context *ldb; + int ret; + + /* XXX: Perhaps call this from PySambaLdb's init function ? */ + + ldb = pyldb_Ldb_AS_LDBCONTEXT(self); + ret = ldb_register_samba_handlers(ldb); + + PyErr_LDB_ERROR_IS_ERR_RAISE(py_ldb_error, ret, ldb); + + Py_RETURN_NONE; +} + +static PyMethodDef py_samba_ldb_methods[] = { + { "set_loadparm", (PyCFunction)py_ldb_set_loadparm, METH_VARARGS, + "ldb_set_loadparm(session_info)\n" + "Set loadparm context to use when connecting." }, + { "set_credentials", (PyCFunction)py_ldb_set_credentials, METH_VARARGS, + "ldb_set_credentials(credentials)\n" + "Set credentials to use when connecting." }, + { "set_opaque_integer", (PyCFunction)py_ldb_set_opaque_integer, + METH_VARARGS, NULL }, + { "set_utf8_casefold", (PyCFunction)py_ldb_set_utf8_casefold, + METH_NOARGS, + "ldb_set_utf8_casefold()\n" + "Set the right Samba casefolding function for UTF8 charset." }, + { "register_samba_handlers", (PyCFunction)py_ldb_register_samba_handlers, + METH_NOARGS, + "register_samba_handlers()\n" + "Register Samba-specific LDB modules and schemas." }, + { "set_session_info", (PyCFunction)py_ldb_set_session_info, METH_VARARGS, + "set_session_info(session_info)\n" + "Set session info to use when connecting." }, + { "samba_schema_attribute_add", + (PyCFunction)py_ldb_samba_schema_attribute_add, + METH_VARARGS, NULL }, + {0}, +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "_ldb", + .m_doc = "Samba-specific LDB python bindings", + .m_size = -1, + .m_methods = py_samba_ldb_methods, +}; + +static PyTypeObject PySambaLdb = { + .tp_name = "samba._ldb.Ldb", + .tp_doc = "Connection to a LDB database.", + .tp_methods = py_samba_ldb_methods, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, +}; + + +MODULE_INIT_FUNC(_ldb) +{ + PyObject *m; + + pyldb_module = PyImport_ImportModule("ldb"); + if (pyldb_module == NULL) + return NULL; + + PySambaLdb.tp_base = (PyTypeObject *)PyObject_GetAttrString(pyldb_module, "Ldb"); + if (PySambaLdb.tp_base == NULL) { + Py_CLEAR(pyldb_module); + return NULL; + } + + py_ldb_error = PyObject_GetAttrString(pyldb_module, "LdbError"); + + Py_CLEAR(pyldb_module); + + if (PyType_Ready(&PySambaLdb) < 0) + return NULL; + + m = PyModule_Create(&moduledef); + if (m == NULL) + return NULL; + + Py_INCREF(&PySambaLdb); + PyModule_AddObject(m, "Ldb", (PyObject *)&PySambaLdb); + +#define ADD_LDB_STRING(val) PyModule_AddStringConstant(m, #val, LDB_## val) + ADD_LDB_STRING(SYNTAX_SAMBA_INT32); + + return m; +} diff --git a/lib/ldb-samba/samba_extensions.c b/lib/ldb-samba/samba_extensions.c new file mode 100644 index 0000000..be92d98 --- /dev/null +++ b/lib/ldb-samba/samba_extensions.c @@ -0,0 +1,169 @@ +/* + ldb database library - samba extensions + + Copyright (C) Andrew Tridgell 2010 + + ** NOTE! The following LGPL license applies to the ldb + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" +#include "ldb_module.h" +#include "lib/cmdline/cmdline.h" +#include "auth/gensec/gensec.h" +#include "auth/auth.h" +#include "param/param.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "ldb_wrap.h" +#include "popt.h" + + +static bool is_popt_table_end(const struct poptOption *o) +{ + if (o->longName == NULL && + o->shortName =='\0' && + o->arg == NULL) { + return true; + } + + return false; +} + +/* + work out the length of a popt array + */ +static size_t calculate_popt_array_length(struct poptOption *opts) +{ + size_t i = 0; + + for (i = 0; i < UINT32_MAX; i++) { + struct poptOption *o = &(opts[i]); + + if (is_popt_table_end(o)) { + break; + } + } + + return i; +} + +/* + called to register additional command line options + */ +static int extensions_hook(struct ldb_context *ldb, enum ldb_module_hook_type t) +{ + switch (t) { + case LDB_MODULE_HOOK_CMDLINE_OPTIONS: { + size_t len1, len2; + struct poptOption **popt_options = ldb_module_popt_options(ldb); + struct poptOption *new_array = NULL; + bool ok; + + struct poptOption cmdline_extensions[] = { + POPT_COMMON_SAMBA_LDB + POPT_COMMON_CONNECTION + POPT_COMMON_CREDENTIALS + POPT_LEGACY_S4 + POPT_COMMON_VERSION + POPT_TABLEEND + }; + + ok = samba_cmdline_init(ldb, + SAMBA_CMDLINE_CONFIG_CLIENT, + false /* require_smbconf */); + if (!ok) { + return ldb_oom(ldb); + } + + len1 = calculate_popt_array_length(*popt_options); + len2 = calculate_popt_array_length(cmdline_extensions); + new_array = talloc_array(ldb, + struct poptOption, + len1 + len2 + 1); + if (NULL == new_array) { + return ldb_oom(ldb); + } + + memcpy(new_array, *popt_options, len1*sizeof(struct poptOption)); + memcpy(new_array+len1, cmdline_extensions, (1+len2)*sizeof(struct poptOption)); + +#ifdef DEVELOPER + ok = samba_cmdline_sanity_check(new_array); + if (!ok) { + talloc_free(new_array); + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Duplicate cmdline options detected!"); + } +#endif + + (*popt_options) = new_array; + return LDB_SUCCESS; + } + + case LDB_MODULE_HOOK_CMDLINE_PRECONNECT: { + struct loadparm_context *lp_ctx = NULL; + struct cli_credentials *creds = NULL; + + int r = ldb_register_samba_handlers(ldb); + if (r != LDB_SUCCESS) { + return ldb_operr(ldb); + } + gensec_init(); + + lp_ctx = samba_cmdline_get_lp_ctx(); + creds = samba_cmdline_get_creds(); + + if (ldb_set_opaque( + ldb, + DSDB_SESSION_INFO, + system_session(lp_ctx))) { + + return ldb_operr(ldb); + } + if (ldb_set_opaque(ldb, "credentials", creds)) { + return ldb_operr(ldb); + } + if (ldb_set_opaque(ldb, "loadparm", lp_ctx)) { + return ldb_operr(ldb); + } + + ldb_set_utf8_fns(ldb, NULL, wrap_casefold); + break; + } + + case LDB_MODULE_HOOK_CMDLINE_POSTCONNECT: + /* get the domain SID into the cache for SDDL processing */ + samdb_domain_sid(ldb); + break; + } + + return LDB_SUCCESS; +} + + +/* + initialise the module + */ +_PUBLIC_ int ldb_samba_extensions_init(const char *ldb_version) +{ + ldb_register_hook(extensions_hook); + + return LDB_SUCCESS; +} diff --git a/lib/ldb-samba/tests/index.py b/lib/ldb-samba/tests/index.py new file mode 100644 index 0000000..2256e3e --- /dev/null +++ b/lib/ldb-samba/tests/index.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Tests for comparison expressions on indexed keys +# +# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019 +# +# 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/>. +# +"""Tests for expressions containing comparisons on indexed attributes. + Copied from ldb's index.py""" + +import os +from unittest import TestCase +import sys +from samba import _ldb +import shutil +from ldb import SCOPE_SUBTREE +from samba.tests.subunitrun import TestProgram + +PY3 = sys.version_info > (3, 0) + +TDB_PREFIX = "tdb://" +MDB_PREFIX = "mdb://" + +def tempdir(): + import tempfile + try: + dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp") + except KeyError: + dir_prefix = None + return tempfile.mkdtemp(dir=dir_prefix) + +class LdbBaseTest(TestCase): + def setUp(self): + super(LdbBaseTest, self).setUp() + try: + if self.prefix is None: + self.prefix = TDB_PREFIX + except AttributeError: + self.prefix = TDB_PREFIX + + def tearDown(self): + super(LdbBaseTest, self).tearDown() + + def url(self): + return self.prefix + self.filename + + def flags(self): + if self.prefix == MDB_PREFIX: + return ldb.FLG_NOSYNC + else: + return 0 + + def options(self): + if self.prefix == MDB_PREFIX: + return ['disable_full_db_scan_for_self_test:1'] + else: + return None + +class LdbTDBIndexedComparisonExpressions(LdbBaseTest): + def tearDown(self): + shutil.rmtree(self.testdir) + super(LdbTDBIndexedComparisonExpressions, self).tearDown() + + # Ensure the LDB is closed now, so we close the FD + del(self.l) + + def setUp(self): + super(LdbTDBIndexedComparisonExpressions, self).setUp() + self.testdir = tempdir() + self.filename = os.path.join(self.testdir, "indexedcomptest.ldb") + # Note that the maximum key length is set to 54 + # This accounts for the 4 bytes added by the dn formatting + # a leading dn=, and a trailing zero terminator + # + self.l = _ldb.Ldb(self.url(), options=self.options()) + self.l.add({"dn": "@ATTRIBUTES"}) + self.l.add({"dn": "@INDEXLIST", + "@IDXATTR": [b"int32attr"], + "@IDXONE": [b"1"], + "@IDXGUID": [b"objectUUID"], + "@IDX_DN_GUID": [b"GUID"]}) + + def test_comparison_expression(self): + self.l.samba_schema_attribute_add("int32attr", 0, + _ldb.SYNTAX_SAMBA_INT32) + + int32_max = 2**31-1 + int32_min = -2**31 + test_nums = list(range(-5, 5)) + test_nums += list(range(int32_max-5, int32_max+1)) + test_nums += list(range(int32_min, int32_min+5)) + test_nums = sorted(test_nums) + + for i in test_nums: + ouuid = 0x0123456789abcdef + i + ouuid_s = bytes(('0' + hex(ouuid)[2:]).encode()) + self.l.add({"dn": "OU=COMPTESTOU{},DC=SAMBA,DC=ORG".format(i), + "objectUUID": ouuid_s, + "int32attr": str(i)}) + + def assert_int32_expr(expr, py_expr=None): + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=SCOPE_SUBTREE, + expression="(int32attr%s)" % (expr)) + + if not py_expr: + py_expr = expr + expect = [n for n in test_nums if eval(str(n) + py_expr)] + vals = sorted([int(r.get("int32attr")[0]) for r in res]) + self.assertEqual(len(res), len(expect)) + self.assertEqual(set(vals), set(expect)) + self.assertEqual(expect, vals) + + assert_int32_expr(">=-2") + assert_int32_expr("<=2") + assert_int32_expr(">=" + str(int32_min)) + assert_int32_expr("<=" + str(int32_min)) + assert_int32_expr("<=" + str(int32_min+1)) + assert_int32_expr("<=" + str(int32_max)) + assert_int32_expr(">=" + str(int32_max)) + assert_int32_expr(">=" + str(int32_max-1)) + assert_int32_expr("=10", "==10") + + def test_comparison_expression_duplicates(self): + self.l.samba_schema_attribute_add("int32attr", 0, + _ldb.SYNTAX_SAMBA_INT32) + + int32_max = 2**31-1 + int32_min = -2**31 + + test_nums = list(range(-5, 5)) * 3 + test_nums += list(range(-20, 20, 5)) * 2 + test_nums += list(range(-50, 50, 15)) + test_nums = sorted(test_nums) + + for i, n in enumerate(test_nums): + ouuid = 0x0123456789abcdef + i + ouuid_s = bytes(('0' + hex(ouuid)[2:]).encode()) + self.l.add({"dn": "OU=COMPTESTOU{},DC=SAMBA,DC=ORG".format(i), + "objectUUID": ouuid_s, + "int32attr": str(n)}) + + def assert_int32_expr(expr, py_expr=None): + res = self.l.search(base="DC=SAMBA,DC=ORG", + scope=SCOPE_SUBTREE, + expression="(int32attr%s)" % (expr)) + + if not py_expr: + py_expr = expr + expect = [n for n in test_nums if eval(str(n) + py_expr)] + vals = sorted([int(r.get("int32attr")[0]) for r in res]) + self.assertEqual(len(res), len(expect)) + self.assertEqual(set(vals), set(expect)) + self.assertEqual(expect, vals) + + assert_int32_expr(">=-2") + assert_int32_expr("<=2") + assert_int32_expr(">=" + str(int32_min)) + assert_int32_expr("<=" + str(int32_min)) + assert_int32_expr("<=" + str(int32_min+1)) + assert_int32_expr("<=" + str(int32_max)) + assert_int32_expr(">=" + str(int32_max)) + assert_int32_expr(">=" + str(int32_max-1)) + assert_int32_expr("=-5", "==-5") + assert_int32_expr("=5", "==5") + +# Run the same tests against an lmdb backend +class LdbLMDBIndexedComparisonExpressions(LdbTDBIndexedComparisonExpressions): + + def setUp(self): + if os.environ.get('HAVE_LMDB', '1') == '0': + self.skipTest("No lmdb backend") + self.prefix = MDB_PREFIX + super(LdbLMDBIndexedComparisonExpressions, self).setUp() + + def tearDown(self): + super(LdbLMDBIndexedComparisonExpressions, self).tearDown() + + +TestProgram(module=__name__, opts=[]) diff --git a/lib/ldb-samba/tests/match_rules.py b/lib/ldb-samba/tests/match_rules.py new file mode 100755 index 0000000..2fe6c3e --- /dev/null +++ b/lib/ldb-samba/tests/match_rules.py @@ -0,0 +1,1797 @@ +#!/usr/bin/env python3 + +import optparse +import sys +import os +import samba +import samba.getopt as options + +from samba.tests.subunitrun import SubunitOptions, TestProgram + +from samba.samdb import SamDB +from samba.auth import system_session +from ldb import Message, MessageElement, Dn, LdbError +from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE +from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL + +# TODO I'm ignoring case in these tests for now. +# This should be fixed to work inline with Windows. +# The literal strings are in the case Windows uses. +# Windows appear to preserve casing of the RDN and uppercase the other keys. + + +class MatchRulesTestsBase(samba.tests.TestCase): + def setUp(self): + super().setUp() + self.lp = self.sambaopts.get_loadparm() + self.creds = self.credopts.get_credentials(self.lp) + + self.ldb = SamDB(self.host, credentials=self.creds, + session_info=system_session(self.lp), + lp=self.lp) + self.base_dn = self.ldb.domain_dn() + self.ou_rdn = "OU=matchrulestest" + self.ou = self.ou_rdn + "," + self.base_dn + self.ou_users = "OU=users,%s" % self.ou + self.ou_groups = "OU=groups,%s" % self.ou + self.ou_computers = "OU=computers,%s" % self.ou + + try: + self.ldb.delete(self.ou, ["tree_delete:1"]) + except LdbError as e: + pass + + # Add a organizational unit to create objects + self.ldb.add({ + "dn": self.ou, + "objectclass": "organizationalUnit"}) + + self.addCleanup(self.ldb.delete, self.ou, controls=['tree_delete:0']) + + + # Add the following OU hierarchy and set otherWellKnownObjects, + # which has BinaryDN syntax: + # + # o1 + # |--> o2 + # | |--> o3 + # | | |-->o4 + + self.ldb.add({ + "dn": "OU=o1,%s" % self.ou, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": "OU=o2,OU=o1,%s" % self.ou, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": "OU=o3,OU=o2,OU=o1,%s" % self.ou, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": "OU=o4,OU=o3,OU=o2,OU=o1,%s" % self.ou, + "objectclass": "organizationalUnit"}) + + m = Message() + m.dn = Dn(self.ldb, self.ou) + m["otherWellKnownObjects"] = MessageElement("B:32:00000000000000000000000000000001:OU=o1,%s" % self.ou, + FLAG_MOD_ADD, "otherWellKnownObjects") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "OU=o1,%s" % self.ou) + m["otherWellKnownObjects"] = MessageElement("B:32:00000000000000000000000000000002:OU=o2,OU=o1,%s" % self.ou, + FLAG_MOD_ADD, "otherWellKnownObjects") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "OU=o2,OU=o1,%s" % self.ou) + m["otherWellKnownObjects"] = MessageElement("B:32:00000000000000000000000000000003:OU=o3,OU=o2,OU=o1,%s" % self.ou, + FLAG_MOD_ADD, "otherWellKnownObjects") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "OU=o3,OU=o2,OU=o1,%s" % self.ou) + m["otherWellKnownObjects"] = MessageElement("B:32:00000000000000000000000000000004:OU=o4,OU=o3,OU=o2,OU=o1,%s" % self.ou, + FLAG_MOD_ADD, "otherWellKnownObjects") + self.ldb.modify(m) + + # Create OU for users and groups + self.ldb.add({ + "dn": self.ou_users, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": self.ou_groups, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": self.ou_computers, + "objectclass": "organizationalUnit"}) + + # Add four groups + self.ldb.add({ + "dn": "cn=g1,%s" % self.ou_groups, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=g2,%s" % self.ou_groups, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=g4,%s" % self.ou_groups, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=g3,%s" % self.ou_groups, + "objectclass": "group"}) + + # Add four users + self.ldb.add({ + "dn": "cn=u1,%s" % self.ou_users, + "objectclass": "user"}) + self.ldb.add({ + "dn": "cn=u2,%s" % self.ou_users, + "objectclass": "user"}) + self.ldb.add({ + "dn": "cn=u3,%s" % self.ou_users, + "objectclass": "user"}) + self.ldb.add({ + "dn": "cn=u4,%s" % self.ou_users, + "objectclass": "user"}) + + # Add computers to test Object(DN-Binary) syntax + self.ldb.add({ + "dn": "cn=c1,%s" % self.ou_computers, + "objectclass": "computer", + "dNSHostName": "c1.%s" % self.lp.get("realm").lower(), + "servicePrincipalName": ["HOST/c1"], + "sAMAccountName": "c1$", + "userAccountControl": "83890178"}) + + self.ldb.add({ + "dn": "cn=c2,%s" % self.ou_computers, + "objectclass": "computer", + "dNSHostName": "c2.%s" % self.lp.get("realm").lower(), + "servicePrincipalName": ["HOST/c2"], + "sAMAccountName": "c2$", + "userAccountControl": "83890178"}) + + self.ldb.add({ + "dn": "cn=c3,%s" % self.ou_computers, + "objectclass": "computer", + "dNSHostName": "c3.%s" % self.lp.get("realm").lower(), + "servicePrincipalName": ["HOST/c3"], + "sAMAccountName": "c3$", + "userAccountControl": "83890178"}) + + # Create the following hierarchy: + # g4 + # |--> u4 + # |--> g3 + # | |--> u3 + # | |--> g2 + # | | |--> u2 + # | | |--> g1 + # | | | |--> u1 + + # u1 member of g1 + m = Message() + m.dn = Dn(self.ldb, "CN=g1,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u1,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # u2 member of g2 + m = Message() + m.dn = Dn(self.ldb, "CN=g2,%s" % self.ou_groups) + m["member"] = MessageElement("cn=u2,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # u3 member of g3 + m = Message() + m.dn = Dn(self.ldb, "cn=g3,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u3,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # u4 member of g4 + m = Message() + m.dn = Dn(self.ldb, "cn=g4,%s" % self.ou_groups) + m["member"] = MessageElement("cn=u4,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # g3 member of g4 + m = Message() + m.dn = Dn(self.ldb, "CN=g4,%s" % self.ou_groups) + m["member"] = MessageElement("cn=g3,%s" % self.ou_groups, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # g2 member of g3 + m = Message() + m.dn = Dn(self.ldb, "cn=g3,%s" % self.ou_groups) + m["member"] = MessageElement("CN=g2,%s" % self.ou_groups, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # g1 member of g2 + m = Message() + m.dn = Dn(self.ldb, "cn=g2,%s" % self.ou_groups) + m["member"] = MessageElement("cn=g1,%s" % self.ou_groups, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # Add a couple of ms-Exch-Configuration-Container to test forward-link + # attributes without backward link (addressBookRoots2) + # e1 + # |--> e2 + # | |--> c1 + self.ldb.add({ + "dn": "cn=e1,%s" % self.ou, + "objectclass": "msExchConfigurationContainer"}) + self.ldb.add({ + "dn": "cn=e2,%s" % self.ou, + "objectclass": "msExchConfigurationContainer"}) + + m = Message() + m.dn = Dn(self.ldb, "cn=e2,%s" % self.ou) + m["e1"] = MessageElement("cn=c1,%s" % self.ou_computers, + FLAG_MOD_ADD, "addressBookRoots2") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "cn=e1,%s" % self.ou) + m["e1"] = MessageElement("cn=e2,%s" % self.ou, + FLAG_MOD_ADD, "addressBookRoots2") + self.ldb.modify(m) + + + +class MatchRulesTests(MatchRulesTestsBase): + def setUp(self): + self.sambaopts = sambaopts + self.credopts = credopts + self.host = host + super().setUp() + + # The msDS-RevealedUsers is owned by system and cannot be modified + # directly. Set the schemaUpgradeInProgress flag as workaround + # and create this hierarchy: + # ou=computers + # |-> c1 + # | |->c2 + # | | |->u1 + + # + # While appropriate for this test, this is NOT a good practice + # in general. This is only done here because the alternative + # is to make a schema modification. + # + # IF/WHEN Samba protects this attribute better, this + # particular part of the test can be removed, as the same code + # is covered by the addressBookRoots2 case well enough. + # + m = Message() + m.dn = Dn(self.ldb, "") + m["e1"] = MessageElement("1", FLAG_MOD_REPLACE, "schemaUpgradeInProgress") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "cn=c2,%s" % self.ou_computers) + m["e1"] = MessageElement("B:8:01010101:cn=c3,%s" % self.ou_computers, + FLAG_MOD_ADD, "msDS-RevealedUsers") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "cn=c1,%s" % self.ou_computers) + m["e1"] = MessageElement("B:8:01010101:cn=c2,%s" % self.ou_computers, + FLAG_MOD_ADD, "msDS-RevealedUsers") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "") + m["e1"] = MessageElement("0", FLAG_MOD_REPLACE, "schemaUpgradeInProgress") + self.ldb.modify(m) + + + def test_u1_member_of_g4(self): + # Search without transitive match must return 0 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=u1,%s" % self.ou_users, + scope=SCOPE_BASE, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + # Search with transitive match must return 1 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search("cn=u1,%s" % self.ou_users, + scope=SCOPE_BASE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=u1,%s" % self.ou_users).lower()) + + def test_g1_member_of_g4(self): + # Search without transitive match must return 0 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g1,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + # Search with transitive match must return 1 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search("cn=g1,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g1,%s" % self.ou_groups).lower()) + + def test_u1_groups(self): + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g1,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g1,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + def test_u2_groups(self): + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g2,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + def test_u3_groups(self): + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g3,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + def test_u4_groups(self): + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_users, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + def test_extended_dn_u1(self): + res1 = self.ldb.search("cn=u1,%s" % self.ou_users, + scope=SCOPE_BASE, + expression="objectClass=*", + attrs=['objectSid', 'objectGUID']) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("cn=u1,%s" % self.ou_users).lower()) + + sid = self.ldb.schema_format_value("objectSid", res1[0]["objectSid"][0]).decode('utf8') + guid = self.ldb.schema_format_value("objectGUID", res1[0]['objectGUID'][0]).decode('utf8') + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<SID=%s>" % sid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g1,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g1,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g1,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g1,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g1,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g1,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + def test_extended_dn_u2(self): + res1 = self.ldb.search("cn=u2,%s" % self.ou_users, + scope=SCOPE_BASE, + expression="objectClass=*", + attrs=['objectSid', 'objectGUID']) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("cn=u2,%s" % self.ou_users).lower()) + + sid = self.ldb.schema_format_value("objectSid", res1[0]["objectSid"][0]).decode('utf8') + guid = self.ldb.schema_format_value("objectGUID", res1[0]['objectGUID'][0]).decode('utf8') + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<SID=%s>" % sid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g2,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g2,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g2,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + def test_extended_dn_u3(self): + res1 = self.ldb.search("cn=u3,%s" % self.ou_users, + scope=SCOPE_BASE, + expression="objectClass=*", + attrs=['objectSid', 'objectGUID']) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("cn=u3,%s" % self.ou_users).lower()) + + sid = self.ldb.schema_format_value("objectSid", res1[0]["objectSid"][0]).decode('utf8') + guid = self.ldb.schema_format_value("objectGUID", res1[0]['objectGUID'][0]).decode('utf8') + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<SID=%s>" % sid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g3,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g3,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=g3,%s" % self.ou_groups).lower() in dn_list) + self.assertTrue(("CN=g4,%s" % self.ou_groups).lower() in dn_list) + + def test_extended_dn_u4(self): + res1 = self.ldb.search("cn=u4,%s" % self.ou_users, + scope=SCOPE_BASE, + expression="objectClass=*", + attrs=['objectSid', 'objectGUID']) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("cn=u4,%s" % self.ou_users).lower()) + + sid = self.ldb.schema_format_value("objectSid", res1[0]["objectSid"][0]).decode('utf8') + guid = self.ldb.schema_format_value("objectGUID", res1[0]['objectGUID'][0]).decode('utf8') + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<SID=%s>" % sid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<SID=%s>" % sid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=<GUID=%s>" % guid) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + def test_object_dn_binary(self): + res1 = self.ldb.search(self.ou_computers, + scope=SCOPE_SUBTREE, + expression="msDS-RevealedUsers=B:8:01010101:cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=c2,%s" % self.ou_computers).lower()) + + res1 = self.ldb.search(self.ou_computers, + scope=SCOPE_ONELEVEL, + expression="msDS-RevealedUsers=B:8:01010101:cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=c2,%s" % self.ou_computers).lower()) + + res1 = self.ldb.search(self.ou_computers, + scope=SCOPE_SUBTREE, + expression="msDS-RevealedUsers:1.2.840.113556.1.4.1941:=B:8:01010101:cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=c1,%s" % self.ou_computers).lower() in dn_list) + self.assertTrue(("CN=c2,%s" % self.ou_computers).lower() in dn_list) + + res1 = self.ldb.search(self.ou_computers, + scope=SCOPE_ONELEVEL, + expression="msDS-RevealedUsers:1.2.840.113556.1.4.1941:=B:8:01010101:cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=c1,%s" % self.ou_computers).lower() in dn_list) + self.assertTrue(("CN=c2,%s" % self.ou_computers).lower() in dn_list) + + def test_one_way_links(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="addressBookRoots2=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=e2,%s" % self.ou).lower()) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_ONELEVEL, + expression="addressBookRoots2=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=e2,%s" % self.ou).lower()) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="addressBookRoots2:1.2.840.113556.1.4.1941:=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=e1,%s" % self.ou).lower() in dn_list) + self.assertTrue(("CN=e2,%s" % self.ou).lower() in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_ONELEVEL, + expression="addressBookRoots2:1.2.840.113556.1.4.1941:=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn).lower() for res in res1] + self.assertTrue(("CN=e1,%s" % self.ou).lower() in dn_list) + self.assertTrue(("CN=e2,%s" % self.ou).lower() in dn_list) + + def test_not_linked_attrs(self): + res1 = self.ldb.search(self.base_dn, + scope=SCOPE_BASE, + expression="wellKnownObjects=B:32:aa312825768811d1aded00c04fd8d5cd:CN=computers,%s" % self.base_dn) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), self.base_dn.lower()) + + def test_invalid_basedn(self): + res1 = self.ldb.search(self.base_dn, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=c1,ou=computers,ou=matchrulestest,%sXX" % self.base_dn) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.base_dn, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=XX,ou=computers,ou=matchrulestest,%s" % self.base_dn) + self.assertEqual(len(res1), 0) + + def test_subtree(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="otherWellKnownObjects=B:32:00000000000000000000000000000004:OU=o4,OU=o3,OU=o2,OU=o1,%s" % self.ou) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("OU=o3,OU=o2,OU=o1,%s" % self.ou).lower()) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_ONELEVEL, + expression="otherWellKnownObjects=B:32:00000000000000000000000000000004:OU=o4,OU=o3,OU=o2,OU=o1,%s" % self.ou) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="otherWellKnownObjects:1.2.840.113556.1.4.1941:=B:32:00000000000000000000000000000004:OU=o4,OU=o3,OU=o2,OU=o1,%s" % self.ou) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_ONELEVEL, + expression="otherWellKnownObjects:1.2.840.113556.1.4.1941:=B:32:00000000000000000000000000000004:OU=o4,OU=o3,OU=o2,OU=o1,%s" % self.ou) + self.assertEqual(len(res1), 0) + + def test_unknown_oid(self): + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:2.4.681.226012.2.8.3882:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:8.16.8720.1008448.8.32.15528:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.3.4:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + def test_nul_text(self): + self.assertRaises((ValueError,TypeError), + lambda: self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="\00member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users)) + self.assertRaises((ValueError,TypeError), + lambda: self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840\00.113556.1.4.1941:=cn=u1,%s" % self.ou_users)) + self.assertRaises((ValueError,TypeError), + lambda: self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1\00,%s" % self.ou_users)) + self.assertRaises(LdbError, + lambda: self.ldb.search("cn=\00g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users)) + self.assertRaises(LdbError, + lambda: self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:")) + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=") + self.assertEqual(len(res1), 0) + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=") + self.assertEqual(len(res1), 0) + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=nonexistent") + self.assertEqual(len(res1), 0) + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=nonexistent") + self.assertEqual(len(res1), 0) + self.assertRaises(LdbError, + lambda: self.ldb.search("cn=\00g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:cn=u1,%s" % self.ou_users)) + self.assertRaises(LdbError, + lambda: self.ldb.search("cn=\00g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1")) + self.assertRaises(LdbError, + lambda: self.ldb.search("cn=\00g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=")) + self.assertRaises(LdbError, + lambda: self.ldb.search("cn=\00g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member::=cn=u1,%s" % self.ou_users)) + + def test_misc_matches(self): + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_BASE, + expression="member=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g1,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g2,%s" % self.ou_groups) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g2,%s" % self.ou_groups) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g1,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="member:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_BASE, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), ("CN=g3,%s" % self.ou_groups)) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), ("CN=g3,%s" % self.ou_groups)) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_BASE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_ONELEVEL, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou_groups, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + +class MatchRuleConditionTests(samba.tests.TestCase): + def setUp(self): + super(MatchRuleConditionTests, self).setUp() + self.lp = sambaopts.get_loadparm() + self.creds = credopts.get_credentials(self.lp) + + self.ldb = SamDB(host, credentials=self.creds, + session_info=system_session(self.lp), + lp=self.lp) + self.base_dn = self.ldb.domain_dn() + self.ou = "OU=matchruleconditiontests,%s" % self.base_dn + self.ou_users = "OU=users,%s" % self.ou + self.ou_groups = "OU=groups,%s" % self.ou + self.ou_computers = "OU=computers,%s" % self.ou + + # Add a organizational unit to create objects + self.ldb.add({ + "dn": self.ou, + "objectclass": "organizationalUnit"}) + + # Create users, groups, and computers + self.ldb.add({ + "dn": self.ou_users, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": self.ou_groups, + "objectclass": "organizationalUnit"}) + self.ldb.add({ + "dn": self.ou_computers, + "objectclass": "organizationalUnit"}) + + self.ldb.add({ + "dn": "cn=g1,%s" % self.ou_groups, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=g2,%s" % self.ou_groups, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=g3,%s" % self.ou_groups, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=g4,%s" % self.ou_groups, + "objectclass": "group"}) + + self.ldb.add({ + "dn": "cn=u1,%s" % self.ou_users, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=u2,%s" % self.ou_users, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=u3,%s" % self.ou_users, + "objectclass": "group"}) + self.ldb.add({ + "dn": "cn=u4,%s" % self.ou_users, + "objectclass": "group"}) + + self.ldb.add({ + "dn": "cn=c1,%s" % self.ou_computers, + "objectclass": "user"}) + + self.ldb.add({ + "dn": "cn=c2,%s" % self.ou_computers, + "objectclass": "user"}) + + self.ldb.add({ + "dn": "cn=c3,%s" % self.ou_computers, + "objectclass": "user"}) + + self.ldb.add({ + "dn": "cn=c4,%s" % self.ou_computers, + "objectclass": "user"}) + + # Assign groups according to the following structure: + # g1-->g2---->g3 --g4 + # \ | / | / / | + # u1- >u2-- | u3<- | u4 + # \ \ \ \ | + # c1* >c2 ->c3 c4 + # *c1 is a member of u1, u2, u3, and u4 + + # u2 is a member of g1 and g2 + m = Message() + m.dn = Dn(self.ldb, "CN=g1,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u2,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=g2,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u2,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # g2 is a member of g1 + m = Message() + m.dn = Dn(self.ldb, "CN=g1,%s" % self.ou_groups) + m["member"] = MessageElement("CN=g2,%s" % self.ou_groups, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # g3 is a member of g2 + m = Message() + m.dn = Dn(self.ldb, "CN=g2,%s" % self.ou_groups) + m["member"] = MessageElement("CN=g3,%s" % self.ou_groups, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # u3 is a member of g3 and g4 + m = Message() + m.dn = Dn(self.ldb, "CN=g3,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u3,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=g4,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u3,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # u4 is a member of g4 + m = Message() + m.dn = Dn(self.ldb, "CN=g4,%s" % self.ou_groups) + m["member"] = MessageElement("CN=u4,%s" % self.ou_users, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # c1 is a member of u1, u2, u3, and u4 + m = Message() + m.dn = Dn(self.ldb, "CN=u1,%s" % self.ou_users) + m["member"] = MessageElement("CN=c1,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=u2,%s" % self.ou_users) + m["member"] = MessageElement("CN=c1,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=u3,%s" % self.ou_users) + m["member"] = MessageElement("CN=c1,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=u4,%s" % self.ou_users) + m["member"] = MessageElement("CN=c1,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # c2 is a member of u1 + m = Message() + m.dn = Dn(self.ldb, "CN=u1,%s" % self.ou_users) + m["member"] = MessageElement("CN=c2,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # c3 is a member of u2 and g3 + m = Message() + m.dn = Dn(self.ldb, "CN=u2,%s" % self.ou_users) + m["member"] = MessageElement("CN=c3,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=g3,%s" % self.ou_groups) + m["member"] = MessageElement("CN=c3,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + # c4 is a member of u4 and g4 + m = Message() + m.dn = Dn(self.ldb, "CN=u4,%s" % self.ou_users) + m["member"] = MessageElement("CN=c4,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "CN=g4,%s" % self.ou_groups) + m["member"] = MessageElement("CN=c4,%s" % self.ou_computers, + FLAG_MOD_ADD, "member") + self.ldb.modify(m) + + def tearDown(self): + super(MatchRuleConditionTests, self).tearDown() + self.ldb.delete(self.ou, controls=['tree_delete:0']) + + def test_g1_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 6) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=g1,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + def test_g2_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=g2,%s" % self.ou_groups) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s" % self.ou_groups) + self.assertEqual(len(res1), 5) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=g2,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g1,%s" % self.ou_groups) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=g2,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g1,%s" % self.ou_groups) + + def test_g3_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=g3,%s" % self.ou_groups) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s" % self.ou_groups) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=g3,%s" % self.ou_groups) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g2,%s" % self.ou_groups) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=g3,%s" % self.ou_groups) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + + def test_g4_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=g4,%s" % self.ou_groups) + self.assertEqual(len(res1), 0) + + def test_u1_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c2,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c2,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + def test_u2_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u2,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + + def test_u3_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=c1,%s" % self.ou_computers) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=u3,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=c1,%s" % self.ou_computers) + + def test_u4_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g4,%s" % self.ou_groups) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=g4,%s" % self.ou_groups) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=u4,%s" % self.ou_users) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + def test_c1_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u1,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 8) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u1,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=c1,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + def test_c2_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=c2,%s" % self.ou_computers) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=u1,%s" % self.ou_users) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=c2,%s" % self.ou_computers) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=u1,%s" % self.ou_users) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=c2,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=c2,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + def test_c3_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 4) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=c3,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + def test_c4_members(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member=cn=c4,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="member:1.2.840.113556.1.4.1941:=cn=c4,%s" % self.ou_computers) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf=cn=c4,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression="memberOf:1.2.840.113556.1.4.1941:=cn=c4,%s" % self.ou_computers) + self.assertEqual(len(res1), 0) + + def test_or_member_queries(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(member:1.2.840.113556.1.4.1941:=cn=c1,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c2,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 8) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u1,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(member:1.2.840.113556.1.4.1941:=cn=c2,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c3,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 5) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u1,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(member:1.2.840.113556.1.4.1941:=cn=c2,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c4,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u1,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(member:1.2.840.113556.1.4.1941:=cn=c3,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c4,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 6) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(member:1.2.840.113556.1.4.1941:=cn=u1,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c4,%s))") % ( + self.ou_users, self.ou_computers)) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g4,%s" % self.ou_groups in dn_list) + + def test_and_member_queries(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(member:1.2.840.113556.1.4.1941:=cn=c1,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c2,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn), "CN=u1,%s" % self.ou_users) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(member:1.2.840.113556.1.4.1941:=cn=c2,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=c3,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 0) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(member:1.2.840.113556.1.4.1941:=cn=c3,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=u3,%s))") % ( + self.ou_computers, self.ou_users)) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=g1,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(member:1.2.840.113556.1.4.1941:=cn=c1,%s)" + "(member:1.2.840.113556.1.4.1941:=cn=u4,%s))") % ( + self.ou_computers, self.ou_computers)) + self.assertEqual(len(res1), 0) + + def test_or_memberOf_queries(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 6) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 6) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 8) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g2,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s))") % + (self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 5) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 7) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(|(memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 5) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u4,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c4,%s" % self.ou_computers in dn_list) + + def test_and_memberOf_queries(self): + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 5) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u2,%s" % self.ou_users in dn_list) + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=g3,%s" % self.ou_groups in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 3) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + self.assertTrue("CN=c3,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g2,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g3,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=g4,%s))") % ( + self.ou_groups, self.ou_groups)) + self.assertEqual(len(res1), 2) + dn_list = [str(res.dn) for res in res1] + self.assertTrue("CN=u3,%s" % self.ou_users in dn_list) + self.assertTrue("CN=c1,%s" % self.ou_computers in dn_list) + + res1 = self.ldb.search(self.ou, + scope=SCOPE_SUBTREE, + expression=("(&(memberOf:1.2.840.113556.1.4.1941:=cn=g1,%s)" + "(memberOf:1.2.840.113556.1.4.1941:=cn=c1,%s))") % ( + self.ou_groups, self.ou_computers)) + self.assertEqual(len(res1), 0) + +if __name__ == "__main__": + + parser = optparse.OptionParser("match_rules.py [options] <host>") + sambaopts = options.SambaOptions(parser) + parser.add_option_group(sambaopts) + parser.add_option_group(options.VersionOptions(parser)) + + # use command line creds if available + credopts = options.CredentialsOptions(parser) + parser.add_option_group(credopts) + opts, args = parser.parse_args() + subunitopts = SubunitOptions(parser) + parser.add_option_group(subunitopts) + + if len(args) < 1: + parser.print_usage() + sys.exit(1) + + host = args[0] + + if "://" not in host: + if os.path.isfile(host): + host = "tdb://%s" % host + else: + host = "ldap://%s" % host + + TestProgram(module=__name__, opts=subunitopts) diff --git a/lib/ldb-samba/tests/match_rules_remote.py b/lib/ldb-samba/tests/match_rules_remote.py new file mode 100755 index 0000000..122231f --- /dev/null +++ b/lib/ldb-samba/tests/match_rules_remote.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import optparse +import sys +import os +import samba +import samba.getopt as options + +from samba.tests.subunitrun import SubunitOptions, TestProgram + +from samba.samdb import SamDB +from samba.auth import system_session +from samba import sd_utils +from samba.ndr import ndr_unpack +from ldb import Message, MessageElement, Dn, LdbError +from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE +from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL + +from match_rules import MatchRulesTestsBase + + +class MatchRulesTestsUser(MatchRulesTestsBase): + def setUp(self): + self.sambaopts = sambaopts + self.credopts = credopts + self.host = host + super().setUp() + self.sd_utils = sd_utils.SDUtils(self.ldb) + + self.user_pass = "samba123@" + self.match_test_user = "matchtestuser" + self.ldb.newuser(self.match_test_user, + self.user_pass, + userou=self.ou_rdn) + user_creds = self.insta_creds(template=self.creds, + username=self.match_test_user, + userpass=self.user_pass) + self.user_ldb = SamDB(host, credentials=user_creds, lp=self.lp) + token_res = self.user_ldb.search(scope=SCOPE_BASE, + base="", + attrs=["tokenGroups"]) + self.user_sid = ndr_unpack(samba.dcerpc.security.dom_sid, + token_res[0]["tokenGroups"][0]) + + self.member_attr_guid = "bf9679c0-0de6-11d0-a285-00aa003049e2" + + def test_with_denied_link(self): + + # add an ACE that denies the user Read Property (RP) access to + # the member attr (which is similar to making the attribute + # confidential) + ace = "(OD;;RP;{0};;{1})".format(self.member_attr_guid, + self.user_sid) + g2_dn = Dn(self.ldb, "CN=g2,%s" % self.ou_groups) + + # add the ACE that denies access to the attr under test + self.sd_utils.dacl_add_ace(g2_dn, ace) + + # Search without transitive match must return 0 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + # Search with transitive match must return 1 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + # Search as a user match must return 0 results as the intermediate link can't be seen + res1 = self.user_ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + + +parser = optparse.OptionParser("match_rules_remote.py [options] <host>") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +opts, args = parser.parse_args() +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] + +if "://" not in host: + if os.path.isfile(host): + host = "tdb://%s" % host + else: + host = "ldap://%s" % host + +TestProgram(module=__name__, opts=subunitopts) diff --git a/lib/ldb-samba/wscript_build b/lib/ldb-samba/wscript_build new file mode 100644 index 0000000..d02bc95 --- /dev/null +++ b/lib/ldb-samba/wscript_build @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# LDBSAMBA gets included in the ldb build when we are building ldb_ildap +# as a built-in module and this delutes the symbols in the ldb library with +# the symbols of all of ldb_ildap's dependencies. + +bld.SAMBA_LIBRARY('ldbsamba', + source='ldif_handlers.c ldb_matching_rules.c', + autoproto='ldif_handlers_proto.h', + public_deps='ldb', + deps='samba-security ndr NDR_DRSBLOBS NDR_DNSP ldbwrap samdb-common SAMDB_SCHEMA tdb samba-errors', + private_library=True + ) + +bld.SAMBA_SUBSYSTEM('ldbwrap', + source='ldb_wrap.c', + public_headers='ldb_wrap.h', + deps='ldb samba-util ldbsamba samba-hostconfig' + ) + +pyparam_util = bld.pyembed_libname('pyparam_util') +pyldb_util = bld.pyembed_libname('pyldb-util') +pyauth = 'pyauth' +bld.SAMBA_PYTHON('python_samba__ldb', 'pyldb.c', + deps='ldbsamba %s ldbwrap %s %s' % (pyparam_util, pyldb_util, pyauth), + realname='samba/_ldb.so') + +bld.SAMBA_MODULE('ldbsamba_extensions', + source='samba_extensions.c', + init_function='ldb_samba_extensions_init', + module_init_name='ldb_init_module', + subsystem='ldb', + deps='ldb ldbsamba CMDLINE_S4 gensec', + internal_module=False) + + +# the s4-internal ldap backend +bld.SAMBA_MODULE('ldb_ildap', + source='ldb_ildap.c', + init_function='ldb_ildap_init', + module_init_name='ldb_init_module', + deps='talloc cli-ldap samba-credentials auth_system_session', + internal_module=False, + subsystem='ldb') |