[PATCH v2] dump and restore domain trust info

Philipp Gesang philipp.gesang at intra2net.com
Wed Jan 16 16:15:42 UTC 2019


Hi,

attached is v2 of the primarytrust dump/undump patchset [0]. It
implements the suggested changes.

CI: https://gitlab.com/samba-team/devel/samba/pipelines/43589034

-- 8< ----------------------------------------------------- >8 --

Main changes since v1:

- Subcommands are named import/export instead of dumpinfo /
  readinfo; explicitly passing --json is no longer required.

- export always includes the passwords, import always accepts
  passwords.

- primarytrust import will abort if domain credentials are
  present. Passing --force overrides the check.

- Include .next_change of the info1 struct in JSON export.

- Unit test previous passwords and the contents of next_change.

- Timestamps in ISO8601 (includes a workaround for the somewhat
  aged glibc used by Gitlab CI).

-- 8< ----------------------------------------------------- >8 --

Review appreciated.

Best regards,
Philipp

[0] Cf. https://lists.samba.org/archive/samba-technical/2019-January/131924.html


-------------- next part --------------
From 5541cb490908ff1bea14d16ade0dd80835352dec 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: [PATCH v2 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 01ac0eaca74f525764e53f294ca28094ca33f161 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: [PATCH v2 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_time_t(),
        - json_add_ntstatus(),
        - json_add_base64(),

        - json_get_int(),
        - json_get_string(),
        - json_get_sid(),
        - json_get_uint64_t(),
        - json_get_uint32_t(),
        - json_get_time_t(),
        - json_get_ntstatus(),
        - 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 | 1282 +++++++++++++++++++++++++----
 lib/audit_logging/audit_logging.h |   48 ++
 2 files changed, 1182 insertions(+), 148 deletions(-)

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index fe2df2c9f8a..c20ccb90b28 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.
@@ -414,235 +489,604 @@ int json_add_int(struct json_object *object, const char *name, const int value)
 }
 
 /*
- * @brief Add a boolean value to a JSON object.
+ * @brief Extract integer from JSON object.
  *
- * Add a boolean value named 'name' to the 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.
- * @param value the value.
+ * @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_add_bool(struct json_object *object,
-		  const char *name,
-		  const bool value)
+int json_get_int(const struct json_object *object, const char *name,
+		 int *value)
 {
-	int ret = 0;
+	const json_t *jstmp = NULL;
 
-	if (json_is_invalid(object)) {
-		DBG_ERR("Unable to add boolean [%s] value [%d], "
-			"target object is invalid\n",
-			name,
-			value);
+	if (object == NULL || json_is_invalid(object)) {
+		DBG_ERR("Unable to retrieve integer value [%s] "
+			"from invalid object\n",
+			name);
 		return JSON_ERROR;
 	}
 
-	ret = json_object_set_new(object->root, name, json_boolean(value));
-	if (ret != 0) {
-		DBG_ERR("Unable to add boolean [%s] value [%d]\n", name, value);
+	if (name == NULL) {
+		DBG_ERR("Unable to retrieve integer value, specified "
+			"field is NULL\n");
+		return JSON_ERROR;
 	}
-	return ret;
+
+	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 a string value to a JSON object.
+ * @brief Add an 64 bit unsigned integer value to a JSON object.
  *
- * Add a string value named 'name' to the 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.
+ * @param name the name of the value.
  * @param value the value.
  *
  * @return 0 the operation was successful
  *        -1 the operation failed
  *
  */
-int json_add_string(struct json_object *object,
-		    const char *name,
-		    const char *value)
+int json_add_uint64(struct json_object *object, const char *name,
+		    const uint64_t value)
 {
-	int ret = 0;
+	uint8_t buf [sizeof (uint64_t)] = { 0 };
 
 	if (json_is_invalid(object)) {
-		DBG_ERR("Unable to add string [%s], target object is invalid\n",
-			name);
+		DBG_ERR("Unable to add uint64_t [%s] value [%"PRIu64"], "
+			"target object is invalid\n",
+			name,
+			value);
 		return JSON_ERROR;
 	}
-	if (value) {
-		json_t *string = json_string(value);
-		if (string == NULL) {
-			DBG_ERR("Unable to add string [%s], "
-				"could not create string object\n",
-				name);
-			return JSON_ERROR;
-		}
-		ret = json_object_set_new(object->root, name, string);
-		if (ret != 0) {
-			json_decref(string);
-			DBG_ERR("Unable to add string [%s]\n", name);
-			return ret;
-		}
-	} else {
-		ret = json_object_set_new(object->root, name, json_null());
-		if (ret != 0) {
-			DBG_ERR("Unable to add null string [%s]\n", name);
-			return ret;
-		}
-	}
-	return ret;
+
+	SBVAL(buf, 0, value);
+
+	return json_add_base64(object, name,
+			       (DATA_BLOB)
+			       { .data = buf
+			       , .length = sizeof (buf) });
 }
 
 /*
- * @brief Assert that the current JSON object is an array.
+ * @brief Extract 64 bit unsigned integer from JSON object.
  *
- * Check that the current object is a JSON array, and if not
- * invalidate the object. We also log an error message as this indicates
- * bug in the calling code.
+ * 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
  *
- * @param object the JSON object to be validated.
  */
-void json_assert_is_array(struct json_object *array) {
+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 (json_is_invalid(array)) {
-		return;
+	if (value == NULL) {
+		DBG_ERR("Unable to store uint64_t value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
 	}
 
-	if (json_is_array(array->root) == false) {
-		DBG_ERR("JSON object is not an array\n");
-		array->valid = false;
-		return;
+	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 a JSON object to a JSON object.
+ * @brief Add an 32 bit unsigned integer value to a JSON object.
  *
- * Add a JSON object named 'name' to the 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.
+ * @param name the name of the value.
  * @param value the value.
  *
  * @return 0 the operation was successful
  *        -1 the operation failed
  *
  */
-int json_add_object(struct json_object *object,
-		    const char *name,
-		    struct json_object *value)
+int json_add_uint32(struct json_object *object, const char *name,
+		    const uint32_t value)
 {
 	int ret = 0;
-	json_t *jv = NULL;
+	json_t *integer = NULL;
 
-	if (value != NULL && json_is_invalid(value)) {
-		DBG_ERR("Invalid JSON object [%s] supplied\n", name);
-		return JSON_ERROR;
-	}
 	if (json_is_invalid(object)) {
-		DBG_ERR("Unable to add object [%s], target object is invalid\n",
-			name);
+		DBG_ERR("Unable to add uint32_t [%s] value [%"PRIu32"], "
+			"target object is invalid\n",
+			name,
+			value);
 		return JSON_ERROR;
 	}
 
-	jv = value == NULL ? json_null() : value->root;
-
-	if (json_is_array(object->root)) {
-		ret = json_array_append_new(object->root, jv);
-	} else if (json_is_object(object->root)) {
-		ret = json_object_set_new(object->root, name, jv);
-	} else {
-		DBG_ERR("Invalid JSON object type\n");
-		ret = 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) {
-		DBG_ERR("Unable to add object [%s]\n", name);
+		json_decref(integer);
+		DBG_ERR("Unable to add uint32_t [%s] value [%"PRIu32"]\n",
+                        name, value);
 	}
+
 	return ret;
 }
 
 /*
- * @brief Add a string to a JSON object, truncating if necessary.
- *
- *
- * Add a string value named 'name' to the json object, the string will be
- * truncated if it is more than len characters long. If len is 0 the value
- * is encoded as a JSON null.
+ * @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.
- * @param value the value.
- * @param len the maximum number of characters to be copied.
+ * @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_add_stringn(struct json_object *object,
-		     const char *name,
-		     const char *value,
-		     const size_t len)
+int json_get_uint32(const struct json_object *object, const char *name,
+		    uint32_t *value)
 {
+	const json_t *jstmp = NULL;
 
-	int ret = 0;
-	if (json_is_invalid(object)) {
-		DBG_ERR("Unable to add string [%s], target object is invalid\n",
+	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 (value != NULL && len > 0) {
-		json_t *string = NULL;
-		char buffer[len+1];
+	if (name == NULL) {
+		DBG_ERR("Unable to retrieve uint32 value, specified "
+			"field is NULL\n");
+		return JSON_ERROR;
+	}
 
-		strncpy(buffer, value, len);
-		buffer[len] = '\0';
+	if (value == NULL) {
+		DBG_ERR("Unable to store uint32 value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
+	}
 
-		string = json_string(buffer);
-		if (string == NULL) {
-			DBG_ERR("Unable to add string [%s], "
-				"could not create string object\n",
-				name);
-			return JSON_ERROR;
-		}
-		ret = json_object_set_new(object->root, name, string);
-		if (ret != 0) {
-			json_decref(string);
-			DBG_ERR("Unable to add string [%s]\n", name);
-			return ret;
-		}
-	} else {
-		ret = json_object_set_new(object->root, name, json_null());
-		if (ret != 0) {
-			DBG_ERR("Unable to add null string [%s]\n", name);
-			return ret;
-		}
+	jstmp = json_object_get(object->root, name);
+	if (jstmp == NULL) {
+		DBG_ERR("JSON object has no key [%s]\n", name);
+		return JSON_ERROR;
 	}
-	return ret;
+
+	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 version object to a JSON object
- *
- * Add a version object to the JSON object
- * 	"version":{"major":1, "minor":0}
- *
- * The version tag is intended to aid the processing of the JSON messages
- * The major version number should change when an attribute is:
- *  - renamed
- *  - removed
- *  - its meaning changes
- *  - its contents change format
- * The minor version should change whenever a new attribute is added and for
- * minor bug fixes to an attributes content.
+ * @brief Add a unix timestamp to a JSON object.
  *
+ * The timestamp is stored as UTC in ISO8601 with seconds precision.
  *
  * @param object the JSON object to be updated.
- * @param major the major version number
- * @param minor the minor version number
+ * @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)
+{
+	struct tm ttmp;
+	char buf [22] = { 0 }; /* -1111-11-11T11:11:11Z\x00 */
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add time_t field [%s] value [%ld], "
+			"target object is invalid\n",
+			name, t);
+		return JSON_ERROR;
+	}
+
+	errno = 0;
+	if (gmtime_r(&t, &ttmp) == NULL) {
+		DBG_ERR("Unable to add time_t field [%s] value [%ld], "
+			"conversion to UTC failed: %s\n",
+			name, t, strerror(errno));
+		return JSON_ERROR;
+	}
+
+	if (strftime(buf, sizeof(buf), "%FT%TZ", &ttmp) == 0) {
+		DBG_ERR("Unable to add time_t field [%s] value [%ld], "
+			"failed to format timestamp\n",
+			name, t);
+		return JSON_ERROR;
+	}
+
+	return json_add_string(object, name, buf);
+}
+
+/*
+ * @brief Add a boolean value to a JSON object.
+ *
+ * Add a boolean value named 'name' to the json object.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_bool(struct json_object *object,
+		  const char *name,
+		  const bool value)
+{
+	int ret = 0;
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add boolean [%s] value [%d], "
+			"target object is invalid\n",
+			name,
+			value);
+		return JSON_ERROR;
+	}
+
+	ret = json_object_set_new(object->root, name, json_boolean(value));
+	if (ret != 0) {
+		DBG_ERR("Unable to add boolean [%s] value [%d]\n", name, value);
+	}
+	return ret;
+}
+
+/*
+ * @brief Add a string value to a JSON object.
+ *
+ * Add a string value named 'name' to the json object.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_string(struct json_object *object,
+		    const char *name,
+		    const char *value)
+{
+	int ret = 0;
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add string [%s], target object is invalid\n",
+			name);
+		return JSON_ERROR;
+	}
+	if (value) {
+		json_t *string = json_string(value);
+		if (string == NULL) {
+			DBG_ERR("Unable to add string [%s], "
+				"could not create string object\n",
+				name);
+			return JSON_ERROR;
+		}
+		ret = json_object_set_new(object->root, name, string);
+		if (ret != 0) {
+			json_decref(string);
+			DBG_ERR("Unable to add string [%s]\n", name);
+			return ret;
+		}
+	} else {
+		ret = json_object_set_new(object->root, name, json_null());
+		if (ret != 0) {
+			DBG_ERR("Unable to add null string [%s]\n", name);
+			return ret;
+		}
+	}
+	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.
+ *
+ * Check that the current object is a JSON array, and if not
+ * invalidate the object. We also log an error message as this indicates
+ * bug in the calling code.
+ *
+ * @param object the JSON object to be validated.
+ */
+void json_assert_is_array(struct json_object *array) {
+
+	if (json_is_invalid(array)) {
+		return;
+	}
+
+	if (json_is_array(array->root) == false) {
+		DBG_ERR("JSON object is not an array\n");
+		array->valid = false;
+		return;
+	}
+}
+
+/*
+ * @brief Add a JSON object to a JSON object.
+ *
+ * Add a JSON object named 'name' to the json object.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_object(struct json_object *object,
+		    const char *name,
+		    struct json_object *value)
+{
+	int ret = 0;
+	json_t *jv = NULL;
+
+	if (value != NULL && json_is_invalid(value)) {
+		DBG_ERR("Invalid JSON object [%s] supplied\n", name);
+		return JSON_ERROR;
+	}
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add object [%s], target object is invalid\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	jv = value == NULL ? json_null() : value->root;
+
+	if (json_is_array(object->root)) {
+		ret = json_array_append_new(object->root, jv);
+	} else if (json_is_object(object->root)) {
+		ret = json_object_set_new(object->root, name, jv);
+	} else {
+		DBG_ERR("Invalid JSON object type\n");
+		ret = JSON_ERROR;
+	}
+	if (ret != 0) {
+		DBG_ERR("Unable to add object [%s]\n", name);
+	}
+	return ret;
+}
+
+/*
+ * @brief Add a string to a JSON object, truncating if necessary.
+ *
+ *
+ * Add a string value named 'name' to the json object, the string will be
+ * truncated if it is more than len characters long. If len is 0 the value
+ * is encoded as a JSON null.
+ *
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ * @param len the maximum number of characters to be copied.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_stringn(struct json_object *object,
+		     const char *name,
+		     const char *value,
+		     const size_t len)
+{
+
+	int ret = 0;
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add string [%s], target object is invalid\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	if (value != NULL && len > 0) {
+		json_t *string = NULL;
+		char buffer[len+1];
+
+		strncpy(buffer, value, len);
+		buffer[len] = '\0';
+
+		string = json_string(buffer);
+		if (string == NULL) {
+			DBG_ERR("Unable to add string [%s], "
+				"could not create string object\n",
+				name);
+			return JSON_ERROR;
+		}
+		ret = json_object_set_new(object->root, name, string);
+		if (ret != 0) {
+			json_decref(string);
+			DBG_ERR("Unable to add string [%s]\n", name);
+			return ret;
+		}
+	} else {
+		ret = json_object_set_new(object->root, name, json_null());
+		if (ret != 0) {
+			DBG_ERR("Unable to add null string [%s]\n", name);
+			return ret;
+		}
+	}
+	return ret;
+}
+
+/*
+ * @brief Add a version object to a JSON object
+ *
+ * Add a version object to the JSON object
+ * 	"version":{"major":1, "minor":0}
+ *
+ * The version tag is intended to aid the processing of the JSON messages
+ * The major version number should change when an attribute is:
+ *  - renamed
+ *  - removed
+ *  - its meaning changes
+ *  - its contents change format
+ * The minor version should change whenever a new attribute is added and for
+ * minor bug fixes to an attributes content.
+ *
+ *
+ * @param object the JSON object to be updated.
+ * @param major the major version number
+ * @param minor the minor version number
  *
  * @return 0 the operation was successful
  *        -1 the operation failed
@@ -839,15 +1283,91 @@ int json_add_sid(struct json_object *object,
 	} else {
 		struct dom_sid_buf sid_buf;
 
-		ret = json_add_string(
-			object, name, dom_sid_str_buf(sid, &sid_buf));
+		ret = json_add_string(
+			object, name, dom_sid_str_buf(sid, &sid_buf));
+		if (ret != 0) {
+			DBG_ERR("Unable to add SID [%s] value [%s]\n",
+				name,
+				sid_buf.buf);
+			return ret;
+		}
+	}
+	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 SID [%s] value [%s]\n",
-				name,
-				sid_buf.buf);
-			return ret;
+			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;
 }
 
@@ -907,7 +1427,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 +1473,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 +1606,418 @@ 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 an ISO8601 timestamp from a JSON object key and return it as
+ * time_t. To compensate for older (pre-2015) glibc, there is a fallback
+ * logic to accept UTC timestamps in traling-'Z' notation. However, the colon
+ * notation ("+10:30") will not work properly on such old systems as the
+ * minute part ends up being ignored.
+ *
+ * @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;
+	TALLOC_CTX *ctx;
+	time_t ttmp, gmtoff;
+	struct tm tmtmp = { 0 };
+	const char *raw = 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;
+	}
+
+	ret = json_get_string(ctx, object, name, &raw);
+	if (ret != 0 || raw == NULL) {
+		DBG_ERR("Failed to get timestamp value from JSON object\n");
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	if (strptime(raw, "%FT%T%z", &tmtmp) == NULL) {
+		DBG_ERR("Failed to process value [%s] of key [%s] as ISO8601 "
+			"timestamp\n", raw, name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	TALLOC_FREE(ctx);
+
+	errno = 0;
+	gmtoff = tmtmp.tm_gmtoff; /* timegm() mutates its argument */
+	ttmp = timegm(&tmtmp);
+	if (ttmp == (time_t)-1) {
+		DBG_ERR("Failed to convert value [%s] of key [%s] to UNIX "
+			"timestamp: %s\n", raw, name, strerror(errno));
+		return JSON_ERROR;
+	}
+
+	*t = ttmp + gmtoff;
+
+	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;
+}
+
+/*
+ * @brief Add an NT status code.
+ *
+ * Status codes are converted to their symbolic names.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param nt_code the status code to add
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_ntstatus(struct json_object *object, const char *name,
+		      const NTSTATUS nt_code)
+{
+	const char *str;
+
+	if (object == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL"
+			"object\n");
+		return JSON_ERROR;
+	}
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add NT status field [%s] with value "
+			"[%"PRIu32"], target object is invalid\n",
+			name, NT_STATUS_V(nt_code));
+		return JSON_ERROR;
+	}
+
+	str = nt_errstr(nt_code);
+	if (str == NULL) {
+		DBG_ERR("Failed to look up name of NT status code [%"PRIu32"]"
+			"for value [%s]\n", NT_STATUS_V(nt_code), name);
+		return JSON_ERROR;
+	}
+
+	return json_add_string(object, name, str);
+}
+
+/*
+ * @brief Get an NT status code from object.
+ *
+ * Retrieve status code contained in a string member of the object.
+ *
+ * @param object The JSON object to access.
+ * @param name Field name to access.
+ * @param nt_code Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_ntstatus(const struct json_object *object,
+		      const char *name,
+		      NTSTATUS *nt_code)
+{
+	int ret = 0;
+	const char *data = NULL;
+	TALLOC_CTX *ctx = NULL;
+
+	/* object and name are checked in json_get_string() */
+	if (nt_code == 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 [%s] from JSON object "
+			"for conversion to NT status", name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	/*
+	 * The conversion does not signal failure; unknown codes end up as
+	 * NT_STATUS_UNSUCCESSFUL.
+	 */
+        *nt_code = nt_status_string_to_code(data);;
+	TALLOC_FREE(ctx);
+
+	return 0;
+}
+
 #endif
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
index 86e9134a86a..7e4e6c9ce0a 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,39 @@ _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_ int json_add_ntstatus(struct json_object *object,
+					   const char *name,
+					   const NTSTATUS nt_code);
+_WARN_UNUSED_RESULT_ int json_get_ntstatus(const struct json_object *object,
+					   const char *name, NTSTATUS *nt_code);
+
+_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 +137,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 e62f2884ac831e4fcc24c576e259d80b273d4ff0 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: [PATCH v2 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_add_ntstatus(), json_get_ntstatus(),
    - json_get_base64(), and json_add_base64().

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

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index c20ccb90b28..a38a5d73d09 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -1740,10 +1740,19 @@ int json_get_time_t(const struct json_object *object,
 	}
 
 	if (strptime(raw, "%FT%T%z", &tmtmp) == NULL) {
-		DBG_ERR("Failed to process value [%s] of key [%s] as ISO8601 "
-			"timestamp\n", raw, name);
-		TALLOC_FREE(ctx);
-		return JSON_ERROR;
+		const char *tz;
+
+		/*
+		 * Work around old glibc that can't handle 'Z' for zero
+		 * UTC offset.
+		 */
+		if ((tz = strptime(raw, "%FT%T", &tmtmp)) == NULL ||
+		    *tz != 'Z') {
+			DBG_ERR("Failed to process value [%s] of key [%s] as "
+				"ISO8601 timestamp\n", raw, name);
+			TALLOC_FREE(ctx);
+			return JSON_ERROR;
+		}
 	}
 
 	TALLOC_FREE(ctx);
diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c
index acd2a4f697f..f3f437f2366 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,346 @@ 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_unix", 0);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_time_t(&object, "t_now", time (NULL));
+	assert_int_equal(0, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	/* EOVERFLOW */
+	rc = json_add_time_t(&object, "t_heat_death", LONG_LONG_MAX);
+	assert_int_equal(JSON_ERROR, 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_now, t;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	t_now = time (NULL);
+
+	rc = json_add_time_t(&object, "t_unix", 0);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_time_t(&object, "t_now", t_now);
+	assert_int_equal(0, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	/* EOVERFLOW */
+	rc = json_add_time_t(&object, "t_heat_death", LONG_LONG_MAX);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	/* EOVERFLOW */
+	rc = json_add_time_t(&object, "t_big_bang", LONG_LONG_MIN);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	rc = json_get_time_t(&object, "t_unix", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, 0);
+
+	rc = json_get_time_t(&object, "t_now", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, t_now);
+
+	rc = json_get_time_t(&object, "t_heat_death", &t);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+}
+
+static void test_json_get_time_t_strings(void **state)
+{
+	struct json_object object;
+	int rc;
+	time_t t;
+	const char *testdata =
+		"{\"ok1\": \"1111-11-11T11:11:11Z\""
+		",\"ok2\": \"3333-03-03T03:33:33+0300\""
+		",\"ok3\": \"1970-01-01T00:00:00+0000\""
+		",\"ok4\": \"1970-01-01T00:00:00+0042\""
+		",\"bad1\": 42"
+		",\"bad2\": \"1111-42-11T11:11:11Z\""
+		",\"bad3\": \"1111-11-42T11:11:11Z\""
+		",\"bad4\": \"1111-11-11\""
+		",\"bad5\": \"1111-11-11 11:11:11Z\""
+		"}";
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testdata);
+	assert_int_equal(0, rc);
+	assert_false(json_is_invalid(&object));
+
+	rc = json_get_time_t(&object, "ok1", &t);
+	assert_int_equal(0, rc);
+
+	rc = json_get_time_t(&object, "ok2", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, 43017460413);
+
+	rc = json_get_time_t(&object, "ok3", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, 0);
+
+	rc = json_get_time_t(&object, "ok4", &t);
+	assert_int_equal(0, rc);
+	assert_int_equal(t, 42 * 60);
+
+	rc = json_get_time_t(&object, "bad1", &t);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_time_t(&object, "bad2", &t);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_time_t(&object, "bad3", &t);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_time_t(&object, "bad4", &t);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_time_t(&object, "bad5", &t);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+}
+
+static void test_json_add_ntstatus(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	rc = json_add_ntstatus(NULL, "yikes", NT_STATUS_OK);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(0, json_object_size(object.root));
+
+	rc = json_add_ntstatus(&object, "ok", NT_STATUS_OK);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_ntstatus(&object, "ok", NT_STATUS(UINT32_MAX));
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	json_free(&object);
+}
+
+static void test_json_get_ntstatus(void **state)
+{
+	struct json_object object;
+	int rc;
+	NTSTATUS status = NT_STATUS_OK;
+	const char *testdata =
+		"{\"ok1\": \"NT_STATUS_OK\""
+		",\"ok2\": \"NT_STATUS_NO_MEMORY\""
+		",\"bad1\": 42"
+		",\"bad2\": \"NT_STATUS_NOPROBLEMO\""
+		"}";
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testdata);
+	assert_int_equal(0, rc);
+	assert_false(json_is_invalid(&object));
+
+	rc = json_get_ntstatus(&object, "enoent", &status);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_ntstatus(NULL, "ok1", &status);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_ntstatus(&object, "ok1", &status);
+	assert_int_equal(0, rc);
+	assert_true(NT_STATUS_IS_OK(status));
+
+	rc = json_get_ntstatus(&object, "ok2", &status);
+	assert_int_equal(0, rc);
+	assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY));
+
+	rc = json_get_ntstatus(&object, "bad1", &status);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_ntstatus(&object, "bad2", &status);
+	assert_int_equal(0, rc);
+	assert_true(NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL));
+}
+
 static void test_json_add_bool(void **state)
 {
 	struct json_object object;
@@ -173,6 +519,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 +958,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 +1029,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 +1180,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 +1538,18 @@ 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_get_time_t_strings),
+		cmocka_unit_test(test_json_add_ntstatus),
+		cmocka_unit_test(test_json_get_ntstatus),
 		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 +1557,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 05af62eeb05ab27fecb1ad8fa12b5d80a545586a 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: [PATCH v2 04/10] s3: net: add primarytrust subcommand `export'

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 import
functionality will arrive in a separate patch.)

As with ``dumpinfo'', 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                |   2 +
 source3/passdb/machine_account_secrets.c | 304 +++++++++++++++++++++++
 source3/utils/net.c                      |  67 +++++
 3 files changed, 373 insertions(+)

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 24ae5bd0664..14a3c58824b 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -119,6 +119,8 @@ 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);
 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..ace652dc751 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,306 @@ 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);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	return true;
+failure:
+	json_free(&jsobj);
+	return false;
+}
+
+static bool json_add_secrets_domain_info1_change
+			(struct json_object *jsdst,
+			 const char *key,
+			 const struct secrets_domain_info1_change *next)
+{
+	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_ntstatus(&jsobj, "Local Status", next->local_status);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_ntstatus(&jsobj, "Remote Status", next->remote_status);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_time_t(&jsobj, "Change Time",
+			      nt_time_to_unix(next->change_time));
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Change Server", next->change_server);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (next->password != NULL &&
+	    !json_add_secrets_domain_info1_password(&jsobj,
+						    "Password",
+						    next->password))
+	{
+		goto failure;
+	}
+
+	ret = json_add_object(jsdst, key, &jsobj);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	return true;
+failure:
+	json_free(&jsobj);
+	return false;
+}
+
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1)
+{
+	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;
+	}
+
+	if (info1->next_change != NULL &&
+	    !json_add_secrets_domain_info1_change
+		(&jsinfo, "Next Change ", info1->next_change))
+	{
+		goto failure;
+	}
+
+	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)
+{
+	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..721106c7a1a 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -56,6 +56,10 @@
 #include "utils/net_afs.h"
 #endif
 
+#ifdef HAVE_JANSSON
+#include "audit_logging.h"
+#endif /* [HAVE_JANSSON] */
+
 /***********************************************************************/
 /* end of internationalization section                                 */
 /***********************************************************************/
@@ -94,6 +98,59 @@ static void set_line_buffering(FILE *f)
 	setvbuf(f, NULL, _IOLBF, 0);
 }
 
+#ifdef HAVE_JANSSON
+static int net_primarytrust_export(struct net_context *c, int argc,
+				   const char **argv)
+{
+	int role = lp_server_role();
+	const char *domain = lp_workgroup();
+	struct secrets_domain_info1 *info = NULL;
+	char *str = NULL;
+	NTSTATUS status;
+
+	if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+		d_printf(_("net primarytrust export is only supported "
+			 "on a DOMAIN_MEMBER for now.\n"));
+		return 1;
+	}
+
+	status = secrets_fetch_or_upgrade_domain_info(domain,
+						      talloc_tos(),
+						      &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		d_fprintf(stderr,
+			  _("Unable to fetch the information for domain[%s] "
+			  "in the secrets database.\n"),
+			  domain);
+		return 1;
+	}
+
+	str = secrets_domain_info_json(talloc_tos(), info);
+	if (str == NULL) {
+		d_fprintf(stderr, "secrets_domain_info_json() failed.\n");
+		return 1;
+	}
+
+	d_printf("%s", str);
+	if (!c->opt_force) {
+		d_fprintf(stderr,
+			  _("The password values are only included using "
+			    "-f flag.\n"));
+	}
+
+	TALLOC_FREE(info);
+	return 0;
+}
+#else /* [HAVE_JANSSON] */
+static int net_primarytrust_export(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)
 {
@@ -165,6 +222,16 @@ 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.")
 		},
+		{
+			"export",
+			net_primarytrust_export,
+			NET_TRANSPORT_LOCAL,
+			N_("Export the workstation trust"),
+			N_("  net [options] primarytrust export'\n"
+			   "    Export the workstation trust in secrets.tdb "
+			   "as JSON.\n"
+			   "    Requires the -f flag to include the password values.")
+		},
 		{NULL, NULL, 0, NULL, NULL}
 	};
 
-- 
2.17.2


From 61264b81e84ad4403319951c29fc4efccf53ed7e 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: [PATCH v2 05/10] s3: net: add primarytrust subcommand `import`

Implement the inverse operation of `net primarytrust export'
to restore an earlier domain info dump. The import accepts input
of the same JSON structure with certain components being
optional: `Join Time', `Supported Encryption Types',
`Password Last Change', `Next Change' `Change Time', as
well as the three passwords.

