[WIP] Log database changes.

Gary Lockyer gary at catalyst.net.nz
Sun May 6 19:20:50 UTC 2018


Current state of this task.

Comments appreciated.

Thanks
Gary

On 05/04/18 15:13, Gary Lockyer via samba-technical wrote:
> I'm currently adding logging of Database changes, the attached patch set
> is the work to date.
> 
> It still needs the the following done:
> * separating out the common code with the auth logging
> * cleaning up the code
> * plumbing the remote IP through
>   - it's not currently available in all cases
> * Add a session ID GUID and log that in the Authorization messages as
>   well
> * Log/Audit group membership changes.
> * Write unit and integration Tests.
> 
> Notes:
> * Currently the attribute truncation limit is set artificially low to
>   aid development
> 
> Sample Human readable messages.
>   Values enclosed in {} are base64 encoded
>   Values ending with ... have been truncated.
> 
>   Line breaks added for clarity
> 
> Samdb Change [Add] at [Thu, 05 Apr 2018 14:56:28.086708 NZST]
>   transaction id [d9184e5e-aeca-4793-b7a7-a8a558378421]
>   status [Success] remote host [Unknown]
>   SID [S-1-5-21-202143440-2159076023-2784540227-500]
>   DN [CN=krbtgt,CN=Users,DC=addom,DC=samba,DC=example,DC=com]
>   attributes [
>     accountExpires [9223372036854775807]
>     adminCount [1]
>     clearTextPassword [REDACTED SECRET ATTRIBUTE]
>     description [Key Distribution Cen...]
>     isCriticalSystemObject [TRUE]
>     objectClass [top] [person] [organizationalPerson] [user]
>     objectSid {AQUAAAAAAAUVAAAA0HYMDLfisIA=...}
>     sAMAccountName [krbtgt]
>     servicePrincipalName [kadmin/changepw]
>     showInAdvancedViewOnly [TRUE] userAccountControl [514]]
> 
> Password Change [Reset] at [Thu, 05 Apr 2018 14:56:28.086738 NZST]
>    transaction id [d9184e5e-aeca-4793-b7a7-a8a558378421]
>    status [Success] remote host [Unknown]
>    SID [S-1-5-21-202143440-2159076023-2784540227-500]
>    DN [CN=krbtgt,CN=Users,DC=addom,DC=samba,DC=example,DC=com]
> 
> 
> Sample JSON messages:
> JSON samdbChange: {
>   "timestamp": "2018-04-05T14:56:28.086749+1200",
>   "type": "samdbChange",
>   "samdbChange": {
>     "status": "Success",
>     "version": {"major": 1, "minor": 0},
>     "remoteAddress": "NULL",
>     "operation": "Add",
>     "userSid": "S-1-5-21-202143440-2159076023-2784540227-500",
>     "dn": "CN=krbtgt,CN=Users,DC=addom,DC=samba,DC=example,DC=com",
>     "transactionId": "d9184e5e-aeca-4793-b7a7-a8a558378421",
>     "attributes": {
>       "adminCount": {"values": [{"value": "1"}]},
>       "objectSid": {"values": [
>          {"truncated": true, "base64": true,
>             "value": "AQUAAAAAAAUVAAAA0HYMDLfisIA="}]},
>     "accountExpires": {"values": [{"value": "9223372036854775807"}]},
>     "objectClass": {"values": [
>       {"value": "top"},
>       {"value": "person"},
>       {"value": "organizationalPerson"},
>       {"value": "user"}
>     ]},
>     "clearTextPassword": {"redacted": true},
>     "description": {"values": [
>       {"truncated": true, "value": "Key Distribution Cen"}
>     ]},
>     "isCriticalSystemObject": {"values": [{"value": "TRUE"}]},
>     "sAMAccountName": {"values": [{"value": "krbtgt"}]},
>     "servicePrincipalName": {"values": [{"value": "kadmin/changepw"}]},
>     "showInAdvancedViewOnly": {"values": [{"value": "TRUE"}]},
>     "userAccountControl": {"values": [{"value": "514"}]}}}}
> 
> JSON passwordChange: {
>   "timestamp": "2018-04-05T14:56:28.086947+1200",
>   "type": "passwordChange",
>   "passwordChange": {
>     "status": "Success",
>     "version": {"major": 1, "minor": 0},
>     "remoteAddress": "NULL",
>     "userSid": "S-1-5-21-202143440-2159076023-2784540227-500",
>     "action": "Reset",
>     "dn": "CN=krbtgt,CN=Users,DC=addom,DC=samba,DC=example,DC=com",
>    "transactionId": "d9184e5e-aeca-4793-b7a7-a8a558378421"}}
> 
> Comments appreciated
> Gary
> 
-------------- next part --------------
From bc942578632d50936bc18ff537ece760b2c83c4b Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Wed, 4 Apr 2018 11:55:00 +1200
Subject: [PATCH 01/21] dsdb: refactor password attibutes to constant

The password attributes are defined as literal in two places in the
password_hash code.  They will also be needed to support password change
logging. This patch replaces the individual definitions with a shared
constant.

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source4/dsdb/common/util.h                     | 6 ++++++
 source4/dsdb/samdb/ldb_modules/password_hash.c | 8 ++------
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/source4/dsdb/common/util.h b/source4/dsdb/common/util.h
index ede6d8b..85fabde 100644
--- a/source4/dsdb/common/util.h
+++ b/source4/dsdb/common/util.h
@@ -65,6 +65,12 @@ bool is_attr_in_list(const char * const * attrs, const char *attr);
 #define DSDB_SECRET_ATTRIBUTES_COMMA ,
 #define DSDB_SECRET_ATTRIBUTES DSDB_SECRET_ATTRIBUTES_EX(DSDB_SECRET_ATTRIBUTES_COMMA)
 
+#define DSDB_PASSWORD_ATTRIBUTES \
+	"userPassword", \
+	"clearTextPassword", \
+	"unicodePwd", \
+	"dBCSPwd"
+
 struct GUID;
 
 char *NS_GUID_string(TALLOC_CTX *mem_ctx, const struct GUID *guid);
diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c
index 146fb6f..2641cb9 100644
--- a/source4/dsdb/samdb/ldb_modules/password_hash.c
+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c
@@ -4008,10 +4008,7 @@ static int password_hash_needed(struct ldb_module *module,
 	const struct ldb_message *msg = NULL;
 	struct ph_context *ac = NULL;
 	const char *passwordAttrs[] = {
-		"userPassword",
-		"clearTextPassword",
-		"unicodePwd",
-		"dBCSPwd",
+		DSDB_PASSWORD_ATTRIBUTES,
 		NULL
 	};
 	const char **a = NULL;
@@ -4242,8 +4239,7 @@ static int password_hash_modify(struct ldb_module *module, struct ldb_request *r
 {
 	struct ldb_context *ldb = ldb_module_get_ctx(module);
 	struct ph_context *ac = NULL;
-	const char *passwordAttrs[] = { "userPassword", "clearTextPassword",
-		"unicodePwd", "dBCSPwd", NULL }, **l;
+	const char *passwordAttrs[] = {DSDB_PASSWORD_ATTRIBUTES, NULL}, **l;
 	unsigned int del_attr_cnt, add_attr_cnt, rep_attr_cnt;
 	struct ldb_message_element *passwordAttr;
 	struct ldb_message *msg;
-- 
2.7.4


From a3f4e69cd34e907fe3094327692d3933de57c5e2 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 10 Apr 2018 06:44:00 +1200
Subject: [PATCH 02/21] auth: Add unique session GUID identifier

Generate a GUID for each successful authorization, this will allow the
tying of events in the logs back to a specific session.

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 librpc/idl/auth.idl      | 7 +++++++
 source3/auth/auth_util.c | 6 ++++++
 source4/auth/session.c   | 2 ++
 3 files changed, 15 insertions(+)

diff --git a/librpc/idl/auth.idl b/librpc/idl/auth.idl
index 6cc7dcf..d26f575 100644
--- a/librpc/idl/auth.idl
+++ b/librpc/idl/auth.idl
@@ -105,6 +105,13 @@ interface auth
 		[noprint] DATA_BLOB session_key;
 
 		[value(NULL), ignore] cli_credentials *credentials;
+
+	        /*
+		 * It is really handy to have our authorization code log a
+		 * token that can be used to tie later requests togeather.
+		 * We generate this in auth_generate_session_info()
+		 */
+	        GUID unique_session_token;
 	} auth_session_info;
 
 	typedef [public] struct {
diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c
index 3b951e7..24d1e37 100644
--- a/source3/auth/auth_util.c
+++ b/source3/auth/auth_util.c
@@ -488,6 +488,8 @@ NTSTATUS create_local_token(TALLOC_CTX *mem_ctx,
 			return NT_STATUS_NO_MEMORY;
 		}
 
+		session_info->unique_session_token = GUID_random();
+
 		*session_info_out = session_info;
 		return NT_STATUS_OK;
 	}
@@ -658,6 +660,8 @@ NTSTATUS create_local_token(TALLOC_CTX *mem_ctx,
 		return status;
 	}
 
+	session_info->unique_session_token = GUID_random();
+
 	*session_info_out = session_info;
 	return NT_STATUS_OK;
 }
@@ -1209,6 +1213,8 @@ done:
 		return status;
 	}
 
+	session_info->unique_session_token = GUID_random();
+	
 	*session_info_out = talloc_move(mem_ctx, &session_info);
 	TALLOC_FREE(frame);
 	return NT_STATUS_OK;
diff --git a/source4/auth/session.c b/source4/auth/session.c
index 982d51d..c27d273 100644
--- a/source4/auth/session.c
+++ b/source4/auth/session.c
@@ -220,6 +220,8 @@ _PUBLIC_ NTSTATUS auth_generate_session_info(TALLOC_CTX *mem_ctx,
 		return nt_status;
 	}
 
+	session_info->unique_session_token = GUID_random();
+
 	session_info->credentials = NULL;
 
 	talloc_steal(mem_ctx, session_info);
-- 
2.7.4


From 2235afa0e985a6a72f492e6e2f8f89625de7b515 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 10 Apr 2018 06:47:40 +1200
Subject: [PATCH 03/21] auth log: Log the unique session GUID

Log the unique_session_token GUID on successful Authorizations.
This patch adds the "sessionID" attribute to the Authorization object
and increments the version to 1.1

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 auth/auth_log.c | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/auth/auth_log.c b/auth/auth_log.c
index c143ae3..97b6537 100644
--- a/auth/auth_log.c
+++ b/auth/auth_log.c
@@ -43,7 +43,7 @@
 #define AUTH_MAJOR 1
 #define AUTH_MINOR 0
 #define AUTHZ_MAJOR 1
-#define AUTHZ_MINOR 0
+#define AUTHZ_MINOR 1
 
 #include "includes.h"
 #include "../lib/tsocket/tsocket.h"
@@ -56,6 +56,7 @@
 #include "source4/lib/messaging/irpc.h"
 #include "lib/util/server_id_db.h"
 #include "lib/param/param.h"
