[PATCH] dump and restore domain trust info

Philipp Gesang philipp.gesang at intra2net.com
Thu Jan 10 10:53:52 UTC 2019


Hello and a late happy New Year everyone!

While integrating Samba with our backup system, I’ve been adding functionality
for dumping and undumping the domain member information in a hopefully portable
way. I think I have now reached a point where I’d like to elicit external
feedback so I would like you have a look at the attached patchset. Eventually
we would like for this functionality to be merged.

After some experiments I settled on extending “net primarytrust dumpinfo” with
json output and adding a companion “net primarytrust readinfo” for replaying a
dump obtained this way.

An example dump as used in the blackbox tests:

    { "Reserved Flags": "AAAAAAAAAAA=",
      "Join Time": "KgAAAAAAAAA=",
      "Computer Name": "LOCALADMEMBER",
      "Account Name": "LOCALADMEMBER$",
      "Secure Channel Type": 2,
      "Trust Flags": 26,
      "Trust Type": 2,
      "Trust Attributes": 26,
      "Supported Encryption Types": 31,
      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
      "Password Last Change": "NWUTXAAAAAA=",
      "Password Changes": "AQAAAAAAAAA=",
      "Password": {
        "Change Time": "ysIkXAAAAAA=",
        "Change Server": "ADDC",
        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
      "DNS Domain Info": {
        "Domain NetBios Name": "ADDOMAIN",
        "Domain DNS Name": "addom.samba.example.com",
        "Domain Forest Name": "addom.samba.example.com",
        "Domain SID": "S-1-5-21-42-1337-1701",
        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }

Two patches contain the meat of it:

    s3: net: add json printer to `net primarytrust`
    s3: net: add primarytrust subcommand `readinfo`

There’s one patch that fixes some typos, the rest is auxiliary stuff and tests.
I’ve marked some issues with XXX comments. These mainly concern how flags
values should be represented.

CI: https://gitlab.com/samba-team/devel/samba/pipelines/42583194
I’m sorting out that failure in build_samba right now.

PS: FWIW, “readinfo” can be used to inject “offline join” blobs generated by
    djoin.exe. If you’re interested I have a PoC that I can share.

Feedback and directions appreciated,
Philipp

-------------- next part --------------
From 853aaf37eb241fd7123b4b827d36608e6aa54433 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 14:59:50 +0200
Subject: [RFC PATCH 01/10] lib/audit_logging/test: fix typos

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 lib/audit_logging/audit_logging.c             |  4 +-
 .../tests/audit_logging_error_test.c          | 50 +++++++++----------
 2 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index 6944da7f872..fe2df2c9f8a 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -293,7 +293,7 @@ void audit_message_send(
  * 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
+ * Free with a call to json_free_object, note that the jansson implementation
  * allocates memory with malloc and not talloc.
  *
  * @return a struct json_object, valid will be set to false if the object
@@ -320,7 +320,7 @@ struct json_object json_new_object(void) {
  * 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
+ * Free with a call to json_free_object, note that the jansson implementation
  * allocates memory with malloc and not talloc.
  *
  * @return a struct json_object, error will be set to true if the array
diff --git a/lib/audit_logging/tests/audit_logging_error_test.c b/lib/audit_logging/tests/audit_logging_error_test.c
index 1c0929a1b99..153e4f5b1fa 100644
--- a/lib/audit_logging/tests/audit_logging_error_test.c
+++ b/lib/audit_logging/tests/audit_logging_error_test.c
@@ -55,7 +55,7 @@
 
 #include "lib/audit_logging/audit_logging.h"
 
-const int JANNASON_FAILURE = -1;
+const int JANSSON_FAILURE = -1;
 const int CALL_ORIG = -2;
 
 /*
@@ -300,7 +300,7 @@ static void test_json_add_int(void **state)
 	 * Test json object set new failure
 	 */
 	will_return(__wrap_json_integer, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_int(&object, "name", 2);
 
 	assert_false(json_is_invalid(&object));
@@ -320,7 +320,7 @@ static void test_json_add_bool(void **state)
 	 * json_boolean does not return an error code.
 	 * Test json object set new failure
 	 */
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_bool(&object, "name", true);
 
 	assert_false(json_is_invalid(&object));
@@ -351,7 +351,7 @@ static void test_json_add_string(void **state)
 	 * Test json object set new failure
 	 */
 	will_return(__wrap_json_string, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_string(&object, "name", "value");
 
 	assert_false(json_is_invalid(&object));
@@ -360,7 +360,7 @@ static void test_json_add_string(void **state)
 	/*
 	 * Test json object set new failure for a NULL string
 	 */
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_string(&object, "null", NULL);
 
 	assert_false(json_is_invalid(&object));
@@ -384,7 +384,7 @@ static void test_json_add_object(void **state)
 	/*
 	 * Test json object set new failure
 	 */
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_object(&object, "name", &value);
 
 	assert_false(json_is_invalid(&object));
@@ -394,7 +394,7 @@ static void test_json_add_object(void **state)
 	/*
 	 * Test json object set new failure for a NULL value
 	 */
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_object(&object, "null", NULL);
 
 	assert_false(json_is_invalid(&object));
@@ -419,7 +419,7 @@ static void test_json_add_to_array(void **state)
 	/*
 	 * Test json array append new failure
 	 */
-	will_return(__wrap_json_array_append_new, JANNASON_FAILURE);
+	will_return(__wrap_json_array_append_new, JANSSON_FAILURE);
 	rc = json_add_object(&array, "name", &value);
 
 	assert_false(json_is_invalid(&array));
@@ -429,7 +429,7 @@ static void test_json_add_to_array(void **state)
 	/*
 	 * Test json append new failure with a NULL value
 	 */
-	will_return(__wrap_json_array_append_new, JANNASON_FAILURE);
+	will_return(__wrap_json_array_append_new, JANSSON_FAILURE);
 	rc = json_add_object(&array, "null", NULL);
 
 	assert_false(json_is_invalid(&array));
@@ -461,7 +461,7 @@ static void test_json_add_timestamp(void **state)
 	will_return(__wrap_gettimeofday, 0);
 	will_return(__wrap_localtime, false);
 	will_return(__wrap_json_string, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_timestamp(&object);
 
 	assert_false(json_is_invalid(&object));
@@ -511,7 +511,7 @@ static void test_json_add_stringn(void **state)
 	 * Test json object set new failure
 	 */
 	will_return(__wrap_json_string, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_stringn(&object, "name", "value", 3);
 
 	assert_false(json_is_invalid(&object));
@@ -520,7 +520,7 @@ static void test_json_add_stringn(void **state)
 	/*
 	 * Test json object set new failure for a NULL string
 	 */
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_stringn(&object, "null", NULL, 2);
 
 	assert_false(json_is_invalid(&object));
@@ -529,7 +529,7 @@ static void test_json_add_stringn(void **state)
 	/*
 	 * Test json object set new failure for a zero string size
 	 */
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_stringn(&object, "zero", "no value", 0);
 
 	assert_false(json_is_invalid(&object));
@@ -567,7 +567,7 @@ static void test_json_add_version(void **state)
 
 	will_return(__wrap_json_object, false);
 	will_return(__wrap_json_integer, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_version(&object, 2, 12);
 
 	assert_false(json_is_invalid(&object));
@@ -588,7 +588,7 @@ static void test_json_add_version(void **state)
 	will_return(__wrap_json_integer, false);
 	will_return(__wrap_json_object_set_new, CALL_ORIG);
 	will_return(__wrap_json_integer, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_version(&object, 3, 13);
 
 	assert_false(json_is_invalid(&object));
@@ -609,7 +609,7 @@ static void test_json_add_version(void **state)
 	will_return(__wrap_json_integer, false);
 	will_return(__wrap_json_object_set_new, CALL_ORIG);
 	will_return(__wrap_json_integer, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_version(&object, 4, 14);
 
 	assert_false(json_is_invalid(&object));
@@ -635,7 +635,7 @@ static void test_json_add_address(void **state)
 	will_return(__wrap_json_object, false);
 	object = json_new_object();
 
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_address(&object, "name", NULL);
 
 	assert_false(json_is_invalid(&object));
@@ -650,7 +650,7 @@ static void test_json_add_address(void **state)
 	will_return(__wrap_talloc_named_const, REAL_TALLOC);
 	will_return(__wrap_tsocket_address_string, false);
 	will_return(__wrap_json_string, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_address(&object, "name", ip);
 
 	assert_false(json_is_invalid(&object));
@@ -698,7 +698,7 @@ static void test_json_add_sid(void **state)
 	will_return(__wrap_json_object, false);
 	object = json_new_object();
 
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_sid(&object, "null", NULL);
 	assert_int_equal(JSON_ERROR, rc);
 
@@ -707,7 +707,7 @@ static void test_json_add_sid(void **state)
 	 */
 	assert_true(string_to_sid(&sid, SID));
 	will_return(__wrap_json_string, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_sid(&object, "sid", &sid);
 	assert_int_equal(JSON_ERROR, rc);
 
@@ -728,7 +728,7 @@ static void test_json_add_guid(void **state)
 	will_return(__wrap_json_object, false);
 	object = json_new_object();
 
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_guid(&object, "null", NULL);
 	assert_int_equal(JSON_ERROR, rc);
 
@@ -738,7 +738,7 @@ static void test_json_add_guid(void **state)
 	status = GUID_from_string(GUID, &guid);
 	assert_true(NT_STATUS_IS_OK(status));
 	will_return(__wrap_json_string, false);
-	will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+	will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
 	rc = json_add_guid(&object, "guid", &guid);
 	assert_int_equal(JSON_ERROR, rc);
 
@@ -818,7 +818,7 @@ static void test_json_get_object(void **state)
 {
 	struct json_object object;
 	struct json_object stored;
-	struct json_object retreived;
+	struct json_object retrieved;
 
 	int rc;
 
@@ -839,8 +839,8 @@ static void test_json_get_object(void **state)
 	 */
 	will_return(__wrap_json_object, false);
 	will_return(__wrap_json_object_update, true);
-	retreived = json_get_object(&object, "stored");
-	assert_true(json_is_invalid(&retreived));
+	retrieved = json_get_object(&object, "stored");
+	assert_true(json_is_invalid(&retrieved));
 
 	json_free(&object);
 }
-- 
2.17.2


From 74cf2bc676264a0a716489b74bd7c120d9975789 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 1 Oct 2018 11:24:56 +0200
Subject: [RFC PATCH 02/10] lib/audit_logging: add JSON object accessors

This adds a wrappers for libjansson routines to handle a greater
variety of data types:

        - json_has_key(),

        - json_add_uint32(),
        - json_add_uint64(),
        - json_add_base64(),

        - json_get_int(),
        - json_get_string(),
        - json_get_sid(),
        - json_get_uint64_t(),
        - json_get_uint32_t(),
        - json_get_guid(), and
        - json_get_base64().

Also add helpers ``json_from_{string,FILEp}()`` for decoding JSON
from strings and file handles, respectively. The inputs may be
either JSON objects or arrays. Add a new constructor
``json_new_null()`` to make it more obvious that incoming JSON
values may be of any type.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 lib/audit_logging/audit_logging.c | 880 +++++++++++++++++++++++++++++-
 lib/audit_logging/audit_logging.h |  43 ++
 2 files changed, 920 insertions(+), 3 deletions(-)

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index fe2df2c9f8a..41c2ce9b901 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -24,10 +24,13 @@
 
 #include "includes.h"
 
+#include "inttypes.h"
 #include "librpc/ndr/libndr.h"
 #include "lib/tsocket/tsocket.h"
 #include "libcli/security/dom_sid.h"
 #include "lib/messaging/messaging.h"
+#include "lib/util/base64.h"
+#include "lib/util/byteorder.h"
 #include "auth/common_auth.h"
 #include "audit_logging.h"
 
@@ -84,7 +87,7 @@ char* audit_get_timestamp(TALLOC_CTX *frame)
  *
  * @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_class The debug class to log the message with.
  * @param debug_level The debug level to log the message with.
  */
 void audit_log_human_text(const char* prefix,
@@ -96,17 +99,64 @@ void audit_log_human_text(const char* prefix,
 }
 
 #ifdef HAVE_JANSSON
+
 /*
  * Constant for empty json object initialisation
  */
 const struct json_object json_empty_object = {.valid = false, .root = NULL};
+
+/*
+ * @brief Get string representation of the type of a JSON entity.
+ *
+ * Returns the JSON type name, ``UNKNOWN'' otherwise. This is operates
+ * directly on libjansson types.
+ *
+ * @param jsobj The object whose type to return.
+ */
+static const char *json_type_to_string(const json_type jstype)
+{
+    switch (jstype) {
+        default:           return "UNKNOWN";
+        case JSON_OBJECT:  return "object";
+        case JSON_ARRAY:   return "array";
+        case JSON_STRING:  return "string";
+        case JSON_INTEGER: return "integer";
+        case JSON_REAL:    return "real";
+        case JSON_TRUE:    return "true";
+        case JSON_FALSE:   return "false";
+        case JSON_NULL:    return "null";
+    }
+}
+
+/*
+ * @brief Get string representation of the type of a json_object entity.
+ *
+ * Returns a human-readable type name for the toplevel entity in a json_object,
+ * ``INVALID'' for a bad object, and ``UNKNOWN'' in case the type is not
+ * present in our definitions.
+ *
+ * @param jsobj The object whose type to return.
+ */
+static const char *json_object_type_to_string(const struct json_object *jsobj)
+{
+	json_type jstype = JSON_NULL;
+
+	if (json_is_invalid(jsobj)) {
+	    return "INVALID";
+	}
+
+	jstype = json_typeof(jsobj->root);
+
+	return json_type_to_string(jstype);
+}
+
 /*
  * @brief write a json object to the samba audit logs.
  *
  * Write the json object to the audit logs as a formatted string
  *
  * @param message The content of the log line.
- * @param debub_class The debug class to log the message with.
+ * @param debug_class The debug class to log the message with.
  * @param debug_level The debug level to log the message with.
  */
 void audit_log_json(struct json_object* message,
@@ -341,6 +391,31 @@ struct json_object json_new_array(void) {
 	return array;
 }
 
+/*
+ * @brief Create a new struct json_object, wrapping a JSON value of
+ * 	NULL type.
+ *
+ * Create a new object of NULL type.
+ *
+ * Free with a call to json_free_object, note that the jansson implementation
+ * allocates memory with malloc and not talloc.
+ *
+ * @return a struct json_object, valid will be set to false if the object
+ *         could not be created.
+ *
+ */
+struct json_object json_new_null(void) {
+	struct json_object object = json_empty_object;
+
+	object.root = json_null();
+	if (object.root == NULL) {
+		object.valid = false;
+		DBG_ERR("Unable to create JSON null value\n");
+		return object;
+	}
+	object.valid = true;
+	return object;
+}
 
 /*
  * @brief free and invalidate a previously created JSON object.
@@ -413,6 +488,294 @@ int json_add_int(struct json_object *object, const char *name, const int value)
 	return ret;
 }
 
+/*
+ * @brief Extract integer from JSON object.
+ *
+ * Read a field from the object as a JSON number and return the result as
+ * integer.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value where to store the result.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_get_int(const struct json_object *object, const char *name,
+		 int *value)
+{
+	const json_t *jstmp = NULL;
+
+	if (object == NULL || json_is_invalid(object)) {
+		DBG_ERR("Unable to retrieve integer value [%s] "
+			"from invalid object\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	if (name == NULL) {
+		DBG_ERR("Unable to retrieve integer value, specified "
+			"field is NULL\n");
+		return JSON_ERROR;
+	}
+
+	if (value == NULL) {
+		DBG_ERR("Unable to store integer value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	jstmp = json_object_get(object->root, name);
+	if (jstmp == NULL) {
+		DBG_ERR("JSON object has no key [%s]\n", name);
+		return JSON_ERROR;
+	}
+
+	if (!json_is_integer (jstmp)) {
+		DBG_ERR("value of key [%s] is not an integer but a JSON "
+			"entity of type %s\n",
+			name, json_type_to_string(json_typeof(jstmp)));
+		return JSON_ERROR;
+	}
+
+	*value = json_integer_value(jstmp);
+
+	return 0;
+}
+
+/*
+ * @brief Add an 64 bit unsigned integer value to a JSON object.
+ *
+ * Add an integer value named 'name' to the json object. JSON numbers
+ * are unsuitable for this purpose because they are represented as doubles.
+ * Thus we fall back on base64 encoded data in SMB byte order.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_uint64(struct json_object *object, const char *name,
+		    const uint64_t value)
+{
+	uint8_t buf [sizeof (uint64_t)] = { 0 };
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add uint64_t [%s] value [%"PRIu64"], "
+			"target object is invalid\n",
+			name,
+			value);
+		return JSON_ERROR;
+	}
+
+	SBVAL(buf, 0, value);
+
+	return json_add_base64(object, name,
+			       (DATA_BLOB)
+			       { .data = buf
+			       , .length = sizeof (buf) });
+}
+
+/*
+ * @brief Extract 64 bit unsigned integer from JSON object.
+ *
+ * Read a base64 encoded integer value named 'name' from the json object.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value where to store the result.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_get_uint64(const struct json_object *object, const char *name,
+		    uint64_t *value)
+{
+	int ret = 0;
+	DATA_BLOB rawu64 = { 0 };
+	TALLOC_CTX *ctx = NULL;
+
+	if (value == NULL) {
+		DBG_ERR("Unable to store uint64_t value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	/* object and name are validated indirectly here */
+	ret = json_get_base64(ctx, object, name, &rawu64);
+	if (ret != 0) {
+		DBG_ERR("Unable to read field [%s] as base64 encoded data\n",
+			name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	if (rawu64.length != sizeof (uint64_t)) {
+		DBG_ERR("Invalid decoded base64 encoded field [%s]: content "
+			"length (%zu B) inadequate for uint64_t\n",
+			name, rawu64.length);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	*value = BVAL(rawu64.data, 0);
+
+	data_blob_free(&rawu64);
+	TALLOC_FREE(ctx);
+
+	return 0;
+}
+
+/*
+ * @brief Add an 32 bit unsigned integer value to a JSON object.
+ *
+ * Add an integer value named 'name' to the json object. This can be
+ * represented by JSON numbers so we'll use those directly.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_uint32(struct json_object *object, const char *name,
+		    const uint32_t value)
+{
+	int ret = 0;
+	json_t *integer = NULL;
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add uint32_t [%s] value [%"PRIu32"], "
+			"target object is invalid\n",
+			name,
+			value);
+		return JSON_ERROR;
+	}
+
+	integer = json_integer(value);
+	if (integer == NULL) {
+		DBG_ERR("Unable to create value [%s] for uint32_t [%"PRIu32"]\n",
+			name,
+			value);
+		return JSON_ERROR;
+	}
+
+	ret = json_object_set_new(object->root, name, integer);
+	if (ret != 0) {
+		json_decref(integer);
+		DBG_ERR("Unable to add uint32_t [%s] value [%"PRIu32"]\n",
+                        name, value);
+	}
+
+	return ret;
+}
+
+/*
+ * @brief Extract 32 bit unsigned integer from JSON object.
+ *
+ * Read a field from the object as a JSON number and return the result as
+ * integer.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value where to store the result.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_get_uint32(const struct json_object *object, const char *name,
+		    uint32_t *value)
+{
+	const json_t *jstmp = NULL;
+
+	if (object == NULL || json_is_invalid(object)) {
+		DBG_ERR("Unable to retrieve uint32_t value [%s] "
+			"from invalid object\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	if (name == NULL) {
+		DBG_ERR("Unable to retrieve uint32 value, specified "
+			"field is NULL\n");
+		return JSON_ERROR;
+	}
+
+	if (value == NULL) {
+		DBG_ERR("Unable to store uint32 value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	jstmp = json_object_get(object->root, name);
+	if (jstmp == NULL) {
+		DBG_ERR("JSON object has no key [%s]\n", name);
+		return JSON_ERROR;
+	}
+
+	if (!json_is_integer (jstmp)) {
+		DBG_ERR("value of key [%s] is not an integer but a JSON "
+			"entity of type %s\n",
+			name, json_type_to_string(json_typeof(jstmp)));
+		return JSON_ERROR;
+	}
+
+	*value = (uint32_t)json_integer_value(jstmp);
+
+	return 0;
+}
+
+/*
+ * @brief Add a unix timestamp to a JSON object.
+ *
+ * time_t is a typedef for long long on x64 bit systems so we store it as
+ * int64_t.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param t the value
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_time_t(struct json_object *object, const char *name, const time_t t)
+{
+	uint8_t buf [sizeof (int64_t)] = { 0 };
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add time_t [%s] value [%ld], "
+			"target object is invalid\n",
+			name,
+			t);
+		return JSON_ERROR;
+	}
+
+	SBVAL(buf, 0, (int64_t)t);
+
+	return json_add_base64(object, name,
+			       (DATA_BLOB)
+			       { .data = buf
+			       , .length = sizeof (buf) });
+}
+
 /*
  * @brief Add a boolean value to a JSON object.
  *
@@ -495,6 +858,78 @@ int json_add_string(struct json_object *object,
 	return ret;
 }
 
+/*
+ * @brief Get string value from a JSON object.
+ *
+ * Retrieve the string value with the key `name' from a json object
+ * and store it at `*value'. The destination pointer is only updated
+ * when all input checks succeed.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param value Pointer to the destination string.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_string(TALLOC_CTX *ctx,
+		    const struct json_object *object,
+		    const char *name,
+		    const char **value)
+{
+	const char   *sttmp = NULL;
+	const json_t *jstmp = NULL;
+
+	if (object == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL"
+			"object\n");
+		return JSON_ERROR;
+	}
+
+	if (name == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"as key\n");
+		return JSON_ERROR;
+	}
+
+	if (value == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"target\n");
+		return JSON_ERROR;
+	}
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Invalid argument, refusing to operate on invalid "
+			"object\n");
+		return JSON_ERROR;
+	}
+
+	if (!json_is_object(object->root)) {
+		DBG_ERR("refusing to operate on non-object JSON entity of "
+			"type %s\n",
+			json_object_type_to_string(object));
+		return JSON_ERROR;
+	}
+
+	jstmp = json_object_get(object->root, name);
+	if (jstmp == NULL) {
+		DBG_ERR("JSON object has no key [%s]\n", name);
+		return JSON_ERROR;
+	}
+
+	sttmp = talloc_strdup(ctx, json_string_value(jstmp));
+	if (sttmp == NULL) {
+		DBG_ERR("value of key [%s] is not a string but a JSON "
+			"entity of type %s\n",
+			name, json_type_to_string(json_typeof(jstmp)));
+		return JSON_ERROR;
+	}
+
+	*value = sttmp;
+
+	return 0;
+}
+
 /*
  * @brief Assert that the current JSON object is an array.
  *
@@ -851,6 +1286,82 @@ int json_add_sid(struct json_object *object,
 	return ret;
 }
 
+/*
+ * @brief Add data as Base64 encoded string.
+ *
+ * Wrapper for json_add_string() which converts the passed byte array to base64
+ * first.
+ *
+ * "foo":"YmFyCg=="
+ *
+ * @param object the JSON object to be updated.
+ * @param name the key
+ * @param data pointer to the start of the data to be encoded
+ * @param len number of bytes to process
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_add_base64(struct json_object *object,
+		    const char *name,
+		    const DATA_BLOB src)
+{
+	int ret = 0;
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add base64 data [%s], "
+			"target object is invalid\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	if (src.data == NULL) {
+		ret = json_object_set_new(object->root, name, json_null());
+		if (ret != 0) {
+			DBG_ERR("Unable to add null data blob at key [%s]\n",
+				name);
+			return JSON_ERROR;
+		}
+	} else if (src.length == 0) {
+		/* the functions in base64.c will not ``encode'' the empty string */
+		ret = json_add_string(object, name, "");
+		if (ret != 0) {
+			DBG_ERR("Unable to add the empty string to json "
+				"object with key [%s]\n", name);
+			return JSON_ERROR;
+		}
+	} else {
+		TALLOC_CTX *ctx = talloc_new(NULL);
+		const char *s = NULL;
+
+		if (ctx == NULL) {
+			DBG_ERR("Out of memory creating context for base64 "
+				"data field [%s]\n", name);
+			return JSON_ERROR;
+		}
+
+		s = base64_encode_data_blob (ctx, src);
+		if (s == NULL) {
+			DBG_ERR("Out of memory encoding %zu B of data "
+				"as base64 for json key [%s]\n",
+				src.length, name);
+			TALLOC_FREE(ctx);
+			return JSON_ERROR;
+		}
+
+		ret = json_add_string(object, name, s);
+		if (ret != 0) {
+			DBG_ERR("Unable to add base64 encoded data to json "
+				"object with key [%s]\n", name);
+			TALLOC_FREE(ctx);
+			return JSON_ERROR;
+		}
+		TALLOC_FREE(ctx);
+	}
+
+	return ret;
+}
+
 /*
  * @brief Add a formatted string representation of a guid to a json object.
  *
@@ -907,7 +1418,7 @@ int json_add_guid(struct json_object *object,
 /*
  * @brief Convert a JSON object into a string
  *
- * Convert the jsom object into a string suitable for printing on a log line,
+ * Convert the json object into a string suitable for printing on a log line,
  * i.e. with no embedded line breaks.
  *
  * If the object is invalid it logs an error and returns NULL.
@@ -953,6 +1464,58 @@ char *json_to_string(TALLOC_CTX *mem_ctx, const struct json_object *object)
 	return json_string;
 }
 
+/*
+ * @brief Decode string as JSON.
+ *
+ * Convert assumed JSON encoded data to a json_object.
+ * If the input is neither an object nor an array, or it is invalid, return an
+ * error status. The destination is only assigned to if the input was
+ * successfully decoded. Otherwise, dst is not mutated. An existing JSON object
+ * is freed before the assignment.
+ *
+ * @param dst A valid struct json_object to store the decoded data.
+ * @param jsdata Raw JSON encoded data to decode.
+ *
+ * @return Status code, zero on success.
+ */
+int json_from_string(struct json_object *dst, const char *jsdata)
+{
+	struct json_object object;
+	json_error_t       jserr;
+
+	if (dst == NULL) {
+		DBG_ERR("Invalid destination for decoding JSON, cannot assign "
+			"to NULL\n");
+		return JSON_ERROR;
+	}
+
+	if (json_is_invalid(dst)) {
+		DBG_ERR("Invalid destination for decoding JSON, cannot assign "
+			"to NULL\n");
+		return JSON_ERROR;
+	}
+
+	if (jsdata == NULL) {
+		DBG_ERR("Invalid raw JSON input, cannot operate on NULL\n");
+		return JSON_ERROR;
+	}
+
+	object.root = json_loads(jsdata, 0, &jserr);
+	if (object.root == NULL) {
+		DBG_ERR("Error while decoding data as JSON at position %d:%d "
+			"(%d B): source=%s description=%s\n",
+			jserr.line, jserr.column, jserr.position,
+			jserr.source, jserr.text);
+		return JSON_ERROR;
+	}
+
+	json_free(dst);
+	object.valid = true;
+	*dst = object;
+
+	return 0;
+}
+
 /*
  * @brief get a json array named "name" from the json object.
  *
@@ -1034,4 +1597,315 @@ struct json_object json_get_object(struct json_object *object, const char *name)
 	}
 	return o;
 }
+
+/*
+ * @brief Decode data reading from file descriptor.
+ *
+ * If the input is neither an object nor an array, or it is invalid, return an
+ * error status. The destination is only assigned to if the input was
+ * successfully decoded. Otherwise, dst is not mutated. An existing JSON object
+ * is freed before the assignment.
+ *
+ * @param dst A valid struct json_object to store the decoded data.
+ * @param jsfd File descriptor to read JSON encoded data from for decoding.
+ *
+ * @return Status code, zero on success.
+ */
+int json_from_FILEp(struct json_object *dst, FILE *jsfh)
+{
+	struct json_object object;
+	json_error_t jserr;
+
+	if (dst == NULL) {
+		DBG_ERR("Invalid destination for decoding JSON, cannot assign "
+			"to NULL\n");
+		return JSON_ERROR;
+	}
+
+	if (jsfh == NULL) {
+		DBG_ERR("Invalid stdio handle passed for reading\n");
+		return JSON_ERROR;
+	}
+
+	object = json_empty_object;
+	object.root = json_loadf(jsfh, 0, &jserr);
+	if (object.root == NULL) {
+		DBG_ERR("error at position %d:%d (%d B) while reading JSON "
+			"object from handle (fd=%d): source=%s description=%s\n",
+			jserr.line, jserr.column, jserr.position,
+			fileno (jsfh), jserr.source, jserr.text);
+		return JSON_ERROR;
+	}
+
+	json_free(dst);
+	object.valid = true;
+	*dst = object;
+
+	return 0;
+}
+
+/*
+ * @brief Test for key in object.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ *
+ * @return true  The object and key are valid and the object has the key,
+ *         false otherwise.
+ */
+
+bool json_has_key(const struct json_object *object, const char *name)
+{
+	if (object == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL"
+			"object\n");
+		return false;
+	}
+
+	if (name == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"as key\n");
+		return false;
+	}
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Invalid argument, refusing to operate on invalid "
+			"object\n");
+		return false;
+	}
+
+	if (!json_is_object(object->root)) {
+		DBG_ERR("refusing to operate on non-object JSON entity of "
+			"type %s\n",
+			json_object_type_to_string(object));
+		return false;
+	}
+
+	return json_object_get(object->root, name) != NULL;
+}
+
+/*
+ * @brief Get a time_t from a JSON object.
+ *
+ * Retrieve a 64 bit integer from a JSON object key and return it as time_t.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param t Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_time_t(const struct json_object *object,
+		    const char *name,
+		    time_t *t)
+{
+	int ret = 0;
+	DATA_BLOB rawi64 = { 0 };
+	TALLOC_CTX *ctx = NULL;
+
+	if (t == NULL) {
+		DBG_ERR("Unable to store time_t value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	/* object and name are validated indirectly here */
+	ret = json_get_base64(ctx, object, name, &rawi64);
+	if (ret != 0) {
+		DBG_ERR("Unable to read field [%s] as base64 encoded data\n",
+			name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	if (rawi64.length != sizeof (int64_t)) {
+		DBG_ERR("Invalid decoded base64 encoded field [%s]: content "
+			"length (%zu B) inadequate for int64_t\n",
+			name, rawi64.length);
+		data_blob_free(&rawi64);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	*t = BVAL((time_t)rawi64.data, 0); /* potentially lossy on 32 bit */
+
+	data_blob_free(&rawi64);
+	TALLOC_FREE(ctx);
+
+	return 0;
+}
+
+/*
+ * @brief Get a SID from a JSON object.
+ *
+ * Retrieve the SID under the key `name' from a json object and store it in
+ * dom_sid. On the JSON end, value is assumed to be of type `string' conforming
+ * to the ``string format syntax'' for SIDs. The destination pointer is only
+ * updated when all input checks succeed.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param sid Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_sid(const struct json_object *object,
+		 const char *name,
+		 struct dom_sid *sid)
+{
+	int ret = 0;
+	const char *data = NULL;
+	struct dom_sid tmp = { 0 };
+	TALLOC_CTX *ctx = NULL;
+
+	/* object and name are checked in json_get_string() */
+	if (sid == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"target\n");
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	ret = json_get_string(ctx, object, name, &data);
+	if (ret != 0 || data == NULL) {
+		DBG_ERR("Failed to get string value from JSON object\n");
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	if (!dom_sid_parse(data, &tmp)) {
+		DBG_ERR("Failed to process string value as SID\n");
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+	TALLOC_FREE(ctx);
+
+        *sid = tmp;
+
+	return 0;
+}
+
+/*
+ * @brief Get a GUID from a JSON object.
+ *
+ * Retrieve the GUID under the key `name' from a json object and store it in
+ * dom_sid. On the JSON end, value is assumed to be of type `string' conforming
+ * to the canonical UUID format. The destination object is only updated when
+ * all input checks succeed.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param sid Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_guid(const struct json_object *object,
+		  const char *name,
+		  struct GUID *guid)
+{
+	int ret = 0;
+	NTSTATUS status;
+	const char *data = NULL;
+	TALLOC_CTX *ctx = NULL;
+	struct GUID tmp = { 0 };
+
+	/* object and name are checked in json_get_string() */
+	if (guid == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"target\n");
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	ret = json_get_string(ctx, object, name, &data);
+	if (ret != 0 || data == NULL) {
+		DBG_ERR("Failed to get string value from JSON object\n");
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	status = GUID_from_string (data, &tmp);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("Failed to process string value [%s] -> [%s] as GUID\n",
+			name, data);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+	TALLOC_FREE(ctx);
+
+	*guid = tmp;
+
+	return 0;
+}
+
+/*
+ * @brief Get base64 encoded data from a JSON object.
+ *
+ * Retrieve the JSON string member at `key' and return its decoded value
+ * as binary data.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param value Pointer to the destination blob.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_base64(TALLOC_CTX *ctx,
+		    const struct json_object *object,
+		    const char *name,
+		    DATA_BLOB *dst)
+{
+	int         ret = 0;
+	const char *raw = NULL;
+	DATA_BLOB   tmp = { 0 };
+
+	/* object and name are checked in json_get_string() */
+	if (dst == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"target\n");
+		return JSON_ERROR;
+	}
+
+	ret = json_get_string(ctx, object, name, &raw);
+	if (ret != 0 || raw == NULL) {
+		DBG_ERR("Failed to get string value from JSON object\n");
+		return JSON_ERROR;
+	}
+
+	if (raw [0] == '\0') {
+		*dst = data_blob_const(NULL, 0);
+		return 0;
+	}
+
+	tmp = base64_decode_data_blob_talloc(ctx, raw);
+	if (tmp.data == NULL && tmp.length == 0) {
+		DBG_ERR("Failed to decode string at key [%s] as base64", name);
+		return JSON_ERROR;
+	}
+
+	*dst = tmp;
+
+	return 0;
+}
 #endif
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
index 86e9134a86a..7785b816de6 100644
--- a/lib/audit_logging/audit_logging.h
+++ b/lib/audit_logging/audit_logging.h
@@ -22,6 +22,7 @@
 #include "lib/messaging/irpc.h"
 #include "lib/tsocket/tsocket.h"
 #include "lib/util/attr.h"
+#include "lib/util/data_blob.h"
 
 _WARN_UNUSED_RESULT_ char *audit_get_timestamp(TALLOC_CTX *frame);
 void audit_log_human_text(const char *prefix,
@@ -52,6 +53,7 @@ void audit_message_send(struct imessaging_context *msg_ctx,
 			struct json_object *message);
 _WARN_UNUSED_RESULT_ struct json_object json_new_object(void);
 _WARN_UNUSED_RESULT_ struct json_object json_new_array(void);
+_WARN_UNUSED_RESULT_ struct json_object json_new_null(void);
 void json_free(struct json_object *object);
 void json_assert_is_array(struct json_object *array);
 _WARN_UNUSED_RESULT_ bool json_is_invalid(const struct json_object *object);
@@ -59,6 +61,15 @@ _WARN_UNUSED_RESULT_ bool json_is_invalid(const struct json_object *object);
 _WARN_UNUSED_RESULT_ int json_add_int(struct json_object *object,
 				      const char *name,
 				      const int value);
+_WARN_UNUSED_RESULT_ int json_add_uint64(struct json_object *object,
+					 const char *name,
+					 const uint64_t value);
+_WARN_UNUSED_RESULT_ int json_add_uint32(struct json_object *object,
+					 const char *name,
+					 const uint32_t value);
+_WARN_UNUSED_RESULT_ int json_add_time_t(struct json_object *object,
+					 const char *name,
+					 const time_t t);
 _WARN_UNUSED_RESULT_ int json_add_bool(struct json_object *object,
 				       const char *name,
 				       const bool value);
@@ -86,6 +97,34 @@ _WARN_UNUSED_RESULT_ int json_add_sid(struct json_object *object,
 _WARN_UNUSED_RESULT_ int json_add_guid(struct json_object *object,
 				       const char *name,
 				       const struct GUID *guid);
+_WARN_UNUSED_RESULT_ int json_add_base64(struct json_object *object,
+					 const char *name,
+					 const DATA_BLOB src);
+_WARN_UNUSED_RESULT_ int json_get_int(const struct json_object *object,
+				      const char *name, int *value);
+_WARN_UNUSED_RESULT_ int json_get_uint64(const struct json_object *object,
+					 const char *name, uint64_t *value);
+_WARN_UNUSED_RESULT_ int json_get_uint32(const struct json_object *object,
+					 const char *name, uint32_t *value);
+_WARN_UNUSED_RESULT_ int json_get_time_t(const struct json_object *object,
+					 const char *name, time_t *t);
+_WARN_UNUSED_RESULT_ int json_get_string(TALLOC_CTX *ctx,
+                                         const struct json_object *object,
+					 const char *name,
+					 const char **value);
+_WARN_UNUSED_RESULT_ int json_get_sid(const struct json_object *object,
+				      const char *name,
+				      struct dom_sid *sid);
+_WARN_UNUSED_RESULT_ int json_get_guid(const struct json_object *object,
+				       const char *name,
+				       struct GUID *guid);
+_WARN_UNUSED_RESULT_ int json_get_base64(TALLOC_CTX *ctx,
+					 const struct json_object *object,
+					 const char *name,
+					 DATA_BLOB *dst);
+
+_WARN_UNUSED_RESULT_ bool json_has_key(const struct json_object *object,
+				       const char *name);
 
 _WARN_UNUSED_RESULT_ struct json_object json_get_array(
     struct json_object *object, const char *name);
@@ -93,5 +132,9 @@ _WARN_UNUSED_RESULT_ struct json_object json_get_object(
     struct json_object *object, const char *name);
 _WARN_UNUSED_RESULT_ char *json_to_string(TALLOC_CTX *mem_ctx,
 					  const struct json_object *object);
+
+_WARN_UNUSED_RESULT_ int json_from_FILEp(struct json_object *dst, FILE *jsfh);
+_WARN_UNUSED_RESULT_ int json_from_string(struct json_object *dst,
+					  const char *jsdata);
 #endif
 #endif
-- 
2.17.2


From 9941e79d4528b34c744f10f7ca9ebe57af5f328f Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 4 Oct 2018 09:44:33 +0200
Subject: [RFC PATCH 03/10] lib/audit_logging: unit test more JSON helpers

Unit test additional JSON object helpers:

    - json_has_key(),
    - json_from_string(),
    - json_from_FILEp(),
    - json_get_string(),
    - json_get_sid(),
    - json_add_uint32(), json_get_uint32(),
    - json_add_uint64(), json_get_uint64(),
    - json_add_time_t(), json_get_time_t(),
    - json_get_base64(), and json_add_base64().

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 lib/audit_logging/tests/audit_logging_test.c | 588 +++++++++++++++++++
 1 file changed, 588 insertions(+)

diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c
index acd2a4f697f..58aefe1716b 100644
--- a/lib/audit_logging/tests/audit_logging_test.c
+++ b/lib/audit_logging/tests/audit_logging_test.c
@@ -43,7 +43,13 @@
 #include <setjmp.h>
 #include <cmocka.h>
 
+#include <limits.h>
 #include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
 #include <time.h>
 #include <tevent.h>
 #include <config.h>
@@ -101,6 +107,216 @@ static void test_json_add_int(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+#define TEST_JSON_UINT32_NVALUES 3
+static struct { const char *k; uint32_t v; }
+	testme_u32 [TEST_JSON_UINT32_NVALUES] =
+	{ { "zero", 0 }
+	, { "one" , 1 }
+	, { "max" , UINT32_MAX }
+	};
+
+static void test_json_add_uint32(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	int i;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	for (i = 0; i < TEST_JSON_UINT32_NVALUES; ++i) {
+		rc = json_add_uint32(&object, testme_u32[i].k, testme_u32[i].v);
+		assert_int_equal(0, rc);
+	}
+
+	assert_int_equal(TEST_JSON_UINT32_NVALUES,
+			 json_object_size(object.root));
+
+	object.valid = false;
+	rc = json_add_uint32(&object, "should fail 1", 0xf1u);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+
+	rc = json_add_uint32(&object, "should fail 2", 0xf2u);
+	assert_int_equal(JSON_ERROR, rc);
+}
+
+static void test_json_get_uint32(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	uint32_t v;
+	int i;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	for (i = 0; i < TEST_JSON_UINT32_NVALUES; ++i) {
+		rc = json_add_uint32(&object, testme_u32[i].k, testme_u32[i].v);
+		assert_int_equal(0, rc);
+	}
+
+	assert_int_equal(TEST_JSON_UINT32_NVALUES,
+			 json_object_size(object.root));
+
+	for (i = 0; i < TEST_JSON_UINT32_NVALUES; ++i) {
+		rc = json_get_uint32(&object, testme_u32[i].k, &v);
+		assert_int_equal(0, rc);
+		assert_int_equal(v, testme_u32[i].v);
+	}
+
+	object.valid = false;
+	rc = json_get_uint32(&object, "should fail 1", &v);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+
+	rc = json_get_uint32(&object, "should fail 2", &v);
+	assert_int_equal(JSON_ERROR, rc);
+}
+
+#define TEST_JSON_UINT64_NVALUES 3
+static struct { const char *k; uint64_t v; }
+	testme_u64 [TEST_JSON_UINT64_NVALUES] =
+	{ { "zero", 0 }
+	, { "one" , 1 }
+	, { "max" , UINT64_MAX }
+	};
+
+static void test_json_add_uint64(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	int i;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+		rc = json_add_uint64(&object, testme_u64[i].k, testme_u64[i].v);
+		assert_int_equal(0, rc);
+	}
+
+	assert_int_equal(TEST_JSON_UINT64_NVALUES,
+			 json_object_size(object.root));
+
+	object.valid = false;
+	rc = json_add_uint64(&object, "should fail 1", 0xf1ull);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+
+	rc = json_add_uint64(&object, "should fail 2", 0xf2ull);
+	assert_int_equal(JSON_ERROR, rc);
+}
+
+static void test_json_get_uint64(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	uint64_t v;
+	DATA_BLOB tmp = { 0 };
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	int i;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+		rc = json_add_uint64(&object, testme_u64[i].k, testme_u64[i].v);
+		assert_int_equal(0, rc);
+	}
+
+	assert_int_equal(TEST_JSON_UINT64_NVALUES,
+			 json_object_size(object.root));
+
+	/* validate values as decoded */
+	for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+		rc = json_get_uint64(&object, testme_u64[i].k, &v);
+		assert_int_equal(0, rc);
+		assert_memory_equal(&v, &testme_u64[i].v, sizeof(v));
+	}
+
+	/*
+	 * validate base64-well formedness of entries created to guard against
+	 * accidental encoding as json numbers
+	 */
+	for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+		rc = json_get_base64(ctx, &object, testme_u64[i].k, &tmp);
+		assert_int_equal(0, rc);
+		assert_int_equal(sizeof(uint64_t), tmp.length);
+		data_blob_free(&tmp);
+	}
+
+	object.valid = false;
+	rc = json_get_uint64(&object, "should fail 1", &v);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+
+	rc = json_get_uint64(&object, "should fail 2", &v);
+	assert_int_equal(JSON_ERROR, rc);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_json_add_time_t(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	rc = json_add_time_t(&object, "t_0", time (NULL));
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_time_t(&object, "t_1", LONG_LONG_MAX);
+	assert_int_equal(0, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	json_free(&object);
+}
+
+static void test_json_get_time_t(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	time_t t_0, t;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	t_0 = time (NULL);
+	rc = json_add_time_t(&object, "t_0", t_0);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_time_t(&object, "t_1", LONG_LONG_MAX);
+	assert_int_equal(0, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	rc = json_add_time_t(&object, "t_2", LONG_LONG_MIN);
+	assert_int_equal(0, rc);
+	assert_int_equal(3, json_object_size(object.root));
+
+	rc = json_get_time_t(&object, "t_0", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, t_0);
+
+	rc = json_get_time_t(&object, "t_1", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, LONG_LONG_MAX);
+
+	rc = json_get_time_t(&object, "t_2", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, LONG_LONG_MIN);
+
+	json_free(&object);
+}
+
 static void test_json_add_bool(void **state)
 {
 	struct json_object object;
@@ -173,6 +389,58 @@ static void test_json_add_string(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+static void test_json_get_string(void **state)
+{
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	struct json_object object;
+	const char *testdata = "{\"test_int\"    : 42"
+			       ",\"test_string\" : \"ook\""
+			       ",\"test_null\"   : null"
+			       "}";
+	const char *res = NULL;
+
+	object = json_new_null();
+
+	assert_int_equal(0, json_from_string(&object, testdata));
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	assert_int_equal(0, json_get_string(ctx, &object, "test_string", &res));
+	assert_non_null(res);
+	assert_string_equal(res, "ook");
+
+	res = NULL;
+	assert_int_equal(JSON_ERROR,
+			 json_get_string(ctx, &object, "test_int", &res));
+	assert_null(res);
+
+	assert_int_equal(JSON_ERROR,
+			 json_get_string(ctx, &object, "test_null", &res));
+	assert_null(res);
+
+	assert_int_equal(JSON_ERROR, json_get_string(ctx, &object, "enoent", &res));
+	assert_null(res);
+
+	res = "notnull";
+	assert_int_equal(JSON_ERROR, json_get_string(ctx, &object, NULL, &res));
+	assert_string_equal(res, "notnull");
+
+	assert_int_equal(JSON_ERROR,
+			 json_get_string(ctx, NULL, "test_string", &res));
+	assert_string_equal(res, "notnull");
+
+	assert_int_equal(JSON_ERROR,
+			 json_get_string(ctx, &object, "test_string", NULL));
+
+	json_free(&object);
+
+	assert_int_equal(JSON_ERROR,
+			 json_get_string(ctx, &object, "test_string", &res));
+	assert_string_equal(res, "notnull");
+
+	TALLOC_FREE(ctx);
+}
+
 static void test_json_add_object(void **state)
 {
 	struct json_object object;
@@ -560,6 +828,37 @@ static void test_json_add_sid(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+static void test_json_get_sid(void **state)
+{
+	struct json_object object;
+	const char *testdata = "{\"null\":\"S-1-0-0\""
+			       ",\"allauth\":\"S-1-5-42-42-42-42-42-42-42-42"
+						      "-42-42-42-42-42-42-42\""
+			       "}";
+	struct dom_sid sid;
+	struct dom_sid cmp;
+
+	object = json_new_null();
+
+	assert_int_equal(0, json_from_string(&object, testdata));
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	assert_int_equal(0, json_get_sid(&object, "null", &sid));
+	assert_true(dom_sid_equal(&sid, &global_sid_NULL));
+
+	assert_int_equal(0, json_get_sid(&object, "allauth", &sid));
+	assert_true(string_to_sid(&cmp, "S-1-5-42-42-42-42-42-42-42-42"
+						"-42-42-42-42-42-42-42"));
+	assert_true(dom_sid_equal(&sid, &cmp));
+
+	assert_int_equal(JSON_ERROR, json_get_sid(&object, "enoent", &sid));
+	assert_int_equal(JSON_ERROR, json_get_sid(&object, "null", NULL));
+	assert_int_equal(JSON_ERROR, json_get_sid(&object, NULL, &sid));
+
+	json_free(&object);
+}
+
 static void test_json_add_guid(void **state)
 {
 	struct json_object object;
@@ -600,6 +899,123 @@ static void test_json_add_guid(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+static void test_json_add_base64(void **state)
+{
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	int rc;
+	struct json_object object;
+	struct json_t *value = NULL;
+	const char *testdata = "foo";
+	const char *tmp = NULL;
+
+	object = json_new_object();
+
+	rc = json_add_base64(&object, "test",
+			     (DATA_BLOB)
+			     { .data = discard_const_p (uint8_t, testdata)
+			     , .length = strlen(testdata) });
+	assert_int_equal(0, rc);
+
+	value = json_object_get(object.root, "test");
+	assert_true(json_is_string(value));
+
+	rc = json_get_string(ctx, &object, "test", &tmp);
+	assert_int_equal(0, rc);
+	assert_non_null(tmp);
+	assert_string_equal(tmp, "Zm9v");
+
+	json_free(&object);
+
+	/* empty data -> empty encoded */
+	object = json_new_object();
+	rc = json_add_base64(&object, "test",
+			     (DATA_BLOB)
+			     { .data = discard_const_p (uint8_t, testdata)
+			     , .length = 0 });
+	assert_int_equal(0, rc);
+
+	rc = json_get_string(ctx, &object, "test", &tmp);
+	assert_int_equal(0, rc);
+	assert_non_null(tmp);
+	assert_int_equal(strlen(tmp), 0);
+
+	json_free(&object);
+
+	rc = json_add_base64(&object, "freed_object",
+			     (DATA_BLOB)
+			     { .data = discard_const_p (uint8_t, testdata)
+			     , .length = strlen(testdata) });
+	assert_int_equal(JSON_ERROR, rc);
+
+	TALLOC_FREE(ctx);
+}
+
+static void test_json_has_key(void **state)
+{
+	int rc;
+	struct json_object object;
+	const char *testdata = "{\"klucz\": true"
+			       ",\"key\": \"exists\"}";
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testdata);
+	assert_int_equal(0, rc);
+	assert_false(json_is_invalid(&object));
+
+	assert_true(json_has_key(&object, "key"));
+	assert_true(json_has_key(&object, "klucz"));
+	assert_false(json_has_key(&object, "clef"));
+	assert_false(json_has_key(&object, ""));
+	assert_false(json_has_key(&object, NULL));
+	assert_false(json_has_key(NULL, NULL));
+
+	json_free(&object);
+	assert_false(json_has_key(&object, "key"));
+}
+
+static void test_json_get_base64(void **state)
+{
+	int rc;
+	struct json_object object;
+	const char *testdata = "{\"test\":\"Zm9vAA==\""
+			       ",\"empty\":\"\""
+			       ",\"junk\":\"** MALFORMED **\""
+			       "}";
+	TALLOC_CTX *ctx = talloc_new(NULL);
+	DATA_BLOB tmp = { 0 };
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testdata);
+	assert_int_equal(0, rc);
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	rc = json_get_base64(ctx, &object, "test", &tmp);
+	assert_int_equal(0, rc);
+	assert_non_null(tmp.data);
+	assert_int_equal(tmp.length, 4);
+
+	assert_string_equal(tmp.data, "foo");
+	data_blob_free(&tmp);
+
+	rc = json_get_base64(ctx, &object, "empty", &tmp);
+	assert_int_equal(0, rc);
+	assert_int_equal(tmp.length, 0);
+	assert_null(tmp.data);
+	data_blob_free(&tmp);
+
+	rc = json_get_base64(ctx, &object, "junk", &tmp);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(tmp.length, 0);
+	assert_null(tmp.data);
+	data_blob_free(&tmp);
+
+	json_free(&object);
+	TALLOC_FREE(ctx);
+}
+
 static void test_json_to_string(void **state)
 {
 	struct json_object object;
@@ -634,6 +1050,165 @@ static void test_json_to_string(void **state)
 	TALLOC_FREE(ctx);
 }
 
+static void test_json_from_string(void **state)
+{
+	int rc;
+	struct json_object object;
+	/* decodable: object, array */
+	const char *testobj = "{\"test_int\"    : 42"
+			      ",\"test_string\" : \"ook\""
+			      ",\"test_null\"   : null"
+			      "}";
+	const char *testarr = "[ 42, 13.37 ]";
+	/* undecodable: primitives */
+	const char *testint = "42";
+	const char *teststring = "\"ook\"";
+	const char *testnull = "null";
+	/* bad inputs */
+	const char *testjunk1 = "{\"unbalanced\" : 42"
+			        ", UNEXPECTED_EOF";
+	const char *testjunk2 = "{ 42: \"malformed\" }";
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testobj);
+	assert_int_equal(0, rc);
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	assert_non_null(json_object_get(object.root, "test_int"));
+	assert_non_null(json_object_get(object.root, "test_string"));
+	assert_non_null(json_object_get(object.root, "test_null"));
+
+	json_free(&object);
+	object = json_new_null();
+
+	rc = json_from_string(&object, testarr);
+	assert_int_equal(0, rc);
+	assert_non_null(object.root);
+	assert_true(json_is_array(object.root));
+	assert_int_equal(2, json_array_size(object.root));
+
+	assert_true(json_is_integer(json_array_get(object.root, 0)));
+	assert_true(json_is_real(json_array_get(object.root, 1)));
+
+	json_free(&object);
+	object = json_new_null();
+
+	rc = json_from_string(&object, testint);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_from_string(&object, teststring);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_from_string(&object, testnull);
+
+	json_free(&object);
+	object = json_new_object();
+
+	rc = json_from_string(&object, testjunk1);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+	object = json_new_object();
+
+	rc = json_from_string(&object, testjunk2);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+}
+
+/*
+ * Note on below test: we use posix shm here because of its availability
+ * on older kernels; memfd_create(2) would be even simpler to use but it
+ * is also the more recent API.
+ */
+static void test_json_from_FILEp(void **state)
+{
+	int rc;
+	int fd;
+	FILE *fh;
+	ssize_t sz;
+	struct json_object object;
+	const char *testdata = "{\"test_int\"    : 42"
+			       ",\"test_string\" : \"ook\""
+			       ",\"test_null\"   : null"
+			       "}";
+	const char *testjunk1 = "{\"unbalanced\" : 42"
+			        ", UNEXPECTED_EOF";
+	const char *testjunk2 = "{ 42: \"malformed\" }";
+
+	fd = shm_open("samba-test", O_RDWR | O_CREAT | O_TRUNC, 0600);
+	assert_true(fd != -1);
+	(void)shm_unlink("samba-test"); /* the fd is sufficient */
+
+	fh = fdopen (dup (fd), "r"); /* dup(2) to preserve the fd through fclose() */
+	assert_non_null(fh);
+
+	object = json_new_null();
+	assert_false(json_is_invalid(&object));
+
+	/* test empty ``file'' */
+	rc = json_from_FILEp(&object, fh);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_false(json_is_invalid(&object));
+
+	(void)fclose(fh);
+
+	/* test with good data */
+	sz = write(fd, testdata, strlen(testdata));
+	assert_int_equal(sz, (ssize_t)strlen(testdata));
+	(void)lseek(fd, 0, SEEK_SET);
+
+	fh = fdopen (dup (fd), "r");
+	assert_non_null(fh);
+
+	rc = json_from_FILEp(&object, fh);
+	assert_int_equal(0, rc);
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	(void)fclose(fh);
+	assert_int_equal(ftruncate(fd, 0), 0);
+
+	/* test with garbage */
+	sz = write(fd, testjunk1, strlen(testdata));
+	assert_int_equal(sz, (ssize_t)strlen(testdata));
+	(void)lseek(fd, 0, SEEK_SET);
+
+	fh = fdopen (dup (fd), "r");
+	assert_non_null(fh);
+
+	rc = json_from_FILEp(&object, fh);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_false(json_is_invalid(&object));
+	assert_true(json_is_object(object.root));
+
+	(void)fclose(fh);
+	assert_int_equal(ftruncate(fd, 0), 0);
+
+	sz = write(fd, testjunk2, strlen(testdata));
+	assert_int_equal(sz, (ssize_t)strlen(testdata));
+	(void)lseek(fd, 0, SEEK_SET);
+
+	fh = fdopen (dup (fd), "r");
+	assert_non_null(fh);
+
+	rc = json_from_FILEp(&object, fh);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_false(json_is_invalid(&object));
+	assert_true(json_is_object(object.root));
+
+	(void)fclose(fh);
+	(void)close(fd);
+
+	assert_non_null(json_object_get(object.root, "test_int"));
+	assert_non_null(json_object_get(object.root, "test_string"));
+	assert_non_null(json_object_get(object.root, "test_null"));
+
+	json_free(&object);
+}
+
 static void test_json_get_array(void **state)
 {
 	struct json_object object;
@@ -833,8 +1408,15 @@ int main(int argc, const char **argv)
 {
 	const struct CMUnitTest tests[] = {
 		cmocka_unit_test(test_json_add_int),
+		cmocka_unit_test(test_json_add_uint32),
+		cmocka_unit_test(test_json_get_uint32),
+		cmocka_unit_test(test_json_add_uint64),
+		cmocka_unit_test(test_json_get_uint64),
+		cmocka_unit_test(test_json_add_time_t),
+		cmocka_unit_test(test_json_get_time_t),
 		cmocka_unit_test(test_json_add_bool),
 		cmocka_unit_test(test_json_add_string),
+		cmocka_unit_test(test_json_get_string),
 		cmocka_unit_test(test_json_add_object),
 		cmocka_unit_test(test_json_add_to_array),
 		cmocka_unit_test(test_json_add_timestamp),
@@ -842,10 +1424,16 @@ int main(int argc, const char **argv)
 		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_get_sid),
 		cmocka_unit_test(test_json_add_guid),
+		cmocka_unit_test(test_json_add_base64),
+		cmocka_unit_test(test_json_get_base64),
 		cmocka_unit_test(test_json_to_string),
+		cmocka_unit_test(test_json_from_string),
+		cmocka_unit_test(test_json_from_FILEp),
 		cmocka_unit_test(test_json_get_array),
 		cmocka_unit_test(test_json_get_object),
+		cmocka_unit_test(test_json_has_key),
 		cmocka_unit_test(test_audit_get_timestamp),
 	};
 
-- 
2.17.2


From fe9dfa4bead877f1b74aab4b8beea50a61ae3a48 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 14 Dec 2018 10:16:18 +0100
Subject: [RFC PATCH 04/10] s3: net: add json printer to `net primarytrust`

Dump the contents of the domain info and credentials (struct
secrets_domain_info1) as nested JSON. This allows saving that
information externally so it can be restored at a later point
without re-joining the host. (The corresponding restore
functionality is subject of a separate patch.)

As with the unstructured version, passing ``--force'' is required
to include the logon credentials in the output.

Naturally this functionality requires Samba to be built with
libjansson.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 source3/include/secrets.h                |   3 +
 source3/passdb/machine_account_secrets.c | 233 +++++++++++++++++++++++
 source3/utils/net.c                      |  26 ++-
 3 files changed, 256 insertions(+), 6 deletions(-)

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 24ae5bd0664..400ac8987c7 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -119,6 +119,9 @@ void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info,
 			       const char *name);
 char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
 				 const char *name, bool include_secrets);
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1,
+			       const char *name, bool include_secrets);
 NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
 					TALLOC_CTX *mem_ctx,
 					struct secrets_domain_info1 **pinfo);
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index dfc21f295a1..9a756831231 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -42,6 +42,10 @@
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_PASSDB
 
+#ifdef HAVE_JANSSON
+#include "audit_logging.h"
+#endif /* [HAVE_JANSSON] */
+
 static char *domain_info_keystr(const char *domain);
 
 static char *des_salt_key(const char *realm);
@@ -815,6 +819,235 @@ char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domai
 	return ret;
 }
 
+#ifdef HAVE_JANSSON
+static bool json_add_secrets_domain_info1_password
+			(struct json_object *jsdst,
+			 const char *key,
+			 const struct secrets_domain_info1_password *pw)
+{
+	int ret = 0;
+	struct json_object jsobj = json_new_object();
+
+	if (json_is_invalid(&jsobj)) {
+		DBG_ERR("refusing to write to invalid JSON object\n");
+
+		return false;
+	}
+
+	if (json_is_invalid(jsdst)) {
+		DBG_ERR("refusing to write to invalid JSON object\n");
+
+		goto failure;
+	}
+
+	if (key == NULL) {
+		DBG_ERR("refusing to access NULL key\n");
+
+		goto failure;
+	}
+
+	ret = json_add_time_t(&jsobj, "Change Time",
+			      nt_time_to_unix(pw->change_time));
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Change Server", pw->change_server);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (pw->change_server != NULL) {
+		ret = json_add_base64(&jsobj, "Cleartext Blob",
+				      pw->cleartext_blob);
+		if (ret != 0) {
+			goto failure;
+		}
+	}
+
+	ret = json_add_object(jsdst, key, &jsobj);
+
+	return true;
+failure:
+	json_free(&jsobj);
+	return false;
+}
+
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1,
+			       const char *name, bool include_secrets)
+{
+	int 		    ret    = 0;
+	char 		   *result = NULL;
+	struct json_object  jsinfo = json_new_object();
+	struct json_object  jsdns  = json_new_object();
+
+	if (json_is_invalid(&jsinfo) || json_is_invalid(&jsdns))
+	{
+		DBG_DEBUG("error setting up JSON value\n");
+
+		goto failure;
+	}
+
+	/* XXX: might use flag names */
+	ret = json_add_uint64(&jsinfo, "Reserved Flags", info1->reserved_flags);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_time_t(&jsinfo, "Join Time",
+			      nt_time_to_unix(info1->join_time));
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsinfo, "Computer Name", info1->computer_name);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsinfo, "Account Name", info1->account_name);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* XXX: might be symbolic name of enum like ndr_misc.c */
+	ret = json_add_int(&jsinfo, "Secure Channel Type",
+			   (int)info1->secure_channel_type);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* <domain-info> modeled after cmd_lsarpc.c */
+	ret = json_add_string(&jsdns, "Domain NetBios Name",
+			      info1->domain_info.name.string);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsdns, "Domain DNS Name",
+			      info1->domain_info.dns_domain.string);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsdns, "Domain Forest Name",
+			      info1->domain_info.dns_forest.string);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_sid(&jsdns, "Domain SID", info1->domain_info.sid);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_guid(&jsdns, "Domain GUID",
+			    &info1->domain_info.domain_guid);
+	if (ret != 0) {
+		goto failure;
+	}
+	/* </domain-info> */
+
+	/* XXX: might use flag names (netlogon.idl) */
+	ret = json_add_uint32(&jsinfo, "Trust Flags", info1->trust_flags);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* XXX: might use enum names (lsa.idl) */
+	ret = json_add_int(&jsinfo, "Trust Type", (int)info1->trust_type);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* XXX: might use attribute names (lsa.idl) */
+	ret = json_add_uint32(&jsinfo, "Trust Attributes", info1->trust_flags);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* NOTE: reserved_routing is unused, cf. secrets.idl */
+
+	/* XXX: might use flag names names (security.idl) */
+	ret = json_add_uint32(&jsinfo, "Supported Encryption Types",
+			      info1->supported_enc_types);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_base64(&jsinfo, "Salt Principal",
+			      (DATA_BLOB)
+			      { .data = discard_const_p(uint8_t,
+							info1->salt_principal)
+			      , .length = strlen (info1->salt_principal) });
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_time_t(&jsinfo, "Password Last Change",
+			      nt_time_to_unix (info1->password_last_change));
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_uint64(&jsinfo, "Password Changes",
+			      info1->password_changes);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* XXX: do we want to dump .next_change here? */
+
+	if (include_secrets) {
+		if (info1->password != NULL &&
+		    !json_add_secrets_domain_info1_password
+			(&jsinfo, "Password", info1->password))
+		{
+			goto failure;
+		}
+
+		if (info1->old_password != NULL &&
+		    !json_add_secrets_domain_info1_password
+			(&jsinfo, "Old Password", info1->old_password))
+		{
+			goto failure;
+		}
+
+		if (info1->older_password != NULL &&
+		    !json_add_secrets_domain_info1_password
+			(&jsinfo, "Older Password", info1->older_password))
+		{
+			goto failure;
+		}
+	}
+
+	ret = json_add_object(&jsinfo, "DNS Domain Info", &jsdns);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	result = json_to_string(mem_ctx, &jsinfo);
+	json_free(&jsinfo);
+
+	return result;
+failure:
+	json_free(&jsdns);
+	json_free(&jsinfo);
+
+	return NULL;
+}
+#else /* [HAVE_JANSSON] */
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1,
+			       const char *name, bool include_secrets)
+{
+	DBG_ERR("JSON support not available\n");
+
+	return NULL;
+}
+#endif /* [HAVE_JANSSON] */
+
 static NTSTATUS secrets_store_domain_info1_by_key(const char *key,
 					const struct secrets_domain_info1 *info1)
 {
diff --git a/source3/utils/net.c b/source3/utils/net.c
index 8350e8c0967..86c5e623fed 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -127,16 +127,30 @@ static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
 		return 1;
 	}
 
-	str = secrets_domain_info_string(info, info, domain, include_secrets);
-	if (str == NULL) {
-		d_fprintf(stderr, "secrets_domain_info_string() failed.\n");
-		return 1;
+	if (c->opt_json) {
+		str = secrets_domain_info_json(info, info, domain,
+					       include_secrets);
+		if (str == NULL) {
+			d_fprintf(stderr,
+				  "secrets_domain_info_json() failed.\n");
+			return 1;
+		}
+	} else {
+		str = secrets_domain_info_string(info, info, domain,
+						 include_secrets);
+		if (str == NULL) {
+			d_fprintf(stderr,
+				  "secrets_domain_info_string() failed.\n");
+			return 1;
+		}
 	}
 
 	d_printf("%s", str);
 	if (!c->opt_force) {
-		d_printf(_("The password values are only included using "
-			 "-f flag.\n"));
+		/* on stderr to prevent interleaving with JSON data */
+		d_fprintf(stderr,
+			  _("The password values are only included using "
+			    "-f flag.\n"));
 	}
 
 	TALLOC_FREE(info);
-- 
2.17.2


From 38bdf48cf02bbe143686b8cc33b822a920b695c0 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 14 Dec 2018 17:01:56 +0100
Subject: [RFC PATCH 05/10] s3: net: add primarytrust subcommand `readinfo`

Implement the inverse operation of ``net primarytrust dumpinfo
--json`` to restore an earlier domaininfo dump. `readinfo'
accepts input of the same JSON structure with certain components
being optional: ``Join Time'', ``Supported Encryption Types'',
``Password Last Change'', ``Change Time'', as well as the three
passwords.

As with dumpinfo, the `--force' flag must be passed for secrets
to be injected as well.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 source3/include/secrets.h                |   5 +
 source3/passdb/machine_account_secrets.c | 405 +++++++++++++++++++++++
 source3/utils/net.c                      |  68 ++++
 3 files changed, 478 insertions(+)

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 400ac8987c7..8078fe53da3 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -125,6 +125,11 @@ char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
 NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
 					TALLOC_CTX *mem_ctx,
 					struct secrets_domain_info1 **pinfo);
+struct json_object;
+NTSTATUS secrets_set_domain_info_json(const char *domain,
+				      TALLOC_CTX *mem_ctx,
+				      const struct json_object *jsdata,
+				      bool include_secrets);
 NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
 					 const char *cleartext_unix,
 					 TALLOC_CTX *mem_ctx,
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index 9a756831231..efe3b9f766c 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -873,6 +873,82 @@ failure:
 	return false;
 }
 
+static NTSTATUS json_get_secrets_domain_info1_password
+			(TALLOC_CTX *ctx,
+			 const struct json_object *jssrc,
+			 const char *key,
+			 struct secrets_domain_info1_password **dst)
+{
+	int ret;
+	time_t tmp_time;
+	struct secrets_domain_info1_password *pw;
+	struct json_object jspw;
+
+	if (json_is_invalid(jssrc)) {
+		DBG_ERR("password: refusing to read domain password from "
+			"invalid JSON object\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (key == NULL) {
+		DBG_ERR("password: refusing to access NULL key\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (dst == NULL) {
+		DBG_ERR("password: refusing to write to NULL address\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	pw = talloc_zero_size(ctx, sizeof(*pw));
+	if (pw == NULL) {
+		DBG_ERR("password: error allocating memory for domain "
+			"password\n");
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	jspw = json_get_object(discard_const_p (struct json_object, jssrc),
+			       key);
+	if (json_is_invalid(&jspw)) {
+		DBG_ERR("password: failed to obtain field [%s] from domain "
+			"info\n", key);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_time_t(&jspw, "Change Time", &tmp_time);
+	if (ret != 0) {
+		DBG_DEBUG("password: Optional field \"Change Time\" missing or "
+			  "invalid; falling back on current time.\n");
+		unix_to_nt_time(&pw->change_time, time(NULL));
+	} else {
+		unix_to_nt_time(&pw->change_time, tmp_time);
+	}
+
+	ret = json_get_string(ctx, &jspw, "Change Server", &pw->change_server);
+	if (ret != 0) {
+		DBG_ERR("password: Field \"Change Server\" missing or "
+			"invalid.\n");
+		goto failure;
+	}
+
+	ret = json_get_base64(ctx, &jspw, "Cleartext Blob",
+			      &pw->cleartext_blob);
+	if (ret != 0) {
+		DBG_ERR("password: Field \"Cleartext Blob\" missing or "
+			"invalid.\n");
+		goto failure;
+	}
+
+	*dst = pw;
+
+	return NT_STATUS_OK;
+failure:
+	free(discard_const_p(char,pw->change_server));
+	data_blob_free(&pw->cleartext_blob);
+
+	return NT_STATUS_INVALID_PARAMETER;
+}
+
 char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
 			       const struct secrets_domain_info1 *info1,
 			       const char *name, bool include_secrets)
@@ -1530,6 +1606,335 @@ static NTSTATUS secrets_domain_info_password_create(TALLOC_CTX *mem_ctx,
 	return NT_STATUS_OK;
 }
 
+#ifdef HAVE_JANSSON
+static NTSTATUS
+secrets_json_get_domain_info (TALLOC_CTX *mem_ctx,
+			      const struct json_object *jsdata,
+			      struct secrets_domain_info1 **dst,
+			      bool include_secrets)
+{
+	int ret;
+	NTSTATUS status;
+	struct secrets_domain_info1 *info1;
+	time_t tmp_time;
+	DATA_BLOB tmp_blob = { 0 };
+	struct json_object jsdns;
+
+	info1 = talloc_zero(mem_ctx, struct secrets_domain_info1);
+	if (info1 == NULL) {
+		DBG_ERR("talloc_zero failed for struct secrets_domain_info1\n");
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	ret = json_get_uint64(jsdata, "Reserved Flags", &info1->reserved_flags);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Reserved Flags\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_time_t(jsdata, "Join Time", &tmp_time);
+	if (ret != 0) {
+		DBG_DEBUG("Optional field \"Join Time\" missing or "
+			  "invalid; falling back on current time.\n");
+		unix_to_nt_time(&info1->join_time, time(NULL));
+	} else {
+		unix_to_nt_time(&info1->join_time, tmp_time);
+	}
+
+	if (json_has_key (jsdata, "Computer Name")) {
+		ret = json_get_string(mem_ctx, jsdata, "Computer Name",
+				      &info1->computer_name);
+		if (ret != 0) {
+			DBG_ERR("Optional field \"Computer Name\" present but "
+				"invalid.\n");
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+	} else {
+		DBG_DEBUG("Optional field \"Computer Name\" absent, using "
+			  "Netbios name from smb.conf [%s].\n",
+			  lp_netbios_name());
+		info1->computer_name = lp_netbios_name();
+	}
+
+	if (json_has_key (jsdata, "Computer Name")) {
+		ret = json_get_string(mem_ctx, jsdata, "Account Name",
+				      &info1->account_name);
+		if (ret != 0) {
+			DBG_ERR("Optional field \"Account Name\" present but "
+				"invalid.\n");
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+	} else {
+		DBG_DEBUG("Optional field \"Account Name\" absent, using "
+			  "computer name [%s] as the base.\n",
+			  info1->computer_name);
+		info1->account_name = talloc_asprintf(mem_ctx, "%s$",
+						      info1->computer_name);
+	}
+
+	ret = json_get_int(jsdata, "Secure Channel Type",
+			   (int *)&info1->secure_channel_type);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Secure Channel Type\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_uint32(jsdata, "Trust Flags", &info1->trust_flags);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Trust Flags\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_int(jsdata, "Trust Type", (int *)&info1->trust_type);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Trust Type\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_uint32(jsdata, "Trust Attributes",
+			      &info1->trust_attributes);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Trust Attributes\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_uint32(jsdata, "Supported Encryption Types",
+			      &info1->supported_enc_types);
+	if (ret != 0) {
+		DBG_DEBUG("Optional field \"Supported Encryption Types\" "
+			  "missing or invalid, falling back on zero.\n");
+	}
+
+	if (json_has_key (jsdata, "Salt Principal")) {
+		ret = json_get_base64(mem_ctx, jsdata, "Salt Principal",
+				      &tmp_blob);
+
+		if (ret != 0) {
+			DBG_ERR("Optional field \"Salt Principal\" invalid.\n");
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+	} else  {
+		const char *nacl = kerberos_secrets_fetch_salt_princ();
+
+		if (nacl == NULL) {
+			DBG_ERR("Failed to fall back on default salt for "
+				"missing optional field \"Salt Principal\".\n");
+
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+
+		tmp_blob = data_blob_string_const(talloc_strdup(mem_ctx, nacl));
+		if (tmp_blob.data == NULL) {
+			DBG_ERR("Out of memory allocating %zu B for default "
+				"salt.\n",
+				strlen (nacl));
+
+			return NT_STATUS_NO_MEMORY;
+		}
+	}
+
+	info1->salt_principal =
+		talloc_zero_size(mem_ctx, tmp_blob.length + 1);
+	if (info1->salt_principal == NULL) {
+		DBG_ERR("Out of memory allocating %zu B for salt principal\n",
+			tmp_blob.length + 1);
+		data_blob_free(&tmp_blob);
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	memcpy(discard_const_p(char, info1->salt_principal),
+	       tmp_blob.data, tmp_blob.length);
+	data_blob_free(&tmp_blob);
+
+	ret = json_get_time_t(jsdata, "Password Last Change", &tmp_time);
+	if (ret != 0) {
+		DBG_DEBUG("Optional field \"Password Last Change\" missing or "
+			  "invalid; falling back on current time.\n");
+		unix_to_nt_time(&info1->password_last_change, time(NULL));
+	} else {
+		unix_to_nt_time(&info1->password_last_change, tmp_time);
+	}
+
+	ret = json_get_uint64(jsdata, "Password Changes",
+			      &info1->password_changes);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Password Changes\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (json_has_key (jsdata, "Password")) {
+		if (!include_secrets) {
+			DBG_ERR("Setting the \"Password\" field requires "
+				"--force\n");
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+		status = json_get_secrets_domain_info1_password
+			(mem_ctx, jsdata, "Password", &info1->password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Password\" invalid\n");
+			return status;
+		}
+	}
+
+	if (json_has_key (jsdata, "Old Password")) {
+		if (!include_secrets) {
+			DBG_ERR("Setting the \"Old Password\" field requires "
+				"--force\n");
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+		status = json_get_secrets_domain_info1_password
+			(mem_ctx, jsdata, "Old Password", &info1->old_password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Old Password\" invalid\n");
+			return status;
+		}
+	}
+
+	if (json_has_key (jsdata, "Older Password")) {
+		if (!include_secrets) {
+			DBG_ERR("Setting the \"Older Password\" field requires "
+				"--force\n");
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+
+		status = json_get_secrets_domain_info1_password
+				(mem_ctx, jsdata, "Older Password",
+				 &info1->older_password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Older Password\" invalid\n");
+			return status;
+		}
+	}
+
+	jsdns = json_get_object(discard_const_p(struct json_object, jsdata),
+				"DNS Domain Info");
+	if (json_is_invalid(&jsdns)) {
+		DBG_ERR("Mandatory field \"DNS Domain Info\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(mem_ctx, &jsdns, "Domain NetBios Name",
+			      &info1->domain_info.name.string);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain NetBios Name\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(mem_ctx, &jsdns, "Domain DNS Name",
+			      &info1->domain_info.dns_domain.string);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain DNS Name\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(mem_ctx, &jsdns, "Domain Forest Name",
+			      &info1->domain_info.dns_forest.string);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain Forest Name\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	info1->domain_info.sid =
+		talloc_zero_size(mem_ctx, sizeof(*info1->domain_info.sid));
+	if (info1->domain_info.sid == NULL) {
+		DBG_ERR("Out of memory allocating storage for domain SID\n");
+		json_free(&jsdns);
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	ret = json_get_sid(&jsdns, "Domain SID", info1->domain_info.sid);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain SID\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_guid(&jsdns, "Domain GUID",
+			    &info1->domain_info.domain_guid);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain GUID\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	*dst = info1;
+
+	return NT_STATUS_OK;
+}
+#else /* [HAVE_JANSSON] */
+static NTSTATUS
+secrets_json_get_domain_info (TALLOC_CTX *mem_ctx,
+			      const struct json_object *jsdata,
+			      struct secrets_domain_info1 **dst,
+			      bool include_secrets)
+{
+	DBG_ERR("JSON support not available\n");
+
+	return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* [HAVE_JANSSON] */
+
+NTSTATUS secrets_set_domain_info_json(const char *domain,
+				      TALLOC_CTX *mem_ctx,
+				      const struct json_object *jsdata,
+				      bool include_secrets)
+{
+	int ret;
+	struct secrets_domain_info1 *info1 = NULL;
+	struct db_context *db;
+	NTSTATUS status;
+
+	status = secrets_json_get_domain_info (mem_ctx, jsdata, &info1,
+					       include_secrets);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_json_get_domain_info() failed\n");
+		return status;
+	}
+
+	db = secrets_db_ctx();
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	secrets_debug_domain_info(DBGLVL_INFO, info1, "set");
+
+	status = secrets_store_domain_info(info1, true /* upgrade */);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	return status;
+}
+
 NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
 					TALLOC_CTX *mem_ctx,
 					struct secrets_domain_info1 **pinfo)
diff --git a/source3/utils/net.c b/source3/utils/net.c
index 86c5e623fed..0604d3b937a 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -44,6 +44,7 @@
 #include "popt_common_cmdline.h"
 #include "utils/net.h"
 #include "secrets.h"
+#include "librpc/gen_ndr/netlogon.h"
 #include "lib/netapi/netapi.h"
 #include "../libcli/security/security.h"
 #include "passdb.h"
@@ -56,6 +57,11 @@
 #include "utils/net_afs.h"
 #endif
 
+#ifdef HAVE_JANSSON
+#include "audit_logging.h"
+#endif /* [HAVE_JANSSON] */
+
+
 /***********************************************************************/
 /* end of internationalization section                                 */
 /***********************************************************************/
@@ -94,6 +100,57 @@ static void set_line_buffering(FILE *f)
 	setvbuf(f, NULL, _IOLBF, 0);
 }
 
+#ifdef HAVE_JANSSON
+static int net_primarytrust_readinfo(struct net_context *c, int argc,
+				     const char **argv)
+{
+	int ret = 0;
+	NTSTATUS status = NT_STATUS_OK;
+	int role = lp_server_role();
+	struct json_object jsobj = json_new_object ();
+	const char *domain = lp_workgroup();
+	bool include_secrets = c->opt_force;
+
+	if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+		d_printf(_("net primarytrust readinfo is only supported "
+			 "on a DOMAIN_MEMBER for now.\n"));
+		return 1;
+	}
+
+	if (isatty(STDIN_FILENO) == 1) {
+		set_line_buffering(stderr);
+		/* signal user that net expects some input */
+		d_fprintf(stderr, "hint: waiting for input\n");
+	}
+
+	ret = json_from_FILEp(&jsobj, stdin);
+	if (ret != 0 || json_is_invalid(&jsobj)) {
+		d_fprintf(stderr, _("Error reading JSON from stdin.\n"));
+		return 1;
+	}
+
+	status = secrets_set_domain_info_json(domain, talloc_tos(), &jsobj,
+					      include_secrets);
+	json_free (&jsobj);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		d_fprintf(stderr,
+			  _("Failed to inject the provided domain info.\n"));
+		return 1;
+	}
+
+	return 0;
+}
+#else /* [HAVE_JANSSON] */
+static int net_primarytrust_readinfo(struct net_context *_c, int _argc,
+				     const char **_argv)
+{
+	d_fprintf(stderr, _("JSON support not available\n"));
+
+	return -1;
+}
+#endif /* [HAVE_JANSSON] */
+
 static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
 				     const char **argv)
 {
@@ -179,6 +236,17 @@ static int net_primarytrust(struct net_context *c, int argc, const char **argv)
 			   "in secrets.tdb.\n"
 			   "    Requires the -f flag to include the password values.")
 		},
+		{
+			"readinfo",
+			net_primarytrust_readinfo,
+			NET_TRANSPORT_LOCAL,
+			N_("Set the workstation trust"),
+			N_("  net [options] primarytrust readinfo'\n"
+			   "    Set the workstation trust from JSON dump "
+			   "in secrets.tdb\n."
+			   "    Requires the -f flag to include the password "
+			   "values.")
+		},
 		{NULL, NULL, 0, NULL, NULL}
 	};
 
-- 
2.17.2


From 37a4ed6230a4d085602764b6d6e7f12aa081d411 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 13:57:42 +0200
Subject: [RFC PATCH 06/10] testprogs/blackbox/subunit.sh: add json output
 helper

Add a naive JSON validator to ensure no stray data interleaves
into the structured output.

Depends on jq(1). If the tools is absent, the test is skipped.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 testprogs/blackbox/subunit.sh | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/testprogs/blackbox/subunit.sh b/testprogs/blackbox/subunit.sh
index bcc5bd6a928..919d62bce5d 100755
--- a/testprogs/blackbox/subunit.sh
+++ b/testprogs/blackbox/subunit.sh
@@ -115,6 +115,33 @@ testit_grep () {
 	return $status
 }
 
+if ! command -v jq >/dev/null ; then
+	testit_json () { subunit_skip_test $@ ; }
+else
+	# This returns 0 if the command gave success and the output on stdout
+	# was accepted as JSON by jq; all other cases return != 0
+	testit_json () {
+		name="$1"
+		shift
+		cmdline="$@"
+		subunit_start_test "$name"
+		output=`$cmdline >&1`
+		status=$?
+		if [ x$status != x0 ]; then
+			printf '%s' "$output" | subunit_fail_test "$name"
+			return $status
+		fi
+		printf '%s' "$output" | jq empty
+		gstatus=$?
+		if [ x$gstatus = x0 ]; then
+			subunit_pass_test "$name"
+		else
+			printf 'jq: output failed to parse as JSON:\n%s' "$output" | subunit_fail_test "$name"
+		fi
+		return $status
+	}
+fi
+
 testit_expect_failure () {
 	name="$1"
 	shift
-- 
2.17.2


From bd8d6e4a6dbdd4cd81d5c6b562a8a3f55c30c4e5 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 14:00:35 +0200
Subject: [RFC PATCH 07/10] testprogs: blackbox test `net primarytrust
 {dump,read}info`

Test wellformedness of --json output and whether primarytrust
readinfo accepts it back as-is.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 testprogs/blackbox/test_net_ads.sh | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/testprogs/blackbox/test_net_ads.sh b/testprogs/blackbox/test_net_ads.sh
index d3c4de5b741..7c0eb0a1187 100755
--- a/testprogs/blackbox/test_net_ads.sh
+++ b/testprogs/blackbox/test_net_ads.sh
@@ -60,6 +60,11 @@ testit "test setspn list shows the newly deleted spn ($spn) is gone" test $found
 
 testit "changetrustpw" $VALGRIND $net_tool ads changetrustpw || failed=`expr $failed + 1`
 
+testit "primarytrust dumpinfo" $VALGRIND $net_tool primarytrust dumpinfo --force || failed=`expr $failed + 1`
+testit_json "primarytrust dumpinfo(json)" $net_tool primarytrust dumpinfo --json --force || failed=`expr $failed + 1`
+dominfo="$($net_tool primarytrust dumpinfo --json --force 2>/dev/null)"
+testit "primarytrust readinfo(json)" $VALGRIND $net_tool primarytrust readinfo --force <<<"${dominfo}" || failed=`expr $failed + 1`
+
 testit "leave" $VALGRIND $net_tool ads leave -U$DC_USERNAME%$DC_PASSWORD || failed=`expr $failed + 1`
 
 # Test with kerberos method = secrets and keytab
-- 
2.17.2


From 3595a4f8b8143a7a7d6d1b738c21311da59a6046 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 11 Oct 2018 11:06:25 +0200
Subject: [RFC PATCH 08/10] python/samba/tests: add stdin handling helper when
 testing commands

Add an extended version ``check_output_with_stdin()`` which takes
a string that is passed to the standard input of the child and
otherwise assigns /dev/null. Under python 3 it supports timeouts
as well.

The rationale for the addition is that ``check_output()'' lacks a
way to control stdin behavior for the forked process. This is
inconvenient when working with binaries that assume interactive
context and prompt for user inputs unexpectedly.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 python/samba/tests/__init__.py | 35 ++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py
index 2eaf9785a1d..540fa08220d 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -58,6 +58,10 @@ except ImportError:
 
 BINDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
                                       "../../../../bin"))
+try:
+    DEVNULL_FILEOBJ = subprocess.DEVNULL
+except AttributeError: # python 2
+    DEVNULL_FILEOBJ = open ("/dev/null", "r")
 
 HEXDUMP_FILTER = bytearray([x if ((len(repr(chr(x))) == 3) and (x < 127)) else ord('.') for x in range(256)])
 
@@ -430,6 +434,37 @@ class BlackboxTestCase(TestCaseInTempDir):
             raise BlackboxProcessError(retcode, line, stdoutdata, stderrdata)
         return stdoutdata
 
+    def check_output_with_stdin(self, line, stdin=None, timeout=None):
+        """
+        Like check_output() above, but with fine-grained control over stdin.
+
+        :param line:    Command line to execute (supports only strings for
+                        system() on account of the `self._make_cmdline` pass).
+        :param stdin:   If *None*, assign `/dev/null` to fd 0; otherwise feed
+                        the value (*bytes*) to the child.
+        :param timeout: Timeout in seconds; ignored with python 2.x.
+        :return:        *bytes*,
+        """
+
+        input = DEVNULL_FILEOBJ
+        if stdin is not None:
+            input = subprocess.PIPE
+
+        line = self._make_cmdline(line)
+        p = subprocess.Popen(line, stdin=input, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE, shell=True,
+                             close_fds=True)
+
+        if sys.hexversion < 0x30000000:
+            stdoutdata, stderrdata = p.communicate(input=stdin)
+        else:
+            stdoutdata, stderrdata = p.communicate(input=stdin,
+                                                   timeout=timeout)
+        retcode = p.returncode
+        if retcode != 0:
+            raise BlackboxProcessError(retcode, line, stdoutdata, stderrdata)
+        return stdoutdata
+
     # Generate a random password that can be safely  passed on the command line
     # i.e. it does not contain any shell meta characters.
     def random_password(self, count=32):
-- 
2.17.2


From b3b7cd1b9b32937057682f24dd4e33ef644e8511 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 12 Oct 2018 14:54:28 +0200
Subject: [RFC PATCH 09/10] python/samba/tests: add simple file hashing helper

Wrap MD5 hashing of files to detect side-effects during blackbox
tests.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 python/samba/tests/__init__.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py
index 540fa08220d..60bbad14ac6 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -18,6 +18,7 @@
 
 """Samba Python tests."""
 
+import hashlib
 import os
 import tempfile
 import warnings
@@ -476,6 +477,15 @@ class BlackboxTestCase(TestCaseInTempDir):
                     string.digits) for x in range(count - 3))
         return password
 
+    def hash_file (self, path):
+        md5ctx = hashlib.md5 ()
+        with open (path, "rb") as fh:
+            while True:
+                chunk = fh.read (4096)
+                if chunk == b"": break
+                md5ctx.update (chunk)
+        return md5ctx.digest ()
+
 
 def connect_samdb(samdb_url, lp=None, session_info=None, credentials=None,
                   flags=0, ldb_options=None, ldap_only=False, global_schema=True):
-- 
2.17.2


From 822cb5279dc278c2856f6e1b2cd0e31b4a6e9704 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 5 Oct 2018 11:00:09 +0200
Subject: [RFC PATCH 10/10] tests/blackbox: cover `net primarytrust' in json
 tests

Run the existing test variants (well-formedness, matching keys)
on `net primarytrust dumpinfo' as well. Since this extends the
scope of the tests, the script and the test objects are renamed
to something more generic. The added test requires a joined
domain member with machine account credentials present in
secrets.tdb, so move it to the `ad_member:local' suite.

Also add new test script for replaying a domain info dump using
`net primarytrust readinfo'. This test focuses on behavior wrt.
invalid inputs.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 .../blackbox/{netads_json.py => net_json.py}  |  62 ++--
 .../samba/tests/blackbox/netprimarytrust.py   | 321 ++++++++++++++++++
 source4/selftest/tests.py                     |   7 +-
 3 files changed, 367 insertions(+), 23 deletions(-)
 rename python/samba/tests/blackbox/{netads_json.py => net_json.py} (53%)
 create mode 100644 python/samba/tests/blackbox/netprimarytrust.py

diff --git a/python/samba/tests/blackbox/netads_json.py b/python/samba/tests/blackbox/net_json.py
similarity index 53%
rename from python/samba/tests/blackbox/netads_json.py
rename to python/samba/tests/blackbox/net_json.py
index 1c254468d36..759b485984f 100644
--- a/python/samba/tests/blackbox/netads_json.py
+++ b/python/samba/tests/blackbox/net_json.py
@@ -1,4 +1,4 @@
-# Blackbox tests for the "net ads ... --json" commands
+# Blackbox tests for the "net ... --json" commands
 # Copyright (C) 2018 Intra2net AG
 #
 # This program is free software; you can redistribute it and/or modify
@@ -21,9 +21,17 @@ import re
 import samba.tests
 from samba.compat import get_string
 
-COMMAND         = "bin/net ads"
+COMMAND_NET_ADS      = "bin/net ads"
+COMMAND_NET_PTRUST   = "bin/net primarytrust"
 # extract keys from non-json version
-PLAIN_KEY_REGEX = re.compile ("^([^ \t:][^:]*):")
+PLAIN_KEY_REGEX      = re.compile ("^([^ \t:][^:]*):")
+
+
+def compat_json_loads (raw):
+    # in some python versions (<=3.4?) json.loads() is picky about the input
+    if isinstance (raw, bytes):
+        raw = raw.decode ("ascii", "ignore")
+    return json.loads (raw)
 
 class BaseWrapper (object):
     """
@@ -31,19 +39,20 @@ class BaseWrapper (object):
     being run by unittest directly.
     """
 
-    class NetAdsJSONTests_Base(samba.tests.BlackboxTestCase):
+    class NetJSONTests_Base(samba.tests.BlackboxTestCase):
         """Blackbox tests for JSON output of the net ads suite of commands."""
-        subcmd = None
+        command = "/bin/false"
+        subcmd  = None
 
         def setUp(self):
-            super(BaseWrapper.NetAdsJSONTests_Base, self).setUp()
+            super(BaseWrapper.NetJSONTests_Base, self).setUp()
 
         def test_json_wellformed (self):
             """The output of ``--json`` commands must parse as JSON."""
-            argv = "%s %s --json" % (COMMAND, self.subcmd)
+            argv = "%s %s --json" % (self.command, self.subcmd)
             try:
-                out = self.check_output(argv)
-                json.loads (get_string(out))
+                out = self.check_output_with_stdin(argv, stdin=None)
+                compat_json_loads (get_string (out))
             except samba.tests.BlackboxProcessError as e:
                 self.fail("Error calling [%s]: %s" % (argv, e))
 
@@ -53,32 +62,45 @@ class BaseWrapper (object):
             respective plain counterpart.
 
             Does not check nested dictionaries (e. g. the ``Flags`` value of
-            ``net ads lookup``..
+            ``net ads lookup``..)
             """
-            argv = "%s %s" % (COMMAND, self.subcmd)
+            if self.matching is False: self.skipTest ("test does not apply")
+
+            argv = "%s %s" % (self.command, self.subcmd)
             try:
-                out_plain = get_string(self.check_output(argv))
+                out_plain = get_string(self.check_output_with_stdin(argv,
+                                                                    stdin=None))
             except samba.tests.BlackboxProcessError as e:
                 self.fail("Error calling [%s]: %s" % (argv, e))
 
-            argv = "%s %s --json" % (COMMAND, self.subcmd)
+            argv = "%s %s --json" % (self.command, self.subcmd)
             try:
-                out_jsobj = self.check_output(argv)
+                out_jsobj = get_string(self.check_output_with_stdin(argv,
+                                                                    stdin=None))
             except samba.tests.BlackboxProcessError as e:
                 self.fail("Error calling [%s]: %s" % (argv, e))
 
-            parsed = json.loads (get_string(out_jsobj))
+            parsed = compat_json_loads (get_string (out_jsobj))
 
             for key in [ re.match (PLAIN_KEY_REGEX, line).group(1)
                          for line in out_plain.split ("\n")
-                            if line != "" and line [0] not in " \t:" ]:
+                         if line != "" and line [0] not in " \t:" ]:
                 self.assertTrue (parsed.get (key) is not None)
                 del parsed [key]
 
             self.assertTrue (len (parsed) == 0) # tolerate no leftovers
 
-class NetAdsJSONInfoTests(BaseWrapper.NetAdsJSONTests_Base):
-    subcmd = "info"
+class NetJSON_AdsInfoTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_ADS
+    subcmd   = "info"
+    matching = True
+
+class NetJSON_AdsLookupTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_ADS
+    subcmd   = "lookup"
+    matching = True
 
-class NetAdsJSONlookupTests(BaseWrapper.NetAdsJSONTests_Base):
-    subcmd = "lookup"
+class NetJSON_PrimarytrustDumpinfoTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_PTRUST
+    subcmd   = "dumpinfo"
+    matching = False
diff --git a/python/samba/tests/blackbox/netprimarytrust.py b/python/samba/tests/blackbox/netprimarytrust.py
new file mode 100644
index 00000000000..2ea9ab92b6c
--- /dev/null
+++ b/python/samba/tests/blackbox/netprimarytrust.py
@@ -0,0 +1,321 @@
+# Blackbox tests for the "net primarytrust ... --json" commands
+# Copyright (C) 2018 Intra2net AG
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import json
+import os
+
+import samba.tests
+from samba.compat import get_string
+
+COMMAND              = "bin/net primarytrust"
+CHILD_TIMEOUT        = 3 # s, don't delay tests expecting input
+SECRETS_PATH_FMT     = "%s/%s/private/secrets.tdb"
+
+def compat_json_loads (raw):
+    # in some python versions (<=3.4?) json.loads() is picky about the input
+    if isinstance (raw, bytes):
+        raw = raw.decode ("ascii", "ignore")
+    return json.loads (raw)
+
+DOMINFO_BLOB_OK = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "KgAAAAAAAAA=",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "NWUTXAAAAAA=",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "ysIkXAAAAAA=",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain DNS Name": "addom.samba.example.com",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-1-5-21-42-1337-1701",
+        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_OK_MISSING_ENCTYPES = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "KgAAAAAAAAA=",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "NWUTXAAAAAA=",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "ysIkXAAAAAA=",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain DNS Name": "addom.samba.example.com",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-1-5-21-42-1337-1701",
+        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_OK_MISSING_TIMESTAMPS = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Server": "ADDC",
+        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain DNS Name": "addom.samba.example.com",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-1-5-21-42-1337-1701",
+        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_BAD_MISSING_FIELDS_TRUSTFLAGS = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Join Time": "KgAAAAAAAAA=",
+      "Secure Channel Type": 2,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "NWUTXAAAAAA=",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "ysIkXAAAAAA=",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain DNS Name": "addom.samba.example.com",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-1-5-21-42-1337-1701",
+        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_BAD_MISSING_FIELDS_DNSDOMAIN = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Join Time": "KgAAAAAAAAA=",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "NWUTXAAAAAA=",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "ysIkXAAAAAA=",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-1-5-21-42-1337-1701",
+        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_BAD_MALFORMED_SID_1 = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "KgAAAAAAAAA=",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "NWUTXAAAAAA=",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "ysIkXAAAAAA=",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain DNS Name": "addom.samba.example.com",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-256-5-21-42-1337-1701",
+        "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+class NetPrimarytrustBaseWrapper (object):
+    """
+    Guard the base so it doesn't inherit from TestCase. This prevents it from
+    being run by unittest directly.
+    """
+
+    class NetPrimarytrustTests_Base(samba.tests.BlackboxTestCase):
+        """Blackbox tests for replaying machine account credentials."""
+        expect_success  = True
+        expect_equalout = True
+        inblob          = DOMINFO_BLOB_OK
+        backupfile      = None
+        secrets_path    = None
+
+        def setUp(self):
+            """
+            Save a backup of the secrets.tdb that the main test manipulates.
+            """
+            super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+                .setUp()
+
+            envname = os.getenv ("ENVNAME")
+            stpfx   = os.getenv ("TESTENV_SELFTEST_PREFIX")
+            assert envname is not None
+            assert stpfx   is not None
+            self.secrets_path = SECRETS_PATH_FMT % (stpfx, envname)
+
+            assert self.check_output("tdbbackup \"%s\"" % self.secrets_path) == b""
+            self.backupfile = "%s.bak" % self.secrets_path
+
+        def tearDown(self):
+            """
+            Move backed up secrets.tdb over the changed file.
+            """
+            super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+                .tearDown()
+            if self.backupfile is not None: # only after successful tdbbackup
+                try:
+                    os.rename (self.backupfile, self.secrets_path)
+                except OSError as exn: # some test envs are weird
+                    print("no [%s] during test tearDown() [err: %s]; ignoring"
+                          % (self.backupfile, exn))
+
+        def test_json_readinfo (self):
+            """
+            Postconditions: the command must have terminated zero without
+            generating any messages on stdout. Also, the injection of
+            credentials must have had an effect on the ``secrets.tdb`` used
+            during the test.
+            """
+            argv_r = "%s readinfo --force"        % COMMAND
+            argv_d = "%s dumpinfo --force --json" % COMMAND
+            failed = False
+            out    = None
+            chksum = self.hash_file (self.secrets_path)
+
+            js1 = compat_json_loads (self.check_output (argv_d))
+
+            try:
+                out = self.check_output_with_stdin(argv_r, stdin=self.inblob,
+                                                   timeout=CHILD_TIMEOUT)
+            except samba.tests.BlackboxProcessError as e:
+                if self.expect_success is True:
+                    self.fail("Error calling [%s]: %s" % (argv_r, e))
+                failed = True
+
+            if self.expect_success is True:
+                self.assertTrue (out == b"")
+                self.assertTrue (chksum != self.hash_file (self.secrets_path))
+                injs = compat_json_loads (self.inblob)
+
+                try:
+                    outjs = compat_json_loads (self.check_output (argv_d))
+                except samba.tests.BlackboxProcessError as e:
+                    self.fail("Error verifying postconditions while calling "
+                              "[%s]: %s" % (argv_d, e))
+
+                if self.expect_equalout is True:
+                    self.assertTrue (outjs == injs)
+
+                return
+
+            if failed is False:
+                self.fail ("Expected test failure did not occur.")
+            self.assertTrue (out is None)
+
+class NetPrimarytrustOkTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    pass
+
+class NetPrimarytrustBadEmptyTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    expect_success = False
+    inblob         = b""
+
+class NetPrimarytrustBadJunkTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Reject non-JSON."""
+    expect_success = False
+    inblob         = b"""this won't parse as json"""
+
+class NetPrimarytrustBadMalformedJSONTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Reject incomplete JSON fragment."""
+    expect_success = False
+    inblob         = b"{\"Computer Name\": \"LOCALADMEMBER"
+
+class NetPrimarytrustOkMissingTimestamps \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    The fields ``Join Time``, ``Password Last Change``, and ``Change Time`` are
+    absent.
+    """
+    expect_success  = True
+    expect_equalout = False
+    inblob          = DOMINFO_BLOB_OK_MISSING_TIMESTAMPS
+
+class NetPrimarytrustOkMissingKrb5Enctypes \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    Blob lacks ``Supported Encryption Types``; the local ones are substituted.
+    """
+    expect_success  = True
+    expect_equalout = False
+    inblob          = DOMINFO_BLOB_OK_MISSING_ENCTYPES
+
+class NetPrimarytrustBadMissingFields1Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Fail on absence of field ``Trust Flags``."""
+    expect_success = False
+    inblob         = DOMINFO_BLOB_BAD_MISSING_FIELDS_TRUSTFLAGS
+
+class NetPrimarytrustBadMissingFields2Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Fail on absence of field ``DNS Domain Info->Domain DNS Name``."""
+    expect_success = False
+    inblob         = DOMINFO_BLOB_BAD_MISSING_FIELDS_DNSDOMAIN
+
+class NetPrimarytrustBadMalformedSID1Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Garbage SID version field."""
+    expect_success = False
+    inblob         = DOMINFO_BLOB_BAD_MALFORMED_SID_1
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 32887066161..3f63dc6968a 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -483,9 +483,10 @@ plantestsuite("samba4.blackbox.client_etypes_strong(ad_dc:client)", "ad_dc:clien
 plantestsuite("samba4.blackbox.net_ads_dns(ad_member:local)", "ad_member:local", [os.path.join(bbdir, "test_net_ads_dns.sh"), '$DC_SERVER', '$DC_USERNAME', '$DC_PASSWORD', '$REALM', '$USERNAME', '$PASSWORD'])
 plantestsuite("samba4.blackbox.samba-tool_ntacl(ad_member:local)", "ad_member:local", [os.path.join(bbdir, "test_samba-tool_ntacl.sh"), '$PREFIX'])
 plantestsuite_loadlist("samba4.rpc.echo against NetBIOS alias", "ad_dc_ntvfs", [valgrindify(smbtorture4), "$LISTOPT", "$LOADLIST", 'ncacn_np:$NETBIOSALIAS', '-U$DOMAIN/$USERNAME%$PASSWORD', 'rpc.echo'])
-# json tests hook into ``chgdcpass'' to make them run in contributor CI on
-# gitlab
-planpythontestsuite("chgdcpass", "samba.tests.blackbox.netads_json")
+planpythontestsuite("ad_member:local", "samba.tests.blackbox.net_json",
+                    py3_compatible=True)
+planpythontestsuite("ad_member:local", "samba.tests.blackbox.netprimarytrust",
+                    py3_compatible=True)
 
 # Tests using the "Simple" NTVFS backend
 for t in ["base.rw1"]:
-- 
2.17.2

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20190110/178cbd56/signature-0001.sig>


More information about the samba-technical mailing list