By default, `import' will abort if member credentials already
exist. Passing `--force' overrides this safety guard.

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

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 14a3c58824b..7dcf9778833 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -124,6 +124,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 overwrite);
 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 ace652dc751..52fffebcdba 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -942,6 +942,171 @@ 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");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_base64(ctx,
+			      &jspw,
+			      "Cleartext Blob",
+			      &pw->cleartext_blob);
+	if (ret != 0) {
+		DBG_ERR("password: Field \"Cleartext Blob\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	*dst = pw;
+
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS json_get_secrets_domain_info1_change
+			(TALLOC_CTX *ctx,
+			 const struct json_object *jssrc,
+			 const char *key,
+			 struct secrets_domain_info1_change **dst)
+{
+	int ret;
+	time_t tmp_time;
+	struct secrets_domain_info1_change *next;
+	struct json_object jsnext;
+	NTSTATUS status;
+
+	if (json_is_invalid(jssrc)) {
+		DBG_ERR("next change: refusing to read from invalid JSON "
+			"object\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (key == NULL) {
+		DBG_ERR("next change: refusing to access NULL key\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (dst == NULL) {
+		DBG_ERR("next change: refusing to write to NULL address\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	next = talloc_zero_size(ctx, sizeof(*next));
+	if (next == NULL) {
+		DBG_ERR("next change: error allocating memory for domain "
+			"password\n");
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	jsnext = json_get_object(discard_const_p(struct json_object, jssrc),
+				 key);
+	if (json_is_invalid(&jsnext)) {
+		DBG_ERR("next change: failed to retrieve field [%s] from "
+			"domain info\n", key);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_ntstatus(&jsnext, "Local Status", &next->local_status);
+	if (ret != 0) {
+		DBG_DEBUG("next change: Field \"Local Status\" missing or "
+			  "invalid.");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_ntstatus(&jsnext, "Remote Status", &next->remote_status);
+	if (ret != 0) {
+		DBG_DEBUG("next change: Field \"Remote Status\" missing or "
+			  "invalid.");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_time_t(&jsnext, "Change Time", &tmp_time);
+	if (ret != 0) {
+		DBG_DEBUG("next change: Field \"Change Time\" missing or "
+			  "invalid.");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+	unix_to_nt_time(&next->change_time, tmp_time);
+
+	ret = json_get_string(ctx,
+			      &jsnext,
+			      "Change Server",
+			      &next->change_server);
+	if (ret != 0) {
+		DBG_ERR("next change: Field \"Change Server\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (json_has_key (&jsnext, "Password")) {
+		status = json_get_secrets_domain_info1_password(ctx,
+								&jsnext,
+								"Password",
+								&next->password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("next change: Optional field \"Password\" "
+				"invalid\n");
+			return status;;
+		}
+	}
+
+	*dst = next;
+
+	return NT_STATUS_OK;
+}
+
 char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
 			       const struct secrets_domain_info1 *info1)
 {
@@ -1601,6 +1766,340 @@ 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)
+{
+	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, "Next Change")) {
+		status = json_get_secrets_domain_info1_change
+				(mem_ctx, jsdata, "Next Change",
+				 &info1->next_change);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Next Change\" invalid.\n");
+			return status;
+		}
+	}
+
+	if (json_has_key (jsdata, "Password")) {
+		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")) {
+		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")) {
+		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)
+{
+	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 overwrite)
+{
+	int ret;
+	struct secrets_domain_info1 *info1 = NULL;
+	struct db_context *db;
+	NTSTATUS status;
+
+	if (!overwrite) {
+		status = secrets_fetch_domain_info(domain, mem_ctx, &info1);
+		if (info1 != NULL) {
+			TALLOC_FREE(info1);
+			info1 = NULL;
+		}
+		if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+			DBG_ERR("Primary trust for domain %s exists; "
+				"use --force to overwrite.\n",
+				domain);
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+	}
+
+	status = secrets_json_get_domain_info (mem_ctx, jsdata, &info1);
+	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 721106c7a1a..21a92040da5 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"
@@ -141,6 +142,47 @@ static int net_primarytrust_export(struct net_context *c, int argc,
 	TALLOC_FREE(info);
 	return 0;
 }
+
+static int net_primarytrust_import(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 overwrite = c->opt_force;
+
+	if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+		d_printf(_("net primarytrust import 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,
+					      overwrite);
+	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_export(struct net_context *_c, int _argc,
 				   const char **_argv)
@@ -149,6 +191,14 @@ static int net_primarytrust_export(struct net_context *_c, int _argc,
 
 	return -1;
 }
+
+static int net_primarytrust_import(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,
@@ -232,6 +282,16 @@ static int net_primarytrust(struct net_context *c, int argc, const char **argv)
 			   "as JSON.\n"
 			   "    Requires the -f flag to include the password values.")
 		},
+		{
+			"import",
+			net_primarytrust_import,
+			NET_TRANSPORT_LOCAL,
+			N_("Import the workstation trust"),
+			N_("  net [options] primarytrust import'\n"
+			   "    Import the workstation trust from JSON dump.\n"
+			   "    The -f flag is required to overwrite existing "
+			   "values.")
+		},
 		{NULL, NULL, 0, NULL, NULL}
 	};
 
-- 
2.17.2


From bc92583ec7a3e2b0860e99453a76d1cb01922e1a 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: [PATCH v2 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 tool 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 7e29858fc25139babbedc0632e25ecbc8548a2de 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: [PATCH v2 07/10] testprogs: blackbox test `net primarytrust
 {ex,im}port'

Test wellformedness of --json output and whether `net
primarytrust import' 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 f15b40178803f7e86c6bdadc559c80fffc59dfa0 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: [PATCH v2 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 f904499b90b..d9c03237e20 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -59,6 +59,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)])
 
@@ -447,6 +451,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 9c0d8422906f188a4319558dc2bcc9ca880e8042 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: [PATCH v2 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 | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py
index d9c03237e20..ac75eda5d27 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -18,6 +18,8 @@
 
 """Samba Python tests."""
 from __future__ import print_function
+
+import hashlib
 import os
 import tempfile
 import warnings
@@ -493,6 +495,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 4071dfd42630228728ce7202711597643ebb6e34 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: [PATCH v2 10/10] tests/blackbox: cover `net primarytrust {ex,im}port
 in tests'

Run the existing JSON tests on `net primarytrust export' 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 import'. 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   | 463 ++++++++++++++++++
 source4/selftest/tests.py                     |   7 +-
 3 files changed, 509 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..d8531490312 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_PrimarytrustExportTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_PTRUST # does not actually need --json
+    subcmd   = "export"
+    matching = False
diff --git a/python/samba/tests/blackbox/netprimarytrust.py b/python/samba/tests/blackbox/netprimarytrust.py
new file mode 100644
index 00000000000..41a72d540cf
--- /dev/null
+++ b/python/samba/tests/blackbox/netprimarytrust.py
@@ -0,0 +1,463 @@
+# 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.samba3 import param as s3param
+from samba.compat import get_string
+
+COMMAND              = "bin/net primarytrust"
+CHILD_TIMEOUT        = 3 # s, don't delay tests expecting input
+SECRETS_PATH_FMT     = "%s/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": "2011-11-11T11:11:11Z",
+      "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": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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_NEXT_CHANGE = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "2011-11-11T11:11:11Z",
+      "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": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Next Change ": {
+        "Local Status": "NT_STATUS_OK",
+        "Remote Status": "NT_STATUS_NOT_COMMITTED",
+        "Change Time": "2019-11-11T11:11:11Z",
+        "Change Server": "ADDC",
+        "Password": {
+          "Change Time": "2019-11-11T11:11:11Z",
+          "Change Server": "ADDC",
+          "Cleartext Blob": "QwBvAG0AbQBlACAAbAAZIGUAbgBjAHIAZQAsACAAdQBuAGUAIABmAG8AaQBzACAAZgBpAHgA6QBlACAALwAgAEMAZQAgAHEAdQAZIG8AbgAgAGYAYQBpAHQALAAgAG4AZQAgAHMAGSBlAGYAZgBhAGMAZQAgAHAAYQBzACAALwAgAEUAdAAgAG4AbwBzACAAbQBvAHQAcwAgAHMAbwBuAHQAIABpAG0AcAByAGkAbQDpAHMAIAAvACAARABhAG4AcwAgAGwAGSBhAGkAcgAgAHEAdQBpACwAIABwAGEAcwBzAGEAaQB0ACAAcABhAHIAIABsAOAAIAAKAA==" } },
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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_MULTIPASS = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "2018-08-08T08:08:08Z",
+      "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": "2012-12-12T12:12:12Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2011-11-11T11:11:11Z",
+        "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=" },
+      "Old Password": {
+        "Change Time": "2010-10-10T10:10:10Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "DwD9ABkAUgDRADIAxwB+AO0AYAHaAKwgMAA2AOkAeQBQADwAfQElAHcApACwAG8ArgAPAD8AKQDGAkoARgA8AM4AqwDIALMAAgDoAHIAqgB8ACgAzABrAG4AoQDdADcA1QB+AecACQD8AM0AEyDTADkgMwAAAEoA3ABCAOkAswAZACUAtgDHABkA8QBsAPEAsgBqABoAaABKABQgSQBYAEkABwA6IPIAaAByADogJABmAKEACAAPACAgUwHzALQAxgCmAHsAJwDMAMkAkgHkAPkATAAgIBwAPQDaACkA2wBCAMAAQgC2ABQAZwArAMYCEABhAQcAPwAgIOoAOiAvABkgFQC6AGcA1ABcAGkAGiAmIN0AGgBDAMIAvAD2AMcA" },
+      "Older Password": {
+        "Change Time": "2009-09-09T09:09:09Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "SgAAAD4A/QDfAPcAOADsAEYAHACkALgAYABbAPgA3ABgAH0B3AB2AN4AHCDUABsAtQAgIKEA1ADAAB0ApQBgAX0ARgAcAFMB9ACxAKIAfQDZAM8A1gB7ABggFwBYALMAJiCSAWkAYgDBAMUATgDxAGkAAQBwADMAAABtAFQAGSB5AHYAxwBpAAsAwgArAPoAwwDLABcApwDxAHoAsABDADog4QChACIADwDIANkAIQDnABwgvwAUIPYAKADuAGMA5QAfABoAEgD8APkA0QCgAB0gCwB+AE0A5gAdANgAGSDLAF4AYwAqAJIBCgDQABkgdQAhIH4ByQBTAOAAoACyADYAOQAfANgAYAFXAFkAbAAVAC0AHSDZAGwAugALAA==" },
+      "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-3-5-8-13-21-34-55-89",
+        "Domain GUID": "00000000-1111-2222-3333-444444444444" } }
+"""
+
+DOMINFO_BLOB_OK_MISSING_ENCTYPES = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "2011-11-11T11:11:11Z",
+      "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": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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": "2011-11-11T11:11:11Z",
+      "Secure Channel Type": 2,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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_INCOMPLETE_PASSWORD = b"""
+    { "Reserved Flags": "AAAAAAAAAAA=",
+      "Join Time": "2011-11-11T11:11:11Z",
+      "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": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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": "2011-11-11T11:11:11Z",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+      "Password Last Change": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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": "2011-11-11T11:11:11Z",
+      "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": "2018-11-11T13:37:42Z",
+      "Password Changes": "AQAAAAAAAAA=",
+      "Password": {
+        "Change Time": "2018-11-11T11:11:11Z",
+        "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."""
+        force_import    = True # append --force to command line
+        expect_success  = True
+        expect_equalout = True
+        inblob          = DOMINFO_BLOB_OK
+        backupfile      = None
+        delete_secrets  = False
+        secrets_path    = None
+
+        def setUp(self):
+            """
+            Save a backup of the secrets.tdb that the main test manipulates.
+            """
+            super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+                .setUp()
+
+            s3conf = s3param.get_context()
+            s3conf.load(self.get_loadparm().configfile)
+            private = s3conf.get("private dir")
+            assert private is not None
+
+            self.secrets_path = SECRETS_PATH_FMT % private
+
+            assert self.check_output("tdbbackup \"%s\"" % self.secrets_path) == b""
+            self.backupfile = "%s.bak" % self.secrets_path
+            if self.delete_secrets is True:
+                os.remove (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_import (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: if there were existing credentials, then the
+            file contents must have changed. Otherwise, a new one must have
+            been created.
+            """
+            argv_r = "%s import%s" % (COMMAND,
+                                      self.force_import and " --force" or "")
+            argv_d = "%s export" % COMMAND
+            failed = False
+            out    = None
+            chksum = None
+            if self.delete_secrets is False:
+                # Secrets must be obtainable as json; hash secrets.tdb.
+                outpre = compat_json_loads (self.check_output (argv_d))
+                chksum = self.hash_file (self.secrets_path)
+
+            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"")
+                if chksum is not None:
+                    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 NetPrimarytrustOkNextChangeTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    inblob          = DOMINFO_BLOB_OK_NEXT_CHANGE
+    expect_equalout = False # info1->next_change is temporary
+
+class NetPrimarytrustOkMUltipassTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    inblob          = DOMINFO_BLOB_OK_MULTIPASS
+
+class NetPrimarytrustBadImportNoForceTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    Samba is joined domain member during the test so ``net primarytrust
+    import`` must not succeed without --force.
+    """
+    expect_success  = False
+    force_import    = False
+
+class NetPrimarytrustOkImportNoForceTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    ``secrets.tdb`` has been erased so ``net primarytrust import`` must succeed
+    without --force.
+    """
+    expect_success  = True
+    force_import    = False
+    delete_secrets  = True
+
+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 NetPrimarytrustBadMissingFields3Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    Fail on absence of field ``Password->Change Server``. Passwords are
+    optional but they have mandatory fields.
+    """
+    expect_success  = False
+    inblob          = DOMINFO_BLOB_BAD_INCOMPLETE_PASSWORD
+
+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 a1f38424af7..2dfc7022766 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/20190116/dd859362/signature.sig>


More information about the samba-technical mailing list