+#include "librpc/ndr/libndr.h"
 
 /*
  * Get a human readable timestamp.
@@ -431,6 +432,26 @@ static void add_sid(struct json_context *context,
 }
 
 /*
+ * Add a formatted string representation of a GUID to a json object.
+ *
+ */
+static void add_guid(struct json_context *context,
+		     const char *name,
+		     struct GUID *guid)
+{
+
+	char *guid_str;
+	struct GUID_txt_buf guid_buff;
+
+	if (context->error) {
+		return;
+	}
+
+	guid_str = GUID_buf_string(guid, &guid_buff);
+	add_string(context, name, guid_str);
+}
+
+/*
  * Write a machine parsable json formatted authentication log entry.
  *
  * IF removing or changing the format/meaning of a field please update the
@@ -561,6 +582,9 @@ static void log_successful_authz_event_json(
 	add_string(&authorization, "domain", session_info->info->domain_name);
 	add_string(&authorization, "account", session_info->info->account_name);
 	add_sid(&authorization, "sid", &session_info->security_token->sids[0]);
+	add_guid(&authorization,
+		 "sessionId",
+		 &session_info->unique_session_token);
 	add_string(&authorization,
 		   "logonServer",
 		   session_info->info->logon_server);
-- 
2.7.4


From 85bd9a164ab6f6fe84fc62d083d79efd47dd3b8f Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 30 Apr 2018 09:13:58 +1200
Subject: [PATCH 04/21] auth logging tests: Add tests for sessionId

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 python/samba/tests/auth_log.py                    | 5 +++++
 python/samba/tests/auth_log_base.py               | 8 ++++++++
 python/samba/tests/auth_log_ncalrpc.py            | 1 +
 python/samba/tests/auth_log_netlogon.py           | 1 +
 python/samba/tests/auth_log_netlogon_bad_creds.py | 1 +
 python/samba/tests/auth_log_samlogon.py           | 1 +
 6 files changed, 17 insertions(+)

diff --git a/python/samba/tests/auth_log.py b/python/samba/tests/auth_log.py
index 9e68c4f..34312cb 100644
--- a/python/samba/tests/auth_log.py
+++ b/python/samba/tests/auth_log.py
@@ -94,6 +94,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals(authTypes[2], msg["Authorization"]["authType"])
         self.assertEquals("SMB", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
         # Check the third message it should be an Authentication
         # if we are expecting 4 messages
@@ -148,6 +149,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals(authTypes[3], msg["Authorization"]["authType"])
         self.assertEquals("SMB", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
 
     def test_rpc_ncacn_np_ntlm_dns_sign(self):
@@ -277,6 +279,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals(authTypes[1], msg["Authorization"]["authType"])
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
         # Check the second message it should be an Authentication
         msg = messages[1]
@@ -301,6 +304,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals(authTypes[1], msg["Authorization"]["authType"])
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
         # Check the second message it should be an Authentication
         msg = messages[1]
@@ -1313,3 +1317,4 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals("schannel",  msg["Authorization"]["authType"])
         self.assertEquals("SEAL", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
diff --git a/python/samba/tests/auth_log_base.py b/python/samba/tests/auth_log_base.py
index 6c1baea..5bb9821 100644
--- a/python/samba/tests/auth_log_base.py
+++ b/python/samba/tests/auth_log_base.py
@@ -27,6 +27,7 @@ from samba.dcerpc import srvsvc, dnsserver
 import time
 import json
 import os
+import re
 from samba import smb
 from samba.samdb import SamDB
 
@@ -120,3 +121,10 @@ class AuthLogTestBase(samba.tests.TestCase):
             return sd != "NETLOGON"
 
         return list(filter(is_not_netlogon, messages))
+
+    GUID_RE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
+    #
+    # Is the supplied GUID string correctly formatted
+    #
+    def is_guid(self, guid):
+        return re.match(self.GUID_RE, guid)
diff --git a/python/samba/tests/auth_log_ncalrpc.py b/python/samba/tests/auth_log_ncalrpc.py
index be7f6b2..2f61cc5 100644
--- a/python/samba/tests/auth_log_ncalrpc.py
+++ b/python/samba/tests/auth_log_ncalrpc.py
@@ -74,6 +74,7 @@ class AuthLogTestsNcalrpc(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals(authTypes[1], msg["Authorization"]["authType"])
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
         # Check the second message it should be an Authentication
         msg = messages[1]
diff --git a/python/samba/tests/auth_log_netlogon.py b/python/samba/tests/auth_log_netlogon.py
index 228fbe9..9b0512d 100644
--- a/python/samba/tests/auth_log_netlogon.py
+++ b/python/samba/tests/auth_log_netlogon.py
@@ -114,6 +114,7 @@ class AuthLogTestsNetLogon(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals("ncalrpc", msg["Authorization"]["authType"])
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
         # Check the fourth message it should be a NETLOGON Authentication
         msg = messages[3]
diff --git a/python/samba/tests/auth_log_netlogon_bad_creds.py b/python/samba/tests/auth_log_netlogon_bad_creds.py
index 2bae02e..c18d270 100644
--- a/python/samba/tests/auth_log_netlogon_bad_creds.py
+++ b/python/samba/tests/auth_log_netlogon_bad_creds.py
@@ -115,6 +115,7 @@ class AuthLogTestsNetLogonBadCreds(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals("ncalrpc", msg["Authorization"]["authType"])
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
     def test_netlogon_bad_machine_name(self):
         self._test_netlogon("bad_name",
diff --git a/python/samba/tests/auth_log_samlogon.py b/python/samba/tests/auth_log_samlogon.py
index a3a9f50..105a16d 100644
--- a/python/samba/tests/auth_log_samlogon.py
+++ b/python/samba/tests/auth_log_samlogon.py
@@ -171,6 +171,7 @@ class AuthLogTestsSamLogon(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals("ncalrpc", msg["Authorization"]["authType"])
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
 
     def test_ncalrpc_samlogon(self):
-- 
2.7.4


From a4244b6bfc95bc4db3ef2a99eb28618bccadc35a Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 30 Apr 2018 10:35:25 +1200
Subject: [PATCH 05/21] auth logging tests: Clean up flake8 warnings

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 python/samba/tests/auth_log.py                    | 471 +++++++++++-----------
 python/samba/tests/auth_log_base.py               |  27 +-
 python/samba/tests/auth_log_ncalrpc.py            |  27 +-
 python/samba/tests/auth_log_netlogon_bad_creds.py |   1 +
 python/samba/tests/auth_log_pass_change.py        | 148 +++----
 python/samba/tests/auth_log_samlogon.py           |  23 +-
 6 files changed, 326 insertions(+), 371 deletions(-)

diff --git a/python/samba/tests/auth_log.py b/python/samba/tests/auth_log.py
index 34312cb..6cec63a 100644
--- a/python/samba/tests/auth_log.py
+++ b/python/samba/tests/auth_log.py
@@ -18,22 +18,18 @@
 from __future__ import print_function
 """Tests for the Auth and AuthZ logging.
 """
-from samba import auth
 import samba.tests
-from samba.messaging import Messaging
-from samba.dcerpc.messaging import MSG_AUTH_LOG, AUTH_EVENT_NAME
 from samba.dcerpc import srvsvc, dnsserver
-import time
-import json
 import os
 from samba import smb
 from samba.samdb import SamDB
 import samba.tests.auth_log_base
-from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
+from samba.credentials import DONT_USE_KERBEROS, MUST_USE_KERBEROS
 from samba import NTSTATUSError
 from subprocess import call
 from ldb import LdbError
 
+
 class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
     def setUp(self):
@@ -43,8 +39,6 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
     def tearDown(self):
         super(AuthLogTests, self).tearDown()
 
-
-
     def _test_rpc_ncacn_np(self, authTypes, creds, service,
                            binding, protection, checkFunction):
         def isLastExpectedMessage(msg):
@@ -59,8 +53,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         if service == "dnsserver":
             x = dnsserver.dnsserver("ncacn_np:%s%s" % (self.server, binding),
-                                self.get_loadparm(),
-                                creds)
+                                    self.get_loadparm(),
+                                    creds)
         elif service == "srvsvc":
             x = srvsvc.srvsvc("ncacn_np:%s%s" % (self.server, binding),
                               self.get_loadparm(),
@@ -84,8 +78,9 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("SMB",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[1], msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[1],
+                          msg["Authentication"]["authDescription"])
 
         # Check the second message it should be an Authorization
         msg = messages[1]
@@ -106,11 +101,19 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
             self.assertEquals("Authentication", msg["type"])
             self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
             self.assertTrue(
-                checkServiceDescription(msg["Authentication"]["serviceDescription"]))
+                checkServiceDescription(
+                    msg["Authentication"]["serviceDescription"]))
 
-            self.assertEquals(authTypes[3], msg["Authentication"]["authDescription"])
+            self.assertEquals(authTypes[3],
+                              msg["Authentication"]["authDescription"])
 
-    def rpc_ncacn_np_krb5_check(self, messages, authTypes, service, binding, protection):
+    def rpc_ncacn_np_krb5_check(
+            self,
+            messages,
+            authTypes,
+            service,
+            binding,
+            protection):
 
         expected_messages = len(authTypes)
         self.assertEquals(expected_messages,
@@ -124,8 +127,9 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[1], msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[1],
+                          msg["Authentication"]["authDescription"])
 
         # Check the second message it should be an Authentication
         # This this the TCP Authentication in response to the message too big
@@ -134,8 +138,9 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[2], msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[2],
+                          msg["Authentication"]["authDescription"])
 
         # Check the third message it should be an Authorization
         msg = messages[2]
@@ -151,7 +156,6 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("SMB", msg["Authorization"]["transportProtection"])
         self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
-
     def test_rpc_ncacn_np_ntlm_dns_sign(self):
         creds = self.insta_creds(template=self.get_credentials(),
                                  kerberos_state=DONT_USE_KERBEROS)
@@ -197,8 +201,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                                  "ENC-TS Pre-authentication",
                                  "ENC-TS Pre-authentication",
                                  "krb5"],
-                                 creds, "dnsserver", "sign", "SIGN",
-                                 self.rpc_ncacn_np_krb5_check)
+                                creds, "dnsserver", "sign", "SIGN",
+                                self.rpc_ncacn_np_krb5_check)
 
     def test_rpc_ncacn_np_krb_srv_sign(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -207,8 +211,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                                  "ENC-TS Pre-authentication",
                                  "ENC-TS Pre-authentication",
                                  "krb5"],
-                                 creds, "srvsvc", "sign", "SIGN",
-                                 self.rpc_ncacn_np_krb5_check)
+                                creds, "srvsvc", "sign", "SIGN",
+                                self.rpc_ncacn_np_krb5_check)
 
     def test_rpc_ncacn_np_krb_dns(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -234,9 +238,9 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         creds = self.insta_creds(template=self.get_credentials(),
                                  kerberos_state=MUST_USE_KERBEROS)
         self._test_rpc_ncacn_np(["ncacn_np",
-                                "ENC-TS Pre-authentication",
-                                "ENC-TS Pre-authentication",
-                                "krb5"],
+                                 "ENC-TS Pre-authentication",
+                                 "ENC-TS Pre-authentication",
+                                 "krb5"],
                                 creds, "srvsvc", "", "SMB",
                                 self.rpc_ncacn_np_krb5_check)
 
@@ -252,15 +256,15 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
             binding = "[%s]" % binding
 
         if service == "dnsserver":
-            conn = dnsserver.dnsserver("ncacn_ip_tcp:%s%s" % (self.server, binding),
-                                       self.get_loadparm(),
-                                       creds)
+            conn = dnsserver.dnsserver(
+                "ncacn_ip_tcp:%s%s" % (self.server, binding),
+                self.get_loadparm(),
+                creds)
         elif service == "srvsvc":
             conn = srvsvc.srvsvc("ncacn_ip_tcp:%s%s" % (self.server, binding),
                                  self.get_loadparm(),
                                  creds)
 
-
         messages = self.waitForMessages(isLastExpectedMessage, conn)
         checkFunction(messages, authTypes, service, binding, protection)
 
@@ -286,8 +290,9 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("DCE/RPC",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[2], msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[2],
+                          msg["Authentication"]["authDescription"])
 
     def rpc_ncacn_ip_tcp_krb5_check(self, messages, authTypes, service,
                                     binding, protection):
@@ -311,16 +316,18 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[2], msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[2],
+                          msg["Authentication"]["authDescription"])
 
         # Check the third message it should be an Authentication
         msg = messages[2]
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[2], msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[2],
+                          msg["Authentication"]["authDescription"])
 
     def test_rpc_ncacn_ip_tcp_ntlm_dns_sign(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -328,8 +335,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self._test_rpc_ncacn_ip_tcp(["NTLMSSP",
                                      "ncacn_ip_tcp",
                                      "NTLMSSP"],
-                                     creds, "dnsserver", "sign", "SIGN",
-                                     self.rpc_ncacn_ip_tcp_ntlm_check)
+                                    creds, "dnsserver", "sign", "SIGN",
+                                    self.rpc_ncacn_ip_tcp_ntlm_check)
 
     def test_rpc_ncacn_ip_tcp_krb5_dns_sign(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -338,8 +345,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                                      "ncacn_ip_tcp",
                                      "ENC-TS Pre-authentication",
                                      "ENC-TS Pre-authentication"],
-                                     creds, "dnsserver", "sign", "SIGN",
-                                     self.rpc_ncacn_ip_tcp_krb5_check)
+                                    creds, "dnsserver", "sign", "SIGN",
+                                    self.rpc_ncacn_ip_tcp_krb5_check)
 
     def test_rpc_ncacn_ip_tcp_ntlm_dns(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -347,8 +354,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self._test_rpc_ncacn_ip_tcp(["NTLMSSP",
                                      "ncacn_ip_tcp",
                                      "NTLMSSP"],
-                                     creds, "dnsserver", "", "SIGN",
-                                     self.rpc_ncacn_ip_tcp_ntlm_check)
+                                    creds, "dnsserver", "", "SIGN",
+                                    self.rpc_ncacn_ip_tcp_ntlm_check)
 
     def test_rpc_ncacn_ip_tcp_krb5_dns(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -357,8 +364,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                                      "ncacn_ip_tcp",
                                      "ENC-TS Pre-authentication",
                                      "ENC-TS Pre-authentication"],
-                                     creds, "dnsserver", "", "SIGN",
-                                     self.rpc_ncacn_ip_tcp_krb5_check)
+                                    creds, "dnsserver", "", "SIGN",
+                                    self.rpc_ncacn_ip_tcp_krb5_check)
 
     def test_rpc_ncacn_ip_tcp_ntlm_dns_connect(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -366,8 +373,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self._test_rpc_ncacn_ip_tcp(["NTLMSSP",
                                      "ncacn_ip_tcp",
                                      "NTLMSSP"],
-                                     creds, "dnsserver", "connect", "NONE",
-                                     self.rpc_ncacn_ip_tcp_ntlm_check)
+                                    creds, "dnsserver", "connect", "NONE",
+                                    self.rpc_ncacn_ip_tcp_ntlm_check)
 
     def test_rpc_ncacn_ip_tcp_krb5_dns_connect(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -376,8 +383,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                                      "ncacn_ip_tcp",
                                      "ENC-TS Pre-authentication",
                                      "ENC-TS Pre-authentication"],
-                                     creds, "dnsserver", "connect", "NONE",
-                                     self.rpc_ncacn_ip_tcp_krb5_check)
+                                    creds, "dnsserver", "connect", "NONE",
+                                    self.rpc_ncacn_ip_tcp_krb5_check)
 
     def test_rpc_ncacn_ip_tcp_ntlm_dns_seal(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -385,8 +392,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self._test_rpc_ncacn_ip_tcp(["NTLMSSP",
                                      "ncacn_ip_tcp",
                                      "NTLMSSP"],
-                                     creds, "dnsserver", "seal", "SEAL",
-                                     self.rpc_ncacn_ip_tcp_ntlm_check)
+                                    creds, "dnsserver", "seal", "SEAL",
+                                    self.rpc_ncacn_ip_tcp_ntlm_check)
 
     def test_rpc_ncacn_ip_tcp_krb5_dns_seal(self):
         creds = self.insta_creds(template=self.get_credentials(),
@@ -395,8 +402,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                                      "ncacn_ip_tcp",
                                      "ENC-TS Pre-authentication",
                                      "ENC-TS Pre-authentication"],
-                                     creds, "dnsserver", "seal", "SEAL",
-                                     self.rpc_ncacn_ip_tcp_krb5_check)
+                                    creds, "dnsserver", "seal", "SEAL",
+                                    self.rpc_ncacn_ip_tcp_krb5_check)
 
     def test_ldap(self):
 
@@ -407,7 +414,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                     msg["Authorization"]["authType"] == "krb5")
 
         self.samdb = SamDB(url="ldap://%s" % os.environ["SERVER"],
-                           lp = self.get_loadparm(),
+                           lp=self.get_loadparm(),
                            credentials=self.get_credentials())
 
         messages = self.waitForMessages(isLastExpectedMessage)
@@ -420,18 +427,18 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
+                          msg["Authentication"]["serviceDescription"])
         self.assertEquals("ENC-TS Pre-authentication",
-                           msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["authDescription"])
 
-        # Check the first message it should be an Authentication
+        # Check the second message it should be an Authentication
         msg = messages[1]
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
+                          msg["Authentication"]["serviceDescription"])
         self.assertEquals("ENC-TS Pre-authentication",
-                           msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["authDescription"])
 
     def test_ldap_ntlm(self):
 
@@ -442,7 +449,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                     msg["Authorization"]["authType"] == "NTLMSSP")
 
         self.samdb = SamDB(url="ldap://%s" % os.environ["SERVER_IP"],
-                           lp = self.get_loadparm(),
+                           lp=self.get_loadparm(),
                            credentials=self.get_credentials())
 
         messages = self.waitForMessages(isLastExpectedMessage)
@@ -454,7 +461,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("LDAP",
-                           msg["Authentication"]["serviceDescription"])
+                          msg["Authentication"]["serviceDescription"])
         self.assertEquals("NTLMSSP", msg["Authentication"]["authDescription"])
 
     def test_ldap_simple_bind(self):
@@ -466,10 +473,10 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         creds = self.insta_creds(template=self.get_credentials())
         creds.set_bind_dn("%s\\%s" % (creds.get_domain(),
-                                     creds.get_username()))
+                          creds.get_username()))
 
         self.samdb = SamDB(url="ldaps://%s" % os.environ["SERVER"],
-                           lp = self.get_loadparm(),
+                           lp=self.get_loadparm(),
                            credentials=creds)
 
         messages = self.waitForMessages(isLastExpectedMessage)
@@ -482,27 +489,27 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("LDAP",
-                           msg["Authentication"]["serviceDescription"])
+                          msg["Authentication"]["serviceDescription"])
         self.assertEquals("simple bind",
-                           msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["authDescription"])
 
     def test_ldap_simple_bind_bad_password(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
                     msg["Authentication"]["serviceDescription"] == "LDAP" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
                     msg["Authentication"]["authDescription"] == "simple bind")
 
         creds = self.insta_creds(template=self.get_credentials())
         creds.set_password("badPassword")
         creds.set_bind_dn("%s\\%s" % (creds.get_domain(),
-                                     creds.get_username()))
+                          creds.get_username()))
 
         thrown = False
         try:
             self.samdb = SamDB(url="ldaps://%s" % os.environ["SERVER"],
-                               lp = self.get_loadparm(),
+                               lp=self.get_loadparm(),
                                credentials=creds)
         except LdbError:
             thrown = True
@@ -513,13 +520,12 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           len(messages),
                           "Did not receive the expected number of messages")
 
-
     def test_ldap_simple_bind_bad_user(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
                     msg["Authentication"]["serviceDescription"] == "LDAP" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
                     msg["Authentication"]["authDescription"] == "simple bind")
 
         creds = self.insta_creds(template=self.get_credentials())
@@ -528,7 +534,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         thrown = False
         try:
             self.samdb = SamDB(url="ldaps://%s" % os.environ["SERVER"],
-                               lp = self.get_loadparm(),
+                               lp=self.get_loadparm(),
                                credentials=creds)
         except LdbError:
             thrown = True
@@ -539,13 +545,12 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           len(messages),
                           "Did not receive the expected number of messages")
 
-
     def test_ldap_simple_bind_unparseable_user(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
                     msg["Authentication"]["serviceDescription"] == "LDAP" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
                     msg["Authentication"]["authDescription"] == "simple bind")
 
         creds = self.insta_creds(template=self.get_credentials())
@@ -554,7 +559,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         thrown = False
         try:
             self.samdb = SamDB(url="ldaps://%s" % os.environ["SERVER"],
-                               lp = self.get_loadparm(),
+                               lp=self.get_loadparm(),
                                credentials=creds)
         except LdbError:
             thrown = True
@@ -572,23 +577,23 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
     def test_ldap_anonymous_access_bind_only(self):
         # Should be no logging for anonymous bind
         # so receiving any message indicates a failure.
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return True
 
         creds = self.insta_creds(template=self.get_credentials())
         creds.set_anonymous()
 
         self.samdb = SamDB(url="ldaps://%s" % os.environ["SERVER"],
-                           lp = self.get_loadparm(),
+                           lp=self.get_loadparm(),
                            credentials=creds)
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         self.assertEquals(0,
                           len(messages),
                           "Did not receive the expected number of messages")
 
     def test_ldap_anonymous_access(self):
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (msg["type"] == "Authorization" and
                     msg["Authorization"]["serviceDescription"]  == "LDAP" and
                     msg["Authorization"]["transportProtection"] == "TLS" and
@@ -599,19 +604,20 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         creds.set_anonymous()
 
         self.samdb = SamDB(url="ldaps://%s" % os.environ["SERVER"],
-                           lp = self.get_loadparm(),
+                           lp=self.get_loadparm(),
                            credentials=creds)
 
         try:
-            res = self.samdb.search(base=self.samdb.domain_dn())
-            self.fail( "Expected an LdbError exception")
+            self.samdb.search(base=self.samdb.domain_dn())
+            self.fail("Expected an LdbError exception")
         except LdbError:
             pass
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         self.assertEquals(1,
                           len(messages),
                           "Did not receive the expected number of messages")
+
     def test_smb(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authorization" and
@@ -634,28 +640,28 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
+                          msg["Authentication"]["serviceDescription"])
         self.assertEquals("ENC-TS Pre-authentication",
-                           msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["authDescription"])
 
         # Check the second message it should be an Authentication
         msg = messages[1]
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("Kerberos KDC",
-                           msg["Authentication"]["serviceDescription"])
+                          msg["Authentication"]["serviceDescription"])
         self.assertEquals("ENC-TS Pre-authentication",
-                           msg["Authentication"]["authDescription"])
+                          msg["Authentication"]["authDescription"])
 
     def test_smb_bad_password(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "Kerberos KDC" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["authDescription"]
-                        == "ENC-TS Pre-authentication")
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "Kerberos KDC") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "ENC-TS Pre-authentication"))
 
         creds = self.insta_creds(template=self.get_credentials())
         creds.set_password("badPassword")
@@ -675,16 +681,15 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           len(messages),
                           "Did not receive the expected number of messages")
 
-
     def test_smb_bad_user(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "Kerberos KDC" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
-                    msg["Authentication"]["authDescription"]
-                        == "ENC-TS Pre-authentication")
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "Kerberos KDC") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "ENC-TS Pre-authentication"))
 
         creds = self.insta_creds(template=self.get_credentials())
         creds.set_username("badUser")
@@ -829,8 +834,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                     msg["Authentication"]["serviceDescription"] == "SMB" and
                     msg["Authentication"]["authDescription"] == "NTLMSSP" and
                     msg["Authentication"]["passwordType"] == "NTLMv2" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD")
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD"))
 
         creds = self.insta_creds(template=self.get_credentials(),
                                  kerberos_state=DONT_USE_KERBEROS)
@@ -857,8 +862,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                     msg["Authentication"]["serviceDescription"] == "SMB" and
                     msg["Authentication"]["authDescription"] == "NTLMSSP" and
                     msg["Authentication"]["passwordType"] == "NTLMv2" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER")
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER"))
 
         creds = self.insta_creds(template=self.get_credentials(),
                                  kerberos_state=DONT_USE_KERBEROS)
@@ -916,8 +921,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                     msg["Authentication"]["serviceDescription"] == "SMB" and
                     msg["Authentication"]["authDescription"] == "bare-NTLM" and
                     msg["Authentication"]["passwordType"] == "NTLMv1" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD")
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD"))
 
         creds = self.insta_creds(template=self.get_credentials(),
                                  kerberos_state=DONT_USE_KERBEROS)
@@ -935,7 +940,6 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
             thrown = True
         self.assertEquals(thrown, True)
 
-
         messages = self.waitForMessages(isLastExpectedMessage)
         self.assertEquals(1,
                           len(messages),
@@ -947,8 +951,8 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                     msg["Authentication"]["serviceDescription"] == "SMB" and
                     msg["Authentication"]["authDescription"] == "bare-NTLM" and
                     msg["Authentication"]["passwordType"] == "NTLMv1" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER")
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER"))
 
         creds = self.insta_creds(template=self.get_credentials(),
                                  kerberos_state=DONT_USE_KERBEROS)
@@ -966,7 +970,6 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
             thrown = True
         self.assertEquals(thrown, True)
 
-
         messages = self.waitForMessages(isLastExpectedMessage)
         self.assertEquals(1,
                           len(messages),
@@ -976,25 +979,24 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "interactive" and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "interactive") and
                     msg["Authentication"]["status"] == "NT_STATUS_OK" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = os.environ["PASSWORD"]
         samlogon = "samlogon %s %s %s %d" % (user, password, workstation, 1)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1005,26 +1007,25 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "interactive" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "interactive") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = "badPassword"
         samlogon = "samlogon %s %s %s %d" % (user, password, workstation, 1)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1035,26 +1036,25 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "interactive" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "interactive") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = "badUser"
         password = os.environ["PASSWORD"]
         samlogon = "samlogon %s %s %s %d" % (user, password, workstation, 1)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1065,25 +1065,23 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    msg["Authentication"]["authDescription"] == "network" and
                     msg["Authentication"]["status"] == "NT_STATUS_OK" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = os.environ["PASSWORD"]
         samlogon = "samlogon %s %s %s %d" % (user, password, workstation, 2)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1094,26 +1092,24 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    msg["Authentication"]["authDescription"] == "network" and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = "badPassword"
         samlogon = "samlogon %s %s %s %d" % (user, password, workstation, 2)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1124,26 +1120,24 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+        def isLastExpectedMessage(msg):
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] == "network") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = "badUser"
-        password =  os.environ["PASSWORD"]
+        password = os.environ["PASSWORD"]
         samlogon = "samlogon %s %s %s %d" % (user, password, workstation, 2)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1154,26 +1148,25 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"] == "NT_STATUS_OK" and
-                    msg["Authentication"]["passwordType"] == "MSCHAPv2" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+        def isLastExpectedMessage(msg):
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] == "network") and
+                    (msg["Authentication"]["status"] == "NT_STATUS_OK") and
+                    (msg["Authentication"]["passwordType"] == "MSCHAPv2") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = os.environ["PASSWORD"]
-        samlogon = "samlogon %s %s %s %d 0x00010000" % (user, password, workstation, 2)
-
+        samlogon = "samlogon %s %s %s %d 0x00010000" % (
+            user, password, workstation, 2)
 
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1184,27 +1177,26 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["passwordType"] == "MSCHAPv2" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+        def isLastExpectedMessage(msg):
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] == "network") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["passwordType"] == "MSCHAPv2") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = "badPassword"
-        samlogon = "samlogon %s %s %s %d 0x00010000" % (user, password, workstation, 2)
-
+        samlogon = "samlogon %s %s %s %d 0x00010000" % (
+            user, password, workstation, 2)
 
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1215,27 +1207,26 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
-                    msg["Authentication"]["passwordType"] == "MSCHAPv2" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+        def isLastExpectedMessage(msg):
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] == "network") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
+                    (msg["Authentication"]["passwordType"] == "MSCHAPv2") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = "badUser"
         password = os.environ["PASSWORD"]
-        samlogon = "samlogon %s %s %s %d 0x00010000" % (user, password, workstation, 2)
-
+        samlogon = "samlogon %s %s %s %d 0x00010000" % (
+            user, password, workstation, 2)
 
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1246,25 +1237,23 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"] == "NT_STATUS_OK" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+        def isLastExpectedMessage(msg):
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] == "network") and
+                    (msg["Authentication"]["status"] == "NT_STATUS_OK") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = os.environ["PASSWORD"]
         samlogon = "schannel;samlogon %s %s %s" % (user, password, workstation)
 
-
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
@@ -1278,32 +1267,32 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase):
                           msg["Authorization"]["serviceDescription"])
         self.assertEquals("schannel",  msg["Authorization"]["authType"])
         self.assertEquals("SEAL", msg["Authorization"]["transportProtection"])
+        self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
     # Signed logons get promoted to sealed, this test ensures that
-    # this behaviour is not removed accidently
+    # this behaviour is not removed accidentally
     def test_samlogon_schannel_sign(self):
 
         workstation = "AuthLogTests"
 
-        def isLastExpectedMessage( msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SamLogon" and
-                    msg["Authentication"]["authDescription"]
-                        == "network" and
-                    msg["Authentication"]["status"] == "NT_STATUS_OK" and
-                    msg["Authentication"]["workstation"]
-                        == r"\\%s" % workstation)
+        def isLastExpectedMessage(msg):
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SamLogon") and
+                    (msg["Authentication"]["authDescription"] == "network") and
+                    (msg["Authentication"]["status"] == "NT_STATUS_OK") and
+                    (msg["Authentication"]["workstation"] ==
+                        r"\\%s" % workstation))
 
         server   = os.environ["SERVER"]
         user     = os.environ["USERNAME"]
         password = os.environ["PASSWORD"]
-        samlogon = "schannelsign;samlogon %s %s %s" % (user, password, workstation)
-
+        samlogon = "schannelsign;samlogon %s %s %s" % (
+            user, password, workstation)
 
         call(["bin/rpcclient", "-c", samlogon, "-U%", server])
 
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         messages = self.remove_netlogon_messages(messages)
         received = len(messages)
         self.assertIs(True,
diff --git a/python/samba/tests/auth_log_base.py b/python/samba/tests/auth_log_base.py
index 5bb9821..5a70ce3 100644
--- a/python/samba/tests/auth_log_base.py
+++ b/python/samba/tests/auth_log_base.py
@@ -19,34 +19,31 @@ from __future__ import print_function
 """Tests for the Auth and AuthZ logging.
 """
 
-from samba import auth
 import samba.tests
 from samba.messaging import Messaging
 from samba.dcerpc.messaging import MSG_AUTH_LOG, AUTH_EVENT_NAME
-from samba.dcerpc import srvsvc, dnsserver
 import time
 import json
 import os
 import re
-from samba import smb
-from samba.samdb import SamDB
+
 
 class AuthLogTestBase(samba.tests.TestCase):
 
     def setUp(self):
         super(AuthLogTestBase, self).setUp()
         lp_ctx = self.get_loadparm()
-        self.msg_ctx = Messaging((1,), lp_ctx=lp_ctx);
+        self.msg_ctx = Messaging((1,), lp_ctx=lp_ctx)
         self.msg_ctx.irpc_add_name(AUTH_EVENT_NAME)
 
-        def messageHandler( context, msgType, src, message):
+        def messageHandler(context, msgType, src, message):
             # This does not look like sub unit output and it
             # makes these tests much easier to debug.
             print(message)
             jsonMsg = json.loads(message)
-            context["messages"].append( jsonMsg)
+            context["messages"].append(jsonMsg)
 
-        self.context = { "messages": []}
+        self.context = {"messages": []}
         self.msg_handler_and_context = (messageHandler, self.context)
         self.msg_ctx.register(self.msg_handler_and_context,
                               msg_type=MSG_AUTH_LOG)
@@ -62,20 +59,19 @@ class AuthLogTestBase(samba.tests.TestCase):
             self.msg_ctx.deregister(self.msg_handler_and_context,
                                     msg_type=MSG_AUTH_LOG)
 
-
     def waitForMessages(self, isLastExpectedMessage, connection=None):
         """Wait for all the expected messages to arrive
         The connection is passed through to keep the connection alive
         until all the logging messages have been received.
         """
 
-        def completed( messages):
+        def completed(messages):
             for message in messages:
-                if isRemote( message) and isLastExpectedMessage( message):
+                if isRemote(message) and isLastExpectedMessage(message):
                     return True
             return False
 
-        def isRemote( message):
+        def isRemote(message):
             remote = None
             if message["type"] == "Authorization":
                 remote = message["Authorization"]["remoteAddress"]
@@ -93,19 +89,19 @@ class AuthLogTestBase(samba.tests.TestCase):
         self.connection = connection
 
         start_time = time.time()
-        while not completed( self.context["messages"]):
+        while not completed(self.context["messages"]):
             self.msg_ctx.loop_once(0.1)
             if time.time() - start_time > 1:
                 self.connection = None
                 return []
 
         self.connection = None
-        return filter( isRemote, self.context["messages"])
+        return filter(isRemote, self.context["messages"])
 
     # Discard any previously queued messages.
     def discardMessages(self):
         self.msg_ctx.loop_once(0.001)
-        while len( self.context["messages"]):
+        while len(self.context["messages"]):
             self.msg_ctx.loop_once(0.001)
         self.context["messages"] = []
 
@@ -123,6 +119,7 @@ class AuthLogTestBase(samba.tests.TestCase):
         return list(filter(is_not_netlogon, messages))
 
     GUID_RE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
+
     #
     # Is the supplied GUID string correctly formatted
     #
diff --git a/python/samba/tests/auth_log_ncalrpc.py b/python/samba/tests/auth_log_ncalrpc.py
index 2f61cc5..849cee7 100644
--- a/python/samba/tests/auth_log_ncalrpc.py
+++ b/python/samba/tests/auth_log_ncalrpc.py
@@ -18,19 +18,12 @@
 """Tests for the Auth and AuthZ logging.
 """
 
-from samba import auth
 import samba.tests
-from samba.messaging import Messaging
-from samba.dcerpc.messaging import MSG_AUTH_LOG, AUTH_EVENT_NAME
+from samba.credentials import DONT_USE_KERBEROS
 from samba.dcerpc.dcerpc import AS_SYSTEM_MAGIC_PATH_TOKEN
 from samba.dcerpc import samr
-import time
-import json
-import os
-from samba import smb
-from samba.samdb import SamDB
 import samba.tests.auth_log_base
-from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
+
 
 class AuthLogTestsNcalrpc(samba.tests.auth_log_base.AuthLogTestBase):
 
@@ -39,25 +32,23 @@ class AuthLogTestsNcalrpc(samba.tests.auth_log_base.AuthLogTestBase):
         self.remoteAddress = AS_SYSTEM_MAGIC_PATH_TOKEN
 
     def tearDown(self):
-        super(AuthLogTestsNcalrpc , self).tearDown()
-
+        super(AuthLogTestsNcalrpc, self).tearDown()
 
     def _test_rpc_ncaclrpc(self, authTypes, binding, creds,
                            protection, checkFunction):
 
-        def isLastExpectedMessage( msg):
+        def isLastExpectedMessage(msg):
             return (
                 msg["type"] == "Authorization" and
                 msg["Authorization"]["serviceDescription"]  == "DCE/RPC" and
                 msg["Authorization"]["authType"]            == authTypes[0] and
-                msg["Authorization"]["transportProtection"] == protection
-            )
+                msg["Authorization"]["transportProtection"] == protection)
 
         if binding:
             binding = "[%s]" % binding
 
         samr.samr("ncalrpc:%s" % binding, self.get_loadparm(), creds)
-        messages = self.waitForMessages( isLastExpectedMessage)
+        messages = self.waitForMessages(isLastExpectedMessage)
         checkFunction(messages, authTypes, protection)
 
     def rpc_ncacn_np_ntlm_check(self, messages, authTypes, protection):
@@ -81,9 +72,9 @@ class AuthLogTestsNcalrpc(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("Authentication", msg["type"])
         self.assertEquals("NT_STATUS_OK", msg["Authentication"]["status"])
         self.assertEquals("DCE/RPC",
-                           msg["Authentication"]["serviceDescription"])
-        self.assertEquals(authTypes[2], msg["Authentication"]["authDescription"])
-
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals(authTypes[2],
+                          msg["Authentication"]["authDescription"])
 
     def test_ncalrpc_ntlm_dns_sign(self):
 
diff --git a/python/samba/tests/auth_log_netlogon_bad_creds.py b/python/samba/tests/auth_log_netlogon_bad_creds.py
index c18d270..a0a2e28 100644
--- a/python/samba/tests/auth_log_netlogon_bad_creds.py
+++ b/python/samba/tests/auth_log_netlogon_bad_creds.py
@@ -38,6 +38,7 @@ from samba.dsdb import UF_WORKSTATION_TRUST_ACCOUNT, UF_PASSWD_NOTREQD
 from samba.dcerpc.misc import SEC_CHAN_WKSTA
 from samba.dcerpc.netlogon import NETLOGON_NEG_STRONG_KEYS
 
+
 class AuthLogTestsNetLogonBadCreds(samba.tests.auth_log_base.AuthLogTestBase):
 
     def setUp(self):
diff --git a/python/samba/tests/auth_log_pass_change.py b/python/samba/tests/auth_log_pass_change.py
index 9782389..8890694 100644
--- a/python/samba/tests/auth_log_pass_change.py
+++ b/python/samba/tests/auth_log_pass_change.py
@@ -19,23 +19,20 @@ from __future__ import print_function
 """Tests for the Auth and AuthZ logging of password changes.
 """
 
-from samba import auth
 import samba.tests
-from samba.messaging import Messaging
 from samba.samdb import SamDB
 from samba.auth import system_session
-import json
 import os
 import samba.tests.auth_log_base
 from samba.tests import delete_force
 from samba.net import Net
-from samba import ntstatus
 import samba
 from subprocess import call
 from ldb import LdbError
 
 USER_NAME = "authlogtestuser"
-USER_PASS = samba.generate_random_password(32,32)
+USER_PASS = samba.generate_random_password(32, 32)
+
 
 class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
 
@@ -56,9 +53,6 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
         base_dn = self.ldb.domain_dn()
         print("base_dn %s" % base_dn)
 
-        # Gets back the configuration basedn
-        configuration_dn = self.ldb.get_config_basedn().get_linearized()
-
         # Get the old "dSHeuristics" if it was set
         dsheuristics = self.ldb.get_dsheuristics()
 
@@ -82,10 +76,10 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
         # (Re)adds the test user USER_NAME with password USER_PASS
         delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn)
         self.ldb.add({
-             "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
-             "objectclass": "user",
-             "sAMAccountName": USER_NAME,
-             "userPassword": USER_PASS
+            "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
+            "objectclass": "user",
+            "sAMAccountName": USER_NAME,
+            "userPassword": USER_PASS
         })
 
         # discard any auth log messages for the password setup
@@ -94,18 +88,16 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
     def tearDown(self):
         super(AuthLogPassChangeTests, self).tearDown()
 
-
     def test_admin_change_password(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_OK" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SAMR Password Change" and
-                    msg["Authentication"]["authDescription"]
-                        == "samr_ChangePasswordUser3")
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["status"] == "NT_STATUS_OK") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SAMR Password Change") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "samr_ChangePasswordUser3"))
 
-        creds = self.insta_creds(template = self.get_credentials())
+        creds = self.insta_creds(template=self.get_credentials())
 
         lp = self.get_loadparm()
         net = Net(creds, lp, server=self.server_ip)
@@ -115,7 +107,6 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
                             username=USER_NAME,
                             oldpassword=USER_PASS)
 
-
         messages = self.waitForMessages(isLastExpectedMessage)
         print("Received %d messages" % len(messages))
         self.assertEquals(8,
@@ -124,13 +115,13 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
 
     def test_admin_change_password_new_password_fails_restriction(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_PASSWORD_RESTRICTION" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SAMR Password Change" and
-                    msg["Authentication"]["authDescription"]
-                        == "samr_ChangePasswordUser3")
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_PASSWORD_RESTRICTION") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SAMR Password Change") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "samr_ChangePasswordUser3"))
 
         creds = self.insta_creds(template=self.get_credentials())
 
@@ -143,7 +134,7 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
             net.change_password(newpassword=password.encode('utf-8'),
                                 oldpassword=USER_PASS,
                                 username=USER_NAME)
-        except Exception as msg:
+        except Exception:
             exception_thrown = True
         self.assertEquals(True, exception_thrown,
                           "Expected exception not thrown")
@@ -155,13 +146,13 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
 
     def test_admin_change_password_unknown_user(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_NO_SUCH_USER" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SAMR Password Change" and
-                    msg["Authentication"]["authDescription"]
-                        == "samr_ChangePasswordUser3")
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_NO_SUCH_USER") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SAMR Password Change") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "samr_ChangePasswordUser3"))
 
         creds = self.insta_creds(template=self.get_credentials())
 
@@ -174,7 +165,7 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
             net.change_password(newpassword=password.encode('utf-8'),
                                 oldpassword=USER_PASS,
                                 username="badUser")
-        except Exception as msg:
+        except Exception:
             exception_thrown = True
         self.assertEquals(True, exception_thrown,
                           "Expected exception not thrown")
@@ -186,13 +177,13 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
 
     def test_admin_change_password_bad_original_password(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SAMR Password Change" and
-                    msg["Authentication"]["authDescription"]
-                        == "samr_ChangePasswordUser3")
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SAMR Password Change") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "samr_ChangePasswordUser3"))
 
         creds = self.insta_creds(template=self.get_credentials())
 
@@ -205,7 +196,7 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
             net.change_password(newpassword=password.encode('utf-8'),
                                 oldpassword="badPassword",
                                 username=USER_NAME)
-        except Exception as msg:
+        except Exception:
             exception_thrown = True
         self.assertEquals(True, exception_thrown,
                           "Expected exception not thrown")
@@ -221,19 +212,19 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
     # correctly, so we just check it triggers the wrong password path.
     def test_rap_change_password(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "SAMR Password Change" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["authDescription"]
-                        == "OemChangePasswordUser2")
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "SAMR Password Change") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "OemChangePasswordUser2"))
 
         username = os.environ["USERNAME"]
         server = os.environ["SERVER"]
         password = os.environ["PASSWORD"]
         server_param = "--server=%s" % server
-        creds = "-U%s%%%s" % (username,password)
+        creds = "-U%s%%%s" % (username, password)
         call(["bin/net", "rap", server_param,
               "password", USER_NAME, "notMyPassword", "notGoingToBeMyPassword",
               server, creds, "--option=client ipc max protocol=nt1"])
@@ -245,23 +236,21 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
 
     def test_ldap_change_password(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_OK" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "LDAP Password Change" and
-                    msg["Authentication"]["authDescription"]
-                        == "LDAP Modify")
-
-        new_password = samba.generate_random_password(32,32)
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["status"] == "NT_STATUS_OK") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "LDAP Password Change") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "LDAP Modify"))
+
+        new_password = samba.generate_random_password(32, 32)
         self.ldb.modify_ldif(
             "dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
             "changetype: modify\n" +
             "delete: userPassword\n" +
             "userPassword: " + USER_PASS + "\n" +
             "add: userPassword\n" +
-            "userPassword: " + new_password + "\n"
-        )
+            "userPassword: " + new_password + "\n")
 
         messages = self.waitForMessages(isLastExpectedMessage)
         print("Received %d messages" % len(messages))
@@ -276,11 +265,10 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
     def test_ldap_change_password_bad_user(self):
         def isLastExpectedMessage(msg):
             return (msg["type"] == "Authorization" and
-                    msg["Authorization"]["serviceDescription"]
-                        == "LDAP" and
+                    msg["Authorization"]["serviceDescription"] == "LDAP" and
                     msg["Authorization"]["authType"] == "krb5")
 
-        new_password = samba.generate_random_password(32,32)
+        new_password = samba.generate_random_password(32, 32)
         try:
             self.ldb.modify_ldif(
                 "dn: cn=" + "badUser" + ",cn=users," + self.base_dn + "\n" +
@@ -288,8 +276,7 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
                 "delete: userPassword\n" +
                 "userPassword: " + USER_PASS + "\n" +
                 "add: userPassword\n" +
-                "userPassword: " + new_password + "\n"
-            )
+                "userPassword: " + new_password + "\n")
             self.fail()
         except LdbError as e:
             (num, msg) = e.args
@@ -303,15 +290,15 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
 
     def test_ldap_change_password_bad_original_password(self):
         def isLastExpectedMessage(msg):
-            return (msg["type"] == "Authentication" and
-                    msg["Authentication"]["status"]
-                        == "NT_STATUS_WRONG_PASSWORD" and
-                    msg["Authentication"]["serviceDescription"]
-                        == "LDAP Password Change" and
-                    msg["Authentication"]["authDescription"]
-                        == "LDAP Modify")
-
-        new_password = samba.generate_random_password(32,32)
+            return ((msg["type"] == "Authentication") and
+                    (msg["Authentication"]["status"] ==
+                        "NT_STATUS_WRONG_PASSWORD") and
+                    (msg["Authentication"]["serviceDescription"] ==
+                        "LDAP Password Change") and
+                    (msg["Authentication"]["authDescription"] ==
+                        "LDAP Modify"))
+
+        new_password = samba.generate_random_password(32, 32)
         try:
             self.ldb.modify_ldif(
                 "dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
@@ -319,8 +306,7 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
                 "delete: userPassword\n" +
                 "userPassword: " + "badPassword" + "\n" +
                 "add: userPassword\n" +
-                "userPassword: " + new_password + "\n"
-            )
+                "userPassword: " + new_password + "\n")
             self.fail()
         except LdbError as e1:
             (num, msg) = e1.args
diff --git a/python/samba/tests/auth_log_samlogon.py b/python/samba/tests/auth_log_samlogon.py
index 105a16d..d3b14f3 100644
--- a/python/samba/tests/auth_log_samlogon.py
+++ b/python/samba/tests/auth_log_samlogon.py
@@ -19,14 +19,8 @@
     Tests auth logging tests that exercise SamLogon
 """
 
-from samba import auth
 import samba.tests
-from samba.messaging import Messaging
-from samba.dcerpc.messaging import MSG_AUTH_LOG, AUTH_EVENT_NAME
-import time
-import json
 import os
-from samba import smb
 from samba.samdb import SamDB
 import samba.tests.auth_log_base
 from samba.credentials import (
@@ -42,6 +36,7 @@ from samba.tests import delete_force
 from samba.dsdb import UF_WORKSTATION_TRUST_ACCOUNT, UF_PASSWD_NOTREQD
 from samba.dcerpc.misc import SEC_CHAN_WKSTA
 
+
 class AuthLogTestsSamLogon(samba.tests.auth_log_base.AuthLogTestBase):
 
     def setUp(self):
@@ -63,9 +58,8 @@ class AuthLogTestsSamLogon(samba.tests.auth_log_base.AuthLogTestBase):
         self.samlogon_dn   = ("cn=%s,cn=users,%s" %
                               (self.netbios_name, self.base_dn))
 
-
     def tearDown(self):
-        super(AuthLogTestsSamLogon , self).tearDown()
+        super(AuthLogTestsSamLogon, self).tearDown()
         delete_force(self.ldb, self.samlogon_dn)
 
     def _test_samlogon(self, binding, creds, checkFunction):
@@ -119,7 +113,6 @@ class AuthLogTestsSamLogon(samba.tests.auth_log_base.AuthLogTestBase):
         eol.AvId = ntlmssp.MsvAvEOL
         target_info.pair = [domainname, computername, eol]
 
-
         target_info_blob = ndr_pack(target_info)
 
         response = creds.get_ntlm_response(flags=CLI_CRED_NTLMv2_AUTH,
@@ -144,15 +137,14 @@ class AuthLogTestsSamLogon(samba.tests.auth_log_base.AuthLogTestBase):
 
         validation_level = samba.dcerpc.netlogon.NetlogonValidationSamInfo4
 
-
-        result = netlogon_conn.netr_LogonSamLogonEx(os.environ["SERVER"],
-                                               machine_creds.get_workstation(),
-                                               logon_level, logon,
-                                               validation_level, netr_flags)
+        result = netlogon_conn.netr_LogonSamLogonEx(
+            os.environ["SERVER"],
+            machine_creds.get_workstation(),
+            logon_level, logon,
+            validation_level, netr_flags)
 
         (validation, authoritative, netr_flags_out) = result
 
-
         messages = self.waitForMessages(isLastExpectedMessage, netlogon_conn)
         checkFunction(messages)
 
@@ -173,7 +165,6 @@ class AuthLogTestsSamLogon(samba.tests.auth_log_base.AuthLogTestBase):
         self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
         self.assertTrue(self.is_guid(msg["Authorization"]["sessionId"]))
 
-
     def test_ncalrpc_samlogon(self):
 
         creds = self.insta_creds(template=self.get_credentials(),
-- 
2.7.4


From 60faf08cb796c77c9ba0413b5d71c372803baea5 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 10 Apr 2018 06:45:47 +1200
Subject: [PATCH 06/21] auth logging: Extract common audit logging code

Extract the common audit logging code into a library to allow it's
re-use in other logging modules.

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 lib/audit_logging/audit_logging.c            | 816 +++++++++++++++++++++++++++
 lib/audit_logging/audit_logging.h            |  98 ++++
 lib/audit_logging/tests/audit_logging_test.c | 557 ++++++++++++++++++
 lib/audit_logging/wscript_build              |  23 +
 source4/selftest/tests.py                    |   2 +
 wscript_build                                |   1 +
 6 files changed, 1497 insertions(+)
 create mode 100644 lib/audit_logging/audit_logging.c
 create mode 100644 lib/audit_logging/audit_logging.h
 create mode 100644 lib/audit_logging/tests/audit_logging_test.c
 create mode 100644 lib/audit_logging/wscript_build

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
new file mode 100644
index 0000000..a2a8471
--- /dev/null
+++ b/lib/audit_logging/audit_logging.c
@@ -0,0 +1,816 @@
+/*
+   common routines for audit logging
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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/>.
+*/
+
+/*
+ * Error handling:
+ *
+ * The json_object structure contains a boolean 'error'.  This is set whenever
+ * an error is detected. All the library functions check this flag and return
+ * immediately if it is set.
+ *
+ *	if (object->error) {
+ *		return;
+ *	}
+ *
+ * This allows the operations to be sequenced naturally with out the clutter
+ * of error status checks.
+ *
+ *	audit = json_new_object();
+ *	json_add_version(&audit, OPERATION_MAJOR, OPERATION_MINOR);
+ *	json_add_int(&audit, "statusCode", ret);
+ *	json_add_string(&audit, "status", ldb_strerror(ret));
+ *	json_add_string(&audit, "operation", operation);
+ *	json_add_address(&audit, "remoteAddress", remote);
+ *	json_add_sid(&audit, "userSid", sid);
+ *	json_add_string(&audit, "dn", dn);
+ *	json_add_guid(&audit, "transactionId", &ac->transaction_guid);
+ *	json_add_guid(&audit, "sessionId", unique_session_token);
+ *
+ * The assumptions are that errors will be rare, and that the audit logging
+ * code should not cause failures. So errors are logged but processing
+ * continues on a best effort basis.
+ */
+
+#include "includes.h"
+
+#include "librpc/ndr/libndr.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/messaging/messaging.h"
+#include "auth/common_auth.h"
+#include "audit_logging.h"
+
+/*
+ * @brief Get a human readable timestamp.
+ *
+ * Returns the current time formatted as
+ *  "Tue, 14 Mar 2017 08:38:42.209028 NZDT"
+ *
+ * The returned string is allocated by talloc in the supplied context.
+ * It is the callers responsibility to free it.
+ *
+ * @param mem_ctx talloc memory context that owns the returned string.
+ *
+ * @return a human readable time stamp.
+ *
+ */
+char* audit_get_timestamp(TALLOC_CTX *frame)
+{
+	char buffer[40];	/* formatted time less usec and timezone */
+	char tz[10];		/* formatted time zone			 */
+	struct tm* tm_info;	/* current local time			 */
+	struct timeval tv;	/* current system time			 */
+	int r;			/* response code from gettimeofday	 */
+	char * ts;		/* formatted time stamp			 */
+
+	r = gettimeofday(&tv, NULL);
+	if (r) {
+		DBG_ERR("Unable to get time of day: (%d) %s\n",
+			errno,
+			strerror(errno));
+		return NULL;
+	}
+
+	tm_info = localtime(&tv.tv_sec);
+	if (tm_info == NULL) {
+		DBG_ERR("Unable to determine local time\n");
+		return NULL;
+	}
+
+	strftime(buffer, sizeof(buffer)-1, "%a, %d %b %Y %H:%M:%S", tm_info);
+	strftime(tz, sizeof(tz)-1, "%Z", tm_info);
+	ts = talloc_asprintf(frame, "%s.%06ld %s", buffer, tv.tv_usec, tz);
+	if (ts == NULL) {
+		DBG_ERR("Out of memory formatting time stamp\n");
+	}
+	return ts;
+}
+
+/*
+ * @brief write an audit message to the audit logs.
+ *
+ * Write the audit message to the samba logs.
+ *
+ * @param prefix Text to be printed at the start of the log line
+ * @param message The content of the log line.
+ * @param debub_class The debug class to log the message with.
+ * @param debug_level The debug level to log the message with.
+ */
+void audit_log_hr(
+	const char* prefix,
+	const char* message,
+	int debug_class,
+	int debug_level)
+{
+	DEBUGC(debug_class, debug_level, ("%s %s\n", prefix, message));
+}
+
+#ifdef HAVE_JANSSON
+/*
+ * @brief write a json object to the samba audit logs.
+ *
+ * Write the json object to the audit logs as a formatted stringjson object to the audit logs as a formatted string
+ *
+ * @param prefix Text to be printed at the start of the log line
+ * @param message The content of the log line.
+ * @param debub_class The debug class to log the message with.
+ * @param debug_level The debug level to log the message with.
+ */
+void audit_log_json(
+	const char* prefix,
+	struct json_object* message,
+	int debug_class,
+	int debug_level)
+{
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	char *s = json_to_string(ctx, message);
+	DEBUGC(debug_class, debug_level, ("JSON %s: %s\n", prefix, s));
+	TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief get a connection to the messaging event server.
+ *
+ * Get a connection to the messaging event server registered by server_name.
+ *
+ * @param msg_ctx a valid imessaging_context.
+ * @param server_name name of messaging event server to connect to.
+ * @param server_id The event server details to populate
+ *
+ * @return NTSTATUS
+ */
+static NTSTATUS get_event_server(
+	struct imessaging_context *msg_ctx,
+	const char *server_name,
+	struct server_id *event_server)
+{
+	NTSTATUS status;
+	TALLOC_CTX *frame = talloc_stackframe();
+	unsigned num_servers, i;
+	struct server_id *servers;
+
+	status = irpc_servers_byname(
+		msg_ctx,
+		frame,
+		server_name,
+		&num_servers,
+		&servers);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_NOTICE(
+			"Failed to find '%s' registered on the message bus to "
+			"send audit events to: %s\n",
+			server_name,
+			nt_errstr(status));
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * Select the first server that is listening, because we get
+	 * connection refused as NT_STATUS_OBJECT_NAME_NOT_FOUND
+	 * without waiting
+	 */
+	for (i = 0; i < num_servers; i++) {
+		status = imessaging_send(
+			msg_ctx,
+			servers[i],
+			MSG_PING,
+			&data_blob_null);
+		if (NT_STATUS_IS_OK(status)) {
+			*event_server = servers[i];
+			TALLOC_FREE(frame);
+			return NT_STATUS_OK;
+		}
+	}
+	DBG_NOTICE(
+		"Failed to find '%s' registered on the message bus to "
+		"send audit events to: %s\n",
+		server_name,
+		nt_errstr(status));
+	TALLOC_FREE(frame);
+	return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+/*
+ * @brief send an audit message to a messaging event server.
+ *
+ * Send the message to a registered and listening event server.
+ * Note: Any errors are logged, and the message is not sent.  This is to ensure
+ *       that a poorly behaved event server does not impact Samba.
+ *
+ *       As it is possible to lose messages, especially during server
+ *       shut down, currently this function is primarily intended for use
+ *       in integration tests.
+ *
+ * @param msg_ctx an imessaging_context, can be NULL in which case no message
+ *                will be sent.
+ * @param server_name the naname of the event server to send the message to.
+ * @param messag_type A message type defined in librpc/idl/messaging.idl
+ * @param message The message to send.
+ *
+ */
+void audit_message_send(
+	struct imessaging_context *msg_ctx,
+	const char *server_name,
+	uint32_t message_type,
+	struct json_object *message)
+{
+	struct server_id event_server;
+	NTSTATUS status;
+
+	const char *message_string = NULL;
+	DATA_BLOB message_blob = data_blob_null;
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	if (msg_ctx == NULL) {
+		DBG_DEBUG("No messaging context\n");
+		return;
+	}
+
+	/* Need to refetch the address each time as the destination server may
+	 * have disconnected and reconnected in the interim, in which case
+	 * messages may get lost
+	 */
+	status = get_event_server(msg_ctx, server_name, &event_server);
+	if (!NT_STATUS_IS_OK(status) &&
+	    !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+		DBG_ERR("get_event_server for %s returned (%s)\n",
+			server_name,
+			nt_errstr(status));
+		return;
+	}
+
+	message_string = json_to_string(ctx, message);
+	message_blob = data_blob_string_const(message_string);
+	status = imessaging_send(
+		msg_ctx,
+		event_server,
+		message_type,
+		&message_blob);
+
+	/*
+	 * If the server crashed, try to find it again
+	 */
+	if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+		status = get_event_server(msg_ctx, server_name, &event_server);
+		if (!NT_STATUS_IS_OK(status) &&
+		    !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+			DBG_ERR("get_event_server for %s returned (%s)\n",
+				server_name,
+				nt_errstr(status));
+			TALLOC_FREE(ctx);
+			return;
+		}
+		imessaging_send(
+			msg_ctx,
+			event_server,
+			message_type,
+			&message_blob);
+	}
+	TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief Create a new struct json_object, wrapping a JSON Object.
+ *
+ * Create a new json object, the json_object wraps the underlying json
+ * implementations JSON Object representation.
+ *
+ * Free with a call to json_free_object, note that the jansson inplementation
+ * allocates memory with malloc and not talloc.
+ *
+ * @return a struct json_object, error will be set to true if the object
+ *         could not be created.
+ *
+ */
+struct json_object json_new_object(void) {
+
+	struct json_object object;
+	object.error = false;
+
+	object.root = json_object();
+	if (object.root == NULL) {
+		object.error = true;
+		DBG_ERR("Unable to create json_object\n");
+	}
+	return object;
+}
+
+/*
+ * @brief Create a new struct json_object wrapping a JSON Array.
+ *
+ * Create a new json object, the json_object wraps the underlying json
+ * implementations JSON Array representation.
+ *
+ * Free with a call to json_free_object, note that the jansson inplementation
+ * allocates memory with malloc and not talloc.
+ *
+ * @return a struct json_object, error will be set to true if the array
+ *         could not be created.
+ *
+ */
+struct json_object json_new_array(void) {
+
+	struct json_object array;
+	array.error = false;
+
+	array.root = json_array();
+	if (array.root == NULL) {
+		array.error = true;
+		DBG_ERR("Unable to create json_array\n");
+	}
+	return array;
+}
+
+
+/*
+ * @brief free and invalidate a previously created JSON object.
+ *
+ * Release any resources owned by a json_object, and then mark the structure
+ * as invalid.  It is safe to call this multiple times on an object.
+ *
+ */
+void json_free(struct json_object *object)
+{
+	if (object->root != NULL) {
+		json_decref(object->root);
+	}
+	object->root = NULL;
+	object->error = true;
+}
+
+/*
+ * @brief is the current JSON object invalid?
+ *
+ * Check the state of the object to determine if it is invalid.
+ *
+ * @return is the object valid?
+ *
+ */
+bool json_is_invalid(struct json_object *object)
+{
+	return object->error;
+}
+
+/*
+ * @brief Add an integer value to a JSON object.
+ *
+ * Add an integer value named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value the value.
+ *
+ */
+void json_add_int(struct json_object *object,
+		  const char* name,
+		  const int value)
+{
+	int rc = 0;
+
+	if (object->error) {
+		return;
+	}
+
+	rc = json_object_set_new(object->root, name, json_integer(value));
+	if (rc) {
+		DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
+		object->error = true;
+	}
+}
+
+/*
+ * @brief Add a boolean value to a JSON object.
+ *
+ * Add a boolean value named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ */
+void json_add_bool(struct json_object *object,
+		   const char* name,
+		   const bool value)
+{
+	int rc = 0;
+
+	if (object->error) {
+		return;
+	}
+
+	rc = json_object_set_new(object->root, name, json_boolean(value));
+	if (rc) {
+		DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
+		object->error = true;
+	}
+
+}
+
+/*
+ * @brief Add a string value to a JSON object.
+ *
+ * Add a string value named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ */
+void json_add_string(struct json_object *object,
+		     const char* name,
+		     const char* value)
+{
+	int rc = 0;
+
+	if (object->error) {
+		return;
+	}
+
+	if (value) {
+		rc = json_object_set_new(
+			object->root,
+			name,
+			json_string(value));
+	} else {
+		rc = json_object_set_new(object->root, name, json_null());
+	}
+	if (rc) {
+		DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
+		object->error = true;
+	}
+}
+
+/*
+ * @brief Assert that the current JSON object is an array.
+ *
+ * Check that the current object is a JSON array, and if not
+ * invalidate the object. We also log an error message as this indicates
+ * bug in the calling code.
+ *
+ * @param object the JSON object to be validated.
+ */
+void json_assert_is_array(struct json_object *array) {
+
+	if (array->error) {
+		return;
+	}
+
+	if (json_is_array(array->root) == false) {
+		DBG_ERR("JSON object is not an array\n");
+		array->error = true;
+		return;
+	}
+}
+
+/*
+ * @brief Add a JSON object to a JSON object.
+ *
+ * Add a JSON object named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ */
+void json_add_object(struct json_object *object,
+		const char* name,
+		struct json_object *value)
+{
+	int rc = 0;
+	json_t *jv = NULL;
+
+	if (object->error) {
+		return;
+	}
+
+	if (value != NULL && value->error) {
+		object->error = true;
+		return;
+	}
+
+	jv = value == NULL ? json_null() : value->root;
+
+	if (json_is_array(object->root)) {
+		rc = json_array_append_new(object->root, jv);
+	} else if (json_is_object(object->root)) {
+		rc = json_object_set_new(object->root, name,  jv);
+	} else {
+		DBG_ERR("Invalid JSON object type\n");
+		object->error = true;
+	}
+	if (rc) {
+		DBG_ERR("Unable to add object [%s]\n", name);
+		object->error = true;
+	}
+}
+
+/*
+ * @brief Add a string to a JSON object, truncating if necessary.
+ *
+ *
+ * Add a string value named 'name' to the json object, the string will be
+ * truncated if it is more than len characters long. If len is 0 the value
+ * is encoded as a JSON null.
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ * @param len the maximum number of characters to be copied.
+ *
+ */
+void json_add_stringn(
+	struct json_object *object,
+	const char *name,
+	const char *value,
+	const size_t len)
+{
+
+	int rc = 0;
+	if (object->error) {
+		return;
+	}
+
+	if (value != NULL && len > 0) {
+		char buffer[len+1];
+		strncpy(buffer, value, len);
+		buffer[len] = '\0';
+		rc = json_object_set_new(object->root,
+					 name,
+					 json_string(buffer));
+	} else {
+		rc = json_object_set_new(object->root, name, json_null());
+	}
+	if (rc) {
+		DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
+		object->error = true;
+	}
+}
+
+/*
+ * @brief Add a version object to a JSON object
+ *
+ * Add a version object to the JSON object
+ * 	"version":{"major":1, "minor":0}
+ *
+ * The version tag is intended to aid the processing of the JSON messages
+ * The major version number should change when an attribute is:
+ *  - renamed
+ *  - removed
+ *  - its meaning changes
+ *  - its contents change format
+ * The minor version should change whenever a new attribute is added and for
+ * minor bug fixes to an attributes content.
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param major the major version number
+ * @param minor the minor version number
+ */
+void json_add_version(struct json_object *object, int major, int minor)
+{
+	struct json_object version = json_new_object();
+	json_add_int(&version, "major", major);
+	json_add_int(&version, "minor", minor);
+	json_add_object(object, "version", &version);
+}
+
+/*
+ * @brief add an ISO 8601 timestamp to the object.
+ *
+ * Add the current date and time as a timestamp in ISO 8601 format
+ * to a JSON object
+ *
+ * "timestamp":"2017-03-06T17:18:04.455081+1300"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ */
+void json_add_timestamp(struct json_object *object)
+{
+	char buffer[40];	/* formatted time less usec and timezone */
+	char timestamp[50];	/* the formatted ISO 8601 time stamp	 */
+	char tz[10];		/* formatted time zone			 */
+	struct tm* tm_info;	/* current local time			 */
+	struct timeval tv;	/* current system time			 */
+	int r;			/* response code from gettimeofday	 */
+
+	if (object->error) {
+		return;
+	}
+
+	r = gettimeofday(&tv, NULL);
+	if (r) {
+		DBG_ERR("Unable to get time of day: (%d) %s\n",
+			errno,
+			strerror(errno));
+		object->error = true;
+		return;
+	}
+
+	tm_info = localtime(&tv.tv_sec);
+	if (tm_info == NULL) {
+		DBG_ERR("Unable to determine local time\n");
+		object->error = true;
+		return;
+	}
+
+	strftime(buffer, sizeof(buffer)-1, "%Y-%m-%dT%T", tm_info);
+	strftime(tz, sizeof(tz)-1, "%z", tm_info);
+	snprintf(
+		timestamp,
+		sizeof(timestamp),
+		"%s.%06ld%s",
+		buffer,
+		tv.tv_usec,
+		tz);
+	json_add_string(object, "timestamp", timestamp);
+}
+
+
+/*
+ *@brief Add a tsocket_address to a JSON object
+ *
+ * Add the string representation of a Samba tsocket_address to the object.
+ *
+ * "localAddress":"ipv6::::0"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param address the tsocket_address.
+ *
+ */
+void json_add_address(
+	struct json_object *object,
+	const char *name,
+	const struct tsocket_address *address)
+{
+
+	if (object->error) {
+		return;
+	}
+	if (address == NULL) {
+		int rc = json_object_set_new(object->root, name, json_null());
+		if (rc) {
+			DBG_ERR("Unable to set address [%s] to null\n", name);
+			object->error = true;
+		}
+	} else {
+		TALLOC_CTX *ctx = talloc_new(NULL);
+		char *s = NULL;
+
+		s = tsocket_address_string(address, ctx);
+		json_add_string(object, name, s);
+		TALLOC_FREE(ctx);
+	}
+}
+
+/*
+ * @brief Add a formatted string representation of a sid to a json object.
+ *
+ * Add the string representation of a Samba sid to the object.
+ *
+ * "sid":"S-1-5-18"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param sid the sid
+ *
+ */
+void json_add_sid(
+	struct json_object *object,
+	const char *name,
+	const struct dom_sid *sid)
+{
+
+	if (object->error) {
+		return;
+	}
+	if (sid == NULL) {
+		int rc = json_object_set_new(object->root, name, json_null());
+		if (rc) {
+			DBG_ERR("Unable to set SID [%s] to null\n", name);
+			object->error = true;
+		}
+	} else {
+		char sid_buf[DOM_SID_STR_BUFLEN];
+
+		dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
+		json_add_string(object, name, sid_buf);
+	}
+}
+
+/*
+ * @brief Add a formatted string representation of a guid to a json object.
+ *
+ * Add the string representation of a Samba GUID to the object.
+ *
+ * "guid":"1fb9f2ee-2a4d-4bf8-af8b-cb9d4529a9ab"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param guid the guid.
+ *
+ *
+ */
+void json_add_guid(
+	struct json_object *object,
+	const char *name,
+	const struct GUID *guid)
+{
+
+
+	if (object->error) {
+		return;
+	}
+	if (guid == NULL) {
+		int rc = json_object_set_new(object->root, name, json_null());
+		if (rc) {
+			DBG_ERR("Unable to set GUID [%s] to null\n", name);
+			object->error = true;
+		}
+	} else {
+		char *guid_str;
+		struct GUID_txt_buf guid_buff;
+
+		guid_str = GUID_buf_string(guid, &guid_buff);
+		json_add_string(object, name, guid_str);
+	}
+}
+
+
+/*
+ * @brief Convert a JSON object into a string
+ *
+ * Convert the jsom object into a string suitable for printing on a log line,
+ * i.e. with no embedded line breaks.
+ *
+ * If the object is invalid it returns NULL.
+ *
+ * @param mem_ctx the talloc memory context owning the returned string
+ * @param object the json object.
+ *
+ * @return A string representation of the object or NULL if the object
+ *         is invalid.
+ */
+char *json_to_string(TALLOC_CTX *mem_ctx, struct json_object *object)
+{
+	char *json = NULL;
+	char *json_string = NULL;
+
+	if (object->error) {
+		return NULL;
+	}
+
+	/*
+	 * json_dumps uses malloc, so need to call free(json) to release
+	 * the memory
+	 */
+	json = json_dumps(object->root, 0);
+	if (json == NULL) {
+		DBG_ERR("Unable to convert JSON object to string\n");
+		return NULL;
+	}
+
+	json_string = talloc_strdup(mem_ctx, json);
+	if (json_string == NULL) {
+		free(json);
+		DBG_ERR("Unable to copy JSON object string to talloc string\n");
+		return NULL;
+	}
+	free(json);
+
+	return json_string;
+}
+#endif
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
new file mode 100644
index 0000000..596ad6d
--- /dev/null
+++ b/lib/audit_logging/audit_logging.h
@@ -0,0 +1,98 @@
+/*
+   common routines for audit logging
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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 <talloc.h>
+#include "lib/messaging/irpc.h"
+#include "lib/tsocket/tsocket.h"
+
+char *audit_get_timestamp(
+	TALLOC_CTX *frame);
+void audit_log_hr(
+	const char *prefix,
+	const char *message,
+	int debug_class,
+	int debug_level);
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+/*
+ * Wrapper for jannson JSON object
+ *
+ */
+struct json_object {
+	json_t *root;
+	bool error;
+};
+
+void audit_log_json(
+	const char *prefix,
+	struct json_object *message,
+	int debug_class,
+	int debug_level);
+void audit_message_send(
+	struct imessaging_context *msg_ctx,
+	const char *server_name,
+	uint32_t message_type,
+	struct json_object *message);
+struct json_object json_new_object(void);
+struct json_object json_new_array(void);
+void json_free(struct json_object *object);
+void json_assert_is_array(struct json_object *array);
+bool json_is_invalid(struct json_object *object);
+
+void json_add_int(
+	struct json_object *object,
+	const char *name,
+	const int value);
+void json_add_bool(
+	struct json_object *object,
+	const char *name,
+	const bool value);
+void json_add_string(
+	struct json_object *object,
+	const char *name,
+	const char *value);
+void json_add_object(
+	struct json_object *object,
+	const char *name,
+	struct json_object *value);
+void json_add_stringn(
+	struct json_object *object,
+	const char *name,
+	const char *value,
+	const size_t len);
+void json_add_version(
+	struct json_object *object,
+	int major,
+	int minor);
+void json_add_timestamp(struct json_object *object);
+void json_add_address(
+	struct json_object *object,
+	const char *name,
+	const struct tsocket_address *address);
+void json_add_sid(
+	struct json_object *object,
+	const char *name,
+	const struct dom_sid *sid);
+void json_add_guid(
+	struct json_object *object,
+	const char *name,
+	const struct GUID *guid);
+
+char *json_to_string(TALLOC_CTX *mem_ctx, struct json_object *object);
+#endif
diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c
new file mode 100644
index 0000000..7481df5
--- /dev/null
+++ b/lib/audit_logging/tests/audit_logging_test.c
@@ -0,0 +1,557 @@
+/*
+ * Unit tests for the audit_logging library.
+ *
+ *  Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+ *
+ * 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/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+/*
+ * Note that the messaging routines (audit_message_send and get_event_server)
+ * are not tested by these unit tests.  Currently they are for integration
+ * test support, and as such are exercised by the integration tests.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <string.h>
+#include <time.h>
+#include <tevent.h>
+#include <config.h>
+#include <talloc.h>
+#include "lib/util/talloc_stack.h"
+
+#include "lib/util/data_blob.h"
+#include "lib/util/time.h"
+#include "libcli/util/werror.h"
+#include "lib/param/loadparm.h"
+#include "libcli/security/dom_sid.h"
+#include "librpc/ndr/libndr.h"
+
+#include "lib/audit_logging/audit_logging.h"
+
+#ifdef HAVE_JANSSON
+static void test_json_add_int(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+	double n;
+
+	object = json_new_object();
+	json_add_int(&object, "positive_one", 1);
+	json_add_int(&object, "zero", 0);
+	json_add_int(&object, "negative_one", -1);
+
+
+	assert_int_equal(3, json_object_size(object.root));
+
+	value = json_object_get(object.root, "positive_one");
+	assert_true(json_is_integer(value));
+	n = json_number_value(value);
+	assert_true(n == 1.0);
+
+	value = json_object_get(object.root, "zero");
+	assert_true(json_is_integer(value));
+	n = json_number_value(value);
+	assert_true(n == 0.0);
+
+	value = json_object_get(object.root, "negative_one");
+	assert_true(json_is_integer(value));
+	n = json_number_value(value);
+	assert_true(n == -1.0);
+
+	json_free(&object);
+}
+
+static void test_json_add_bool(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+
+	object = json_new_object();
+	json_add_bool(&object, "true", true);
+	json_add_bool(&object, "false", false);
+
+
+	assert_int_equal(2, json_object_size(object.root));
+
+	value = json_object_get(object.root, "true");
+	assert_true(json_is_boolean(value));
+	assert_true(value == json_true());
+
+	value = json_object_get(object.root, "false");
+	assert_true(json_is_boolean(value));
+	assert_true(value == json_false());
+
+	json_free(&object);
+}
+
+static void test_json_add_string(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+	const char *s = NULL;
+
+	object = json_new_object();
+	json_add_string(&object, "null", NULL);
+	json_add_string(&object, "empty", "");
+	json_add_string(&object, "name", "value");
+
+
+
+	assert_int_equal(3, json_object_size(object.root));
+
+	value = json_object_get(object.root, "null");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "empty");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("", s);
+
+	value = json_object_get(object.root, "name");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("value", s);
+	json_free(&object);
+}
+
+static void test_json_add_object(void **state)
+{
+	struct json_object object;
+	struct json_object other;
+	struct json_t *value = NULL;
+
+	object = json_new_object();
+	other  = json_new_object();
+	json_add_object(&object, "null", NULL);
+	json_add_object(&object, "other", &other);
+
+
+
+	assert_int_equal(2, json_object_size(object.root));
+
+	value = json_object_get(object.root, "null");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "other");
+	assert_true(json_is_object(value));
+	assert_ptr_equal(other.root, value);
+
+	json_free(&object);
+}
+
+static void test_json_add_to_array(void **state)
+{
+	struct json_object array;
+	struct json_object o1;
+	struct json_object o2;
+	struct json_object o3;
+	struct json_t *value = NULL;
+
+	array = json_new_array();
+	assert_true(json_is_array(array.root));
+
+	o1 = json_new_object();
+	o2 = json_new_object();
+	o3 = json_new_object();
+
+	json_add_object(&array, NULL, &o3);
+	json_add_object(&array, "", &o2);
+	json_add_object(&array, "will-be-ignored", &o1);
+	json_add_object(&array, NULL, NULL);
+
+	assert_int_equal(4, json_array_size(array.root));
+
+	value = json_array_get(array.root, 0);
+	assert_ptr_equal(o3.root, value);
+
+	value = json_array_get(array.root, 1);
+	assert_ptr_equal(o2.root, value);
+
+	value = json_array_get(array.root, 2);
+	assert_ptr_equal(o1.root, value);
+
+	value = json_array_get(array.root, 3);
+	assert_true(json_is_null(value));
+
+	json_free(&array);
+
+}
+
+static void test_json_add_timestamp(void **state)
+{
+	struct json_object object;
+	struct json_t *ts = NULL;
+	const char *t = NULL;
+	int rc;
+	int usec, tz;
+	char c[2];
+	struct tm tm;
+	time_t before;
+	time_t after;
+	time_t actual;
+
+
+	object = json_new_object();
+	before = time(NULL);
+	json_add_timestamp(&object);
+	after = time(NULL);
+
+	ts = json_object_get(object.root, "timestamp");
+	assert_true(json_is_string(ts));
+
+	/*
+	 * Convert the returned ISO 8601 timestamp into a time_t
+	 * Note for convenience we ignore the value of the microsecond
+	 * part of the time stamp.
+	 */
+	t = json_string_value(ts);
+ 	rc = sscanf(
+		t,
+		"%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d",
+		&tm.tm_year,
+		&tm.tm_mon,
+		&tm.tm_mday,
+		&tm.tm_hour,
+		&tm.tm_min,
+		&tm.tm_sec,
+		&usec,
+		c,
+		&tz);
+	assert_int_equal(9, rc);
+	tm.tm_year = tm.tm_year - 1900;
+	tm.tm_mon = tm.tm_mon - 1;
+	tm.tm_isdst = -1;
+	actual = mktime(&tm);
+
+	/*
+	 * The timestamp should be before <= actual <= after
+	 */
+	assert_true(difftime(actual, before) >= 0);
+	assert_true(difftime(after, actual) >= 0);
+
+	json_free(&object);
+}
+
+static void test_json_add_stringn(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+	const char *s = NULL;
+
+	object = json_new_object();
+	json_add_stringn(&object, "null", NULL, 10);
+	json_add_stringn(&object, "null-zero-len", NULL, 0);
+	json_add_stringn(&object, "empty", "", 1);
+	json_add_stringn(&object, "empty-zero-len", "", 0);
+	json_add_stringn(&object, "value-less-than-len", "123456", 7);
+	json_add_stringn(&object, "value-greater-than-len", "abcd", 3);
+	json_add_stringn(&object, "value-equal-len", "ZYX", 3);
+	json_add_stringn(&object, "value-len-is-zero", "this will be null", 0);
+
+
+	assert_int_equal(8, json_object_size(object.root));
+
+	value = json_object_get(object.root, "null");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "null-zero-len");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "empty");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("", s);
+
+	value = json_object_get(object.root, "empty-zero-len");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "value-greater-than-len");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("abc", s);
+	assert_int_equal(3, strlen(s));
+
+	value = json_object_get(object.root, "value-equal-len");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("ZYX", s);
+	assert_int_equal(3, strlen(s));
+
+	value = json_object_get(object.root, "value-len-is-zero");
+	assert_true(json_is_null(value));
+
+	json_free(&object);
+}
+
+static void test_json_add_version(void **state)
+{
+	struct json_object object;
+	struct json_t *version = NULL;
+	struct json_t *v = NULL;
+	double n;
+
+	object = json_new_object();
+	json_add_version(&object, 3, 1);
+
+	assert_int_equal(1, json_object_size(object.root));
+
+	version = json_object_get(object.root, "version");
+	assert_true(json_is_object(version));
+	assert_int_equal(2, json_object_size(version));
+
+	v = json_object_get(version, "major");
+	assert_true(json_is_integer(v));
+	n = json_number_value(v);
+	assert_true(n == 3.0);
+
+	v = json_object_get(version, "minor");
+	assert_true(json_is_integer(v));
+	n = json_number_value(v);
+	assert_true(n == 1.0);
+
+	json_free(&object);
+}
+
+static void test_json_add_address(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+	struct tsocket_address *ip4  = NULL;
+	struct tsocket_address *ip6  = NULL;
+	struct tsocket_address *pipe = NULL;
+	const char *s = NULL;
+	int rc;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	object = json_new_object();
+
+	json_add_address(&object, "null", NULL);
+
+	rc = tsocket_address_inet_from_strings(
+		ctx,
+		"ip",
+		"127.0.0.1",
+		21,
+		&ip4);
+	assert_int_equal(0, rc);
+	json_add_address(&object, "ip4", ip4);
+
+	rc = tsocket_address_inet_from_strings(
+		ctx,
+		"ip",
+		"2001:db8:0:0:1:0:0:1",
+		42,
+		&ip6);
+	assert_int_equal(0, rc);
+	json_add_address(&object, "ip6", ip6);
+
+	rc = tsocket_address_unix_from_path(ctx, "/samba/pipe", &pipe);
+	assert_int_equal(0, rc);
+	json_add_address(&object, "pipe", pipe);
+
+	assert_int_equal(4, json_object_size(object.root));
+
+	value = json_object_get(object.root, "null");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "ip4");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("ipv4:127.0.0.1:21", s);
+
+	value = json_object_get(object.root, "ip6");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("ipv6:2001:db8::1:0:0:1:42", s);
+
+	value = json_object_get(object.root, "pipe");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal("unix:/samba/pipe", s);
+
+	json_free(&object);
+	TALLOC_FREE(ctx);
+}
+
+static void test_json_add_sid(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+	const char *SID = "S-1-5-21-2470180966-3899876309-2637894779";
+	struct dom_sid sid;
+	const char *s = NULL;
+
+
+	object = json_new_object();
+
+	json_add_sid(&object, "null", NULL);
+
+	assert_true(string_to_sid(&sid, SID));
+	json_add_sid(&object, "sid", &sid);
+
+	assert_int_equal(2, json_object_size(object.root));
+
+	value = json_object_get(object.root, "null");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "sid");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal(SID, s);
+	json_free(&object);
+}
+
+static void test_json_add_guid(void **state)
+{
+	struct json_object object;
+	struct json_t *value = NULL;
+	const char *GUID = "3ab88633-1e57-4c1a-856c-d1bc4b15bbb1";
+	struct GUID guid;
+	const char *s = NULL;
+	NTSTATUS status;
+
+
+	object = json_new_object();
+
+	json_add_guid(&object, "null", NULL);
+
+	status = GUID_from_string(GUID, &guid);
+	assert_true(NT_STATUS_IS_OK(status));
+	json_add_guid(&object, "guid", &guid);
+
+	assert_int_equal(2, json_object_size(object.root));
+
+	value = json_object_get(object.root, "null");
+	assert_true(json_is_null(value));
+
+	value = json_object_get(object.root, "guid");
+	assert_true(json_is_string(value));
+	s = json_string_value(value);
+	assert_string_equal(GUID, s);
+
+	json_free(&object);
+}
+
+static void test_json_to_string(void **state)
+{
+	struct json_object object;
+	char *s = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	object = json_new_object();
+	object.error = true;
+
+	s = json_to_string(ctx, &object);
+	assert_null(s);
+
+	object.error = false;
+	s = json_to_string(ctx, &object);
+	assert_string_equal("{}", s);
+	TALLOC_FREE(s);
+
+	json_add_string(&object, "name", "value");
+	s = json_to_string(ctx, &object);
+	assert_string_equal("{\"name\": \"value\"}", s);
+	TALLOC_FREE(s);
+
+	json_free(&object);
+	TALLOC_FREE(ctx);
+}
+#endif
+
+static void test_audit_get_timestamp(void **state)
+{
+	const char *t = NULL;
+	char *c;
+	struct tm tm;
+	time_t before;
+	time_t after;
+	time_t actual;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	before = time(NULL);
+	t = audit_get_timestamp(ctx);
+	after = time(NULL);
+
+
+	c = strptime(t, "%a, %d %b %Y %H:%M:%S", &tm);
+	tm.tm_isdst = -1;
+	if (c != NULL && *c == '.') {
+		char *e;
+		strtod(c, &e);
+		c = e;
+	}
+	if (c != NULL && *c == ' ') {
+		struct tm tz;
+		c = strptime(c, " %Z", &tz);
+	}
+	assert_int_equal(0, strlen(c));
+
+	actual = mktime(&tm);
+
+	/*
+	 * The timestamp should be before <= actual <= after
+	 */
+	assert_true(difftime(actual, before) >= 0);
+	assert_true(difftime(after, actual) >= 0);
+
+	TALLOC_FREE(ctx);
+}
+
+int main(int argc, const char **argv)
+{
+	const struct CMUnitTest tests[] = {
+#ifdef HAVE_JANSSON
+		cmocka_unit_test(test_json_add_int),
+		cmocka_unit_test(test_json_add_bool),
+		cmocka_unit_test(test_json_add_string),
+		cmocka_unit_test(test_json_add_object),
+		cmocka_unit_test(test_json_add_to_array),
+		cmocka_unit_test(test_json_add_timestamp),
+		cmocka_unit_test(test_json_add_stringn),
+		cmocka_unit_test(test_json_add_version),
+		cmocka_unit_test(test_json_add_address),
+		cmocka_unit_test(test_json_add_sid),
+		cmocka_unit_test(test_json_add_guid),
+		cmocka_unit_test(test_json_to_string),
+#endif
+		cmocka_unit_test(test_audit_get_timestamp),
+	};
+
+	cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+	return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/audit_logging/wscript_build b/lib/audit_logging/wscript_build
new file mode 100644
index 0000000..35bf79c
--- /dev/null
+++ b/lib/audit_logging/wscript_build
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM(
+    'audit_logging',
+    deps='',
+    source='audit_logging.c'
+)
+
+bld.SAMBA_BINARY(
+    'audit_logging_test',
+    source='tests/audit_logging_test.c',
+    deps='''
+        audit_logging
+        jansson
+        cmocka
+        talloc
+        samba-debug
+        samba-util
+        LIBTSOCKET
+        MESSAGING_SEND
+    ''',
+    install=False
+)
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index b1483aa..129901c 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -1052,3 +1052,5 @@ plantestsuite("samba4.dsdb.samdb.ldb_modules.unique_object_sids" , "none",
               [os.path.join(bindir(), "test_unique_object_sids")])
 plantestsuite("samba4.dsdb.samdb.ldb_modules.encrypted_secrets", "none",
                   [os.path.join(bindir(), "test_encrypted_secrets")])
+plantestsuite("lib.audit_logging.audit_logging", "none",
+                  [os.path.join(bindir(), "audit_logging_test")])
diff --git a/wscript_build b/wscript_build
index 8a59bdf..5454876 100644
--- a/wscript_build
+++ b/wscript_build
@@ -46,6 +46,7 @@ bld.RECURSE('lib/texpect')
 bld.RECURSE('lib/addns')
 bld.RECURSE('lib/ldb')
 bld.RECURSE('lib/param')
+bld.RECURSE('lib/audit_logging')
 bld.RECURSE('dynconfig')
 bld.RECURSE('lib/util/charset')
 bld.RECURSE('python')
-- 
2.7.4


From 4daf72457d802586749d45f452581dd7f1fac655 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Wed, 4 Apr 2018 11:56:30 +1200
Subject: [PATCH 07/21] logging: add ldb audit classes

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 docs-xml/smbdotconf/logging/loglevel.xml | 28 +++++++++++++++++++++++++---
 lib/util/debug.c                         |  6 ++++++
 lib/util/debug.h                         |  6 ++++++
 3 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/docs-xml/smbdotconf/logging/loglevel.xml b/docs-xml/smbdotconf/logging/loglevel.xml
index d3b5c45..3535b1b 100644
--- a/docs-xml/smbdotconf/logging/loglevel.xml
+++ b/docs-xml/smbdotconf/logging/loglevel.xml
@@ -46,6 +46,12 @@
         <listitem><para><parameter moreinfo="none">auth_audit</parameter></para></listitem>
         <listitem><para><parameter moreinfo="none">auth_json_audit</parameter></para></listitem>
         <listitem><para><parameter moreinfo="none">kerberos</parameter></para></listitem>
+        <listitem><para><parameter moreinfo="none">samdb_audit</parameter></para></listitem>
+        <listitem><para><parameter moreinfo="none">samdb_json_audit</parameter></para></listitem>
+        <listitem><para><parameter moreinfo="none">password_audit</parameter></para></listitem>
+        <listitem><para><parameter moreinfo="none">password_json_audit</parameter></para></listitem>
+        <listitem><para><parameter moreinfo="none">transaction_audit</parameter></para></listitem>
+        <listitem><para><parameter moreinfo="none">transaction_json_audit</parameter></para></listitem>
     </itemizedlist>
 
     <para>Authentication and authorization audit information is logged
@@ -58,7 +64,7 @@
     as well as the implicit authentication in password changes.  In
     the file server, NTLM authentication, SMB and RPC authorization is
     covered.</para>
-    
+
     <para>Log levels for auth_audit and auth_audit_json are:</para>
     <itemizedlist>
 	<listitem><para>2: Authentication Failure</para></listitem>
@@ -66,9 +72,25 @@
 	<listitem><para>4: Authorization Success</para></listitem>
 	<listitem><para>5: Anonymous Authentication and Authorization Success</para></listitem>
     </itemizedlist>
-    
 
-    
+    <para>Changes to the samdb database are logged
+    under the samdb_audit, and if Samba is compiled against the jansson
+    JSON library, a JSON representation is logged under
+    samdb_json_audit.</para>
+
+    <para>Password changes and Password resets are logged under
+    the password_audit, and if Samba is compiled against the jansson
+    JSON library, a JSON representation is logged under the
+    password_json_audit.</para>
+
+    <para>Transaction rollbacks and prepare commit failures are logged under
+    the transaction_audit the password_audit, and if Samba is compiled against
+    the jansson JSON library, a JSON representation is logged under the
+    password_json_audit. Logging the transaction details allows the
+    identification of password and samdb operations that have been rolled
+    back.</para>
+
+
 </description>
 <value type="default">0</value>
 <value type="example">3 passdb:5 auth:10 winbind:2</value>
diff --git a/lib/util/debug.c b/lib/util/debug.c
index d010b72..db4f71a 100644
--- a/lib/util/debug.c
+++ b/lib/util/debug.c
@@ -543,6 +543,12 @@ static const char *default_classname_table[] = {
 	[DBGC_DRS_REPL] =       "drs_repl",
 	[DBGC_SMB2] =           "smb2",
 	[DBGC_SMB2_CREDITS] =   "smb2_credits",
+	[DBGC_SAMDB_AUDIT]  =	"samdb_audit",
+	[DBGC_SAMDB_AUDIT_JSON] = "samdb_json_audit",
+	[DBGC_PWD_AUDIT]  =	"password_audit",
+	[DBGC_PWD_AUDIT_JSON] = "password_json_audit",
+	[DBGC_TRN_AUDIT]  =	"transaction_audit",
+	[DBGC_TRN_AUDIT_JSON] = "transaction_json_audit",
 };
 
 /*
diff --git a/lib/util/debug.h b/lib/util/debug.h
index 1e184b4..4e425a0 100644
--- a/lib/util/debug.h
+++ b/lib/util/debug.h
@@ -95,6 +95,12 @@ bool dbghdr( int level, const char *location, const char *func);
 #define DBGC_DRS_REPL           27
 #define DBGC_SMB2               28
 #define DBGC_SMB2_CREDITS       29
+#define DBGC_SAMDB_AUDIT	30
+#define DBGC_SAMDB_AUDIT_JSON	31
+#define DBGC_PWD_AUDIT		32
+#define DBGC_PWD_AUDIT_JSON	33
+#define DBGC_TRN_AUDIT		34
+#define DBGC_TRN_AUDIT_JSON	35
 
 /* So you can define DBGC_CLASS before including debug.h */
 #ifndef DBGC_CLASS
-- 
2.7.4


From 1df2279b609a02ea0cf5e98bb33f75c4d0e7be26 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Thu, 12 Apr 2018 10:19:16 +1200
Subject: [PATCH 08/21] smb conf: Add samdb event notification parameter

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 .../smbdotconf/misc/passwordeventnotification.xml  | 27 ++++++++++++++++++++++
 .../smbdotconf/misc/samdbeventnotification.xml     | 27 ++++++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 docs-xml/smbdotconf/misc/passwordeventnotification.xml
 create mode 100644 docs-xml/smbdotconf/misc/samdbeventnotification.xml

diff --git a/docs-xml/smbdotconf/misc/passwordeventnotification.xml b/docs-xml/smbdotconf/misc/passwordeventnotification.xml
new file mode 100644
index 0000000..e83f05d
--- /dev/null
+++ b/docs-xml/smbdotconf/misc/passwordeventnotification.xml
@@ -0,0 +1,27 @@
+<samba:parameter name="password event notification"
+                 context="G"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+	<para>When enabled, this option causes Samba (acting as an
+	Active Directory Domain Controller) to stream password change
+	and reset events across the internal message bus.
+	Scripts built using Samba's python bindings can listen to these
+	events by registering as the service
+	<filename moreinfo="none">password_event</filename>.</para>
+
+	<para>This should be considered a developer option (it assists
+	in the Samba testsuite) rather than a facility for external
+	auditing, as message delivery is not guaranteed (a feature
+	that the testsuite works around).  Additionally Samba must be
+	compiled with the jansson support for this option to be
+	effective.</para>
+
+	<para>The password events are also logged via the normal
+	logging methods when the <smbconfoption name="log level"/> is
+	set appropriately.</para>
+
+</description>
+
+<value type="default">no</value>
+</samba:parameter>
diff --git a/docs-xml/smbdotconf/misc/samdbeventnotification.xml b/docs-xml/smbdotconf/misc/samdbeventnotification.xml
new file mode 100644
index 0000000..a1d7aca
--- /dev/null
+++ b/docs-xml/smbdotconf/misc/samdbeventnotification.xml
@@ -0,0 +1,27 @@
+<samba:parameter name="samdb event notification"
+                 context="G"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+	<para>When enabled, this option causes Samba (acting as an
+	Active Directory Domain Controller) to stream Samba database
+	events across the internal message bus.  Scripts built using
+	Samba's python bindings can listen to these events by
+	registering as the service
+	<filename moreinfo="none">samdb_event</filename>.</para>
+
+	<para>This should be considered a developer option (it assists
+	in the Samba testsuite) rather than a facility for external
+	auditing, as message delivery is not guaranteed (a feature
+	that the testsuite works around).  Additionally Samba must be
+	compiled with the jansson support for this option to be
+	effective.</para>
+
+	<para>The Samba database events are also logged via the normal
+	logging methods when the <smbconfoption name="log level"/> is
+	set appropriately.</para>
+
+</description>
+
+<value type="default">no</value>
+</samba:parameter>
-- 
2.7.4


From 25b569e74a74ae6ec76981ee49e97a9ae0cbf89e Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Thu, 12 Apr 2018 13:19:16 +1200
Subject: [PATCH 09/21] idl messaging: Add Samdb and Password events and
 message types

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 librpc/idl/messaging.idl | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/librpc/idl/messaging.idl b/librpc/idl/messaging.idl
index 14a6f92..73cceff 100644
--- a/librpc/idl/messaging.idl
+++ b/librpc/idl/messaging.idl
@@ -140,8 +140,11 @@ interface messaging
 		MSG_NTVFS_OPLOCK_BREAK          = 0x0703,
 		MSG_DREPL_ALLOCATE_RID          = 0x0704,
 
-		/* Called during authentication and authorization to allow out-of- */
+		/*
+		 * Audit, Authentication and Authorisation event messages */
 		MSG_AUTH_LOG                    = 0x0800,
+		MSG_SAMDB_LOG                   = 0x0801,
+		MSG_PWD_LOG                     = 0x0802,
 
 		/* dbwrap messages 4001-4999 (0x0FA0 - 0x1387) */
 		/* MSG_DBWRAP_TDB2_CHANGES		= 4001, */
@@ -178,5 +181,7 @@ interface messaging
 	} messaging_reclog;
 
         /* This allows this well known service name to be referenced in python and C */
