Implement LDAP_CONTROL_NOTIFICATION
Stefan Metzmacher
metze at samba.org
Mon Feb 15 12:42:45 UTC 2016
Am 15.02.2016 um 12:54 schrieb Stefan Metzmacher:
> Am 02.02.2016 um 13:26 schrieb Stefan Metzmacher:
>> Hi,
>>
>> here're patches which implement the LDAP NOTIFICATION control.
>>
>> This only works via ldap{,s,i}:// as the ldap server does
>> periodic retries every 5 seconds (and an immediate retry
>> after a ldap write operation).
>>
>> This can be improved by sending message around,
>> but this works and should be good enough for a first implementation.
>>
>> This patchset depends on the ldb-1.1.25 patchset I posted earlier today.
>
> I meant the ldb-1.1.26 patchset...
here's also an updated patchset, rebased on master
after the DLIST_REMOVE() changes.
metze
-------------- next part --------------
From cf6eaf93fdbf620ed96c1d59233a464e4235ef66 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 23 Jul 2015 12:06:11 +0200
Subject: [PATCH 1/8] s4:ldap_server: make sure we only have one
tstream_read_pdu_blob_send() on a connection
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/ldap_server/ldap_server.c | 8 ++++++++
source4/ldap_server/ldap_server.h | 1 +
2 files changed, 9 insertions(+)
diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c
index 3afbcdb..02c3c95 100644
--- a/source4/ldap_server/ldap_server.c
+++ b/source4/ldap_server/ldap_server.c
@@ -65,6 +65,7 @@ static void ldapsrv_terminate_connection(struct ldapsrv_connection *conn,
conn->limits.endtime = timeval_current_ofs(0, 500);
tevent_queue_stop(conn->sockets.send_queue);
+ TALLOC_FREE(conn->sockets.read_req);
if (conn->active_call) {
tevent_req_cancel(conn->active_call);
conn->active_call = NULL;
@@ -412,6 +413,10 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
timeval_current_ofs(conn->limits.conn_idle_time, 0);
}
+ if (conn->sockets.read_req != NULL) {
+ return true;
+ }
+
/*
* The minimun size of a LDAP pdu is 7 bytes
*
@@ -455,6 +460,7 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
conn->connection->event.ctx,
conn->limits.endtime);
tevent_req_set_callback(subreq, ldapsrv_call_read_done, conn);
+ conn->sockets.read_req = subreq;
return true;
}
@@ -470,6 +476,8 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq)
struct asn1_data *asn1;
DATA_BLOB blob;
+ conn->sockets.read_req = NULL;
+
call = talloc_zero(conn, struct ldapsrv_call);
if (!call) {
ldapsrv_terminate_connection(conn, "no memory");
diff --git a/source4/ldap_server/ldap_server.h b/source4/ldap_server/ldap_server.h
index 6f8b433..bfd95c0 100644
--- a/source4/ldap_server/ldap_server.h
+++ b/source4/ldap_server/ldap_server.h
@@ -34,6 +34,7 @@ struct ldapsrv_connection {
struct {
struct tevent_queue *send_queue;
+ struct tevent_req *read_req;
struct tstream_context *raw;
struct tstream_context *tls;
struct tstream_context *sasl;
--
1.9.1
From cac021f5f9f0566512fd8dab6801fd4fc05f0f83 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 1 Feb 2016 23:02:14 +0100
Subject: [PATCH 2/8] s4:dsdb/tests: don't use spaces in lDAPDisplayName in
urgent_replication.py
This should result in LDAP_UNWILLING_TO_PERFORM/WERR_DS_INVALID_LDAP_DISPLAY_NAME,
so better use a useful value without spaces.
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/dsdb/tests/python/urgent_replication.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/source4/dsdb/tests/python/urgent_replication.py b/source4/dsdb/tests/python/urgent_replication.py
index 19176c1..93f3553 100755
--- a/source4/dsdb/tests/python/urgent_replication.py
+++ b/source4/dsdb/tests/python/urgent_replication.py
@@ -161,7 +161,7 @@ adminDescription: test attributeSchema
oMSyntax: 64
systemOnly: FALSE
searchFlags: 8
-lDAPDisplayName: test attributeSchema
+lDAPDisplayName: testAttributeSchema
name: test attributeSchema""")
# urgent replication should be enabled when creating
@@ -174,7 +174,7 @@ name: test attributeSchema""")
# urgent replication should be enabled when modifying
m = Message()
m.dn = Dn(self.ldb, "CN=test attributeSchema,CN=Schema,CN=Configuration," + self.base_dn)
- m["lDAPDisplayName"] = MessageElement("updated test attributeSchema", FLAG_MOD_REPLACE,
+ m["lDAPDisplayName"] = MessageElement("updatedTestAttributeSchema", FLAG_MOD_REPLACE,
"lDAPDisplayName")
self.ldb.modify(m)
res = self.ldb.load_partition_usn("cn=Schema,cn=Configuration," + self.base_dn)
@@ -195,7 +195,7 @@ showInAdvancedViewOnly: TRUE
adminDisplayName: test classSchema
adminDescription: test classSchema
objectClassCategory: 1
-lDAPDisplayName: test classSchema
+lDAPDisplayName: testClassSchema
name: test classSchema
systemOnly: FALSE
systemPossSuperiors: dfsConfiguration
@@ -215,7 +215,7 @@ defaultHidingValue: TRUE""")
# urgent replication should be enabled when modifying
m = Message()
m.dn = Dn(self.ldb, "CN=test classSchema,CN=Schema,CN=Configuration," + self.base_dn)
- m["lDAPDisplayName"] = MessageElement("updated test classSchema", FLAG_MOD_REPLACE,
+ m["lDAPDisplayName"] = MessageElement("updatedTestClassSchema", FLAG_MOD_REPLACE,
"lDAPDisplayName")
self.ldb.modify(m)
res = self.ldb.load_partition_usn("cn=Schema,cn=Configuration," + self.base_dn)
--
1.9.1
From 270b99dad069d1b93c6d0042acdcb94fed2b54ee Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 1 Feb 2016 23:04:04 +0100
Subject: [PATCH 3/8] s4:dsdb/samldb: check for valid lDAPDisplayName vaues on
add()
This still leaves modifies(), but that's a task for another day.
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/dsdb/samdb/ldb_modules/samldb.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
index 2394bd9..479f89a 100644
--- a/source4/dsdb/samdb/ldb_modules/samldb.c
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -683,6 +683,7 @@ static int samldb_fill_object(struct samldb_ctx *ac)
}
case SAMLDB_TYPE_CLASS: {
+ const char *lDAPDisplayName = NULL;
const struct ldb_val *rdn_value, *def_obj_cat_val;
unsigned int v = ldb_msg_find_attr_as_uint(ac->msg, "objectClassCategory", -2);
@@ -719,6 +720,20 @@ static int samldb_fill_object(struct samldb_ctx *ac)
}
}
+ lDAPDisplayName = ldb_msg_find_attr_as_string(ac->msg,
+ "lDAPDisplayName",
+ NULL);
+ ret = ldb_valid_attr_name(lDAPDisplayName);
+ if (ret != 1 ||
+ lDAPDisplayName[0] == '*' ||
+ lDAPDisplayName[0] == '@')
+ {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_LDAP_DISPLAY_NAME,
+ "lDAPDisplayName is invalid");
+ }
+
if (!ldb_msg_find_element(ac->msg, "schemaIDGUID")) {
struct GUID guid;
/* a new GUID */
@@ -780,6 +795,7 @@ static int samldb_fill_object(struct samldb_ctx *ac)
}
case SAMLDB_TYPE_ATTRIBUTE: {
+ const char *lDAPDisplayName = NULL;
const struct ldb_val *rdn_value;
struct ldb_message_element *el;
rdn_value = ldb_dn_get_rdn_val(ac->msg->dn);
@@ -797,6 +813,20 @@ static int samldb_fill_object(struct samldb_ctx *ac)
}
}
+ lDAPDisplayName = ldb_msg_find_attr_as_string(ac->msg,
+ "lDAPDisplayName",
+ NULL);
+ ret = ldb_valid_attr_name(lDAPDisplayName);
+ if (ret != 1 ||
+ lDAPDisplayName[0] == '*' ||
+ lDAPDisplayName[0] == '@')
+ {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_LDAP_DISPLAY_NAME,
+ "lDAPDisplayName is invalid");
+ }
+
/* do not allow one to mark an attributeSchema as RODC filtered if it
* is system-critical */
if (check_rodc_critical_attribute(ac->msg)) {
--
1.9.1
From 8f890847766372956a37fbab30ddb91147037b02 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 23 Jul 2015 12:09:45 +0200
Subject: [PATCH 4/8] s4:dsdb: add dsdb_notification module
This adds a simple implementation of LDB_CONTROL_NOTIFICATION_OID.
It requires caller (the ldap server task) to retry the request peridically,
using the same ldb_control structure in order to get some progress and
the never ending search behaviour an LDAP client expects.
For now we remember the known_usn in a cookie stored
in the otherwise unused ldb_control->data fielf
and we do a simple search using (uSNChanged>=${known_usn}+1).
In future we may do things based on the uSNChanged value.
for (i = old_highest + 1; i <= current_highest; i) {
search for (uSNChanged=i)
}
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/dsdb/samdb/ldb_modules/dsdb_notification.c | 262 +++++++++++++++++++++
.../dsdb/samdb/ldb_modules/wscript_build_server | 9 +
2 files changed, 271 insertions(+)
create mode 100644 source4/dsdb/samdb/ldb_modules/dsdb_notification.c
diff --git a/source4/dsdb/samdb/ldb_modules/dsdb_notification.c b/source4/dsdb/samdb/ldb_modules/dsdb_notification.c
new file mode 100644
index 0000000..02ccc44
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/dsdb_notification.c
@@ -0,0 +1,262 @@
+/*
+ notification control module
+
+ Copyright (C) Stefan Metzmacher 2015
+
+ 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/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct dsdb_notification_cookie {
+ uint64_t known_usn;
+};
+
+static int dsdb_notification_verify_tree(struct ldb_parse_tree *tree)
+{
+ unsigned int i;
+ int ret;
+ unsigned int num_ok = 0;
+ /*
+ * these attributes are present on every object
+ * and windows accepts them.
+ *
+ * While [MS-ADTS] says only '(objectClass=*)'
+ * would be allowed.
+ */
+ static const char * const attrs_ok[] = {
+ "objectClass",
+ "objectGUID",
+ "distinguishedName",
+ "name",
+ NULL,
+ };
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ for (i = 0; i < tree->u.list.num_elements; i++) {
+ /*
+ * all elements need to be valid
+ */
+ ret = dsdb_notification_verify_tree(tree->u.list.elements[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ num_ok++;
+ }
+ break;
+ case LDB_OP_OR:
+ for (i = 0; i < tree->u.list.num_elements; i++) {
+ /*
+ * at least one element needs to be valid
+ */
+ ret = dsdb_notification_verify_tree(tree->u.list.elements[i]);
+ if (ret == LDB_SUCCESS) {
+ num_ok++;
+ break;
+ }
+ }
+ break;
+ case LDB_OP_NOT:
+ case LDB_OP_EQUALITY:
+ case LDB_OP_GREATER:
+ case LDB_OP_LESS:
+ case LDB_OP_APPROX:
+ case LDB_OP_SUBSTRING:
+ case LDB_OP_EXTENDED:
+ break;
+
+ case LDB_OP_PRESENT:
+ ret = ldb_attr_in_list(attrs_ok, tree->u.present.attr);
+ if (ret == 1) {
+ num_ok++;
+ }
+ break;
+ }
+
+ if (num_ok != 0) {
+ return LDB_SUCCESS;
+ }
+
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+}
+
+static int dsdb_notification_filter_search(struct ldb_module *module,
+ struct ldb_request *req,
+ struct ldb_control *control)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ char *filter_usn = NULL;
+ struct ldb_parse_tree *down_tree = NULL;
+ struct ldb_request *down_req = NULL;
+ struct dsdb_notification_cookie *cookie = NULL;
+ int ret;
+
+ if (req->op.search.tree == NULL) {
+ return dsdb_module_werror(module, LDB_ERR_OTHER,
+ WERR_DS_NOTIFY_FILTER_TOO_COMPLEX,
+ "Search filter missing.");
+ }
+
+ ret = dsdb_notification_verify_tree(req->op.search.tree);
+ if (ret != LDB_SUCCESS) {
+ return dsdb_module_werror(module, ret,
+ WERR_DS_NOTIFY_FILTER_TOO_COMPLEX,
+ "Search filter too complex.");
+ }
+
+ /*
+ * For now we use a very simple design:
+ *
+ * - We don't do fully async ldb_requests,
+ * the caller needs to retry periodically!
+ * - The only useful caller is the LDAP server, which is a long
+ * running task that can do periodic retries.
+ * - We use a cookie in order to transfer state between the
+ * retries.
+ * - We just search the available new objects each time we're
+ * called.
+ *
+ * As the only valid search filter is '(objectClass=*)' or
+ * something similar that matches every object, we simply
+ * replace it with (uSNChanged >= ) filter.
+ * We could improve this later if required...
+ */
+
+ /*
+ * The ldap_control_handler() decode_flag_request for
+ * LDB_CONTROL_NOTIFICATION_OID. This makes sure
+ * notification_control->data is NULL when comming from
+ * the client.
+ */
+ if (control->data == NULL) {
+ cookie = talloc_zero(control, struct dsdb_notification_cookie);
+ if (cookie == NULL) {
+ return ldb_module_oom(module);
+ }
+ control->data = (uint8_t *)cookie;
+
+ /* mark the control as done */
+ control->critical = 0;
+ }
+
+ cookie = talloc_get_type_abort(control->data,
+ struct dsdb_notification_cookie);
+
+ if (cookie->known_usn != 0) {
+ filter_usn = talloc_asprintf(req, "%llu",
+ (unsigned long long)(cookie->known_usn)+1);
+ if (filter_usn == NULL) {
+ return ldb_module_oom(module);
+ }
+ }
+
+ ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ,
+ &cookie->known_usn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (filter_usn == NULL) {
+ /*
+ * It's the first time, let the caller comeback later
+ * as we won't find any new objects.
+ */
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ down_tree = talloc_zero(req, struct ldb_parse_tree);
+ if (down_tree == NULL) {
+ return ldb_module_oom(module);
+ }
+ down_tree->operation = LDB_OP_GREATER;
+ down_tree->u.equality.attr = "uSNChanged";
+ down_tree->u.equality.value = data_blob_string_const(filter_usn);
+ talloc_move(down_req, &filter_usn);
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, req,
+ req->op.search.base,
+ req->op.search.scope,
+ down_tree,
+ req->op.search.attrs,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int dsdb_notification_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control = NULL;
+
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * check if there's an extended dn control
+ */
+ control = ldb_request_get_control(req, LDB_CONTROL_NOTIFICATION_OID);
+ if (control == NULL) {
+ /* not found go on */
+ return ldb_next_request(module, req);
+ }
+
+ return dsdb_notification_filter_search(module, req, control);
+}
+
+static int dsdb_notification_init(struct ldb_module *module)
+{
+ int ret;
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_NOTIFICATION_OID);
+ if (ret != LDB_SUCCESS) {
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "notification: Unable to register control with rootdse!\n");
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_dsdb_notification_module_ops = {
+ .name = "dsdb_notification",
+ .search = dsdb_notification_search,
+ .init_context = dsdb_notification_init,
+};
+
+/*
+ initialise the module
+ */
+_PUBLIC_ int ldb_dsdb_notification_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_dsdb_notification_module_ops);
+ return ret;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server
index 8fa9939..aba2d87 100755
--- a/source4/dsdb/samdb/ldb_modules/wscript_build_server
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server
@@ -365,6 +365,15 @@ bld.SAMBA_MODULE('ldb_dirsync',
deps='talloc samba-security samdb DSDB_MODULE_HELPERS'
)
+bld.SAMBA_MODULE('ldb_dsdb_notification',
+ source='dsdb_notification.c',
+ subsystem='ldb',
+ init_function='ldb_dsdb_notification_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-security samdb DSDB_MODULE_HELPERS'
+ )
+
bld.SAMBA_MODULE('ldb_dns_notify',
source='dns_notify.c',
subsystem='ldb',
--
1.9.1
From 0da8cf3a8e4d9288ce7649e7a5df50db7317221b Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 23 Jul 2015 12:08:42 +0200
Subject: [PATCH 5/8] s4:ldap_server: add support for async notification
requests
This is a simplified version that works with the current
dsdb_notification module that requires the caller to retry
periodically. We do that every 5 seconds or 100 microseconds
if we're forcing a retry.
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/ldap_server/ldap_backend.c | 82 +++++++++++++++++++--
source4/ldap_server/ldap_bind.c | 22 ++++--
source4/ldap_server/ldap_extended.c | 8 ++-
source4/ldap_server/ldap_server.c | 138 ++++++++++++++++++++++++++++++++++--
source4/ldap_server/ldap_server.h | 15 ++++
5 files changed, 248 insertions(+), 17 deletions(-)
diff --git a/source4/ldap_server/ldap_backend.c b/source4/ldap_server/ldap_backend.c
index 7efb7ed..6a8a0cf 100644
--- a/source4/ldap_server/ldap_backend.c
+++ b/source4/ldap_server/ldap_backend.c
@@ -513,6 +513,7 @@ static NTSTATUS ldapsrv_SearchRequest(struct ldapsrv_call *call)
struct ldb_search_options_control *search_options;
struct ldb_control *extended_dn_control;
struct ldb_extended_dn_control *extended_dn_decoded = NULL;
+ struct ldb_control *notification_control = NULL;
enum ldb_scope scope = LDB_SCOPE_DEFAULT;
const char **attrs = NULL;
const char *scope_str, *errstr = NULL;
@@ -617,6 +618,31 @@ static NTSTATUS ldapsrv_SearchRequest(struct ldapsrv_call *call)
}
}
+ notification_control = ldb_request_get_control(lreq, LDB_CONTROL_NOTIFICATION_OID);
+ if (notification_control != NULL) {
+ const struct ldapsrv_call *pc = NULL;
+ size_t count = 0;
+
+ for (pc = call->conn->pending_calls; pc != NULL; pc = pc->next) {
+ count += 1;
+ }
+
+ if (count >= call->conn->limits.max_notifications) {
+ DEBUG(10,("SearchRequest: error MaxNotificationPerConn\n"));
+ result = map_ldb_error(local_ctx,
+ LDB_ERR_ADMIN_LIMIT_EXCEEDED,
+ "MaxNotificationPerConn reached",
+ &errstr);
+ goto reply;
+ }
+
+ /*
+ * For now we need to do periodic retries on our own.
+ * As the dsdb_notification module will return after each run.
+ */
+ call->notification.busy = true;
+ }
+
ldb_set_timeout(samdb, lreq, req->timelimit);
if (!call->conn->is_privileged) {
@@ -667,6 +693,22 @@ queue_reply:
ldapsrv_queue_reply(call, ent_r);
}
+ if (call->notification.busy) {
+ /* Move/Add it to the end */
+ DLIST_DEMOTE(call->conn->pending_calls, call);
+ call->notification.generation =
+ call->conn->service->notification.generation;
+
+ if (res->count != 0) {
+ call->notification.generation += 1;
+ ldapsrv_notification_retry_setup(call->conn->service,
+ true);
+ }
+
+ talloc_free(local_ctx);
+ return NT_STATUS_OK;
+ }
+
/* Send back referrals if they do exist (search operations) */
if (res->refs != NULL) {
char **ref;
@@ -691,6 +733,9 @@ queue_reply:
}
reply:
+ DLIST_REMOVE(call->conn->pending_calls, call);
+ call->notification.busy = false;
+
done_r = ldapsrv_init_reply(call, LDAP_TAG_SearchResultDone);
NT_STATUS_HAVE_NO_MEMORY(done_r);
@@ -1157,8 +1202,23 @@ static NTSTATUS ldapsrv_CompareRequest(struct ldapsrv_call *call)
static NTSTATUS ldapsrv_AbandonRequest(struct ldapsrv_call *call)
{
-/* struct ldap_AbandonRequest *req = &call->request.r.AbandonRequest;*/
+ struct ldap_AbandonRequest *req = &call->request->r.AbandonRequest;
+ struct ldapsrv_call *c = NULL;
+ struct ldapsrv_call *n = NULL;
+
DEBUG(10, ("AbandonRequest\n"));
+
+ for (c = call->conn->pending_calls; c != NULL; c = n) {
+ n = c->next;
+
+ if (c->request->messageid != req->messageid) {
+ continue;
+ }
+
+ DLIST_REMOVE(call->conn->pending_calls, c);
+ TALLOC_FREE(c);
+ }
+
return NT_STATUS_OK;
}
@@ -1169,6 +1229,8 @@ NTSTATUS ldapsrv_do_call(struct ldapsrv_call *call)
struct ldb_context *samdb = call->conn->ldb;
NTSTATUS status;
time_t *lastts;
+ bool recheck_schema = false;
+
/* Check for undecoded critical extensions */
for (i=0; msg->controls && msg->controls[i]; i++) {
if (!msg->controls_decoded[i] &&
@@ -1187,26 +1249,31 @@ NTSTATUS ldapsrv_do_call(struct ldapsrv_call *call)
case LDAP_TAG_SearchRequest:
return ldapsrv_SearchRequest(call);
case LDAP_TAG_ModifyRequest:
+ recheck_schema = true;
status = ldapsrv_ModifyRequest(call);
break;
case LDAP_TAG_AddRequest:
+ recheck_schema = true;
status = ldapsrv_AddRequest(call);
break;
case LDAP_TAG_DelRequest:
- return ldapsrv_DelRequest(call);
+ status = ldapsrv_DelRequest(call);
+ break;
case LDAP_TAG_ModifyDNRequest:
- return ldapsrv_ModifyDNRequest(call);
+ status = ldapsrv_ModifyDNRequest(call);
+ break;
case LDAP_TAG_CompareRequest:
return ldapsrv_CompareRequest(call);
case LDAP_TAG_AbandonRequest:
return ldapsrv_AbandonRequest(call);
case LDAP_TAG_ExtendedRequest:
- return ldapsrv_ExtendedRequest(call);
+ status = ldapsrv_ExtendedRequest(call);
+ break;
default:
return ldapsrv_unwilling(call, LDAP_PROTOCOL_ERROR);
}
- if (NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(status) && recheck_schema) {
lastts = (time_t *)ldb_get_opaque(samdb, DSDB_OPAQUE_LAST_SCHEMA_UPDATE_MSG_OPAQUE_NAME);
if (lastts && !*lastts) {
DEBUG(10, ("Schema update now was requested, "
@@ -1222,5 +1289,10 @@ NTSTATUS ldapsrv_do_call(struct ldapsrv_call *call)
ldb_set_opaque(samdb, DSDB_OPAQUE_LAST_SCHEMA_UPDATE_MSG_OPAQUE_NAME, lastts);
}
}
+
+ if (NT_STATUS_IS_OK(status)) {
+ ldapsrv_notification_retry_setup(call->conn->service, true);
+ }
+
return status;
}
diff --git a/source4/ldap_server/ldap_bind.c b/source4/ldap_server/ldap_bind.c
index fcbdadf..77dfe90 100644
--- a/source4/ldap_server/ldap_bind.c
+++ b/source4/ldap_server/ldap_bind.c
@@ -333,11 +333,25 @@ NTSTATUS ldapsrv_BindRequest(struct ldapsrv_call *call)
struct ldapsrv_reply *reply;
struct ldap_BindResponse *resp;
+ if (call->conn->pending_calls != NULL) {
+ reply = ldapsrv_init_reply(call, LDAP_TAG_BindResponse);
+ if (!reply) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ resp = &reply->msg->r.BindResponse;
+ resp->response.resultcode = LDAP_BUSY;
+ resp->response.dn = NULL;
+ resp->response.errormessage = talloc_asprintf(reply, "Pending requests on this LDAP session");
+ resp->response.referral = NULL;
+ resp->SASL.secblob = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+ }
+
/*
- * TODO: we should fail the bind request
- * if there're any pending requests.
- *
- * also a simple bind should cancel an
+ * TODO: a simple bind should cancel an
* inprogress SASL bind.
* (see RFC 4513)
*/
diff --git a/source4/ldap_server/ldap_extended.c b/source4/ldap_server/ldap_extended.c
index 338858f..2d4a534 100644
--- a/source4/ldap_server/ldap_extended.c
+++ b/source4/ldap_server/ldap_extended.c
@@ -112,8 +112,7 @@ static NTSTATUS ldapsrv_StartTLS(struct ldapsrv_call *call,
/*
* TODO: give LDAP_OPERATIONS_ERROR also when
- * there're pending requests or there's
- * a SASL bind in progress
+ * there's a SASL bind in progress
* (see rfc4513 section 3.1.1)
*/
if (call->conn->sockets.tls) {
@@ -126,6 +125,11 @@ static NTSTATUS ldapsrv_StartTLS(struct ldapsrv_call *call,
return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR);
}
+ if (call->conn->pending_calls != NULL) {
+ (*errstr) = talloc_asprintf(reply, "START-TLS: pending requests on this LDAP session");
+ return NT_STATUS_LDAP(LDAP_BUSY);
+ }
+
context = talloc(call, struct ldapsrv_starttls_postprocess_context);
NT_STATUS_HAVE_NO_MEMORY(context);
diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c
index 02c3c95..9b2f185 100644
--- a/source4/ldap_server/ldap_server.c
+++ b/source4/ldap_server/ldap_server.c
@@ -62,6 +62,8 @@ static void ldapsrv_terminate_connection(struct ldapsrv_connection *conn,
return;
}
+ DLIST_REMOVE(conn->service->connections, conn);
+
conn->limits.endtime = timeval_current_ofs(0, 500);
tevent_queue_stop(conn->sockets.send_queue);
@@ -167,6 +169,7 @@ static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
conn->limits.initial_timeout = 120;
conn->limits.conn_idle_time = 900;
conn->limits.max_page_size = 1000;
+ conn->limits.max_notifications = 5;
conn->limits.search_timeout = 120;
@@ -233,6 +236,10 @@ static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
conn->limits.max_page_size = policy_value;
continue;
}
+ if (strcasecmp("MaxNotificationPerConn", policy_name) == 0) {
+ conn->limits.max_notifications = policy_value;
+ continue;
+ }
if (strcasecmp("MaxQueryDuration", policy_name) == 0) {
conn->limits.search_timeout = policy_value;
continue;
@@ -347,6 +354,8 @@ static void ldapsrv_accept(struct stream_connection *c,
/* register the server */
irpc_add_name(c->msg_ctx, "ldap_server");
+ DLIST_ADD_END(ldapsrv_service->connections, conn);
+
if (port != 636 && port != 3269) {
ldapsrv_call_read_next(conn);
return;
@@ -405,7 +414,11 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
{
struct tevent_req *subreq;
- if (timeval_is_zero(&conn->limits.endtime)) {
+ if (conn->pending_calls != NULL) {
+ conn->limits.endtime = timeval_zero();
+
+ ldapsrv_notification_retry_setup(conn->service, false);
+ } else if (timeval_is_zero(&conn->limits.endtime)) {
conn->limits.endtime =
timeval_current_ofs(conn->limits.initial_timeout, 0);
} else {
@@ -456,9 +469,11 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
"no memory for tstream_read_pdu_blob_send");
return false;
}
- tevent_req_set_endtime(subreq,
- conn->connection->event.ctx,
- conn->limits.endtime);
+ if (!timeval_is_zero(&conn->limits.endtime)) {
+ tevent_req_set_endtime(subreq,
+ conn->connection->event.ctx,
+ conn->limits.endtime);
+ }
tevent_req_set_callback(subreq, ldapsrv_call_read_done, conn);
conn->sockets.read_req = subreq;
return true;
@@ -544,6 +559,7 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq)
conn->active_call = subreq;
}
+
static void ldapsrv_call_writev_done(struct tevent_req *subreq);
static void ldapsrv_call_process_done(struct tevent_req *subreq)
@@ -590,7 +606,9 @@ static void ldapsrv_call_process_done(struct tevent_req *subreq)
}
if (blob.length == 0) {
- TALLOC_FREE(call);
+ if (!call->notification.busy) {
+ TALLOC_FREE(call);
+ }
ldapsrv_call_read_next(conn);
return;
@@ -654,7 +672,9 @@ static void ldapsrv_call_writev_done(struct tevent_req *subreq)
return;
}
- TALLOC_FREE(call);
+ if (!call->notification.busy) {
+ TALLOC_FREE(call);
+ }
ldapsrv_call_read_next(conn);
}
@@ -688,6 +708,112 @@ static void ldapsrv_call_postprocess_done(struct tevent_req *subreq)
ldapsrv_call_read_next(conn);
}
+static void ldapsrv_notification_retry_done(struct tevent_req *subreq);
+
+void ldapsrv_notification_retry_setup(struct ldapsrv_service *service, bool force)
+{
+ struct ldapsrv_connection *conn = NULL;
+ struct timeval retry;
+ size_t num_pending = 0;
+ size_t num_active = 0;
+
+ if (force) {
+ TALLOC_FREE(service->notification.retry);
+ service->notification.generation += 1;
+ }
+
+ if (service->notification.retry != NULL) {
+ return;
+ }
+
+ for (conn = service->connections; conn != NULL; conn = conn->next) {
+ if (conn->pending_calls == NULL) {
+ continue;
+ }
+
+ num_pending += 1;
+
+ if (conn->pending_calls->notification.generation !=
+ service->notification.generation)
+ {
+ num_active += 1;
+ }
+ }
+
+ if (num_pending == 0) {
+ return;
+ }
+
+ if (num_active != 0) {
+ retry = timeval_current_ofs(0, 100);
+ } else {
+ retry = timeval_current_ofs(5, 0);
+ }
+
+ service->notification.retry = tevent_wakeup_send(service,
+ service->task->event_ctx,
+ retry);
+ if (service->notification.retry == NULL) {
+ /* retry later */
+ return;
+ }
+
+ tevent_req_set_callback(service->notification.retry,
+ ldapsrv_notification_retry_done,
+ service);
+}
+
+static void ldapsrv_notification_retry_done(struct tevent_req *subreq)
+{
+ struct ldapsrv_service *service =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_service);
+ struct ldapsrv_connection *conn = NULL;
+ struct ldapsrv_connection *conn_next = NULL;
+ bool ok;
+
+ service->notification.retry = NULL;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ /* ignore */
+ }
+
+ for (conn = service->connections; conn != NULL; conn = conn_next) {
+ struct ldapsrv_call *call = conn->pending_calls;
+
+ conn_next = conn->next;
+
+ if (conn->pending_calls == NULL) {
+ continue;
+ }
+
+ if (conn->active_call != NULL) {
+ continue;
+ }
+
+ DLIST_DEMOTE(conn->pending_calls, call);
+ call->notification.generation =
+ service->notification.generation;
+
+ /* queue the call in the global queue */
+ subreq = ldapsrv_process_call_send(call,
+ conn->connection->event.ctx,
+ conn->service->call_queue,
+ call);
+ if (subreq == NULL) {
+ ldapsrv_terminate_connection(conn,
+ "ldapsrv_process_call_send failed");
+ continue;
+ }
+ tevent_req_set_callback(subreq, ldapsrv_call_process_done, call);
+ conn->active_call = subreq;
+ }
+
+ ldapsrv_notification_retry_setup(service, false);
+}
+
struct ldapsrv_process_call_state {
struct ldapsrv_call *call;
};
diff --git a/source4/ldap_server/ldap_server.h b/source4/ldap_server/ldap_server.h
index bfd95c0..27e0f13 100644
--- a/source4/ldap_server/ldap_server.h
+++ b/source4/ldap_server/ldap_server.h
@@ -24,6 +24,7 @@
#include "system/network.h"
struct ldapsrv_connection {
+ struct ldapsrv_connection *next, *prev;
struct loadparm_context *lp_ctx;
struct stream_connection *connection;
struct gensec_security *gensec;
@@ -48,15 +49,19 @@ struct ldapsrv_connection {
int initial_timeout;
int conn_idle_time;
int max_page_size;
+ int max_notifications;
int search_timeout;
struct timeval endtime;
const char *reason;
} limits;
struct tevent_req *active_call;
+
+ struct ldapsrv_call *pending_calls;
};
struct ldapsrv_call {
+ struct ldapsrv_call *prev, *next;
struct ldapsrv_connection *conn;
struct ldap_message *request;
struct ldapsrv_reply {
@@ -70,12 +75,22 @@ struct ldapsrv_call {
void *private_data);
NTSTATUS (*postprocess_recv)(struct tevent_req *req);
void *postprocess_private;
+
+ struct {
+ bool busy;
+ uint64_t generation;
+ } notification;
};
struct ldapsrv_service {
struct tstream_tls_params *tls_params;
struct task_server *task;
struct tevent_queue *call_queue;
+ struct ldapsrv_connection *connections;
+ struct {
+ uint64_t generation;
+ struct tevent_req *retry;
+ } notification;
};
#include "ldap_server/proto.h"
--
1.9.1
From 689eb6865ee3e8cb5440010b902aeea3598405f1 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 23 Jul 2015 12:09:45 +0200
Subject: [PATCH 6/8] s4:dsdb: let samba_dsdb make use of the dsdb_notification
module
This means our LDAP server will support LDB_CONTROL_NOTIFICATION_OID now.
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/dsdb/samdb/ldb_modules/samba_dsdb.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
index 26c583e..4830e65 100644
--- a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
+++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
@@ -261,6 +261,7 @@ static int samba_dsdb_init(struct ldb_module *module)
*/
static const char *modules_list1[] = {"resolve_oids",
"rootdse",
+ "dsdb_notification",
"schema_load",
"lazy_commit",
"dirsync",
--
1.9.1
From 62b8df7bcd6e5725c16da3a6f4676cecf76d806f Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 1 Feb 2016 10:58:41 +0100
Subject: [PATCH 7/8] s4:dsdb/tests: add notification.py with some basic tests
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/dsdb/tests/python/notification.py | 352 ++++++++++++++++++++++++++++++
1 file changed, 352 insertions(+)
create mode 100755 source4/dsdb/tests/python/notification.py
diff --git a/source4/dsdb/tests/python/notification.py b/source4/dsdb/tests/python/notification.py
new file mode 100755
index 0000000..d799c91
--- /dev/null
+++ b/source4/dsdb/tests/python/notification.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+#
+# Unit tests for the notification control
+# Copyright (C) Stefan Metzmacher 2016
+#
+# 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/>.
+
+import optparse
+import sys
+import os
+
+sys.path.insert(0, "bin/python")
+import samba
+
+from samba.tests.subunitrun import SubunitOptions, TestProgram
+
+import samba.getopt as options
+
+from samba.auth import system_session
+from samba import ldb
+from samba.samdb import SamDB
+from samba.ndr import ndr_unpack
+from samba import gensec
+from samba.credentials import Credentials
+import samba.tests
+
+from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
+
+from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError
+from ldb import ERR_TIME_LIMIT_EXCEEDED, ERR_ADMIN_LIMIT_EXCEEDED, ERR_UNWILLING_TO_PERFORM
+from ldb import Message
+
+parser = optparse.OptionParser("notification.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)
+subunitopts = SubunitOptions(parser)
+parser.add_option_group(subunitopts)
+opts, args = parser.parse_args()
+
+if len(args) < 1:
+ parser.print_usage()
+ sys.exit(1)
+
+url = args[0]
+
+lp = sambaopts.get_loadparm()
+creds = credopts.get_credentials(lp)
+creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+
+class LDAPNotificationTest(samba.tests.TestCase):
+
+ def setUp(self):
+ super(samba.tests.TestCase, self).setUp()
+ self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
+ self.base_dn = self.ldb.domain_dn()
+
+ res = self.ldb.search("", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
+ self.assertEquals(len(res), 1)
+
+ self.user_sid_dn = "<SID=%s>" % str(ndr_unpack(samba.dcerpc.security.dom_sid, res[0]["tokenGroups"][0]))
+
+ def test_simple_search(self):
+ """Testing a notification with an modify and a timeout"""
+ if not url.startswith("ldap"):
+ self.fail(msg="This test is only valid on ldap")
+
+ msg1 = None
+ search1 = self.ldb.search_iterator(base=self.user_sid_dn,
+ expression="(objectClass=*)",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name", "objectGUID", "displayName"])
+ for reply in search1:
+ self.assertIsInstance(reply, ldb.Message)
+ self.assertIsNone(msg1)
+ msg1 = reply
+ res1 = search1.result()
+
+ search2 = self.ldb.search_iterator(base=self.base_dn,
+ expression="(objectClass=*)",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name", "objectGUID", "displayName"])
+ refs2 = 0
+ msg2 = None
+ for reply in search2:
+ if isinstance(reply, str):
+ refs2 += 1
+ continue
+ self.assertIsInstance(reply, ldb.Message)
+ if reply["objectGUID"][0] == msg1["objectGUID"][0]:
+ self.assertIsNone(msg2)
+ msg2 = reply
+ self.assertEqual(msg1.dn, msg2.dn)
+ self.assertEqual(len(msg1), len(msg2))
+ self.assertEqual(msg1["name"], msg2["name"])
+ #self.assertEqual(msg1["displayName"], msg2["displayName"])
+ res2 = search2.result()
+
+ self.ldb.modify_ldif("""
+dn: """ + self.user_sid_dn + """
+changetype: modify
+replace: otherLoginWorkstations
+otherLoginWorkstations: BEFORE"
+""")
+ notify1 = self.ldb.search_iterator(base=self.base_dn,
+ expression="(objectClass=*)",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name", "objectGUID", "displayName"],
+ controls=["notification:1"],
+ timeout=1)
+
+ self.ldb.modify_ldif("""
+dn: """ + self.user_sid_dn + """
+changetype: modify
+replace: otherLoginWorkstations
+otherLoginWorkstations: AFTER"
+""")
+
+ msg3 = None
+ for reply in notify1:
+ self.assertIsInstance(reply, ldb.Message)
+ if reply["objectGUID"][0] == msg1["objectGUID"][0]:
+ self.assertIsNone(msg3)
+ msg3 = reply
+ self.assertEqual(msg1.dn, msg3.dn)
+ self.assertEqual(len(msg1), len(msg3))
+ self.assertEqual(msg1["name"], msg3["name"])
+ #self.assertEqual(msg1["displayName"], msg3["displayName"])
+ try:
+ res = notify1.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_TIME_LIMIT_EXCEEDED)
+ self.assertIsNotNone(msg3)
+
+ self.ldb.modify_ldif("""
+dn: """ + self.user_sid_dn + """
+changetype: delete
+delete: otherLoginWorkstations
+""")
+
+ def test_max_search(self):
+ """Testing the max allowed notifications"""
+ if not url.startswith("ldap"):
+ self.fail(msg="This test is only valid on ldap")
+
+ max_notifications = 5
+
+ notifies = [None] * (max_notifications + 1)
+ for i in xrange(0, max_notifications + 1):
+ notifies[i] = self.ldb.search_iterator(base=self.base_dn,
+ expression="(objectClass=*)",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=1)
+ for i in xrange(0, max_notifications + 1):
+ try:
+ for msg in notifies[i]:
+ continue
+ res = notifies[i].result()
+ self.fail()
+ except LdbError, (num, _):
+ if i >= max_notifications:
+ self.assertEquals(num, ERR_ADMIN_LIMIT_EXCEEDED)
+ else:
+ self.assertEquals(num, ERR_TIME_LIMIT_EXCEEDED)
+
+ def test_invalid_filter(self):
+ """Testing invalid filters for notifications"""
+ if not url.startswith("ldap"):
+ self.fail(msg="This test is only valid on ldap")
+
+ valid_attrs = ["objectClass", "objectGUID", "distinguishedName", "name"]
+
+ for va in valid_attrs:
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s=*)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=1)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_TIME_LIMIT_EXCEEDED)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(|(%s=*)(%s=value))" % (va, va),
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=1)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_TIME_LIMIT_EXCEEDED)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(&(%s=*)(%s=value))" % (va, va),
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s=value)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s>=value)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s<=value)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s=*value*)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(!(%s=*))" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ res = self.ldb.search(base=self.ldb.get_schema_basedn(),
+ expression="(objectClass=attributeSchema)",
+ scope=ldb.SCOPE_ONELEVEL,
+ attrs=["lDAPDisplayName"],
+ controls=["paged_results:1:2500"])
+ for msg in res:
+ va = msg["lDAPDisplayName"][0]
+ if va in valid_attrs:
+ continue
+
+ try:
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s=*)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ if num != ERR_UNWILLING_TO_PERFORM:
+ print "va[%s]" % va
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+ try:
+ va = "noneAttributeName"
+ hnd = self.ldb.search_iterator(base=self.base_dn,
+ expression="(%s=*)" % va,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["name"],
+ controls=["notification:1"],
+ timeout=0)
+ for reply in hnd:
+ self.fail()
+ res = hnd.result()
+ self.fail()
+ except LdbError, (num, _):
+ if num != ERR_UNWILLING_TO_PERFORM:
+ print "va[%s]" % va
+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+
+if not "://" in url:
+ if os.path.isfile(url):
+ url = "tdb://%s" % url
+ else:
+ url = "ldap://%s" % url
+
+TestProgram(module=__name__, opts=subunitopts)
--
1.9.1
From 72be4bb8a4a3e0cf2eecbb0d9214675bc263ede1 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 1 Feb 2016 12:27:18 +0100
Subject: [PATCH 8/8] s4:selftest: run samba4.ldap.notification.python
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
source4/selftest/tests.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 6160152..d76eda6 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -506,6 +506,7 @@ planoldpythontestsuite("ad_dc_ntvfs", "dsdb_schema_info",
plantestsuite_loadlist("samba4.urgent_replication.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/urgent_replication.py"), '$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb', '$LOADLIST', '$LISTOPT'])
plantestsuite_loadlist("samba4.ldap.dirsync.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/dirsync.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
plantestsuite_loadlist("samba4.ldap.match_rules.python", "ad_dc_ntvfs", [python, os.path.join(srcdir(), "lib/ldb-samba/tests/match_rules.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
+plantestsuite_loadlist("samba4.ldap.notification.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/notification.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
plantestsuite_loadlist("samba4.ldap.sites.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/sites.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
for env in ["ad_dc_ntvfs", "fl2000dc", "fl2003dc", "fl2008r2dc"]:
plantestsuite_loadlist("samba4.ldap_schema.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap_schema.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
--
1.9.1
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20160215/9f0deb97/signature.sig>
More information about the samba-technical
mailing list