Implement LDAP_CONTROL_NOTIFICATION

Stefan Metzmacher metze at samba.org
Tue Feb 2 12:26:50 UTC 2016


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.

Please review and push:-)

Thanks!
metze
-------------- next part --------------
From 7e0bb41821e4132d110a024d50b8ca9c079fc82f 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 0188045c0940440997cf60acf5a64efd4cc7b615 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 32331e452833279855995525bb0bc3d9c6a38410 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 9253a11434ad7e12d0187a2b5c1ee1b7457979cd 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 3590458b028df9077971f7f5ff266219a4cb57db 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 fd2b579..cb5b68d 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, NULL);
+			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..e819487 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, NULL);
+
 	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, NULL);
+		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 988d4d9e3a2af8cf160e629b6d134fba60720dd4 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 9dd86b42dd2063489209c0e78f82ef9a047f8961 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 d044275612ec59bf4338a257d83d9d5a62b04cec 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/20160202/06d7a7a9/signature.sig>


More information about the samba-technical mailing list