-        const string AUTH_EVENT_NAME = "auth_event";
+        const string AUTH_EVENT_NAME  = "auth_event";
+	const string SAMDB_EVENT_NAME = "samdb_event";
+	const string PWD_EVENT_NAME   = "password_event";
 }
-- 
2.7.4


From 8dc72eb439c4b661f61fd6dfd9b727d315780522 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Wed, 4 Apr 2018 11:59:41 +1200
Subject: [PATCH 10/21] dsdb: audit samdb and password changes

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source4/dsdb/samdb/ldb_modules/audit_log.c         | 935 ++++++++++++++++++++
 source4/dsdb/samdb/ldb_modules/audit_util.c        | 470 ++++++++++
 source4/dsdb/samdb/ldb_modules/samba_dsdb.c        |   3 +-
 .../dsdb/samdb/ldb_modules/tests/test_audit_log.c  | 468 ++++++++++
 .../dsdb/samdb/ldb_modules/tests/test_audit_util.c | 958 +++++++++++++++++++++
 source4/dsdb/samdb/ldb_modules/wscript_build       |  28 +-
 .../dsdb/samdb/ldb_modules/wscript_build_server    |  16 +
 source4/selftest/tests.py                          |   4 +
 8 files changed, 2879 insertions(+), 3 deletions(-)
 create mode 100644 source4/dsdb/samdb/ldb_modules/audit_log.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/audit_util.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c

diff --git a/source4/dsdb/samdb/ldb_modules/audit_log.c b/source4/dsdb/samdb/ldb_modules/audit_log.c
new file mode 100644
index 0000000..67182d2
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/audit_log.c
@@ -0,0 +1,935 @@
+/*
+   ldb database library
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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/>.
+*/
+
+/*
+ * Provide an audit log of changes made to the database and at a higher level
+ * details of any password changes and resets.
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/dom_sid.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+
+#define OPERATION_JSON_TYPE "samdbChange"
+#define OPERATION_HR_TAG "Samdb Change"
+#define OPERATION_MAJOR 1
+#define OPERATION_MINOR 0
+#define OPERATION_LOG_LVL 5
+
+#define PASSWORD_JSON_TYPE "passwordChange"
+#define PASSWORD_HR_TAG "Password Change"
+#define PASSWORD_MAJOR 1
+#define PASSWORD_MINOR 0
+#define PASSWORD_LOG_LVL 5
+
+#define TRANSACTION_JSON_TYPE "samdbTransaction"
+#define TRANSACTION_HR_TAG "Samdb Transaction"
+#define TRANSACTION_MAJOR 1
+#define TRANSACTION_MINOR 0
+/*
+ * Currently we only log roll backs and prepare commit failures
+ */
+#define TRANSACTION_LOG_LVL 5
+
+#define MAX_LENGTH 1024
+
+#define min(a, b) (((a)>(b))?(b):(a))
+
+static const char * const secret_attributes[] = {DSDB_SECRET_ATTRIBUTES, NULL};
+static const char * const password_attributes[] = {
+	DSDB_PASSWORD_ATTRIBUTES,
+	NULL};
+
+struct audit_context {
+	bool send_samdb_events;
+	bool send_password_events;
+	struct imessaging_context *msg_ctx;
+	struct GUID transaction_guid;
+};
+
+/*
+ * @brief Has the password changed.
+ *
+ * Does the message contain a change to one of the password attributes? The
+ * password attributes are defined in DSDB_PASSWORD_ATTRIBUTES
+ *
+ * @return true if the message contains a password attribute
+ *
+ */
+static bool has_password_changed(const struct ldb_message *message)
+{
+	int i;
+	if (message == NULL) {
+		return false;
+	}
+	for (i=0;i<message->num_elements;i++) {
+		if (ldb_attr_in_list(
+			password_attributes,
+			message->elements[i].name)) {
+
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * @brief Is the request a password "Change" or a "Reset"
+ *
+ * Get a description of the action being performed on the user password.  This
+ * routine assumes that the request contains password attributes and that the
+ * password ACL checks have been performed by acl.c
+ *
+ * @param req the ldb_request to inspect
+ *
+ * @return "Change" if the password is being changed.
+ *         "Reset"  if the password is being reset.
+ */
+static const char *get_password_action(const struct ldb_request *request)
+{
+	if(request->operation == LDB_ADD) {
+		return "Reset";
+	} else {
+		struct ldb_control *pav_ctrl = NULL;
+		struct dsdb_control_password_acl_validation *pav = NULL;
+
+		pav_ctrl = ldb_request_get_control(
+			discard_const(request),
+			DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+		if (pav_ctrl == NULL) {
+			return "Change";
+		}
+
+		pav = talloc_get_type_abort(
+			pav_ctrl->data,
+			struct dsdb_control_password_acl_validation);
+
+		if (pav->pwd_reset) {
+			return "Reset";
+		} else {
+			return "Change";
+		}
+	}
+}
+
+
+#ifdef HAVE_JANSSON
+/*
+ * @brief generate a JSON object as a
+ */
+static struct json_object operation_json(
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const int ret)
+{
+	struct ldb_context *ldb = NULL;
+	const struct dom_sid *sid = NULL;
+	struct json_object wrapper;
+	struct json_object audit;
+	const struct tsocket_address *remote = NULL;
+	const char *dn = NULL;
+	const char* operation = NULL;
+	const struct GUID *unique_session_token = NULL;
+	const struct ldb_message *message = NULL;
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	ldb = ldb_module_get_ctx(module);
+
+	remote = get_remote_address(ldb);
+	sid = get_user_sid(module);
+	dn = get_primary_dn(request);
+	operation = get_operation_name(request);
+	unique_session_token = get_unique_session_token(module);
+
+	audit = json_new_object();
+	json_add_version(&audit, OPERATION_MAJOR, OPERATION_MINOR);
+	json_add_int(&audit, "statusCode", ret);
+	json_add_string(&audit, "status", ldb_strerror(ret));
+	json_add_string(&audit, "operation", operation);
+	json_add_address(&audit, "remoteAddress", remote);
+	json_add_sid(&audit, "userSid", sid);
+	json_add_string(&audit, "dn", dn);
+	json_add_guid(&audit, "transactionId", &ac->transaction_guid);
+	json_add_guid(&audit, "sessionId", unique_session_token);
+
+	message = get_message(request);
+	if (message != NULL) {
+		struct json_object attributes
+			= attributes_json(request->operation, message);
+		json_add_object(&audit, "attributes", &attributes);
+	}
+
+	wrapper = json_new_object();
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", OPERATION_JSON_TYPE);
+	json_add_object(&wrapper, OPERATION_JSON_TYPE, &audit);
+	return wrapper;
+}
+
+static struct json_object password_change_json(
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const int ret)
+{
+	struct ldb_context *ldb = NULL;
+	const struct dom_sid *sid = NULL;
+	const char* dn = NULL;
+	struct json_object wrapper;
+	struct json_object audit;
+	const struct tsocket_address *remote = NULL;
+	const char* action = NULL;
+	const struct GUID *unique_session_token = NULL;
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+
+	ldb = ldb_module_get_ctx(module);
+
+	remote = get_remote_address(ldb);
+	sid = get_user_sid(module);
+	dn = get_primary_dn(request);
+	action = get_password_action(request);
+	unique_session_token = get_unique_session_token(module);
+
+	audit = json_new_object();
+	json_add_version(&audit, PASSWORD_MAJOR, PASSWORD_MINOR);
+	json_add_int(&audit, "statusCode", ret);
+	json_add_string(&audit, "status", ldb_strerror(ret));
+	json_add_address(&audit, "remoteAddress", remote);
+	json_add_sid(&audit, "userSid", sid);
+	json_add_string(&audit, "dn", dn);
+	json_add_string(&audit, "action", action);
+	json_add_guid(&audit, "transactionId", &ac->transaction_guid);
+	json_add_guid(&audit, "sessionId", unique_session_token);
+
+	wrapper = json_new_object();
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", PASSWORD_JSON_TYPE);
+	json_add_object(&wrapper, PASSWORD_JSON_TYPE, &audit);
+
+	return wrapper;
+}
+
+static struct json_object transaction_json(
+	const char *action,
+	struct GUID *transaction_id)
+{
+	struct json_object wrapper;
+	struct json_object audit;
+
+	audit = json_new_object();
+	json_add_version(&audit, TRANSACTION_MAJOR, TRANSACTION_MINOR);
+	json_add_string(&audit, "action", action);
+	json_add_guid(&audit, "transactionId", transaction_id);
+
+	wrapper = json_new_object();
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", TRANSACTION_JSON_TYPE);
+	json_add_object(&wrapper, TRANSACTION_JSON_TYPE, &audit);
+
+	return wrapper;
+}
+
+
+static struct json_object prepare_commit_failure_json(
+	int status,
+	const char *reason,
+	struct GUID *transaction_id)
+{
+	struct json_object wrapper;
+	struct json_object audit;
+
+	audit = json_new_object();
+	json_add_version(&audit, TRANSACTION_MAJOR, TRANSACTION_MINOR);
+	json_add_string(&audit, "action", "prepare");
+	json_add_guid(&audit, "transactionId", transaction_id);
+	json_add_int(&audit, "statusCode", status);
+	json_add_string(&audit, "status", ldb_strerror(status));
+	json_add_string(&audit, "reason", reason);
+
+	wrapper = json_new_object();
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", TRANSACTION_JSON_TYPE);
+	json_add_object(&wrapper, TRANSACTION_JSON_TYPE, &audit);
+
+	return wrapper;
+}
+
+#endif
+
+static char *password_change_human_readable(
+	TALLOC_CTX *mem_ctx,
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	int ret)
+{
+	struct ldb_context *ldb = NULL;
+	const char *remote_host = NULL;
+	const struct dom_sid *sid = NULL;
+	const char *user_sid = NULL;
+	const char *timestamp = NULL;
+	char *log_entry = NULL;
+	const char *action = NULL;
+	const char *dn = NULL;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	ldb = ldb_module_get_ctx(module);
+
+	remote_host = get_remote_host(ldb, frame);
+	sid = get_user_sid(module);
+	user_sid = dom_sid_string(frame, sid);
+	timestamp = audit_get_timestamp(frame);
+	action = get_password_action(request);
+	dn = get_primary_dn(request);
+
+	log_entry = talloc_asprintf(
+		frame,
+		"[%s] at [%s] status [%s] "
+		"remote host [%s] SID [%s] DN [%s]",
+		action,
+		timestamp,
+		ldb_strerror(ret),
+		remote_host,
+		user_sid,
+		dn);
+	TALLOC_FREE(frame);
+	return log_entry;
+}
+
+static char *log_attributes(struct ldb_context *ldb,
+			    char *buffer,
+			    enum ldb_request_type operation,
+			    const struct ldb_message *message)
+{
+	int i, j;
+	for (i=0;i<message->num_elements;i++) {
+		if (i > 0) {
+			buffer = talloc_asprintf_append_buffer(buffer, " ");
+		}
+
+		if (message->elements[i].name == NULL) {
+			ldb_debug(ldb,
+				  LDB_DEBUG_ERROR,
+				  "Error: Invalid element name (NULL) at "
+				  "position %d", i);
+			return NULL;
+		}
+
+		if (operation == LDB_MODIFY) {
+			const char *action =NULL;
+			action = get_modification_action(
+				message->elements[i].flags);
+			buffer = talloc_asprintf_append_buffer(
+				buffer,
+				"%s: %s ",
+				action,
+				message->elements[i].name);
+		} else {
+			buffer = talloc_asprintf_append_buffer(
+				buffer,
+				"%s ",
+				message->elements[i].name);
+		}
+
+		if (ldb_attr_in_list(secret_attributes, message->elements[i].name)) {
+			/* Do not log the value of any secret attributes */
+			buffer = talloc_asprintf_append_buffer(
+				buffer,
+				"[REDACTED SECRET ATTRIBUTE]");
+			continue;
+		}
+
+		for (j=0;j<message->elements[i].num_values;j++) {
+			struct ldb_val v;
+			bool use_b64_encode = false;
+			int length;
+			if (j > 0) {
+				buffer = talloc_asprintf_append_buffer(buffer, " ");
+			}
+
+			v = message->elements[i].values[j];
+			length = min(MAX_LENGTH, v.length);
+			use_b64_encode = ldb_should_b64_encode(ldb, &v);
+			if (use_b64_encode) {
+				const char *encoded = ldb_base64_encode(
+					buffer,
+					(char *)v.data,
+					length);
+				buffer = talloc_asprintf_append_buffer(
+					buffer,
+				        "{%s%s}",
+					encoded,
+					(v.length > MAX_LENGTH ? "..." : ""));
+			} else {
+				buffer = talloc_asprintf_append_buffer(
+					buffer,
+					"[%*.*s%s]",
+					length,
+					length,
+					(char *)v.data,
+					(v.length > MAX_LENGTH ? "..." : ""));
+			}
+		}
+	}
+	return buffer;
+}
+
+static char *operation_human_readable(
+	TALLOC_CTX *mem_ctx,
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const int ret)
+{
+	struct ldb_context *ldb = NULL;
+	const char *remote_host = NULL;
+	const struct dom_sid *sid = NULL;
+	const char *user_sid = NULL;
+	const char *timestamp = NULL;
+	const char *op_name = NULL;
+	char *log_entry = NULL;
+	const char *dn = NULL;
+	const char *new_dn = NULL;
+	const struct ldb_message *message = NULL;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	ldb = ldb_module_get_ctx(module);
+
+	remote_host = get_remote_host(ldb, frame);
+	sid = get_user_sid(module);
+	user_sid = dom_sid_string(frame, sid);
+	timestamp = audit_get_timestamp(frame);
+	op_name = get_operation_name(request);
+	dn = get_primary_dn(request);
+	new_dn = get_secondary_dn(request);
+
+	message = get_message(request);
+
+	log_entry = talloc_asprintf(
+		mem_ctx,
+		"[%s] at [%s] status [%s] "
+		"remote host [%s] SID [%s] DN [%s]",
+		op_name,
+		timestamp,
+		ldb_strerror(ret),
+		remote_host,
+		user_sid,
+		dn);
+	if (new_dn != NULL) {
+		log_entry = talloc_asprintf_append_buffer(
+			log_entry,
+			" New DN [%s]",
+			new_dn);
+	}
+	if (message != NULL) {
+		log_entry = talloc_asprintf_append_buffer(log_entry,
+							  " attributes [");
+		log_entry = log_attributes(ldb,
+					   log_entry,
+					   request->operation,
+					   message);
+		log_entry = talloc_asprintf_append_buffer(log_entry, "]");
+	}
+	TALLOC_FREE(frame);
+	return log_entry;
+}
+
+static char *transaction_human_readable(
+	TALLOC_CTX *mem_ctx,
+	const char* action)
+{
+	const char *timestamp = NULL;
+	char *log_entry = NULL;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	timestamp = audit_get_timestamp(frame);
+
+	log_entry = talloc_asprintf(
+		mem_ctx,
+		"[%s] at [%s]",
+		action,
+		timestamp);
+
+	TALLOC_FREE(frame);
+	return log_entry;
+}
+
+static char *prepare_commit_failure_human_readable(
+	TALLOC_CTX *mem_ctx,
+	int status,
+	const char *reason)
+{
+	const char *timestamp = NULL;
+	char *log_entry = NULL;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	timestamp = audit_get_timestamp(frame);
+
+	log_entry = talloc_asprintf(
+		mem_ctx,
+		"[prepare] at [%s] status [%d] reason [%s]",
+		timestamp,
+		status,
+		reason);
+
+	TALLOC_FREE(frame);
+	return log_entry;
+}
+
+static void log_operation(
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const int ret)
+{
+
+	const struct ldb_message *message = get_message(request);
+	bool password_changed = has_password_changed(message);
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	if (CHECK_DEBUGLVLC(DBGC_SAMDB_AUDIT, OPERATION_LOG_LVL)) {
+		char *entry = NULL;
+		entry = operation_human_readable(
+			frame,
+			module,
+			request,
+			ret);
+		audit_log_hr(
+			OPERATION_HR_TAG,
+			entry,
+			DBGC_SAMDB_AUDIT,
+			OPERATION_LOG_LVL);
+		TALLOC_FREE(entry);
+	}
+	if (CHECK_DEBUGLVLC(DBGC_PWD_AUDIT, PASSWORD_LOG_LVL)) {
+		if (password_changed) {
+			char *entry = NULL;
+			entry = password_change_human_readable(
+				frame,
+				module,
+				request,
+				ret);
+			audit_log_hr(
+				PASSWORD_HR_TAG,
+				entry,
+				DBGC_PWD_AUDIT,
+				PASSWORD_LOG_LVL);
+			TALLOC_FREE(entry);
+		}
+	}
+#ifdef HAVE_JANSSON
+	if (CHECK_DEBUGLVLC(DBGC_SAMDB_AUDIT_JSON, OPERATION_LOG_LVL)) {
+		struct json_object json;
+		json = operation_json(module, request, ret);
+		audit_log_json(
+			OPERATION_JSON_TYPE,
+			&json,
+			DBGC_SAMDB_AUDIT_JSON,
+			OPERATION_LOG_LVL);
+		if (ac->send_samdb_events) {
+			audit_message_send(
+				ac->msg_ctx,
+				SAMDB_EVENT_NAME,
+				MSG_SAMDB_LOG,
+				&json);
+		}
+		json_free(&json);
+	}
+	if (CHECK_DEBUGLVLC(DBGC_PWD_AUDIT_JSON, PASSWORD_LOG_LVL)) {
+		if (password_changed) {
+			struct json_object json;
+			json = password_change_json(module, request, ret);
+			audit_log_json(
+				PASSWORD_JSON_TYPE,
+				&json,
+				DBGC_PWD_AUDIT_JSON,
+				PASSWORD_LOG_LVL);
+			if (ac->send_password_events) {
+				audit_message_send(
+					ac->msg_ctx,
+					PWD_EVENT_NAME,
+					MSG_PWD_LOG,
+					&json);
+			}
+			json_free(&json);
+		}
+	}
+#endif
+	TALLOC_FREE(frame);
+}
+
+static void log_transaction(
+	struct ldb_module *module,
+	const char *action)
+{
+
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	if (CHECK_DEBUGLVLC(DBGC_TRN_AUDIT, TRANSACTION_LOG_LVL)) {
+		char* entry = NULL;
+		entry = transaction_human_readable(frame, action);
+		audit_log_hr(
+			TRANSACTION_HR_TAG,
+			entry,
+			DBGC_TRN_AUDIT,
+			TRANSACTION_LOG_LVL);
+		TALLOC_FREE(entry);
+	}
+#ifdef HAVE_JANSSON
+	if (CHECK_DEBUGLVLC(DBGC_TRN_AUDIT_JSON, TRANSACTION_LOG_LVL)) {
+		struct json_object json;
+		json = transaction_json(action, &ac->transaction_guid);
+		audit_log_json(
+			TRANSACTION_JSON_TYPE,
+			&json,
+			DBGC_TRN_AUDIT_JSON,
+			TRANSACTION_LOG_LVL);
+		if (ac->send_samdb_events) {
+			audit_message_send(
+				ac->msg_ctx,
+				SAMDB_EVENT_NAME,
+				MSG_SAMDB_LOG,
+				&json);
+		}
+		json_free(&json);
+	}
+#endif
+	TALLOC_FREE(frame);
+}
+
+static void log_prepare_commit_failure(
+	struct ldb_module *module,
+	int status)
+{
+
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+	const char* reason = get_ldb_error_string(module, status);
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	if (CHECK_DEBUGLVLC(DBGC_TRN_AUDIT, TRANSACTION_LOG_LVL)) {
+		char* entry = NULL;
+		entry = prepare_commit_failure_human_readable(
+			frame,
+			status,
+			reason);
+		audit_log_hr(
+			TRANSACTION_HR_TAG,
+			entry,
+			DBGC_TRN_AUDIT,
+			TRANSACTION_LOG_LVL);
+		TALLOC_FREE(entry);
+	}
+#ifdef HAVE_JANSSON
+	if (CHECK_DEBUGLVLC(DBGC_TRN_AUDIT_JSON, TRANSACTION_LOG_LVL)) {
+		struct json_object json;
+		json = prepare_commit_failure_json(
+			status,
+			reason,
+			&ac->transaction_guid);
+		audit_log_json(
+			TRANSACTION_JSON_TYPE,
+			&json,
+			DBGC_TRN_AUDIT_JSON,
+			TRANSACTION_LOG_LVL);
+		if (ac->send_samdb_events) {
+			audit_message_send(ac->msg_ctx,
+					   SAMDB_EVENT_NAME,
+					   MSG_SAMDB_LOG,
+					   &json);
+		}
+		json_free(&json);
+	}
+#endif
+	TALLOC_FREE(frame);
+}
+
+struct audit_callback_context {
+	struct ldb_request *request;
+	struct ldb_module *module;
+};
+
+static int audit_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+	struct audit_callback_context *ac = NULL;
+
+	ac = talloc_get_type(req->context,
+			     struct audit_callback_context);
+
+	if (!ares) {
+		return ldb_module_done(ac->request, NULL, NULL,
+				       LDB_ERR_OPERATIONS_ERROR);
+	}
+
+	/* pass on to the callback */
+	switch (ares->type) {
+	case LDB_REPLY_ENTRY:
+		return ldb_module_send_entry(ac->request,
+					     ares->message,
+					     ares->controls);
+
+	case LDB_REPLY_REFERRAL:
+		return ldb_module_send_referral(ac->request,
+						ares->referral);
+
+	case LDB_REPLY_DONE:
+		/* Log on DONE now we have the error or success */
+		log_operation(ac->module, ac->request, ares->error);
+		return ldb_module_done(ac->request,
+				       ares->controls,
+				       ares->response,
+				       ares->error);
+		break;
+
+	default:
+		/* Can't happen */
+		return LDB_ERR_OPERATIONS_ERROR;
+	}
+}
+
+static int log_add(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	int ret;
+
+	ldb = ldb_module_get_ctx(module);
+	context = talloc_zero(req, struct audit_callback_context);
+
+	if (context == NULL) {
+		return ldb_oom(ldb);
+	}
+	context->request = req;
+	context->module  = module;
+	/*
+	 * We want to log the return code status, so we need to register
+	 * a callback function to get the actual result.
+	 * We need to take a new copy so that we don't alter the callers copy
+	 */
+	ret = ldb_build_add_req(&new_req,
+				ldb,
+				req,
+				req->op.add.message,
+				req->controls,
+				context,
+				audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+	return ldb_next_request(module, new_req);
+}
+
+static int log_delete(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	int ret;
+
+	ldb = ldb_module_get_ctx(module);
+	context = talloc_zero(req, struct audit_callback_context);
+
+	if (context == NULL) {
+		return ldb_oom(ldb);
+	}
+	context->request = req;
+	context->module  = module;
+	/*
+	 * We want to log the return code status, so we need to register
+	 * a callback function to get the actual result.
+	 * We need to take a new copy so that we don't alter the callers copy
+	 */
+	ret = ldb_build_del_req(&new_req,
+				ldb,
+				req,
+				req->op.del.dn,
+				req->controls,
+				context,
+				audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+	return ldb_next_request(module, new_req);
+}
+
+static int log_modify(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	int ret;
+
+	ldb = ldb_module_get_ctx(module);
+	context = talloc_zero(req, struct audit_callback_context);
+
+	if (context == NULL) {
+		return ldb_oom(ldb);
+	}
+	context->request = req;
+	context->module  = module;
+	/*
+	 * We want to log the return code status, so we need to register
+	 * a callback function to get the actual result.
+	 * We need to take a new copy so that we don't alter the callers copy
+	 */
+	ret = ldb_build_mod_req(&new_req,
+				ldb,
+				req,
+				req->op.mod.message,
+				req->controls,
+				context,
+				audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+	return ldb_next_request(module, new_req);
+}
+
+static int log_start_transaction(struct ldb_module *module)
+{
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	/*
+	 * We do not log transaction begins
+	 *
+	 */
+	ac->transaction_guid = GUID_random();
+	return ldb_next_start_trans(module);
+}
+
+static int log_prepare_commit(struct ldb_module *module)
+{
+
+	int ret = ldb_next_prepare_commit(module);
+	if (ret != LDB_SUCCESS) {
+		/*
+		 * We currently only log prepare commit failures
+		 */
+		log_prepare_commit_failure(module, ret);
+	}
+	return ret;
+}
+
+static int log_end_transaction(struct ldb_module *module)
+{
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	/*
+	 * We do not currently log transaction commits
+	 */
+	memset(&ac->transaction_guid, 0, sizeof(struct GUID));
+
+	return ldb_next_end_trans(module);
+}
+
+static int log_del_transaction(struct ldb_module *module)
+{
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	log_transaction(module, "rollback");
+	memset(&ac->transaction_guid, 0, sizeof(struct GUID));
+	return ldb_next_del_trans(module);
+}
+
+static int log_init(struct ldb_module *module)
+{
+
+	struct ldb_context *ldb = ldb_module_get_ctx(module);
+	struct audit_context *context = NULL;
+	struct loadparm_context *lp_ctx
+		= talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+					struct loadparm_context);
+	struct tevent_context *ec = ldb_get_event_context(ldb);
+	bool sdb_events = false;
+	bool pwd_events = false;
+
+	context = talloc_zero(module, struct audit_context);
+	if (context == NULL) {
+		return ldb_module_oom(module);
+	}
+
+	if (lp_ctx != NULL) {
+		sdb_events = lpcfg_samdb_event_notification(lp_ctx);
+		pwd_events = lpcfg_password_event_notification(lp_ctx);
+	}
+	if (sdb_events || pwd_events) {
+		context->send_samdb_events = sdb_events;
+		context->send_password_events = pwd_events;
+		context->msg_ctx = imessaging_client_init(ec, lp_ctx, ec);
+	}
+
+	ldb_module_set_private(module, context);
+	return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_audit_log_module_ops = {
+	.name              = "audit_log",
+	.add		   = log_add,
+	.modify		   = log_modify,
+	.del		   = log_delete,
+	.init_context	   = log_init,
+	.start_transaction = log_start_transaction,
+	.prepare_commit    = log_prepare_commit,
+	.end_transaction   = log_end_transaction,
+	.del_transaction   = log_del_transaction,
+};
+
+int ldb_audit_log_module_init(const char *version)
+{
+	LDB_MODULE_CHECK_VERSION(version);
+	return ldb_register_module(&ldb_audit_log_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/audit_util.c b/source4/dsdb/samdb/ldb_modules/audit_util.c
new file mode 100644
index 0000000..c85661a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/audit_util.c
@@ -0,0 +1,470 @@
+/*
+   ldb database module utility library
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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/>.
+*/
+
+/*
+ * Common utility functions for SamDb audit logging.
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/dom_sid.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#define MAX_LENGTH 1024
+
+#define min(a, b) (((a)>(b))?(b):(a))
+
+static const char * const secret_attributes[] = {DSDB_SECRET_ATTRIBUTES, NULL};
+
+
+/*
+ * @brief Get the remote address from the ldb context.
+ *
+ * The remote address is stored in the ldb opaque value "remooteAddress"
+ * it is the responsibility of the higher level code to ensure that this
+ * value is set.
+ *
+ * @param ldb the ldb_context.
+ *
+ * @return the remote address if known, otherwise NULL.
+ */
+const struct tsocket_address *get_remote_address(
+	struct ldb_context *ldb)
+{
+	void *opaque_remote_address = NULL;
+	struct tsocket_address *remote_address;
+
+	opaque_remote_address = ldb_get_opaque(ldb,
+					       "remoteAddress");
+	if (opaque_remote_address == NULL) {
+		return NULL;
+	}
+
+	remote_address = talloc_get_type(opaque_remote_address,
+					 struct tsocket_address);
+	return remote_address;
+}
+
+/*
+ * @brief get the ldb error string.
+ *
+ * Get the ldb error string if set, otherwise get the generic error code
+ * for the status code.
+ *
+ * @param ldb the ldb_context.
+ * @param status the ldb_status code.
+ *
+ * @return a string descrfing the error.
+ */
+const char *get_ldb_error_string(
+	struct ldb_module *module,
+	int status)
+{
+	struct ldb_context *ldb = ldb_module_get_ctx(module);
+	const char *err_string = ldb_errstring(ldb);
+
+	if (err_string == NULL) {
+		return ldb_strerror(status);
+	}
+	return err_string;
+}
+
+/*
+ * @brief get the SID of the user performing the operation.
+ *
+ * Get the SID of the user performing the operation.
+ *
+ * @param module the ldb_module.
+ *
+ * @return the SID of the currently logged on user.
+ */
+const struct dom_sid *get_user_sid(const struct ldb_module *module)
+{
+	struct security_token *user_token = NULL;
+
+	/*
+	 * acl_user_token does not alter module so it's safe
+	 * to discard the const.
+	 */
+	user_token = acl_user_token(discard_const(module));
+	if (user_token == NULL) {
+		return NULL;
+	}
+	return &user_token->sids[0];
+
+}
+
+/*
+ * @brief get the session identifier GUID
+ *
+ * Get the GUID that uniquely identifies the current authenticated session.
+ *
+ * @param module the ldb_module.
+ *
+ * @return the unique session GUID
+ */
+const struct GUID *get_unique_session_token(const struct ldb_module *module)
+{
+	struct ldb_context *ldb = ldb_module_get_ctx(discard_const(module));
+	struct auth_session_info *session_info
+		= (struct auth_session_info *)ldb_get_opaque(
+			ldb,
+			"sessionInfo");
+	if(!session_info) {
+		return NULL;
+	}
+	return &session_info->unique_session_token;
+}
+
+/*
+ * @brief Get a printable string value for the remote host address.
+ *
+ * Get a printable string representation of the remote host, for display in the
+ * the audit logs.
+ *
+ * @param ldb the ldb context.
+ * @param mem_ctx the talloc memory context that will own the returned string.
+ *
+ * @return A string representation of the remote host address or "Unknown"
+ *
+ */
+char *get_remote_host(
+	struct ldb_context *ldb,
+	TALLOC_CTX *mem_ctx)
+{
+	const struct tsocket_address *remote_address;
+	char* remote_host = NULL;
+
+	remote_address = get_remote_address(ldb);
+	if (remote_address == NULL) {
+		remote_host = talloc_asprintf(mem_ctx, "Unknown");
+		return remote_host;
+	}
+
+	remote_host = tsocket_address_string(remote_address, mem_ctx);
+	return remote_host;
+}
+
+/*
+ * @brief get a printable representation of the primary DN.
+ *
+ * Get a printable representation of the primary DN. The primary DN is the
+ * DN of the object being added, deleted, modified or renamed.
+ *
+ * @param the ldb_request.
+ *
+ * @return a printable and linearized DN
+ */
+const char* get_primary_dn(
+	const struct ldb_request *request)
+{
+	struct ldb_dn *dn = NULL;
+	switch (request->operation) {
+	case LDB_ADD:
+		if (request->op.add.message != NULL) {
+			dn = request->op.add.message->dn;
+		}
+		break;
+	case LDB_MODIFY:
+		if (request->op.mod.message != NULL) {
+			dn = request->op.mod.message->dn;
+		}
+		break;
+	case LDB_DELETE:
+		dn = request->op.del.dn;
+		break;
+	case LDB_RENAME:
+		dn = request->op.rename.olddn;
+		break;
+	default:
+		dn = NULL;
+		break;
+	}
+	if (dn == NULL) {
+		return NULL;
+	}
+	return ldb_dn_get_linearized(dn);
+}
+
+/*
+ * @brief Get the ldb_message from a request.
+ *
+ * Get the ldb_message for the request, returns NULL is there is no
+ * associated ldb_message
+ *
+ * @param The request
+ *
+ * @return the message associated with this request, or NULL
+ */
+const struct ldb_message *get_message(
+	const struct ldb_request *request)
+{
+	switch (request->operation) {
+	case LDB_ADD:
+		return request->op.add.message;
+	case LDB_MODIFY:
+		return request->op.mod.message;
+	default:
+		return NULL;
+	}
+}
+
+/*
+ * @brief get the secondary dn, i.e. the target dn for a rename.
+ *
+ * Get the secondary dn, i.e. the target for a rename. This is only applicable
+ * got a rename operation, for the non rename operations this function returns
+ * NULL.
+ *
+ * @param request the ldb_request.
+ *
+ * @return the secondary dn in a printable and linearized form.
+ */
+const char *get_secondary_dn(
+	const struct ldb_request *request)
+{
+	switch (request->operation) {
+	case LDB_RENAME:
+		return ldb_dn_get_linearized(request->op.rename.newdn);
+	default:
+		return NULL;
+	}
+}
+
+/*
+ * @brief Map the request operation to a description.
+ *
+ * Get a description of the operation for logging
+ *
+ * @param request the ldb_request
+ *
+ * @return a string describing the operation, or "Unknown" if the operation
+ *         is not known.
+ */
+const char *get_operation_name(
+	const struct ldb_request *request)
+{
+	switch (request->operation) {
+	case LDB_SEARCH:
+		return "Search";
+	case LDB_ADD:
+		return "Add";
+	case LDB_MODIFY:
+		return "Modify";
+	case LDB_DELETE:
+		return "Delete";
+	case LDB_RENAME:
+		return "Rename";
+	case LDB_EXTENDED:
+		return "Extended";
+	case LDB_REQ_REGISTER_CONTROL:
+		return "Register Control";
+	case LDB_REQ_REGISTER_PARTITION:
+		return "Register Partition";
+	default:
+		return "Unknown";
+	}
+}
+
+/*
+ * @brief get a description of a modify action for logging.
+ *
+ * Get a brief description of the modification action suitable for logging.
+ *
+ * @param flags the ldb_attributes flags.
+ *
+ * @return a brief description, or "unknown".
+ */
+const char *get_modification_action(
+	unsigned int flags)
+{
+	switch (LDB_FLAG_MOD_TYPE(flags)) {
+	case LDB_FLAG_MOD_ADD:
+		return "add";
+	case LDB_FLAG_MOD_DELETE:
+		return "delete";
+	case LDB_FLAG_MOD_REPLACE:
+		return "replace";
+	default:
+		return "unknown";
+	}
+}
+
+/*
+ * @brief Add an ldb_value to a json object array
+ *
+ * Convert the current ldb_value to a JSON object and append it to array.
+ * {
+ *	"value":"xxxxxxxx",
+ *	"base64":true
+ *	"truncated":true
+ * }
+ *
+ * value     is the JSON string representation of the ldb_val,
+ *           will be null if the value is zero length. The value will be
+ *           truncated if it is more than MAX_LENGTH bytes long. It will also
+ *           be base64 encoded if it contains any non printable characters.
+ *
+ * base64    Indicates that the value is base64 encoded, will be absent if the
+ *           value is not encoded.
+ *
+ * truncated Indicates that the length of the value exceeded MAX_LENGTH and was
+ *           truncated.  Note that vales are truncated and then base64 encoded.
+ *           so an encoded value can be longer than MAX_LENGTH.
+ *
+ * @param array the JSON array to append the value to.
+ * @param lv the ldb_val to convert and append to the array.
+ *
+ */
+static void add_ldb_value(
+	struct json_object *array,
+	const struct ldb_val lv)
+{
+
+	json_assert_is_array(array);
+	if (json_is_invalid(array)) {
+		return;
+	}
+
+	if (lv.length == 0 || lv.data == NULL) {
+		json_add_object(array, NULL, NULL);
+		return;
+	}
+
+	bool base64 = ldb_should_b64_encode(NULL, &lv);
+	int len = min(lv.length, MAX_LENGTH);
+	struct json_object value = json_new_object();
+	if (lv.length > MAX_LENGTH) {
+		json_add_bool(&value, "truncated", true);
+	}
+	if (base64) {
+		TALLOC_CTX *ctx = talloc_new(NULL);
+		char *encoded = ldb_base64_encode(
+			ctx,
+			(char*) lv.data,
+			len);
+
+		json_add_bool(&value, "base64", true);
+		json_add_string(&value, "value", encoded);
+		TALLOC_FREE(ctx);
+	} else {
+		json_add_stringn(&value, "value", (char *)lv.data, len);
+	}
+	/*
+	 * As array is a JSON array the element name is NULL
+	 */
+	json_add_object(array, NULL, &value);
+}
+
+/*
+ * @brief Build a JSON object containing the attributes in an ldb_message.
+ *
+ * Build a JSON object containing all the attributes in an ldb_message.
+ * The attributes are keyed by attribute name, the values of "secret attributes"
+ * are supressed.
+ *
+ * {
+ * 	"password":{
+ * 		"redacted":true,
+ * 		"action":"delete"
+ * 	},
+ * 	"name":{
+ * 		"values": [
+ * 			{
+ *				"value":"xxxxxxxx",
+ *				"base64":true
+ *				"truncated":true
+ *			},
+ * 		],
+ * 		"action":"add",
+ * 	}
+ * }
+ *
+ * values is an array of json objects generated by add_ldb_value.
+ * redacted indicates that the attribute is secret.
+ * action is only set for modification operations.
+ *
+ * @param operation the ldb operation being performed
+ * @param message the ldb_message to process.
+ *
+ * @return A populated json object.
+ *
+ */
+struct json_object attributes_json(
+	enum ldb_request_type operation,
+	const struct ldb_message* message)
+{
+
+	struct json_object attributes = json_new_object();
+	int i, j;
+	for (i=0;i<message->num_elements;i++) {
+		struct json_object values;
+		struct json_object attribute = json_new_object();
+
+		/*
+		 * If this is a modify operation tag the attribute with
+		 * the modification action.
+		 */
+		if (operation == LDB_MODIFY) {
+			const char *action = NULL;
+			const int flags =  message->elements[i].flags;
+			action = get_modification_action(flags);
+			json_add_string(&attribute, "action" , action);
+		}
+
+		/*
+		 * If the attribute is a secret attribute, tag it as redacted
+		 * and don't include the values
+		 */
+		bool redact = ldb_attr_in_list(
+			secret_attributes,
+			message->elements[i].name);
+		if (redact) {
+			json_add_bool(&attribute, "redacted", true);
+			json_add_object(
+				&attributes,
+				message->elements[i].name,
+				&attribute);
+			continue;
+		}
+
+		/*
+		 * Add the values for the object
+		 */
+		values = json_new_array();
+		for (j=0;j<message->elements[i].num_values;j++) {
+			add_ldb_value(
+				&values,
+				message->elements[i].values[j]);
+		}
+		json_add_object(&attribute, "values", &values);
+		json_add_object(
+			&attributes,
+			message->elements[i].name,
+			&attribute);
+	}
+	return attributes;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
index 54ec6a2..baa30f9 100644
--- a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
+++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
@@ -292,7 +292,8 @@ static int samba_dsdb_init(struct ldb_module *module)
 					     "extended_dn_store",
 					     NULL };
 	/* extended_dn_in or extended_dn_in_openldap goes here */
-	static const char *modules_list1a[] = {"objectclass",
+	static const char *modules_list1a[] = {"audit_log",
+					     "objectclass",
 					     "tombstone_reanimate",
 					     "descriptor",
 					     "acl",
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c
new file mode 100644
index 0000000..7948322
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c
@@ -0,0 +1,468 @@
+/*
+   Unit tests for the dsdb audit logging code code in audit_log.c
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_audit_log_module_init(const char *version);
+#include "../audit_log.c"
+
+#include "lib/ldb/include/ldb_private.h"
+
+static void test_has_password_changed(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	struct ldb_message *msg = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+
+	/*
+	 * Empty message
+	 */
+	msg = ldb_msg_new(ldb);
+	assert_false(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 * No password attributes
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "attr01", "value01");
+	assert_false(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 * No password attributes >1 entries
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "attr01", "value01");
+	ldb_msg_add_string(msg, "attr02", "value03");
+	ldb_msg_add_string(msg, "attr03", "value03");
+	assert_false(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  userPassword set
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "userPassword", "value01");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  clearTextPassword set
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "clearTextPassword", "value01");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  unicodePwd set
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "unicodePwd", "value01");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  dBCSPwd set
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "dBCSPwd", "value01");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  All attributes set
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "userPassword", "value01");
+	ldb_msg_add_string(msg, "clearTextPassword", "value02");
+	ldb_msg_add_string(msg, "unicodePwd", "value03");
+	ldb_msg_add_string(msg, "dBCSPwd", "value04");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  first attribute is a password attribute
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "userPassword", "value01");
+	ldb_msg_add_string(msg, "attr02", "value02");
+	ldb_msg_add_string(msg, "attr03", "value03");
+	ldb_msg_add_string(msg, "attr04", "value04");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  last attribute is a password attribute
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "attr01", "value01");
+	ldb_msg_add_string(msg, "attr02", "value02");
+	ldb_msg_add_string(msg, "attr03", "value03");
+	ldb_msg_add_string(msg, "clearTextPassword", "value04");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	/*
+	 *  middle attribute is a password attribute
+	 */
+	msg = ldb_msg_new(ldb);
+	ldb_msg_add_string(msg, "attr01", "value01");
+	ldb_msg_add_string(msg, "attr02", "value02");
+	ldb_msg_add_string(msg, "unicodePwd", "pwd");
+	ldb_msg_add_string(msg, "attr03", "value03");
+	ldb_msg_add_string(msg, "attr04", "value04");
+	assert_true(has_password_changed(msg));
+	TALLOC_FREE(msg);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_password_action(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	struct ldb_request *req = NULL;
+	struct dsdb_control_password_acl_validation *pav = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	ldb = talloc_zero(ctx, struct ldb_context);
+
+	/*
+	 * Add request, will always be a reset
+	 */
+	ldb_build_add_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+	assert_string_equal("Reset", get_password_action(req));
+	TALLOC_FREE(req);
+
+	/*
+	 * No password control acl, expect "Change"
+	 */
+	ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+	assert_string_equal("Change", get_password_action(req));
+	TALLOC_FREE(req);
+
+	/*
+	 * dsdb_control_password_acl_validation reset = false, expect "Change"
+	 */
+	ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+	pav = talloc_zero(req, struct dsdb_control_password_acl_validation);
+
+	ldb_request_add_control(
+		req,
+		DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID,
+		false,
+		pav);
+	assert_string_equal("Change", get_password_action(req));
+	TALLOC_FREE(req);
+
+	/*
+	 * dsdb_control_password_acl_validation reset = true, expect "Reset"
+	 */
+	ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+	pav = talloc_zero(req, struct dsdb_control_password_acl_validation);
+	pav->pwd_reset = true;
+
+	ldb_request_add_control(
+		req,
+		DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID,
+		false,
+		pav);
+	assert_string_equal("Reset", get_password_action(req));
+	TALLOC_FREE(req);
+
+	TALLOC_FREE(ctx);
+}
+
+/*
+ * minimal unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_operation_json_empty(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	struct ldb_module  *module = NULL;
+	struct ldb_request *req = NULL;
+	struct audit_context *ac = NULL;
+
+	struct json_object json;
+	json_t *audit = NULL;
+	json_t *v = NULL;
+	json_t *o = NULL;
+
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+	ac = talloc_zero(ctx, struct audit_context);
+
+	module = talloc_zero(ctx, struct ldb_module);
+	module->ldb = ldb;
+	ldb_module_set_private(module, ac);
+
+	req = talloc_zero(ctx, struct ldb_request);
+
+	json = operation_json(module, req, LDB_SUCCESS);
+	assert_int_equal(3, json_object_size(json.root));
+
+
+	v = json_object_get(json.root, "type");
+	assert_non_null(v);
+	assert_string_equal("samdbChange", json_string_value(v));
+
+	v = json_object_get(json.root, "timestamp");
+	assert_non_null(v);
+	assert_true(json_is_string(v));
+
+	audit = json_object_get(json.root, "samdbChange");
+	assert_non_null(audit);
+	assert_true(json_is_object(audit));
+	assert_int_equal(9, json_object_size(audit));
+
+	o = json_object_get(audit, "version");
+	assert_non_null(o);
+
+	v = json_object_get(audit, "statusCode");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "status");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "operation");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "remoteAddress");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "userSid");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "dn");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "transactionId");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "sessionId");
+	assert_non_null(v);
+
+	json_free(&json);
+	TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of password_change_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_password_change_json_empty(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	struct ldb_module  *module = NULL;
+	struct ldb_request *req = NULL;
+	struct audit_context *ac = NULL;
+
+	struct json_object json;
+	json_t *audit = NULL;
+	json_t *v = NULL;
+	json_t *o = NULL;
+
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+	ac = talloc_zero(ctx, struct audit_context);
+
+	module = talloc_zero(ctx, struct ldb_module);
+	module->ldb = ldb;
+	ldb_module_set_private(module, ac);
+
+	req = talloc_zero(ctx, struct ldb_request);
+
+	json = password_change_json(module, req, LDB_SUCCESS);
+	assert_int_equal(3, json_object_size(json.root));
+
+
+	v = json_object_get(json.root, "type");
+	assert_non_null(v);
+	assert_string_equal("passwordChange", json_string_value(v));
+
+	v = json_object_get(json.root, "timestamp");
+	assert_non_null(v);
+	assert_true(json_is_string(v));
+
+	audit = json_object_get(json.root, "passwordChange");
+	assert_non_null(audit);
+	assert_true(json_is_object(audit));
+	assert_int_equal(9, json_object_size(audit));
+
+	o = json_object_get(audit, "version");
+	assert_non_null(o);
+
+	v = json_object_get(audit, "statusCode");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "status");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "remoteAddress");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "userSid");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "dn");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "transactionId");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "sessionId");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "action");
+	assert_non_null(v);
+
+	json_free(&json);
+	TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of transaction_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_transaction_json_empty(void **state)
+{
+
+	struct GUID guid;
+	const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+	struct json_object json;
+	json_t *audit = NULL;
+	json_t *v = NULL;
+	json_t *o = NULL;
+
+	GUID_from_string(GUID, &guid);
+	json = transaction_json("delete", &guid);
+
+	assert_int_equal(3, json_object_size(json.root));
+
+
+	v = json_object_get(json.root, "type");
+	assert_non_null(v);
+	assert_string_equal("samdbTransaction", json_string_value(v));
+
+	v = json_object_get(json.root, "timestamp");
+	assert_non_null(v);
+	assert_true(json_is_string(v));
+
+	audit = json_object_get(json.root, "samdbTransaction");
+	assert_non_null(audit);
+	assert_true(json_is_object(audit));
+	assert_int_equal(3, json_object_size(audit));
+
+	o = json_object_get(audit, "version");
+	assert_non_null(o);
+
+	v = json_object_get(audit, "transactionId");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "action");
+	assert_non_null(v);
+
+	json_free(&json);
+
+}
+
+/*
+ * minimal unit test of prepare_commit_failure_json, that ensures that all the
+ * expected attributes and objects are in the json object.
+ */
+static void test_prepare_commit_failure_json_empty(void **state)
+{
+
+	struct GUID guid;
+	const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+	struct json_object json;
+	json_t *audit = NULL;
+	json_t *v = NULL;
+	json_t *o = NULL;
+
+	GUID_from_string(GUID, &guid);
+	json = prepare_commit_failure_json(LDB_SUCCESS, "because", &guid);
+
+	assert_int_equal(3, json_object_size(json.root));
+
+
+	v = json_object_get(json.root, "type");
+	assert_non_null(v);
+	assert_string_equal("samdbTransaction", json_string_value(v));
+
+	v = json_object_get(json.root, "timestamp");
+	assert_non_null(v);
+	assert_true(json_is_string(v));
+
+	audit = json_object_get(json.root, "samdbTransaction");
+	assert_non_null(audit);
+	assert_true(json_is_object(audit));
+	assert_int_equal(6, json_object_size(audit));
+
+	o = json_object_get(audit, "version");
+	assert_non_null(o);
+
+	v = json_object_get(audit, "transactionId");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "action");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "status");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "statusCode");
+	assert_non_null(v);
+
+	v = json_object_get(audit, "reason");
+	assert_non_null(v);
+
+	json_free(&json);
+
+}
+int main(void) {
+	const struct CMUnitTest tests[] = {
+		cmocka_unit_test(test_has_password_changed),
+		cmocka_unit_test(test_get_password_action),
+		cmocka_unit_test(test_operation_json_empty),
+		cmocka_unit_test(test_password_change_json_empty),
+		cmocka_unit_test(test_transaction_json_empty),
+		cmocka_unit_test(test_prepare_commit_failure_json_empty),
+	};
+
+	cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+	return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c
new file mode 100644
index 0000000..5ed4cd6
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c
@@ -0,0 +1,958 @@
+/*
+   Unit tests for the dsdb audit logging utility code code in audit_util.c
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+#include "../audit_util.c"
+
+#include "lib/ldb/include/ldb_private.h"
+
+#ifdef HAVE_JANSSON
+static void test_add_ldb_value(void **state)
+{
+	struct json_object object;
+	struct json_object array;
+	struct ldb_val val = data_blob_null;
+	struct json_t *el  = NULL;
+	struct json_t *atr = NULL;
+	char* base64 = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	/*
+	 * Test a non array object
+	 */
+	object = json_new_object();
+	assert_false(json_is_invalid(&object));
+	add_ldb_value(&object, val);
+	assert_true(json_is_invalid(&object));
+	json_free(&object);
+
+	array = json_new_array();
+	/*
+	 * Test a data_blob_null, should encode as a JSON null value.
+	 */
+	val = data_blob_null;
+	add_ldb_value(&array, val);
+	el = json_array_get(array.root, 0);
+	assert_true(json_is_null(el));
+
+	/*
+	 * Test a +ve length but a null data ptr, should encode as a null.
+	 */
+	val = data_blob_null;
+	val.length = 1;
+	add_ldb_value(&array, val);
+	el = json_array_get(array.root, 1);
+	assert_true(json_is_null(el));
+
+	/*
+	 * Test a zero length but a non null data ptr, should encode as a null.
+	 */
+	val = data_blob_null;
+	val.data = discard_const("Data on the stack");
+	add_ldb_value(&array, val);
+	el = json_array_get(array.root, 2);
+	assert_true(json_is_null(el));
+
+	/*
+	 * Test a printable value.
+	 * value should not be encoded
+	 * truncated and base64 should be missing
+	 */
+	val = data_blob_string_const("A value of interest");
+	add_ldb_value(&array, val);
+	el = json_array_get(array.root, 3);
+	assert_true(json_is_object(el));
+	atr = json_object_get(el, "value");
+	assert_true(json_is_string(atr));
+	assert_string_equal("A value of interest", json_string_value(atr));
+	assert_null(json_object_get(el, "truncated"));
+	assert_null(json_object_get(el, "base64"));
+
+	/*
+	 * Test non printable value, should be base64 encoded.
+	 * truncated should be missing and base64 should be set.
+	 */
+	val = data_blob_string_const("A value of interest\n");
+	add_ldb_value(&array, val);
+	el = json_array_get(array.root, 4);
+	assert_true(json_is_object(el));
+	atr = json_object_get(el, "value");
+	assert_true(json_is_string(atr));
+	assert_string_equal(
+		"QSB2YWx1ZSBvZiBpbnRlcmVzdAo=",
+		json_string_value(atr));
+	atr = json_object_get(el, "base64");
+	assert_true(json_is_boolean(atr));
+	assert_true(json_boolean(atr));
+	assert_null(json_object_get(el, "truncated"));
+
+	/*
+	 * test a printable value exactly max bytes long
+	 * should not be truncated or encoded.
+	 */
+	val = data_blob_null;
+	val.length = MAX_LENGTH;
+	val.data = (unsigned char *)generate_random_str_list(
+		ctx,
+		MAX_LENGTH,
+		"abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"1234567890!@#$%^&*()");
+
+	add_ldb_value(&array, val);
+
+	el = json_array_get(array.root, 5);
+	assert_true(json_is_object(el));
+	atr = json_object_get(el, "value");
+	assert_true(json_is_string(atr));
+	assert_int_equal(MAX_LENGTH, strlen(json_string_value(atr)));
+	assert_memory_equal(val.data, json_string_value(atr), MAX_LENGTH);
+
+	assert_null(json_object_get(el, "base64"));
+	assert_null(json_object_get(el, "truncated"));
+
+
+	/*
+	 * test a printable value exactly max + 1 bytes long
+	 * should be truncated and not encoded.
+	 */
+	val = data_blob_null;
+	val.length = MAX_LENGTH + 1;
+	val.data = (unsigned char *)generate_random_str_list(
+		ctx,
+		MAX_LENGTH + 1,
+		"abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"1234567890!@#$%^&*()");
+
+	add_ldb_value(&array, val);
+
+	el = json_array_get(array.root, 6);
+	assert_true(json_is_object(el));
+	atr = json_object_get(el, "value");
+	assert_true(json_is_string(atr));
+	assert_int_equal(MAX_LENGTH, strlen(json_string_value(atr)));
+	assert_memory_equal(val.data, json_string_value(atr), MAX_LENGTH);
+
+	atr = json_object_get(el, "truncated");
+	assert_true(json_is_boolean(atr));
+	assert_true(json_boolean(atr));
+
+	assert_null(json_object_get(el, "base64"));
+
+	TALLOC_FREE(val.data);
+
+	/*
+	 * test a non-printable value exactly max bytes long
+	 * should not be truncated but should be encoded.
+	 */
+	val = data_blob_null;
+	val.length = MAX_LENGTH;
+	val.data = (unsigned char *)generate_random_str_list(
+		ctx,
+		MAX_LENGTH,
+		"abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"1234567890!@#$%^&*()");
+
+	val.data[0] = 0x03;
+	add_ldb_value(&array, val);
+	base64 = ldb_base64_encode(ctx, (char*) val.data, MAX_LENGTH);
+
+	el = json_array_get(array.root, 7);
+	assert_true(json_is_object(el));
+	atr = json_object_get(el, "value");
+	assert_true(json_is_string(atr));
+	assert_int_equal(strlen(base64), strlen(json_string_value(atr)));
+	assert_string_equal(base64, json_string_value(atr));
+
+	atr = json_object_get(el, "base64");
+	assert_true(json_is_boolean(atr));
+	assert_true(json_boolean(atr));
+
+	assert_null(json_object_get(el, "truncated"));
+	TALLOC_FREE(base64);
+	TALLOC_FREE(val.data);
+
+	/*
+	 * test a non-printable value exactly max + 1 bytes long
+	 * should be truncated and encoded.
+	 */
+	val = data_blob_null;
+	val.length = MAX_LENGTH + 1;
+	val.data = (unsigned char *)generate_random_str_list(
+		ctx,
+		MAX_LENGTH + 1,
+		"abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"1234567890!@#$%^&*()");
+
+	val.data[0] = 0x03;
+	add_ldb_value(&array, val);
+	/*
+	 * The data is truncated before it is base 64 encoded
+	 */
+	base64 = ldb_base64_encode(ctx, (char*) val.data, MAX_LENGTH);
+
+	el = json_array_get(array.root, 8);
+	assert_true(json_is_object(el));
+	atr = json_object_get(el, "value");
+	assert_true(json_is_string(atr));
+	assert_int_equal(strlen(base64), strlen(json_string_value(atr)));
+	assert_string_equal(base64, json_string_value(atr));
+
+	atr = json_object_get(el, "base64");
+	assert_true(json_is_boolean(atr));
+	assert_true(json_boolean(atr));
+
+	atr = json_object_get(el, "truncated");
+	assert_true(json_is_boolean(atr));
+	assert_true(json_boolean(atr));
+
+	TALLOC_FREE(base64);
+	TALLOC_FREE(val.data);
+
+	json_free(&array);
+	TALLOC_FREE(ctx);
+}
+
+static void test_attributes_json(void **state)
+{
+	struct ldb_message *msg = NULL;
+
+	struct json_object o;
+	json_t *a = NULL;
+	json_t *v = NULL;
+	json_t *x = NULL;
+	json_t *y = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+
+	/*
+	 * Test an empty message
+	 * Should get an empty attributes object
+	 */
+	msg = talloc_zero(ctx, struct ldb_message);
+
+	o = attributes_json(LDB_ADD, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(0, json_object_size(o.root));
+	json_free(&o);
+
+	o = attributes_json(LDB_MODIFY, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(0, json_object_size(o.root));
+	json_free(&o);
+
+	/*
+	 * Test a message with a single secret attribute
+	 * should only have that object and it should have no value
+	 * attribute and redacted should be set.
+	 */
+	msg = talloc_zero(ctx, struct ldb_message);
+	ldb_msg_add_string(msg, "clearTextPassword", "secret");
+
+	o = attributes_json(LDB_ADD, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(1, json_object_size(o.root));
+
+	a = json_object_get(o.root, "clearTextPassword");
+	assert_int_equal(1, json_object_size(a));
+	v = json_object_get(a, "redacted");
+	assert_true(json_is_boolean(v));
+	assert_true(json_boolean(v));
+
+	json_free(&o);
+
+	/*
+	 * Test as a modify message, should add an action attribute
+	 */
+	o = attributes_json(LDB_MODIFY, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(1, json_object_size(o.root));
+
+	a = json_object_get(o.root, "clearTextPassword");
+	assert_true(json_is_object(a));
+	assert_int_equal(2, json_object_size(a));
+
+	v = json_object_get(a, "redacted");
+	assert_true(json_is_boolean(v));
+	assert_true(json_boolean(v));
+
+	v = json_object_get(a, "action");
+	assert_true(json_is_string(v));
+	assert_string_equal("unknown", json_string_value(v));
+
+	json_free(&o);
+	TALLOC_FREE(msg);
+
+	/*
+	 * Test a message with a single attribute, single valued attribute
+	 */
+	msg = talloc_zero(ctx, struct ldb_message);
+	ldb_msg_add_string(msg, "attribute", "value");
+
+	o = attributes_json(LDB_ADD, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(1, json_object_size(o.root));
+
+	a = json_object_get(o.root, "attribute");
+	assert_true(json_is_object(a));
+	assert_int_equal(1, json_object_size(a));
+
+	v = json_object_get(a, "values");
+	assert_true(json_is_array(v));
+	assert_int_equal(1, json_array_size(v));
+	x = json_array_get(v, 0);
+	assert_true(json_is_object(x));
+	y = json_object_get(x, "value");
+	assert_string_equal("value", json_string_value(y));
+
+	json_free(&o);
+	TALLOC_FREE(msg);
+
+	/*
+	 * Test a message with a single attribute, single valued attribute
+	 * And as a modify
+	 */
+	msg = talloc_zero(ctx, struct ldb_message);
+	ldb_msg_add_string(msg, "attribute", "value");
+
+	o = attributes_json(LDB_MODIFY, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(1, json_object_size(o.root));
+
+	a = json_object_get(o.root, "attribute");
+	assert_true(json_is_object(a));
+	assert_int_equal(2, json_object_size(a));
+
+	v = json_object_get(a, "action");
+	assert_true(json_is_string(v));
+	assert_string_equal("unknown", json_string_value(v));
+
+	v = json_object_get(a, "values");
+	assert_true(json_is_array(v));
+	assert_int_equal(1, json_array_size(v));
+	x = json_array_get(v, 0);
+	assert_true(json_is_object(x));
+	y = json_object_get(x, "value");
+	assert_string_equal("value", json_string_value(y));
+
+	json_free(&o);
+	TALLOC_FREE(msg);
+
+	/*
+	 * Test a message with a single attribute, single valued attribute
+	 */
+	msg = talloc_zero(ctx, struct ldb_message);
+	ldb_msg_add_string(msg, "attribute01", "value01");
+	ldb_msg_add_string(msg, "attribute02", "value02");
+	ldb_msg_add_string(msg, "attribute02", "value03");
+
+	o = attributes_json(LDB_ADD, msg);
+	assert_true(json_is_object(o.root));
+	assert_int_equal(2, json_object_size(o.root));
+
+	a = json_object_get(o.root, "attribute01");
+	assert_true(json_is_object(a));
+	assert_int_equal(1, json_object_size(a));
+	v = json_object_get(a, "values");
+	assert_true(json_is_array(v));
+	assert_int_equal(1, json_array_size(v));
+	x = json_array_get(v, 0);
+	assert_true(json_is_object(x));
+	y = json_object_get(x, "value");
+	assert_string_equal("value01", json_string_value(y));
+
+	a = json_object_get(o.root, "attribute02");
+	assert_true(json_is_object(a));
+	assert_int_equal(1, json_object_size(a));
+	v = json_object_get(a, "values");
+	assert_true(json_is_array(v));
+	assert_int_equal(2, json_array_size(v));
+	x = json_array_get(v, 0);
+	assert_true(json_is_object(x));
+	y = json_object_get(x, "value");
+	assert_string_equal("value02", json_string_value(y));
+	x = json_array_get(v, 1);
+	assert_true(json_is_object(x));
+	y = json_object_get(x, "value");
+	assert_string_equal("value03", json_string_value(y));
+
+	json_free(&o);
+	TALLOC_FREE(msg);
+
+	TALLOC_FREE(ctx);
+}
+#endif
+
+static void test_get_remote_address(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	const struct tsocket_address *ts = NULL;
+	struct tsocket_address *in = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	/*
+	 * Test a freshly initialized ldb
+	 * should return NULL
+	 */
+	ldb = talloc_zero(ctx, struct ldb_context);
+	ts = get_remote_address(ldb);
+	assert_null(ts);
+
+	/*
+	 * opaque set to null, should return NULL
+	 */
+	ldb_set_opaque(ldb, "remoteAddress", NULL);
+	ts = get_remote_address(ldb);
+	assert_null(ts);
+
+	/*
+	 * Ensure that the value set is returned
+	 */
+	tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &in);
+	ldb_set_opaque(ldb, "remoteAddress", in);
+	ts = get_remote_address(ldb);
+	assert_non_null(ts);
+	assert_ptr_equal(in, ts);
+
+	TALLOC_FREE(ldb);
+	TALLOC_FREE(ctx);
+
+}
+
+static void test_get_ldb_error_string(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	struct ldb_module *module = NULL;
+	const char *s = NULL;
+	const char * const text = "Custom reason";
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+	module = talloc_zero(ctx, struct ldb_module);
+	module->ldb = ldb;
+
+	/*
+	 * No ldb error string set should get the default error description for
+	 * the status code
+	 */
+	s = get_ldb_error_string(module, LDB_ERR_OPERATIONS_ERROR);
+	assert_string_equal("Operations error", s);
+
+	/*
+	 * Set the error string that should now be returned instead of the
+	 * default description.
+	 */
+	ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, text);
+	s = get_ldb_error_string(module, LDB_ERR_OPERATIONS_ERROR);
+	/*
+	 * Only test the start of the string as ldb_error adds location data.
+	 */
+	assert_int_equal(0, strncmp(text, s, strlen(text)));
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_user_sid(void **state)
+{
+	struct ldb_context *ldb        = NULL;
+	struct ldb_module *module      = NULL;
+	const struct dom_sid *sid      = NULL;
+	struct auth_session_info *sess = NULL;
+	struct security_token *token   = NULL;
+	struct dom_sid sid0;
+	struct dom_sid sid1;
+	struct dom_sid *sids[] = {&sid0, &sid1};
+	const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779";
+	const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761";
+	char sid_buf[DOM_SID_STR_BUFLEN];
+
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+	module = talloc_zero(ctx, struct ldb_module);
+	module->ldb = ldb;
+
+	/*
+	 * Freshly initialised structures, will be no session data
+	 * so expect NULL
+	 */
+	sid = get_user_sid(module);
+	assert_null(sid);
+
+	/*
+	 * Now add a NULL session info
+	 */
+	ldb_set_opaque(ldb, "sessionInfo", NULL);
+	sid = get_user_sid(module);
+	assert_null(sid);
+
+	/*
+	 * Now add a session info with no user sid
+	 */
+	sess = talloc_zero(ctx, struct auth_session_info);
+	ldb_set_opaque(ldb, "sessionInfo", sess);
+	sid = get_user_sid(module);
+	assert_null(sid);
+
+	/*
+	 * Now add an empty security token.
+	 */
+	token = talloc_zero(ctx, struct security_token);
+	sess->security_token = token;
+	sid = get_user_sid(module);
+	assert_null(sid);
+
+	/*
+	 * Add a single SID
+	 */
+	string_to_sid(&sid0, SID0);
+	token->num_sids = 1;
+	token->sids = sids[0];
+	sid = get_user_sid(module);
+	assert_non_null(sid);
+	dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
+	assert_string_equal(SID0, sid_buf);
+
+	/*
+	 * Add a second SID, should still use the first SID
+	 */
+	string_to_sid(&sid1, SID1);
+	token->num_sids = 2;
+	sid = get_user_sid(module);
+	assert_non_null(sid);
+	dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
+	assert_string_equal(SID0, sid_buf);
+
+
+	/*
+	 * Now test a null sid in the first position
+	 */
+	token->num_sids = 1;
+	token->sids = NULL;
+	sid = get_user_sid(module);
+	assert_null(sid);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_unique_session_token(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	struct ldb_module *module = NULL;
+	struct auth_session_info *sess = NULL;
+	const struct GUID *guid;
+	const char * const GUID_S = "7130cb06-2062-6a1b-409e-3514c26b1773";
+	struct GUID in;
+	char *guid_str;
+	struct GUID_txt_buf guid_buff;
+
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+	module = talloc_zero(ctx, struct ldb_module);
+	module->ldb = ldb;
+
+	/*
+	 * Test a freshly initialized ldb
+	 * should return NULL
+	 */
+	guid = get_unique_session_token(module);
+	assert_null(guid);
+
+	/*
+	 * Now add a NULL session info
+	 */
+	ldb_set_opaque(ldb, "sessionInfo", NULL);
+	guid = get_unique_session_token(module);
+	assert_null(guid);
+
+	/*
+	 * Now add a session info with no session id
+	 * Note if the memory has not been zeroed correctly all bets are
+	 *      probably off.
+	 */
+	sess = talloc_zero(ctx, struct auth_session_info);
+	ldb_set_opaque(ldb, "sessionInfo", sess);
+	guid = get_unique_session_token(module);
+	/*
+	 * We will get a GUID, but it's contents will be undefined
+	 */
+	assert_non_null(guid);
+
+	/*
+	 * Now set the session id and confirm that we get it back.
+	 */
+	GUID_from_string(GUID_S, &in);
+	sess->unique_session_token = in;
+	guid = get_unique_session_token(module);
+	assert_non_null(guid);
+	guid_str = GUID_buf_string(guid, &guid_buff);
+	assert_string_equal(GUID_S, guid_str);
+
+	TALLOC_FREE(ctx);
+
+}
+
+static void test_get_remote_host(void **state)
+{
+	struct ldb_context *ldb = NULL;
+	char *rh = NULL;
+	struct tsocket_address *in = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = talloc_zero(ctx, struct ldb_context);
+
+	/*
+	 * Test a freshly initialized ldb
+	 * should return "Unknown"
+	 */
+	rh = get_remote_host(ldb, ctx);
+	assert_string_equal("Unknown", rh);
+	TALLOC_FREE(rh);
+
+	/*
+	 * opaque set to null, should return NULL
+	 */
+	ldb_set_opaque(ldb, "remoteAddress", NULL);
+	rh = get_remote_host(ldb, ctx);
+	assert_string_equal("Unknown", rh);
+	TALLOC_FREE(rh);
+
+	/*
+	 * Ensure that the value set is returned
+	 */
+	tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 42, &in);
+	ldb_set_opaque(ldb, "remoteAddress", in);
+	rh = get_remote_host(ldb, ctx);
+	assert_string_equal("ipv4:127.0.0.1:42", rh);
+	TALLOC_FREE(rh);
+
+	TALLOC_FREE(ctx);
+
+}
+
+static void test_get_primary_dn(void **state)
+{
+	struct ldb_request *req = NULL;
+	struct ldb_message *msg = NULL;
+	struct ldb_context *ldb = NULL;
+
+	struct ldb_dn *dn = NULL;
+
+	const char * const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+	const char *s = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	req = talloc_zero(ctx, struct ldb_request);
+	msg = talloc_zero(ctx, struct ldb_message);
+	ldb = talloc_zero(ctx, struct ldb_context);
+	dn = ldb_dn_new(ctx, ldb, DN);
+
+	/*
+	 * Try an empty request.
+	 */
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Now try an add with a null message.
+	 */
+	req->operation = LDB_ADD;
+	req->op.add.message = NULL;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Now try an mod with a null message.
+	 */
+	req->operation = LDB_MODIFY;
+	req->op.mod.message = NULL;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Now try an add with a missing dn
+	 */
+	req->operation = LDB_ADD;
+	req->op.add.message = msg;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Now try a mod with a messing dn
+	 */
+	req->operation = LDB_ADD;
+	req->op.mod.message = msg;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Add a dn to the message
+	 */
+	msg->dn = dn;
+
+	/*
+	 * Now try an add with a dn
+	 */
+	req->operation = LDB_ADD;
+	req->op.add.message = msg;
+	s = get_primary_dn(req);
+	assert_non_null(s);
+	assert_string_equal(DN, s);
+
+	/*
+	 * Now try a mod with a dn
+	 */
+	req->operation = LDB_MODIFY;
+	req->op.mod.message = msg;
+	s = get_primary_dn(req);
+	assert_non_null(s);
+	assert_string_equal(DN, s);
+
+	/*
+	 * Try a delete without a dn
+	 */
+	req->operation = LDB_DELETE;
+	req->op.del.dn = NULL;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Try a delete with a dn
+	 */
+	req->operation = LDB_DELETE;
+	req->op.del.dn = dn;
+	s = get_primary_dn(req);
+	assert_non_null(s);
+	assert_string_equal(DN, s);
+
+	/*
+	 * Try a rename without a dn
+	 */
+	req->operation = LDB_RENAME;
+	req->op.rename.olddn = NULL;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Try a rename with a dn
+	 */
+	req->operation = LDB_RENAME;
+	req->op.rename.olddn = dn;
+	s = get_primary_dn(req);
+	assert_non_null(s);
+	assert_string_equal(DN, s);
+
+	/*
+	 * Try an extended operation, i.e. one that does not have a DN
+	 * associated with it for logging purposes.
+	 */
+	req->operation = LDB_EXTENDED;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_message(void **state)
+{
+	struct ldb_request *req = NULL;
+	struct ldb_message *msg = NULL;
+	const struct ldb_message *r = NULL;
+
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	req = talloc_zero(ctx, struct ldb_request);
+	msg = talloc_zero(ctx, struct ldb_message);
+
+	/*
+	 * Test an empty message
+	 */
+	r = get_message(req);
+	assert_null(r);
+
+	/*
+	 * Test an add message
+	 */
+	req->operation = LDB_ADD;
+	req->op.add.message = msg;
+	r = get_message(req);
+	assert_ptr_equal(msg, r);
+
+	/*
+	 * Test a modify message
+	 */
+	req->operation = LDB_MODIFY;
+	req->op.mod.message = msg;
+	r = get_message(req);
+	assert_ptr_equal(msg, r);
+
+	/*
+	 * Test a Delete message, i.e. trigger the default case
+	 */
+	req->operation = LDB_DELETE;
+	r = get_message(req);
+	assert_null(r);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_secondary_dn(void **state)
+{
+	struct ldb_request *req = NULL;
+	struct ldb_context *ldb = NULL;
+
+	struct ldb_dn *dn = NULL;
+
+	const char * const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+	const char *s = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	req = talloc_zero(ctx, struct ldb_request);
+	ldb = talloc_zero(ctx, struct ldb_context);
+	dn = ldb_dn_new(ctx, ldb, DN);
+
+	/*
+	 * Try an empty request.
+	 */
+	s = get_secondary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Try a rename without a dn
+	 */
+	req->operation = LDB_RENAME;
+	req->op.rename.newdn = NULL;
+	s = get_secondary_dn(req);
+	assert_null(s);
+
+	/*
+	 * Try a rename with a dn
+	 */
+	req->operation = LDB_RENAME;
+	req->op.rename.newdn = dn;
+	s = get_secondary_dn(req);
+	assert_non_null(s);
+	assert_string_equal(DN, s);
+
+	/*
+	 * Try an extended operation, i.e. one that does not have a DN
+	 * associated with it for logging purposes.
+	 */
+	req->operation = LDB_EXTENDED;
+	s = get_primary_dn(req);
+	assert_null(s);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_operation_name(void **state)
+{
+	struct ldb_request *req = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	req = talloc_zero(ctx, struct ldb_request);
+
+	req->operation =  LDB_SEARCH;
+	assert_string_equal("Search", get_operation_name(req));
+
+	req->operation =  LDB_ADD;
+	assert_string_equal("Add", get_operation_name(req));
+
+	req->operation =  LDB_MODIFY;
+	assert_string_equal("Modify", get_operation_name(req));
+
+	req->operation =  LDB_DELETE;
+	assert_string_equal("Delete", get_operation_name(req));
+
+	req->operation =  LDB_RENAME;
+	assert_string_equal("Rename", get_operation_name(req));
+
+	req->operation =  LDB_EXTENDED;
+	assert_string_equal("Extended", get_operation_name(req));
+
+	req->operation =  LDB_REQ_REGISTER_CONTROL;
+	assert_string_equal("Register Control", get_operation_name(req));
+
+	req->operation =  LDB_REQ_REGISTER_PARTITION;
+	assert_string_equal("Register Partition", get_operation_name(req));
+
+	/*
+	 * Trigger the default case
+	 */
+	req->operation =  -1;
+	assert_string_equal("Unknown", get_operation_name(req));
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_get_modification_action(void **state)
+{
+	assert_string_equal(
+		"add",
+		get_modification_action(LDB_FLAG_MOD_ADD));
+	assert_string_equal(
+		"delete",
+		get_modification_action(LDB_FLAG_MOD_DELETE));
+	assert_string_equal(
+		"replace",
+		get_modification_action(LDB_FLAG_MOD_REPLACE));
+	/*
+	 * Trigger the default case
+	 */
+	assert_string_equal(
+		"unknown",
+		get_modification_action(0));
+}
+
+int main(void) {
+	const struct CMUnitTest tests[] = {
+#ifdef HAVE_JANSSON
+		cmocka_unit_test(test_add_ldb_value),
+		cmocka_unit_test(test_attributes_json),
+#endif
+		cmocka_unit_test(test_get_remote_address),
+		cmocka_unit_test(test_get_ldb_error_string),
+		cmocka_unit_test(test_get_user_sid),
+		cmocka_unit_test(test_get_unique_session_token),
+		cmocka_unit_test(test_get_remote_host),
+		cmocka_unit_test(test_get_primary_dn),
+		cmocka_unit_test(test_get_message),
+		cmocka_unit_test(test_get_secondary_dn),
+		cmocka_unit_test(test_get_operation_name),
+		cmocka_unit_test(test_get_modification_action),
+	};
+
+	cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+	return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build b/source4/dsdb/samdb/ldb_modules/wscript_build
index 9e0ac28..da21e96 100644
--- a/source4/dsdb/samdb/ldb_modules/wscript_build
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build
@@ -7,9 +7,9 @@ bld.SAMBA_LIBRARY('dsdb-module',
 	grouping_library=True)
 
 bld.SAMBA_SUBSYSTEM('DSDB_MODULE_HELPERS',
-	source='util.c acl_util.c schema_util.c netlogon.c',
+	source='util.c acl_util.c schema_util.c netlogon.c audit_util.c',
 	autoproto='util_proto.h',
-	deps='ldb ndr samdb-common samba-security'
+	deps='ldb ndr samdb-common samba-security audit_logging'
 	)
 
 bld.SAMBA_SUBSYSTEM('DSDB_MODULE_HELPER_RIDALLOC',
@@ -40,6 +40,30 @@ bld.SAMBA_BINARY('test_encrypted_secrets',
             DSDB_MODULE_HELPERS
         ''',
         install=False)
+bld.SAMBA_BINARY('test_audit_util',
+        source='tests/test_audit_util.c',
+        deps='''
+            talloc
+            samba-util
+            samdb-common
+            samdb
+            cmocka
+            audit_logging
+            DSDB_MODULE_HELPERS
+        ''',
+        install=False)
+bld.SAMBA_BINARY('test_audit_log',
+        source='tests/test_audit_log.c',
+        deps='''
+            talloc
+            samba-util
+            samdb-common
+            samdb
+            cmocka
+            audit_logging
+            DSDB_MODULE_HELPERS
+        ''',
+        install=False)
 
 if bld.AD_DC_BUILD_IS_ENABLED():
     bld.PROCESS_SEPARATE_RULE("server")
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server
index 368260a..6c821fb 100644
--- a/source4/dsdb/samdb/ldb_modules/wscript_build_server
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server
@@ -425,3 +425,19 @@ bld.SAMBA_MODULE('ldb_encrypted_secrets',
             gnutls
         '''
 	)
+
+bld.SAMBA_MODULE('ldb_audit_log',
+	source='audit_log.c',
+	subsystem='ldb',
+	init_function='ldb_audit_log_module_init',
+	module_init_name='ldb_init_module',
+	internal_module=False,
+	deps='''
+            audit_logging
+            talloc
+            samba-util
+            samdb-common
+            DSDB_MODULE_HELPERS
+            samdb
+        '''
+	)
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 129901c..5b943ab 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -1054,3 +1054,7 @@ plantestsuite("samba4.dsdb.samdb.ldb_modules.encrypted_secrets", "none",
                   [os.path.join(bindir(), "test_encrypted_secrets")])
 plantestsuite("lib.audit_logging.audit_logging", "none",
                   [os.path.join(bindir(), "audit_logging_test")])
+plantestsuite("samba4.dsdb.samdb.ldb_modules.audit_util", "none",
+                  [os.path.join(bindir(), "test_audit_util")])
+plantestsuite("samba4.dsdb.samdb.ldb_modules.audit_log", "none",
+                  [os.path.join(bindir(), "test_audit_log")])
-- 
2.7.4


From ff9abe1393fc839afb35ffc1a15dc18ea9d6e6bc Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Wed, 4 Apr 2018 12:38:25 +1200
Subject: [PATCH 11/21] cldap: set the remote ip address

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source4/cldap_server/rootdse.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source4/cldap_server/rootdse.c b/source4/cldap_server/rootdse.c
index 3f389ce..8c616ad 100644
--- a/source4/cldap_server/rootdse.c
+++ b/source4/cldap_server/rootdse.c
@@ -166,6 +166,8 @@ void cldapd_rootdse_request(struct cldap_socket *cldap,
 	cldapd_rootdse_fill(cldapd, tmp_ctx, search, &reply.response,
 			    reply.result);
 
+	ldb_set_opaque(cldapd->samctx, "remoteAddress", NULL);
+	
 	status = cldap_reply_send(cldap, &reply);
 	if (!NT_STATUS_IS_OK(status)) {
 		DEBUG(2,("cldap rootdse query failed '%s' - %s\n",
-- 
2.7.4


From 5dda3a3c38750ad1c427ef64cf4edb2f1fcf1779 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Wed, 4 Apr 2018 12:39:55 +1200
Subject: [PATCH 12/21] dsdb pass the remote address to samdb connect

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source3/passdb/pdb_samba_dsdb.c    |  2 +-
 source4/dns_server/dlz_bind9.c     |  2 +-
 source4/dsdb/samdb/samdb.c         | 37 +++++++++++++++++++++++++++++--------
 source4/dsdb/samdb/samdb.h         |  2 +-
 source4/ldap_server/ldap_backend.c |  9 +++++----
 source4/torture/dns/dlz_bind9.c    |  4 +++-
 6 files changed, 40 insertions(+), 16 deletions(-)

diff --git a/source3/passdb/pdb_samba_dsdb.c b/source3/passdb/pdb_samba_dsdb.c
index 05052a6..f48f1fd 100644
--- a/source3/passdb/pdb_samba_dsdb.c
+++ b/source3/passdb/pdb_samba_dsdb.c
@@ -3893,7 +3893,7 @@ static NTSTATUS pdb_init_samba_dsdb(struct pdb_methods **pdb_method,
 				state->lp_ctx,
 				system_session(state->lp_ctx),
 				0, location,
-				&state->ldb, &errstring);
+				NULL, &state->ldb, &errstring);
 
 	if (!state->ldb) {
 		DEBUG(0, ("samdb_connect failed: %s: %s\n",
diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
index cf171cb..17afd1f 100644
--- a/source4/dns_server/dlz_bind9.c
+++ b/source4/dns_server/dlz_bind9.c
@@ -706,7 +706,7 @@ _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
 	ret = samdb_connect_url(state, state->ev_ctx, state->lp,
 				system_session(state->lp), 0,
 				state->options.url,
-				&state->samdb, &errstring);
+				NULL, &state->samdb, &errstring);
 	if (ret != LDB_SUCCESS) {
 		state->log(ISC_LOG_ERROR,
 			   "samba_dlz: Failed to connect to %s: %s",
diff --git a/source4/dsdb/samdb/samdb.c b/source4/dsdb/samdb/samdb.c
index 1f80267..7a1bb79 100644
--- a/source4/dsdb/samdb/samdb.c
+++ b/source4/dsdb/samdb/samdb.c
@@ -42,6 +42,7 @@
 #include "auth/credentials/credentials.h"
 #include "param/secrets.h"
 #include "auth/auth.h"
+#include "lib/tsocket/tsocket.h"
 
 /*
   connect to the SAM database specified by URL
@@ -51,7 +52,9 @@ int samdb_connect_url(TALLOC_CTX *mem_ctx,
 		      struct tevent_context *ev_ctx,
 		      struct loadparm_context *lp_ctx,
 		      struct auth_session_info *session_info,
-		      unsigned int flags, const char *url,
+		      unsigned int flags,
+		      const char *url,
+		      struct tsocket_address *remote_address,
 		      struct ldb_context **ldb_ret,
 		      char **errstring)
 {
@@ -59,13 +62,17 @@ int samdb_connect_url(TALLOC_CTX *mem_ctx,
 	int ret;
 	*ldb_ret = NULL;
 	*errstring = NULL;
-	ldb = ldb_wrap_find(url, ev_ctx, lp_ctx, session_info, NULL, flags);
-	if (ldb != NULL) {
-		*ldb_ret = talloc_reference(mem_ctx, ldb);
-		if (*ldb_ret == NULL) {
-			return LDB_ERR_OPERATIONS_ERROR;
+
+	if (remote_address == NULL) {
+		ldb = ldb_wrap_find(url, ev_ctx, lp_ctx,
+				    session_info, NULL, flags);
+		if (ldb != NULL) {
+			*ldb_ret = talloc_reference(mem_ctx, ldb);
+			if (*ldb_ret == NULL) {
+				return LDB_ERR_OPERATIONS_ERROR;
+			}
+			return LDB_SUCCESS;
 		}
-		return LDB_SUCCESS;
 	}
 
 	ldb = samba_ldb_init(mem_ctx, ev_ctx, lp_ctx, session_info, NULL);
@@ -91,6 +98,20 @@ int samdb_connect_url(TALLOC_CTX *mem_ctx,
 		return LDB_ERR_OPERATIONS_ERROR;
 	}
 
+	/*
+	 * If a remote_address was specified, then set it on the DB
+	 * and do not add to the wrap list (as we need to keep the LDB
+	 * pointer unique for the address).
+	 *
+	 * We use this for audit logging and for the "netlogon" attribute
+	 */
+	if (remote_address != NULL) {
+		ldb_set_opaque(ldb, "remoteAddress",
+			       remote_address);
+		*ldb_ret = ldb;
+		return LDB_SUCCESS;
+	}
+		
 	if (!ldb_wrap_add(url, ev_ctx, lp_ctx, session_info, NULL, flags, ldb)) {
 		*errstring = talloc_asprintf(mem_ctx,
 					     "Failed to add cached DB reference"
@@ -118,7 +139,7 @@ struct ldb_context *samdb_connect(TALLOC_CTX *mem_ctx,
 	char *errstring;
 	struct ldb_context *ldb;
 	int ret = samdb_connect_url(mem_ctx, ev_ctx, lp_ctx, session_info, flags,
-				    "sam.ldb", &ldb, &errstring);
+				    "sam.ldb", NULL, &ldb, &errstring);
 	if (ret == LDB_SUCCESS) {
 		return ldb;
 	}
diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h
index a095858..d2686af 100644
--- a/source4/dsdb/samdb/samdb.h
+++ b/source4/dsdb/samdb/samdb.h
@@ -28,7 +28,7 @@ struct dsdb_extended_replicated_object;
 struct dsdb_extended_replicated_objects;
 struct loadparm_context;
 struct tevent_context;
-
+struct tsocket_address;
 struct dsdb_trust_routing_table;
 
 #include "librpc/gen_ndr/security.h"
diff --git a/source4/ldap_server/ldap_backend.c b/source4/ldap_server/ldap_backend.c
index 95c7ee7..39f1aa2 100644
--- a/source4/ldap_server/ldap_backend.c
+++ b/source4/ldap_server/ldap_backend.c
@@ -31,6 +31,7 @@
 #include <ldb_errors.h>
 #include <ldb_module.h>
 #include "ldb_wrap.h"
+#include "lib/tsocket/tsocket.h"
 
 static int map_ldb_error(TALLOC_CTX *mem_ctx, int ldb_err,
 	const char *add_err_string, const char **errstring)
@@ -188,7 +189,10 @@ int ldapsrv_backend_Init(struct ldapsrv_connection *conn,
 				    conn->lp_ctx,
 				    conn->session_info,
 				    conn->global_catalog ? LDB_FLG_RDONLY : 0,
-				    "sam.ldb", &conn->ldb, errstring);
+				    "sam.ldb",
+				    conn->connection->remote_address,
+				    &conn->ldb,
+				    errstring);
 	if (ret != LDB_SUCCESS) {
 		return ret;
 	}
@@ -229,9 +233,6 @@ int ldapsrv_backend_Init(struct ldapsrv_connection *conn,
 		ldb_set_opaque(conn->ldb, "supportedSASLMechanisms", sasl_mechs);
 	}
 
-	ldb_set_opaque(conn->ldb, "remoteAddress",
-		       conn->connection->remote_address);
-
 	return LDB_SUCCESS;
 }
 
diff --git a/source4/torture/dns/dlz_bind9.c b/source4/torture/dns/dlz_bind9.c
index 42b104e..d948487 100644
--- a/source4/torture/dns/dlz_bind9.c
+++ b/source4/torture/dns/dlz_bind9.c
@@ -92,7 +92,9 @@ static isc_result_t dlz_bind9_writeable_zone_hook(dns_view_t *view,
 				    system_session(tctx->lp_ctx),
 				    0,
 				    test_dlz_bind9_binddns_dir(tctx, "dns/sam.ldb"),
-				    &samdb, &errstring);
+				    NULL,
+				    &samdb,
+				    &errstring);
 	struct ldb_message *msg;
 	const char *attrs[] = {
 		NULL
-- 
2.7.4


From 55e6a08106f1b7c90a2273ad52f74ebfc6e25bdb Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Thu, 12 Apr 2018 06:41:30 +1200
Subject: [PATCH 13/21] samdb: Add remote address to connect

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source3/modules/vfs_dfs_samba4.c                   |   4 +-
 source3/passdb/pdb_samba_dsdb.c                    |   7 +-
 source4/auth/ntlm/auth.c                           |   7 +-
 source4/cldap_server/cldap_server.c                |   7 +-
 source4/dns_server/dlz_bind9.c                     |  11 +-
 source4/dns_server/dns_server.c                    |   8 +-
 source4/dsdb/dns/dns_update.c                      |   8 +-
 source4/dsdb/kcc/kcc_service.c                     |   7 +-
 source4/dsdb/repl/drepl_service.c                  |   7 +-
 source4/dsdb/samdb/samdb.c                         |  16 ++-
 source4/kdc/db-glue.c                              |   8 +-
 source4/kdc/kdc-heimdal.c                          |   8 +-
 source4/kdc/kdc-service-mit.c                      |   1 +
 source4/kdc/kpasswd-helper.c                       |   1 +
 source4/kdc/kpasswd_glue.c                         |   8 +-
 source4/ldap_server/ldap_server.c                  |   8 +-
 source4/libnet/libnet_samsync_ldb.c                |   5 +-
 source4/nbt_server/nbt_server.c                    |   7 +-
 source4/ntp_signd/ntp_signd.c                      |   7 +-
 source4/rpc_server/backupkey/dcesrv_backupkey.c    |   7 +-
 .../backupkey/dcesrv_backupkey_heimdal.c           |   7 +-
 source4/rpc_server/common/server_info.c            |   8 +-
 source4/rpc_server/dnsserver/dcerpc_dnsserver.c    |   8 +-
 source4/rpc_server/drsuapi/dcesrv_drsuapi.c        |  20 +++-
 source4/rpc_server/lsa/dcesrv_lsa.c                |  22 +++-
 source4/rpc_server/lsa/lsa_init.c                  |   7 +-
 source4/rpc_server/netlogon/dcerpc_netlogon.c      | 124 ++++++++++++++++-----
 source4/rpc_server/samr/dcesrv_samr.c              |  16 ++-
 source4/rpc_server/samr/samr_password.c            |  28 +++--
 source4/smb_server/smb/trans2.c                    |   7 +-
 source4/smbd/server.c                              |   9 +-
 source4/torture/dns/dlz_bind9.c                    |  17 +--
 source4/torture/gpo/apply.c                        |   8 +-
 source4/torture/libnet/libnet_BecomeDC.c           |   7 +-
 source4/winbind/idmap.c                            |   7 +-
 35 files changed, 332 insertions(+), 105 deletions(-)

diff --git a/source3/modules/vfs_dfs_samba4.c b/source3/modules/vfs_dfs_samba4.c
index d7f4edf..1c7b50e 100644
--- a/source3/modules/vfs_dfs_samba4.c
+++ b/source3/modules/vfs_dfs_samba4.c
@@ -76,7 +76,9 @@ static int dfs_samba4_connect(struct vfs_handle_struct *handle,
 	data->sam_ctx = samdb_connect(data,
 				      data->ev,
 				      data->lp_ctx,
-				      system_session(data->lp_ctx), 0);
+				      system_session(data->lp_ctx),
+				      NULL,
+				      0);
 	if (!data->sam_ctx) {
 		DEBUG(0, ("samdb_connect failed\n"));
 		SMB_VFS_NEXT_DISCONNECT(handle);
diff --git a/source3/passdb/pdb_samba_dsdb.c b/source3/passdb/pdb_samba_dsdb.c
index f48f1fd..957524d 100644
--- a/source3/passdb/pdb_samba_dsdb.c
+++ b/source3/passdb/pdb_samba_dsdb.c
@@ -3892,8 +3892,11 @@ static NTSTATUS pdb_init_samba_dsdb(struct pdb_methods **pdb_method,
 				state->ev,
 				state->lp_ctx,
 				system_session(state->lp_ctx),
-				0, location,
-				NULL, &state->ldb, &errstring);
+				0,
+				location,
+				NULL,
+				&state->ldb,
+				&errstring);
 
 	if (!state->ldb) {
 		DEBUG(0, ("samdb_connect failed: %s: %s\n",
diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c
index 7e10a55..1293b28 100644
--- a/source4/auth/ntlm/auth.c
+++ b/source4/auth/ntlm/auth.c
@@ -713,7 +713,12 @@ _PUBLIC_ NTSTATUS auth_context_create_methods(TALLOC_CTX *mem_ctx, const char *
 	if (sam_ctx) {
 		ctx->sam_ctx = sam_ctx;
 	} else {
-		ctx->sam_ctx = samdb_connect(ctx, ctx->event_ctx, ctx->lp_ctx, system_session(ctx->lp_ctx), 0);
+		ctx->sam_ctx = samdb_connect(ctx,
+					     ctx->event_ctx,
+					     ctx->lp_ctx,
+					     system_session(ctx->lp_ctx),
+					     NULL,
+					     0);
 	}
 
 	for (i=0; methods && methods[i] ; i++) {
diff --git a/source4/cldap_server/cldap_server.c b/source4/cldap_server/cldap_server.c
index a0cde77..d93ac0b 100644
--- a/source4/cldap_server/cldap_server.c
+++ b/source4/cldap_server/cldap_server.c
@@ -221,7 +221,12 @@ static void cldapd_task_init(struct task_server *task)
 	}
 
 	cldapd->task = task;
-	cldapd->samctx = samdb_connect(cldapd, task->event_ctx, task->lp_ctx, system_session(task->lp_ctx), 0);
+	cldapd->samctx = samdb_connect(cldapd,
+				       task->event_ctx,
+				       task->lp_ctx,
+				       system_session(task->lp_ctx),
+				       NULL,
+				       0);
 	if (cldapd->samctx == NULL) {
 		task_server_terminate(task, "cldapd failed to open samdb", true);
 		return;
diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
index 17afd1f..9cb85f6 100644
--- a/source4/dns_server/dlz_bind9.c
+++ b/source4/dns_server/dlz_bind9.c
@@ -703,10 +703,15 @@ _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
 		}
 	}
 
-	ret = samdb_connect_url(state, state->ev_ctx, state->lp,
-				system_session(state->lp), 0,
+	ret = samdb_connect_url(state,
+				state->ev_ctx,
+				state->lp,
+				system_session(state->lp),
+				0,
 				state->options.url,
-				NULL, &state->samdb, &errstring);
+				NULL,
+				&state->samdb,
+				&errstring);
 	if (ret != LDB_SUCCESS) {
 		state->log(ISC_LOG_ERROR,
 			   "samba_dlz: Failed to connect to %s: %s",
diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c
index 5c6aca5..9db1133 100644
--- a/source4/dns_server/dns_server.c
+++ b/source4/dns_server/dns_server.c
@@ -838,8 +838,12 @@ static void dns_task_init(struct task_server *task)
 		return;
 	}
 
-	dns->samdb = samdb_connect(dns, dns->task->event_ctx, dns->task->lp_ctx,
-			      system_session(dns->task->lp_ctx), 0);
+	dns->samdb = samdb_connect(dns,
+				   dns->task->event_ctx,
+				   dns->task->lp_ctx,
+				   system_session(dns->task->lp_ctx),
+				   NULL,
+				   0);
 	if (!dns->samdb) {
 		task_server_terminate(task, "dns: samdb_connect failed", true);
 		return;
diff --git a/source4/dsdb/dns/dns_update.c b/source4/dsdb/dns/dns_update.c
index 10be7ce..0655673 100644
--- a/source4/dsdb/dns/dns_update.c
+++ b/source4/dsdb/dns/dns_update.c
@@ -660,8 +660,12 @@ static void dnsupdate_task_init(struct task_server *task)
 		return;
 	}
 
-	service->samdb = samdb_connect(service, service->task->event_ctx, task->lp_ctx,
-				       service->system_session_info, 0);
+	service->samdb = samdb_connect(service,
+				       service->task->event_ctx,
+				       task->lp_ctx,
+				       service->system_session_info,
+				       NULL,
+				       0);
 	if (!service->samdb) {
 		task_server_terminate(task, "dnsupdate: Failed to connect to local samdb\n",
 				      true);
diff --git a/source4/dsdb/kcc/kcc_service.c b/source4/dsdb/kcc/kcc_service.c
index a5508af..7642378 100644
--- a/source4/dsdb/kcc/kcc_service.c
+++ b/source4/dsdb/kcc/kcc_service.c
@@ -56,7 +56,12 @@ static WERROR kccsrv_connect_samdb(struct kccsrv_service *service, struct loadpa
 {
 	const struct GUID *ntds_guid;
 
-	service->samdb = samdb_connect(service, service->task->event_ctx, lp_ctx, service->system_session_info, 0);
+	service->samdb = samdb_connect(service,
+				       service->task->event_ctx,
+				       lp_ctx,
+				       service->system_session_info,
+				       NULL,
+				       0);
 	if (!service->samdb) {
 		return WERR_DS_UNAVAILABLE;
 	}
diff --git a/source4/dsdb/repl/drepl_service.c b/source4/dsdb/repl/drepl_service.c
index 8f16a2e..10772d4 100644
--- a/source4/dsdb/repl/drepl_service.c
+++ b/source4/dsdb/repl/drepl_service.c
@@ -68,7 +68,12 @@ static WERROR dreplsrv_connect_samdb(struct dreplsrv_service *service, struct lo
 	const struct GUID *ntds_guid;
 	struct drsuapi_DsBindInfo28 *bind_info28;
 
-	service->samdb = samdb_connect(service, service->task->event_ctx, lp_ctx, service->system_session_info, 0);
+	service->samdb = samdb_connect(service,
+				       service->task->event_ctx,
+				       lp_ctx,
+				       service->system_session_info,
+				       NULL,
+				       0);
 	if (!service->samdb) {
 		return WERR_DS_UNAVAILABLE;
 	}
diff --git a/source4/dsdb/samdb/samdb.c b/source4/dsdb/samdb/samdb.c
index 7a1bb79..10db0c5 100644
--- a/source4/dsdb/samdb/samdb.c
+++ b/source4/dsdb/samdb/samdb.c
@@ -54,7 +54,7 @@ int samdb_connect_url(TALLOC_CTX *mem_ctx,
 		      struct auth_session_info *session_info,
 		      unsigned int flags,
 		      const char *url,
-		      struct tsocket_address *remote_address,
+		      const struct tsocket_address *remote_address,
 		      struct ldb_context **ldb_ret,
 		      char **errstring)
 {
@@ -107,7 +107,7 @@ int samdb_connect_url(TALLOC_CTX *mem_ctx,
 	 */
 	if (remote_address != NULL) {
 		ldb_set_opaque(ldb, "remoteAddress",
-			       remote_address);
+			       discard_const(remote_address));
 		*ldb_ret = ldb;
 		return LDB_SUCCESS;
 	}
@@ -134,12 +134,20 @@ struct ldb_context *samdb_connect(TALLOC_CTX *mem_ctx,
 				  struct tevent_context *ev_ctx,
 				  struct loadparm_context *lp_ctx,
 				  struct auth_session_info *session_info,
+				  const struct tsocket_address *remote_address,
 				  unsigned int flags)
 {
 	char *errstring;
 	struct ldb_context *ldb;
-	int ret = samdb_connect_url(mem_ctx, ev_ctx, lp_ctx, session_info, flags,
-				    "sam.ldb", NULL, &ldb, &errstring);
+	int ret = samdb_connect_url(mem_ctx,
+				    ev_ctx,
+				    lp_ctx,
+				    session_info,
+				    flags,
+				    "sam.ldb",
+				    remote_address,
+				    &ldb,
+				    &errstring);
 	if (ret == LDB_SUCCESS) {
 		return ldb;
 	}
diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c
index c2dd236..bb428e4 100644
--- a/source4/kdc/db-glue.c
+++ b/source4/kdc/db-glue.c
@@ -2774,8 +2774,12 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte
 	}
 
 	/* Setup the link to LDB */
-	kdc_db_ctx->samdb = samdb_connect(kdc_db_ctx, base_ctx->ev_ctx,
-					  base_ctx->lp_ctx, session_info, 0);
+	kdc_db_ctx->samdb = samdb_connect(kdc_db_ctx,
+					  base_ctx->ev_ctx,
+					  base_ctx->lp_ctx,
+					  session_info,
+					  NULL,
+					  0);
 	if (kdc_db_ctx->samdb == NULL) {
 		DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot open samdb for KDC backend!"));
 		talloc_free(kdc_db_ctx);
diff --git a/source4/kdc/kdc-heimdal.c b/source4/kdc/kdc-heimdal.c
index fcc1eb0..83ace26 100644
--- a/source4/kdc/kdc-heimdal.c
+++ b/source4/kdc/kdc-heimdal.c
@@ -305,8 +305,12 @@ static void kdc_task_init(struct task_server *task)
 
 
 	/* get a samdb connection */
-	kdc->samdb = samdb_connect(kdc, kdc->task->event_ctx, kdc->task->lp_ctx,
-				   system_session(kdc->task->lp_ctx), 0);
+	kdc->samdb = samdb_connect(kdc,
+				   kdc->task->event_ctx,
+				   kdc->task->lp_ctx,
+				   system_session(kdc->task->lp_ctx),
+				   NULL,
+				   0);
 	if (!kdc->samdb) {
 		DEBUG(1,("kdc_task_init: unable to connect to samdb\n"));
 		task_server_terminate(task, "kdc: krb5_init_context samdb connect failed", true);
diff --git a/source4/kdc/kdc-service-mit.c b/source4/kdc/kdc-service-mit.c
index e5b20ff..1d28fc4 100644
--- a/source4/kdc/kdc-service-mit.c
+++ b/source4/kdc/kdc-service-mit.c
@@ -303,6 +303,7 @@ void mitkdc_task_init(struct task_server *task)
 				   kdc->task->event_ctx,
 				   kdc->task->lp_ctx,
 				   system_session(kdc->task->lp_ctx),
+				   NULL,
 				   0);
 	if (kdc->samdb == NULL) {
 		task_server_terminate(task,
diff --git a/source4/kdc/kpasswd-helper.c b/source4/kdc/kpasswd-helper.c
index 6de2837..995f548 100644
--- a/source4/kdc/kpasswd-helper.c
+++ b/source4/kdc/kpasswd-helper.c
@@ -180,6 +180,7 @@ NTSTATUS kpasswd_samdb_set_password(TALLOC_CTX *mem_ctx,
 			      event_ctx,
 			      lp_ctx,
 			      session_info,
+			      NULL,
 			      0);
 	if (samdb == NULL) {
 		return NT_STATUS_INTERNAL_DB_CORRUPTION;
diff --git a/source4/kdc/kpasswd_glue.c b/source4/kdc/kpasswd_glue.c
index b12a209..0addfdf 100644
--- a/source4/kdc/kpasswd_glue.c
+++ b/source4/kdc/kpasswd_glue.c
@@ -79,8 +79,12 @@ NTSTATUS samdb_kpasswd_change_password(TALLOC_CTX *mem_ctx,
 	}
 
 	/* Start a SAM with user privileges for the password change */
-	samdb = samdb_connect(mem_ctx, event_ctx, lp_ctx,
-			      session_info, 0);
+	samdb = samdb_connect(mem_ctx,
+			      event_ctx,
+			      lp_ctx,
+			      session_info,
+			      NULL,
+			      0);
 	if (!samdb) {
 		*error_string = "Failed to open samdb";
 		return NT_STATUS_ACCESS_DENIED;
diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c
index 91e5d05..80b8c19 100644
--- a/source4/ldap_server/ldap_server.c
+++ b/source4/ldap_server/ldap_server.c
@@ -1055,8 +1055,12 @@ static NTSTATUS add_socket(struct task_server *task,
 	}
 
 	/* Load LDAP database, but only to read our settings */
-	ldb = samdb_connect(ldap_service, ldap_service->task->event_ctx, 
-			    lp_ctx, system_session(lp_ctx), 0);
+	ldb = samdb_connect(ldap_service,
+			    ldap_service->task->event_ctx,
+			    lp_ctx,
+			    system_session(lp_ctx),
+			    NULL,
+			    0);
 	if (!ldb) {
 		return NT_STATUS_INTERNAL_DB_CORRUPTION;
 	}
diff --git a/source4/libnet/libnet_samsync_ldb.c b/source4/libnet/libnet_samsync_ldb.c
index 5fdef79..9ea7dce 100644
--- a/source4/libnet/libnet_samsync_ldb.c
+++ b/source4/libnet/libnet_samsync_ldb.c
@@ -1245,11 +1245,12 @@ NTSTATUS libnet_samsync_ldb(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, str
 	state->secrets         = NULL;
 	state->trusted_domains = NULL;
 
-	state->sam_ldb         = samdb_connect(mem_ctx, 
+	state->sam_ldb         = samdb_connect(mem_ctx,
 					       ctx->event_ctx,
 					       ctx->lp_ctx,
 					       r->in.session_info,
-						   0);
+					       NULL,
+					       0);
 	if (!state->sam_ldb) {
 		return NT_STATUS_INTERNAL_DB_ERROR;
 	}
diff --git a/source4/nbt_server/nbt_server.c b/source4/nbt_server/nbt_server.c
index 834c72f..6bdf9b4 100644
--- a/source4/nbt_server/nbt_server.c
+++ b/source4/nbt_server/nbt_server.c
@@ -73,7 +73,12 @@ static void nbtd_task_init(struct task_server *task)
 		return;
 	}
 
-	nbtsrv->sam_ctx = samdb_connect(nbtsrv, task->event_ctx, task->lp_ctx, system_session(task->lp_ctx), 0);
+	nbtsrv->sam_ctx = samdb_connect(nbtsrv,
+				        task->event_ctx,
+					task->lp_ctx,
+					system_session(task->lp_ctx),
+					NULL,
+					0);
 	if (nbtsrv->sam_ctx == NULL) {
 		task_server_terminate(task, "nbtd failed to open samdb", true);
 		return;
diff --git a/source4/ntp_signd/ntp_signd.c b/source4/ntp_signd/ntp_signd.c
index e09c693..508adcf 100644
--- a/source4/ntp_signd/ntp_signd.c
+++ b/source4/ntp_signd/ntp_signd.c
@@ -515,7 +515,12 @@ static void ntp_signd_task_init(struct task_server *task)
 	ntp_signd->task = task;
 
 	/* Must be system to get at the password hashes */
-	ntp_signd->samdb = samdb_connect(ntp_signd, task->event_ctx, task->lp_ctx, system_session(task->lp_ctx), 0);
+	ntp_signd->samdb = samdb_connect(ntp_signd,
+					 task->event_ctx,
+					 task->lp_ctx,
+					 system_session(task->lp_ctx),
+					 NULL,
+					 0);
 	if (ntp_signd->samdb == NULL) {
 		task_server_terminate(task, "ntp_signd failed to open samdb", true);
 		return;
diff --git a/source4/rpc_server/backupkey/dcesrv_backupkey.c b/source4/rpc_server/backupkey/dcesrv_backupkey.c
index cf9af1f..d2c3f2a 100644
--- a/source4/rpc_server/backupkey/dcesrv_backupkey.c
+++ b/source4/rpc_server/backupkey/dcesrv_backupkey.c
@@ -1774,9 +1774,12 @@ static WERROR dcesrv_bkrp_BackupKey(struct dcesrv_call_state *dce_call,
 		return WERR_NOT_SUPPORTED;
 	}
 
-	ldb_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	ldb_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 
 	if (samdb_rodc(ldb_ctx, &is_rodc) != LDB_SUCCESS) {
 		talloc_unlink(mem_ctx, ldb_ctx);
diff --git a/source4/rpc_server/backupkey/dcesrv_backupkey_heimdal.c b/source4/rpc_server/backupkey/dcesrv_backupkey_heimdal.c
index 5985d52..d2fc480 100644
--- a/source4/rpc_server/backupkey/dcesrv_backupkey_heimdal.c
+++ b/source4/rpc_server/backupkey/dcesrv_backupkey_heimdal.c
@@ -1814,9 +1814,12 @@ static WERROR dcesrv_bkrp_BackupKey(struct dcesrv_call_state *dce_call,
 		return WERR_NOT_SUPPORTED;
 	}
 
-	ldb_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	ldb_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 
 	if (samdb_rodc(ldb_ctx, &is_rodc) != LDB_SUCCESS) {
 		talloc_unlink(mem_ctx, ldb_ctx);
diff --git a/source4/rpc_server/common/server_info.c b/source4/rpc_server/common/server_info.c
index 39c75cc..0aabcda 100644
--- a/source4/rpc_server/common/server_info.c
+++ b/source4/rpc_server/common/server_info.c
@@ -83,7 +83,13 @@ uint32_t dcesrv_common_get_server_type(TALLOC_CTX *mem_ctx, struct tevent_contex
 				break;
 			}
 			/* open main ldb */
-			samctx = samdb_connect(tmp_ctx, event_ctx, dce_ctx->lp_ctx, anonymous_session(tmp_ctx, dce_ctx->lp_ctx), 0);
+			samctx = samdb_connect(
+				tmp_ctx,
+				event_ctx,
+				dce_ctx->lp_ctx,
+				anonymous_session(tmp_ctx, dce_ctx->lp_ctx),
+				NULL,
+				0);
 			if (samctx == NULL) {
 				DEBUG(2,("Unable to open samdb in determining server announce flags\n"));
 			} else {
diff --git a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
index 5d3cb9e..b9ed3dd 100644
--- a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
+++ b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
@@ -119,8 +119,12 @@ static struct dnsserver_state *dnsserver_connect(struct dcesrv_call_state *dce_c
 	dsstate->lp_ctx = dce_call->conn->dce_ctx->lp_ctx;
 
 	/* FIXME: create correct auth_session_info for connecting user */
-	dsstate->samdb = samdb_connect(dsstate, dce_call->event_ctx, dsstate->lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	dsstate->samdb = samdb_connect(dsstate,
+				       dce_call->event_ctx,
+				       dsstate->lp_ctx,
+				       dce_call->conn->auth_state.session_info,
+				       dce_call->conn->remote_address,
+				       0);
 	if (dsstate->samdb == NULL) {
 		DEBUG(0,("dnsserver: Failed to open samdb"));
 		goto failed;
diff --git a/source4/rpc_server/drsuapi/dcesrv_drsuapi.c b/source4/rpc_server/drsuapi/dcesrv_drsuapi.c
index 026098d..250b4c7 100644
--- a/source4/rpc_server/drsuapi/dcesrv_drsuapi.c
+++ b/source4/rpc_server/drsuapi/dcesrv_drsuapi.c
@@ -96,8 +96,13 @@ static WERROR dcesrv_drsuapi_DsBind(struct dcesrv_call_state *dce_call, TALLOC_C
 	/*
 	 * connect to the samdb
 	 */
-	b_state->sam_ctx = samdb_connect(b_state, dce_call->event_ctx, 
-					 dce_call->conn->dce_ctx->lp_ctx, auth_info, 0);
+	b_state->sam_ctx = samdb_connect(
+		b_state,
+		dce_call->event_ctx,
+		dce_call->conn->dce_ctx->lp_ctx,
+		auth_info,
+		dce_call->conn->remote_address,
+		0);
 	if (!b_state->sam_ctx) {
 		return WERR_FOOBAR;
 	}
@@ -110,9 +115,14 @@ static WERROR dcesrv_drsuapi_DsBind(struct dcesrv_call_state *dce_call, TALLOC_C
 		werr = drs_security_level_check(dce_call, NULL, SECURITY_RO_DOMAIN_CONTROLLER,
 						samdb_domain_sid(b_state->sam_ctx));
 		if (W_ERROR_IS_OK(werr)) {
-			b_state->sam_ctx_system = samdb_connect(b_state, dce_call->event_ctx,
-								dce_call->conn->dce_ctx->lp_ctx,
-								system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+			b_state->sam_ctx_system
+			= samdb_connect(
+				b_state,
+				dce_call->event_ctx,
+				dce_call->conn->dce_ctx->lp_ctx,
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 			if (!b_state->sam_ctx_system) {
 				return WERR_FOOBAR;
 			}
diff --git a/source4/rpc_server/lsa/dcesrv_lsa.c b/source4/rpc_server/lsa/dcesrv_lsa.c
index 9371bee..8c540ab 100644
--- a/source4/rpc_server/lsa/dcesrv_lsa.c
+++ b/source4/rpc_server/lsa/dcesrv_lsa.c
@@ -3180,6 +3180,7 @@ static NTSTATUS dcesrv_lsa_CreateSecret(struct dcesrv_call_state *dce_call, TALL
 	struct lsa_secret_state *secret_state;
 	struct dcesrv_handle *handle;
 	struct ldb_message **msgs, *msg;
+	struct ldb_context *samdb = NULL;
 	const char *attrs[] = {
 		NULL
 	};
@@ -3233,8 +3234,14 @@ static NTSTATUS dcesrv_lsa_CreateSecret(struct dcesrv_call_state *dce_call, TALL
 		/* We need to connect to the database as system, as this is one
 		 * of the rare RPC calls that must read the secrets (and this
 		 * is denied otherwise) */
-		secret_state->sam_ldb = talloc_reference(secret_state,
-							 samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(dce_call->conn->dce_ctx->lp_ctx), 0));
+		samdb = samdb_connect(
+			mem_ctx,
+			dce_call->event_ctx,
+			dce_call->conn->dce_ctx->lp_ctx,
+			system_session(dce_call->conn->dce_ctx->lp_ctx),
+			dce_call->conn->remote_address,
+			0);
+		secret_state->sam_ldb = talloc_reference(secret_state, samdb);
 		NT_STATUS_HAVE_NO_MEMORY(secret_state->sam_ldb);
 
 		/* search for the secret record */
@@ -3337,6 +3344,7 @@ static NTSTATUS dcesrv_lsa_OpenSecret(struct dcesrv_call_state *dce_call, TALLOC
 	struct lsa_secret_state *secret_state;
 	struct dcesrv_handle *handle;
 	struct ldb_message **msgs;
+	struct ldb_context *samdb = NULL;
 	const char *attrs[] = {
 		NULL
 	};
@@ -3372,8 +3380,14 @@ static NTSTATUS dcesrv_lsa_OpenSecret(struct dcesrv_call_state *dce_call, TALLOC
 	if (strncmp("G$", r->in.name.string, 2) == 0) {
 		name = &r->in.name.string[2];
 		/* We need to connect to the database as system, as this is one of the rare RPC calls that must read the secrets (and this is denied otherwise) */
-		secret_state->sam_ldb = talloc_reference(secret_state,
-							 samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(dce_call->conn->dce_ctx->lp_ctx), 0));
+		samdb = samdb_connect(
+			mem_ctx,
+			dce_call->event_ctx,
+			dce_call->conn->dce_ctx->lp_ctx,
+			system_session(dce_call->conn->dce_ctx->lp_ctx),
+			dce_call->conn->remote_address,
+			0);
+		secret_state->sam_ldb = talloc_reference(secret_state, samdb);
 		secret_state->global = true;
 
 		if (strlen(name) < 1) {
diff --git a/source4/rpc_server/lsa/lsa_init.c b/source4/rpc_server/lsa/lsa_init.c
index 5628c5b..5602294 100644
--- a/source4/rpc_server/lsa/lsa_init.c
+++ b/source4/rpc_server/lsa/lsa_init.c
@@ -70,7 +70,12 @@ NTSTATUS dcesrv_lsa_get_policy_state(struct dcesrv_call_state *dce_call,
 	}
 
 	/* make sure the sam database is accessible */
-	state->sam_ldb = samdb_connect(state, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, dce_call->conn->auth_state.session_info, 0);
+	state->sam_ldb = samdb_connect(state,
+				       dce_call->event_ctx,
+				       dce_call->conn->dce_ctx->lp_ctx,
+				       dce_call->conn->auth_state.session_info,
+				       dce_call->conn->remote_address,
+				       0);
 	if (state->sam_ldb == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index 638dfc2..c19d33c 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -291,8 +291,12 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate3_helper(
 		return NT_STATUS_INVALID_PARAMETER;
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				dce_call->conn->dce_ctx->lp_ctx,
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -699,7 +703,12 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call
 							&creds);
 	NT_STATUS_NOT_OK_RETURN(nt_status);
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				dce_call->conn->dce_ctx->lp_ctx,
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -759,7 +768,12 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal
 							&creds);
 	NT_STATUS_NOT_OK_RETURN(nt_status);
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				dce_call->conn->dce_ctx->lp_ctx,
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -1583,9 +1597,12 @@ static WERROR dcesrv_netr_GetDcName(struct dcesrv_call_state *dce_call, TALLOC_C
 		 */
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_DS_UNAVAILABLE;
 	}
@@ -1785,8 +1802,13 @@ static WERROR dcesrv_netr_LogonControl_base_call(struct dcesrv_netr_LogonControl
 		if (!ok) {
 			struct ldb_context *sam_ctx;
 
-			sam_ctx = samdb_connect(state, state->dce_call->event_ctx,
-						lp_ctx, system_session(lp_ctx), 0);
+			sam_ctx = samdb_connect(
+				state,
+				state->dce_call->event_ctx,
+				lp_ctx,
+				system_session(lp_ctx),
+				state->dce_call->conn->remote_address,
+				0);
 			if (sam_ctx == NULL) {
 				return WERR_DS_UNAVAILABLE;
 			}
@@ -1996,8 +2018,12 @@ static WERROR dcesrv_netr_GetAnyDCName(struct dcesrv_call_state *dce_call, TALLO
 		r->in.domainname = lpcfg_workgroup(lp_ctx);
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_DS_UNAVAILABLE;
 	}
@@ -2142,8 +2168,12 @@ static WERROR dcesrv_netr_DsRGetSiteName(struct dcesrv_call_state *dce_call, TAL
 	struct ldb_context *sam_ctx;
 	struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx;
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_DS_UNAVAILABLE;
 	}
@@ -2265,9 +2295,12 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
 	}
 	NT_STATUS_NOT_OK_RETURN(status);
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -2662,9 +2695,12 @@ static NTSTATUS dcesrv_netr_NetrLogonSendToSam(struct dcesrv_call_state *dce_cal
 		return NT_STATUS_INVALID_PARAMETER;
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -2788,8 +2824,12 @@ static WERROR dcesrv_netr_DsRGetDCName_base_call(struct dcesrv_netr_DsRGetDCName
 
 	ZERO_STRUCTP(r->out.info);
 
-	sam_ctx = samdb_connect(state, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(state,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_DS_UNAVAILABLE;
 	}
@@ -3259,8 +3299,12 @@ static WERROR dcesrv_netr_DsRAddressToSitenamesExW(struct dcesrv_call_state *dce
 	const char *res;
 	uint32_t i;
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_DS_UNAVAILABLE;
 	}
@@ -3376,8 +3420,12 @@ static WERROR dcesrv_netr_DsrGetDcSiteCoverageW(struct dcesrv_call_state *dce_ca
 	struct DcSitesCtr *ctr;
 	struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx;
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_DS_UNAVAILABLE;
 	}
@@ -3544,8 +3592,12 @@ static WERROR dcesrv_netr_DsrEnumerateDomainTrusts(struct dcesrv_call_state *dce
 	trusts->count = 0;
 	r->out.trusts = trusts;
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_GEN_FAILURE;
 	}
@@ -3677,8 +3729,12 @@ static WERROR dcesrv_netr_DsRGetForestTrustInformation(struct dcesrv_call_state
 		return WERR_INVALID_FLAGS;
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return WERR_GEN_FAILURE;
 	}
@@ -3829,8 +3885,12 @@ static NTSTATUS dcesrv_netr_GetForestTrustInformation(struct dcesrv_call_state *
 		return NT_STATUS_NOT_IMPLEMENTED;
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INTERNAL_ERROR;
 	}
@@ -3924,8 +3984,12 @@ static NTSTATUS dcesrv_netr_ServerGetTrustInfo(struct dcesrv_call_state *dce_cal
 		return NT_STATUS_INVALID_PARAMETER;
 	}
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
-				lp_ctx, system_session(lp_ctx), 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				lp_ctx,
+				system_session(lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c
index c7d692a..bf086f7 100644
--- a/source4/rpc_server/samr/dcesrv_samr.c
+++ b/source4/rpc_server/samr/dcesrv_samr.c
@@ -166,7 +166,12 @@ static NTSTATUS dcesrv_samr_Connect(struct dcesrv_call_state *dce_call, TALLOC_C
 	}
 
 	/* make sure the sam database is accessible */
-	c_state->sam_ctx = samdb_connect(c_state, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, dce_call->conn->auth_state.session_info, 0);
+	c_state->sam_ctx = samdb_connect(c_state,
+					 dce_call->event_ctx,
+					 dce_call->conn->dce_ctx->lp_ctx,
+					 dce_call->conn->auth_state.session_info,
+					 dce_call->conn->remote_address,
+					 0);
 	if (c_state->sam_ctx == NULL) {
 		talloc_free(c_state);
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
@@ -4180,9 +4185,12 @@ static NTSTATUS dcesrv_samr_GetDomPwInfo(struct dcesrv_call_state *dce_call, TAL
 
 	ZERO_STRUCTP(r->out.info);
 
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
-					 dce_call->conn->dce_ctx->lp_ctx,
-					 dce_call->conn->auth_state.session_info, 0);
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
+				dce_call->conn->dce_ctx->lp_ctx,
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c
index 22f456f..d7b5e16 100644
--- a/source4/rpc_server/samr/samr_password.c
+++ b/source4/rpc_server/samr/samr_password.c
@@ -135,9 +135,12 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call,
 
 	/* Connect to a SAMDB with system privileges for fetching the old pw
 	 * hashes. */
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -212,9 +215,12 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call,
 	}
 
 	/* Connect to a SAMDB with user privileges for the password change */
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -327,9 +333,12 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call,
 
 	/* Connect to a SAMDB with system privileges for fetching the old pw
 	 * hashes. */
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+				system_session(dce_call->conn->dce_ctx->lp_ctx),
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
@@ -408,9 +417,12 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call,
 	}
 
 	/* Connect to a SAMDB with user privileges for the password change */
-	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+	sam_ctx = samdb_connect(mem_ctx,
+				dce_call->event_ctx,
 				dce_call->conn->dce_ctx->lp_ctx,
-				dce_call->conn->auth_state.session_info, 0);
+				dce_call->conn->auth_state.session_info,
+				dce_call->conn->remote_address,
+				0);
 	if (sam_ctx == NULL) {
 		return NT_STATUS_INVALID_SYSTEM_SERVICE;
 	}
diff --git a/source4/smb_server/smb/trans2.c b/source4/smb_server/smb/trans2.c
index 89402ba..447095c 100644
--- a/source4/smb_server/smb/trans2.c
+++ b/source4/smb_server/smb/trans2.c
@@ -876,7 +876,12 @@ static NTSTATUS trans2_getdfsreferral(struct smbsrv_request *req,
 	r = talloc_zero(req, struct dfs_GetDFSReferral);
 	NT_STATUS_HAVE_NO_MEMORY(r);
 
-	ldb = samdb_connect(r, req->tcon->ntvfs->event_ctx, lp_ctx, system_session(lp_ctx), 0);
+	ldb = samdb_connect(r,
+			    req->tcon->ntvfs->event_ctx,
+			    lp_ctx,
+			    system_session(lp_ctx),
+			    NULL,
+			    0);
 	if (ldb == NULL) {
 		DEBUG(2,(__location__ ": Failed to open samdb\n"));
 		talloc_free(r);
diff --git a/source4/smbd/server.c b/source4/smbd/server.c
index beac4ff..ed81c01 100644
--- a/source4/smbd/server.c
+++ b/source4/smbd/server.c
@@ -238,10 +238,11 @@ static void prime_ldb_databases(struct tevent_context *event_ctx)
 	db_context = talloc_new(event_ctx);
 
 	samdb_connect(db_context,
-			event_ctx,
-			cmdline_lp_ctx,
-			system_session(cmdline_lp_ctx),
-			0);
+		      event_ctx,
+		      cmdline_lp_ctx,
+		      system_session(cmdline_lp_ctx),
+		      NULL,
+		      0);
 	privilege_connect(db_context, cmdline_lp_ctx);
 
 	/* we deliberately leave these open, which allows them to be
diff --git a/source4/torture/dns/dlz_bind9.c b/source4/torture/dns/dlz_bind9.c
index d948487..ef7220b 100644
--- a/source4/torture/dns/dlz_bind9.c
+++ b/source4/torture/dns/dlz_bind9.c
@@ -88,13 +88,16 @@ static isc_result_t dlz_bind9_writeable_zone_hook(dns_view_t *view,
 	struct torture_context *tctx = talloc_get_type((void *)view, struct torture_context);
 	struct ldb_context *samdb = NULL;
 	char *errstring = NULL;
-	int ret = samdb_connect_url(tctx, NULL, tctx->lp_ctx,
-				    system_session(tctx->lp_ctx),
-				    0,
-				    test_dlz_bind9_binddns_dir(tctx, "dns/sam.ldb"),
-				    NULL,
-				    &samdb,
-				    &errstring);
+	int ret = samdb_connect_url(
+			tctx,
+			NULL,
+			tctx->lp_ctx,
+			system_session(tctx->lp_ctx),
+			0,
+			test_dlz_bind9_binddns_dir(tctx, "dns/sam.ldb"),
+			NULL,
+			&samdb,
+			&errstring);
 	struct ldb_message *msg;
 	const char *attrs[] = {
 		NULL
diff --git a/source4/torture/gpo/apply.c b/source4/torture/gpo/apply.c
index 88da0b1..248ddbb 100644
--- a/source4/torture/gpo/apply.c
+++ b/source4/torture/gpo/apply.c
@@ -119,8 +119,12 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 		       "Failed to fetch the gpo update command");
 
 	/* Open and read the samba db and store the initial password settings */
-	samdb = samdb_connect(ctx, tctx->ev, tctx->lp_ctx,
-			      system_session(tctx->lp_ctx), 0);
+	samdb = samdb_connect(ctx,
+			      tctx->ev,
+			      tctx->lp_ctx,
+			      system_session(tctx->lp_ctx),
+			      NULL,
+			      0);
 	torture_assert(tctx, samdb, "Failed to connect to the samdb");
 
 	ret = ldb_search(samdb, ctx, &result, ldb_get_default_basedn(samdb),
diff --git a/source4/torture/libnet/libnet_BecomeDC.c b/source4/torture/libnet/libnet_BecomeDC.c
index ffba17c..c3d66d5 100644
--- a/source4/torture/libnet/libnet_BecomeDC.c
+++ b/source4/torture/libnet/libnet_BecomeDC.c
@@ -148,7 +148,12 @@ bool torture_net_become_dc(struct torture_context *torture)
 	private_dir = talloc_asprintf(s, "%s/%s", location, "private");
 	lpcfg_set_cmdline(lp_ctx, "private dir", private_dir);
 	torture_comment(torture, "Reopen the SAM LDB with system credentials and all replicated data: %s\n", private_dir);
-	ldb = samdb_connect(s, torture->ev, lp_ctx, system_session(lp_ctx), 0);
+	ldb = samdb_connect(s,
+			    torture->ev,
+			    lp_ctx,
+			    system_session(lp_ctx),
+			    NULL,
+			    0);
 	torture_assert_goto(torture, ldb != NULL, ret, cleanup,
 				      talloc_asprintf(torture,
 				      "Failed to open '%s/sam.ldb'\n", private_dir));
diff --git a/source4/winbind/idmap.c b/source4/winbind/idmap.c
index edeb724..86fd354 100644
--- a/source4/winbind/idmap.c
+++ b/source4/winbind/idmap.c
@@ -175,7 +175,12 @@ struct idmap_context *idmap_init(TALLOC_CTX *mem_ctx,
 		goto fail;
 	}
 
-	idmap_ctx->samdb = samdb_connect(idmap_ctx, ev_ctx, lp_ctx, system_session(lp_ctx), 0);
+	idmap_ctx->samdb = samdb_connect(idmap_ctx,
+					 ev_ctx,
+					 lp_ctx,
+					 system_session(lp_ctx),
+					 NULL,
+					 0);
 	if (idmap_ctx->samdb == NULL) {
 		DEBUG(0, ("Failed to load sam.ldb in idmap_init\n"));
 		goto fail;
-- 
2.7.4


From 060cd8543bcb4c4df1633519e88c6d4dbc939716 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 10 Apr 2018 11:45:32 +1200
Subject: [PATCH 14/21] auth_log: Use common code from audit_logging

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 auth/auth_log.c    | 544 ++++++++++-------------------------------------------
 auth/wscript_build |   2 +-
 2 files changed, 103 insertions(+), 443 deletions(-)

diff --git a/auth/auth_log.c b/auth/auth_log.c
index 97b6537..19e0cba 100644
--- a/auth/auth_log.c
+++ b/auth/auth_log.c
@@ -57,48 +57,7 @@
 #include "lib/util/server_id_db.h"
 #include "lib/param/param.h"
 #include "librpc/ndr/libndr.h"
-
-/*
- * Get a human readable timestamp.
- *
- * Returns the current time formatted as
- *  "Tue, 14 Mar 2017 08:38:42.209028 NZDT"
- *
- * The returned string is allocated by talloc in the supplied context.
- * It is the callers responsibility to free it.
- *
- */
-static const char* get_timestamp(TALLOC_CTX *frame)
-{
-	char buffer[40];	/* formatted time less usec and timezone */
-	char tz[10];		/* formatted time zone			 */
-	struct tm* tm_info;	/* current local time			 */
-	struct timeval tv;	/* current system time			 */
-	int r;			/* response code from gettimeofday	 */
-	const char * ts;	/* formatted time stamp			 */
-
-	r = gettimeofday(&tv, NULL);
-	if (r) {
-		DBG_ERR("Unable to get time of day: (%d) %s\n",
-			errno,
-			strerror(errno));
-		return NULL;
-	}
-
-	tm_info = localtime(&tv.tv_sec);
-	if (tm_info == NULL) {
-		DBG_ERR("Unable to determine local time\n");
-		return NULL;
-	}
-
-	strftime(buffer, sizeof(buffer)-1, "%a, %d %b %Y %H:%M:%S", tm_info);
-	strftime(tz, sizeof(tz)-1, "%Z", tm_info);
-	ts = talloc_asprintf(frame, "%s.%06ld %s", buffer, tv.tv_usec, tz);
-	if (ts == NULL) {
-		DBG_ERR("Out of memory formatting time stamp\n");
-	}
-	return ts;
-}
+#include "lib/audit_logging/audit_logging.h"
 
 /*
  * Determine the type of the password supplied for the
@@ -109,349 +68,26 @@ static const char* get_password_type(const struct auth_usersupplied_info *ui);
 
 #ifdef HAVE_JANSSON
 
-#include <jansson.h>
-#include "system/time.h"
-
-/*
- * Context required by the JSON generation
- *  routines
- *
- */
-struct json_context {
-	json_t *root;
-	bool error;
-};
-
-static NTSTATUS get_auth_event_server(struct imessaging_context *msg_ctx,
-				      struct server_id *auth_event_server)
-{
-	NTSTATUS status;
-	TALLOC_CTX *frame = talloc_stackframe();
-	unsigned num_servers, i;
-	struct server_id *servers;
-
-	status = irpc_servers_byname(msg_ctx, frame,
-				     AUTH_EVENT_NAME,
-				     &num_servers, &servers);
-
-	if (!NT_STATUS_IS_OK(status)) {
-		DBG_NOTICE("Failed to find 'auth_event' registered on the "
-			   "message bus to send JSON authentication events to: %s\n",
-			   nt_errstr(status));
-		TALLOC_FREE(frame);
-		return status;
-	}
-
-	/*
-	 * Select the first server that is listening, because
-	 * we get connection refused as
-	 * NT_STATUS_OBJECT_NAME_NOT_FOUND without waiting
-	 */
-	for (i = 0; i < num_servers; i++) {
-		status = imessaging_send(msg_ctx, servers[i], MSG_PING,
-					 &data_blob_null);
-		if (NT_STATUS_IS_OK(status)) {
-			*auth_event_server = servers[i];
-			TALLOC_FREE(frame);
-			return NT_STATUS_OK;
-		}
-	}
-	DBG_NOTICE("Failed to find a running 'auth_event' server "
-		   "registered on the message bus to send JSON "
-		   "authentication events to\n");
-	TALLOC_FREE(frame);
-	return NT_STATUS_OBJECT_NAME_NOT_FOUND;
-}
-
-static void auth_message_send(struct imessaging_context *msg_ctx,
-			      const char *json)
-{
-	struct server_id auth_event_server;
-	NTSTATUS status;
-	DATA_BLOB json_blob = data_blob_string_const(json);
-	if (msg_ctx == NULL) {
-		return;
-	}
-
-	/* Need to refetch the address each time as the destination server may
-	 * have disconnected and reconnected in the interim, in which case
-	 * messages may get lost, manifests in the auth_log tests
-	 */
-	status = get_auth_event_server(msg_ctx, &auth_event_server);
-	if (!NT_STATUS_IS_OK(status)) {
-		return;
-	}
-
-	status = imessaging_send(msg_ctx, auth_event_server, MSG_AUTH_LOG,
-				 &json_blob);
-
-	/* If the server crashed, try to find it again */
-	if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
-		status = get_auth_event_server(msg_ctx, &auth_event_server);
-		if (!NT_STATUS_IS_OK(status)) {
-			return;
-		}
-		imessaging_send(msg_ctx, auth_event_server, MSG_AUTH_LOG,
-				&json_blob);
-
-	}
-}
-
 /*
  * Write the json object to the debug logs.
  *
  */
 static void log_json(struct imessaging_context *msg_ctx,
 		     struct loadparm_context *lp_ctx,
-		     struct json_context *context,
+		     struct json_object *context,
 		     const char *type, int debug_class, int debug_level)
 {
-	char* json = NULL;
-
-	if (context->error) {
-		return;
-	}
-
-	json = json_dumps(context->root, 0);
-	if (json == NULL) {
-		DBG_ERR("Unable to convert JSON object to string\n");
-		context->error = true;
-		return;
-	}
 
-	DEBUGC(debug_class, debug_level, ("JSON %s: %s\n", type, json));
+	audit_log_json(type, context, debug_class, debug_level);
 	if (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx)) {
-		auth_message_send(msg_ctx, json);
-	}
-
-	if (json) {
-		free(json);
-	}
-
-}
-
-/*
- * Create a new json logging context.
- *
- * Free with a call to free_json_context
- *
- */
-static struct json_context get_json_context(void) {
-
-	struct json_context context;
-	context.error = false;
-
-	context.root = json_object();
-	if (context.root == NULL) {
-		context.error = true;
-		DBG_ERR("Unable to create json_object\n");
-	}
-	return context;
-}
-
-/*
- * free a previously created json_context
- *
- */
-static void free_json_context(struct json_context *context)
-{
-	if (context->root) {
-		json_decref(context->root);
+		audit_message_send(msg_ctx,
+				   AUTH_EVENT_NAME,
+				   MSG_AUTH_LOG,
+				   context);
 	}
 }
 
 /*
- * Output a JSON pair with name name and integer value value
- *
- */
-static void add_int(struct json_context *context,
-		    const char* name,
-		    const int value)
-{
-	int rc = 0;
-
-	if (context->error) {
-		return;
-	}
-
-	rc = json_object_set_new(context->root, name, json_integer(value));
-	if (rc) {
-		DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
-		context->error = true;
-	}
-
-}
-
-/*
- * Output a JSON pair with name name and string value value
- *
- */
-static void add_string(struct json_context *context,
-		       const char* name,
-		       const char* value)
-{
-	int rc = 0;
-
-	if (context->error) {
-		return;
-	}
-
-	if (value) {
-		rc = json_object_set_new(context->root, name, json_string(value));
-	} else {
-		rc = json_object_set_new(context->root, name, json_null());
-	}
-	if (rc) {
-		DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
-		context->error = true;
-	}
-}
-
-
-/*
- * Output a JSON pair with name name and object value
- *
- */
-static void add_object(struct json_context *context,
-		       const char* name,
-		       struct json_context *value)
-{
-	int rc = 0;
-
-	if (value->error) {
-		context->error = true;
-	}
-	if (context->error) {
-		return;
-	}
-	rc = json_object_set_new(context->root, name, value->root);
-	if (rc) {
-		DBG_ERR("Unable to add object [%s]\n", name);
-		context->error = true;
-	}
-}
-
-/*
- * Output a version object
- *
- * "version":{"major":1,"minor":0}
- *
- */
-static void add_version(struct json_context *context, int major, int minor)
-{
-	struct json_context version = get_json_context();
-	add_int(&version, "major", major);
-	add_int(&version, "minor", minor);
-	add_object(context, "version", &version);
-}
-
-/*
- * Output the current date and time as a timestamp in ISO 8601 format
- *
- * "timestamp":"2017-03-06T17:18:04.455081+1300"
- *
- */
-static void add_timestamp(struct json_context *context)
-{
-	char buffer[40];	/* formatted time less usec and timezone */
-	char timestamp[50];	/* the formatted ISO 8601 time stamp	 */
-	char tz[10];		/* formatted time zone			 */
-	struct tm* tm_info;	/* current local time			 */
-	struct timeval tv;	/* current system time			 */
-	int r;			/* response code from gettimeofday	 */
-
-	if (context->error) {
-		return;
-	}
-
-	r = gettimeofday(&tv, NULL);
-	if (r) {
-		DBG_ERR("Unable to get time of day: (%d) %s\n",
-			errno,
-			strerror(errno));
-		context->error = true;
-		return;
-	}
-
-	tm_info = localtime(&tv.tv_sec);
-	if (tm_info == NULL) {
-		DBG_ERR("Unable to determine local time\n");
-		context->error = true;
-		return;
-	}
-
-	strftime(buffer, sizeof(buffer)-1, "%Y-%m-%dT%T", tm_info);
-	strftime(tz, sizeof(tz)-1, "%z", tm_info);
-	snprintf(timestamp, sizeof(timestamp),"%s.%06ld%s",
-		 buffer, tv.tv_usec, tz);
-	add_string(context,"timestamp", timestamp);
-}
-
-
-/*
- * Output an address pair, with name name.
- *
- * "localAddress":"ipv6::::0"
- *
- */
-static void add_address(struct json_context *context,
-			const char *name,
-			const struct tsocket_address *address)
-{
-	char *s = NULL;
-	TALLOC_CTX *frame = talloc_stackframe();
-
-	if (context->error) {
-		return;
-	}
-
-	s = tsocket_address_string(address, frame);
-	add_string(context, name, s);
-	talloc_free(frame);
-
-}
-
-/*
- * Output a SID with name name
- *
- * "sid":"S-1-5-18"
- *
- */
-static void add_sid(struct json_context *context,
-		    const char *name,
-		    const struct dom_sid *sid)
-{
-	char sid_buf[DOM_SID_STR_BUFLEN];
-
-	if (context->error) {
-		return;
-	}
-
-	dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
-	add_string(context, name, sid_buf);
-}
-
-/*
- * Add a formatted string representation of a GUID to a json object.
- *
- */
-static void add_guid(struct json_context *context,
-		     const char *name,
-		     struct GUID *guid)
-{
-
-	char *guid_str;
-	struct GUID_txt_buf guid_buff;
-
-	if (context->error) {
-		return;
-	}
-
-	guid_str = GUID_buf_string(guid, &guid_buff);
-	add_string(context, name, guid_str);
-}
-
-/*
  * Write a machine parsable json formatted authentication log entry.
  *
  * IF removing or changing the format/meaning of a field please update the
@@ -482,57 +118,72 @@ static void log_authentication_event_json(
 			struct dom_sid *sid,
 			int debug_level)
 {
-	struct json_context context = get_json_context();
-	struct json_context authentication;
+	struct json_object context = json_new_object();
+	struct json_object authentication;
 	char negotiate_flags[11];
 
-	add_timestamp(&context);
-	add_string(&context, "type", AUTH_JSON_TYPE);
-
-	authentication = get_json_context();
-	add_version(&authentication, AUTH_MAJOR, AUTH_MINOR);
-	add_string(&authentication, "status", nt_errstr(status));
-	add_address(&authentication, "localAddress", ui->local_host);
-	add_address(&authentication, "remoteAddress", ui->remote_host);
-	add_string(&authentication,
-		   "serviceDescription",
-		   ui->service_description);
-	add_string(&authentication, "authDescription", ui->auth_description);
-	add_string(&authentication, "clientDomain", ui->client.domain_name);
-	add_string(&authentication, "clientAccount", ui->client.account_name);
-	add_string(&authentication, "workstation", ui->workstation_name);
-	add_string(&authentication, "becameAccount", account_name);
-	add_string(&authentication, "becameDomain", domain_name);
-	add_sid(&authentication, "becameSid", sid);
-	add_string(&authentication, "mappedAccount", ui->mapped.account_name);
-	add_string(&authentication, "mappedDomain", ui->mapped.domain_name);
-	add_string(&authentication,
-		   "netlogonComputer",
-		   ui->netlogon_trust_account.computer_name);
-	add_string(&authentication,
-		   "netlogonTrustAccount",
-		   ui->netlogon_trust_account.account_name);
+	json_add_timestamp(&context);
+	json_add_string(&context, "type", AUTH_JSON_TYPE);
+
+	authentication = json_new_object();
+	json_add_version(&authentication, AUTH_MAJOR, AUTH_MINOR);
+	json_add_string(&authentication, "status", nt_errstr(status));
+	json_add_address(&authentication, "localAddress", ui->local_host);
+	json_add_address(&authentication, "remoteAddress", ui->remote_host);
+	json_add_string(&authentication,
+			"serviceDescription",
+			ui->service_description);
+	json_add_string(&authentication,
+			"authDescription",
+			ui->auth_description);
+	json_add_string(&authentication,
+			"clientDomain",
+			ui->client.domain_name);
+	json_add_string(&authentication,
+			"clientAccount",
+			ui->client.account_name);
+	json_add_string(&authentication,
+			"workstation",
+			ui->workstation_name);
+	json_add_string(&authentication, "becameAccount", account_name);
+	json_add_string(&authentication, "becameDomain", domain_name);
+	json_add_sid(&authentication, "becameSid", sid);
+	json_add_string(&authentication,
+			"mappedAccount",
+			ui->mapped.account_name);
+	json_add_string(&authentication,
+			"mappedDomain",
+			ui->mapped.domain_name);
+	json_add_string(&authentication,
+			"netlogonComputer",
+			ui->netlogon_trust_account.computer_name);
+	json_add_string(&authentication,
+			"netlogonTrustAccount",
+			ui->netlogon_trust_account.account_name);
 	snprintf(negotiate_flags,
 		 sizeof( negotiate_flags),
 		 "0x%08X",
 		 ui->netlogon_trust_account.negotiate_flags);
-	add_string(&authentication, "netlogonNegotiateFlags", negotiate_flags);
-	add_int(&authentication,
-		"netlogonSecureChannelType",
-		ui->netlogon_trust_account.secure_channel_type);
-	add_sid(&authentication,
-		"netlogonTrustAccountSid",
-		ui->netlogon_trust_account.sid);
-	add_string(&authentication, "passwordType", get_password_type(ui));
-	add_object(&context,AUTH_JSON_TYPE, &authentication);
-
-	log_json(msg_ctx,
-		 lp_ctx,
-		 &context,
-		 AUTH_JSON_TYPE,
-		 DBGC_AUTH_AUDIT,
-		 debug_level);
-	free_json_context(&context);
+	json_add_string(&authentication,
+			"netlogonNegotiateFlags",
+			negotiate_flags);
+	json_add_int(&authentication,
+		     "netlogonSecureChannelType",
+		     ui->netlogon_trust_account.secure_channel_type);
+	json_add_sid(&authentication,
+		     "netlogonTrustAccountSid",
+		     ui->netlogon_trust_account.sid);
+	json_add_string(&authentication, "passwordType", get_password_type(ui));
+	json_add_object(&context,AUTH_JSON_TYPE, &authentication);
+
+	log_json(
+		msg_ctx,
+		lp_ctx,
+		&context,
+		AUTH_JSON_TYPE,
+		DBGC_AUTH_AUDIT,
+		debug_level);
+	json_free(&context);
 }
 
 /*
@@ -566,36 +217,45 @@ static void log_successful_authz_event_json(
 				struct auth_session_info *session_info,
 				int debug_level)
 {
-	struct json_context context = get_json_context();
-	struct json_context authorization;
+	struct json_object context = json_new_object();
+	struct json_object authorization;
 	char account_flags[11];
 
-	//start_object(&context, NULL);
-	add_timestamp(&context);
-	add_string(&context, "type", AUTHZ_JSON_TYPE);
-	authorization = get_json_context();
-	add_version(&authorization, AUTHZ_MAJOR, AUTHZ_MINOR);
-	add_address(&authorization, "localAddress", local);
-	add_address(&authorization, "remoteAddress", remote);
-	add_string(&authorization, "serviceDescription", service_description);
-	add_string(&authorization, "authType", auth_type);
-	add_string(&authorization, "domain", session_info->info->domain_name);
-	add_string(&authorization, "account", session_info->info->account_name);
-	add_sid(&authorization, "sid", &session_info->security_token->sids[0]);
-	add_guid(&authorization,
-		 "sessionId",
-		 &session_info->unique_session_token);
-	add_string(&authorization,
-		   "logonServer",
-		   session_info->info->logon_server);
-	add_string(&authorization, "transportProtection", transport_protection);
+	json_add_timestamp(&context);
+	json_add_string(&context, "type", AUTHZ_JSON_TYPE);
+	authorization = json_new_object();
+	json_add_version(&authorization, AUTHZ_MAJOR, AUTHZ_MINOR);
+	json_add_address(&authorization, "localAddress", local);
+	json_add_address(&authorization, "remoteAddress", remote);
+	json_add_string(&authorization,
+			"serviceDescription",
+			service_description);
+	json_add_string(&authorization, "authType", auth_type);
+	json_add_string(&authorization,
+			"domain",
+			session_info->info->domain_name);
+	json_add_string(&authorization,
+			"account",
+			session_info->info->account_name);
+	json_add_sid(&authorization,
+		     "sid",
+		     &session_info->security_token->sids[0]);
+	json_add_guid(&authorization,
+		      "sessionId",
+		      &session_info->unique_session_token);
+	json_add_string(&authorization,
+			"logonServer",
+			session_info->info->logon_server);
+	json_add_string(&authorization,
+			"transportProtection",
+			transport_protection);
 
 	snprintf(account_flags,
 		 sizeof(account_flags),
 		 "0x%08X",
 		 session_info->info->acct_flags);
-	add_string(&authorization, "accountFlags", account_flags);
-	add_object(&context,AUTHZ_JSON_TYPE, &authorization);
+	json_add_string(&authorization, "accountFlags", account_flags);
+	json_add_object(&context, AUTHZ_JSON_TYPE, &authorization);
 
 	log_json(msg_ctx,
 		 lp_ctx,
@@ -603,7 +263,7 @@ static void log_successful_authz_event_json(
 		 AUTHZ_JSON_TYPE,
 		 DBGC_AUTH_AUDIT,
 		 debug_level);
-	free_json_context(&context);
+	json_free(&context);
 }
 
 #else
@@ -738,7 +398,7 @@ static void log_authentication_event_human_readable(
 
 	password_type = get_password_type(ui);
 	/* Get the current time */
-        ts = get_timestamp(frame);
+        ts = audit_get_timestamp(frame);
 
 	/* Only log the NETLOGON details if they are present */
 	if (ui->netlogon_trust_account.computer_name ||
@@ -869,7 +529,7 @@ static void log_successful_authz_event_human_readable(
 	frame = talloc_stackframe();
 
 	/* Get the current time */
-        ts = get_timestamp(frame);
+        ts = audit_get_timestamp(frame);
 
 	remote_str = tsocket_address_string(remote, frame);
 	local_str = tsocket_address_string(local, frame);
diff --git a/auth/wscript_build b/auth/wscript_build
index 88e9a03..e2e3d21 100644
--- a/auth/wscript_build
+++ b/auth/wscript_build
@@ -2,7 +2,7 @@
 
 bld.SAMBA_LIBRARY('common_auth',
                   source='auth_sam_reply.c wbc_auth_util.c auth_log.c',
-                  deps='talloc samba-security samba-util util_str_escape LIBTSOCKET jansson MESSAGING_SEND server_id_db ',
+                  deps='talloc samba-security samba-util util_str_escape LIBTSOCKET audit_logging jansson MESSAGING_SEND server_id_db ',
                   private_library=True)
 
 bld.RECURSE('gensec')
-- 
2.7.4


From 2dfa39de6f08b75b15c1b83a2dbe4f553aaf0d74 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 10 Apr 2018 11:57:41 +1200
Subject: [PATCH 15/21] auth_log: tidy up code formatting

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 auth/auth_log.c | 176 +++++++++++++++++++++++++++++---------------------------
 1 file changed, 91 insertions(+), 85 deletions(-)

diff --git a/auth/auth_log.c b/auth/auth_log.c
index 19e0cba..69e28bf 100644
--- a/auth/auth_log.c
+++ b/auth/auth_log.c
@@ -74,16 +74,18 @@ static const char* get_password_type(const struct auth_usersupplied_info *ui);
  */
 static void log_json(struct imessaging_context *msg_ctx,
 		     struct loadparm_context *lp_ctx,
-		     struct json_object *context,
-		     const char *type, int debug_class, int debug_level)
+		     struct json_object *object,
+		     const char *type,
+		     int debug_class,
+		     int debug_level)
 {
 
-	audit_log_json(type, context, debug_class, debug_level);
+	audit_log_json(type, object, debug_class, debug_level);
 	if (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx)) {
 		audit_message_send(msg_ctx,
 				   AUTH_EVENT_NAME,
 				   MSG_AUTH_LOG,
-				   context);
+				   object);
 	}
 }
 
@@ -108,22 +110,22 @@ static void log_json(struct imessaging_context *msg_ctx,
  *           \t\(.Authentication.localAddress)"'
  */
 static void log_authentication_event_json(
-	                struct imessaging_context *msg_ctx,
-			struct loadparm_context *lp_ctx,
-			const struct auth_usersupplied_info *ui,
-			NTSTATUS status,
-			const char *domain_name,
-			const char *account_name,
-			const char *unix_username,
-			struct dom_sid *sid,
-			int debug_level)
+	struct imessaging_context *msg_ctx,
+	struct loadparm_context *lp_ctx,
+	const struct auth_usersupplied_info *ui,
+	NTSTATUS status,
+	const char *domain_name,
+	const char *account_name,
+	const char *unix_username,
+	struct dom_sid *sid,
+	int debug_level)
 {
-	struct json_object context = json_new_object();
+	struct json_object wrapper = json_new_object();
 	struct json_object authentication;
 	char negotiate_flags[11];
 
-	json_add_timestamp(&context);
-	json_add_string(&context, "type", AUTH_JSON_TYPE);
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", AUTH_JSON_TYPE);
 
 	authentication = json_new_object();
 	json_add_version(&authentication, AUTH_MAJOR, AUTH_MINOR);
@@ -174,16 +176,16 @@ static void log_authentication_event_json(
 		     "netlogonTrustAccountSid",
 		     ui->netlogon_trust_account.sid);
 	json_add_string(&authentication, "passwordType", get_password_type(ui));
-	json_add_object(&context,AUTH_JSON_TYPE, &authentication);
+	json_add_object(&wrapper, AUTH_JSON_TYPE, &authentication);
 
 	log_json(
 		msg_ctx,
 		lp_ctx,
-		&context,
+		&wrapper,
 		AUTH_JSON_TYPE,
 		DBGC_AUTH_AUDIT,
 		debug_level);
-	json_free(&context);
+	json_free(&wrapper);
 }
 
 /*
@@ -207,22 +209,22 @@ static void log_authentication_event_json(
  *
  */
 static void log_successful_authz_event_json(
-				struct imessaging_context *msg_ctx,
-				struct loadparm_context *lp_ctx,
-				const struct tsocket_address *remote,
-				const struct tsocket_address *local,
-				const char *service_description,
-				const char *auth_type,
-				const char *transport_protection,
-				struct auth_session_info *session_info,
-				int debug_level)
+	struct imessaging_context *msg_ctx,
+	struct loadparm_context *lp_ctx,
+	const struct tsocket_address *remote,
+	const struct tsocket_address *local,
+	const char *service_description,
+	const char *auth_type,
+	const char *transport_protection,
+	struct auth_session_info *session_info,
+	int debug_level)
 {
-	struct json_object context = json_new_object();
+	struct json_object wrapper = json_new_object();
 	struct json_object authorization;
 	char account_flags[11];
 
-	json_add_timestamp(&context);
-	json_add_string(&context, "type", AUTHZ_JSON_TYPE);
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", AUTHZ_JSON_TYPE);
 	authorization = json_new_object();
 	json_add_version(&authorization, AUTHZ_MAJOR, AUTHZ_MINOR);
 	json_add_address(&authorization, "localAddress", local);
@@ -255,15 +257,15 @@ static void log_successful_authz_event_json(
 		 "0x%08X",
 		 session_info->info->acct_flags);
 	json_add_string(&authorization, "accountFlags", account_flags);
-	json_add_object(&context, AUTHZ_JSON_TYPE, &authorization);
+	json_add_object(&wrapper, AUTHZ_JSON_TYPE, &authorization);
 
 	log_json(msg_ctx,
 		 lp_ctx,
-		 &context,
+		 &wrapper,
 		 AUTHZ_JSON_TYPE,
 		 DBGC_AUTH_AUDIT,
 		 debug_level);
-	json_free(&context);
+	json_free(&wrapper);
 }
 
 #else
@@ -275,13 +277,15 @@ static void log_no_json(struct imessaging_context *msg_ctx,
 		static bool auth_event_logged = false;
 		if (auth_event_logged == false) {
 			auth_event_logged = true;
-			DBG_ERR("auth event notification = true but Samba was not compiled with jansson\n");
+			DBG_ERR("auth event notification = true but Samba was "
+				"not compiled with jansson\n");
 		}
 	} else {
 		static bool json_logged = false;
 		if (json_logged == false) {
 			json_logged = true;
-			DBG_NOTICE("JSON auth logs not available unless compiled with jansson\n");
+			DBG_NOTICE("JSON auth logs not available unless "
+				   "compiled with jansson\n");
 		}
 	}
 
@@ -289,30 +293,30 @@ static void log_no_json(struct imessaging_context *msg_ctx,
 }
 
 static void log_authentication_event_json(
-	                struct imessaging_context *msg_ctx,
-			struct loadparm_context *lp_ctx,
-			const struct auth_usersupplied_info *ui,
-			NTSTATUS status,
-			const char *domain_name,
-			const char *account_name,
-			const char *unix_username,
-			struct dom_sid *sid,
-			int debug_level)
+	struct imessaging_context *msg_ctx,
+	struct loadparm_context *lp_ctx,
+	const struct auth_usersupplied_info *ui,
+	NTSTATUS status,
+	const char *domain_name,
+	const char *account_name,
+	const char *unix_username,
+	struct dom_sid *sid,
+	int debug_level)
 {
 	log_no_json(msg_ctx, lp_ctx);
 	return;
 }
 
 static void log_successful_authz_event_json(
-				struct imessaging_context *msg_ctx,
-				struct loadparm_context *lp_ctx,
-				const struct tsocket_address *remote,
-				const struct tsocket_address *local,
-				const char *service_description,
-				const char *auth_type,
-				const char *transport_protection,
-				struct auth_session_info *session_info,
-				int debug_level)
+	struct imessaging_context *msg_ctx,
+	struct loadparm_context *lp_ctx,
+	const struct tsocket_address *remote,
+	const struct tsocket_address *local,
+	const char *service_description,
+	const char *auth_type,
+	const char *transport_protection,
+	struct auth_session_info *session_info,
+	int debug_level)
 {
 	log_no_json(msg_ctx, lp_ctx);
 	return;
@@ -375,13 +379,13 @@ static const char* get_password_type(const struct auth_usersupplied_info *ui)
  *
  */
 static void log_authentication_event_human_readable(
-			const struct auth_usersupplied_info *ui,
-			NTSTATUS status,
-			const char *domain_name,
-			const char *account_name,
-			const char *unix_username,
-			struct dom_sid *sid,
-			int debug_level)
+	const struct auth_usersupplied_info *ui,
+	NTSTATUS status,
+	const char *domain_name,
+	const char *account_name,
+	const char *unix_username,
+	struct dom_sid *sid,
+	int debug_level)
 {
 	TALLOC_CTX *frame = NULL;
 
@@ -450,7 +454,7 @@ static void log_authentication_event_human_readable(
 		logon_line,
 		local,
 		nl ? nl : ""
-	       ));
+	));
 
 	talloc_free(frame);
 }
@@ -462,14 +466,15 @@ static void log_authentication_event_human_readable(
  * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the
  * authentication events over the message bus.
  */
-void log_authentication_event(struct imessaging_context *msg_ctx,
-			      struct loadparm_context *lp_ctx,
-			      const struct auth_usersupplied_info *ui,
-			      NTSTATUS status,
-			      const char *domain_name,
-			      const char *account_name,
-			      const char *unix_username,
-			      struct dom_sid *sid)
+void log_authentication_event(
+	struct imessaging_context *msg_ctx,
+	struct loadparm_context *lp_ctx,
+	const struct auth_usersupplied_info *ui,
+	NTSTATUS status,
+	const char *domain_name,
+	const char *account_name,
+	const char *unix_username,
+	struct dom_sid *sid)
 {
 	/* set the log level */
 	int debug_level = AUTH_FAILURE_LEVEL;
@@ -511,13 +516,13 @@ void log_authentication_event(struct imessaging_context *msg_ctx,
  *
  */
 static void log_successful_authz_event_human_readable(
-				const struct tsocket_address *remote,
-				const struct tsocket_address *local,
-				const char *service_description,
-				const char *auth_type,
-				const char *transport_protection,
-				struct auth_session_info *session_info,
-				int debug_level)
+	const struct tsocket_address *remote,
+	const struct tsocket_address *local,
+	const char *service_description,
+	const char *auth_type,
+	const char *transport_protection,
+	struct auth_session_info *session_info,
+	int debug_level)
 {
 	TALLOC_CTX *frame = NULL;
 
@@ -567,14 +572,15 @@ static void log_successful_authz_event_human_readable(
  * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the
  * authentication events over the message bus.
  */
-void log_successful_authz_event(struct imessaging_context *msg_ctx,
-				struct loadparm_context *lp_ctx,
-				const struct tsocket_address *remote,
-				const struct tsocket_address *local,
-				const char *service_description,
-				const char *auth_type,
-				const char *transport_protection,
-				struct auth_session_info *session_info)
+void log_successful_authz_event(
+	struct imessaging_context *msg_ctx,
+	struct loadparm_context *lp_ctx,
+	const struct tsocket_address *remote,
+	const struct tsocket_address *local,
+	const char *service_description,
+	const char *auth_type,
+	const char *transport_protection,
+	struct auth_session_info *session_info)
 {
 	int debug_level = AUTHZ_SUCCESS_LEVEL;
 
-- 
2.7.4


From 9fbaada8aa5d9a608c25a1e008a127720a0c3cc2 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 16 Apr 2018 07:59:43 +1200
Subject: [PATCH 16/21] samdb: Add transaction id control

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source4/dsdb/samdb/samdb.h          | 9 +++++++++
 source4/libcli/ldap/ldap_controls.c | 1 +
 source4/setup/schema_samba4.ldif    | 1 +
 3 files changed, 11 insertions(+)

diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h
index d2686af..65d22ea 100644
--- a/source4/dsdb/samdb/samdb.h
+++ b/source4/dsdb/samdb/samdb.h
@@ -204,6 +204,15 @@ struct dsdb_control_password_acl_validation {
 	bool pwd_reset;
 };
 
+/*
+ * Used to pass the current transaction identifier from the audit_log
+ * module to group membership auditing module
+ */
+#define DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID "1.3.6.1.4.1.7165.4.3.34"
+struct dsdb_control_transaction_identifier {
+	struct GUID transaction_guid;
+};
+
 #define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1"
 struct dsdb_extended_replicated_object {
 	struct ldb_message *msg;
diff --git a/source4/libcli/ldap/ldap_controls.c b/source4/libcli/ldap/ldap_controls.c
index 7ecc908..716ca14 100644
--- a/source4/libcli/ldap/ldap_controls.c
+++ b/source4/libcli/ldap/ldap_controls.c
@@ -1272,6 +1272,7 @@ static const struct ldap_control_handler ldap_known_controls[] = {
 	{ DSDB_EXTENDED_ALLOCATE_RID_POOL, NULL, NULL },
 	{ DSDB_CONTROL_NO_GLOBAL_CATALOG, NULL, NULL },
 	{ DSDB_EXTENDED_SCHEMA_UPGRADE_IN_PROGRESS_OID, NULL, NULL },
+	{ DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID, NULL, NULL},
 	{ NULL, NULL, NULL }
 };
 
diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif
index 6aafc9e..5b26dc0 100644
--- a/source4/setup/schema_samba4.ldif
+++ b/source4/setup/schema_samba4.ldif
@@ -227,6 +227,7 @@
 #Allocated: DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE 1.3.6.1.4.1.7165.4.3.31
 #Allocated: DSDB_CONTROL_INVALID_NOT_IMPLEMENTED 1.3.6.1.4.1.7165.4.3.32
 #Allocated: DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID 1.3.6.1.4.1.7165.4.3.33
+#Allocated: DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID 1.3.6.1.4.1.7165.4.3.34
 
 
 # Extended 1.3.6.1.4.1.7165.4.4.x
-- 
2.7.4


From f587c2a9927e32c1cd4145ab80345bf422e3e436 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 16 Apr 2018 14:01:13 +1200
Subject: [PATCH 17/21] SamDb audit logging: Add transaction id control to
 requests

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source4/dsdb/samdb/ldb_modules/audit_log.c | 37 ++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/source4/dsdb/samdb/ldb_modules/audit_log.c b/source4/dsdb/samdb/ldb_modules/audit_log.c
index 67182d2..b0c8e13 100644
--- a/source4/dsdb/samdb/ldb_modules/audit_log.c
+++ b/source4/dsdb/samdb/ldb_modules/audit_log.c
@@ -725,6 +725,31 @@ static int audit_callback(struct ldb_request *req, struct ldb_reply *ares)
 	}
 }
 
+static int add_transaction_id(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+	struct dsdb_control_transaction_identifier *transaction_id;
+
+	transaction_id = talloc_zero(
+		req,
+		struct dsdb_control_transaction_identifier);
+	if (transaction_id == NULL) {
+		struct ldb_context *ldb = ldb_module_get_ctx(module);
+		return ldb_oom(ldb);
+	}
+	transaction_id->transaction_guid = ac->transaction_guid;
+	ldb_request_add_control(req,
+				DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID,
+				false,
+				transaction_id);
+	return LDB_SUCCESS;
+
+}
+
 static int log_add(
 	struct ldb_module *module,
 	struct ldb_request *req)
@@ -758,6 +783,10 @@ static int log_add(
 	if (ret != LDB_SUCCESS) {
 		return ret;
 	}
+	ret = add_transaction_id(module, new_req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
 	return ldb_next_request(module, new_req);
 }
 
@@ -794,6 +823,10 @@ static int log_delete(
 	if (ret != LDB_SUCCESS) {
 		return ret;
 	}
+	ret = add_transaction_id(module, new_req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
 	return ldb_next_request(module, new_req);
 }
 
@@ -830,6 +863,10 @@ static int log_modify(
 	if (ret != LDB_SUCCESS) {
 		return ret;
 	}
+	ret = add_transaction_id(module, new_req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
 	return ldb_next_request(module, new_req);
 }
 
-- 
2.7.4


From 6eb91f54793bd5d8c091a74d7e5f6602403b9762 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 23 Apr 2018 08:49:26 +1200
Subject: [PATCH 18/21] messaging idl add group membersip events

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 librpc/idl/messaging.idl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/librpc/idl/messaging.idl b/librpc/idl/messaging.idl
index 73cceff..b0f0ef0 100644
--- a/librpc/idl/messaging.idl
+++ b/librpc/idl/messaging.idl
@@ -145,6 +145,7 @@ interface messaging
 		MSG_AUTH_LOG                    = 0x0800,
 		MSG_SAMDB_LOG                   = 0x0801,
 		MSG_PWD_LOG                     = 0x0802,
+		MSG_GROUP_LOG                   = 0x0803,
 
 		/* dbwrap messages 4001-4999 (0x0FA0 - 0x1387) */
 		/* MSG_DBWRAP_TDB2_CHANGES		= 4001, */
@@ -184,4 +185,5 @@ interface messaging
         const string AUTH_EVENT_NAME  = "auth_event";
 	const string SAMDB_EVENT_NAME = "samdb_event";
 	const string PWD_EVENT_NAME   = "password_event";
+	const string GROUP_EVENT_NAME = "group_event";
 }
-- 
2.7.4


From e82a16a1e1a2ffd6830e6cba07c005be283b9a29 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 23 Apr 2018 09:00:54 +1200
Subject: [PATCH 19/21] smb.conf: Add group change notification parameter

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 .../smbdotconf/misc/groupchangenotification.xml    | 27 ++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 docs-xml/smbdotconf/misc/groupchangenotification.xml

diff --git a/docs-xml/smbdotconf/misc/groupchangenotification.xml b/docs-xml/smbdotconf/misc/groupchangenotification.xml
new file mode 100644
index 0000000..09278cc
--- /dev/null
+++ b/docs-xml/smbdotconf/misc/groupchangenotification.xml
@@ -0,0 +1,27 @@
+<samba:parameter name="group change notification"
+                 context="G"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+	<para>When enabled, this option causes Samba (acting as an
+	Active Directory Domain Controller) to stream group membership change
+	events across the internal message bus.  Scripts built using
+	Samba's python bindings can listen to these events by
+	registering as the service
+	<filename moreinfo="none">group_event</filename>.</para>
+
+	<para>This should be considered a developer option (it assists
+	in the Samba testsuite) rather than a facility for external
+	auditing, as message delivery is not guaranteed (a feature
+	that the testsuite works around).  Additionally Samba must be
+	compiled with the jansson support for this option to be
+	effective.</para>
+
+	<para>The group events are also logged via the normal
+	logging methods when the <smbconfoption name="log level"/> is
+	set appropriately.</para>
+
+</description>
+
+<value type="default">no</value>
+</samba:parameter>
-- 
2.7.4


From 24d409c4706e6986eacf85567e1f4ae1d8e3f5a6 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 23 Apr 2018 12:24:34 +1200
Subject: [PATCH 20/21] debug: Add group logging classes

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 lib/util/debug.c | 2 ++
 lib/util/debug.h | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/lib/util/debug.c b/lib/util/debug.c
index db4f71a..9e6b5d8 100644
--- a/lib/util/debug.c
+++ b/lib/util/debug.c
@@ -549,6 +549,8 @@ static const char *default_classname_table[] = {
 	[DBGC_PWD_AUDIT_JSON] = "password_json_audit",
 	[DBGC_TRN_AUDIT]  =	"transaction_audit",
 	[DBGC_TRN_AUDIT_JSON] = "transaction_json_audit",
+	[DBGC_GROUP_AUDIT] =	"group_audit",
+	[DBGC_GROUP_AUDIT_JSON] = "group_json_audit",
 };
 
 /*
diff --git a/lib/util/debug.h b/lib/util/debug.h
index 4e425a0..67b737e 100644
--- a/lib/util/debug.h
+++ b/lib/util/debug.h
@@ -101,6 +101,8 @@ bool dbghdr( int level, const char *location, const char *func);
 #define DBGC_PWD_AUDIT_JSON	33
 #define DBGC_TRN_AUDIT		34
 #define DBGC_TRN_AUDIT_JSON	35
+#define DBGC_GROUP_AUDIT	36
+#define DBGC_GROUP_AUDIT_JSON	37
 
 /* So you can define DBGC_CLASS before including debug.h */
 #ifndef DBGC_CLASS
-- 
2.7.4


From 64a695f35f124b8c95971bde1a197d11c99857b7 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 16 Apr 2018 14:03:14 +1200
Subject: [PATCH 21/21] SamDb: Audit group membership changes.:w

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
---
 source4/dsdb/samdb/ldb_modules/group_audit.c       | 936 +++++++++++++++++++++
 source4/dsdb/samdb/ldb_modules/samba_dsdb.c        |   1 +
 .../dsdb/samdb/ldb_modules/wscript_build_server    |  16 +
 3 files changed, 953 insertions(+)
 create mode 100644 source4/dsdb/samdb/ldb_modules/group_audit.c

diff --git a/source4/dsdb/samdb/ldb_modules/group_audit.c b/source4/dsdb/samdb/ldb_modules/group_audit.c
new file mode 100644
index 0000000..019a829
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/group_audit.c
@@ -0,0 +1,936 @@
+/*
+   ldb database library
+
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+
+   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/>.
+*/
+
+/*
+ * Provide an audit log of changes made to group memberships
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/dom_sid.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+
+#define AUDIT_JSON_TYPE "groupChange"
+#define AUDIT_HR_TAG "Group Change"
+#define AUDIT_MAJOR 1
+#define AUDIT_MINOR 0
+#define GROUP_LOG_LVL 5
+
+static const char * const member_attr[]        = {"member", NULL};
+static const char * const primary_group_attr[] = {"primaryGroupID",
+						   "objectSID",
+						   NULL};
+
+struct audit_context {
+	bool send_events;
+	struct imessaging_context *msg_ctx;
+};
+
+struct audit_callback_context {
+	struct ldb_request *request;
+	struct ldb_module *module;
+	struct ldb_message_element *members;
+	uint32_t primary_group;
+	void (*log_changes)(struct audit_callback_context *acc,
+			    const int status);
+};
+
+static struct GUID *get_transaction_id(
+	const struct ldb_request *request)
+{
+	struct ldb_control *control;
+	struct dsdb_control_transaction_identifier *transaction_id;
+
+	control = ldb_request_get_control(
+		discard_const(request),
+		DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID);
+	if (control == NULL) {
+		return NULL;
+	}
+	transaction_id = talloc_get_type(
+		control->data,
+		struct dsdb_control_transaction_identifier);
+	if (transaction_id == NULL) {
+		return NULL;
+	}
+	return &transaction_id->transaction_guid;
+}
+
+static struct json_object audit_group_json(
+	const struct ldb_module *module,
+	const struct ldb_request *request,
+	const char *action,
+	const char *user,
+	const char *group,
+	const int status)
+{
+	struct ldb_context *ldb = NULL;
+	const struct dom_sid *sid = NULL;
+	struct json_object wrapper;
+	struct json_object audit;
+	const struct tsocket_address *remote = NULL;
+	const struct GUID *unique_session_token = NULL;
+	struct GUID *transaction_id = NULL;
+
+	ldb = ldb_module_get_ctx(discard_const(module));
+
+	remote = get_remote_address(ldb);
+	sid = get_user_sid(module);
+	unique_session_token = get_unique_session_token(module);
+	transaction_id = get_transaction_id(request);
+
+	audit = json_new_object();
+	json_add_version(&audit, AUDIT_MAJOR, AUDIT_MINOR);
+	json_add_int(&audit, "statusCode", status);
+	json_add_string(&audit, "status", ldb_strerror(status));
+	json_add_string(&audit, "action", action);
+	json_add_address(&audit, "remoteAddress", remote);
+	json_add_sid(&audit, "userSid", sid);
+	json_add_string(&audit, "group", group);
+	json_add_guid(&audit, "transactionId", transaction_id);
+	json_add_guid(&audit, "sessionId", unique_session_token);
+	json_add_string(&audit, "user", user);
+
+	wrapper = json_new_object();
+	json_add_timestamp(&wrapper);
+	json_add_string(&wrapper, "type", AUDIT_JSON_TYPE);
+	json_add_object(&wrapper, AUDIT_JSON_TYPE, &audit);
+
+	return wrapper;
+}
+
+static char *audit_group_human_readable(
+	TALLOC_CTX *mem_ctx,
+	const struct ldb_module *module,
+	const struct ldb_request *request,
+	const char *action,
+	const char *user,
+	const char * group,
+	const int status)
+{
+	struct ldb_context *ldb = NULL;
+	const char *remote_host = NULL;
+	const struct dom_sid *sid = NULL;
+	const char *user_sid = NULL;
+	const char *timestamp = NULL;
+	char *log_entry = NULL;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	ldb = ldb_module_get_ctx(discard_const(module));
+
+	remote_host = get_remote_host(ldb, frame);
+	sid = get_user_sid(module);
+	user_sid = dom_sid_string(frame, sid);
+	timestamp = audit_get_timestamp(frame);
+
+	log_entry = talloc_asprintf(
+		mem_ctx,
+		"[%s] at [%s] status [%s] "
+		"Remote host [%s] SID [%s] Group [%s] User [%s]",
+		action,
+		timestamp,
+		ldb_strerror(status),
+		remote_host,
+		user_sid,
+		group,
+		user);
+	TALLOC_FREE(frame);
+	return log_entry;
+}
+
+/*
+ * Get an array of 'struct parsed_dns' without the parsing.
+ * The parsed_dns are parsed only when needed to avoid the expense of parsing.
+ *
+ * This procedure assumes that the dn's are sorted in GUID order and contains
+ * no duplicates.  This should be valid as the module sits below repl_meta_data
+ * which ensures this.
+ */
+static struct parsed_dn *get_parsed_dns(
+	TALLOC_CTX *mem_ctx,
+	struct ldb_message_element *el)
+{
+	struct parsed_dn *pdn = NULL;
+
+	int i;
+
+	if (el == NULL || el->num_values == 0) {
+		return NULL;
+	}
+
+	pdn = talloc_zero_array(mem_ctx, struct parsed_dn, el->num_values);
+	if (pdn == NULL) {
+		// TODO error handling/logging
+		return NULL;
+	}
+
+	for (i = 0; i < el->num_values; i++) {
+		pdn[i].v = &el->values[i];
+	}
+	return pdn;
+
+}
+
+static int dn_compare(
+	TALLOC_CTX *mem_ctx,
+	struct ldb_context *ldb,
+	struct parsed_dn *old_val,
+	struct parsed_dn *new_val) {
+
+	/*
+	 * Do a binary compare first to avoid unnecessary parsing
+	 */
+	if (data_blob_cmp(old_val->v, new_val->v) == 0) {
+		/*
+		 * Values are equal at a binary level so no need
+		 * for further processing
+		 */
+		return 0;
+	}
+	/*
+	 * Values not equal at the binary level, so lets
+	 * do a GUID ordering compare. To do this we will need to ensure
+	 * that the dn's have been parsed.
+	 */
+	really_parse_trusted_dn(mem_ctx,
+				ldb,
+				old_val,
+				LDB_SYNTAX_DN);
+	really_parse_trusted_dn(mem_ctx,
+				ldb,
+				new_val,
+				LDB_SYNTAX_DN);
+
+
+	return ndr_guid_compare(&new_val->guid, &old_val->guid);
+}
+
+static const char *get_primary_group_dn(
+	TALLOC_CTX *mem_ctx,
+	struct ldb_module *module,
+	struct dom_sid *account_sid,
+	uint32_t primary_group_rid)
+{
+	NTSTATUS status;
+
+	struct ldb_context *ldb = NULL;
+	struct dom_sid *domain_sid = NULL;
+	struct dom_sid *primary_group_sid = NULL;
+	char *sid = NULL;
+	struct ldb_dn *dn = NULL;
+	struct ldb_message *msg = NULL;
+	int rc;
+
+	ldb = ldb_module_get_ctx(module);
+
+	status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
+	if (!NT_STATUS_IS_OK(status)) {
+		return NULL;
+	}
+
+	primary_group_sid = dom_sid_add_rid(
+		mem_ctx,
+		domain_sid,
+		primary_group_rid);
+	if (!primary_group_sid) {
+		return NULL;
+	}
+
+	sid = dom_sid_string(mem_ctx, primary_group_sid);
+	if (sid == NULL) {
+		return NULL;
+	}
+
+	dn = ldb_dn_new_fmt(mem_ctx, ldb, "<SID=%s>", sid);
+	if(dn == NULL) {
+		return sid;
+	}
+	rc = dsdb_search_one(
+		ldb,
+		mem_ctx,
+		&msg,
+		dn,
+		LDB_SCOPE_BASE,
+		NULL,
+		0,
+		NULL);
+	if (rc != LDB_SUCCESS) {
+		return NULL;
+	}
+
+	return ldb_dn_get_linearized(msg->dn);
+}
+
+static void log_primary_group_change(
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const char *action,
+	const char *group,
+	const int  status)
+{
+	const char *user = NULL;
+
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	user = get_primary_dn(request);
+	if (CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT, GROUP_LOG_LVL)) {
+		char *message = NULL;
+		message = audit_group_human_readable(
+			frame,
+			module,
+			request,
+			action,
+			user,
+			group,
+			status);
+		audit_log_hr(
+			AUDIT_HR_TAG,
+			message,
+			DBGC_GROUP_AUDIT,
+			GROUP_LOG_LVL);
+		TALLOC_FREE(message);
+	}
+
+#ifdef HAVE_JANSSON
+	if (CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT_JSON, GROUP_LOG_LVL)) {
+		struct json_object json;
+		json = audit_group_json(
+			module,
+			request,
+			action,
+			user,
+			group,
+			status);
+		audit_log_json(
+			AUDIT_JSON_TYPE,
+			&json,
+			DBGC_GROUP_AUDIT_JSON,
+			GROUP_LOG_LVL);
+		if (ac->send_events) {
+			audit_message_send(
+				ac->msg_ctx,
+				GROUP_EVENT_NAME,
+				MSG_GROUP_LOG,
+				&json);
+		}
+		json_free(&json);
+	}
+#endif
+	TALLOC_FREE(frame);
+}
+
+static void log_membership_change(
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const char *action,
+	const char *user,
+	const int  status)
+{
+	const char *group = NULL;
+	struct audit_context *ac =
+		talloc_get_type(ldb_module_get_private(module),
+				struct audit_context);
+
+	TALLOC_CTX *frame = talloc_stackframe();
+	group = get_primary_dn(request);
+	if (CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT, GROUP_LOG_LVL)) {
+		char *message = NULL;
+		message = audit_group_human_readable(frame,
+						     module,
+						     request,
+						     action,
+						     user,
+						     group,
+						     status);
+		audit_log_hr(
+			AUDIT_HR_TAG,
+			message,
+			DBGC_GROUP_AUDIT,
+			GROUP_LOG_LVL);
+		TALLOC_FREE(message);
+	}
+
+#ifdef HAVE_JANSSON
+	if (CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT_JSON, GROUP_LOG_LVL)) {
+		struct json_object json;
+		json = audit_group_json(
+			module,
+			request,
+			action,
+			user,
+			group,
+			status);
+		audit_log_json(
+			AUDIT_JSON_TYPE,
+			&json,
+			DBGC_GROUP_AUDIT_JSON,
+			GROUP_LOG_LVL);
+		if (ac->send_events) {
+			audit_message_send(
+				ac->msg_ctx,
+				GROUP_EVENT_NAME,
+				MSG_GROUP_LOG,
+				&json);
+		}
+		json_free(&json);
+	}
+#endif
+	TALLOC_FREE(frame);
+}
+
+static void log_membership_changes(
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	struct ldb_message_element *el,
+	struct ldb_message_element *old_el,
+	int status)
+{
+	unsigned int i, old_i, new_i;
+	unsigned int old_num_values;
+	unsigned int max_num_values;
+	unsigned int new_num_values;
+	struct parsed_dn *old_val = NULL;
+	struct parsed_dn *new_val = NULL;
+	struct parsed_dn *new_values = NULL;
+	struct parsed_dn *old_values = NULL;
+	struct ldb_context *ldb = NULL;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	old_num_values = old_el ? old_el->num_values : 0;
+	new_num_values = el ? el->num_values : 0;
+	max_num_values = old_num_values + new_num_values;
+
+	if (max_num_values == 0) {
+		/* There is nothing to do! */
+	}
+
+	old_values = get_parsed_dns(frame, old_el);
+	new_values = get_parsed_dns(frame, el);
+	ldb = ldb_module_get_ctx(module);
+
+	old_i = 0;
+	new_i = 0;
+	for (i = 0; i < max_num_values; i++) {
+		int cmp;
+		if (old_i < old_num_values && new_i < new_num_values) {
+			old_val = &old_values[old_i];
+			new_val = &new_values[new_i];
+			// samba_start_debugger();
+			cmp = dn_compare(frame, ldb, old_val, new_val);
+		} else if (old_i < old_num_values) {
+			/* the new list is empty, read the old list */
+			old_val = &old_values[old_i];
+			new_val = NULL;
+			cmp = -1;
+		} else if (new_i < new_num_values) {
+			/* the old list is empty, read new list */
+			old_val = NULL;
+			new_val = &new_values[new_i];
+			cmp = 1;
+		} else {
+			break;
+		}
+
+		if (cmp < 0) {
+			/*
+			 * Have an entry in the original record that is not in
+			 * the new record. So it's been deleted
+			 */
+			const char *user = NULL;
+			really_parse_trusted_dn(frame,
+						ldb,
+						old_val,
+						LDB_SYNTAX_DN);
+			user = ldb_dn_get_linearized(old_val->dsdb_dn->dn);
+			log_membership_change(module,
+					      request,
+					      "Removed",
+					      user,
+					      status);
+			old_i++;
+		} else if (cmp == 0) {
+			/*
+			 * Value is unchanged so need to log it,
+			 * Note: This currently ignores the setting of the
+			 *       deleted flags. i.e. a change is not logged
+			 *       if the group is deleted or undeleted.
+			 */
+			old_i++;
+			new_i++;
+		} else {
+			/*
+			 * Member in the updated record that's not in the
+			 * original, so it must have been added.
+			 */
+			const char *user = NULL;
+			really_parse_trusted_dn(frame,
+						ldb,
+						new_val,
+						LDB_SYNTAX_DN);
+			user = ldb_dn_get_linearized(new_val->dsdb_dn->dn);
+			log_membership_change(module,
+					      request,
+					      "Added",
+					      user,
+					      status);
+			new_i++;
+		}
+	}
+
+	TALLOC_FREE(frame);
+}
+
+
+static void log_user(
+	struct audit_callback_context *acc,
+	const int status)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	uint32_t new_rid;
+	struct dom_sid *account_sid = NULL;
+	int ret;
+	const struct ldb_message *msg = get_message(acc->request);
+	if (status == LDB_SUCCESS && msg != NULL) {
+		struct ldb_result *res = NULL;
+		ret = dsdb_module_search_dn(acc->module,
+					    frame,
+					    &res,
+					    msg->dn,
+					    primary_group_attr,
+					    DSDB_FLAG_NEXT_MODULE |
+					    DSDB_SEARCH_REVEAL_INTERNALS |
+					    DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+					    NULL);
+		if (ret == LDB_SUCCESS) {
+			new_rid = ldb_msg_find_attr_as_uint(msg,
+							    "primaryGroupID",
+							    ~0);
+			account_sid = samdb_result_dom_sid(frame,
+							   res->msgs[0],
+							   "objectSid");
+		}
+	}
+	/*
+	 * If we don't have a new value then the user has been deleted
+	 * which we currently do not log.
+	 * Otherwise only log if the primary group has actually changed.
+	 */
+	if (account_sid != NULL &&
+	    new_rid != ~0 &&
+	    acc->primary_group!= new_rid) {
+		const char* group = get_primary_group_dn(frame,
+							 acc->module,
+							 account_sid,
+							 new_rid);
+		log_primary_group_change(acc->module,
+					 acc->request,
+					 "PrimaryGroup",
+					 group,
+					 status);
+	}
+	TALLOC_FREE(frame);
+}
+
+static void log_group(
+	struct audit_callback_context *acc,
+	const int status)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct ldb_message_element *new_val = NULL;
+	int ret;
+	const struct ldb_message *msg = get_message(acc->request);
+	if (status == LDB_SUCCESS && msg != NULL) {
+		struct ldb_result *res = NULL;
+		ret = dsdb_module_search_dn(acc->module,
+					    frame,
+					    &res,
+					    msg->dn,
+					    member_attr,
+					    DSDB_FLAG_NEXT_MODULE |
+					    DSDB_SEARCH_REVEAL_INTERNALS |
+					    DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+					    NULL);
+		if (ret == LDB_SUCCESS) {
+			new_val = ldb_msg_find_element(res->msgs[0], "member");
+		}
+	}
+	log_membership_changes(acc->module,
+			       acc->request,
+			       new_val,
+			       acc->members,
+			       status);
+	TALLOC_FREE(frame);
+}
+
+static int group_audit_callback(
+	struct ldb_request *req,
+	struct ldb_reply *ares)
+{
+	struct audit_callback_context *ac = NULL;
+
+	ac = talloc_get_type(req->context,
+			     struct audit_callback_context);
+
+	if (!ares) {
+		return ldb_module_done(ac->request, NULL, NULL,
+				       LDB_ERR_OPERATIONS_ERROR);
+	}
+
+	/* pass on to the callback */
+	switch (ares->type) {
+	case LDB_REPLY_ENTRY:
+		return ldb_module_send_entry(ac->request,
+					     ares->message,
+					     ares->controls);
+
+	case LDB_REPLY_REFERRAL:
+		return ldb_module_send_referral(ac->request,
+						ares->referral);
+
+	case LDB_REPLY_DONE:
+		/*
+		 * Log on DONE now we have a result code
+		 */
+		ac->log_changes(ac, ares->error);
+		return ldb_module_done(ac->request,
+				       ares->controls,
+				       ares->response,
+				       ares->error);
+		break;
+
+	default:
+		/* Can't happen */
+		return LDB_ERR_OPERATIONS_ERROR;
+	}
+}
+
+static bool has_primary_group_id(struct ldb_request *req)
+{
+	struct ldb_message_element *el = NULL;
+	const struct ldb_message *msg = NULL;
+
+	msg = get_message(req);
+	el = ldb_msg_find_element(msg, "primaryGroupID");
+
+	return (el != NULL);
+}
+
+static bool has_group_membership_changes(struct ldb_request *req)
+{
+	struct ldb_message_element *el = NULL;
+	const struct ldb_message *msg = NULL;
+
+	msg = get_message(req);
+	el = ldb_msg_find_element(msg, "member");
+
+	return (el != NULL);
+}
+
+
+static int set_group_add_callback(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	int ret;
+	/*
+	 * Adding group memberships so will need to log the changes.
+	 */
+	ldb = ldb_module_get_ctx(module);
+	context = talloc_zero(req, struct audit_callback_context);
+
+	if (context == NULL) {
+		return ldb_oom(ldb);
+	}
+	context->request = req;
+	context->module = module;
+	context->log_changes = log_group;
+	/*
+	 * We want to log the return code status, so we need to register
+	 * a callback function to get the actual result.
+	 * We need to take a new copy so that we don't alter the callers copy
+	 */
+	ret = ldb_build_add_req(&new_req,
+				ldb,
+				req,
+				req->op.add.message,
+				req->controls,
+				context,
+				group_audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+	return ldb_next_request(module, new_req);
+}
+
+
+static int set_user_modify_callback(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	const struct ldb_message *msg = NULL;
+	struct ldb_result *res = NULL;
+	int ret;
+
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	ldb = ldb_module_get_ctx(module);
+
+	context = talloc_zero(req, struct audit_callback_context);
+	if (context == NULL) {
+		ret = ldb_oom(ldb);
+		goto exit;
+	}
+	context->request = req;
+	context->module = module;
+	context->log_changes = log_user;
+
+	msg = get_message(req);
+	ret = dsdb_module_search_dn(module,
+				    frame,
+				    &res,
+				    msg->dn,
+				    primary_group_attr,
+				    DSDB_FLAG_NEXT_MODULE |
+				    DSDB_SEARCH_REVEAL_INTERNALS |
+				    DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+				    NULL);
+	if (ret == LDB_SUCCESS) {
+		uint32_t pg;
+		pg = ldb_msg_find_attr_as_uint(res->msgs[0],
+				               "primaryGroupID",
+					       ~0);
+		context->primary_group = pg;
+	}
+	/*
+	 * We want to log the return code status, so we need to register
+	 * a callback function to get the actual result.
+	 * We need to take a new copy so that we don't alter the callers copy
+	 */
+	ret = ldb_build_mod_req(&new_req,
+				ldb,
+				req,
+				req->op.add.message,
+				req->controls,
+				context,
+				group_audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		goto exit;
+	}
+	ret = ldb_next_request(module, new_req);
+exit:
+	TALLOC_FREE(frame);
+	return ret;
+}
+
+static int set_user_add_callback(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	int ret;
+	/*
+	 * Adding a user with a primary group.
+	 */
+	ldb = ldb_module_get_ctx(module);
+	context = talloc_zero(req, struct audit_callback_context);
+
+	if (context == NULL) {
+		return ldb_oom(ldb);
+	}
+	context->request = req;
+	context->module = module;
+	context->log_changes = log_user;
+	/*
+	 * We want to log the return code status, so we need to register
+	 * a callback function to get the actual result.
+	 * We need to take a new copy so that we don't alter the callers copy
+	 */
+	ret = ldb_build_add_req(&new_req,
+				ldb,
+				req,
+				req->op.add.message,
+				req->controls,
+				context,
+				group_audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+	return ldb_next_request(module, new_req);
+}
+
+static int group_add(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+
+	if (CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT, GROUP_LOG_LVL) ||
+	    CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT_JSON, GROUP_LOG_LVL)) {
+		/*
+		 * Avoid the overheads of logging unless it has been
+		 * enabled
+		 */
+		if (has_group_membership_changes(req)) {
+			return set_group_add_callback(module, req);
+		}
+		if (has_primary_group_id(req)) {
+			return set_user_add_callback(module, req);
+		}
+	}
+	return ldb_next_request(module, req);
+}
+
+static int group_delete(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	return ldb_next_request(module, req);
+}
+
+static int set_group_modify_callback(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+	struct audit_callback_context *context = NULL;
+	struct ldb_request *new_req = NULL;
+	struct ldb_context *ldb = NULL;
+	struct ldb_result *res = NULL;
+	int ret;
+
+	ldb = ldb_module_get_ctx(module);
+	context = talloc_zero(req, struct audit_callback_context);
+
+	if (context == NULL) {
+		return ldb_oom(ldb);
+	}
+	context->request = req;
+	context->module  = module;
+	context->log_changes = log_group;
+
+	/*
+	 * About to change the group memberships need to read
+	 * the current state from the database.
+	 */
+	ret = dsdb_module_search_dn(module,
+				    context,
+				    &res,
+				    req->op.add.message->dn,
+				    member_attr,
+				    DSDB_FLAG_NEXT_MODULE |
+				    DSDB_SEARCH_REVEAL_INTERNALS |
+				    DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+				    NULL);
+	if (ret == LDB_SUCCESS) {
+		context->members = ldb_msg_find_element(res->msgs[0], "member");
+	}
+
+	ret = ldb_build_mod_req(&new_req,
+				ldb,
+				req,
+				req->op.mod.message,
+				req->controls,
+				context,
+				group_audit_callback,
+				req);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+	return ldb_next_request(module, new_req);
+}
+
+static int group_modify(
+	struct ldb_module *module,
+	struct ldb_request *req)
+{
+
+	if (CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT, GROUP_LOG_LVL) ||
+	    CHECK_DEBUGLVLC(DBGC_GROUP_AUDIT_JSON, GROUP_LOG_LVL)) {
+		/*
+		 * Avoid the overheads of logging unless it has been
+		 * enabled
+		 */
+		if (has_group_membership_changes(req)) {
+			return set_group_modify_callback(module, req);
+		}
+		if (has_primary_group_id(req)) {
+			return set_user_modify_callback(module, req);
+		}
+	}
+	return ldb_next_request(module, req);
+}
+
+static int group_init(struct ldb_module *module)
+{
+
+	struct ldb_context *ldb = ldb_module_get_ctx(module);
+	struct audit_context *context = NULL;
+	struct loadparm_context *lp_ctx
+		= talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+					struct loadparm_context);
+	struct tevent_context *ec = ldb_get_event_context(ldb);
+
+	context = talloc_zero(module, struct audit_context);
+	if (context == NULL) {
+		return ldb_module_oom(module);
+	}
+
+	if (lp_ctx && lpcfg_group_change_notification(lp_ctx)) {
+		context->send_events = true;
+		context->msg_ctx = imessaging_client_init(ec, lp_ctx, ec);
+	}
+
+	ldb_module_set_private(module, context);
+	return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_group_audit_log_module_ops = {
+	.name              = "group_audit_log",
+	.add		   = group_add,
+	.modify		   = group_modify,
+	.del		   = group_delete,
+	.init_context	   = group_init,
+};
+
+int ldb_group_audit_log_module_init(const char *version)
+{
+	LDB_MODULE_CHECK_VERSION(version);
+	return ldb_register_module(&ldb_group_audit_log_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
index baa30f9..fa58f19 100644
--- a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
+++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
@@ -313,6 +313,7 @@ static int samba_dsdb_init(struct ldb_module *module)
 		"rdn_name",
 		"subtree_delete",
 		"repl_meta_data",
+		"group_audit_log",
 		"encrypted_secrets",
 		"operational",
 		"unique_object_sids",
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server
index 6c821fb..e5c5032 100644
--- a/source4/dsdb/samdb/ldb_modules/wscript_build_server
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server
@@ -441,3 +441,19 @@ bld.SAMBA_MODULE('ldb_audit_log',
             samdb
         '''
 	)
+
+bld.SAMBA_MODULE('ldb_group_audit_log',
+	source='group_audit.c',
+	subsystem='ldb',
+	init_function='ldb_group_audit_log_module_init',
+	module_init_name='ldb_init_module',
+	internal_module=False,
+	deps='''
+            audit_logging
+            talloc
+            samba-util
+            samdb-common
+            DSDB_MODULE_HELPERS
+            samdb
+        '''
+	)
-- 
2.7.4

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20180507/70fd2314/signature-0001.sig>


More information about the samba-technical mailing list